使用XSLT,如何根据节点的值分隔节点?

时间:2010-03-25 15:30:05

标签: xslt xpath saxon

我有一个非常扁平的XML结构,我需要重新排序到分类的部分,并且在我的生活中,我无法弄清楚如何在XSLT中做到这一点(不是说我无论如何都是专家。 )

基本上,原始XML看起来有点像:

<things>
  <thing>
    <value>one</value>
    <type>a</type>
  </thing>
  <thing>
    <value>two</value>
    <type>b</type>
  </thing>
  <thing>
    <value>thee</value>
    <type>b</type>
  </thing>
  <thing>
    <value>four</value>
    <type>a</type>
  </thing>
  <thing>
    <value>five</value>
    <type>d</type>
  </thing>
</things>

我需要输出类似的内容:

<data>
  <a-things>
    <a>one</a>
    <a>four</a>
  </a-things>
  <b-things>
    <b>two</b>
    <b>three</b>
  </b-things>
  <d-things>
    <d>five</d>
  </d-things>
</data>

请注意,如果没有任何<c-things>元素,我就无法输出<c>,但我事先知道完整的类型列表是什么,并且它很短,所以手写模板对于每种类型绝对是可能的。感觉好像我可以使用<xsl:if><xsl:for-each>一起破解某些东西,但也感觉必须有更多......'模板'的方法来做到这一点。有人可以帮忙吗?

干杯。

6 个答案:

答案 0 :(得分:4)

在使用Saxon时,请使用本机XSLT 2.0分组。

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

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

    <xsl:template match="things">
        <data>
            <xsl:for-each-group select="thing" group-by="type">
                <xsl:element name="{concat(current-grouping-key(),'-things')}">
                    <xsl:for-each select="current-group()">
                        <xsl:element name="{current-grouping-key()}">
                            <xsl:value-of select="value" />
                        </xsl:element>
                    </xsl:for-each>
                </xsl:element>
            </xsl:for-each-group>
        </data>
    </xsl:template>

</xsl:stylesheet>

在XSLT 1.0中,您可以使用键进行分组。这种方法称为 Muenchian Grouping

xsl:key定义包含thing元素的索引,按其type元素的字符串值进行分组。函数key()返回具有指定值的键中的所有节点。

外部xsl:for-each选择由thing返回的key()元素作为其值。

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

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

    <xsl:key name="thing" match="thing" use="type" />

    <xsl:template match="things">
        <data>
            <xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
                <xsl:element name="{concat(type,'-things')}">
                    <xsl:for-each select="key('thing',type)">
                        <xsl:element name="{type}">
                            <xsl:value-of select="value" />
                        </xsl:element>
                    </xsl:for-each>
                </xsl:element>
            </xsl:for-each>
        </data>
    </xsl:template>

</xsl:stylesheet>

答案 1 :(得分:1)

通用解决方案是使用XSL密钥:

<xsl:key name="kThingByType" match="thing" use="type" />

<xsl:template match="things">
  <xsl:copy>
    <xsl:apply-templates select="thing" mode="group">
      <xsl:sort select="type" />
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>

<xsl:template match="thing" mode="group">
  <xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
  <xsl:if test="generate-id() = generate-id($wholeGroup[1])">
    <xsl:element name="{type}-thing">
      <xsl:copy-of select="$wholeGroup/value" />
    </xsl:element>
  </xsl:if>
</xsl:template>

以上产量:

<things>
  <a-thing>
    <value>one</value>
    <value>four</value>
  </a-thing>
  <b-thing>
    <value>two</value>
    <value>thee</value>
  </b-thing>
  <d-thing>
    <value>five</value>
  </d-thing>
</things>

答案 2 :(得分:0)

当然,问这个问题很明显......

我的解决方案确实使用<xsl:if>,但我现在看不出它是怎么回事。我的解决方案基本上看起来像:

<xsl:if test="/things/thing/type = 'a'">
  <a-things>
    <xsl:apply-templates select="/things/thing[type='a']" mode="a" />
  </a-things>
</if>

<xsl:template match="/things/thing[type='a']" mode="a">
    <a><xsl:value-of select="value"/>
</xsl:template>

并重复其他类型。我把它编码了,似乎工作得很好。

答案 3 :(得分:0)

<a-things>
    <xsl:for-each select="thing[type = 'a']">
        <a><xsl:value-of select="./value" /></a>
    </xsl:for-each>
</a-things>

如果你想变得时髦,请用参数替换<a-things>和谓词,并使用属性值模板:

<xsl:param name="type" />
<xsl:element name="{$type}-things">
    <xsl:for-each select="thing[type = $type]">
        <xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
    </xsl:for-each>
</xsl:element>

答案 4 :(得分:0)

使用分组,您可以在没有if:

的情况下进行分组
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="things">
    <data>
      <xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
        <xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
          <xsl:element name="concat($type, '-things')">
            <xsl:for-each select="../thing[type=$type]">
              <xsl:element name="$type">
                <xsl:value-of select="value" />
              </xsl:element>
            </xsl:for-each>
          </xsl:element>
      </xsl:for-each>
    </data>
  </xsl:template>
</xsl:stylesheet>

答案 5 :(得分:0)

在XSLT 2中,您可以非常优雅地执行此操作。假设您有一个模板用于格式化每个事物,然后将其包装在&lt; a&gt;中。元素:

<xsl:template match="thing" mode="format-thing">
    <xsl:value-of select="value/text()"/>
</xsl:template>

然后,您可以将其应用于某些$type的每个内容,以构建&lt; a-things&gt;元素通过函数:

<xsl:function name="my:things-group" as="element()">
    <xsl:param name="type" as="xs:string"/>
    <xsl:param name="things" as="element(thing)*"/>

    <xsl:element name="{ concat($type, '-things') }">
        <xsl:for-each select="$things[type/text() eq $type]">
            <xsl:element name="{ $type }">
                <xsl:apply-templates select="." mode="format-thing"/>
            </xsl:element>
        </xsl:for-each>
    </xsl:element>
</xsl:function>

然后你可以为每个唯一类型(样本输入中的a,b,d)调用该函数来构建整个输出,然后你就完成了:

<xsl:template match="/">
    <data>
        <xsl:sequence select="
            for $type in distinct-values(things/thing/type/text())
            return my:things-group($type, /things/thing)
            "/>
    </data>
</xsl:template>