1

我有一个这样的 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>

我想将类别标签的每个子集包装成一个集合标签(类别)。这里的问题是这是一棵递归树,树的深度是未知的。

我尝试为此使用 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>

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

4

1 回答 1

4

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

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

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

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

您可以采取的一种方法是让您的模板与任何Category元素的父级匹配(已排除Categories元素)

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

然后,您可以在其中复制元素,并在其中插入一个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之后的任何元素,这些元素将被移动到创建的Categories元素之前。

另一种方法是匹配父元素中第一次出现的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 在寻找与节点匹配的模板时,总是优先考虑更具体的模板匹配。因此,“Category[1]”将被用于第一个子Category元素的“Category”。

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

于 2013-07-31T12:21:23.497 回答