Applying Conditional Logic using XSL - xslt

I have an XML document where I get several addresses for a member as address type PRIMARY, MAILING etc. however I only want to read the address as PRIMARY, MAILING when memberId is '0'. Please see the sample xml below.
<core>
<address>
<postalZipCode>90017</postalZipCode>
<updateSource>xxxxxx</updateSource>
<city>LOS ANGELES</city>
<stateProvince>CA</stateProvince>
<type>MAILING</type>
<line1>818 WEST SEVENTH STREET</line1>
</address>
<address>
<postalZipCode>95014</postalZipCode>
<updateSource>xxxxxx</updateSource>
<city>CUPERTINO</city>
<stateProvince>CA</stateProvince>
<type>PRIMARY</type>
<line1>1234 XYZ STREET</line1>
</address>
<memberId>0</memberId>
</core>
The XSL condition I am trying to evaluate in my XSLT file is as below -
<xsl:template match="core">
<xsl:when test="memberId[.='0'] and address/type[.='PRIMARY']">
<fo:table-row>
<fo:table-cell xsl:use-attribute-sets="data">
<fo:block>Line 1</fo:block>
</fo:table-cell>
</fo:table-row>
But this condition check is not working and address is not rendered in the generated document .
Could the experts here please suggest how do I go about such conditional check ?

First, you can't have <xsl:when> as a child of anything other than <xsl:choose>. I suspect in this case you probably meant to use <xsl:if>.
Secondly, your template matches core, not the primary address element- Not sure if this is intentional or not though.
Thirdly, your template doesn't actually output anything from the source document anyway, unless there's some missing from what you've included here.
As a rule it's generally advisable to write templates with predicates that fit your conditions, rather than explicit conditional logic within a template. I think what you probably want to do is:
<xsl:template match="core[memberId='0']/address[type='PRIMARY']">
<fo:etc..
</xsl:template>

Related

Consuming the same node twice in Streaming XSLT

I am trying to convert some XML to the interstitial JSON representation specified in XSLT 3.0 (for later conversion to JSON via xml-to-json). My current XSLT works well as a non-streaming stylesheet, but I'm running into issues during the conversion to streaming. Specifically, there are situations where I need to consume the same node twice, especially in the case of repeating tags in XML, which I am converting to arrays in equivalent JSON representation.
<xsl:if test="boolean(cdf:AdjudicatorName)">
<array key="AdjudicatorName">
<xsl:for-each select="cdf:AdjudicatorName">
<string>
<xsl:value-of select="."/>
</string>
</xsl:for-each>
</array>
</xsl:if>
boolean(cdf:AdjudicatorName) tests for the existance of the tag, and creates an array if so.
This code fails in Oxygen (Saxon-EE) with the following message
Template rule is declared streamable but it does not satisfy the streamability rules. * There is more than one consuming operand: {fn:exists(...)} on line {X}, and {<string {(attr{key=...}, ...)}/>} on line {Y}
I am aware of the copy-of workaround, however, many items in the source file can repeat at the highest level, so use of this approach would yield minimal memory savings.
This looks like the perfect use case for xsl:where-populated:
<xsl:where-populated>
<array key="AdjudicatorName">
<xsl:for-each select="cdf:AdjudicatorName">
<string>
<xsl:value-of select="."/>
</string>
</xsl:for-each>
</array>
</xsl:where-populated>
The xsl:where-populated instruction (which was invented for exactly this purpose) logically evaluates its child instructions, and then eliminates any items in the resulting sequence that are "deemed empty", where an element is deemed empty if it has no children. In a streaming implementation the start tag (<array>) will be "held back" until its first child is generated, and when the corresponding end tag (</array>) is emitted, the pair of tags will be discarded if there were no intervening children emitted.

Traverse xml document using xpath and xsl

I am using XSL and XPATH to traverse, read and transform a complex XML file where I have multiple nodes with same names but different values. My sample xml file is below -
<core>
<address>
<postalZipCode>90017</postalZipCode>
<updateSource>xxxxxx</updateSource>
<city>LOS ANGELES</city>
<stateProvince>CA</stateProvince>
<type>MAILING</type>
<line1>818 WEST SEVENTH STREET</line1>
</address>
<address>
<postalZipCode>95014</postalZipCode>
<updateSource>xxxxxx</updateSource>
<city>CUPERTINO</city>
<stateProvince>CA</stateProvince>
<type>PRIMARY</type>
<line1>1234 XYZ STREET</line1>
</address>
<memberId>0</memberId>
</core>
When I read the MAILING ADDRESS Line 1 value , the value I am getting is "1234 XYZ STREET" which essentially is the PRIMARY ADDRESS . Here is my xsl file snippet :-
<xsl:template match="core">
<xsl:if test="memberId['0'] and address/type['MAILING']">
<fo:table-row>
<fo:table-cell><fo:block /></fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="data">
<fo:block>Line 1</fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="data">
<fo:block><xsl:value-of select="address/line1/text()"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
Could the experts here please suggest if there is any other way possible to force XSL to read the correct value of where address is MAILING, instead of reading the first value it finds ???
if there is any other way possible to force XSL to read the correct
value of where address is MAILING, instead of reading the first value
it finds ???
Your question is rather confusing, because MAILING is the first address in your example - and the snippet you show does not produce the result you say it does.
In any case, to make sure you get data from the MAILING address regardless of its position, change this:
<xsl:value-of select="address/line1/text()"/>
to:
<xsl:value-of select="address[type='MAILING']/line1"/>
To understand how this works, read more about predicates.
The code you have written says "if there is an address with type MAILING, then print the value of address/line1 (whether or not this is in any way related to the address with type MAILING)".
In XSLT 1.0, when xsl:value-of selects more than one node, it displays the value of the first one it finds. In XSLT 2.0 it concatenates the values of all the selected nodes, with a separator that defaults to a single space.
If there's more than one node and you want to select a specific one, then select it with a predicate, e.g. xsl:value-of select="address[#type='MAILING']/line1.

