XSLT:走一个树状的结构

时间:2009-03-12 11:34:00

标签: xml xslt tree

我有一个xml文档,其中包含类别列表:

<categories>
    <category id="1" parent="0">Configurations</category>
    <category id="11" parent="13">LCD Monitor</category>
    <category id="12" parent="13">CRT Monitor</category>
    <category id="13" parent="1"">Monitors</category>
    <category id="123" parent="122">Printer</category>
    ...
</categories>

产品清单:

<products>
  <product>
    ...
   <category>12</category>
    ...
  </product>
    ...
</products>

如果产品的类别等于12,则应将其转换为“Configurations / Monitors / CRT Monitor”(取12类,然后是父类(13)等)。如果parent为0,则停止。

使用XSL转换是否有一种优雅的方法?

4 个答案:

答案 0 :(得分:4)

我不知道这是否会被认为是优雅的,但有了这个输入:

<root>
    <categories>
        <category id="1" parent="0">Configurations</category>
        <category id="11" parent="13">LCD Monitor</category>
        <category id="12" parent="13">CRT Monitor</category>
        <category id="13" parent="1">Monitors</category>
        <category id="123" parent="122">Printer</category>
    </categories>
    <products>
        <product>
             <category>12</category>
        </product>
        <product>
             <category>11</category>
        </product>
     </products>
</root>

这个XSLT:

<?xml version="1.0"?>

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

<xsl:template match="/">
  <root>
  <xsl:apply-templates select="//product"/>
  </root>
</xsl:template>

<xsl:template match="product">
  <product>
    <path>
      <xsl:call-template name="catwalk">
        <xsl:with-param name="id"><xsl:value-of select="category"/>
        </xsl:with-param>
      </xsl:call-template>
    </path>
  </product>
</xsl:template>

<xsl:template name="catwalk">
  <xsl:param name="id"/>
  <xsl:if test="$id != '0'">
    <xsl:call-template name="catwalk">
      <xsl:with-param name="id"><xsl:value-of select="//category[@id = $id]/@parent"/>
      </xsl:with-param>
    </xsl:call-template>
    <xsl:value-of select="//category[@id = $id]"/><xsl:text>/</xsl:text>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

会给你这个输出:

<?xml version="1.0" encoding="utf-8"?>
  <root>
  <product>
    <path>Configurations/Monitors/CRT Monitor/
    </path>
  </product>
  <product>
     <path>Configurations/Monitors/LCD Monitor/
     </path>
  </product>
  </root>

路径仍然有一个额外的尾部斜杠,你需要另外一点条件XSLT才能使斜线只在你不在第一级时发出。

至关重要的是,您的类别层次结构是正确的,否则您的转换很容易进入无限循环,只有在内存不足时才会停止。如果我在实际系统中实现这样的东西,我很想在catWalk模板中添加一个参数,该模板在每次调用时递增并将其添加到测试中,以便在10次调用后停止循环,无论是否已找到父级

答案 1 :(得分:3)

建议使用<xsl:key>

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

  <xsl:output method="text" />

  <xsl:key name="category" match="categories/category" use="@id" />

  <xsl:template match="/">
    <xsl:apply-templates select="//products/product" />
  </xsl:template>

  <xsl:template match="product">
    <xsl:apply-templates select="key('category', category)" />
    <xsl:text>&#10;</xsl:text>
  </xsl:template>

  <xsl:template match="category">
    <xsl:if test="@parent &gt; 0">
      <xsl:apply-templates select="key('category', @parent)" />
      <xsl:text>/</xsl:text>
    </xsl:if>
    <xsl:value-of select="."/>
  </xsl:template>

</xsl:stylesheet>

这会产生:

Configurations/Monitors/LCD Monitor
Configurations/Monitors/CRT Monitor

在以下XML上测试时:

<data>
  <categories>
    <category id="1" parent="0">Configurations</category>
    <category id="11" parent="13">LCD Monitor</category>
    <category id="12" parent="13">CRT Monitor</category>
    <category id="13" parent="1">Monitors</category>
    <category id="123" parent="122">Printer</category>
  </categories>
  <products>
    <product>
      <category>11</category>
    </product>
    <product>
      <category>12</category>
    </product>
  </products>
</data>

答案 2 :(得分:1)

这应该让你足够接近(我已经把xslt代码放在这里了,所以我已经逃脱了它,希望它能正常工作

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

  <xsl:output omit-xml-declaration="yes"/>

  <xsl:template match="/">
    <xsl:call-template name="OutputCategoryTree">
      <xsl:with-param name="productId" select="12"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="OutputCategoryTree">
    <xsl:param name="productId"/>
    <xsl:variable name="parentId" select="/categories/category[@id=$productId]/@parent"/>
    <xsl:if test="$parentId!=0"> 
      <xsl:call-template name="OutputCategoryTree">
        <xsl:with-param name="productId" select="/categories/category[@id=$productId]/@parent"/>
      </xsl:call-template>
    </xsl:if>/
    <xsl:value-of select="/categories/category[@id=$productId]"/>
  </xsl:template>
</xsl:stylesheet>

很抱歉粗略的示例代码,但确实生成了

  

/配置/监视器/ CRT监视器

答案 3 :(得分:1)

您可以考虑首先将类别文档从平面的节点列表转换为层次结构。这简化了转换输入文档的问题。此外,如果您的产品列表很大,它的性能会比搜索类别层次结构中每个步骤的类别的平面列表的方法好得多。

<xsl:template match="categories">
    <categories>
        <xsl:apply-templates select="category[@parent='0']"/>
    </categories>
</xsl:template>

<xsl:template match="category">
    <category id='{@id}'>
       <xsl:value-of select="text()"/>
       <xsl:apply-templates select="/categories/category[@parent=current()/@id]"/>
    </category>
</xsl:template>

这会产生这样的结果:

<categories>
    <category id="1">Configurations
       <category id="13">Monitors
          <category id="11">LCD Monitor</category>
           <category id="12">CRT Monitor</category>
       </category>
    </category>
    ...
</categories>

假设您已将转换后的类别文档作为参数传递到XSLT中(或使用document()函数将其读入变量),产品模板变得非常简单:

<xsl:template match="product"/>
   <xsl:variable name="c" select="$categories/categories/category[@id=current()/category]"/>
   <xsl:foreach select="$c/ancestor-or-self::category">
      <xsl:value-of select="text()"/>
      <xsl:if test="position() != last()">
         <xsl:text>/</xsl:text>
      </xsl:if>
   </xsl:foreach>
</xsl:template>