【问题标题】:XSL to create nested list from flat tree problemXSL 从平面树问题创建嵌套列表
【发布时间】:2011-06-06 18:43:39
【问题描述】:

我需要能够从平面树创建嵌套列表。例如,输入可能是这样的:

<root>
    <h1>text</h1>
    <list level="1">num1</list>
    <list level="1">num2</list>
    <list level="2">sub-num1</list>
    <list level="2">sub-num2</list>
    <list level="3">sub-sub-num1</list>
    <list level="1">num3</list>
    <p>text</p>
    <list>num1</list>
    <list>num2</list>
    <h2>text</h2>
</root>

并且输出应该嵌套如下:

<root>
<h1>text</h1>
    <ol>
        <li>num1</li>
        <li>num2
             <ol>
                <li>sub-num1</li>
                <li>sub-num2
                    <ol>
                        <li>sub-sub-num1</li>
                    </ol>
                </li>
            </ol>
        </li>
        <li>num3</li>
    </ol>
    <p>text</p>
    <ol>
        <li>num1</li>
        <li>num2</li>
    </ol>
    <h2>text</h2>
</root>

我尝试了一些方法,但似乎无法获得它。任何帮助是极大的赞赏。 注意:我需要使用 XSLT 1.0 来执行此操作。

【问题讨论】:

  • @Phrog:不是这样。那里的树由结构 a 定义。这使用节点顺序和深度属性。
  • 很好的问题,+1。看我的答案,这比@Flack 的简单(不调用模板,不传递参数),可能比@Flack 的答案短,(我的 66 行,他的 53 行,但他的要宽得多,需要水平滚动)。

标签: xml xslt xpath xslt-grouping


【解决方案1】:

它几乎把我逼疯了,但我完成了。花了我将近 2 个小时。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="node() | @*">
    <xsl:copy>
        <xsl:apply-templates select="node() | @*"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="list[not(preceding-sibling::*[1][self::list])]">
    <ol>
        <xsl:variable name="selfId" select="generate-id()"/>
        <xsl:call-template name="recurseItems"/>
        <xsl:apply-templates select="
            following-sibling::list
            [@level = 1 or not(@level)]
            [preceding-sibling::*[1][self::list]]
            [$selfId = generate-id(
                preceding-sibling::list[not(preceding-sibling::*[1][self::list])][1]
                )
            ]
            [not(position() = 1)]
            " mode="recurse"/>
    </ol>
</xsl:template>

<xsl:template name="recurseItems">
    <xsl:param name="nodes" select="."/>
    <xsl:variable name="nextStep" select="$nodes/following-sibling::*[1][self::list]"/>
    <xsl:choose>
        <xsl:when test="$nodes/@level and ($nodes/@level &lt; $nextStep/@level)">
            <li>
                <xsl:value-of select="$nodes"/>
                <ol>
                    <xsl:call-template name="recurseItems">
                        <xsl:with-param name="nodes" select="$nextStep"/>
                    </xsl:call-template>
                </ol>
            </li>
        </xsl:when>
        <xsl:when test="$nodes/@level and ($nodes/@level > $nextStep/@level)">
            <xsl:apply-templates select="$nodes" mode="create"/>
        </xsl:when>
        <xsl:when test="$nextStep">
            <xsl:apply-templates select="$nodes" mode="create"/>
            <xsl:call-template name="recurseItems">
                <xsl:with-param name="nodes" select="$nextStep"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:when test="not($nextStep)">
            <xsl:apply-templates select="$nodes" mode="create"/>
        </xsl:when>
    </xsl:choose>
</xsl:template>

<xsl:template match="list" mode="recurse">
    <xsl:call-template name="recurseItems"/>
</xsl:template>

<xsl:template match="list" mode="create">
    <li>
        <xsl:value-of select="."/>
    </li>
</xsl:template>

<xsl:template match="list"/>

</xsl:stylesheet>

应用于稍微复杂的文档:

<root>
    <h1>text</h1>
    <list level="1">1.1</list>
    <list level="1">1.2</list>
    <list level="2">1.2.1</list>
    <list level="2">1.2.2</list>
    <list level="3">1.2.2.1</list>
    <list level="1">1.3</list>
    <p>text</p>
    <list>2.1</list>
    <list>2.2</list>
    <h2>text</h2>
    <h1>text</h1>
    <list level="1">3.1</list>
    <list level="1">3.2</list>
    <list level="2">3.2.1</list>
    <list level="2">3.2.2</list>
    <list level="3">3.2.2.1</list>
    <list level="1">3.3</list>
    <list level="2">3.3.1</list>
    <list level="2">3.3.2</list>
    <p>text</p>
