【问题标题】:XSLT: Walking a tree-like structureXSLT:遍历树状结构
【发布时间】:2009-03-12 11:34:00
【问题描述】:

我有一个 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 转换的优雅方法来做到这一点?

【问题讨论】:

    标签: xml xslt tree


    【解决方案1】:

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

    <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 次调用后停止循环,无论是否找到父级.

    【讨论】:

      【解决方案2】:

      建议使用&lt;xsl:key&gt;

      <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>
      

      这会产生:

      配置/显示器/液晶显示器 配置/监视器/CRT 监视器

      在以下 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>
      

      【讨论】:

        【解决方案3】:

        这应该让你足够接近(我一直在努力将 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 监视器

        【讨论】:

          【解决方案4】:

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

          <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>
          

          【讨论】:

            猜你喜欢
            • 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
            相关资源
            最近更新 更多