6

我使用了一个极简的 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>

它表示以下结构:

    • 电视
      • 管子
      • 液晶显示器
      • 等离子体
    • 便携式电子产品
      • MP3 播放器
        • 闪光
      • CD 播放器
      • 2路收音机

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

PS:这是在 MySQL 中管理分层数据的示例树。

4

4 回答 4

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

于 2009-03-10T10:56:02.047 回答
1

非常有帮助!

一个建议是移动模板内的 < ul > 会删除空的 ul。

<xsl:template match="tree">
      <xsl:apply-templates select="node[not(@parent)]"/>
  </xsl:template>
<xsl:template match="node">
   <ul>
    <li>
      <xsl:value-of select="."/>
      <xsl:apply-templates select="key('nodeChildren',@key)"/>  
    </li>
   </ul>
  </xsl:template>
</xsl:stylesheet>
于 2010-07-23T13:26:13.067 回答
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>

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

顺便说一句,您的示例结果中有一个错误:“FLASH”应该是“MP3 PLAYERS”的孩子而不是兄弟姐妹。

编辑

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

这是一个正确的 XSLT 1.0 版本的“节点”模板(使用 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!

第二次编辑

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

于 2009-03-10T12:01:12.683 回答
0

您实际上并没有说过您希望 html 输出看起来像什么,但我可以告诉您,从 XSLT 的角度来看,如果您也是,从平面结构到树将是复杂且昂贵的基于项目在树中的位置及其与兄弟姐妹的关系。

提供属性/节点提供.<parent><depth>

于 2009-03-10T10:57:18.813 回答