1

我有一个 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,则应将其转换为“配置/监视器/CRT 监视器”(取类别 12,然后它是父级 (13) 等)。如果 parent 为 0,则停止。

有没有一种优雅的方法可以使用 XSL 转换来做到这一点?

4

4 回答 4

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

于 2009-03-12T12:01:59.390 回答
3

建议使用 an <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>

这会产生:

配置/显示器/液晶显示器
配置/监视器/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>
于 2009-03-12T12:59:12.433 回答
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 监视器

于 2009-03-12T12:02:32.773 回答
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>
于 2009-03-12T17:56:10.227 回答