如何在 XSL/XML 中构建递归导航 [英] How to build a recursive navigation in XSL/XML

查看:33
本文介绍了如何在 XSL/XML 中构建递归导航的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to build a navigation that in theory could recurse infinitely with XSL. Unfortunately my skills in this arena are still being built. Can anyone tell me where I'm going wrong with this code?

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

The kicker here is that the first 2 levels of the nav are somewhat unique (class names and such) but after level 3 the nav is pretty much the same elements being nested over and over.

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

To Update: There's no recursion after nav level 2, the output is non existent after level 2, and I can't figure out how to apply the recursion. I feel like this should be easier but my skills in XSL are not that great.

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 非常专注于拉"而不是推",并且可以使用一些重新设计使其更具功能性(在函数式编程意义上).

Your XSLT is very "pull" focused rather than "push" focused and could use some re-engineering to be more functional (in the functional programming sense).

但主要错误在这里:

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

这是声明一个"命名模板" 但它从来没有被调用过.不是坏事,因为 name/应尽可能避免使用调用模板.

This is declaring a "named template" but it's never called. Not a bad thing, as name/call-templates should be avoided where possible.

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

Change it to the below and it should work as expected when using the apply-templates you already have in the XSLT.

<xsl:template match="folder">

推送式方法

这可能并不完美,但有几点:* 您完全跳过 ,当它们是列表,而 是列表项.* 您似乎重复了 1、2 和 3 级的物品代码.* 3级及以上你只做一件,其他的都会被忽略?

This might not be perfect, but a few things: * You skip over <folders> entirely, when they are the list, where as the <folder>s are the list items. * You appear to duplicate the item code for levels 1, 2 and 3. * You only ever make one item in the level 3 and above, all others would be ignored?

请注意,我已经根据级别定义了我希望 的呈现方式,以及 的独立呈现方式.

Notice below, that I've defined how I want <folders> to be rendered, based on level, and how <folder>s are rendered independently.

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

请注意上面的模板比最初的模板短,因为删除了重复的元素.所有 的渲染都是一样的,所以只需要一个模板.事实上, 不需要知道它们的级别,只有父 知道,所以逻辑被移动到正确的模板.

Notice how the above template is shorter than your initial one, as repeated elements are removed. All <folder>s render the same, so only one template is needed. In fact, the <folder>s don't need to know their level, only the parent <folders> do, so that logic is moved to the correct template.

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

People who are new to XSLT often overlook how apply-templates can be used effectively to define recursion and allow very clear separations between elements. When creating templates, try and focus on how this particular element should be represented, and knowing how its child elements may be required. Then, focus on a new template for the child elements and how those should be rendered and so on.

除上述之外,请始终注意 @selects 或 中的 ../>s 因为这意味着您的逻辑依赖于当前范围"之外的元素.并不总是很糟糕,但需要注意一些事情.

Adding on to the above, always be on the look out for ../ in @selects or <value-of>s as it implies that your logic is dependant on elements that are outside the current "scope". Not always bad, but something to be aware of.

在可能的情况下尝试最小化 for-each 并查看是否可以重写它们以使用 apply-templates,除非出于某些奇怪的原因绝对需要尝试并且永远不要使用调用模板,因为它们功能.

Try and minimise for-each where possible and see if they can be rewritten to use apply-templates, and unless absolutely required for some odd reason try and never use call-templates as they are less functional.

这篇关于如何在 XSL/XML 中构建递归导航的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