繁体   English   中英

使用 XSLT 从平面 HTML 创建嵌套的 XML

[英]Create nested XML from flat HTML using XSLT

我有这个特定的平面输入 HTML 结构:

<!DOCTYPE html>
<html>
  <head>
    <title>Article <b>bold</b> title</title>
  </head>
  <body>
    <article>
      <h1 class="h-title"><span class="span-title">1 </span> Title 1 with some <sup>sup</sup> elements.</h1>
      <p>Some <b>bold</b> text for 1.</p>
      <p>Some more <b>bold</b> text for 1.</p>
      <h1 class="h-title"><span class="span-title">2 </span> Title 2 with some <sup>sup</sup> elements.</h1>
      <ul>
        <li>The first list item.</li>
        <li>The second list item with <i>italic</i> text.</li>
      </ul>
      <p>Some <b>bold</b> text for 2.</p>
      <h2 class="h-title"><span class="span-title">2.1</span> Title 2.1 with some <sup>sup</sup> elements.</h2>
      <p>Some <b>bold</b> text for 2.1.</p>
      <h2 class="h-title"><span class="span-title">2.2</span> Title 2.2 with some <sup>sup</sup> elements.</h2>
      <p>Some <b>bold</b> text for 2.2.</p>
      <h3 class="h-title"><span class="span-title">2.2.1</span> Title 2.2.1 with some <sup>sup</sup> elements.</h3>
      <p>Some <b>bold</b> text for 2.2.1.</p>
      <h3 class="h-title"><span class="span-title">2.2.2</span> Title 2.2.2 with some <sup>sup</sup> elements.</h3>
      <p>Some <b>bold</b> text for 2.2.2.</p>
      <h2 class="h-title"><span class="span-title">2.3</span> Title 2.3 with some <sup>sup</sup> elements.</h2>
      <p>Some <b>bold</b> text for 2.3.</p>
      <h1 class="h-title"><span class="span-title">3</span> Title 3 with some <sup>sup</sup> elements.</h1>
      <p>Some <b>bold</b> text for 3.</p>
    </article>
  </body>
</html>

我需要创建一个嵌套的输出 XML 结构,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xml>
  <front type="head">
    <title>Article <b>bold</b> title</title>
  </front>
  <body>
    <sec id="s1" sec-type="Title 1 with some sup elements.">
      <label>1</label>
      <title>Title 1 with some <sup>sup</sup> elements.</title>
      <p>Some <b>bold</b> text for 1.</p>
      <p>Some more <b>bold</b> text for 1.</p>
    </sec>
    <sec id="s2" sec-type="Title 2 with some sup elements.">
      <label>2</label>
      <title>Title 2 with some <sup>sup</sup> elements.</title>
      <list list-type="bullet">
        <list-item>The first list item.</list-item>
        <list-item>The second list item with <i>italic</i> text.</list-item>
      </list>
      <p>Some <b>bold</b> text for 2.</p>
      <sec id="s2.1" sec-type="Title 2.1 with some sup elements.">
        <label>2.1</label>
        <title>Title 2.1 with some <sup>sup</sup> elements.</title>
        <p>Some <b>bold</b> text for 2.1.</p>
      </sec>
      <sec id="s2.2" sec-type="Title 2.2 with some sup elements.">
        <label>2.2</label>
        <title>Title 2.2 with some <sup>sup</sup> elements.</title>
        <p>Some <b>bold</b> text for 2.2.</p>
        <sec id="s2.2.1" sec-type="Title 2.2.1 with some sup elements.">
          <label>2.2.1</label>
          <title>Title 2.2.1 with some <sup>sup</sup> elements.</title>
          <p>Some <b>bold</b> text for 2.2.1.</p>
        </sec>
        <sec id="s2.2.2" sec-type="Title 2.2.2 with some sup elements.">
          <label>2.2.2</label>
          <title>Title 2.2.2 with some <sup>sup</sup> elements.</title>
          <p>Some <b>bold</b> text for 2.2.2.</p>
        </sec>
      </sec>
      <sec id="s2.3" sec-type="Title 2.3 with some sup elements.">
        <label>2.3</label>
        <title>Title 2.3 with some <sup>sup</sup> elements.</title>
        <p>Some <b>bold</b> text for 2.3.</p>
      </sec> 
    </sec>
    <sec id="s3" sec-type="Title 3 with some sup elements.">
      <label>3</label>
      <title>Title 3 with some <sup>sup</sup> elements.</title>
      <p>Some <b>bold</b> text for 3.</p>
    </sec>
  </body>
</xml>

