按周期分隔的XSL排序编号

时间:2016-10-23 11:43:13

标签: xml xslt transformation

我有一个排序数字的问题,用句点分隔(例如1,2,1.1,1.3)。我在这里找到了一个解决方案XSL recursive sort。这就是我需要的,略有不同。 在我的xml中,标签就像

 <root>
   <row>
       <col name="rank"/>
       <name>A</name>
       <val>1.1</val>
   </row>
   <row>
       <col name="rank"/>
       <name>B</name>
       <val>1</val>
   </row>
   <row>
       <col name="level"/>
       <name>C</name>
       <val>test</val>
   </row>
   <row>
       <col name="rank"/>
       <name>D</name>
       <val>1.2.2</val>
   </row>
   <row>
       <col name="rank"/>
       <name>E</name>
       <val>1.2.1</val>
    </row>
   <row>
       <col name="rank"/>
       <name>F</name>
       <val>1.2</val>
    </row>
 </root>

我希望根据“val”标签对col / @ name =“rank”的所有行进行排序。是否可以通过仅修改链接问题中的已接受答案来获取输出?如果没有,是否有任何xsl版本1的解决方案(如果没有,则为2)。 我需要的输出是:

<ul>
   <li>1 - B
     <ul>
       <li>1.1 - A</li>
       <li>1.2 - F
          <ul>
             <li>1.2.1 - E</li>
             <li>1.2.2 - D</li>
          </ul>
       </li>
    </ul>      
   </li>
</ul>

更新我:基于michael.hor257k的回答,这是我正在寻找的解决方案

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" version="5.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/root">
    <html>
    <body>
      <ul>
        <xsl:apply-templates select="row[not(contains(val, '.'))][contains(col/@name, 'rank')]">
          <xsl:sort select="val" data-type="number" order="ascending"/>
        </xsl:apply-templates>
      </ul>
    </body>    
    </html>      
  </xsl:template>

  <xsl:template match="row">
    <li>
      <xsl:variable name="parent" select="concat(val, '.')" />
      <xsl:value-of select="./name"/> - <xsl:value-of select="./val"/>
      <xsl:if test="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
        <ul>
          <xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
            <xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
          </xsl:apply-templates>
        </ul>
      </xsl:if>
    </li>
  </xsl:template>
</xsl:stylesheet> 

更新II :感谢Dimitre Novatchev,我有一个更好的解决方案,我认为这是最好的答案。所以,我试图理解它,之后我会把它检查为已接受的答案。

更新III :我接受了michael.hor257k发布的答案,因为这是我需要的。我知道我的xml中的排名没有跳跃但是正如Dimitre Novatchev所提到的那样,如果有1.3.2没有1.3,那么这个解决方案会有问题,你可以使用Dimitre Novatchev发布的完整答案。

5 个答案:

答案 0 :(得分:1)

已知级别数量?如果是这样,在XSLT 2.0中你可以使用

<xsl:apply-templates select="row[col/@name = 'rank']">
<xsl:sort select="xs:integer(tokenize(val, '\'.')[1])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[2])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[3])"/>
</xsl:apply-templates/>

三个级别。在XSLT 3.0中,您甚至可以使用任何级别的sort函数来执行此操作:<xsl:apply-templates select="sort(row[col/@name = 'rank'], function($row) { tokenize($row/val, '\.')!xs:integer(.) })">尽管您还希望嵌套我认为使用递归函数执行

<xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[1])"><xsl:sort select="current-grouping-key()"/>...</xsl:for-each-group>

更适合XSLT 2.0或3.0然后纯排序。

完整的样式表

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs mf" version="2.0">

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

    <xsl:function name="mf:nest" as="element()*">
        <xsl:param name="rows" as="element(row)*"/>
        <xsl:sequence select="mf:nest($rows, 1)"/>
    </xsl:function>

    <xsl:function name="mf:nest" as="element()*">
        <xsl:param name="rows" as="element(row)*"/>
        <xsl:param name="level" as="xs:integer"/>
        <xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[$level])">
            <xsl:sort select="current-grouping-key()"/>
            <li>
                <xsl:variable name="item" select="current-group()[not(tokenize(val, '\.')[$level + 1])]"/>
                <xsl:value-of select="$item/concat(name, ' - ', val)"/>
                <xsl:if test="current-group()[2]">
                    <ul>
                        <xsl:sequence select="mf:nest(current-group() except $item, $level + 1)"/>
                    </ul>
                </xsl:if>
            </li>
        </xsl:for-each-group>
    </xsl:function>

    <xsl:template match="root">
        <ul>
            <xsl:sequence select="mf:nest(row[col/@name = 'rank'])"/>
        </ul>
    </xsl:template>

