XSLT将所有子元素包装在父标记中

时间:2013-07-31 08:15:30

标签: xml xslt

我有一个像这样的xml文档:

<Catalogs>
    <Catalog>
        <Code>x</Code>
        <Name>Ox</Name>
        <Categories>
            <Category>
                <Id>9245</Id>
                <Name>a</Name>
                <Category>
                    <Id>9247</Id>
                    <Name>x</Name>
                </Category>
            </Category>
            <Category>
                <Id>9250</Id>
                <Name>x</Name>
                <Category>
                    <Id>9252</Id>
                    <Name>x</Name>
                </Category>
                <Category>
                    <Id>9258</Id>
                    <Name>x</Name>
                    <Category>
                        <Id>9260</Id>
                        <Name>x</Name>
                    </Category>
                    <Category>
                        <Id>9261</Id>
                        <Name>x</Name>
                    </Category>
                    <Category>
                        <Id>9261</Id>
                        <Name>x</Name>
                    </Category>
                </Category>
            </Category>
            <Category>
                <Id>9251</Id>
                <Name>x</Name>
                <Category>
                    <Id>9253</Id>
                    <Name>x</Name>
                </Category>
            </Category>
        </Categories>
    </Catalog>
</Catalogs>

我想将Category-tags的每个子集包装到一个集合标签(Categories)中。 这里的问题是这是一个递归树,树的深度是未知的。

我尝试使用xslt转换,但还没有成功。我的尝试

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="Category">
  <Categories><xsl:apply-templates select="Category"/></Categories>
</xsl:template>

</xsl:stylesheet>

只需用空的分类标签替换所有孩子。

示例输出应该是这样的:

<Catalogs>
    <Catalog>
        <Code>x</Code>
        <Name>Ox</Name>
        <Categories>
            <Category>
                <Id>9245</Id>
                <Name>a</Name>
                <Categories>
                    <Category>
                        <Id>9247</Id>
                        <Name>x</Name>
                    </Category>
                </Categories>
            </Category>
            <Category>
                <Id>9250</Id>
                <Name>x</Name>
                <Categories>
                    <Category>
                        <Id>9252</Id>
                        <Name>x</Name>
                    </Category>
                    <Category>
                        <Id>9258</Id>
                        <Name>x</Name>
                        <Categories>
                            <Category>
                                <Id>9260</Id>
                                <Name>x</Name>
                            </Category>
                            <Category>
                                <Id>9261</Id>
                                <Name>x</Name>
                            </Category>
                            <Category>
                                <Id>9261</Id>
                                <Name>x</Name>
                            </Category>
                        </Categories>
                    </Category>
                </Categories>
            </Category>
            <Category>
                <Id>9251</Id>
                <Name>x</Name>
                <Categories>
                    <Category>
                        <Id>9253</Id>
                        <Name>x</Name>
                    </Category>
                </Categories>
            </Category>
        </Categories>
    </Catalog>
</Catalogs>

任何指针(或完整的解决方案)都将不胜感激。

1 个答案:

答案 0 :(得分:4)

首先,您应该在XSLT身份模板之上构建XSLT,该模板将复制XML中没有明确匹配模板的所有节点

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

如果没有这个,XSLT的内置模板将启动,只输出它找到的任何元素的文本。

您的XSLT的另一个问题是,当您匹配类别元素时,您正在执行<xsl:apply-templates select="Category"/>,这意味着您只是告诉XSLT查找任何类别< / strong>元素是当前元素的子元素。

您可以采取的一种方法是让您的模板与任何类别元素的父级匹配(不包括类别元素)

<xsl:template match="*[not(self::Categories)][Category]">

然后,在此内容中,您可以复制元素,并在其中插入类别元素以包含所有类别元素

<xsl:copy>
  <xsl:apply-templates select="@*|node()[not(self::Category)]"/>
  <Categories>
    <xsl:apply-templates select="Category"/>
  </Categories>
</xsl:copy>

在这种情况下,这是完整的XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="*[not(self::Categories)][Category]">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()[not(self::Category)]"/>
      <Categories>
        <xsl:apply-templates select="Category"/>
      </Categories>
    </xsl:copy>
  </xsl:template>

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

这种方法的一个缺点是,如果您在父级中的类别之后出现任何元素,这些元素将被移动到创建的类别元素之前

另一种方法是匹配父项中 Category 元素的第一次出现,然后复制该元素及其所有后续兄弟

尝试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="Category[1]">
    <Categories>
      <xsl:apply-templates select="self::*|following-sibling::Category" mode="categories"/>
    </Categories>
  </xsl:template>

  <xsl:template match="Category" mode="categories">
    <xsl:call-template name="identity" />
  </xsl:template>

  <xsl:template match="Category" />

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

关于这种方法需要注意两点。首先,在查找与节点匹配的模板时,XSLT将始终优先考虑更具体的模板匹配。因此,对于第一个类别元素,“类别[1]”将被用于“类别”。

其次,请注意此处使用模式,否则您将有两个模板匹配具有相同优先级的“类别”,这是不允许的。