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>.
Is there a built-in or simple way to generate a sequence of values that will sort in alphabetical order?
As an example, the following item elements have a child sequence element intended for sorting on. The data is being generated for another system that requires this information, since it does not work with the document ordering.
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
...
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
Instead of sorting numerically, the system sorts alphabetically and it cannot change this behavior. As a result the items are sorted as 1,10,11,12,13,14,15,2,3,4,5,6,7,8,9
Furthermore, the actual number of items is unbounded, so there needs to be general way of generating an alphabetic sequence that will sort in the right order. That said, the number of items should be relatively small, under 1000.
This is what I have so far:
<xsl:variable name="idchars" as="xs:string *"
select="
('0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z')
"
/>
<xsl:variable name="idlen" as="xs:integer" select="count($idchars)" />
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer" />
<xsl:variable name="vLen" as="xs:integer" select="$pSeq idiv $idlen" />
<xsl:value-of>
<xsl:for-each select="1 to $vLen">
<xsl:sequence select="$idchars[$idlen]" />
</xsl:for-each>
<xsl:sequence select="$idchars[$pSeq mod $idlen]" />
</xsl:value-of>
</xsl:function>
You could use format-number() in order to generate a left-padded sequence of numbers with a variable length of leading zeros, then convert each of those digits into a letter using codepoints-to-string(), and then join them together with string-join():
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="local">
<xsl:output indent="yes"/>
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer" />
<xsl:param name="digits" as="xs:integer"/>
<xsl:variable name="picture" select="string-join( ((1 to $digits) ! '0'), '')" />
<xsl:variable name="padded-string" select="format-number($pSeq, $picture)" as="xs:string"/>
<xsl:variable name="padded-letter-seq" select="(1 to string-length($picture)) ! substring($padded-string, ., 1) ! codepoints-to-string(xs:integer(.) + 65)" as="xs:string*"/>
<xsl:sequence select="string-join($padded-letter-seq, '')"/>
</xsl:function>
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer"/>
<xsl:sequence select="f:genId($pSeq, 4)"/>
</xsl:function>
<xsl:template match="/">
<xsl:sequence select="f:genId(3)"/>
<xsl:sequence select="f:genId(3,5)"/>
</xsl:template>
</xsl:stylesheet>
Just use the <xsl:sort> xslt element, simply like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="*">
<xsl:sort select="sequence"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
...
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
</list>
the wanted, correct result is produced:
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
</list>
Do note:
This:
<xsl:sort select="sequence"/>
is equivalent to:
<xsl:sort select="sequence" data-type="text"/>
as "text" is the default value for the data-type attribute.
If it is necessary to sort numerically, one should explicitly use:
<xsl:sort select="sequence" data-type="number"/>
I have a XML that has a date formatted as (2019-Aug-19) (YYYY-MMM-DD).
I want this to be transformed to (2019-08-19) (YYYY-MM-DD)
Hear is a sample what i have done. But no luck
<xsl:value-of select="format-dateTime(xs:dateTime(concat(date, T00:00:00Z')), '[D01]-[M01]-[Y0001]')"/>
Here you go may be another simple method:
XML:
<time>2019-Aug-19T00:00:00Z</time>
XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="text" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:variable name="dt" select="time"/>
<xsl:template match="/">
<xsl:value-of select="format-dateTime(xs:dateTime(replace($dt, substring-before(substring-after($dt,'-'),'-') ,format-number(index-of(('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov', 'dec'),lower-case(substring-before(substring-after(lower-case($dt),'-'), '-'))),'00'))),'[D01]-[M01]-[Y0001]')"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
Result:
19-08-2019
For YYYY-MM-DD you can do:
<xsl:value-of select="format-dateTime(xs:dateTime(replace($dt, substring-before(substring-after($dt,'-'),'-') ,format-number(index-of(('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov', 'dec'),lower-case(substring-before(substring-after(lower-case($dt),'-'), '-'))),'00'))),'[Y0001]-[M01]-[D01]')"/>
Result:
2019-08-19
See Link: http://xsltransform.net/nbiCsZm
I am trying to change XML node name but it doesn't allow me to do so. In my below code I I have two templates 1. Change Node name 2.Create parent node for DocumentReference. Please see my XML and XSLT.
My XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataArea>
<PurchaseOrder>
<PurchaseOrderLine>
<DocumentReference>
<DocumentID>
<ID>23423</ID>
</DocumentID>
</DocumentReference>
<DocumentReference>
<DocumentID>
<ID>23424</ID>
</DocumentID>
</DocumentReference>
<Item>
<CustomerItemID>
<!-- ArtNr -->
<ID>444</ID>
</CustomerItemID>
</Item>
<Quantity unitCode="PCE">17.3</Quantity>
</PurchaseOrderLine>
</PurchaseOrder>
</DataArea>
Expected Result
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataArea>
<PurchaseOrder>
<POL>
<DocumentReference>
<DocumentID>
<ID>23423</ID>
</DocumentID>
</DocumentReference>
<DocumentReference>
<DocumentID>
<ID>23424</ID>
</DocumentID>
</DocumentReference>
<Item>
<CustomerItemID>
<!-- ArtNr -->
<ID>444</ID>
</CustomerItemID>
</Item>
<Quantity unitCode="PCE">17.3</Quantity>
</POL>
</PurchaseOrder>
</DataArea>
My XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PurchaseOrderLine">
<POL>
<xsl:apply-templates />
</POL>
</xsl:template>
<xsl:template match="PurchaseOrderLine">
<xsl:copy>
<Kiran>
<xsl:apply-templates select="#*|DocumentReference"/>
</Kiran>
<xsl:apply-templates select="#*|Item|Quantity"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Then I think you want the template to look like
<xsl:template match="PurchaseOrderLine">
<POL>
<xsl:apply-templates select="#*"/>
<Kiran>
<xsl:apply-templates select="DocumentReference"/>
</Kiran>
<xsl:apply-templates select="node() except DocumentReference" />
</POL>
</xsl:template>
Is it possible to make xsl:analyze-string return one string instead of a sequence of strings?
Background: I'd like to use xsl:analyze-string in a xsl:function that should encapsulate the pattern matching. Ideally, the function should return an xs:string to be used as sort criteria in an xsl:sort element.
At the moment, i have to apply string-join() on every result of the function call since xsl:analyze-string returns a sequence of strings, and xsl:sort doesn't accept such a sequence as sort criteria. See line 24 of the stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="www.my-personal-namespa.ce"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes" method="xml" />
<xsl:function name="my:sortierung" >
<xsl:param name="inputstring" as="xs:string"/>
<xsl:analyze-string select="$inputstring" regex="[0-9]+">
<xsl:matching-substring>
<xsl:value-of select="format-number(number(.), '00000')" />
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="/input">
<result>
<xsl:apply-templates select="value" >
<xsl:sort select="string-join((my:sortierung(.)), ' ')" />
</xsl:apply-templates>
</result>
</xsl:template>
<xsl:template match="value">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
with this input:
<?xml version="1.0" encoding="UTF-8"?>
<input>
<value>A 1 b 120</value>
<value>A 1 b 1</value>
<value>A 1 b 2</value>
<value>A 1 b 1a</value>
</input>
In my example, is there a way to modify the xsl:function to return a xs:string instead of a sequence?
There are several ways I think, you could put the result of the analyze-string into a variable inside of the function and then use xs:sequence select="string-join($var, ' ')" in the function.
However the following with xsl:value-of should also do:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="www.my-personal-namespa.ce"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="my xs">
<xsl:output indent="yes" method="xml" />
<xsl:function name="my:sortierung" as="xs:string">
<xsl:param name="inputstring" as="xs:string"/>
<xsl:value-of separator=" ">
<xsl:analyze-string select="$inputstring" regex="[0-9]+">
<xsl:matching-substring>
<xsl:value-of select="format-number(number(.), '00000')" />
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:value-of>
</xsl:function>
<xsl:template match="/input">
<result>
<xsl:apply-templates select="value" >
<xsl:sort select="my:sortierung(.)" />
</xsl:apply-templates>
</result>
</xsl:template>
<xsl:template match="value">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>