XSLT:在排序之前替换

时间:2009-10-08 15:55:41

标签: xslt

我正在使用XSLT 1.0(所以我不能使用replace()函数),我需要在使用该字符串进行排序之前在字符串中进行替换。简而言之,我的XML文档如下所示:

<root>
    <item>
        <name>ABC</name>
        <rating>good</rating>
    </item>
    <item>
        <name>BCD</name>
        <rating>3</rating>
    </item>
</root>

然后我需要将'good'替换为'4',以便使用sort()函数打印按评级排序的整个项目列表。由于我使用的是XSLT 1.0,我使用此模板进行替换:

<xsl:template name="string-replace">
  <xsl:param name="subject"     select="''" />
  <xsl:param name="search"      select="''" />
  <xsl:param name="replacement" select="''" />
  <xsl:param name="global"      select="false()" />

  <xsl:choose>
    <xsl:when test="contains($subject, $search)">
      <xsl:value-of select="substring-before($subject, $search)" />
      <xsl:value-of select="$replacement" />
      <xsl:variable name="rest" select="substring-after($subject, $search)" />
      <xsl:choose>
        <xsl:when test="$global">
          <xsl:call-template name="string-replace">
            <xsl:with-param name="subject"     select="$rest" />
            <xsl:with-param name="search"      select="$search" />
            <xsl:with-param name="replacement" select="$replacement" />
            <xsl:with-param name="global"      select="$global" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$rest" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$subject" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

这个模板工作正常,但问题是它总是打印值,(即总是在我打印模板时打印的东西)。因此,在这种情况下,此模板无用,因为我需要修改“评级”值,然后按评级对项目进行排序,最后打印出来。

提前致谢!

PS:解决方法是使用两个不同的XSLT,但由于多种原因,在这种情况下我无法做到。

3 个答案:

答案 0 :(得分:2)

你可以这样做:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="xml" encoding="utf-8" />

  <xsl:template match="/root">
    <xsl:for-each select="item">
      <!-- be sure to include every possible value of <rating>! -->
      <xsl:sort select="
        concat(
          substring('4', 1, rating = 'good' ),
          substring('3', 1, rating = 'medioce' ),
          substring('2', 1, rating = 'bad' ),
          substring('1', 1, rating = 'abyssmal' ),
          substring('4', 1, rating = '4' ),
          substring('3', 1, rating = '3' ),
          substring('2', 1, rating = '2' ),
          substring('1', 1, rating = '1' )
        )
      " order="descending" />
      <xsl:copy-of select="." />
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

输入:

<root>
  <item>
    <name>ABC</name>
    <rating>abyssmal</rating>
  </item>
  <item>
    <name>GEH</name>
    <rating>bad</rating>
  </item>
  <item>
    <name>DEF</name>
    <rating>good</rating>
  </item>
  <item>
    <name>IJK</name>
    <rating>medioce</rating>
  </item>
</root>

我明白了:

<item>
  <name>DEF</name>
  <rating>good</rating>
</item>
<item>
  <name>IJK</name>
  <rating>medioce</rating>
</item>
<item>
  <name>GEH</name>
  <rating>bad</rating>
</item>
<item>
  <name>ABC</name>
  <rating>abyssmal</rating>
</item>

要获得解释,see my other answer。 ; - )


修改

根据OP的评论改变了解决方案:

  

我需要使用评级(使用   字符串替换为整数分数),3   时间:

     
      
  1. 使用评分
  2. 使用<xsl:key ...制作密钥   
  3. 使用评级
  4. 对项目进行排序   
  5. 打印评级。
  6.         

    在每一步中我都应该使用评级   替换之后(即使用整数   分数)。我已经做了重复   concat(...)代码3次,但和你一样   可以看出这不太酷......我   我想找个办法放置   concat (...)一次,不需要   重复一遍。

以下XSLT 1.0解决方案满足所有这些要求:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:tmp="http://tempuri.org/"
  exclude-result-prefixes="tmp"
