如何在 XSL/XML 中构建递归导航



我正在尝试构建一个理论上可以使用XSL无限递归的导航。不幸的是,我在这个领域的技能仍在培养中。谁能告诉我这段代码哪里出了问题?

这里的问题是,导航的前 2 个级别有些独特(类名等),但在第 3 级之后,导航几乎是一遍又一遍嵌套的相同元素。

要更新:导航级别 2 之后没有递归,级别 2 之后输出不存在,我不知道如何应用递归。我觉得这应该更容易,但我在 XSL 方面的技能不是那么好。

HTML(我们需要它产生什么):

  <ul class="nav-l1">
    <li class="nav-active"><a href="nav2.html" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">First Level Parent</span></span></a>
      <ul class="nav-l2 nav-hidden">
        <li class="nav-active"><a href="nav2.html" class="nav-item"><span>Overview</span></a></li>
        <li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">Second Level Parent</span></span></a>
          <ul class="nav-ls nav-hidden">
            <li><a href="#" class="nav-item"><span>Third Level</span></a></li>
            <li><a href="#" class="nav-item"><span>Third Level</span></a></li>
          </ul>
        </li>
        <li><a href="#" class="nav-item"><span>Second Level</span></a></li>
      </ul>
    </li>
    <li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">First Level Parent</span></span></a>
      <!-- 2nd level of navigation.  -->
      <ul class="nav-l2 nav-hidden">
        <li><a href="#" class="nav-item"><span>Overview</span></a></li>
        <li><a href="#" class="nav-item trigger-chan nav-next"><span><span class="trigger-cntr">Second Level Parent</span></span></a>
          <!-- 3rd level of navigation.  -->
          <ul class="nav-ls nav-hidden">
            <li><a href="#" class="nav-item"><span>Third Level</span></a></li>
            <li><a href="#" class="nav-item"><span>Third Level</span></a></li>
            <li><a href="#" class="nav-item"><span>Third Level</span></a></li>
          </ul>
        </li>
        <li><a href="#" class="nav-item"><span>Second Level</span></a></li>
        <li><a href="#" class="nav-item"><span>Second Level</span></a></li>
      </ul>
    </li>
  </ul>

示例 XML(初始数据馈送的格式):

<data>
    <folders level="1">
        <folder clickable="Y" url="/lorem/ipsum.html" name="Lorem Ipsum"/>
        <folder clickable="Y" url="/level/one.html" name="Level One"/>
        <folder clickable="Y" url="/foo/bar.html" name="Foo Bar">
            <folders level="2">
                <folder clickable="Y" url="/level/two.html" name="Level two"/>
                <folder clickable="Y" url="/child/item.html" name="Child Item">
                    <folders level="3">
                        <folder clickable="Y" url="/child/child/item.html" name="Child's Child Item">
                            <folders level="4">
                                <folder clickable="Y" url="/destiny/child/item.html" name="Destiny's Child"/>
                            </folders>
                        </folder>
                    </folders>
                </folder>
            </folders>
        </folder>
    </folders>
</data>

XSL(我们需要什么来转换XML):

    <?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes" indent="yes"/>
  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="/data/folders">
          <!-- NAVIGATION BEGINS HERE -->
          <ul class="nav-l1">
            <!-- LEVEL 1 -->
            <xsl:for-each select="/data/folders[@level=1]/folder[not (@clickable ) or @clickable ='Y' ]">
              <li>
                <xsl:if test="@selected='Y'">
                  <xsl:attribute name="class">nav-active</xsl:attribute>
                </xsl:if>
                <a href="{@url}" class="nav-item">
                  <xsl:choose>
                    <xsl:when test="child::*">
                      <xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
                      <span>
                            <span class="trigger-cntr">
                              <xsl:value-of select="@name"/>
                            </span>
                      </span>
                    </xsl:when>
                    <xsl:otherwise>
                      <span><xsl:value-of select="@name" /></span>
                    </xsl:otherwise>
                  </xsl:choose>
                </a>
                <!-- LEVEL 2 -->
                <xsl:choose>
                  <xsl:when test="child::*">
                    <ul class="nav-l2 nav-hidden">
                      <xsl:for-each select="folders[@level=2]/folder[not (@clickable ) or @clickable ='Y' ]">
                        <li>
                          <xsl:if test="@selected='Y'">
                            <xsl:attribute name="class">nav-active</xsl:attribute>
                          </xsl:if>
                          <a href="{@url}" class="nav-item">
                            <xsl:choose>
                              <xsl:when test="child::*">
                                <xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
                                <span>
                                <span class="trigger-cntr">
                                  <xsl:value-of select="@name"/>
                                </span>
                              </span>
                              </xsl:when>
                              <xsl:otherwise>
                                <span><xsl:value-of select="@name" /></span>
                              </xsl:otherwise>
                            </xsl:choose>
                          </a>
                          <!-- LEVEL 3 and beyond ... -->
                          <xsl:apply-templates />
                        </li>
                      </xsl:for-each>
                    </ul>
                  </xsl:when>
                  <xsl:otherwise>
                  </xsl:otherwise>
                </xsl:choose>
              </li>
            </xsl:for-each>
          </ul>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