pre-processing script to switch product codes

I have a snippet of code I've inherited and I'm trying to get it to work on multiples of the match pattern and set a tag from looking up a value from a table using another tag. What happens is that, for every item, the same lookup is performed and not the relative one for the node. I can't work out the syntax to work thru all entries and substitute the correct one. It's got to be simple it's just that I am simpler :)
My source xml contains this (within an outer /oomsdoc document node not shown):
<item>
<lineqty> 1</lineqty>
<linesku>BNLP5008 </linesku>
<linecustprod>xxxxxxxxxxxxxxx</linecustprod>
<linedesc>London Pride (Bot500mlx8) </linedesc>
</item>
<item>
<lineqty> 1</lineqty>
<linesku>BNBL5008 </linesku>
<linecustprod>xxxxxxxxxxxxxxx</linecustprod>
<linedesc>Bengal Lancer (Bot500mlx8) </linedesc>
</item>
I want to substitute the xxxxxxxxxxxxxxx in each linecustprod tag with the material from the lookup table using the value of the linesku tag.
This is my lookup table:
<Materials>
<product sku='BNLP5008 ' material='LONDON PRIDE'/>
<product sku='BNBL5008 ' material='BENGAL LANCER'/>
</Materials>
and this is my xslt code.
<xsl:variable name="SkuList" select="document('d:\test\transforms\catalogue.xml')/Materials"/>
<xsl:template match="/oomsdoc/item/linecustprod">
<xsl:variable name="MySku" select="/oomsdoc/item/linesku"/>
<linecustprod>
<xsl:value-of select="$SkuList/product[#sku=$MySku]/#material"/>
</linecustprod>
</xsl:template>
I'm guessing some kind of xsl foreach would work but just can't find a usable example to crib :)
Your guidance again would be appreciated at this point in my frustration :)
Thanks,
Brian.
Changing the variable definition to
<xsl:variable name="MySku" select="../linesku"/>
should be sufficient, this will pull out the linesku that is a sibling to the linecustprod you're currently looking at. As currently defined the variable will contain a node set of all the linesku elements in the document, so the value-of will give you the first entry from $SkuList that matches any entry in the main input file.
In addition to Ian Roberts' answer, please change
<xsl:variable name="SkuList" select="document('d:\test\transforms\catalogue.xml')/Materials"/>
to
<xsl:variable name="SkuList" select="document('/d:\test\transforms\catalogue.xml')/Materials"/>
for some reason, the first throws an error (malformed URL).

Using same data element name within for-each

More for reference than actual need: what is the XPath syntax to allow me to reference an element in a xsl:for-each block when the same element name is used elsewhere?
Please note, unfortunately this must be a 1.0 solution
For example, I have the following simple XML, and I want to match up the items with the same id value...
<data>
<block1>
<item><id>1</id><text>Hello</text></item>
<item><id>2</id><text>World</text></item>
</block1>
<block2>
<item><id>1</id><text>123</text></item>
<item><id>2</id><text>ABC</text></item>
</block2>
</data>
If I have a for-each on the block1, how can I reference both the id within the block1 and the id within the block2?
This will work, but I think it is messy...
<xsl:for-each select="//block1/item">
<xsl:variable name="id" select="id"/>
<xsl:value-of select="text"/> - <xsl:value-of select="//block2/item[id=$id]/text"/>
</xsl:for-each>
With the result of...
Hello - 123
World - ABC
Is there a simplified way of replacing the $id in select="//block2/item[id=$id]/text" so that it is referring to the id element from the for-each?
Another way to do it which you may find clearer, and will probably be faster, is to use keys:
<xsl:key name="b2" match="block2/item" use="id"/>
then
<xsl:value-of select="key('b2', id)/text"/>
What you have is correct and common as it is. There's no need to simplify it further; it's a standard idiom recognized and used by those working with XSLT.

Debug possible escaped-text issue in XSLT?

I have an XSL template that gives different results when used in two different contexts.
The template manifesting the defect is:
<xsl:template match="*" mode="blah">
<!-- snip irrelevant stuff -->
<xsl:if test="see">
<xsl:message>Contains a cross-ref. <xsl:value-of select="."/></xsl:message>
</xsl:if>
<xsl:apply-templates select="."/>
</xsl:template>
Given:
<el>This is a '<see cref="foo"/>' cross-referenced element.</el>
In one situation, I get the desired result:
Contains a cross-ref. This is a ' ' cross-referenced element.
(the <see/> is being dealt with as an XML element and is ultimately matched by another template.)
But in another situation, the xsl:if doesn't trigger and if I output the contents with <xsl:message><xsl:value-of select="."/>, I get:
This is a '<see cref="foo"/>' cross-referenced element.
It seems to me that in the latter improperly-behaving scenario, it's acting like it's been output-escaped. Does that make sense? Am I barking up the wrong tree? This is a typically complex XSL situation and trying to trace the call-stack is difficult; is there a particular XSLT processing command I should be looking for?