在 XSLT 中使 ul li 结构内标题节点的 TOC

Make in XSLT a TOC of the heading nodes inside an ul li structure

提问人:fauve 提问时间:10/2/2023 更新时间:10/2/2023 访问量:79

问:

总体概览

我尝试通过仅选择他的标题节点(如 h1、h2)来制作文档的目录 (TOC) ......h9 ()。h[0-9]

这些标题节点应该以级联结构进行结构 - 结构,其中所有依赖 an 的节点都应该包含在 的 中。<ul><li><hN+1><hN><ul><li><hN>

为了更明确,让我们看看这个例子。如果我有以下文件:document.xsl

<?xml version="1.0" encoding="UTF-8"?>

<document>
<h1>Lorem <i>arepo</i> ipsum dolor</h1>
<h2>Lorem ipsum dolor</h2>
<p>
Sed ut <i>perspiciatis</i> unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
</p>

<h1>sit amet et consectetur</h1>
<h2>Quia adipit</h2>
<h3>aliquam quaerat</h3>
<p>
Sed ut <i>perspiciatis</i> unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
</p>
<h2>Erit et nunquam</h2>

</document>

那么预期的渲染应该是:

<ul>
    <li>
        <span>Lorem <i>arepo</i> ispum dolor</span>
        <ul>
            <li><span>Lorem ipsum dolor</span></li>
        </ul>
    <li>
    <li>
        <span>Sit amet et consectetur</span>
        <ul>
            <li>
                <span>Quia adipit</span>
                <ul>
                    <li>
                    <span>aliquam quaerat</span>
                    </li>
                <ul>
            </li>
            <li><span>Erit et nunquam</span></li>
        </ul>
    <li>
</ul>

最小的工作示例

目前,我有这个maketoc.xslt

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="document">
    <ul>
      <xsl:apply-templates select="*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::h7 | self::h8 | self::h9]"/>
    </ul>
  </xsl:template>
  
  <xsl:template match="h1 | h2 | h3 | h4 | h5 | h6 | h7 | h8 | h9">
    <li>
      <span><xsl:value-of select="."/></span>
      <ul>
        <xsl:apply-templates select="following-sibling::*[1][self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::h7 | self::h8 | self::h9]"/>
      </ul>
    </li>
  </xsl:template>
  
  <!-- Ignor anything else -->
  <xsl:template match="*"/>
</xsl:stylesheet>

当前渲染

但是,这个 MWE 生成以下输出:

<ul>
   <li>
      <span>Lorem arepo ipsum dolor</span>
      <ul>
         <li>
            <span>Lorem ipsum dolor</span>
            <ul/>
         </li>
      </ul>
   </li>
   <li>
      <span>Lorem ipsum dolor</span>
      <ul/>
   </li>
   <li>
      <span>sit amet et consectetur</span>
      <ul>
         <li>
            <span>Quia adipit</span>
            <ul>
               <li>
                  <span>aliquam quaerat</span>
                  <ul/>
               </li>
            </ul>
         </li>
      </ul>
   </li>
   <li>
      <span>Quia adipit</span>
      <ul>
         <li>
            <span>aliquam quaerat</span>
            <ul/>
         </li>
      </ul>
   </li>
   <li>
      <span>aliquam quaerat</span>
      <ul/>
   </li>
   <li>
      <span>Erit et nunquam</span>
      <ul/>
   </li>
</ul>

问题

