我使用极简主义的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>
代表以下结构:
如何使用XSLT将此平面XML列表转换为嵌套HTML列表?
PS:这是Managing Hierarchical Data in MySQL的示例树。
答案 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 > 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>
更好。