如何使用XSLT从平面XML列表构建树?

时间:2009-03-10 10:49:02

标签: xslt tree

我使用极简主义的MVC框架,其中 PHP控制器 DOM模型交给 XSLT视图(cf okapi )。

为了构建导航树,我在MYSQL中使用了嵌套集。这样,我最终得到了一个模型XML,如下所示:

<tree>
    <node>
        <name>root</name>
        <depth>0</depth>
    </node>
    <node>
        <name>TELEVISIONS</name>
        <depth>1</depth>
    </node>
    <node>
        <name>TUBE</name>
        <depth>2</depth>
    </node>
    <node>
        <name>LCD</name>
        <depth>2</depth>
    </node>
    <node>
        <name>PLASMA</name>
        <depth>2</depth>
    </node>
    <node>
        <name>PORTABLE ELECTRONICS</name>
        <depth>1</depth>
    </node>
    <node>
        <name>MP3 PLAYERS</name>
        <depth>2</depth>
    </node>
    <node>
        <name>FLASH</name>
        <depth>3</depth>
    </node>
    <node>
        <name>CD PLAYERS</name>
        <depth>2</depth>
    </node>
    <node>
        <name>2 WAY RADIOS</name>
        <depth>2</depth>
    </node>
</tree>

代表以下结构:

    • TELEVISIONS
      • TUBE
      • LCD
      • PLASMA
    • 便携式电子产品
      • MP3播放器
        • FLASH
      • CD播放器
      • 2 WAY RADIOS

如何使用XSLT将此平面XML列表转换为嵌套HTML列表?

PS:这是Managing Hierarchical Data in MySQL的示例树。

4 个答案:

答案 0 :(得分:5)

这种形式的平面列表很难在xslt中使用,因为你需要找到下一个分组的位置,等等。你能使用不同的xml吗?例如,使用平面xml:

<?xml version="1.0" encoding="utf-8" ?>
<tree>
  <node key="0">root</node>
  <node key="1" parent="0">TELEVISIONS</node>
  <node key="2" parent="1">TUBE</node>
  <node key="3" parent="1">LCD</node>
  <node key="4" parent="1">PLASMA</node>
  <node key="5" parent="0">PORTABLE ELECTRONICS</node>
  <node key="6" parent="5">MP3 PLAYERS</node>
  <node key="7" parent="6">FLASH</node>
  <node key="8" parent="5">CD PLAYERS</node>
  <node key="9" parent="5">2 WAY RADIOS</node>
</tree>

做得非常有效(非常有效):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="nodeChildren" match="/tree/node" use="@parent"/>
  <xsl:template match="tree">
    <ul>
      <xsl:apply-templates select="node[not(@parent)]"/>
    </ul>
  </xsl:template>
  <xsl:template match="node">
    <li>
      <xsl:value-of select="."/>
      <ul>
        <xsl:apply-templates select="key('nodeChildren',@key)"/>
      </ul>
    </li>
  </xsl:template>
</xsl:stylesheet>

这是一个选择吗?

当然,如果将xml构建为层次结构,则更容易;-p

答案 1 :(得分:1)

在XSLT 2.0中,使用新的分组功能会相当容易。

在XSLT 1.0中,它有点复杂,但这有效:

<xsl:template match="/tree">
    <xhtml>
        <head/>
        <body>
            <ul>
                <xsl:apply-templates select="node[depth='0']"/>
                </ul>
            </body>
        </xhtml>
    </xsl:template>

<xsl:template match="node">
    <xsl:variable name="thisNodeId" select="generate-id(.)"/>
    <xsl:variable name="depth" select="depth"/>
    <xsl:variable name="descendants">
        <xsl:apply-templates select="following-sibling::node[depth = $depth + 1][preceding-sibling::node[depth = $depth][1]/generate-id() = $thisNodeId]"/>
        </xsl:variable>
    <li>
        <xsl:value-of select="name"/>
        </li>
    <xsl:if test="$descendants/*">
        <ul>
            <xsl:copy-of select="$descendants"/>
            </ul>
        </xsl:if>
    </xsl:template>

问题的核心是长而丑陋的“后代”变量,它在当前节点之后查找节点,其中“深度”子节点大于当前深度,但不是在具有相同节点的另一个节点之后深度作为当前深度(因为如果它们是,那么它们将是该节点的子节点而不是当前节点的子节点。)

BTW你的示例结果中有一个错误:“FLASH”应该是“MP3播放器”的孩子而不是兄弟姐妹。

修改

实际上(正如评论中所提到的),在“纯粹的”XSLT 1.0中,这不起作用有两个原因:路径表达式错误地使用generate-id(),并且不能使用“结果树片段”路径表达。

这是一个正确的XSLT 1.0版本的“node”模板(使用Saxon 6.5成功测试),它不使用EXSLT,也不使用XSLT 1.1:

<xsl:template match="node">
    <xsl:variable name="thisNodeId" select="generate-id(.)"/>
    <xsl:variable name="depth" select="depth"/>
    <xsl:variable name="descendants">
        <xsl:apply-templates select="following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId]"/>
        </xsl:variable>
    <xsl:variable name="descendantsNb">
        <xsl:value-of select="count(following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId])"/>
        </xsl:variable>
    <li>
        <xsl:value-of select="name"/>
        </li>
    <xsl:if test="$descendantsNb &gt; 0">
        <ul>
            <xsl:copy-of select="$descendants"/>
            </ul>
        </xsl:if>
    </xsl:template>

当然,应该考虑重复的路径表达式,但是没有能力将“结果树片段”转换为实际可以处理的XML,我不知道它是否可能? (编写自定义函数当然可以解决问题,但是使用EXSLT会更简单)

底线:如果可以,请使用XSLT 1.1或EXSLT!

第二次编辑

为了避免重复路径表达式,你也可以完全忘记测试,这只会导致一些空的,你可以留在结果中或后处理消除。

答案 2 :(得分:1)

答案 3 :(得分:0)

你实际上并没有说过你希望html输出看起来像什么,但是我可以告诉你,从XSLT的角度来看,从平面结构转向一棵树将是复杂而昂贵的,如果你'这也是基于树中物品的位置及其与兄弟姐妹的关系。

提供<parent>属性/节点比<depth>更好。