【问题标题】:Grouping xml nodes by value of a child in Xsl按 Xsl 中子项的值对 xml 节点进行分组
【发布时间】:2011-01-17 07:50:08
【问题描述】:
<root>
<element>
<id>1</id>
<group>first</group>
</element>

<element>
<id>2</id>
<group>second</group>
</element>


<element>
<id>3</id>
<group>first</group>
</element>
...
<root>

如何在 xslt 1.0 中按组名对元素进行分组。 输出:

<root>
<group name="first">
 <element>
    <id>1</id>
    <group>first</group>
 </element>
 <element>
    <id>3</id>
    <group>first</group>
 </element>
</group>
<group name="second">
 <element>
    <id>2</id>
    <group>second</group>
    </element>
</group>
</root>

有什么想法吗?

【问题讨论】:

  • 顺便说一句,您的 xml 无效 first
  • 仍然无效first :)
  • 好问题,+1。请参阅我的答案以获得比当前接受的解决方案和要点的解释更短的内容。 :)
  • 还添加了 XSLT 2.0 解决方案。 :)

标签: xml xslt xpath


【解决方案1】:

这是 Muenchian Grouping 的工作。您将在 StackOverflow 上的 XSLT 标记中看到大量示例。

首先,您需要定义一个键来帮助您对组进行分组

<xsl:key name="groups" match="group" use="."/>

这将为给定的组名查找 group 元素。

接下来,您需要匹配每个区别组名称的第一个实例的所有出现。这是通过这个看起来很吓人的声明完成的

<xsl:apply-templates select="element/group[generate-id() = generate-id(key('groups', .)[1])]"/>

即匹配组元素,恰好是该元素在我们的键中的第一次出现。

当你匹配了不同的组节点后,你可以遍历所有其他同名的组节点(其中 $currentGroup 是一个保存当前组名的变量)

<xsl:for-each select="key('groups', $currentGroup)">

把这个放在一起

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:key name="groups" match="group" use="."/>

   <xsl:template match="/root">
      <root>
         <xsl:apply-templates select="element/group[generate-id() = generate-id(key('groups', .)[1])]"/>
      </root>
   </xsl:template>

   <xsl:template match="group">
      <xsl:variable name="currentGroup" select="."/>
      <group>
         <xsl:attribute name="name">
            <xsl:value-of select="$currentGroup"/>
         </xsl:attribute>
         <xsl:for-each select="key('groups', $currentGroup)">
            <element>
               <id>
                  <xsl:value-of select="../id"/>
               </id>
               <name>
                  <xsl:value-of select="$currentGroup"/>
               </name>
            </element>
         </xsl:for-each>
      </group>
   </xsl:template>

</xsl:stylesheet>

在您的示例 XML 上应用它会得到以下结果

<root>
   <group name="first">
      <element>
         <id>1</id>
         <name>first</name>
      </element>
      <element>
         <id>3</id>
         <name>first</name>
      </element>
   </group>
   <group name="seccond">
      <element>
         <id>2</id>
         <name>seccond</name>
      </element>
   </group>
</root>

【讨论】:

  • +1 正确答案。但风格是......不是 XSLT 风格:您应该将 element 分组为 name,并使用身份规则或 xsl:copy-of
【解决方案2】:

我。这是一个完整且非常简短的 XSLT 1.0 解决方案

<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="kElsByGroup" match="element" use="group"/>
 
 <xsl:template match="/*">
  <root>
   <xsl:apply-templates/>
  </root>
 </xsl:template>

 <xsl:template match=
   "element[generate-id()=generate-id(key('kElsByGroup',group)[1])]">

  <group name="{group}">
   <xsl:copy-of select="key('kElsByGroup',group)"/>
  </group>
 </xsl:template>
 
 <xsl:template match=
   "element[not(generate-id()=generate-id(key('kElsByGroup',group)[1]))]"/>

</xsl:stylesheet>

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

<root>
    <element>
        <id>1</id>
        <group>first</group>
    </element>
    <element>
        <id>2</id>
        <group>second</group>
    </element>
    <element>
        <id>3</id>
        <group>first</group>
    </element>
</root>

产生想要的正确结果

<root>
    <group name="first"><element>
        <id>1</id>
        <group>first</group>
    </element><element>
        <id>3</id>
        <group>first</group>
    </element></group>
    <group name="second"><element>
        <id>2</id>
        <group>second</group>
    </element></group>
</root>

请注意

  1. Muenchian method for grouping的使用。这是 XSLT 1.0 中最有效的分组方法。

  2. 使用AVTAttribute Value Template)将一个字面量的结果元素和它的变量-值属性指定为一个整体。使用 AVT 可以简化编码并生成更短且更易于理解的代码。

二。更短的 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="/*">
     <root>
      <xsl:for-each-group select="element" group-by="group">
       <group name="{current-grouping-key()}">
        <xsl:copy-of select="current-group()"/>
       </group>
      </xsl:for-each-group>
     </root>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于同一个 XML 文档(上图)时,再次产生相同的正确结果。

请注意

.1。 &lt;xsl:for-each-group&gt;的使用 XSLT 2.0 指令。

.2。 使用标准 XSLT 2.0 函数current-group()current-grouping-key()

【讨论】:

  • @Haroldis:不客气。您的“感谢”是否转化为赞成和/或接受? :)
  • @DimitreNovaatchev,做得很好。 +1。这绝对是正确答案!
【解决方案3】:
<xsl:template match="group[not(.=preceding::group)]">
  <xsl:variable name="current-group" select="." />
  <xsl:for-each select="//root/element[group=$current-group]">
    <group>
      <id><xsl:value-of select="id"/></id>
    </group>
  </xsl:for-each>
</xsl:template>

【讨论】:

  • 这些模板在:match="distinct-values(group)" 中生成错误消息:预期表达式结束,找到'('。distinct-values -->(
  • distinct-values() 函数是 XPath 2 的一部分,不能在 XSLT 1.0 环境中工作
  • 对不起@Nic Gibson 是对的, distinct-values() 函数在 xslt 1.0 中不可用。我试图建议一个适用于 xslt 1.0 的变体
猜你喜欢
  • 2021-03-14
  • 1970-01-01
  • 2012-08-07
  • 2014-09-02
  • 2023-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多