【问题标题】:how to sort values with XSLT?如何使用 XSLT 对值进行排序?
【发布时间】:2011-09-27 19:47:15
【问题描述】:

我有一个类似这样的 XML:

<Test>
  <grapes>
     <a>TypeA</a>
     <b>value1</b>
  </grapes>
  <oranges>
     <a>TypeB</a>
     <b>value2</b>
  </oranges>
  <apples>
    <a>TypeA</a>
     <b>value3</b>
  </apples>
</Test>

其中值是唯一的,但类型可能相同。

我正在尝试对其进行排序,以便输出与此类似:

<group type="TypeA">
  <value v="value1" />
  <value v="value3" />
</group>
<group type="TypeB">
  <value v="value2" />
</group>

我很难确保输出中的组是唯一的,并且值在正确的组中。

我的 XSL 应该如何构建?

【问题讨论】:

  • 由于我没有在标题中看到分组,所以我想澄清一下:您要分组然后排序吗?
  • 非常有用的问题(和很好的答案)。我同意应该改写标题,因为这是一个常见问题,如果他们能更好地发现这个问题是他们的问题,那么许多人都会对答案感兴趣。

标签: xml xslt xml-parsing xslt-2.0


【解决方案1】:

这是一个更简单的解决方案(完全“推式”,没有&lt;xsl:for-each&gt;,没有嵌套,没有&lt;xsl:variable&gt;,没有current(),没有//,没有轴) :

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

 <xsl:key name="kGoodsByType" match="/*/*" use="a"/>

 <xsl:template match=
  "/*/*[generate-id()
       =
        generate-id(key('kGoodsByType', a)[1])
       ]
  ">
     <group type="{a}">
       <xsl:apply-templates select="key('kGoodsByType', a)/b"/>
     </group>
 </xsl:template>

 <xsl:template match="b">
  <value v="{.}"/>
 </xsl:template>

 <xsl:template match="*/* | text()" priority="-1"/>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时:

<Test>
    <grapes>
        <a>TypeA</a>
        <b>value1</b>
    </grapes>
    <oranges>
        <a>TypeB</a>
        <b>value2</b>
    </oranges>
    <apples>
        <a>TypeA</a>
        <b>value3</b>
    </apples>
</Test>

产生想要的正确结果:

<group type="TypeA">
   <value v="value1"/>
   <value v="value3"/>
</group>
<group type="TypeB">
   <value v="value2"/>
</group>

解释:Muenchian grouping of /*/* 使用其a 子级的字符串值作为键。

二。 XSLT 2.0 解决方案:

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

 <xsl:template match="/*">
  <xsl:for-each-group select="*/a" group-by=".">
   <group type="{current-grouping-key()}">
     <xsl:sequence select="current-group()/../b"/>
   </group>
  </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>

当对同一个 XML 文档(如上)执行此转换时,会产生相同的正确结果:

<group type="TypeA">
   <b>value1</b>
   <b>value3</b>
</group>
<group type="TypeB">
   <b>value2</b>
</group>

解释:

  1. &lt;xsl:for-each-group&gt;

  2. current-group()

  3. current-grouping-key()

【讨论】:

    【解决方案2】:

    XSLT 1.0:

    您首先使用 muenchian 方法为您的类型创建唯一组。谷歌它找出它是什么。然后只需要遍历它们并打印出你想要的,你想要的:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:key name="types" match="a" use="text()"/>
    <xsl:template match="/">
    <result>
      <xsl:for-each select="//a[generate-id(.) = generate-id(key('types', text())[1])]">
        <group type="{current()/text()}">
          <xsl:for-each select="//a[text() = current()/text()]">
            <xsl:variable name="values" select="following-sibling::b | preceding-sibling::b"/>
            <xsl:for-each select="$values">
              <value v="{current()}"/>
            </xsl:for-each>
          </xsl:for-each>
        </group>
      </xsl:for-each>
    </result>
    </xsl:template>
    </xsl:stylesheet>
    

    你会发现输出和你期望的一样。

    【讨论】:

    • 确保你理解这里写的内容。如果您有任何问题,请随时提出。
    • 你能解释一下这里到底发生了什么吗? //a[generate-id(.) = generate-id(key('types', text())[1])]
    • 基本上,您会根据文本值为所有 a 元素生成唯一 ID,然后遍历它们。
    • 啊,好吧,还有一件事,为什么需要这个: 如果你已经在循环尽管每个都与组名匹配。
    • 因为一旦有了组,我就需要获取所有兄弟姐妹并遍历它们。在这种情况下,您的兄弟姐妹被命名为 b 但我不知道,例如b 将始终跟随 a 或反之亦然,因此该轴仅选择 a 元素之后和之前的所有 b 元素。
    【解决方案3】:

    我的提议与 FailedDev 的略有不同:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes" />
    
      <xsl:template match="/Test">
        <root>
          <xsl:for-each select="*[a != following::a]">
            <xsl:sort select="a" data-type="text" />
            <group type="{a}">
              <xsl:for-each select="/Test/*[a = current()/a]">
                <xsl:sort select="b" data-type="text" />
                <value v="{b}" />
              </xsl:for-each>
            </group>
          </xsl:for-each>
        </root>
      </xsl:template>
    </xsl:stylesheet>
    

    外层

    <xsl:for-each select="*[a != following::a]" />
    

    选择所有唯一类型,即。 TypeA 和 TypeB。

    <xsl:sort select="a" data-type="text" />
    

    根据名称对它们进行排序,确保 TypeA 将出现在 TypeB 上方。 内部

    <xsl:for-each select="/Test/*[a = current()/a]" />
    

    为每种类型选择一个唯一值列表,即。对于 TypeA,提取值 value1 和 value3。同样,结果列表被排序为列表 value1 在 value3 之前。

    【讨论】:

    • 大错特错——当操作数之一是节点集时,不要使用!= 运算符。 -1。此外,请务必在发布之前运行/测试您提出的解决方案!
    • 经过测试似乎没有问题。尽管我同意您使用 Muenchian 分组的解决方案可能是可行的方法。
    • @_Mace:你的答案是错误的——你很幸运它在提供的 XML 文档上“有效”。试试这个 XML 文档,看看它为“TypeA”生成 两个组:&lt;Test&gt; &lt;grapes&gt; &lt;a&gt;TypeA&lt;/a&gt; &lt;b&gt;value1&lt;/b&gt; &lt;/grapes&gt; &lt;oranges&gt; &lt;a&gt;TypeB&lt;/a&gt; &lt;b&gt;value2&lt;/b&gt; &lt;/oranges&gt; &lt;apples&gt; &lt;a&gt;TypeA&lt;/a&gt; &lt;b&gt;value3&lt;/b&gt; &lt;/apples&gt; &lt;peaches&gt; &lt;a&gt;TypeB&lt;/a&gt; &lt;b&gt;value4&lt;/b&gt; &lt;/peaches&gt; &lt;/Test&gt;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-11
    • 2019-05-08
    • 2010-10-09
    • 1970-01-01
    相关资源
    最近更新 更多