Adding Root Node in the Output - xslt-2.0

My output looks like
<TransactionLog TID="1400" SeqNo="3337446" SQLTransaction="Insert into TankerLoads Values(141221,53,299,18,1,426148,6,'Nov 19 2007 12:00AM','Dec 30 1899 12:59PM',3.00,20682,0,'Zevo','Nov 19 2007 12:00AM',0)" />
where I need to add <root> node so that it will look like below
<root>
<TransactionLog TID="1400" SeqNo="3337446" SQLTransaction="Insert into TankerLoads Values(141221,53,299,18,1,426148,6,'Nov 19 2007 12:00AM','Dec 30 1899 12:59PM',3.00,20682,0,'Zevo','Nov 19 2007 12:00AM',0)" />
</root>
I combined all the records by using below code and now I need to add root node and I need to diplay this with OUTPUT METHOD AS TEXT. Please help me.
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)" />
</xsl:template>

You can capture the output of your transformation in a variable and apply to the contents of the variable a transformation similar to this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vResult1">
<TransactionLog TID="1400"
SeqNo="3337446"
SQLTransaction="Insert into TankerLoads Values(141221,53,299,18,1,426148,6,'Nov 19 2007 12:00AM','Dec 30 1899 12:59PM',3.00,20682,0,'Zevo','Nov 19 2007 12:00AM',0)" />
</xsl:variable>
<xsl:template match="node()|#*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<root>
<xsl:apply-templates select="$vResult1/*" mode="pass2"/>
</root>
</xsl:template>
</xsl:stylesheet>
When this transformation is performed on any XML document (not used), the wanted, correct result is produced:
<root>
<TransactionLog TID="1400" SeqNo="3337446"
SQLTransaction="Insert into TankerLoads Values(141221,53,299,18,1,426148,6,'Nov 19 2007 12:00AM','Dec 30 1899 12:59PM',3.00,20682,0,'Zevo','Nov 19 2007 12:00AM',0)"/>
</root>
Alternatively, and much better, modify your existing transformation to something like this:
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
Or if your transformation already has a template matching /:
<xsl:template match="/">
<root>
<!-- Put the body of your current template here -->
</root>
</xsl:template>
</xsl:stylesheet>

Related

Looking for saxon:evaluate() example code

