0

我有一个非常扁平的 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>但也感觉必须有一个更......'模板'的方式来做到这一点。任何人都可以帮忙吗?

干杯。

4

6 回答 6

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>
于 2010-03-25T17:01:57.100 回答
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>
于 2010-03-25T17:07:21.883 回答
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>

并重复其他类型。我已经对其进行了编码,它似乎工作得很好。

于 2010-03-25T15:48:34.610 回答
0
<a-things>
    <xsl:for-each select="thing[type = 'a']">
        <a><xsl:value-of select="./value" /></a>
    </xsl:for-each>
</a-things>

如果您想变得非常时髦,请将<a-things>and 谓词替换为参数并使用属性值模板:

<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>
于 2010-03-25T15:52:37.740 回答
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>
于 2010-03-25T16:15:46.710 回答
0

在 XSLT 2 中,您可以非常优雅地做到这一点。假设您有一个模板,用于在将每个事物包装在 <a> 元素中之前对其进行格式化:

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

然后,您可以将其应用于某些事物中的每个事物,$type以通过函数构建 <a-things> 元素:

<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>
于 2010-03-25T17:31:35.667 回答