XSLT 3.0 Convert String data type to Date type and apply translate to remove characters - xslt-2.0

Here is my source XML
<?xml version="1.0" encoding="UTF-8"?>
<Compensation>
<Salary>
<BasePay>$18600.1299</BasePay>
<PayDate>15-Mar-2022</PayDate>
<Bonus>$3500.99</Bonus>
<Gym>$670</Gym>
<Tax>$30,000.9912</Tax>
</Salary>
<Salary>
<BasePay>$28600.12</BasePay>
<PayDate>15-Mar-2022</PayDate>
<Bonus>$1500.99</Bonus>
<Gym/>
<Tax>$50,000</Tax>
</Salary>
</Compensation>
I am trying do following on my XML document
Format date to YYYY-MM-DD format. Currently date on my source XML is of string data type
Remove all currency and commas from whole document.
Here is my XSLT 3.0 solution which is working fine.
<?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:this="urn:this-stylesheet"
exclude-result-prefixes="xs this"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:function name="this:fromatDate" as="xs:string">
<xsl:param name="dateString"/>
<xsl:variable name="month"
select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')"/>
<xsl:variable name="dd" select="substring($dateString,1,2)"/>
<xsl:variable name="mm"
select="format-number(index-of($month,substring($dateString,4,3)),'00')"/>
<xsl:variable name="yy" select="substring($dateString,8,4)"/>
<xsl:value-of select="format-date(xs:date( string-join(($yy, $mm, $dd), '-')),'[Y0001]-[M01]-[D01]')"/>
</xsl:function>
<xsl:mode on-no-match="shallow-copy"/>
<!-- Removes $ symbol and commas -->
<xsl:template match="text()">
<xsl:value-of select="translate(.,'$,', '')"/>
</xsl:template>
<!-- Convert string to date -->
<xsl:template match="PayDate">
<FormattedPayDate>
<xsl:value-of select="this:fromatDate(.)"/>
</FormattedPayDate>
</xsl:template>
</xsl:stylesheet>
I'm getting the expected result as below. However, I'd like to use anyone's help to know if there is any efficient way to write this code since I want to use XSLT 3.0.
I'm not sure if there is any functions in xpath 3.0 to handle string conversions to date and character removal.
<?xml version="1.0" encoding="UTF-8"?>
<Compensation>
<Salary>
<BasePay>18600.1299</BasePay>
<FormattedPayDate>2022-03-15</FormattedPayDate>
<Bonus>3500.99</Bonus>
<Gym>670</Gym>
<Tax>30000.9912</Tax>
</Salary>
<Salary>
<BasePay>28600.12</BasePay>
<FormattedPayDate>2022-03-15</FormattedPayDate>
<Bonus>1500.99</Bonus>
<Gym/>
<Tax>50000</Tax>
</Salary>
</Compensation>

Related

XSLT 3.0 Streaming (Saxon) facing error "There is more than one consuming operand" when I use two different string functions within same template

Here is my sample input xml
<?xml version="1.0" encoding="UTF-8"?>
<Update xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Request>
<List>
<RequestP><ManNumber>3B4</ManNumber></RequestP>
<RequestP><ManNumber>8T7_BE</ManNumber></RequestP>
<RequestP><ManNumber>3B5</ManNumber></RequestP>
<RequestP><ManNumber>5E9_BE</ManNumber></RequestP>
<RequestP><ManNumber>9X6</ManNumber></RequestP>
</List>
</Request>
</Update>
and xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" exclude-result-prefixes="#all">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes" />
<xsl:mode streamable="yes" />
<xsl:template match="List/RequestP/ManNumber">
<ManNumber>
<xsl:value-of select="replace(.,'_BE','')" />
</ManNumber>
<xsl:if test="contains(.,'_BE')">
<ManDescrip>BE</ManDescrip>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I am getting below error for above xslt, I am using Saxon 11.2 version
Template rule is not streamable
* There is more than one consuming operand: {<ManNumber {xsl:value-of}/>} on line 6, and {if(fn:contains(...)) then ... else ...} on line 9
The xslt works fine if I use either "replace" or "contains" but not both within same template.
Streamed processing, if you have needs (huge input documents in the size of gigabytes) to use it, requires you to limit your XSLT to streamable code, that means you can for instance make a copy of that element and processed only that small element node as a complete in memory element in a different mode
<xsl:template match="List/RequestP/ManNumber">
<xsl:apply-templates select="copy-of(.)" mode="grounded"/>
</xsl:template>
<xsl:template name="grounded" match="ManNumber">
<ManNumber>
<xsl:value-of select="replace(.,'_BE','')" />
</ManNumber>
<xsl:if test="contains(.,'_BE')">
<ManDescrip>BE</ManDescrip>
</xsl:if>
</xsl:template>

Creating xsl:result-document with xpath 3.1 fn:transform using saxon 9.9 EE

