Difference between 'where' and 'if' in an XSLT script - if-statement

I am new to XML/XSLT, and I'm a bit confused about the difference between the <xsl:if test="x"> and adding a where="x" at the end of a statement.
Below is some example data and two XSLT versions of code. I tried running it both ways here: https://www.w3schools.com/xml/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog_ex1 but nothing appears, so I may be doing something wrong. Is anyone able to clarify this for me?
<?xml version="1.0"?>
<Tests xmlns="http://www.adatum.com">
<Test TestId="0001" TestType="CMD">
<Name>Convert number to string</Name>
<CommandLine>Examp1.EXE</CommandLine>
<Input>1</Input>
<Output>One</Output>
</Test>
<Test TestId="0002" TestType="CMD">
<Name>Find succeeding characters</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>abc</Input>
<Output>def</Output>
</Test>
<Test TestId="0003" TestType="GUI">
<Name>Convert multiple numbers to strings</Name>
<CommandLine>Examp2.EXE /Verbose</CommandLine>
<Input>123</Input>
<Output>One Two Three</Output>
</Test>
<Test TestId="0004" TestType="GUI">
<Name>Find correlated key</Name>
<CommandLine>Examp3.EXE</CommandLine>
<Input>a1</Input>
<Output>b1</Output>
</Test>
<Test TestId="0005" TestType="GUI">
<Name>Count characters</Name>
<CommandLine>FinalExamp.EXE</CommandLine>
<Input>This is a test</Input>
<Output>14</Output>
</Test>
</Tests>
Using where my XSLT is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="Tests/Test" where="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:for-each>
</xsl:template>
</xsl:stylesheet
Code using the if statemtent
<?xml version="1.0" encoding="UTF-8"?>
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="Tests/Test">
<xsl:if test="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet

The is no where attribute for xsl:for-each.
What you mean is called a predicate which is enclosed by Double brackets.
So change your xsl:for-each from
<xsl:for-each select="Tests/Test" where="#TestType='CMD'">
<xsl:value-of select="current()">
</xsl:for-each>
to
<xsl:for-each select="Tests/Test[#TestType='CMD']">
<xsl:value-of select="current()">
</xsl:for-each>
That should do the trick.

Related

XSL 1.0 If not like

I'm using XSL to create a PDF document template, and don't want certain fields to display if the line value is zero.
I have tried
<xsl:if test="line_value != 0">
<xsl:with-param name="value" select="unit_quantity"/>
</xsl:if>
But this doesn't work. I think because line_value is of the format £0.00.
I'm trying to get it to do line_value NOT LIKE '£0.00', but I don't think that's the correct syntax for XSL.
I am assuming that below is your XML:
INPUT:
<?xml version="1.0" encoding="utf-8" ?>
<body>
<line_value>£0.00</line_value>
<line_value>£1.00</line_value>
<line_value>£0.00</line_value>
<line_value>£2.00</line_value>
<line_value>£0.00</line_value>
<line_value>£5.00</line_value>
<line_value>£0.00</line_value>
<line_value>£1.00</line_value>
</body>
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="xs" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="body/line_value">
<xsl:if test="number(translate(., '£', '')) != 0">
<num>
<xsl:value-of select="."/>
</num>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

XSLT for-each-group from variable does not work

for-each-group from XSLT 2.0 works as expected from a file but not from a variable.
Have this file:
~$ cat test.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
Using stylesheet for grouping this file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0"
encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="*">
<!-- variable not used for file test -->
<xsl:variable name="fields">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>
<xsl:for-each-group select="*" group-starting-with="delimiter">
<field>
<xsl:for-each select="current-group()">
<xsl:value-of select="self::c"/>
</xsl:for-each>
</field>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I get the result I want:
<?xml version="1.0" encoding="UTF-8"?>
<field>AB</field>
<field>C</field>
Trying to group the variable name="fields" with:
<xsl:for-each-group select="$fields/*" group-starting-with="delimiter">
I get the result:
<?xml version="1.0" encoding="UTF-8"?>
<field/>
Why does for-each-group works on a file but not from a variable?
The variable fields is a document-node(), you can define the type of the variable to be element()
<xsl:variable name="fields" as="element()">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>

SOAP to XML transformation

