【问题标题】:XSLT to sort on value of multiple attributesXSLT 对多个属性的值进行排序
【发布时间】:2012-01-06 05:14:18
【问题描述】:

我有一个巨大的 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.

【问题讨论】:

  • 相关问题的 Dropbox 链接下可用的示例输入不包含 row、col、sequence 或 tabindex 属性。您可以添加更新的示例吗?
  • @grtjn:当你写评论时,我已经在编辑问题了:-)
  • 您使用的是哪种解析器?用 Saxon 9.1b 运行它对我来说效果很好。单元格按预期排序。
  • 好问题,+1。您可能有兴趣获得更好、更通用的排序。
  • 您可能想要删除您的 Dropbox 链接(404 错误)

标签: xslt sorting


【解决方案1】:

这是一个假设您的数据中没有任何混合内容的响应。它只考虑了前两个步骤(@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>

【讨论】:

  • 你提到的输出看起来正是我想要的样子!不幸的是,当我通过您的示例 XSLT 运行您的示例输入时,我没有得到您的示例输出。 I get this. 看来我需要先在我这边排查问题!
  • 为了使输出更加清晰,可以添加以下指令作为样式表的第一个子项:。您将获得与上述相同的缩进结果。并尝试从命令行运行:java -jar saxon.jar test.xml test.xsl。让我知道你得到了什么输出。
  • 当我看到您的 XML 时,我想知道每个不同的元素(参数、部分、单元格...)是否始终存在相同的属性?可能每个元素名称只有一种不同的排序方式,这比一般任务要容易得多。
  • 重新缩进:indent 技巧有帮助;我也在使用 HTML Tidy,这会破坏一些东西,但我认为只使用 indent 就很完美了。
  • Re属性:我们的XML有不同的block,within每个block,属性都是一样的,只是block之间的属性不同。例如。 grid 块中的所有元素都具有相同类型的属性(row/col/tabindex),但这些属性与parameters 块(名称)中的元素不同。问题是我们有 50 多种不同类型的块,所以一个通用的解决方案似乎比必须设置 50 多个单独的规则更好。
【解决方案2】:

对于具有给定强制属性的特定元素,请考虑使用此 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"

【讨论】:

    【解决方案3】:

    这是一个通用、简单且不长(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 进行排序,并且需要对 order 和 sort 数据类型进行硬编码。

    2. 可以指定确切想要的属性排序顺序。这是他们在$pSortTypes 中的顺序。

    3. 每个属性的排序数据类型在$pSortTypestype属性中指定(目前只有"alpha""numeric"

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

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

    【讨论】:

    • 嗨,Dimitre,干得好。有没有机会像在xsl:sort 元素中一样添加order 属性(ascending|descending)?
    • 好的,我自己想出来的。由于您的代码中有一个元素xsl:sort,我只是将order 属性放在那里。
    • @Jagger,是的,如果需要,可以指定&lt;xsl:sort&gt; 的排序属性。当前问题中不需要这样做。
    • xslt 1.0 有通用解决方案吗?
    • 此解决方案可以转换为 XSLT 1.0 + exslt:node-set() 扩展功能
    猜你喜欢
    • 1970-01-01
    • 2018-03-06
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 2011-05-17
    相关资源
    最近更新 更多