I have a transform.xsl file with will process a input.xml. But there is also an additional config.xml file which will define additional clauses. For e.g. this is the content of the config.xml.
<Location >
<DisplayName>
<Attribute1>ABC</Attribute1>
<Attribute2>XYZ</Attribute2>
<action>concat($Attribute1,$Attribute2)</action>
</DisplayName>
</Location >
So when transform.xsl will encounter the DisplayName variable within the input.xml, then it will form the value with the RESULT of the action expression defined in the config.xml file. transform.xml will call the config.xml just to get the result. (The action can be modified by the end user and hence these are placed outside the xsl file, within the config.xml).
We are using saxon xml processor version 9 and xslt 2.0. So we need to use saxon:evaluate(). I tried to find more examples of saxon:evaluate(), but couldn't find it more. Can anyone show me some examples of how to use it?
Thanks in advance.
***** This is an edited query to highlight the need of saxon:evaluate *****
Here is an example to use an XSLT 3 processor supporting xsl:evaluate (https://www.w3.org/TR/xslt-30/#dynamic-xpath) (i.e. Saxon 9.8 or later with the commercial PE or EE editions or Altova 2017 or later) to process your "config" file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="config-url" as="xs:string">test2018121301.xml</xsl:param>
<xsl:param name="config-doc" select="doc($config-url)"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="element" match="*" use="node-name()"/>
<xsl:function name="mf:config-evaluation" as="item()*">
<xsl:param name="config-doc" as="document-node()"/>
<xsl:param name="element-name" as="xs:QName"/>
<xsl:variable name="display" select="key('element', $element-name, $config-doc)/DisplayName"/>
<xsl:evaluate xpath="$display/regex" with-params="map:merge($display!(* except regex)!map { QName('', local-name()) : string() })"/>
</xsl:function>
<xsl:template match="*[key('element', node-name(), $config-doc)]">
<xsl:copy>
<xsl:value-of select="mf:config-evaluation($config-doc, node-name()), ."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
So with a config.xml
<Location >
<DisplayName>
<Attribute1>ABC</Attribute1>
<Attribute2>XYZ</Attribute2>
<regex>concat($Attribute1,$Attribute2)</regex>
</DisplayName>
</Location >
this would transform an input sample with e.g.
<Root>
<Items>
<Item>
<Data>data 1</Data>
<Location>location 1</Location>
</Item>
<Item>
<Data>data 2</Data>
<Location>location 2</Location>
</Item>
</Items>
</Root>
into
<Root>
<Items>
<Item>
<Data>data 1</Data>
<Location>ABCXYZ location 1</Location>
</Item>
<Item>
<Data>data 2</Data>
<Location>ABCXYZ location 2</Location>
</Item>
</Items>
</Root>
That gives you a great flexibility to allow XPath expressions in the configuration files but as pointed out in https://www.w3.org/TR/xslt-30/#evaluate-effect, also is a security problem: "Stylesheet authors need to be aware of the security risks associated with the use of xsl:evaluate. The instruction should not be used to execute code from an untrusted source.".
As for using the saxon:evaluate function supported in older versions of Saxon not supporting the XSLT 3 xsl:evaluate instruction, a simple example is
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="#all"
version="2.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="example">
<xsl:copy>
<xsl:value-of select="saxon:evaluate(#expression, #foo, #bar)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
which transforms the input
<root>
<example expression="concat($p1, $p2)" foo="This is " bar="an example."/>
<example expression="replace(., $p1, $p2)" foo="\p{L}" bar="X">This is example 2.</example>
</root>
into the result
<root>
<example>This is an example.</example>
<example>XXXX XX XXXXXXX 2.</example>
</root>
Try checking the xsl-attribute tag along with the xsl-value-of tag. If I get what you're asking for, you could probably read the config.xml using the transform.xsl (or a second xsl for an intermediate file) to set the text inside the regex tag to correspond to the value of an tag attribute within the xsl.
https://www.w3schools.com/xml/ref_xsl_el_attribute.asp
Also, check this tutorial for regex in XSLT 2, it may help:
https://www.xml.com/pub/a/2003/06/04/tr.html

How to display latest date from N number of month in xslt?

I need to display latest date in N number of months using xslt.
My input:
2016/10/18
2016//10/15
2016/09/29
2016/09/15
and so on.
My output should be like below:
2016/10/18
2016/09/29
Can anyone help me on this?
Given a string of dates in that format you first need to tokenize to extract the date values, then you need to convert to the xs:date format, then you can group by the month and select the maximum value in each group. Using XSLT 3.0 that can be done as follows:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="tokenize($input, '\s+')!xs:date(replace(., '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 you need to rewrite the date sequence construction a bit:
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="2.0">
<xsl:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="for $dateString in tokenize($input, '\s+') return xs:date(replace($dateString, '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
I. Here is a short XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select="current-group()[. eq max(current-group()/string())][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (unordered and multi-year dates -- to make it more interesting):
<t>
<d>2016/10/15</d>
<d>2016/09/15</d>
<d>2016/10/18</d>
<d>2016/09/29</d>
<d>2017/09/17</d>
</t>
the wanted, correct result is produced:
<d>2016/10/18</d>
<d>2017/09/17</d>
II. If the date that has the same month's highest day is wanted -- regardless of the year, this transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select=
"current-group()[substring(.,9,2) eq max(current-group()/substring(.,9,2))][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the correct result is produced:
<d>2016/10/18</d>
<d>2016/09/29</d>
III. If the dates are given together as a string:
Just use the tokenize() standard XPath 2.0 fy=unction.
For example, the equivalent of the first transformation above becomes:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDates"
select="'2016/10/15 2016/09/15 2016/10/18 2016/09/29 2017/09/17'"/>
<xsl:template match="/">
<xsl:for-each-group select="tokenize($vDates, '\s+')[.]" group-by="substring(.,6,2)">
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

XSLT set directory where result document ends up

The XSLT below creates result-documents as desired, with one exception: the result document ends up in the directory where the stylesheet was invoked from. I want the result document to be where it was found (i.e. overwrite itself with the transform version).
How can I do that?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:template match="/">
<xsl:for-each select="collection(iri-to-uri('file:///home/paul/Text/?select=*.xhtml'))">
<xsl:variable name="filename">
<xsl:value-of select="tokenize(document-uri(.), '/')[last()]"/>
</xsl:variable>
<xsl:result-document indent="yes" method="xml" href="{$filename}">
<xsl:apply-templates/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- transform templates removed -->
</xsl:stylesheet>
Try just using href="{document-uri(.)}" to use the full uri as the target rather than doing the tokenize to pull out the last segment.

How do I merge and concatenate the data from each row in two separate source files?

I have two source files which I need to combine on a row by row basis. I am happy reading the files into a variable and I am happy with the logic but the syntax has me stumped. For each row in file 1 I need to loop round each row in file 2 and output the two variables concatenated together:
File 1:
<rows>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
</rows>
File 2:
<rows>
<row>a</row>
<row>b</row>
</rows>
Required output:
<rows>
<row>1/a</row>
<row>1/b</row>
<row>2/a</row>
<row>2/b</row>
<row>3/a</row>
<row>3/b</row>
<row>4/a</row>
<row>4/b</row>
<rows>
My (poor) attempt at getting the XSLT to work:
<rows>
<xsl:apply-templates select="document('file1.xml')/rows/row" />
</rows>
<xsl:template match="row">
<xsl:apply-templates select="document('file2.xml')/rows/row" />
</xsl:template>
<xsl:template match="row">
<row><xsl:value-of select="???" />/<xsl:value-of select="???" /></row>
</xsl:template>
(These files are simplified versions of what I actually have)
How do I make one template match one 'row' value and the other match another (both source files use the same structure). And how do I set those '???' values?
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vDoc2">
<rows>
<row>a</row>
<row>b</row>
</rows>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<rows>
<xsl:apply-templates/>
</rows>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="$vDoc2/*/row" mode="doc2">
<xsl:with-param name="pValue" select="."/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row" mode="doc2">
<xsl:param name="pValue" />
<row><xsl:sequence select="concat($pValue, '/', .)"/></row>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided first XML document:
<rows>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
</rows>
the wanted, correct result is produced:
<rows>
<row>1/a</row>
<row>1/b</row>
<row>2/a</row>
<row>2/b</row>
<row>3/a</row>
<row>3/b</row>
<row>4/a</row>
<row>4/b</row>
</rows>

XSLT conditionally write to two different files

I need to extract log meesages from an XML file and write them out to plain text files. The log messages come in two flavors, and I want to write them to separate files.
I have written a style sheet that does exactly what I need except that it sometimes creates empty files because the XML file may not contain messages of one type or another.
I am wondering, 1) if what I ma doing is the best method to do this, and 2) if there is a way to suppress empty files.
My sample may contain errors because it has been retyped. (the original is on a closed network)
Note: I am using XSLT 2.0 features.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" encoding="iso-8859-1" />
<xsl:param name="break" select="string('
')" />
<xs:template match="/">
<xsl:result-document method="text" href="foo.txt">
<xsl:apply-templates select="Root/a/b/c[contains(., 'foo')]" />
</xsl:reult-document>
<xsl:result-document method="text" href="bar.txt">
<xsl:apply-templates select="Root/a/b/c[not(contains(., 'foo'))]" />
</xsl:reult-document>
</xsl:template>
<xsl:template match="*">
<xsl:value-of select=concat(normalize-space(.), $break)" />
</xsl:template>
</xsl:stylesheet>
You could use some XSLT 2.0 stylesheet like:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="break" select="string('
')" />
<xsl:template match="/">
<xsl:apply-templates select="Root/a/b/c"/>
</xsl:template>
<xsl:template match="/Root/a/b/c[contains(., 'foo')]">
<xsl:result-document method="text" href="foo.txt">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="/Root/a/b/c[not(contains(., 'foo'))]">
<xsl:result-document method="text" href="bar.txt">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="*">
<xsl:value-of select="concat(normalize-space(.), $break)" />
</xsl:template>
</xsl:stylesheet>
Note: Pattern matching and xsl:next-match.

Resources