XSLT - make xsl:analyze-string return string instead of sequence of strings? - xslt-2.0

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>

Related

Sum two elements by grouping using XSLT 2.0

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>.

Convert Date from DD-MMM-YYYY to YYYY-MMM-DD using xslt 2.0

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

Dynamically replacing substring in an given data(XML to fixed length)

Actually I have started with my XSLT work recently, I am facing difficulty in solving one of the requirement.
I am trying to fetch an substring from DATA element in the mentioned input i,e is ECHO and OKAY these codes need to be replaced with the values present under CODE/ECHO and CODE/OKAY in the same input. I had tried storing the substring in a variable and as the variable value and tag value would be same, I have tried to fetch that in . But its not working.
Is it that we cant use variables in the XPATHS or there is some other representation which needs to be used? Could anyone please help me with this.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:variable name="String" select="substring(DATA, (string-length(substring(DATA,0,77)) + 1), 4)" />
<xsl:variable name="String1" >
<xsl:value-of select="Root/CODES/$String" />
</xsl:variable>
<xsl:value-of select="$break" />
<xsl:value-of select="$String1" />
</xsl:for-each>
</xsl:template>
Input:
<?xml version='1.0' encoding='utf-8'?>
<ROOT>
<INPUT>
<I_FILENAME>ERES</I_FILENAME>
</INPUT>
<CODES>
<ECHO>A1</ECHO>
<OKAY>A2</OKAY>
</CODES>
<TABLES>
<T_ER>
<item>
<DATA> HEADERERESRGCITIS220190301124112000000RGERSD46</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000ECHO00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000OKAY00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>TRAILERERESRGCITIS220190301124112000000001570000</DATA>
</item>
</T_ER>
</TABLES>
</ROOT>
EXPECTED OUT PUT:
HEADERERESRGCITIS220190301124112000000RGERSD46
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A100400300000000000XXXXXX 000{ P 2018-11-05
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A200400300000000000XXXXXX 000{ P 2018-11-05
<xsl:template match="INPUT|CODES">
</xsl:template>
<xsl:template match="TABLES">
<xsl:variable name="break" select="'
'" />
<xsl:for-each select="T_ER/item"><xsl:value-of select="$break"></xsl:value-of>
<xsl:value-of select="DATA"/>
</xsl:for-each>
</xsl:template>

XSLT 2.0: using a regular expression to extract all matching parts to an array

How can I collect all parts of a string matching a regular expression pattern into an array?
<xsl:variable name="matches" select="function('abc_Xza_Y_Sswq', '_[A-Z]')"/>
returning
('_X', '_Y', '_S')
There are no arrays in XSLT/XPath 2.0, you could however write a function that uses analyze-string to return a sequence of strings:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:function name="mf:extract" as="xs:string*">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="pattern" as="xs:string"/>
<xsl:analyze-string select="$input" regex="{$pattern}">
<xsl:matching-substring>
<xsl:sequence select="."/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="/">
<xsl:variable name="matches" select="mf:extract('abc_Xza_Y_Sswq', '_[A-Z]')"/>
<xsl:value-of select="$matches" separator=", "/>
</xsl:template>
</xsl:transform>

XSLT- Pre Define NameSpace

Hi I have an XML that upon each delivery has a different unique named Namespace that I cannot pre determine with standard processes.
<ABC xmlns:this="urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>111</TaggedValue>
</this:ABDList>
<this:SubBegin>0</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>222</TaggedValue>
</this:ABDList>
<this:SubBegin>1</this:SubBegin>
</MatPackage>
<!-- Stuff -->
</Extention>
</Extentions>
</ABC>
The Next XML delivered could be
<ABC xmlns:this="urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>333</TaggedValue>
</this:ABDList>
<this:SubBegin>0</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>444</TaggedValue>
</this:ABDList>
<this:SubBegin>1</this:SubBegin>
</MatPackage>
<!-- Stuff -->
</Extention>
</Extentions>
</ABC>
My current XSL stylesheet works on the first XML predefining the Namespace
But I am looking to find a way to re-define it later on. on the process. I have added a variable to pull the relevant uuid from the Identification element but am not sure how to integrate this. Using the below stylesheet to process any other XML results in false results.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:this="urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:variable name="SelOpGroup" select="/ABC/Extensions/Identification"/>
<!-- Pass thru --->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/ABC/Extensions/SrcPackage>
<xsl:copy>
<this:ABDList>
<xsl:copy-of select ="this:ABDList/*"/>
<TaggedA>888</TaggedA>
</this:ABDList>
<this:SubBegin><xsl:value-of select="somethingelse"/> </this:SubBegin>
</xsl:copy>
</xsl:template>
<xsl:template match="/ABC/Extensions/MatPackage>
<xsl:copy>
<this:ABDList>
<xsl:copy-of select ="this:ABDList/*"/>
<TaggedB>999</TaggedB>
</this:ABDList >
<this:SubBegin><xsl:value-of select="somethingelse"/> </this:SubBegin>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Expected Result
<ABC xmlns:this="urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>333</TaggedValue>
<TaggedA>888</TaggedA>
</this:ABDList>
<this:SubBegin>a value</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>444</TaggedValue>
<TaggedB>999</TaggedA>
</this:ABDList>
<this:SubBegin>a value</this:SubBegin>
</MatPackage>
<!-- Stuff -->
</Extention>
</Extentions>
</ABC>
Many thanks,
Adrian
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:variable name="SelOpGroup" select="/ABC/Extensions/Identification"/>
<!-- Pass thru -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Extension/SrcPackage">
<xsl:copy>
<xsl:element name="{'this:ABDList'}" namespace="{$SelOpGroup}">
<xsl:copy-of select="/*/namespace::*[name()='this']"/>
<xsl:copy-of select ="*[name() = 'this:ABDList']/*"/>
<TaggedA>888</TaggedA>
</xsl:element>
<xsl:element name="{'this:SubBegin'}" namespace="{$SelOpGroup}">
<xsl:copy-of select="/*/namespace::*[name()='this']"/>
<xsl:value-of select="'somethingelse'"/>
</xsl:element>
</xsl:copy>
</xsl:template>
<xsl:template match="Extension/MatPackage">
<xsl:copy>
<xsl:element name="{'this:ABDList'}" namespace="{$SelOpGroup}">
<xsl:copy-of select="/*/namespace::*[name()='this']"/>
<xsl:copy-of select ="*[name() = 'this:ABDList']/*"/>
<TaggedB>999</TaggedB>
</xsl:element>
<xsl:element name="{'this:SubBegin'}" namespace="{$SelOpGroup}">
<xsl:copy-of select="/*/namespace::*[name()='this']"/>
<xsl:value-of select="'somethingelse'"/>
</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied on the first provided XML document:
<ABC xmlns:this="urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>111</TaggedValue>
</this:ABDList>
<this:SubBegin>0</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>222</TaggedValue>
</this:ABDList>
<this:SubBegin>1</this:SubBegin>
</MatPackage>
<!-- Stuff -->
</Extension>
</Extensions>
</ABC>
Produces the wanted, correct result:
<ABC xmlns:this="urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1f15a9-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>111</TaggedValue>
<TaggedA>888</TaggedA>
</this:ABDList>
<this:SubBegin>somethingelse</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>222</TaggedValue>
<TaggedB>999</TaggedB>
</this:ABDList>
<this:SubBegin>somethingelse</this:SubBegin>
</MatPackage><!-- Stuff -->
</Extension>
</Extensions>
</ABC>
When the same transformation is applied on the second provided XML document:
<ABC xmlns:this="urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>333</TaggedValue>
</this:ABDList>
<this:SubBegin>0</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>444</TaggedValue>
</this:ABDList>
<this:SubBegin>1</this:SubBegin>
</MatPackage>
<!-- Stuff -->
</Extension>
</Extensions>
</ABC>
Again the wanted, correct result is produced:
<ABC xmlns:this="urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331" version="1.1">
<Extensions>
<Identification>urn:uuid:9b1FFae4-69de-11d2-b6bc-fcab70ff7331</Identification>
<Extension>
<SrcPackage>
<this:ABDList>
<TaggedValue>333</TaggedValue>
<TaggedA>888</TaggedA>
</this:ABDList>
<this:SubBegin>somethingelse</this:SubBegin>
</SrcPackage>
<MatPackage>
<this:ABDList>
<TaggedValue>444</TaggedValue>
<TaggedB>999</TaggedB>
</this:ABDList>
<this:SubBegin>somethingelse</this:SubBegin>
</MatPackage><!-- Stuff -->
</Extension>
</Extensions>
</ABC>
This is bizarre input (what were they smoking?). But since the namespace is only used on one element, ABDList, my approach would be to select the ABDList elements using *:ABDList in XSLT 2.0, or *[local-name()='ABDList'] in XSLT 1.0.
I have an XML that upon each delivery has a different unique named
Namespace
Someone ahead of you obviously does not understand the purpose of having a namespace.
Perhaps this could work for your unfortunate situation:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="SrcPackage/*/TaggedValue">
<xsl:copy-of select="."/>
<TaggedA>888</TaggedA>
</xsl:template>
<xsl:template match="MatPackage/*/TaggedValue">
<xsl:copy-of select="."/>
<TaggedB>999</TaggedB>
</xsl:template>
</xsl:stylesheet>

Resources