html - 使用 XSLT 从平面 HTML 创建嵌套 XML
问题描述
我有这个特定的平面输入 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)可能永远不会发生。
我有两个问题:
我相信转换需要通过递归来完成,但我无法用 XSLT 来解决。我设法创建了正确的 XML 输出结构并相应地重新标记了所有内容,但我无法设置嵌套结构。
第二个(小)问题是我不知道如何从 XML 输出标签中去除前导/尾随空格,同时使用“node() except 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>
推荐阅读
- c++ - 我应该在我的函数中使用静态变量来防止重新计算值吗?
- javascript - AJAX 响应函数(防止异步错误)
- debugging - eldoc 模式在获取要在 echo 中显示的信息时挂起 emacs
- java - 如何在eclipse中删除方法和属性上方的搜索参考链接
- kubernetes - 无法连接到部署在其他节点上的 pod
- arrays - 如何遍历具有不同数量项目的两个数组?迅速
- reactjs - 了解 redux 中的重新选择
- c# - CsvHelper Configuration.ShouldQuote - 仅对 DTO 上的字符串字段返回 true
- visual-studio - 比较 .cs 文件时忽略格式更改
- sql - 在sql查询中合并行