【问题标题】:Creating a nested tree structure from a path in XSLT从 XSLT 中的路径创建嵌套树结构
【发布时间】:2009-05-16 09:35:53
【问题描述】:

我有一个动态 XML 文档,它表示类别的树结构,但使用路径分隔属性以任意顺序这样做 - 像这样:

   <data>    
      <record ID="24" Name="category 1\sub category 1"/>   
      <record ID="26" Name="category 1"/>     
      <record ID="25" Name="category 1\sub category 1\sub category 2"/>    
      <record ID="27" Name="category 1\sub category 1\sub category 3"/>    
      ...
   </data> 

我需要想出一个“规范化”XML 的解决方案,以便将其转换为如下内容:

   <data>    
      <record ID="26" Name="category 1">    
         <record ID="24" Name="sub category 1">    
            <record ID="25" Name="sub category 2"/>
            <record ID="27" Name="sub category 3"/>    
         </record>
      </record>   
      ...
   </data>

基本上我想知道这是否是 XSLT 可能能够解决的问题,以及如何解决,而不是必须以编程方式解决。

【问题讨论】:

  • @myso:“子类别 3”与输入 XML 中的“子类别 2”处于同一级别。它不能以您在输出 XML 中显示的方式嵌套。
  • 是的,我的错。已编辑。

标签: xml xslt


【解决方案1】:

当然,没问题:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>

  <xsl:output indent="yes" />

  <xsl:template match="/data">
    <!-- copy the document element -->
    <xsl:copy>
      <!-- That's where we start: all "record" nodes that have no "\". -->
      <xsl:apply-templates mode="recurse" select="/data/record[
        not(contains(@Name, '\'))
      ]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="record" mode="recurse">
    <xsl:param name="starting-path" select="''" />

    <!-- The record node and its ID attribute can be copied. --> 
    <xsl:copy>
      <xsl:copy-of select="@ID" />

      <!-- Create the new "name" attribute. -->
      <xsl:attribute name="Name">
        <xsl:value-of select="substring-after(@Name, $starting-path)" />
      </xsl:attribute>

      <!-- Append a backslash to the current path. -->
      <xsl:variable name="current-path" select="concat(@Name, '\')" />

      <!-- Select all "record" nodes that are one level deeper... -->
      <xsl:variable name="children" select="/data/record[
        starts-with(@Name, $current-path)
        and
        not(contains(substring-after(@Name, $current-path), '\'))
      ]" />

      <!-- ...and apply this template to them. -->
      <xsl:apply-templates mode="recurse" select="$children">
        <xsl:with-param name="starting-path" select="$current-path" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

我的系统上的输出:

<data>
  <record ID="26" Name="category 1">
    <record ID="24" Name="sub category 1">
      <record ID="25" Name="sub category 2"></record>
      <record ID="27" Name="sub category 3"></record>
    </record>
  </record>
</data>

请注意,整个解决方案基于所有路径都是规范且不包含尾部反斜杠的假设。

另请注意,任何不匹配/孤立的“记录”元素都不会出现在输出中(当然,除非它们位于根级别)。

还有一件事:模板模式(“递归”)并不是绝对必要的。我包含它是因为模板正在做一些相当特别的事情,并且您的解决方案中可能有另一个模板与“记录”节点匹配。在这种情况下,这个解决方案可以在不破坏其他任何东西的情况下被放入。对于独立解决方案,可以随时删除模板模式。

哦,最后一件事:如果您希望结果文档按名称排序,请在 &lt;xsl:apply-templates&gt; 中包含 &lt;xsl:sort&gt; element(两次出现),如下所示:

<xsl:apply-templates select="...">
  <xsl:sort select="@Name" data-type="text" order="ascending" />
</xsl:apply-templates>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多