Iterate over temporary document using xslt - xslt-2.0

I'm trying to create a temporary document containing some data, so I don't have it scattered all over the xsl file. I'm trying to loop through this data in the following manner:
<xsl:variable name="stuff">
<foo name="bar" key="83"/>
<foo name="baz" key="73"/>
<foo name="qux" key="71"/>
<foo name="quux" key="72"/>
</xsl:variable>
<xsl:for-each select="$stuff/foo" >
<xsl:value-of select="#key" />
</xsl:for-each>
The for-each block is never entered. I've tried to mimic the method described here. I also looked into using node-set(), but as far as I can tell that function is only required for XSLT version 1.0?

Tim C gave a complete answer about this in the following thread: Use a xsl:for-each over an xml to decide which label-value pairs are displayed?. I suggest you have a look at it.
Basically for XSLT 2.0 you can include the XML which you entered now into a <xsl:variable> directly into your XSLT:
<my:stuff>
<foo name="bar" key="83"/>
<foo name="baz" key="73"/>
<foo name="qux" key="71"/>
<foo name="quux" key="72"/>
</my:stuff>
Then in your XSLT where needed declare a variable:
<xsl:variable name="stuff" select="document('')/*/my:stuff" />
So the XSLT will be something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my" exclude-result-prefixes="my">
<my:stuff>
<foo name="bar" key="83"/>
<foo name="baz" key="73"/>
<foo name="qux" key="71"/>
<foo name="quux" key="72"/>
</my:stuff>
<xsl:template match="/">
<xsl:variable name="stuff" select="document('')/*/my:stuff"/>
<xsl:for-each select="$stuff/foo">
<xsl:value-of select="#key" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

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>

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>

Having difficulty in looping partNumber XSLT being applied shown below

Sample SOAP Input XML Given below:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Body>
<PullCustomerPartsPricingResponse xmlns="http://cdx.dealerbuilt.com/Api/0.99/">
<PullCustomerPartsPricingResult xmlns:a="http://schemas.datacontract.org/2004/07/DealerBuilt.BaseApi" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:CustomerPart>
<a:Placement>
<a:GroupId>10</a:GroupId>
</a:Placement>
<a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/DealerBuilt.Models.Parts">
<b:Description>PAD SET, RR.</b:Description>
<b:PartNumber>31500SB2100M</b:PartNumber>
<b:PartNumberFormatted>31500-SB2-100M</b:PartNumberFormatted>
</a:Attributes>
</a:CustomerPart>
<a:CustomerPart>
<a:Placement>
<a:GroupId>10</a:GroupId>
</a:Placement>
<a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/DealerBuilt.Models.Parts">
<b:Description>Kite SET, RR.</b:Description>
<b:PartNumber>60211T7J305ZZ</b:PartNumber>
</a:Attributes>
</a:CustomerPart>
</PullCustomerPartsPricingResult>
</PullCustomerPartsPricingResponse>
</s:Body>
</s:Envelope>
XSLT Code being applied Shown below:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<xsl:for-each select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']">
<partDetail>
<partNumber>
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']/*[local-name()='Attributes'] /*[local-name()='PartNumber']" />
</partNumber>
<partDescription>
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']/*[local-name()='Attributes'] /*[local-name()='Description']" />
</partDescription>
</partDetail>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
My Current sample Output Shown below:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
</root>
My Desired Output shown below:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
<results>
<partDetail>
<partNumber>60211T7J305ZZ</partNumber>
<partDescription>Kite SET, RR.</partDescription>
</partDetail>
</results>
</root>
I'm trying to loop element " a:CustomerPart" to print PartNumber and b:Descriptionas Output but first a:CustomerPart data is getting repeated Twice.I Undestand there is code change is required,please help on this.
Eiríkr Útlendi your patience/suggestions is much appreciated
Inside the xsl:for-each, the selected CustomerPart item becomes the context item. You should then select children/descendants of this CustomerPart using a relative path starting at this context item, not an absolute path starting at the root of the document (/).
That is, you should replace
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']
/*[local-name()='PullCustomerPartsPricingResponse']
/*[local-name()='PullCustomerPartsPricingResult']
/*[local-name()='CustomerPart']
/*[local-name()='Attributes']
/*[local-name()='PartNumber']" />
by
<xsl:value-of select="*[local-name()='Attributes'] /*[local-name()='PartNumber']" />
or better, by
<xsl:value-of select="*:Attributes/*:PartNumber" />

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