</xsl:stylesheet>

它转换输入

<root>
    <row>
        <col name="rank"/>
        <name>A</name>
        <val>1.1</val>
    </row>
    <row>
        <col name="rank"/>
        <name>B</name>
        <val>1</val>
    </row>
    <row>
        <col name="level"/>
        <name>C</name>
        <val>test</val>
    </row>
    <row>
        <col name="rank"/>
        <name>D</name>
        <val>1.2.2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>E</name>
        <val>1.2.1</val>
    </row>
    <row>
        <col name="rank"/>
        <name>foo</name>
        <val>2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>bar</name>
        <val>1.10</val>
    </row>
    <row>
        <col name="rank"/>
        <name>F</name>
        <val>1.2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>F</name>
        <val>1.10.1</val>
    </row>
</root>

进入结果

<ul>
   <li>B - 1
      <ul>
         <li>A - 1.1</li>
         <li>F - 1.2
            <ul>
               <li>E - 1.2.1</li>
               <li>D - 1.2.2</li>
            </ul>
         </li>
         <li>bar - 1.10
            <ul>
               <li>F - 1.10.1</li>
            </ul>
         </li>
      </ul>
   </li>
   <li>foo - 2</li>
</ul>

答案 1 :(得分:1)

考虑以下样式表:

XSLT 1.0

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

<xsl:template match="/root">
    <list>
        <xsl:apply-templates select="row[not(contains(val, '.'))]">
            <xsl:sort select="val" data-type="number" order="ascending"/>
        </xsl:apply-templates>
    </list>
</xsl:template>

<xsl:template match="row">
    <xsl:variable name="parent" select="concat(val, '.')" />
    <item val="{val}">
        <xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))]">
            <xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
        </xsl:apply-templates>
    </item>
</xsl:template>

</xsl:stylesheet> 

应用于以下输入示例:

<强> XML

<root>
   <row>
       <col name="rank"/>
       <name>A</name>
       <val>1.1</val>
   </row>
   <row>
       <col name="rank"/>
       <name>B</name>
       <val>1</val>
   </row>
   <row>
       <col name="rank"/>
       <name>F</name>
       <val>1.10</val>
    </row>
   <row>
       <col name="level"/>
       <name>C</name>
       <val>2</val>
   </row>
   <row>
       <col name="rank"/>
       <name>D</name>
       <val>1.2.2</val>
   </row>
   <row>
       <col name="rank"/>
       <name>E</name>
       <val>1.2.1</val>
    </row>
   <row>
       <col name="rank"/>
       <name>F</name>
       <val>1.2</val>
    </row>
 </root>

结果将是:

<?xml version="1.0" encoding="UTF-8"?>
<list>
   <item val="1">
      <item val="1.1"/>
      <item val="1.2">
         <item val="1.2.1"/>
         <item val="1.2.2"/>
      </item>
      <item val="1.10"/>
   </item>
   <item val="2"/>
</list>

