Answering another XSLT question on this site, I stumbled on a difference between XSLT 1.0 and 2.0 that I don't understand. Who can explain what is happening here, and how the difference may be resolved?
Note: I am using XML Spy version 2011 sp1 (x64).
My input XML is
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Manager grade="10" id="26">
<Employee id="1" grade="9"/>
<Employee id="2" grade="8"/>
</Manager>
<Manager grade="10" id="27">
<Employee id="3" grade="9"/>
<Employee id="4" grade="8"/>
<Employee id="5" grade="4"/>
</Manager>
<Manager grade="7" id="28">
<Employee id="6" grade="8"/>
<Employee id="7" grade="7"/>
<Employee id="8" grade="6"/>
<Employee id="9" grade="9"/>
</Manager>
<Manager grade="9" id="29">
<Employee id="10" grade="9"/>
<Employee id="11" grade="8"/>
<Employee id="12" grade="7"/>
</Manager>
</root>
I wish to select the set of Employees that have a grade larger than or equal to the managers grade. For this I wrote the following 1.0 transformation:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="root/Manager"/>
</root>
</xsl:template>
<xsl:template match="Manager">
<mgr>
<managerId><xsl:value-of select="#id"/></managerId>
<managerGrade><xsl:value-of select="#grade"/></managerGrade>
<empsSelection>
<xsl:copy-of select="Employee[#grade >= ../#grade]"/>
</empsSelection>
</mgr>
</xsl:template>
</xsl:stylesheet>
The output is the expected
<?xml version="1.0" encoding="UTF-8"?>
<root>
<mgr>
<managerId>26</managerId>
<managerGrade>10</managerGrade>
<empsSelection/>
</mgr>
<mgr>
<managerId>27</managerId>
<managerGrade>10</managerGrade>
<empsSelection/>
</mgr>
<mgr>
<managerId>28</managerId>
<managerGrade>7</managerGrade>
<empsSelection>
<Employee id="6" grade="8"/>
<Employee id="7" grade="7"/>
<Employee id="9" grade="9"/>
</empsSelection>
</mgr>
<mgr>
<managerId>29</managerId>
<managerGrade>9</managerGrade>
<empsSelection>
<Employee id="10" grade="9"/>
</empsSelection>
</mgr>
</root>
But when I change the XSLT version to 2.0 (take above stylesheet and change stylesheet/#version to 2.0), I get the below different and unexpected result:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<mgr>
<managerId>26</managerId>
<managerGrade>10</managerGrade>
<empsSelection>
<Employee id="1" grade="9"/>
<Employee id="2" grade="8"/>
</empsSelection>
</mgr>
<mgr>
<managerId>27</managerId>
<managerGrade>10</managerGrade>
<empsSelection>
<Employee id="3" grade="9"/>
<Employee id="4" grade="8"/>
<Employee id="5" grade="4"/>
</empsSelection>
</mgr>
<mgr>
<managerId>28</managerId>
<managerGrade>7</managerGrade>
<empsSelection>
<Employee id="6" grade="8"/>
<Employee id="7" grade="7"/>
<Employee id="9" grade="9"/>
</empsSelection>
</mgr>
<mgr>
<managerId>29</managerId>
<managerGrade>9</managerGrade>
<empsSelection>
<Employee id="10" grade="9"/>
</empsSelection>
</mgr>
</root>
Why is this and how should the stylesheet be changed in order to get the correct result in both XSLT 1.0 and 2.0 version?
I think with XSLT 2.0 you by default get comparison as strings while with XSLT 1.0 the comparison operator converts any operands to numbers first which are then compared so with XSLT 2.0 you need
<xsl:template match="Manager">
<mgr>
<managerId><xsl:value-of select="#id"/></managerId>
<managerGrade><xsl:value-of select="#grade"/></managerGrade>
<empsSelection>
<xsl:copy-of select="Employee[number(#grade) >= number(current()/#grade)]"/>
</empsSelection>
</mgr>
</xsl:template>
to get the result you want. Of course using other number types like xs:integer(#grade) should do as well.
Related
I wish to test for xsi:nil="true".
I'm using the replies from kjhughes & Michael Kay in these posts, respectively.
How to implement if-else statement in XSLT?
How do I check if XML value is nil in XSLT
XML:
<OSM>
<EstablishmentDetail>
<RatingValue>5</RatingValue>
<RatingDate>2008-05-15</RatingDate>
</EstablishmentDetail>
<EstablishmentDetail>
<RatingValue>AwaitingInspection</RatingValue>
<RatingDate xsi:nil="true"/>
</EstablishmentDetail>
</OSM>
A snip of the XSL:
<xsl:template>
"Value": "<xsl:value-of select="if (nilled(RatingDate)) then RatingValue else 'XX' "/>",
</xsl:template>
It's producing output but both are 'XX'. Is it just a syntax error?
The function https://www.w3.org/TR/xpath-functions/#func-nilled is meant to work with schema-aware XSLT and validated input i.e. if you use Saxon EE you can expect it to do its job:
In practice, the function returns true only for an element node that
has the attribute xsi:nil="true" and that is successfully validated
against a schema that defines the element to be nillable;
Need to add the namespace in the input document:
<OSM xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EstablishmentDetail>
<RatingValue>5</RatingValue>
<RatingDate>2008-05-15</RatingDate>
</EstablishmentDetail>
<EstablishmentDetail>
<RatingValue>AwaitingInspection</RatingValue>
<RatingDate xsi:nil="true"/>
</EstablishmentDetail>
</OSM>
I'm not getting the right value out of the standard fn:nilled() function hence substituted a user function:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:l="local:functions">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<RatingValues>
<xsl:for-each select="//EstablishmentDetail">
<EstablishmentDetail index="{position()}" >
<RatingValue>
<xsl:value-of select="('XX'[l:nilled(current()/RatingDate) ],
current()/RatingValue)[1]" />
</RatingValue>
<Nilled>
<xsl:value-of select="nilled(RatingDate)" />
</Nilled>
</EstablishmentDetail>
</xsl:for-each>
</RatingValues>
</xsl:template>
<xsl:function name="l:nilled" as="xs:boolean" >
<xsl:param name="e" as="element()" />
<xsl:sequence select="exists($e/#xsi:nil) and $e/#xsi:nil eq 'true'" />
</xsl:function>
</xsl:stylesheet>
which produces:
<?xml version="1.0" encoding="UTF-8"?>
<RatingValues xmlns:l="local:functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EstablishmentDetail index="1">
<RatingValue>5</RatingValue>
<Nilled>false</Nilled>
</EstablishmentDetail>
<EstablishmentDetail index="2">
<RatingValue>XX</RatingValue>
<Nilled>false</Nilled>
</EstablishmentDetail>
</RatingValues>
The Nilled elements in the output just to show that I'm getting false for both cases.
I'm trying to sum two elements "amount" and "retroAmount" group by "tmid" using xslt 2.0 and I tried two methods, in method-1 everything is stacking up and in the method-2 it displays NaN. Any ideas about how this can be fixed?
Here is my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<row>
<tmid>abc</tmid>
<amount>651.03</amount>
<retroAmount>0</retroAmount>
</row>
<row>
<tmid>abc</tmid>
<amount>250.75</amount>
<retroAmount>-10</retroAmount>
</row>
<row>
<tmid>abc</tmid>
<amount>132</amount>
<retroAmount>-16.1</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>129.19</amount>
<retroAmount>49.96</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>148.76</amount>
<retroAmount>0</retroAmount>
</row>
<row>
<tmid>xyz</tmid>
<amount>92.29</amount>
<retroAmount>12</retroAmount>
</row>
</Request>
Output I am expecting:
<top xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Results>
<tmId>abc</tmId>
<total>1007.68</total>
</Results>
<Results>
<tmId>xyz</tmId>
<total>432.2</total>
</Results>
</top>
Any help is appreciated.
The XSLT code I was playing with:
Method-1 (everything is stacking up or being displayed without summing)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="Request/row"
group-by="tmid">
<row>
<tmid>
<xsl:value-of
select="current-grouping-key()"
/>
</tmid>
<xsl:for-each-group select="current-group()" group-by=".">
<amount>
<xsl:value-of select="sum(number(current-group()/amount))"/>
</amount>
<retroamount>
<xsl:value-of select="sum(number(current-group()/retroAmount))"/>
</retroamount>
</xsl:for-each-group>
</row>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
Method-2 (I was only using "amount" and still it is displaying NaN, I would like to sum up both "amount" and "retroAmount"
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/*">
<top>
<xsl:for-each-group select="//tmid" group-by=".">
<Results>
<tmId>
<xsl:sequence
select="current-grouping-key()"
/>
</tmId>
<total>
<xsl:sequence select="sum(number(current-group()/amount))"/>
</total>
</Results>
</xsl:for-each-group>
</top>
</xsl:template>
</xsl:stylesheet>
You basically want
<xsl:template match="Request">
<xsl:copy>
<xsl:for-each-group select="row" group-by="tmid">
<Results>
<tmId>{current-grouping-key()}</tmId>
<total>{sum(current-group()!(amount, retroAmount))}</total>
</Results>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
(that is XSLT 3 with XPath 3.1 syntax, but in XSLT 2 with XPath 2 syntax you would use
<xsl:template match="Request">
<xsl:copy>
<xsl:for-each-group select="row" group-by="tmid">
<Results>
<tmId>
<xsl:value-of select="current-grouping-key()"/>
</tmId>
<total>
<xsl:value-of select="sum(current-group()/(amount, retroAmount))"/>
</total>
</Results>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
I only later noticed that the Request element is meant to be transformed to a top element so change the <xsl:template match="Request"><xsl:copy>...</xsl:copy></xsl:template> from above suggestions to <xsl:template match="Request"><step>...</step></xsl:template>.
I have an XML lie below:
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>45768799322</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
I want to get only first value of the Product1 reference...but I am unable to get that.Also it is not mandatory that Product 1 will always be the first element in input xml.
Any suggestions how can I get that?
I have tried to get the value as :
<xsl:template match="//Products">
<xsl:variable name="Product1">
<xsl:for-each-group select="/Reference" group-by="/Reference">
<xsl:copy-of select="." />
</xsl:for-each-group>
</xsl:variable>
</xsl:template>
Update:1
<?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" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
My expected output is :000510143244
To get the first occurrence of <Products> who has <Product1>, you might need to match the parent tag or root tag of your input XML.
Assuming your input as below:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<root>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
<Products>
<Product1>
<Reference>02563899183</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
</root>
The following code can give you the result:
<?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" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]" />
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
See the demo: https://xsltfiddle.liberty-development.net/3NJ38Zx
Update:
OR you can simply achieve it by following 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="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Update 2:
<xsl:template match="root">
<xsl:variable name="ref">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]/Reference" />
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="$ref"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/3NJ38Zx/1
Update 3:
You cannot assign a value to global variable from a template.
There are two ways to get what you required.
1) Create a global variable as below which will take first <Products> whose child element is <Product1> and will display it's Reference
<?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" encoding="UTF-8" indent="yes" />
<xsl:variable name="ref" select="root/Products[child::Product1][1]/Product1/Reference" />
<xsl:template match="/">
<xsl:value-of select="$ref" />
</xsl:template>
</xsl:stylesheet>
2) You can modify the template as below to get the result.
<?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" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]/Product1/Reference">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
I am working on XML transformation using XSLT and facing issue while renaming tag. Please find below detail for the same. My transformed XML should have BookName instead of Name and LibraryName instead of Name tag.
Input XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog xmlns="http://example.com">
<Books>
<Book>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book>
<Book>
<Name>Rich Dad Poor Dad</Name>
<author>Orange</author>
</Book>
</Books>
<libraries>
<library>
<Name> Forsyth </Name>
<city> Cumming </city>
</library>
<library>
<Name> COBB </Name>
<city> Marietta </city>
</library>
</libraries>
</Catalog>
Expected XML After Transformation
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog xmlns="http://example.com">
<Books>
<Book>
<BookName>Wise Otherwise</BookName>
<author>Great Expectations</author>
</Book>
<Book>
<Name>Rich Dad Poor Dad</Name>
<author>Orange</author>
</Book>
</Books>
<libraries>
<library>
<LibraryName> Forsyth </LibraryName>
<city> Cumming </city>
</library>
<library>
<LibraryName> COBB </LibraryName>
<city> Marietta </city>
</library>
</libraries>
</Catalog>
My XSLT for the same
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://example.com">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:Name">
<xsl:for-each select="Catalog/Books/Book/Name">
<BookName>
<xsl:apply-templates />
</BookName>
</xsl:for-each>
<xsl:for-each select="Catalog/libraries/library/Name">
<LibraryName>
<xsl:apply-templates />
</LibraryName>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You can use this XSLT for reference:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://example.com" xmlns="http://example.com" exclude-result-prefixes="ns">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:Book/ns:Name">
<BookName>
<xsl:apply-templates/>
</BookName>
</xsl:template>
<xsl:template match="ns:library/ns:Name">
<LibraryName>
<xsl:apply-templates/>
</LibraryName>
</xsl:template>
</xsl:stylesheet>
I declared the namespace with and without prefix. Therefore all new created elements will belong to the default namespace. Also excluded the prefixed one since it is not used.
You can write several templates for matching the nodes you want to change. For example read this tutorial: http://www.xmlplease.com/xsltidentity
I understand this is one such common question posted many times, but unfortunately I am unable to find an exact solution with any of the proposed approaches:
Here's how my XML looks like:
<root>
<parent>
<table>
<attr ......>
<type t="enumone">
<info>
<name .....>
</info>
</attr>
<attr>
<type t="int">
<range min="1" max="255"/>
</type>
<info>
<name .....>
</info>
</attr>
<attr>
<type t="string">
<info>
<name .....>
</info>
</attr>
<attr ......>
<type t="enumtwo">
<info>
<name .....>
</info>
</attr>
<attr>
<type t="float">
<range min="1.0" max="25.5"/>
</type>
<info>
<name .....>
</info>
</attr>
<attr>
<type t="int">
<info>
<name .....>
</info>
</attr>
<attr>
<type t="enumone">
<info>
<name .....>
</info>
</attr>
<attr>
<type t="enumthree">
<info>
<name .....>
</info>
</attr>
<attr>
<type t="enumone">
<info>
<name .....>
</info>
</attr>
</parent>
</root>
The intention is to retrieve one occurrence of attribute "#t" from "type" element by using XSLT:
Using for-each-group:
<xsl-template match="/root/parent">
<xsl:for-each select="table">
<xsl:for-each-group select="//type" group-by="#t">
<xsl:copy-of select="current-group( )[1]"/>
</xsl:for-each-group>
</xsl:for-each>
<xsl-template>
But I got no output! Something flawed I believe.
Using distinct-values():
<xsl:for-each select="distinct-values(type/#t)">
<xsl:sort/>
<xsl:value-of select="."/> <xsl:call-template name="newline"/>
</xsl:for-each>
Still no desired output.
The expected output is:
enumone
int
string
enumtwo
float
enumthree
Appreciate any help in this regard.
Well the input sample is not well-formed XML but if you really only want the distinct t attribute values of all type elements then doing
<xsl:template match="/">
<xsl:value-of select="distinct-values(//type/#t)" separator="
"/>
</xsl:template>
suffices with XSLT 2.0.
If you additionally want to sort the distinct values then doing
<xsl:template match="/">
<xsl:value-of separator="
">
<xsl:perform-sort select="distinct-values(//type/#t)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:value-of>
</xsl:template>
To give a complete example, with the input being
<root>
<foo>
<type t="int"/>
</foo>
<bar>
<type t="enum"/>
</bar>
<foobar>
<foo>
<type t="enum">
<x/>
</type>
</foo>
</foobar>
<foo>
<type t="string"/>
</foo>
</root>
the stylesheet
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of separator="
">
<xsl:perform-sort select="distinct-values(//type/#t)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:value-of>
</xsl:template>
</xsl:stylesheet>
outputs
enum
int
string
Just modify your code to this:
<xsl:template match="/">
<xsl:for-each-group select="//type" group-by="#t">
<xsl:copy-of select="current-group( )[1]"/>
</xsl:for-each-group>
</xsl:template>
The complete transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each-group select="//type" group-by="#t">
<xsl:copy-of select="current-group( )[1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When applied on a well-formed XML document:
<root>
<foo>
<type t="int"/>
</foo>
<bar>
<type t="enum"/>
</bar>
<foobar>
<foo>
<type t="enum">
<x/>
</type>
</foo>
</foobar>
<foo>
<type t="string"/>
</foo>
</root>
the correct result is produced:
<type t="int"/>
<type t="enum"/>
<type t="string"/>