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

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>

Related

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

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>

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.

XSLT on XML Get parent Value from a mixed Node

I have an XML with some mixed Nodes,and I want to get just the value of the parent and not the child.
My XML
<?xml version="1.0" encoding="UTF-8"?>
<Records>
<DET>
<detnumber>100126</detnumber>
<EmployeeNo>100126</EmployeeNo>
<action>CHANGE</action>
<first_name> NewHire-4th
<previous>NewHire</previous>
</first_name>
<last_name>Test-Changed 4th
<previous>Test-Changed 3rd</previous>
</last_name>
<birth_name>
NewHire-Changed 4th
<previous>NewHire-Changed 3rd</previous>
</birth_name>
<formal_name>
NewHire-4th Test-Changed 4th
<previous>NewHire Test-Changed 3rd</previous>
</formal_name>
<salutation>
MISS
<previous>MRS</previous>
</salutation>
<email_address>
testHire4#gmail.com
<previous>testHire2#gmail.com</previous>
</email_address>
</DET>
</Records>
Using XSLT 2.0 ,
I am mostly using copy of in my xslt, But the whole Node and its child are being copied. I need to be able to restrict only to the parent.
<xsl:copy-of select="first_name"/>
<xsl:copy-of select="last_name"/>
<xsl:copy-of select="birth_name"/>
<xsl:copy-of select="formal_name"/>
<xsl:copy-of select="salutation"/>
Below is my preferred output
<?xml version="1.0" encoding="UTF-8"?>
<Records>
<DET>
<detnumber>100126</detnumber>
<EmployeeNo>100126</EmployeeNo>
<action>CHANGE</action>
<first_name> NewHire-4th</first_name>
<last_name>Test-Changed 4th</last_name>
<birth_name>NewHire-Changed 4th</birth_name>
<formal_name>NewHire-4th Test-Changed 4th</formal_name>
<salutation>MISS</salutation>
<email_address>testHire4#gmail.com</email_address>
</DET>
</Records>
Check this Code:-
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="previous"/>
</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>

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