首页 > 解决方案 > 使用 XSLT 重新排列 XML 同级

问题描述

我正在使用 XSLT 将 XML 文档转换为具有更多意义的新 XML 文档。源 XML:

<root>
  <A>
    <country>Italy</country>
    <city>Rome</city>
    <score>13</score>
  </A>
  <A>
    <country>Italy</country>
    <city>Florence</city>
    <score>14</score>
  </A>
  <A>
    <country>France</country>
    <city>Paris</city>
    <score>20</score>
  </A>
</root>

节点<country>和都是兄弟姐妹<city><score>我的问题是:如何在 XSLT 中像这样重新排列兄弟姐妹?

<country>
  <city>
    <score>
    </score>
  </city>
</country>

我预期的 XML:

<root>
  <Italy>
    <Rome>
      <score>13</score>
    </Rome>
    <Florence>
      <score>14</score>
    </Florence>
  </Italy>
  <France>
    <Paris>
      <score>20</score>
    </Paris>
  </France>
</root>

标签: xmlxslt

解决方案


XSLT-1.0 解决方案如下。它使用Muenchian Grouping作为一种方法来获得独特的国家价值。

编辑:
为了确保元素名称是有效的 QNames,我添加了一个translate(...)表达式,它将相应城市名称或国家名称中的所有空格转换为下划线。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="countries" match="A" use="country" />

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

    <xsl:template match="text()" />    

    <xsl:template match="A[generate-id(.) = generate-id(key('countries',country)[1])]">
        <xsl:element name="{translate(country,' ','_')}">
            <xsl:for-each select="key('countries',country)">
                <xsl:element name="{translate(city,' ','_')}">
                    <xsl:copy-of select="score" />
                </xsl:element>           
            </xsl:for-each>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

XSLT-2.0 解决方案更简单,因为它可以使用xsl:for-each-group

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:for-each-group select="A" group-by="country">
                <xsl:element name="{translate(current-grouping-key(),' ','_')}">
                    <xsl:for-each select="current-group()">
                        <xsl:element name="{translate(city,' ','_')}">
                            <xsl:copy-of select="score" />
                        </xsl:element>           
                    </xsl:for-each>
                </xsl:element>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

两种方法的输出是相同的:

<?xml version="1.0"?>
<root>
    <Italy>
        <Rome>
            <score>13</score>
        </Rome>
        <Florence>
            <score>14</score>
        </Florence>
    </Italy>
    <France>
        <Paris>
            <score>20</score>
        </Paris>
    </France>
</root>

推荐阅读