I'd like to create an output document using the xpath 3.1 fn:transform. Following is A.xsl. It creates A.xml when run directly (from oxygen):
<?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:output name="xml" method="xml" indent="true" />
<xsl:template name="xsl:initial-template">
<xsl:message select="'A'"/>
<xsl:result-document href="file:/C:/Work/test/A.xml" format="xml">
<resultDoc>
<text>The result of A.</text>
</resultDoc>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
Result: A.xml is created with the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<resultDoc>
<text>The result of A.</text>
</resultDoc>
Now, using the transform function to call A.xsl:
<?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="3.0">
<xsl:output name="xml" method="xml" encoding="UTF-8" indent="true" />
<!-- Global Constants -->
<xsl:variable name="xsl-file-base" select="'file:/C:/Work/test/'" as="xs:string"/>
<xsl:variable name="xsl-pipeline" select="'A.xsl'" as="xs:string"/>
<!-- Entry Point -->
<xsl:template name="xsl:initial-template">
<xsl:iterate select="$xsl-pipeline">
<xsl:variable name="file" select="$xsl-file-base || ." as="xs:string"/>
<xsl:result-document href="file:/C:/Work/test/A.xml" format="xml">
<xsl:sequence select="transform(map{'stylesheet-location' : $file})?output"/>
</xsl:result-document>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Result: A.xml is created but incomplete. Any help is appreciated.
<?xml version="1.0" encoding="UTF-8"?>
The result of the transform function is a map with an entry named output for the primary result document and further entries for secondary result documents. Your called stylesheet creates a secondary result with the URI file:/C:/Work/test/A.xml so
<xsl:sequence
select="transform(map{'stylesheet-location' : $file})('file:/C:/Work/test/A.xml')"/>
is more likely to produce an output.

How do I split and print values in xslt?

I want to create an xslt(version 2) where value can be split with delimeter ':' and print only first part of split and store the second part into a variable. The values have to be passed to a 'student' tag. Following are the values that are fetched from db
Adam:101
Brad:110
Chad:111
Expected output:
Adam
Brad
Chad
and values 101, 110 and 111 have to stored into a variable.
Please also provide a link where xslt2.0 tutorial is available in detail.
Simply you can use fn:tokenize() to achieve the output:
Assume input:
<student>Adam:101 Brad:110 Chad:111</student>
XSLT:
<?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="#all"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<students>
<xsl:apply-templates/>
</students>
</xsl:template>
<xsl:template match="student">
<xsl:for-each select="tokenize(., ' ')">
<student variable="{substring-after(., ':')}">
<xsl:value-of select="substring-before(., ':')"/>
</student>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student variable="101">Adam</student>
<student variable="110">Brad</student>
<student variable="111">Chad</student>
</students>
Link: https://xsltfiddle.liberty-development.net/gVrvcxx

How can we remove blank line without strip-space and indent XSLT2.0

I am trying remove blank lines without stripping spaces and indentation, because my requirement is indented XML. Can anyone help me, please?
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<Export>
<bio id="b0001">
<p>Maciej</p>
</bio>
<funding-group>
<award-group>
<funding-source>
<named-content content-type="funder-name">Narodowe</named-content>
<named-content content-type="funderidentifier">501100004281</named-content>
</funding-source>
<award-id>/19/B/HS4/</award-id>
<award-id>3</award-id>
</award-group>
</funding-group>
</Export>
Expected Result
<?xml version="1.0" encoding="UTF-8"?>
<Export>
<bio id="b0001">
<p>Maciej</p>
</bio>
<funding-group>
<award-group>
<funding-source>
<named-content content-type="funder-name">Narodowe Centrum Nauki</named-content>
<named-content content-type="funderidentifier">10.13039/501100004281</named-content>
</funding-source>
<award-id>/19/B/HS4/</award-id>
<award-id>3</award-id>
</award-group>
</funding-group>
</Export>
XSLT I have tried this xsl but it's not worked properly if i have run this xsl then generate XML single line.
<?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" omit-xml-declaration="no"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()">
<xsl:value-of select="translate(.,'
', '')"/>
</xsl:template>
</xsl:stylesheet>

XSLT: output " without it being parsed

I am trying to achieve the following XML output:
<Foo bar=""" />
My XSLT file is as follows:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="quote">
<xsl:text>"</xsl:text>
</xsl:variable>
<Foo bar="{$quote}"/>
</xsl:template>
</xsl:stylesheet>
Unfortunately, this gives me the output:
<Foo bar="""/>
How do I alter my XSLT to output & quot; without it being parsed into either a " character or a & #34;?
Ian Roberts has already made the very good point that it doesn't actually matter. But if you really, really wanted to do this, then in XSLT 2.0 (but not XSLT 1.0) you could make use of a character map, like so:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" use-character-maps="quotes" />
<xsl:character-map name="quotes">
<xsl:output-character character=""" string="&quot;" />
</xsl:character-map>
<xsl:template match="/">
<xsl:variable name="quote">
<xsl:text>"</xsl:text>
</xsl:variable>
<Foo bar="{$quote}"/>
</xsl:template>
</xsl:stylesheet>

Resources