到目前为止,我已经在下面生成了这个 XSLT 转换(我相信 h1-h6 部分需要改进):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs"
  version="2.0">
  
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  
  <!-- all -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- html / xml -->
  <xsl:template match="html">
    <xml>
      <xsl:apply-templates select="node()|@*"/>
    </xml>
  </xsl:template>
  
  <!-- head / front -->
  <xsl:template match="head">
    <front type="head">
      <xsl:apply-templates select="node()|@*"/>
    </front>
  </xsl:template>
  
  <!-- article / -->
  <xsl:template match="article">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:template>
  
  <!-- h1-h6 / sec -->
  <xsl:template match="h1[@class='h-title']">
    <xsl:variable name="secId" select="normalize-space(span)"/>
    <xsl:variable name="secType" select="substring-after(.,' ')"/>
    <sec>
      <xsl:attribute name="id" select="normalize-space(concat('s', $secId))"/>
      <xsl:attribute name="sec-type" select="$secType"/>
      <label>
        <xsl:value-of select="$secId"/>
      </label>
      <title>
        <xsl:apply-templates select="node() except span" />
      </title>  
    </sec>
  </xsl:template>
  
  <!-- ul / list -->
  <xsl:template match="ul">
    <list list-type="bullet">
      <xsl:apply-templates select="node()|@*"/>
    </list>
  </xsl:template>
  
  <!-- li / list-item -->
  <xsl:template match="li">
    <list-item>
      <xsl:apply-templates select="node()|@*"/>
    </list-item>
  </xsl:template>
  
</xsl:stylesheet>

简短的介绍:

我有这个需要转换为嵌套 XML 结构的平面 HTML 结构。 原始 HTML 结构可能使用 h1 到 h6 标题,它们应相应地转换为嵌套的输出 XML 部分。 每个标题 (h1...h6) 都有自己的类 (h1-title...h6-title)。 HTML 总是“结构良好”,意味着 h1 后面只能跟 h2 或 h3 等。错误的格式(即 h1->h3->h2)可能永远不会发生。

我有两个问题:

  1. 我相信转换需要通过递归来完成,但我无法用 XSLT 弄清楚。 我设法创建了正确的 XML 输出结构并相应地重新标记所有内容,但我无法设置嵌套结构。

  2. 第二个(小)问题是我不知道如何从 XML 输出标签中去除前导/尾随空格,同时使用“node() 除了 span”? 在这种情况下,函数 normalize-space() 返回错误。

我将永远感激(我是认真的!)能够为我解开上述递归之谜的人。

三天前,我第一次接触了 XSLT 代码。 今天我要发布我的第一个“成就”,它基于 Martin Honnen 的黄金函数 (html(h)->xml(sec))。 我相信代码很丑,我不知道它是否按照所有标准编写,但最终结果是正确的,所以我现在将其发布为我的问题的答案。 如果仍然存在一些异常/问题,如果有人可以发表评论,我很乐意修复它。

它看起来像这样:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="fn xs mf"
  version="2.0">
  
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  
  <!-- all -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- html / xml -->
  <xsl:template match="html">
    <xml>
      <xsl:apply-templates select="node()|@*"/>
    </xml>
  </xsl:template>
  
  <!-- head / front -->
  <xsl:template match="head">
    <front type="head">
      <xsl:apply-templates select="node()|@*"/>
    </front>
  </xsl:template>
  
  <!-- flat html (h1-h6) to nested xml (sec) transformation -->
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="nodes" as="node()*"/>
    <xsl:param name="level" as="xs:integer"/>
    <xsl:for-each-group select="$nodes" group-starting-with="*[starts-with(local-name(), concat('h', $level))]">
      <xsl:choose>
        <xsl:when test="self::*[starts-with(local-name(), concat('h', $level))]">
          <sec>
            <xsl:apply-templates select="."/>
            <xsl:sequence select="mf:group(current-group() except ., $level+1)"/>
          </sec>
        </xsl:when>
        <xsl:when test="$level lt 6">
          <xsl:sequence select="mf:group(current-group(), $level+1)"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>
  
  <!-- article / -->
  <xsl:template match="article">
    <xsl:sequence select="mf:group(node(), 1)"/>
  </xsl:template>
  
  <!-- h1-h6 / sec -->
  <xsl:template match="(h1|h2|h3|h4|h5|h6)[@class='h-title']">
    <xsl:variable name="secId" select="normalize-space(span)"/>
    <xsl:variable name="secType" select="fn:substring-after(normalize-space(.), ' ')"/>
    <xsl:attribute name="id" select="normalize-space(concat('s', $secId))"/>
    <xsl:attribute name="sec-type" select="$secType"/>
    <label>
      <xsl:value-of select="$secId"/>
    </label>
    <title>
      <xsl:apply-templates select="node() except span" />
    </title>
  </xsl:template>
  
  <!-- ul / list -->
  <xsl:template match="ul">
    <list list-type="bullet">
      <xsl:apply-templates select="node()|@*"/>
    </list>
  </xsl:template>
  
  <!-- li / list-item -->
  <xsl:template match="li">
    <list-item>
      <xsl:apply-templates select="node()|@*"/>
    </list-item>
  </xsl:template>
  
</xsl:transform>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM