We get a XML packet of a price change and then want to update the particular section of a HTML doc. The problem is that the only way we can see it working is by a 2-stage transformation which first transforms the XML packet to a well-formed HTML chunk and then a 2nd XSLT to read in the HTML file and overwrite that particular section.
HTML file to update (it's well-formed):
<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>
Incoming 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>
1st 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>
So Saxon-js returns a <td name="price3">$22.34</td>
. All good. So we want to take this HTML chunk and update the HTML.
2nd 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>
Problem:
How do we get the dynamic values of price3
and <td name="price3">$22.34</td>
(which change each new XML that comes in) into the 2nd XSL without re-compiling XSL into a .sef.json which Saxon-js requires and without using parameters to pass-in these values (since we have read that using parameters is not recommended?
Or can all this be done in 1 transformation?
2nd question: Saxon-js docs state:
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.
We have found that this is not true (or are doing it wrong). If we just pass the XSL to the Transform function (stylesheetFileName:), an error is produced.
I think you basically want a single stylesheet along the lines of
<?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('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
</xsl:stylesheet>
Here is an online sample using Saxon-JS 2 in the browser
For compactness I have inlined the secondary data in the parameter, for Saxon-JS 2 and under Node.js you would basically declare the parameter bind a value with e.g. <xsl:param name="price-data" select="doc('sample2.xml')"/>
or you can preload the document with getResource
and then set the parameter to the preloaded document before running the transformation; see the examples section in https://www.saxonica.com/saxon-js/documentation/index.html#!api/getResource.
In your comment you say you pass the XML data in as a string from Node.js to Saxon-JS, in that case you need to use 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('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
</xsl:stylesheet>