xsl choose ignoring the when elements and only outputting the otherwise - xslt

I have an xslt template that i use for converting data, the one i have right now has a choose statement with a bunch of when clauses for each eventuality, the output given is that it seems to ignore all the when's and just goes straight to the otherwise.
ive removed a couple of when statements to shorten it
Ive been through it step by step, changed the select statement to look at the code=a directly.
Something is wrong with my when but i cant seem to see it
<xsl:for-each select="datafield[#tag='300']">
<xsl:choose>
<xsl:when test="./subfield[#code='a'] !='' and ./subfield[#code='e'] !='' and ./subfield[#code='c'] !=''">
<xsl:element name="PhysicalDescription">
<xsl:value-of select="./subfield[#code='a']" />
</xsl:element>
<xsl:element name="CoverColour">
<xsl:value-of select="./subfield[#code='c']" /> - <xsl:value-of select="./subfield[#code='e']" />
</xsl:element>
</xsl:when>
<xsl:when test="./subfield[#code='a'] !='' and ./subfield[#code='c'] !='' and ./subfield[#code='e'] =''">
<xsl:element name="PhysicalDescription">
<xsl:value-of select="./subfield[#code='a']" />
</xsl:element>
<xsl:element name="CoverColour">
<xsl:value-of select="./subfield[#code='c']" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="PhysicalDescription">
<xsl:value-of select="./subfield[#code='a']" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
XML Data
<datafield tag="300" ind1="0" ind2="0">
<subfield code="a">1-914pp (vol.1)</subfield>
</datafield>
<datafield tag="300" ind1="0" ind2="0">
<subfield code="a">915-2385pp (vol.2)</subfield>
</datafield>
<datafield tag="300" ind1="0" ind2="0">
<subfield code="a">cli, 835p (supp)</subfield>
<subfield code="e">Pbk</subfield>
</datafield>
<datafield tag="300" ind1="0" ind2="0">
<subfield code="c">navy cover</subfield>
<subfield code="e">Hbk</subfield>
</datafield>
It should loop through the datafield[#tag='300'] node and for each one that it finds output a value based on the xsl:when.
it only ever outputs the otherwise statement

The problem you have is with expressions of this form....
./subfield[#code='e'] =''
This will only be evaluated to try if a subfield[#code='e'] exists, and its value is an empty string. If there is no subfield[#code='e'] in your XML, then the expression evaluates to false.
What you need to do is write it like this: (Note, the ./ prefix is not really needed in any of your expressions).
not(subfield[#code='e'] !='')
This is like a double-negative, but in the case subfield[#code='e'] does not exist, the expression now gets evaluated to true.
So, for your given XSLT, instead of doing this:
<xsl:when test="./subfield[#code='a'] !='' and ./subfield[#code='c'] !='' and ./subfield[#code='e'] =''">
Do this
<xsl:when test="subfield[#code='a'] !='' and subfield[#code='c'] !='' and not(subfield[#code='e'] !='')">
Do note for your given XSLT, the xsl:when statements need both an "a" and "c" present in the XML, which is not the case for the sample provided.
See http://xsltfiddle.liberty-development.net/ncntCRS as an example.

Related

XSLT determining if a parameter is null or empty then do something

I'm trying to determine if ../../coupon_code is null or empty. I've tried the methods in this thread Check if a string is null or empty in XSLT to no avail. Maybe I'm doing something wrong?
<!--Coupon Code Name and Code-->
<xsl:choose>
<xsl:when test="not(../../coupon_code)">
<xsl:if test="../../coupon_code != ''">
<xsl:value-of select="../../coupon_rule_name" /> <xsl:value-of select="../../coupon_code" /><xsl:value-of select="$sepend" />D.PROMOTION<xsl:value-of select="$sepend" />
</xsl:if>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
<!--End Coupon Code Name and Code-->
I'm doing
<xsl:when test="not(../../coupon_code)">
To determine if it's null. Then, I'm doing
<xsl:if test="../../coupon_code != ''">
To determine if it's empty.
However, I'm looking at data where this is clearly populated, and it's not entering the when/if to display the data at all. So it's failing somewhere and I don't know where.
Sometimes, ../../coupon_code will have a coupon code in it, like COUPON122. Sometimes it won't have anything in it.
You can check
<xsl:if test="normalize-space(../../coupon_code)">
<xsl:value-of select="../../coupon_rule_name" /> <xsl:value-of select="../../coupon_code" /><xsl:value-of select="$sepend" />D.PROMOTION<xsl:value-of select="$sepend" />
</xsl:if>
to have the xsl:value-ofs processed only if the ../../coupon_code element has some non-whitespace content.

XSLT: How do I use a concatenated string as a variable name with xsl:for-each select?

I'm being put on a system migration project, and my task is to receive a SOAP XML request from System A, then convert 6 parameter names inside the request into new names readable by System B, and output a file with just the parameters changed. (all param names changed)
Request:
<q0:Command>
<q0:Transaction>
<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
<q0:ParameterList>
<q0:StringParameter name="Action">Create</q0:StringParameter>
<q0:StructParameter name="Create">
<q0:StringParameter name="UserID">1234567X</q0:StringParameter>
<q0:StringParameter name="Device1">abcd567890</q0:StringParameter>
<q0:StringParameter name="Contract1">Postpaid</q0:StringParameter>
<q0:StringParameter name="Line1">990108024011900</q0:StringParameter>
<q0:StringParameter name="Device2">efgh567890</q0:StringParameter>
<q0:StringParameter name="Contract2">Postpaid</q0:StringParameter>
<q0:StringParameter name="Line2">990104562499105</q0:StringParameter>
</q0:StructParameter>
</q0:ParameterList>
</q0:Operation>
</q0:Transaction>
</q0:Command>
First, I just manually recreated each param with the new name in a for-each select block, and it worked. The situation is, there is now a requirement that the values shouldn't be hardcoded one-by-one, but instead checked in a loop (Just in case the number of devices per user increases).
So I thought of declaring a $LoopIndex variable, and concatenating it to strings that match the parameter names, like so:
So I implemented Tim's answer below into my for-each section, like so:
<xsl:for-each select="//q0:CommandRequestData/q0:Command/q0:Transaction/q0:Operation/q0:ParameterList/q0:StructParameter[#name='Create']">
<xsl:for-each select="q0:StringParameter">
<xsl:choose>
<xsl:when test="#name = 'UserID'">
<ins:Parameter name="USER_ID" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Device')">
<ins:Parameter name="concat('DEVICE_ID_', substring(., 7))" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Contract')">
<ins:Parameter name="concat('CONTRACT_TYPE_', substring(., 9))" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Line')">
<ins:Parameter name="concat('LINE_ID_', substring(., 5))" value="{string()}" />
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
And the above code isn't working. The page isn't understanding that I mean "#name='Device1'" when I say "#name=concat('Device', $LoopIndex)". Is this kind of thing possible with XSLT or will I need to stick to hardcoding the parameter assignments?
Unfortunately, it's only semi-successful. The page is literally taking the "concat('DEVICE_ID_', substring(., 7))" as a string for the parameter name, instead of performing the concatenation.
Update: Got inspired by Tim's answer and found a 'simple' way of using position() to just increment the numbers:
<xsl:for-each select="//q0:CommandRequestData/q0:Command/q0:Transaction/q0:Operation/q0:ParameterList/q0:StructParameter[#name='Create']">
<xsl:for-each select="q0:StringParameter[#name='UserID']">
<ins:Parameter name="USER_ID" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Device')]">
<xsl:variable name="DeviceIndex" select="position()" />
<ins:Parameter name="DEVICE_ID_{$DeviceIndex}" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Contract')]">
<xsl:variable name="ContractIndex" select="position()" />
<ins:Parameter name="CONTRACT_TYPE_{$ContractIndex}" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Line')]">
<xsl:variable name="LineIndex" select="position()" />
<ins:Parameter name="LINE_ID_{$LineIndex}" value="{string()}" />
</xsl:for-each>
</xsl:for-each>
The resulting SOAP XML request passed to System B now successfully looks like below. It's not arranged like the SOAP request...but I'll take it! :)
<q0:Command>
<q0:Transaction>
<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
<q0:ParameterList>
<q0:StringParameter name="Action">Create</q0:StringParameter>
<q0:StructParameter name="Create">
<ins:Parameter name="USER_ID" value="1234567X"/>
<ins:Parameter name="DEVICE_ID_1" value="abcd567890"/>
<ins:Parameter name="DEVICE_ID_2" value="efgh567890"/>
<ins:Parameter name="CONTRACT_TYPE_1" value="Postpaid"/>
<ins:Parameter name="CONTRACT_TYPE_2" value="Postpaid"/>
<ins:Parameter name="LINE_ID_1" value="990108024011900"/>
<ins:Parameter name="LINE_ID_2" value="990104562499105"/>
</q0:StructParameter>
</q0:ParameterList>
</q0:Operation>
</q0:Transaction>
</q0:Command>
I would use template matching here on the attribute, along with the identity template, rather than xsl:for-each and xsl:choose, just to keep the code cleaner (although it is not strictly necessary to solve your problem).
But to solve your problem you can use string functions to extract the current number from the #name attribute.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:q0="q0" >
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="q0:StructParameter[#name='Create']/q0:StringParameter/#name">
<xsl:attribute name="name">
<xsl:choose>
<xsl:when test=". = 'UserID'">
<xsl:text>USER_ID</xsl:text>
</xsl:when>
<xsl:when test="starts-with(., 'Device')">
<xsl:value-of select="concat('DEVICE_ID_', substring(., 7))" />
</xsl:when>
<xsl:when test="starts-with(., 'Contract')">
<xsl:value-of select="concat('CONTRACT_TYPE_', substring(., 9))" />
</xsl:when>
<xsl:when test="starts-with(., 'Line')">
<xsl:value-of select="concat('LINE_ID_', substring(., 5))" />
</xsl:when>
<xsl:otherwise>NA</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Note that you would have to change the namespace URI for q0 to match your actual one accordingly.

XSLT - Format-number output NaN

I'm new to XSLT. I'm working with zip codes and I'm trying to padleft zeros to any zipcode under 5 characters. Otherwise, I want my code to simply write the input parameter exactly as is. I'm running into a problem when the zipcode starts with or contains a letter. My output is returning a NaN. How do I tell the code that whenever the zipcode contains a letter, to simply write out the zipcode as is without running the "format-number" logic? I know about the "starts-with" and "contain" functions but I don't totally understand how they work.
<xsl:template name="MyZipCodeUnder5DigitsPadLeftZerosTemplate">
<xsl:param name="BillToZipcode" />
<xsl:choose>
<xsl:when test="string-length($BillToZipcode) < 5">
<xsl:element name="PostalCode">
<xsl:value-of select="format-number($BillToZipcode, '00000')" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="PostalCode">
<xsl:value-of select="$BillToZipcode"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
How about:
<xsl:template name="MyZipCodeUnder5DigitsPadLeftZerosTemplate">
<xsl:param name="BillToZipcode" />
<PostalCode>
<xsl:choose>
<xsl:when test="number($BillToZipcode)">
<xsl:value-of select="format-number($BillToZipcode, '00000')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$BillToZipcode"/>
</xsl:otherwise>
</xsl:choose>
</PostalCode>
</xsl:template>
This assumes no (numerical) Zipcode can have more than 5 digits. If this is not true, you could use:
<xsl:when test="number($BillToZipcode) < 10000">

confusing error thown while trying to match templates

I've the below XML line of code.
<entry colname="col3" align="left" valign="top"><para>grandchild, cousin, <content-style font-style="italic">etc</content-style>., shall be described as “lawful” and “one of the next-of-kin” or “only next-of-kin”.</para></entry>
and below XSL
<xsl:template match="entry" name="entry">
<xsl:choose>
<xsl:when test="./#namest">
<xsl:variable name="namest" select="#namest" />
<xsl:variable name="nameend" select="#nameend" />
<xsl:variable name="namestPos" select="count(ancestor::tgroup/colspec[#colname=$namest]/preceding-sibling::colspec)" />
<xsl:variable name="nameendPos" select="count(ancestor::tgroup/colspec[#colname=$nameend]/preceding-sibling::colspec)" />
<td colspan="{$nameendPos - $namestPos + 1}" align="{#align}">
<xsl:apply-templates select="child::node()[not(self::page)]" />
</td>
</xsl:when>
<xsl:otherwise>
<td>
<xsl:if test="./#morerows">
<xsl:attribute name="rowspan">
<xsl:value-of select="number(./#morerows)+1" />
</xsl:attribute>
</xsl:if>
<xsl:if test="./#align">
<xsl:attribute name="align">
<xsl:value-of select="#align" />
</xsl:attribute>
</xsl:if>
<xsl:if test="./#valign">
<xsl:attribute name="valign">
<xsl:value-of select="#valign" />
</xsl:attribute>
</xsl:if>
<xsl:for-each select="para">
<div class="para">
<xsl:choose>
<xsl:when test="../#colname='col3' and contains(./text(),'.')">
<xsl:variable name="strl">
<xsl:value-of select="fn:string-length(fn:substring-before(.,'.'))" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$strl < '6'">
<a href="{concat('er:#SCP_ORD_',//chapter/#num,'/','P',translate(./text(),'.','-'))}">
<xsl:value-of select="./text()" />
</a>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates />
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:for-each>
</td>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
and when i run this on my XML in the above mentioned line(the XML given in the sample), it is throwing me an error. and the error is as below.
Wrong occurrence to match required sequence type - Details: - XPTY0004: The supplied sequence ('2' item(s)) has the wrong occurrence to match the sequence type xs:string ('zero or one')
here what i actually was trying to achieve is, i have another table(which is basically a TOC), where in there is some linking needed, and below is such sample entries.
<entry colname="col3" align="right" valign="top"><para>A.1</para></entry>
<entry colname="col3" align="right" valign="top"><para>A.2</para></entry>
here i'm searching if the colname is col3 and if this has a . in it, and the above two cases mentioned are passing and are getting linked successfully, where in the case mentioned in the top is throwing the error, can anyone please suggest some better method to differentiate these two cases, and i use XSLT 2.0.
Thanks
The problem is
contains(./text(),'.')
./text() is not "the text of the current element" but rather the sequence of all text node children of the current element. In the case of
<para>grandchild, cousin, <content-style font-style="italic">etc</content-style>., shall be described as “lawful” and “one of the next-of-kin” or “only next-of-kin”.</para>
there are two such nodes, one containing everything between the <para> and <content-style> tags ("grandchild, cousin, " including the trailing space) and the other containing everything between the </content-style> and the </para>. But the contains function expects its first argument to be a single string, not a sequence of two nodes.
Instead of testing ./text() you probably just need to test .:
contains(., '.')
which is interpreted as the string value of the whole para element, i.e. a single string consisting of the concatenation of all the descendant text nodes (so the whole text "grandchild, cousin, etc., shall be described...").

Different way of sorting a list, depending on a variable in XSLT

I am making a newslist, and have until now sorted the news after their date. Newest first.
But I would like to give the administrator of the site a more flexible solution. This means that in the backend, the admin can choose from a dropdown-list, in wich way he/she want's the list to be sorted. By date(newest first and oldest first), or by title (A-Z and Z-A). This means 4 possible ways right.
Right now I have the following XSLT:
<xsl:variable name="alleNyheder" select="$currentPage//node" />
<xsl:variable name="sort">
<news>
<xsl:for-each select="$alleNyheder[#template='1092']">
<news>
<id>
<xsl:value-of select="./#id"></xsl:value-of>
</id>
<date>
<xsl:choose>
<xsl:when test="./data[#alias='date'] != ''">
<xsl:value-of select="./data[#alias='date']"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="./#createDate"/>
</xsl:otherwise>
</xsl:choose>
</date>
</news>
</xsl:for-each>
</news>
</xsl:variable>
<xsl:for-each select="msxml:node-set($sort)/news/news">
<xsl:sort data-type="text" select="date" order="descending" />
---- My newsitems ----
</xsl:for-each>
So in the moment I sort my list after the "date"-value in my variable $sort.
If I change the "date"-field in $sort, to titles instead of date's I can actually sort my list after the titles of the news. But unfortunately it should be sorted in an ascending order, instead of a descending order. And I can't figure out how to change the order-value dynamically like I do in the select-value.
If it helps anyone, I am working on Umbraco CMS.
Thanks
-Kim
<xsl:choose>
<xsl:when test="$sortfield = 'date' and $sortorder = 'D'>
<xsl:for-each select="msxml:node-set($sort)/news/news">
<xsl:sort data-type="text" select="date" order="descending" />
<!-- ... -->
</xsl:for-each>
</xsl:when>
<xsl:when test="$sortfield = 'date' and $sortorder = 'A'>
<xsl:for-each select="msxml:node-set($sort)/news/news">
<xsl:sort data-type="text" select="date" order="ascending" />
<!-- ... -->
</xsl:for-each>
</xsl:when>
<!-- ... -->
</xsl:choose>
On a different note, you really should look into <xsl:apply-templates> and avoid <xsl:for-each>. Your code gets cleaner and a lot more idiomatic this way. I am also sure that the entire temporary node-set() business is completely avoidable.