【问题标题】:XSLT: moving a grouping html elements into section levelsXSLT:将分组 html 元素移动到部分级别
【发布时间】:2011-05-31 15:24:50
【问题描述】:

我正在尝试编写一个 XSLT,它根据标题级别将 HTML 文件组织到不同的节级别。这是我的输入:

<html>
 <head>
  <title></title>
 </head>
 <body>
  <h1>HEADER 1 CONTENT</h1>
  <p>Level 1 para</p>
  <p>Level 1 para</p>
  <p>Level 1 para</p>
  <p>Level 1 para</p>

  <h2>Header 2 CONTENT</h2>
  <p>Level 2 para</p>
  <p>Level 2 para</p>
  <p>Level 2 para</p>
  <p>Level 2 para</p>
 </body>
</html>

我目前正在使用一个相当简单的结构,因此这种模式将暂时保持不变。我需要这样的输出...

<document> 
  <section level="1">
     <header1>Header 1 CONTENT</header1>
     <p>Level 1 para</p>
     <p>Level 1 para</p>
     <p>Level 1 para</p>
     <p>Level 1 para</p>
     <section level="2">
        <header2>Header 2 CONTENT</header2>
        <p>Level 2 para</p>
        <p>Level 2 para</p>
        <p>Level 2 para</p>
        <p>Level 2 para</p>
     </section>
  </section>
</document>

我一直在处理这个例子:Stackoverflow Answer

但是,我无法让它完全满足我的需要。

我正在使用 Saxon 9 在 Oxygen 中运行 xslt for dev。我将在生产中使用 cmd/bat 文件。仍然是 Saxon 9。如果可能的话,我想处理最多 4 个嵌套的节级别。

非常感谢任何帮助!

我遇到了另一个规定,所以我需要附加到这个上面。我之前可能应该想到这一点。

我遇到以下代码示例

<html>
<head>
<title></title>
</head>
<body>
<p>Level 1 para</p>
<p>Level 1 para</p>
<p>Level 1 para</p>
<p>Level 1 para</p>

<h1>Header 2 CONTENT</h1>
<p>Level 2 para</p>
<p>Level 2 para</p>
<p>Level 2 para</p>
<p>Level 2 para</p>
</body>
</html>

如您所见,&lt;p&gt;&lt;body&gt; 的子级,而在我的第一个 sn-p 中,&lt;p&gt; 始终是标头级别的子级。我想要的结果与上面相同,只是当我遇到&lt;p&gt; 作为&lt;body&gt; 的孩子时,它应该被包裹在&lt;section level="1"&gt; 中。

<document> 
<section level="1">     
<p>Level 1 para</p>
<p>Level 1 para</p>
<p>Level 1 para</p>
<p>Level 1 para</p>
</section>
<section level="1">
<header1>Header 2 CONTENT</header1>
<p>Level 2 para</p>
<p>Level 2 para</p>
<p>Level 2 para</p>
<p>Level 2 para</p>
</section>
</document>

【问题讨论】:

  • Jeff,考虑发布 XML 输入的源代码以及您想用 Saxon 9 创建的相应输出的源代码,然后我们可以提供 XSLT 2.0 代码的帮助。并说明您希望处理多少个级别(固定数量或任意数量)。
  • 应该显示输入输出的源代码。
  • 好问题,+1。请参阅我对 XSLT 1.0 解决方案的回答,该解决方案不会比 Martin Honnen 的 XSLT 2.0 解决方案长。 :)
  • @Alejandro 提供了更复杂的 XML 源文档后,我完全重写了我的解决方案,我认为它值得一看。 Jeni Tennison 几乎被遗忘的珍珠之一。

标签: html xslt grouping saxon


【解决方案1】:

这是一个 XSLT 2.0 样式表:

<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="xs mf"
  version="2.0">

  <xsl:output indent="yes"/>

  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="elements" as="element()*"/>
    <xsl:param name="level" as="xs:integer"/>
    <xsl:for-each-group select="$elements" group-starting-with="*[local-name() eq concat('h', $level)]">
      <xsl:choose>
        <xsl:when test="self::*[local-name() eq concat('h', $level)]">
          <section level="{$level}">
            <xsl:element name="header{$level}"><xsl:apply-templates/></xsl:element>
            <xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
          </section>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>

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

  <xsl:template match="/html">
    <document>
      <xsl:apply-templates select="body"/>
    </document>
  </xsl:template>

  <xsl:template match="body">
    <xsl:sequence select="mf:group(*, 1)"/>
  </xsl:template>