I am trying to do SOAP to XML transformation in XSLT.
I have a field called "acctCd" which is under "externalIDGroup" which is an array. SO i need to check the field lenth for "acctCd" if it is more then 10 charatcer i need to trim it to 10 character and if it is less i need to get the valuen as it is. This value can come multiple times.Please let me know where is the issue in my code
INput:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:ClaimsRequest xmlns:ns2="http://example.com">
<externalIDGroup>
<acctCd>plan1 Options</acctCd>
</externalIDGroup>
<externalIDGroup>
<acctCd>Plan2 Options</acctCd>
</externalIDGroup>
</ns2:ClaimsRequest>
</soap:Body>
</soap:Envelope>
XSL Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:dp="http://www.datapower.com/extensions"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
extension-element-prefixes="dp" exclude-result-prefixes="dp"
version="1.0">
<xsl:template match="/">
<ClaimsRequest>
<xsl:for-each select="//externalIDGroup">
<xsl:variable name="acCode">
<xsl:value-of select="acctCd"/>
</xsl:variable>
<xsl:variable name="acCode1">
<xsl:choose>
<xsl:when test="string-length($acCode) > 10">
<xsl:value-of select="substring($acCode, 1,10)"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$acCode"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<claimDetail>
<accountCode><xsl:value-of select="acctCd1"/></accountCode>
</claimDetail>
</xsl:for-each>
</ClaimsRequest>
</xsl:template>
</xsl:stylesheet>
Desired output :
<ClaimsRequest>
<claimDetail>
<accountCode>plan1 Opti</accountCode>
</claimDetail>
<claimDetail>
<accountCode>Plan2</accountCode>
</claimDetail>
</ClaimsRequest>
I don't understand how you derived your desired output from the input. I suspect that all you need to do is :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<ClaimsRequest>
<xsl:for-each select="//externalIDGroup">
<claimDetail>
<accountCode>
<xsl:value-of select="substring(acctCd, 1, 10)"/>
</accountCode>
</claimDetail>
</xsl:for-each>
</ClaimsRequest>
</xsl:template>
</xsl:stylesheet>

Match node of called template

lets say I have a random template in my xsl:
<xsl:template name="keywords">
<test>
<foo>bar</foo>
<bar>foo</bar>
</test>
<test>
<foo>foobar</foo>
<bar>barfoo</bar>
</test>
<xsl:template>
I want to output let's say only the first node set. Is there an elegant way to do this?
How can I match the node if it is not in the source xml, but the result of a called template?
Thanks!
If you save the result of calling the template in a variable then you can extract parts of it using XPath
<xsl:variable name="result">
<xsl:call-template name="keywords"/>
</xsl:variable>
<xsl:sequence select="$keywords/test[1]" />
You can access the nodes inside a named template using an Xpath expression like:
document('')/xsl:stylesheet/xsl:template[#name='keywords']/test[1]
Added:
For example, the following stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<output>
<xsl:copy-of select="document('')/xsl:stylesheet/xsl:template[#name='keywords']/test[1]"/>
</output>
</xsl:template>
<xsl:template name="keywords">
<test>
<foo>bar</foo>
<bar>foo</bar>
</test>
<test>
<foo>foobar</foo>
<bar>barfoo</bar>
</test>
</xsl:template>
</xsl:stylesheet>
when applied to any well-formed XML input, will return:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<test xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<foo>bar</foo>
<bar>foo</bar>
</test>
</output>
Note: you can get rid of the (harmless) redundant namespace declaration by applying templates instead of deep-copying.

XSLT sort case sensitive

Please look at example:
xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="name()" case-order="upper-first"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
source xml:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<Z>Z</Z>
<a>a</a>
<A>A</A>
<B>B</B>
<k>k</k>
</Test>
Result is. This is case insensitive order. How to force upper-case letters be first?
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<A>A</A>
<a>a</a>
<B>B</B>
<k>k</k>
<Z>Z</Z>
</Test>
But, i need something like this:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<A>A</A>
<B>B</B>
<Z>Z</Z>
<a>a</a>
<k>k</k>
</Test>
What I'm doing wrong?
You may need to sort by the case in a separate sort instruction:
<xsl:apply-templates>
<xsl:sort select="translate(name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '00000000000000000000000000')" />
<xsl:sort select="name()"/>
</xsl:apply-templates>
This solution might need some testing though.