appending nodes into xslt array during foreach - xslt-3.0

I have xml (described below), i want to divide the contetn by the "<eop/>" element
where whenever there is an tag itis a new element.
and save the the content in an global array (for later use)
i have a foreach loop over the xml grouped by eop
but i dont know how to append each group to the global array
this is my xml:
<?xml version="1.0" encoding="UTF-8"?>
<mainBody>
<article_1>
<content>
<p>befre eop
<eop eId="eop_386" />
after oep
</p>
</content>
</article_1>
<article_2>
<content>
<p>point content</p>
</content>
</article_2>
<article_3>
<content>
<p>point content</p>
</content>
</article_3>
<article_4>
<content>
<p>before eop 387<eop eId="eop_387" /> after 387</p>
</content>
</article_4>
<article_5>
<content>
<p> content 5</p>
</content>
</article_5>
<article_6>
<content>
<p> before eop 388<eop eId="eop_388" /> after 388</p>
</content>
</article_6>
<article_7>
<content>
<p>before eop 389<eop eId="eop_389" />
</p>
</content>
</article_7>
</mainBody>
and this part of the xslt:
<xsl:template match="mainBody">
<xsl:for-each-group select="descendant::node()" group-ending-with="eop" >
</xsl:for-each-group>
</xsl:template>
thanks

You can put
<xsl:variable name="groups" as="array(node()*)*">
<xsl:for-each-group select="/mainBody/descendant::node()" group-ending-with="eop">
<xsl:sequence select="array { current-group() }"/>
</xsl:for-each-group>
</xsl:variable>
globally (i.e. as a child of xsl:stylesheet) and that way the variable groups of type sequence of array of nodes should be available in any template.

Related

XSLT XML to HTML 2-stage transformation - must be better way

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>

XSLT - nested foreach Excel provides no output

I am attempting to pull out some values and display an array of arrays in Excel dynamically. However, my nested for-each doesn't and I receive no output no matter what I try. I have, of course, left out some stuff within the sheet and data, so right now they seem as if they're unnecessarily nested, but that isn't the case.
XML:
<report>
<sheet>
<data>
<table>
<row>
<column value="Germany" />
<column value="Berlin" />
<column value="2321341" />
</row>
<row>
<column value="USA" />
<column value="Washington DC" />
<column value="11111" />
</row>
</table>
</data>
</sheet>
</report>
The XSL nested for-each I try to get to work. It works when I remove the inner for-each and try displaying the first value by doing <xsl:value-of select="column/#value">, so it creates two new rows, each containing the name of the country (which is the first element of the column list).
<xsl:for-each select="report/sheet/data/table/row">
<ss:Row>
<xsl:for-each select="row/columm">
<ss:Cell>
<ss:Data ss:Type="String">
<xsl:value-of select="#value" />
</ss:Data>
</ss:Cell>
</xsl:for-each>
</ss:Row>
</xsl:for-each>
First, you have a typo in:
<xsl:for-each select="row/columm">
The name of the element is column, not columm.
More importantly, the instruction:
<xsl:for-each select="report/sheet/data/table/row">
puts you in the context of row. From this context,
<xsl:for-each select="row/column">
selects nothing, because row is not a child of itself. It needs to be:
<xsl:for-each select="column">

add an element to a node, if it doesnot exist, based on matching criteria