</xsl:stylesheet>

它应该按照你的要求做,虽然它不会停留在四个嵌套级别,而是只要找到 h[n] 元素就可以分组。

【讨论】:

    【解决方案2】:

    XSLT 1.0 解决方案(基本上由 Jenni Tennison 借用):

    <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:template match="html">
       <document><xsl:apply-templates/></document>
     </xsl:template>
    
     <xsl:template match="body">
       <xsl:apply-templates select="h1" />
     </xsl:template>
    
     <xsl:key name="next-headings" match="h6"
              use="generate-id(preceding-sibling::*[self::h1 or self::h2 or
                                                   self::h3 or self::h4 or
                                                   self::h5][1])" />
     <xsl:key name="next-headings" match="h5"
              use="generate-id(preceding-sibling::*[self::h1 or self::h2 or
                                                   self::h3 or self::h4][1])" />
     <xsl:key name="next-headings" match="h4"
              use="generate-id(preceding-sibling::*[self::h1 or self::h2 or
                                                   self::h3][1])" />
     <xsl:key name="next-headings" match="h3"
              use="generate-id(preceding-sibling::*[self::h1 or self::h2][1])" />
     <xsl:key name="next-headings" match="h2"
              use="generate-id(preceding-sibling::h1[1])" />
    
     <xsl:key name="immediate-nodes"
              match="node()[not(self::h1 | self::h2 | self::h3 | self::h4 |
                               self::h5 | self::h6)]"
              use="generate-id(preceding-sibling::*[self::h1 or self::h2 or
                                                   self::h3 or self::h4 or
                                                   self::h5 or self::h6][1])" />
    
     <xsl:template match="h1 | h2 | h3 | h4 | h5 | h6">
       <xsl:variable name="vLevel" select="substring-after(name(), 'h')" />
       <section level="{$vLevel}">
          <xsl:element name="header{$vLevel}">
            <xsl:apply-templates />
          </xsl:element>
          <xsl:apply-templates select="key('immediate-nodes', generate-id())" />
          <xsl:apply-templates select="key('next-headings', generate-id())" />
       </section>
     </xsl:template>
    
     <xsl:template match="/*/*/node()" priority="-20">
       <xsl:copy-of select="." />
     </xsl:template>
    </xsl:stylesheet>
    

    当此转换应用于以下 XML 文档时

    <html>
        <body>
            <h1>1</h1>
            <p>1</p>
            <h2>1.1</h2>
            <p>2</p>
            <h3>1.1.1</h3>
            <p>3</p>
            <h2>1.2</h2>
            <p>4</p>
            <h1>2</h1>
            <p>5</p>
            <h2>2.1</h2>
            <p>6</p>
        </body>
    </html>
    

    产生想要的结果

    <document>
       <section level="1">
          <header1>1</header1>
          <p>1</p>
          <section level="2">
             <header2>1.1</header2>
             <p>2</p>
             <section level="3">
                <header3>1.1.1</header3>
                <p>3</p>
             </section>
          </section>
          <section level="2">
             <header2>1.2</header2>
             <p>4</p>
          </section>
       </section>
       <section level="1">
          <header1>2</header1>
          <p>5</p>
          <section level="2">
             <header2>2.1</header2>
             <p>6</p>
          </section>
       </section>
    </document>
    

    【讨论】:

    • +1,很好的解决方案。您的*[starts-with(name(),'h') and (floor(substring... 表达式可以简化为*[translate(name(), 'h123456', '') = '']。 HTML 中没有任何元素会产生误报。
    • @Tomalak:很好的评论——过去 10 年我没有与 HTML 密切合作。
    • 检查&lt;body&gt; &lt;h1&gt;1&lt;/h1&gt; &lt;p&gt;1&lt;/p&gt; &lt;h2&gt;1.1&lt;/h2&gt; &lt;p&gt;2&lt;/p&gt; &lt;h3&gt;1.1.1&lt;/h3&gt; &lt;p&gt;3&lt;/p&gt; &lt;h2&gt;1.2&lt;/h2&gt; &lt;p&gt;4&lt;/p&gt; &lt;h1&gt;2&lt;/h1&gt; &lt;p&gt;5&lt;/p&gt; &lt;h2&gt;2.1&lt;/h2&gt; &lt;p&gt;6&lt;/p&gt; &lt;/body&gt;的结果
    • @Alejandro:谢谢,我现在提供了一个新的解决方案 -- 希望你会喜欢 :)
    • +1 使用xsl:key 的绝佳示例。我错过了这个功能。
    【解决方案3】:

    XSLT 1.0 中更通用的分组

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="kHeaderByPreceding"
                 match="body/*[starts-with(name(),'h')]"
                 use="generate-id(preceding-sibling::*
                                     [starts-with(name(),'h')]
                                     [substring(name(current()),2)
                                       > substring(name(),2)][1])"/>
        <xsl:key name="kElementByPreceding"
                 match="body/*[not(starts-with(name(),'h'))]"
                 use="generate-id(preceding-sibling::*
                                     [starts-with(name(),'h')][1])"/>
        <xsl:template match="node()|@*" mode="copy">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="copy"/>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="body">
            <document>
                <xsl:apply-templates select="key('kHeaderByPreceding','')"/>
            </document>
        </xsl:template>
        <xsl:template match="body/*[starts-with(name(),'h')]">
            <section level="{substring(name(),2)}">
                <xsl:element name="header{substring(name(),2)}">
                    <xsl:apply-templates mode="copy"/>
                </xsl:element>
                <xsl:apply-templates select="key('kElementByPreceding',
                                                 generate-id())"
                                     mode="copy"/>
                <xsl:apply-templates select="key('kHeaderByPreceding',
                                                 generate-id())"/>
            </section>
        </xsl:template>
        <xsl:template match="text()"/>
    </xsl:stylesheet>
    

    输出:

    <document>
        <section level="1">
            <header1>HEADER 1 CONTENT</header1>
            <p>Level 1 para</p>
            <p>Level 1 para</p>
            <p>Level 1 para</p>
            <p>Level 1 para</p>
            <section level="2">
                <header2>Header 2 CONTENT</header2>
                <p>Level 2 para</p>
                <p>Level 2 para</p>
                <p>Level 2 para</p>
                <p>Level 2 para</p>
            </section>
        </section>
    </document>
    

    还有更复杂的输入样本,例如:

    <body>
        <h1>1</h1>
        <p>1</p>
        <h2>1.1</h2>
        <p>2</p>
        <h3>1.1.1</h3>
        <p>3</p>
        <h2>1.2</h2>
        <p>4</p>
        <h1>2</h1>
        <p>5</p>
        <h2>2.1</h2>
        <p>6</p>
    </body>
    

    输出:

    <document>
        <section level="1">
            <header1>1</header1>
            <p>1</p>
            <section level="2">
                <header2>1.1</header2>
                <p>2</p>
                <section level="3">
                    <header3>1.1.1</header3>
                    <p>3</p>
                </section>
            </section>
            <section level="2">
                <header2>1.2</header2>
                <p>4</p>
            </section>
        </section>
        <section level="1">
            <header1>2</header1>
            <p>5</p>
            <section level="2">
                <header2>2.1</header2>
                <p>6</p>
            </section>
        </section>
    </document>
    

    【讨论】:

      【解决方案4】:

      我能够为上面的附录找到一些有用的东西。我在正文模板中添加了逻辑来测试标题标签。它可能不适用于所有情况,但对我的任务来说效果很好。

      <xsl:template match="body">
      <xsl:choose>
      <xsl:when test="descendant::h1">
      <xsl:apply-templates/>
      </xsl:when>
      <xsl:otherwise>
      <section level="1">
      <item>
      <block ccm="yes" onbup="no" quickref="no" web="no">
      <xsl:apply-templates/>
      </block>
      </item>
      </section>              
      </xsl:otherwise>
      </xsl:choose>        
      </xsl:template>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-12
        相关资源
        最近更新 更多