XSLT将同名兄弟节点的值合并/连接到单个节点中

Posted

技术标签:

【中文标题】XSLT将同名兄弟节点的值合并/连接到单个节点中【英文标题】:XSLT merging/concatenating values of siblings nodes of same name into single node 【发布时间】:2012-09-25 15:15:29 【问题描述】:

输入xml

<catalog>
    <product id="1">
        <name>abc</name>
        <category>aaa</category>
        <category>bbb</category>
        <category>ccc</category>
    </product>
    <product id="2">
        <name>cde</name>
        <category>aaa</category>
        <category>bbb</category>
    </product>
</catalog>

预期输出 xml

<products>
    <product>
        <id>1</id>
        <name>abc</name>
        <category>aaa,bbb,ccc</category>
    </product>
    <product>
        <id>2</id>
        <name>cde</name>
        <category>aaa,bbb</category>
    </product>
</products>

用于转换的 XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/catalog">
        <products>
            <xsl:for-each select="product">
                <product>
                    <id><xsl:value-of select="@id"/></id>
                    <name><xsl:value-of select="name"/></name>
                    <category><xsl:value-of select="category" /></category>
                </product>
            </xsl:for-each>
        </products>
    </xsl:template>
</xsl:stylesheet>

实际输出 xml :(

<products>
    <product>
        <id>1</id>
        <name>abc</name>
        <category>aaa</category>
    </product>
    <product>
        <id>2</id>
        <name>cde</name>
        <category>aaa</category>
    </product>
</products>

循环遍历每个“产品”下名称为“类别”的所有兄弟节点并合并/连接到以逗号分隔的单个节点所需的代码。 “类别”的数量因每种产品而异,因此数量未知。

【问题讨论】:

【参考方案1】:

使用定义here的这个方便的加入调用模板,这变得很简单:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/catalog">
        <products>
            <xsl:for-each select="product">
                <product>
                    <id>
                        <xsl:value-of select="@id"/>
                    </id>
                    <name>
                        <xsl:value-of select="name"/>
                    </name>
                    <category>
                        <xsl:call-template name="join">
                            <xsl:with-param name="list" select="category" />
                            <xsl:with-param name="separator" select="','" />
                        </xsl:call-template>
                    </category>
                </product>
            </xsl:for-each>
        </products>
    </xsl:template>

    <xsl:template name="join">
        <xsl:param name="list" />
        <xsl:param name="separator"/>

        <xsl:for-each select="$list">
            <xsl:value-of select="." />
            <xsl:if test="position() != last()">
                <xsl:value-of select="$separator" />
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

输出:

<products>
  <product>
    <id>1</id>
    <name>abc</name>
    <category>aaa,bbb,ccc</category>
  </product>
  <product>
    <id>2</id>
    <name>cde</name>
    <category>aaa,bbb</category>
  </product>
</products>

【讨论】:

【参考方案2】:

在 XSLT 2.0 中,您只需要对代码进行一点小改动:

<category><xsl:value-of select="category" separator=","/></category>

请注意,如果您需要 XSLT 1.0 解决方案,最好这样说。在某些环境中,有些人卡在 1.0 上,但很多人没有。

【讨论】:

啊,是的,它是 1.0,xslt 样式表版本也是这么说的。 样式表上的版本号并没有告诉我们您正在使用的 XSLT 处理器的功能,或者项目迁移到更新处理器的能力。【参考方案3】:

这是另一个 XSLT 1.0 解决方案。

当这个 XSLT:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes" />
  <xsl:strip-space elements="*" />

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="product">
    <xsl:copy>
      <xsl:apply-templates select="*[not(self::category)]" />
      <category>
        <xsl:apply-templates select="category/text()" />
      </category>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="category/text()">
    <xsl:if test="position() &gt; 1">,</xsl:if>
    <xsl:value-of select="."/>
  </xsl:template>
</xsl:stylesheet>

...应用于 OP 的原始 XML:

<catalog>
  <product id="1">
    <name>abc</name>
    <category>aaa</category>
    <category>bbb</category>
    <category>ccc</category>
  </product>
  <product id="2">
    <name>cde</name>
    <category>aaa</category>
    <category>bbb</category>
  </product>
</catalog>

...产生了想要的结果:

<?xml version="1.0"?>
<catalog>
  <product>
    <name>abc</name>
    <category>aaa,bbb,ccc</category>
  </product>
  <product>
    <name>cde</name>
    <category>aaa,bbb</category>
  </product>
</catalog>

说明:

第一个模板——Identity Template——匹配所有节点和属性,并将它们原样复制到结果文档中。 第二个模板通过创建一个新的&lt;category&gt; 元素并处理文档当前位置中每个&lt;category&gt; 元素的文本子元素来覆盖标识模板。 最终模板根据需要输出文本值和逗号。

【讨论】:

没有尝试但很欣赏不同的解决方案 ABach :)

以上是关于XSLT将同名兄弟节点的值合并/连接到单个节点中的主要内容,如果未能解决你的问题,请参考以下文章

使用 XSLT 枚举同名节点

使用 XSLT 将 XML 节点组合成单个节点

XSLT 将来自多个节点的属性连接成单个值

使用 XSLT 将具有相同 ID 的元素 (XML) 合并到 txt 文件

XSLT 1.0 将同一级别的多个相同节点以不同的值分组

xslt将具有相同属性的相邻兄弟合并为一个,同时连接它们的文本