XSLT对多个属性的值进行排序

时间:2011-11-25 09:41:42

标签: xslt sorting

我有一个巨大的XML格式的配置文件。系统不关心标签的顺序,但我们人类做的! (主要是出于版本比较的目的。)I already received下面的XSLT运行良好,但我发现它还不够。

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates>
      <xsl:sort select="(@name, name())[1]"/>
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

我希望通过其name属性的值递归地对所有标签进行排序(这有效!)但是因为该属性并不总是存在,所以它还必须按其他属性排序,任何给定元素中可能存在或不存在任何元素。

我对XSLT基本上没有理解,所以我正在试验。我已将上述内容攻入此内容,但它并没有按预期工作。其结果似乎与上述相同。

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates>
      <xsl:sort select="@name"/>
      <xsl:sort select="@row"      data-type="number"/>
      <xsl:sort select="@col"      data-type="number"/>
      <xsl:sort select="@sequence" data-type="number"/>
      <xsl:sort select="@tabindex" data-type="number"/>
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

我的数据看起来与此类似,问题是cell元素根本没有排序(在grid组内),因为它们没有name属性。这就是为什么我想扩展排序逻辑以在存在时使用name属性,否则应该使用tabindex之类的其他属性来完成排序。在任何给定的组中,可以假定存在相同的属性。

<sections>
  <section name="SomeList">
    <caption>
      <![CDATA[Candidates]]>
    </caption>
    ...
    <parameters>
      <parameter name="pageSize">
        <![CDATA[50]]>
      </parameter>
    </parameters>
    ... 
    <grid>
      <cell row="0" col="7" tabindex="9" colspan="10">
        <field name="Entered" />
      </cell>
    </grid>
  </section>
</sections>

更新
在Vincent的帮助下,我创建了一个适合我们目的的分类。 Here it is.

3 个答案:

答案 0 :(得分:4)

这是一种回应,假设您的数据中没有任何混合内容。它只考虑了两个第一步(@name和@col),您可以适应进一步的步骤。也许它可以用递归命名模板重写,该模板将排序参数列表作为输入。如果我的XSLT不适合你,你能提供XML样本。

XSLT 2.0示例:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
        <xsl:template match="*">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:for-each-group select="*" group-by="if (exists(@name)) then @name else ''">
                    <xsl:sort select="current-grouping-key()" data-type="text"/>
                    <xsl:for-each-group select="current-group()" group-by="if (exists(@row)) then @row else -1">
                        <xsl:sort select="current-grouping-key()" data-type="number"/>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:for-each-group>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
</xsl:stylesheet>

请注意,代码会对具有相同值的组进行迭代,因此,如果元素上不存在属性,则元素将组合在一起。

我将以下XML作为输入:

<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item row="5" col="9"></item>
    <item name="d" row="20" col="12" tabindex="" sequence=""></item>
    <item row="1" col="5" ></item>
    <item name="d" row="5" col="6" ></item>
    <item name="a" row="7" col="8" ></item>
    <item name="s" row="1" col="5" ></item>
    <item name="c" row="5" col="9"></item>
    <item row="2" col="5" ></item>
    <item row="20" col="9"></item>
    <item row="0" col="9"></item>
    <item name="s" row="2" col="10" tabindex="" sequence=""></item>
    <item name="z" row="8" col="15" tabindex="" sequence=""></item>    
</items>

我有以下结果:

<?xml version="1.0" encoding="UTF-8"?>
<items>
   <item row="0" col="9"/>
   <item row="1" col="5"/>
   <item row="2" col="5"/>
   <item row="5" col="9"/>
   <item row="20" col="9"/>
   <item name="a" row="7" col="8"/>
   <item name="c" row="5" col="9"/>
           <item name="d" row="5" col="6"/>
   <item name="d" row="20" col="12" tabindex="" sequence=""/>
   <item name="s" row="1" col="5"/>
   <item name="s" row="2" col="10" tabindex="" sequence=""/>
   <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

答案 1 :(得分:1)

将此XSLT视为具有给定强制属性的特定元素:

   <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:template match="*">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates>
                <xsl:sort select="(@name, name())[1]"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="grid">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:for-each-group select="*" group-by="if (exists(@row)) then @row else -1">
                <xsl:sort select="current-grouping-key()" data-type="number"/>
                <xsl:for-each-group select="current-group()" group-by="if (exists(@col)) then @col else -1">
                    <xsl:sort select="current-grouping-key()" data-type="number"/>
                    <xsl:apply-templates select="current-group()"/>
                </xsl:for-each-group>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

