【问题标题】:How to build a recursive navigation in XSL/XML如何在 XSL/XML 中构建递归导航
【发布时间】:2013-10-31 22:59:31
【问题描述】:

我正在尝试构建一个理论上可以使用 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>

非常感谢任何帮助。

谢谢!

【问题讨论】:

  • 为什么你认为代码有问题?有错误吗?输出错了吗?
  • 在导航级别 2 之后没有递归,在级别 2 之后输出不存在,我不知道如何应用递归。我觉得这应该更容易,但我在 XSL 方面的技能并不是那么好。为什么投反对票?
  • 我没有投反对票,但我认为是因为最初的问题并不清楚。编辑您的评论,它可能会消失。

标签: xml xslt recursion xslt-1.0


【解决方案1】:

您的 XSLT 非常专注于 "pull" focused rather than "push",并且可以使用一些重新设计以使其更加功能(在函数式编程意义上)。

但主要的错误在这里:

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

这是声明"named template" but it's never called。不是坏事,就像name/call-templates should be avoided where possible.

将其更改为以下内容,当使用 XSLT 中已有的 apply-templates 时,它应该可以正常工作。

<xsl:template match="folder">

编辑:推式方法

这可能并不完美,但有几点: * 当&lt;folders&gt; 是列表 时,您完全跳过&lt;folders&gt;,而&lt;folder&gt;s 是列表项。 * 您似乎重复了 1、2 和 3 级的项目代码。 * 3级及以上你只制作过一件,其他的都会被忽略?

请注意,我已经定义了我希望如何根据级别渲染&lt;folders&gt;,以及如何独立渲染&lt;folder&gt;s。

<?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>

请注意,上面的模板比最初的模板要短,因为重复的元素被删除了。所有&lt;folder&gt;s 渲染相同,因此只需要一个模板。事实上,&lt;folder&gt;s 不需要知道他们的级别,只有父 &lt;folders&gt; 知道,这样逻辑就会移动到正确的模板中。

不熟悉 XSLT 的人经常忽略如何有效地使用 apply-templates 来定义递归并允许元素之间非常清晰的分隔。在创建模板时,请尝试关注应该如何表示 this 特定元素,并了解可能需要其子元素的方式。然后,专注于子元素的新模板以及应该如何呈现它们等等。

除此之外,请始终留意@selects 或&lt;value-of&gt;s 中的../,因为这意味着您的逻辑依赖于当前“范围”之外的元素。并不总是很糟糕,但需要注意一些事情。

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

【讨论】:

  • 好的,我会尝试,但你说的有道理。我觉得这段代码非常注重拉力。我在网上看到了一些关于 XSLT 中强大递归功能的示例,但我正在努力将其应用于我的导航。如果这可行,我会接受答案,但看到你的“推动”风格真的很棒。 :)
  • 嗯,这确实对乐高有用。谢谢!对于如何重新调整此代码以使其更实用,您有什么建议吗?
  • @dnyce 我添加了一个示例“推送”模板供您使用。
  • 你是我的新英雄!仅此一项就值得多投赞成票... :) 我剩下的唯一问题是如何处理前两位父母的案件? (导航的第 1 级和第 2 级)我们必须为这 2 级申请的类名与第 3 级及以上文件夹的类名略有不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2021-02-09
  • 2023-01-28
  • 2018-12-29
  • 1970-01-01
  • 2019-01-28
相关资源
最近更新 更多