如您所见,此 MBE 存在一些问题:

  1. 对同一同级中的第一个降序节点进行多次处理。它们的处理量与它们的深度一样多。例如,“aliquam quaerat”出现树时间,因为他是一个节点。<h3>
  2. 一个无用的人有时会出现在一个没有里面的人里面。</ul><li><ul>
  3. 不是兄弟姐妹之间第一个节点的节点被视为 .(参见“Erit et nunquam”的案例,他是.<h1><h2>

问题

如何使xml的标题节点在级联结构中与后代与其上升者密切相关?<ul><li>

XML XSLT HTML 列表 HTML 标题

评论

1赞 michael.hor257k 10/2/2023
在有关 XSLT 的所有问题中,请说明您的处理器支持的 XSLT 版本。
1赞 michael.hor257k 10/2/2023
如果您仅限于 XSLT 1.0,您将在这里找到非常相似的东西:stackoverflow.com/a/40295892/3016153
1赞 y.arazim 10/2/2023
我认为这是 HTML 标准允许的最大值。h6
0赞 fauve 10/2/2023
@y.arazim 是的,但是如果我的 XML 包含高于 h6 的标题节点,我可以将他的排名放在 HTML 类中。目前这不是真正的问题。

答:

0赞 Martin Honnen 10/2/2023 #1

假设 XSLT 3(或 2 但以下版本使用某些 XSLT 3),这是使用以下递归分组的任务:for-each-group group-starting-with

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">
  
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="headings" as="element()*"/>
    <xsl:param name="level" as="xs:integer"/>
    <xsl:for-each-group select="$headings" group-starting-with="*[local-name() = 'h' || $level]">
      <xsl:if test="self::*[local-name() = 'h' || $level]">
        <li>
          <span>
            <xsl:apply-templates/>
          </span>
          <xsl:where-populated>
            <ul>
              <xsl:sequence select="mf:group(tail(current-group()), $level + 1)"/>
            </ul>
          </xsl:where-populated>
        </li>
      </xsl:if>
    </xsl:for-each-group>
  </xsl:function>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="document">
    <ul>
      <xsl:sequence select="mf:group(*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::h7 | self::h8 | self::h9], 1)"/>
    </ul>
  </xsl:template>
  
</xsl:stylesheet>

从您的原始代码中选择,我可能倾向于将其写成 .<xsl:sequence select="mf:group(*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::h7 | self::h8 | self::h9], 1)"/><xsl:sequence select="mf:group(h1 | h2 | h3 | h4 | h5 | h6 | h7 | h8 | h9, 1)"/>

至于您的评论似乎使用不支持 XSLT 3.0 的旧版本 Saxon,您需要将 XPath 3 表达式替换为 XPath 3 函数,然后,对于 ,您需要通过替换整体和内容来做更多的工作,例如'h' || $levelconcat('h', $level)tail(current-group())subsequence(current-group(), 2)xsl:where-populatedxsl:where-populated

<xsl:variable name="tail-of-group" select="subsequence(current-group(), 2)"/>
<xsl:if test="$tail-of-group">
  <ul>
   <xsl:sequence select="mf:group($tail-of-group, $level + 1)"/>
  </ul>
</xsl:if>

XSLT 3 声明需要在 XSLT 2 中通过拼写<xsl:mode on-no-match="shallow-copy"/>

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

评论

0赞 fauve 10/2/2023
我试图在文件:maketoc1.xslt 的第 31 行的 xsl:sequence 处处理此 xslt:Unknown XSLT element: sequence Transformation failed: Run-time errors were reported'。当然,输出文件是空的。saxon-xslt -o output.txt document.xml maketoc1.xslt and I get
0赞 Martin Honnen 10/2/2023
目前支持的 Saxon 版本是 Saxon Java 的 11 和 12、.NET Framework 的 10、SaxonC 的 12 版本、SaxonJS 的 2.5 版本。我不知道 saxon-xslt 是什么,也许是一个 Linux 软件包,我希望有一个使用 Saxon 版本支持 XSLT 3 和 Saxon 9.8 或更高版本,因为它自 2017 年以来就存在。
1赞 Martin Honnen 10/2/2023
似乎有一个使用 Saxon 9.9 HE 的软件包 pkgs.org/download/libsaxonhe-java 它绝对支持 XSLT 3.0。
2赞 y.arazim 10/2/2023 #2

我已将答案从将平面层次结构转换为 XSLT 深度的嵌套层次结构,以适应此处提出的问题。

这是一个 XSLT 1.0 解决方案,但对我来说,它似乎比另一个答案中建议的 XSLT 2.0/3.0 方法更优雅。

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>

<xsl:key name="child" match="h2|h3|h4|h5|h6" use="generate-id(preceding-sibling::*[name()=concat('h', substring-after(name(current()), 'h') - 1)][1])"/>

<xsl:template match="/document">
    <ul>
        <xsl:apply-templates select="h1"/>
    </ul>
</xsl:template>

<xsl:template match="h1|h2|h3|h4|h5|h6">
     <li>
        <span>
            <xsl:copy-of select="node()" />
        </span>
        <ul>
            <xsl:apply-templates select="key('child', generate-id())"/>
        </ul>
    </li>
</xsl:template>

</xsl:stylesheet>

评论

0赞 fauve 10/4/2023
这个解决方案真的很好,而且正如你所说,很优雅。顺便说一句,有时在 <h2> 下不是 <h3>而是直接<h4>(或更高的数字)。因此,您的代码不会处理它,因为它包含一个,并假设使用了所有中间级别。但事实并非总是如此。name(current()), 'h' -1
0赞 y.arazim 10/4/2023
@fauve 在对原始答案的评论中提出了同样的观点,并且还提出了解决方案。
0赞 fauve 10/4/2023
我找到了关于它的讨论,但我没有找到解决方案。
1赞 y.arazim 10/4/2023
解决方案是使用 < 运算符比较“h”之后的子字符串。