<!-- Infinity and beyond (=> Level 3) -->
  <xsl:template name="folder">
    <ul class="nav-ls nav-hidden">
      <li>
          <xsl:if test="@selected='Y'">
            <xsl:attribute name="class">nav-active</xsl:attribute>
          </xsl:if>
          <a href="{@url}" class="nav-item">
            <xsl:choose>
              <xsl:when test="child::*">
                <xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
            <span>
                      <span class="trigger-cntr">
                        <xsl:value-of select="@name"/>
                      </span>
                </span>
              </xsl:when>
              <xsl:otherwise>
                <span><xsl:value-of select="@name" /></span>
              </xsl:otherwise>
            </xsl:choose>
          </a>
        <xsl:apply-templates select="folders[@level>3]/folder" />
      </li>
    </ul>
  </xsl:template>
</xsl:stylesheet>

任何帮助将不胜感激。

谢谢!

您的 XSLT 非常注重"拉动"而不是"推",并且可以使用一些重新设计来使其更具功能性(在函数式编程意义上)。

但主要错误在这里:

<!-- Infinity and beyond (=> Level 3) -->
<xsl:template name="folder">

这是声明一个"命名模板",但它从未被调用。这不是一件坏事,因为应尽可能避免使用名称/呼叫模板。

将其更改为以下内容,在使用 XSLT 中已有apply-templates时,它应该按预期工作。

<xsl:template match="folder">

编辑:推送式方法

这可能并不完美,但有几点: * 当它们是列表时,您完全跳过<folders>,而<folder>列表项。 * 您似乎复制了级别 1、2 和 3 的项目代码。 *你只制作一个3级及以上的物品,其他的都会被忽略吗?

请注意,我已经根据级别定义了我希望如何呈现<folders>,以及如何独立渲染<folder>

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>
<xsl:template match="folders">
    <ul>
        <xsl:attribute name="class">
            <xsl:text>nav-l</xsl:text>
            <xsl:choose>
                <xsl:when test="@level&gt;2">
                    <xsl:text>s nav-hidden</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="@level"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
        <xsl:apply-templates/>
    </ul>
</xsl:template>
<xsl:template match="folder">
    <li>
        <xsl:if test="@selected='Y'">
            <xsl:attribute name="class">nav-active</xsl:attribute>
        </xsl:if>
        <a href="{@url}" class="nav-item">
            <xsl:choose>
                <xsl:when test="child::*">
                    <xsl:attribute name="class">trigger-chan nav-next</xsl:attribute>
                    <span>
                        <span class="trigger-cntr">
                            <xsl:value-of select="@name"/>
                        </span>
                    </span>
                </xsl:when>
                <xsl:otherwise>
                    <span>
                        <xsl:value-of select="@name"/>
                    </span>
                </xsl:otherwise>
            </xsl:choose>
        </a>
        <xsl:apply-templates select="folders"/>
    </li>
</xsl:template>
</xsl:stylesheet>

请注意上面的模板比初始模板短,因为重复的元素被删除了。所有<folder>的渲染都相同,因此只需要一个模板。事实上,<folder>不需要知道它们的级别,只有父级<folders>知道,以便逻辑移动到正确的模板。

刚接触 XSLT 的人经常忽略如何有效地使用apply-templates来定义递归并允许元素之间非常清晰的分离。创建模板时,请尝试关注应如何表示特定元素,并了解如何需要其子元素。然后,重点关注子元素的新模板以及应如何呈现这些模板等。

除此之外,请始终注意@select s 或 <value-of> s 中的../,因为这意味着您的逻辑依赖于当前"范围"之外的元素。并不总是坏事,但需要注意一些事情。

尝试尽可能减少for-each,看看是否可以重写它们以使用apply-templates,除非出于某种奇怪的原因绝对需要,否则尽量不要使用call-templates因为它们的功能较差。

相关内容

  • 没有找到相关文章