我的示例必须涵盖使用匹配*的第一个模板排序的部分和参数。并且还按行和列进行网格排序。 您可以通过复制模板来扩展具有不同排序属性的任何其他元素。

如果您有相同属性的多个元素,请使用match="elt1|elt2|elt3"

答案 2 :(得分:1)

这是一个通用,简单且不长(60格式良好的行)解决方案

所有 所需属性执行排序,这不需要任何手动复制模板

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:my="my:my" exclude-result-prefixes="my">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:param name="pSortTypes" as="element()*">
      <attr name="name" type="alpha" maxLength="15"/>
      <attr name="row" type="numeric" maxLength="6"/>
      <attr name="col" type="numeric" maxLength="4"/>
      <attr name="tabindex" type="numeric" maxLength="2"/>
      <attr name="sequence" type="numeric" maxLength="3"/>
    </xsl:param>

 <xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="*">
     <xsl:sort select="my:OrderedAttributeTuple(.)"/>
    </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>

 <xsl:function name="my:OrderedAttributeTuple" as="xs:string">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vResult" as="xs:string*">
      <xsl:apply-templates select="$pSortTypes">
       <xsl:with-param name="pElem" select="$pElem"/>
      </xsl:apply-templates>
  </xsl:variable>

  <xsl:sequence select="string-join($vResult, '')"/>
 </xsl:function>

 <xsl:template match="attr">
  <xsl:param name="pElem" as="element()"/>

  <xsl:variable name="vVal" select=
       "string($pElem/@*[name() eq current()/@name])"/>

  <xsl:variable name="vPad" as="xs:string*" select=
   "for $cnt in xs:integer(@maxLength) - string-length($vVal),
        $i in 1 to $cnt
     return '.'
   "/>

   <xsl:variable name="vPadding" select="string-join($vPad, '')"/>

   <xsl:variable name="vTuple">
       <xsl:sequence select=
        "if(@type eq 'alpha')
           then concat($vVal, $vPadding)
           else concat($vPadding, $vVal)
        "/>
    </xsl:variable>

   <xsl:sequence select="string($vTuple)"/>
 </xsl:template>
</xsl:stylesheet>

对此XML文档应用此转换时

<items>
    <item row="5" col="9"/>
    <item name="d" row="20" col="12" tabindex="" sequence=""/>
    <item row="1" col="5" />
    <item name="d" row="5" col="6" />
    <item name="a" row="7" col="8" />
    <item name="s" row="1" col="5" tabindex="3" sequence="4"/>
    <item name="s" row="3" col="3" tabindex="3" sequence="4"/>
    <item name="c" row="5" col="9"/>
    <item row="2" col="5" />
    <item row="20" col="9"/>
    <item row="0" col="9"/>
    <item name="s" row="3" col="3" tabindex="1" sequence="2"/>
    <item name="s" row="2" col="10" tabindex="1" sequence="2"/>
    <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

生成了正确排序的结果

<items>
   <item row="0" col="9"/>
   <item row="1" col="5"/>
   <item row="2" col="5"/>
   <item row="5" col="9"/>
   <item row="20" col="9"/>
   <item name="a" row="7" col="8"/>
   <item name="c" row="5" col="9"/>
   <item name="d" row="5" col="6"/>
   <item name="d" row="20" col="12" tabindex="" sequence=""/>
   <item name="s" row="1" col="5" tabindex="3" sequence="4"/>
   <item name="s" row="2" col="10" tabindex="1" sequence="2"/>
   <item name="s" row="3" col="3" tabindex="1" sequence="2"/>
   <item name="s" row="3" col="3" tabindex="3" sequence="4"/>
   <item name="z" row="8" col="15" tabindex="" sequence=""/>
</items>

请注意

  1. 在外部参数$pSortTypes)中指定的 所有 属性上执行排序。将此与当前接受的答案进行比较,该答案仅对@name@row进行排序,并且需要对订单进行硬编码并对数据类型进行排序。

  2. 可以指定属性的确切排序顺序。这是他们的订单,如$pSortTypes

  3. type中的$pSortTypes属性(目前只有"alpha""numeric"中指定了每个属性的排序数据类型 })

  4. 指定属性值的字符串表示的最大长度maxLength中的$pSortTypes属性。这用于正确的填充/对齐,也可以提高分类效率。

  5. 这演示了如何通过生成单一排序的用户定义的xsl:function (在本例中为my:OrderedAttributeTuple())来解决最复杂的排序问题键。