这是递归工作的,并且对级别数没有限制。但请注意,每个项目(&#34;祖先&#34;不包含点的项目除外)必须有父项。

答案 2 :(得分:0)

  

我遇到排序数字的问题,以句点分隔(例如1,   2.1,1.1,1.3)。我在这里找到了一个解决方案XSL recursive sort

第一部分。排序

很容易使原始解决方案适应新案例。 与接受的答案不同,此解决方案正确排序XML文档,其中有

<val>1.3.2</val>

但没有

<val>1.3</val>

请参阅第II部分,将排序后的结果转换为有用的嵌套列表结构。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates select="row">
                <xsl:sort select="substring-before(concat(val, '.'), '.')" 
                     data-type="number"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="row">
        <xsl:param name="prefix" select="''"/>
        <xsl:choose>
            <!-- end of recursion, there isn't any more row with more chunks -->
            <xsl:when test="val = substring($prefix, 1, string-length($prefix)-1)">
                <xsl:copy-of select="."/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="chunk" select=
                 "substring-before(concat(substring-after(val, $prefix), '.'), '.')"/>
                <!-- this tests for grouping row with same prefix, to skip duplicates -->
                <xsl:if test=
                "not(preceding-sibling::row[starts-with(val, concat($prefix, $chunk))])">
                    <xsl:variable name="new-prefix" 
                                          select="concat($prefix, $chunk, '.')"/>
                    <xsl:apply-templates select=
              "../row[starts-with(val, $new-prefix) or val = concat($prefix, $chunk)]">
                        <xsl:sort select= 
              "substring-before(concat(substring-after(val, $new-prefix), '.'), '.')" 
                                  data-type="number"/>
                        <xsl:with-param name="prefix" select="$new-prefix"/>
                    </xsl:apply-templates>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

对以下XML文档应用此转换时 - 请注意<val>1.3.2</val>但没有<val>1.3</val>,并且接受的答案不会产生正确的结果 - - 实际上删除了<row>孩子的整个<val>1.3.2</val>

<root>
    <row>
        <col name="rank"/>
        <name>A</name>
        <val>1.1</val>
    </row>
    <row>
        <col name="rank"/>
        <name>B</name>
        <val>1</val>
    </row>
    <row>
        <col name="rank"/>
        <name>F</name>
        <val>1.10</val>
    </row>
    <row>
        <col name="level"/>
        <name>C</name>
        <val>2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>D</name>
        <val>1.2.2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>D</name>
        <val>1.3.2</val>
    </row>
    <row>
        <col name="rank"/>
        <name>E</name>
        <val>1.2.1</val>
    </row>
    <row>
        <col name="rank"/>
        <name>F</name>
        <val>1.2</val>
    </row>
</root>

生成了正确排序的结果

<root>
   <row>
      <col name="rank"/>
      <name>B</name>
      <val>1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>A</name>
      <val>1.1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>F</name>
      <val>1.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>E</name>
      <val>1.2.1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>D</name>
      <val>1.2.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>D</name>
      <val>1.3.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>F</name>
      <val>1.10</val>
   </row>
   <row>
      <col name="level"/>
      <name>C</name>
      <val>2</val>
   </row>
</root>

最后,还有一个重构:消除所有XSLT条件运算符

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="row">
                <xsl:sort select="substring-before(concat(val, '.'), '.')" 
                          data-type="number"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="row">
        <xsl:param name="prefix" select="''"/>
        <xsl:variable name="vHasChildren" select=
                           "not(val = substring($prefix, 1, string-length($prefix)-1))"/>
        <xsl:copy-of select="self::node()[not($vHasChildren)]"/>

        <xsl:variable name="chunk" 
             select="substring-before(concat(substring-after(val, $prefix), '.'), '.')"/>
        <xsl:variable name="new-prefix" select="concat($prefix, $chunk, '.')"/>
        <xsl:apply-templates select= "self::node()
            [$vHasChildren 
           and not(preceding-sibling::row[starts-with(val, concat($prefix, $chunk))])
            ]
             /../row[starts-with(val, $new-prefix) or val = concat($prefix, $chunk)]">
            <xsl:with-param name="prefix" select="$new-prefix"/>
            <xsl:sort data-type="number" select=
                "substring-before(concat(substring-after(val, $new-prefix), '.'), '.')"/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

第二部分:将已排序的平面结果转换为嵌套列表结构

这里我们从第一部分中产生的转换结果开始,从中我们生成有用的嵌套列表结构。到目前为止我们得到的排序结果是:

<root>
   <row>
      <col name="rank"/>
      <name>B</name>
      <val>1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>A</name>
      <val>1.1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>F</name>
      <val>1.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>E</name>
      <val>1.2.1</val>
   </row>
   <row>
      <col name="rank"/>
      <name>D</name>
      <val>1.2.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>D</name>
      <val>1.3.2</val>
   </row>
   <row>
      <col name="rank"/>
      <name>F</name>
      <val>1.10</val>
   </row>
   <row>
      <col name="level"/>
      <name>C</name>
      <val>2</val>
   </row>
</root>

我们使用此转换

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

  <xsl:template match="/*">
    <list>
      <xsl:apply-templates select=
      "row[not(substring-before(concat(val, '.'), '.') 
              = substring-before(concat(preceding-sibling::row[1]/val,'.'),'.'))]">
       <xsl:with-param name="pPrefix" select="''"/>
      </xsl:apply-templates>
    </list>
  </xsl:template>


  <xsl:template match="row">
    <xsl:param name="pPrefix"/>
    <item val="{val}">
      <xsl:variable name="vnewPrefix" select="concat($pPrefix, val, '.')"/>
      <xsl:variable name="vcurrentVal" select="val"/>
      <xsl:apply-templates select="following-sibling::row
                  [starts-with(val, concat($vcurrentVal,'.'))
                 and
                   (string-length(val) - string-length(translate(val,'.','')) 
                    = 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
                    )
                   or
                    not(starts-with(val, 
                                    concat($vnewPrefix, 
                                           substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
                                                            '.')
                                    )
                        )
                   )
                  ]">
         <xsl:with-param name="pPrefix" select="$vnewPrefix"/>
      </xsl:apply-templates>
    </item>
  </xsl:template>