Using XSLT 3.0,
I have as input the following XML:
<?xml ="1.0" encoding="UTF-8"?>
<TABLE NAME="TABLE.DB">
<DATA RECORDS="2">
<RECORD ID="1">
<RECNO>1</RECNO>
<SEQ>0</SEQ>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<ORDER>10355</ORDER>
<CN>PL</CN>
<PROPERTY>06</PROPERTY>
</RECORD>
<RECORD ID="2">
<RECNO>2</RECNO>
<SEQUENCE>0</SEQUENCE>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<ORDER>000026672</ORDER>
<CN>PL 300 L</CN>
</RECORD>
<RECORD ID="3">
<RECNO>3</RECNO>
<SEQUENCE>0</SEQUENCE>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<NUMBER>10357</NUMBER>
<CN>PL 300 L</CN>
<PROPERTY>0</PROPERTY>
</RECORD>
</DATA>
</TABLE>
given values used for matching:
(i use \t to define the tab separated nature of my input file)
"10355"\t"PL"
"000026672"\t"PL 300 L"
i need to insert to all records that do not already have a PROPERTY tag, with the value of 06
Desired result:
<?xml ="1.0" encoding="UTF-8"?>
<TABLE NAME="TABLE.DB">
<DATA RECORDS="2">
<RECORD ID="1">
<RECNO>1</RECNO>
<SEQ>0</SEQ>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<ORDER>10355</ORDER>
<CN>PL</CN>
<PROPERTY>06</PROPERTY>
</RECORD>
<RECORD ID="2">
<RECNO>2</RECNO>
<SEQUENCE>0</SEQUENCE>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<ORDER>000026672</ORDER>
<CN>PL 300 L</CN>
<PROPERTY>06</PROPERTY>
</RECORD>
<RECORD ID="3">
<RECNO>3</RECNO>
<SEQUENCE>0</SEQUENCE>
<DATE>17/12/1999 2:44:08 μμ</DATE>
<ID>12/11/2015 3:15:25 μμ</ID>
<NUMBER>10357</NUMBER>
<CN>PL 300 L</CN>
</RECORD>
</DATA>
</TABLE>
What i have tried, adds the element property, even if it is already there, so i end up with two elements PROPERTY, in the same node, if it already exists. Could you give me an example implementation, i use SAXON latest release (9.8)
xsl: which adds an element, even when one exists:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="xsl exsl xs">
<xsl:output method="xml" version="1.0" indent="yes" encoding="utf-8" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="//*[local-name() = 'RECORD ID']">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:choose>
<xsl:when test="not(PRODUCT)">
<PRODUCT><xsl:value-of select="98"/></PRODUCT>
</xsl:when>
<xsl:otherwise>
<xsl:copy><xsl:value-of select="98"/></xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Have been using the solution suggested with my real data, (which differ a lot from the example), and i face the following issue:
How could i also have a report that would let me know, which of the additions while they were to be done, according to the input file, were not inserted?
A few observations:
(a) in your sample output, the PROPERTY element has been deleted from record 3. I can't see anything in your description of requirements that explains why.
(b) in your requirements statement, the sentence "i need to insert to all records that do not already have a PROPERTY tag, with the value of 06" is ambiguous. I would read this as saying that if there is a PROPERTY 'tag' (correctly, element) with a value other than 06 then you should insert another PROPERTY element, but that seems to contradict what you say elsewhere.
(c) your code has a template rule with match="//*[local-name() = 'RECORD ID']". You can delete the "//" at the start of a match pattern, it's redundant. More importantly, no element will ever have a local name equal to "RECORD ID" - element names cannot include spaces. So the template rule will never match anything.
(d) assuming that this template rule was intended to match RECORD elements, you certainly don't want the xsl:copy inside the xsl:otherwise, as this will create a nested copy of the whole RECORD.
(e) you've asked for an XSLT 3.0 solution but there's nothing in your problem that requires XSLT 3.0, and in fact your own stylesheet says version="2.0".
(f) I can't see what role the tab-separated parameter file plays in any of this.
In short, there's an awful lot of clarification needed before anyone can start to write any code.
Your solution requires a few changes:
The template shoud match RECORD, not RECORD ID (ID is an
attribute which takes no part in any decision).
Your general concept is OK:
copy the starting tag (<RECORD>),
apply templated to the inside of the current RECORD,
check whether PROPERTY (not PRODUCT) element is absent (in the
current RECORD),
if it is (absent), then output PROPERTY element with the required value,
copy the closing tag (</RECORD>).
I removed the otherwise part (not needed) and changed choose to
a single if..
As you specified XSLT 3.0, even the identity template can be
replaced with (a bit shorter) on-no-match="shallow-copy".
I also added version to your source XML. Otherwise it is not well-formed.
So the whole script can look like below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="RECORD">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:if test="not(PROPERTY)">
<PROPERTY>06</PROPERTY>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Tested on http://xsltfiddle.liberty-development.net/

String replace and concatenation

I have this structure
<ROWS>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>great</good>
<day>month</day>
</ROW>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>Fun</good>
<day>morning</day>
</ROW>
</ROWS>
How do I change that to
<statement> This is a great month, this is a Fun morning </statement>
Using only XSLT 1.0?
The original XML can change tag name. But not the structure! Any ideas?
This seems somewhat similar to creating form letters from a template. Assuming the example is not to be meant literally, you could try something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<statements>
<xsl:for-each select="ROWS/ROW/TEXT">
<statement>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</statement>
</xsl:for-each>
</statements>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="sep" select="'#'"/>
<xsl:choose>
<xsl:when test="contains($string, $sep) and contains(substring-after($string, $sep), $sep)">
<xsl:value-of select="substring-before($string, $sep)" />
<xsl:variable name="placeholder" select="substring-before(substring-after($string, $sep), $sep)" />
<xsl:value-of select="../*[name() = $placeholder]" />
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after(substring-after($string, $sep), $sep)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Given an input of:
<ROWS>
<ROW>
<TEXT>The quick brown #animal# jumps over the #property# dog.</TEXT>
<animal>fox</animal>
<property>lazy</property>
</ROW>
<ROW>
<TEXT>A journey of a #number# miles #action# with a single #act#.</TEXT>
<number>thousand</number>
<action>begins</action>
<act>step</act>
</ROW>
</ROWS>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<statements>
<statement>The quick brown fox jumps over the lazy dog.</statement>
<statement>A journey of a thousand miles begins with a single step.</statement>
</statements>
Look at the XSLT 1.0 spec; find the section on string functions. Study the contains, substring-before, and substring-after functions. The solution to your problem should become clear; if it doesn't, you should at least be able to get far enough on your problem to pose a question that does not look as if it could be paraphrased as "Please do my homework for me."

XSLT 1.0 How to use xsl:key with document() function

I'm trying to use xsl:key to lookup items in an external XML document, using the XSL document() function. I am able to get the xsl:key part to work if, instead of using document(), I just merge the two XML files (using XmlDocument in C#). However both XML files are very large, and I'm starting to get "out of memory" errors in some cases. Also I need to be able to use xls:key, otherwise the process takes hours.
In XSLT 2.0, I believe you can do something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="lookupDoc" select="document('CodeDescriptions.xml')" />
<xsl:key name="LookupDescriptionByCode" match="Code/#description" use="../#code" />
<xsl:template match="ItemCode">
<xsl:call-template name="MakeSpanForCode">
<xsl:with-param name="code" select="text()" />
</xsl:call-template>
</xsl:template>
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/key('LookupDescriptionByCode', $code)" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
How do you accomplish this in XSLT 1.0 though?
You have two possibilities:
without key
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/*/Code[#code = $code]/#description" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
with key
The key definition applies to all documents, but you need to change the context node before using the key() function:
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<!-- trick: change context node to external document -->
<xsl:for-each select="$lookupDoc">
<xsl:value-of select="key('LookupDescriptionByCode', $code)"/>
</xsl:for-each>
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
Also see two great mailing list answers from Mike Kay and Jeni Tennison on this topic

Resources