</root>

它会产生这样的结果:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <h1>text</h1>
    <ol>
        <li>1.1</li>
        <li>1.2
            <ol>
                <li>1.2.1</li>
                <li>1.2.2
                    <ol>
                        <li>1.2.2.1</li>
                    </ol>
                </li>
            </ol>
        </li>
        <li>1.3</li>
    </ol>
    <p>text</p>
    <ol>
        <li>2.1</li>
        <li>2.2</li>
    </ol>
    <h2>text</h2>
    <h1>text</h1>
    <ol>
        <li>3.1</li>
        <li>3.2
            <ol>
                <li>3.2.1</li>
                <li>3.2.2
                    <ol>
                        <li>3.2.2.1</li>
                    </ol>
                </li>
            </ol>
        </li>
        <li>3.3
            <ol>
                <li>3.3.1</li>
                <li>3.3.2</li>
            </ol>
        </li>
    </ol>
    <p>text</p>
</root>

应用到您的样本中也会产生正确的结果:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <h1>text</h1>
    <ol>
        <li>num1</li>
        <li>num2
            <ol>
                <li>sub-num1</li>
                <li>sub-num2
                    <ol>
                        <li>sub-sub-num1</li>
                    </ol>
                </li>
            </ol>
        </li>
        <li>num3</li>
    </ol>
    <p>text</p>
    <ol>
        <li>num1</li>
        <li>num2</li>
    </ol>
    <h2>text</h2>
</root>

【讨论】:

  • 一个非常困难的问题的答案值得 +1。
【解决方案2】:

这个 XSLT 1.0 样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kListByParent"
             match="list"
             use="concat(generate-id(preceding-sibling::*
                                        [not(self::list)][1]),
                         '+',
                         generate-id(preceding-sibling::list
                                        [current()/@level > @level][1]))"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="list[preceding-sibling::*[1]/self::list]"/>
    <xsl:template match="list">
        <xsl:variable name="vListMark"
                      select="generate-id(preceding-sibling::*[1])"/>
        <ol>
            <xsl:apply-templates select="key('kListByParent',
                                             concat($vListMark,'+'))"
                                 mode="makeLi">
                <xsl:with-param name="pListMark" select="$vListMark"/>
            </xsl:apply-templates>
        </ol>
    </xsl:template>
    <xsl:template match="list" mode="makeLi">
        <xsl:param name="pListMark"/>
        <xsl:variable name="vChilds"
                      select="key('kListByParent',
                                  concat($pListMark,'+',generate-id()))"/>
        <li>
            <xsl:value-of select="."/>
            <xsl:if test="$vChilds">
                <ol>
                    <xsl:apply-templates select="$vChilds"
                                         mode="makeLi">
                        <xsl:with-param name="pListMark"
                                        select="$pListMark"/>
                    </xsl:apply-templates>
                </ol>
            </xsl:if>
        </li>
    </xsl:template>
</xsl:stylesheet>

输出:

<root>
    <h1>text</h1>
    <ol>
        <li>num1</li>
        <li>num2
            <ol>
                <li>sub-num1</li>
                <li>sub-num2
                    <ol>
                        <li>sub-sub-num1</li>
                    </ol>
                </li>
            </ol>
        </li>
        <li>num3</li>
    </ol>
    <p>text</p>
    <ol>
        <li>num1</li>
        <li>num2</li>
    </ol>
    <h2>text</h2>
</root>

注意current()XSLT函数在xsl:key/@use中的使用

【讨论】:

  • +1 表示一个好的答案——很难说其中一个答案比其他任何一个都好。
  • @Dimitre:你是对的!我想我们已经回答了这个问题,但我找不到问题...
  • +1。这是另一个 WebStorm 错误。我已经讨厌这个 IDE。删除之前的评论。
  • @Flack:你为什么不试试XSelerator
  • @Alejandro,UI 很糟糕,Win 7 存在一些问题。比如启动时出现异常,文件保存后无法撤消,自动完成错误。
【解决方案3】:

这种转变

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kListGroup" match="list"
  use="generate-id(
          preceding-sibling::node()[not(self::list)][1]
                   )"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()[1]|@*"/>
  </xsl:copy>
  <xsl:apply-templates select=
   "following-sibling::node()[1]"/>
 </xsl:template>

 <xsl:template match=
  "list[preceding-sibling::node()[1][not(self::list)]]">

  <ol>
    <xsl:apply-templates mode="listgroup" select=
     "key('kListGroup',
          generate-id(preceding-sibling::node()[1])
          )
          [not(@level) or @level = 1]
     "/>
  </ol>
  <xsl:apply-templates select=
   "following-sibling::node()[not(self::list)][1]"/>
 </xsl:template>

 <xsl:template match="list" mode="listgroup">
  <li>
    <xsl:value-of select="."/>

    <xsl:variable name="vNext" select=
     "following-sibling::list
            [not(@level > current()/@level)][1]
     |
      following-sibling::node()[not(self::list)][1]
     "/>

     <xsl:variable name="vNextLevel" select=
     "following-sibling::list
     [@level = current()/@level +1]
      [generate-id(following-sibling::list
            [not(@level > current()/@level)][1]
           |
             following-sibling::node()[not(self::list)][1]
                  )
      =
       generate-id($vNext)
      ]
     "/>

     <xsl:if test="$vNextLevel">
     <ol>
      <xsl:apply-templates mode="listgroup"
        select="$vNextLevel"/>
     </ol>
     </xsl:if>
  </li>
 </xsl:template>
</xsl:stylesheet>

应用于此 XML 文档时(故意复杂以表明该解决方案适用于许多边缘情况)

<root>
    <h1>text</h1>
    <list level="1">1.1</list>
    <list level="1">1.2</list>
    <list level="2">1.2.1</list>
    <list level="2">1.2.2</list>
    <list level="3">1.2.2.1</list>
    <list level="1">1.3</list>
    <p>text</p>
    <list>2.1</list>
    <list>2.2</list>
    <h2>text</h2>
    <h1>text</h1>
    <list level="1">3.1</list>
    <list level="1">3.2</list>
    <list level="2">3.2.1</list>
    <list level="2">3.2.2</list>
    <list level="3">3.2.2.1</list>
    <list level="1">3.3</list>
    <list level="2">3.3.1</list>
    <list level="2">3.3.2</list>
    <p>text</p>
</root>

产生想要的正确结果

<root>
   <h1>text</h1>
   <ol>
      <li>1.1</li>
      <li>1.2<ol>
            <li>1.2.1</li>
            <li>1.2.2<ol>
                  <li>1.2.2.1</li>
               </ol>
            </li>
         </ol>
      </li>
      <li>1.3</li>
   </ol>
   <p>text</p>
   <ol>
      <li>2.1</li>
      <li>2.2</li>
   </ol>
   <h2>text</h2>
   <h1>text</h1>
   <ol>
      <li>3.1</li>
      <li>3.2<ol>
            <li>3.2.1</li>
            <li>3.2.2<ol>
                  <li>3.2.2.1</li>
               </ol>
            </li>
         </ol>
      </li>
      <li>3.3<ol>
            <li>3.3.1</li>
            <li>3.3.2</li>
         </ol>
      </li>
   </ol>
   <p>text</p>
</root>

或浏览器显示的

文字

  1. 1.1
  2. 1.2
    1. 1.2.1
    2. 1.2.2
      1. 1.2.2.1
  3. 1.3

文字

  1. 2.1
  2. 2.2

文字

文字

  1. 3.1
  2. 3.2
    1. 3.2.1
    2. 3.2.2
      1. 3.2.2.1
  3. 3.3
    1. 3.3.1
    2. 3.3.2

文字

【讨论】:

  • @Dimitre,你至少拥有我对 xml 示例的支持 :))
  • @Flack:我对您的回答表示赞同并没有任何问题,但正如我对@Alejandro 的回答所评论的那样,我很难清楚地确定我们的三个答案中哪一个是最好的。人们不应该认为一个答案比其他答案更好,而事实并非如此。因此,如果结果所有三个答案都有相同数量的赞成票,我会赞成你。当然,这是理想的情况。让我们稍等片刻,然后我们会看到。
  • 我刚刚尝试了 Flack 和 Dimitre 的答案,并且都非常有效。最重要的是,我学到了一些新东西(我以前从未使用过 current() 函数)。谢谢大家!
  • 我试着做一些数字,但是 XSlerator 中的相对值太大了。可能有人会分析所有解决方案吗?
  • @Jacqueline:随时欢迎您。谢谢你提出的好问题——它比恶魔数独更有趣:)
【解决方案4】:

您将在本文中找到一个非常相似的问题的有效解决方案

http://www.saxonica.com/papers/ideadb-1.1/mhk-paper.xml

注意:它是 XSLT 2.0。

【讨论】:

  • 不幸的是我必须使用 XSLT 1.0。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多