XSLT XML 到 HTML 的 2 阶段转换 - 必须是更好的方法
Posted
技术标签:
【中文标题】XSLT XML 到 HTML 的 2 阶段转换 - 必须是更好的方法【英文标题】:XSLT XML to HTML 2-stage transformation - must be better way 【发布时间】:2021-06-09 01:31:44 【问题描述】:我们得到一个价格变化的 XML 数据包,然后想要更新 html 文档的特定部分。问题是我们可以看到它工作的唯一方法是通过两阶段转换,首先将 XML 数据包转换为格式良好的 HTML 块,然后第二个 XSLT 读取 HTML 文件并覆盖该特定部分。
要更新的 HTML 文件(格式正确):
<html>
<head>
<title>Mini-me Amazon</title>
</head>
<body>
<p>This is our Product Price Sheet</p>
<table style="width:100%">
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr data-key="1">
<td>Whiz-bang widget</td>
<td name="price1">$19.99</td>
</tr>
<tr data-key="3">
<td>Unreal widget</td>
<td name="price3">$99.99</td>
</tr>
...
</table>
</body>
</html>
传入的 XML:
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="xml-price.xsl"?>
<supplier>
<product>
<key>3</key>
<pprice uptype="1">
<price>$22.34</price>
</pprice>
</product>
</supplier>
第一个 XSL:
<xsl:stylesheet ...>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/supplier">
<xsl:apply-templates select="product"/>
</xsl:template>
<xsl:template match="product">
<xsl:variable name="PKey">
<xsl:value-of select="key"/>
</xsl:variable>
<xsl:for-each select="pprice"> <!-- could be more than 1 -->
<xsl:choose>
<xsl:when test="@uptype=0">
</xsl:when>
<xsl:when test="@uptype=1">
<xsl:apply-templates select="price"/>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="price">
<td name="rate$PKey"><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
所以 Saxon-js 返回一个<td name="price3">$22.34</td>
。都好。所以我们想要获取这个 HTML 块并更新 HTML。
第二个 XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td[@name='price3']"> <!-- Problem 1 -->
<td name="price3">$22.34</td> <!-- Problem 2 -->
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="document('/home/tireduser/node/bigstuff/public/update-html.html')/node()"/>
</xsl:template>
</xsl:stylesheet>
问题:
我们如何将 price3
和 <td name="price3">$22.34</td>
的动态值(更改每个新的 XML 传入)到第二个 XSL 中,而不需要将 XSL 重新编译为 Saxon-js 需要的 .sef.json 并且不需要使用参数传入这些值(因为我们已经阅读过不推荐使用参数?
或者这一切都可以在 1 次转换中完成?
第二个问题:Saxon-js 文档状态:
Using fn:transform()
If a source XSLT stylesheet is supplied as input to the fn:transform() function in XPath, the XX compiler will be invoked to compile the stylesheet before it is executed. However, there is no way of capturing the intermediate SEF stylesheet for subsequent re-use.
我们发现这不是真的(或做错了)。如果我们只是将 XSL 传递给 Transform 函数 (stylesheetFileName:),则会产生错误。
【问题讨论】:
fn:transform
表示 XPath 3.1 函数,而不是 Saxon API 方法
您能否澄清一下您是在浏览器中运行 Saxon-JS,还是在 Node.js 下运行?
你从哪里读到不推荐使用参数?对我来说,这听起来像是非常糟糕的建议。编写没有参数的样式表是双手被绑在背后。
@MichaelKay 在 node.js 下使用它。需要买你的书。下面 Martin 的解决方案表明我们缺乏 XSL 技能。
【参考方案1】:
我认为你基本上想要一个单一的样式表
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:param name="price-data">
<supplier>
<product>
<key>3</key>
<pprice uptype="1">
<price>$22.34</price>
</pprice>
</product>
</supplier>
</xsl:param>
<xsl:key name="price" match="product/pprice[@uptype = 1]/price" use="'price' || ancestor::product/key"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="td[@name][key('price', @name, $price-data)]/text()">key('price', ../@name, $price-data)</xsl:template>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment>Run with system-property('xsl:product-name') system-property('xsl:product-version') system-property('Qhttp://saxon.sf.net/platform')</xsl:comment>
</xsl:template>
</xsl:stylesheet>
Here is an online sample 在浏览器中使用 Saxon-JS 2
为了紧凑,我在参数中内联了辅助数据,对于 Saxon-JS 2 和在 Node.js 下,您基本上可以声明参数绑定一个值,例如<xsl:param name="price-data" select="doc('sample2.xml')"/>
或者您可以使用getResource
预加载文档,然后在运行转换之前将参数设置为预加载的文档;请参阅https://www.saxonica.com/saxon-js/documentation/index.html#!api/getResource 中的示例部分。
在您的评论中,您说您将 XML 数据作为字符串从 Node.js 传递到 Saxon-JS,在这种情况下,您需要使用 parse-xml
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:param name="price-data" as="xs:string"><![CDATA[
<supplier>
<product>
<key>3</key>
<pprice uptype="1">
<price>$22.34</price>
</pprice>
</product>
</supplier>
]]></xsl:param>
<xsl:param name="price-doc" select="parse-xml($price-data)"/>
<xsl:key name="price" match="product/pprice[@uptype = 1]/price" use="'price' || ancestor::product/key"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="td[@name][key('price', @name, $price-doc)]/text()">key('price', ../@name, $price-doc)</xsl:template>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment>Run with system-property('xsl:product-name') system-property('xsl:product-version') system-property('Qhttp://saxon.sf.net/platform')</xsl:comment>
</xsl:template>
</xsl:stylesheet>
【讨论】:
或者,如果更方便,您可以使用 document() 函数按需加载“辅助数据”。 @MichaelKay,对,是的,我有点认为 Saxon-JS 中的异步处理需要预加载,因此没有提到doc
或 document
函数,但我已经编辑了答案提到更简单的方法并测试它可以与 Saxon-JS 2.1 和简单的基于相对文件的加载一起使用,没有任何问题。
当然,如果您希望一切都以异步方式正常运行,那么预加载会更好。
@MartinHonnen 感谢您提供示例代码。但它没有加载 HTML 文件的 xsl (/home/tireduser/node/bigstuff/public/update-html.html)。我要为此使用我现有的代码吗?
@ImTalkingCode,据我了解,您可以将该格式良好的 HTML 文件作为上述样式表的主要输入。以上是关于XSLT XML 到 HTML 的 2 阶段转换 - 必须是更好的方法的主要内容,如果未能解决你的问题,请参考以下文章
使用 XSLT 为 XML 到 HTML 转换中的不同 <place> 标记添加超链接