</xsl:stylesheet>

在上述XML文档中应用此转换的结果是有用的嵌套列表结构

<list>
   <item val="1">
      <item val="1.1"/>
      <item val="1.2">
         <item val="1.2.1"/>
         <item val="1.2.2"/>
      </item>
      <item val="1.3.2"/>
      <item val="1.10"/>
   </item>
   <item val="2"/>
</list>

我们可以使用此转换类似地生成所需的HTML:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

  <xsl:template match="/*">
    <ul>
      <xsl:apply-templates select=
      "row[not(substring-before(concat(val, '.'), '.') 
              = substring-before(concat(preceding-sibling::row[1]/val,'.'),'.'))]">
       <xsl:with-param name="pPrefix" select="''"/>
      </xsl:apply-templates>
    </ul>
  </xsl:template>

  <xsl:template match="row">
    <xsl:param name="pPrefix"/>
    <li> <xsl:value-of select="concat(val, ' - ', name, '&#xA;')"/>
      <xsl:variable name="vnewPrefix" select="concat($pPrefix, val, '.')"/>
      <xsl:variable name="vcurrentVal" select="val"/>

      <xsl:variable name="vnextInChain" select=
      "following-sibling::row
                  [starts-with(val, concat($vcurrentVal,'.'))
                 and
                   (string-length(val) - string-length(translate(val,'.','')) 
                    = 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
                    )
                   or
                    not(starts-with(val, 
                                    concat($vnewPrefix, 
                                           substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
                                                            '.')
                                    )
                        )
                   )
                  ]"/>

      <xsl:if test="$vnextInChain">
       <ul>
        <xsl:apply-templates select="following-sibling::row
                  [starts-with(val, concat($vcurrentVal,'.'))
                 and
                   (string-length(val) - string-length(translate(val,'.','')) 
                    = 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
                    )
                   or
                    not(starts-with(val, 
                                    concat($vnewPrefix, 
                                           substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
                                                            '.')
                                    )
                        )
                   )
                  ]">
         <xsl:with-param name="pPrefix" select="$vnewPrefix"/>
        </xsl:apply-templates>
       </ul>
      </xsl:if>
    </li>
  </xsl:template>
</xsl:stylesheet>

当对平面排序结果应用此转换时,将生成所需的HTML结果:

<ul>
    <li>1 - B
        <ul>
            <li>1.1 - A
            </li>
            <li>1.2 - F
                <ul>
                    <li>1.2.1 - E
                    </li>
                    <li>1.2.2 - D
                    </li>
                </ul></li>
            <li>1.3.2 - D
            </li>
            <li>1.10 - F
            </li>
        </ul></li>
    <li>2 - C
    </li>
</ul>

答案 3 :(得分:0)

检查您选择的XSLT处理器是否支持数字排序(一种排序字符串的方式,其中连续的数字序列被视为数字,因此“第2章”在“第10章”之前排序)。例如,使用XSLT 3.0和XPath 3.1中定义的UCA归类URI,这将是

    <xsl:sort select="val" 
         collation="http://www.w3.org/2013/collation/UCA?numeric=yes"/>

在Saxon中也可以使用数字校对多年的形式

collation="http://saxon.sf.net/collation?alphanumeric=yes"

答案 4 :(得分:0)

如果您能够利用它,这是另一个候选者:XPath 3.1中的新fn:sort函数允许使用复合排序键,以便您可以编写

sort(val, function($x){tokenize($x, '\.')!number()})