>
  <xsl:output method="xml" encoding="utf-8" />

  <!-- prepare a list of possible ratings for iteration -->
  <tmp:ratings>
    <tmp:rating num="1" />
    <tmp:rating num="2" />
    <tmp:rating num="3" />
    <tmp:rating num="4" />
  </tmp:ratings>

  <!-- index items by their rating -->
  <xsl:key 
    name="kItemByRating" 
    match="item" 
    use="concat(
      substring('4', 1, rating = 'good' ),
      substring('3', 1, rating = 'medioce' ),
      substring('2', 1, rating = 'bad' ),
      substring('1', 1, rating = 'abyssmal' ),
      substring('4', 1, rating = '4' ),
      substring('3', 1, rating = '3' ),
      substring('2', 1, rating = '2' ),
      substring('1', 1, rating = '1' )
    )
  " />

  <!-- we're going to need that later-on -->
  <xsl:variable name="root" select="/" />

  <xsl:template match="/root">
    <!-- iterate on the prepared list of ratings -->
    <xsl:apply-templates select="document('')/*/tmp:ratings/tmp:rating">
      <xsl:sort select="@num" order="descending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="tmp:rating">
    <xsl:variable name="num" select="@num" />
    <!-- 
      The context node is part of the XSL file now. As a consequence,
      a call to key() would be evaluated within the XSL file.

      The for-each is a means to change the context node back to the 
      XML file, so that the call to key() can return <item> nodes.
    -->
    <xsl:for-each select="$root">
      <!-- now pull out all items with a specific rating -->
      <xsl:apply-templates select="key('kItemByRating', $num)">
        <!-- note that we use the variable here! -->
        <xsl:with-param name="num" select="$num" />
        <xsl:sort select="@name" />
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="item">
    <xsl:param name="num" select="''" />
    <xsl:copy>
      <!-- print out the numeric rating -->
      <xsl:attribute name="num">
        <xsl:value-of select="$num" />
      </xsl:attribute>
      <xsl:copy-of select="node() | @*" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

答案 1 :(得分:1)

如果您只有一小组预定义的替换,您可以使用以下方法:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myExt="http://www.example.com/myExtension"
    exclude-result-prefixes="myExt">

    <xsl:output method="xml" indent="yes"/>

    <myExt:replacements>
      <item>
        <value>good</value>
        <replacement>4</replacement>
      </item>
      <item>
        <value>very good</value>
        <replacement>5</replacement>
      </item>
    </myExt:replacements>

    <xsl:template match="root">
      <out>
         <xsl:for-each select="item">
           <xsl:sort select="number(document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacement | rating)" order="ascending"/>
           <item>
             <name>
               <xsl:value-of select="name"/>
             </name>
             <rating>
               <xsl:value-of select="document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacement | rating"/>
             </rating>
           </item>
         </xsl:for-each>
       </out>
    </xsl:template>

使用document('')是一种技巧,可以让您访问样式表文档中的节点。在我们的例子中,这是一组节点,指定要进行的替换。

| rating元素的select属性中使用xsl:sort是另一招。这意味着select表达式的结果是document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacementrating的并集。在计算select表达式时,仅考虑结果节点集的第一个元素。这样做的结果是,如果没有定义替换,将使用rating元素的值。

输出文档将如何为您提供示例输入:

<?xml version="1.0" encoding="utf-8"?>
<out>
  <item>
    <name>BCD</name>
    <rating>3</rating>
  </item>
  <item>
    <name>ABC</name>
    <rating>4</rating>
  </item>
</out>

答案 2 :(得分:1)

如果您只需要用4替换好的评级,那么试试这个。它会将4的良好评级替换为排序目的,并保留所有不良的评级。额外的空间是为了便于阅读/理解。

<xsl:for-each select="item">
    <xsl:sort select="
        concat(
          substring(
            '4', 
            1, 
            boolean(rating = 'good')
          ),
          substring(
            rating, 
            1, 
            not(boolean(rating = 'good'))
          )
        )
    "/>
</xsl:for-each>

如果您需要更换多个评级但有些已经是数字,则可以执行以下操作:

        concat(
          substring(
            '4', 
            1, 
            boolean(rating = 'good')
          ),
          substring(
            '3', 
            1, 
            boolean(rating = 'average')
          ),
          substring(
            '2', 
            1, 
            boolean(rating = 'bad')
          ),
          substring(
            rating, 
            1, 
            not(boolean(rating = 'bad') or boolean(rating = 'average') or boolean(rating = 'good'))
          )
        )

布尔值要么转换为1表示true,要么转换为0表示false。然后在子字符串中使用它,因此只有一个为true的子字符串将长度为1,其他字符串将以长度为0的子字符串连接。将这些连接在一起会使您获得替换值。