Using correct regex in xslt 2.0 - xslt

I've been stuck using the correct regex. I need to tokenize each data per keys along with there values. In my example below,
Sample File:
<Rec>
<Data>/CHG1/EUR1000,00/EXCH/0,10/CPRP/Payment Code 1</Data>
<Data>/CHG1/EUR1000,00/EXCH/0,10/CPRP/Payment Code 1</Data>
<Data>/CHG3/EUR3000,00/PURP//CD/Payment Code 3</Data>
<Data>/CHG5/EUR5000,00/PURP//PRTRY/Payment Code 5</Data>
<Data>/ORIG//CSID/EUR7000,00/BENM//ID/Payment Code 7</Data>
</Rec>
The keys with '//' in the middle is considered as 1 key. I need to generate the output like this:
<Data>
<Group>
<Token>/CHG1/EUR1000,00</Token>
<Token>/EXCH/0,10</Token>
<Token>/CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>/CHG3/EUR3000,00</Token>
<Token>/PURP//CD/Payment Code 3</Token>
</Group>
<Group>
<Token>/CHG5/EUR5000,00</Token>
<Token>/PURP//PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token>/ORIG//CSID/EUR7000,00</Token>
<Token>/BENM//ID/Payment Code 7</Token>
</Group>
</Data>
But, my generated output is like this:
<Data>
<Group>
<Token>/CHG1/</Token>
<Token>/EXCH/</Token>
<Token>/CPRP/</Token>
</Group>
<Group>
<Token>/CHG3/</Token>
<Token>/PURP//CD/</Token>
</Group>
<Group>
<Token>/CHG5/</Token>
<Token>/PURP//PRTRY/</Token>
</Group>
<Group>
<Token>/ORIG//CSID/</Token>
<Token>/BENM//ID/</Token>
</Group>
</Data>
Here is my XSLT that I've been using:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[boolean(normalize-space())]|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:for-each select=".">
<Group>
<xsl:analyze-string select="." regex="\/[A-Z]+[0-9]?\/(\/(CD|PRTRY|MARF|ID|CSID|NAME|RID)\/)?">
<xsl:matching-substring>
<xsl:variable name="val" select="."/>
<Token>
<xsl:value-of select="$val"/>
</Token>
</xsl:matching-substring>
</xsl:analyze-string>
</Group>
</xsl:for-each>
</xsl:template>
There is something missing in my regex expression. Can anyone help me figure out? Thank you for your feedback.

I can't make heads or tails of your expected output. I suspect the correct output should be actually something like:
<?xml version="1.0" encoding="UTF-8"?>
<Rec>
<Group>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token>CHG3/EUR3000,00</Token>
<Token>PURP/</Token>
<Token>CD/Payment Code 3</Token>
</Group>
<Group>
<Token>CHG5/EUR5000,00</Token>
<Token>PURP/</Token>
<Token>PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token>ORIG/</Token>
<Token>CSID/EUR7000,00</Token>
<Token>BENM/</Token>
<Token>ID/Payment Code 7</Token>
</Group>
</Rec>
If so, I would suggest you change your approach and try:
XSLT 2.0
<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"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<xsl:variable name="t" select="tokenize(., '/')" />
<Group>
<xsl:for-each select="$t[position() mod 2 = 0]">
<xsl:variable name="i" select="index-of($t, .)"/>
<Token>
<xsl:value-of select="." />
<xsl:text>/</xsl:text>
<xsl:value-of select="$t[$i + 1]" />
</Token>
</xsl:for-each>
</Group>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/93dEHGr
Or even simpler:
XSLT 2.0
<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"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data">
<Group>
<xsl:for-each-group select="tokenize(., '/')" group-by="position() idiv 2">
<Token>
<xsl:value-of select="current-group()" separator="/"/>
</Token>
</xsl:for-each-group>
</Group>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/93dEHGr/2
Added:
Actually, the keys with 2 '//' in the middle is considered as 1 key.
Well, then let's take the // strings out of the game before doing the tokenizing, and restore them at the end:
<xsl:template match="Data">
<Group>
<xsl:for-each-group select="tokenize(replace(., '//', '§§§'), '/')" group-by="position() idiv 2">
<Token>
<xsl:value-of select="replace(string-join(current-group(), '/'), '§§§', '//')" />
</Token>
</xsl:for-each-group>
</Group>
</xsl:template>
Result
<?xml version="1.0" encoding="UTF-8"?>
<Rec>
<Group>
<Token/>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token/>
<Token>CHG1/EUR1000,00</Token>
<Token>EXCH/0,10</Token>
<Token>CPRP/Payment Code 1</Token>
</Group>
<Group>
<Token/>
<Token>CHG3/EUR3000,00</Token>
<Token>PURP//CD/Payment Code 3</Token>
</Group>
<Group>
<Token/>
<Token>CHG5/EUR5000,00</Token>
<Token>PURP//PRTRY/Payment Code 5</Token>
</Group>
<Group>
<Token/>
<Token>ORIG//CSID/EUR7000,00</Token>
<Token>BENM//ID/Payment Code 7</Token>
</Group>
</Rec>
http://xsltransform.net/93dEHGr/4

You can try this with tokenize in xslt 2.0
<xsl:template match="Rec">
<Data>
<xsl:apply-templates/>
</Data>
</xsl:template>
<xsl:template match="Data">
<Group>
<xsl:for-each select="tokenize(., '0/')">
<Token>
<xsl:if test="position() ne 1">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
<xsl:if test="position() ne last()">
<xsl:text>0</xsl:text>
</xsl:if>
</Token>
</xsl:for-each>
</Group>
</xsl:template>

Related

How to partition dates with XSLT

I have a group of dates and I'd like to create partitions with a criterion such as "exactly 7 days apart" For example this is my source xml:
<root>
<entry date="2019-05-12" />
<entry date="2019-05-19" />
<entry date="2019-05-26" />
<entry date="2019-06-16" />
<entry date="2019-06-23" />
</root>
The result should be like this:
<root>
<group>
<val>12.5.</val>
<val>19.5.</val>
<val>26.5.</val>
</group>
<group>
<val>16.6.</val>
<val>23.6.</val>
</group>
</root>
since the first three and the last two dates are all on a Sunday without a gap.
What I have so far is this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sd="urn:someprefix"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
>
<xsl:output indent="yes"/>
<xsl:template match="root">
<root>
<xsl:copy-of select="sd:partition(distinct-values(for $i in entry/#date return $i cast as xs:date))"/>
</root>
</xsl:template>
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:for-each-group select="$dates" group-adjacent="format-date(., '[F]')">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
Which only generates one group.
How can I ask for the previous element to be 7 days apart? I know of duration (xs:dayTimeDuration('P1D')), but I don't know how to compare it to a previous value.
I use Saxon 9.8 HE.
I think you can also do it using group-adjacent:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="entry/#date/xs:date(.)"
group-adjacent=". - (position() - 1) * xs:dayTimeDuration('P7D')">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match=".[. instance of xs:date]">
<val>{format-date(.,'[D].[M].')}</val>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ncdD7mM
To do your grouping, you really need to know the difference in days with the previous element, then you can group starting with dates where the difference is not 7 days. So, you can declare a variable where you build up some new XML with the dates and differences, and then use that to group.
Try this function in your XSLT instead.
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:variable name="datesWithDiff" as="element()*">
<xsl:for-each select="$dates">
<xsl:variable name="pos" select="position()" />
<date diff="{(. - $dates[$pos - 1]) div xs:dayTimeDuration('P1D')}">
<xsl:value-of select="." />
</date>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$datesWithDiff" group-starting-with="date[#diff = '' or xs:int(#diff) gt 7]">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>

XSLT Saxon - Getting Latest node value

I am trying to find the last element in my input xml, which looks like
<ROOT>
<Items>
<Item>
<FieldTag>AMOUNT</FieldTag>
</Item>
<Item>
<FieldTag>CAT_TYPE</FieldTag>
</Item>
<Item>
<FieldTag>NUMBER</FieldTag>
</Item>
</Items>
<getResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MAIN>
<ArrayItem>
<NUMBER>123456789</NUMBER>
<CTRY>GB</CTRY>
<NAME>TEST NAME</NAME>
<RS>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<AMOUNT>11111</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<AMOUNT>22222</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<CAT_TYPE>10000</CAT_TYPE>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<CAT_TYPE>20000</CAT_TYPE>
</ArrayOfRSItem>
</RS>
</ArrayItem>
</MAIN>
</getResponse>
</ROOT>
and I use following XSLT file for transformation
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:urn="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02"
xmlns:func="http://www.test.com/"
exclude-result-prefixes="urn func" xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon">
<xsl:output method ="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:if test="count(/ROOT/getResponse/MAIN/ArrayItem) > 0" >
<Data>
<xsl:for-each select="/ROOT/getResponse/MAIN/ArrayItem" >
<xsl:element name="NodeItem">
<xsl:variable name="TempNo" select="NUMBER"/>
<xsl:element name="Number">
<xsl:value-of select="NUMBER"/>
</xsl:element>
<xsl:element name="Country">
<xsl:value-of select="CTRY"/>
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="NAME"/>
</xsl:element>
<xsl:element name="DataChanges">
<xsl:for-each select="./RS/ArrayOfRSItem/*" >
<xsl:if test="name()!='DATE_1' and name()!='DATE_2' ">
<xsl:variable name="ChangedNodeName" select="name()"></xsl:variable>
<xsl:for-each select="/ROOT/Items/Item">
<xsl:variable name="DisplayNodeName" select="FieldTag"></xsl:variable>
<xsl:if test="$DisplayNodeName = $ChangedNodeName">
<xsl:element name="FieldItem">
<xsl:attribute name="Tag">
<xsl:value-of select="FieldTag"/>
</xsl:attribute>
<xsl:variable name="NodeFullText" select="concat('../../getResponse/MAIN/ArrayItem[NUMBER=',$TempNo,']/RS/ArrayOfRSItem/',$DisplayNodeName)"/>
<xsl:attribute name="Value">
<xsl:value-of select="saxon:evaluate($NodeFullText)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each>
</Data>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
to get the desired output in the form of
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
But I am getting the output xml as
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
</DataChanges>
</NodeItem>
</Data>
I need to get the latest AMOUNT entry and CAT_TYPE. Any help or guidance would be much appreciated. Thanks in advance.
Couldn't this be (much) simpler?
XSLT 1.0 (or 2.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:template match="/">
<Data>
<xsl:apply-templates select="ROOT/getResponse/MAIN/ArrayItem"/>
</Data>
</xsl:template>
<xsl:template match="ArrayItem">
<NodeItem>
<Number><xsl:value-of select="NUMBER"/></Number>
<Country><xsl:value-of select="CTRY"/></Country>
<Name><xsl:value-of select="NAME"/></Name>
<DataChanges>
<xsl:apply-templates select="RS/ArrayOfRSItem/AMOUNT">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
<xsl:apply-templates select="RS/ArrayOfRSItem/CAT_TYPE">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
</DataChanges>
</NodeItem>
</xsl:template>
<xsl:template match="AMOUNT | CAT_TYPE">
<xsl:if test="position() = 1">
<FieldItem Tag="{local-name()}" Value="{.}"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Result, when applied to your example input:
<?xml version="1.0" encoding="utf-8"?>
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>

Grouping of grouped data

Input:
<persons>
<person name="John" role="Writer"/>
<person name="John" role="Poet"/>
<person name="Jacob" role="Writer"/>
<person name="Jacob" role="Poet"/>
<person name="Joe" role="Poet"/>
</persons>
Expected Output:
<groups>
<group roles="Wriet, Poet" persons="John, Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
As in the above example, I first need to group on person names and find everyone's roles. If more than one person is found to have the same set of roles (e.g. both John and Jacob are both Writer and Poet), then I need to group on each set of roles and list the person names.
I can do this for the first level of grouping using Muenchian method or EXSLT set:distinct etc.
<groups>
<group roles="Wriet, Poet" persons="John"/>
<group roles="Wriet, Poet" persons="Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
The above was transformed using XSLT 1.0 and EXSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sets="http://exslt.org/sets" extension-element-prefixes="sets">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="persons-by-name" match="person" use="#name"/>
<xsl:template match="persons">
<groups>
<xsl:for-each select="sets:distinct(person/#name)">
<group>
<xsl:attribute name="persons"><xsl:value-of select="."/></xsl:attribute>
<xsl:attribute name="roles">
<xsl:for-each select="key('persons-by-name', .)">
<xsl:value-of select="#role"/>
<xsl:if test="position()!=last()"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
However, I need help to understand how to group on the grouped roles.
If XSLT 1.0 solution is not available, please feel free to recommend XSLT 2.0 approach.
Try it this way?
XSLT 1.0
(using EXSLT node-set() and distinct() functions)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="exsl set">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:key name="person-by-name" match="person" use="#name" />
<xsl:key name="person-by-roles" match="person" use="#roles" />
<xsl:variable name="distinct-persons">
<xsl:for-each select="set:distinct(/persons/person/#name)">
<person name="{.}">
<xsl:attribute name="roles">
<xsl:for-each select="key('person-by-name', .)/#role">
<xsl:sort/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</person>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<groups>
<xsl:for-each select="set:distinct(exsl:node-set($distinct-persons)/person/#roles)">
<group roles="{.}">
<xsl:attribute name="names">
<xsl:for-each select="key('person-by-roles', .)/#name">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Poet, Writer" names="John, Jacob"/>
<group roles="Poet" names="Joe"/>
</groups>
I did exactly the same thing as you already did and then went a step further and grouped again. Now I get the following output with your input:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Writer,Poet" persons="John,Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
This is the XSLT 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns="" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:avintis="http://www.avintis.com/esb" exclude-result-prefixes="#all" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/persons">
<groups>
<xsl:variable name="persons" select="."/>
<!-- create a temporary variable containing all roles of a person -->
<xsl:variable name="roles">
<xsl:for-each select="distinct-values(person/#name)">
<xsl:sort select="."/>
<xsl:variable name="name" select="."/>
<xsl:element name="group">
<xsl:attribute name="roles">
<!-- sort the roles of each person -->
<xsl:variable name="rolesSorted">
<xsl:for-each select="$persons/person[#name=$name]">
<xsl:sort select="#role"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join($rolesSorted/person/#role,',')"/>
</xsl:attribute>
<xsl:attribute name="persons" select="."/>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<!-- now loop again over all roles of the persons and group persons having the same roles -->
<xsl:for-each select="distinct-values($roles/group/#roles)">
<xsl:element name="group">
<xsl:variable name="name" select="."/>
<xsl:attribute name="roles" select="$name"/>
<xsl:attribute name="persons">
<xsl:value-of select="string-join($roles/group[#roles=$name]/#persons,',')"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</groups>
</xsl:template>
<xsl:template match="*|text()|#*">
<xsl:copy>
<xsl:apply-templates select="*|text()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The roles get also sorted - so independent from the input order of the roles and persons.

xsl:for-each loop with attribute matching with parameter

I've been trying to figure this our for a while, but no luck.
I have a for each loop specifically targeting to a node with specific attributes, but somehow the for each condition doesn't seem to work.
I have the following xml
<?xml version="1.0" encoding="UTF-8"?>
<application-template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<config>
<env value="testing">
<subEnv value="env">
<group value="group2s">
<group value="group2as">
<prop value="group2Props">group2PropValues</prop>
</group>
</group>
</subEnv>
<subEnv value="misc1">
<group value="group2s">
<group value="group2as">
<prop value="group2Props">group2sPropValues</prop>
</group>
</group>
<group value="group2s1">
<group value="group2as1">
<prop value="group2Props">group2s1PropValues</prop>
</group>
</group>
</subEnv>
</env>
<env value="testingA">
<subEnv value="env">
<group value="test2">
<group value="test2a">
<group value="test2ab">
<prop value="group2Props">testingAGroup2PropValues</prop>
</group>
</group>
</group>
</subEnv>
</env>
</config>
</application-template>
with the following xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="inputEnv"/>
<xsl:variable name="env" select="/application-template/config/env[#value=$inputEnv]"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos" select="$env/subEnv"/>
<xsl:with-param name="subEnvValue" select="$env/subEnv/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="ou">
<xsl:param name="subEnvPos"/>
<xsl:param name="subEnvValue"/>
<xsl:variable name="test" select="/application-template/config/env[#value=$inputEnv]/subEnv[#value=$subEnvValue]"/>
<xsl:for-each select="$test/group">
testing1:
<xsl:value-of select="$test"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The param inputEnv is "testing" and the param $subEnvValue is "env". The output I got is
testing1:group2PropValues
testing1:group2PropValues
testing1:group2PropValues
testing1:group2PropValues
But I want to just loop once since the the condition is to match the subenv node = $subEnvValue (which is "env"). The output I'm hoping for is
testing1:group2PropValues
I think that instead of your current code:
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv/#value"/>
</xsl:call-template>
</xsl:template>
you want something like this:
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv[1]"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv[1]/#value"/>
</xsl:call-template>
</xsl:template>
With this modification, the complete XSLT code now is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="inputEnv" select="'testing'"/>
<xsl:variable name="env" select=
"/application-template/config/env[#value=$inputEnv]"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv[1]"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv[1]/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="ou">
<xsl:param name="subEnvPos"/>
<xsl:param name="subEnvValue"/>
<xsl:variable name="test" select=
"/application-template/config/env
[#value=$inputEnv]/subEnv[#value=$subEnvValue]"/>
<xsl:for-each select="$test/group">
testing1:
<xsl:value-of select="$test"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
and when applied to the provided XML document:
<application-template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<config>
<env value="testing">
<subEnv value="env">
<group value="group2s">
<group value="group2as">
<group value="group2bs">
<prop value="group2Props">group2PropValues</prop>
</group>
</group>
</group>
</subEnv>
<subEnv value="misc1">
<group value="group2s">
<group value="group2as">
<group value="group2bs">
<prop value="group2Props">group2sPropValues</prop>
</group>
</group>
</group>
<group value="group2s1">
<group value="group2as1">
<group value="group2bs1">
<prop value="group2Props">group2s1PropValues</prop>
</group>
</group>
</group>
</subEnv>
<subEnv value="misc2">
<group value="group2sMisc2">
<group value="group2asMisc2">
<group value="group2bsMisc2">
<prop value="group2Props">group2PropValuesMisc2</prop>
</group>
</group>
</group>
</subEnv>
</env>
</config>
</application-template>
the wanted result is produced:
testing1:
group2PropValues
Update:
After the OP explained in a comment what he wants, here is a short and simple solution to the new problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="vinputEnv" select="'testing'"/>
<xsl:template match="/*/*/env">
<xsl:if test="#value = $vinputEnv">
<xsl:for-each select="descendant::*[not(*)]">
<xsl:value-of select="concat('testing1: ', ., '
')"/>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document (above), the wanted, correct result is produced:
testing1: group2PropValues
testing1: group2sPropValues
testing1: group2s1PropValues
testing1: group2PropValuesMisc2

How do I combine or merge grouped nodes?

Using the XSL:
<?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="2.0">
<xsl:output method="xml"/>
<xsl:template match="/">
<records>
<record>
<!-- Group record by bigID, for further processing -->
<xsl:for-each-group select="records/record" group-by="bigID">
<xsl:sort select="bigID"/>
<xsl:for-each select="current-group()">
<!-- Create new combined record -->
<bigID>
<!-- <xsl:value-of select="."/> -->
<xsl:for-each select=".">
<xsl:value-of select="bigID"/>
</xsl:for-each>
</bigID>
<text>
<xsl:value-of select="text"/>
</text>
</xsl:for-each>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
I'm trying to change:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123</bigID>
<text>Contains text for 123</text>
<bigID>456</bigID>
<text>Some 456 text</text>
<bigID>123</bigID>
<text>More 123 text</text>
<bigID>123</bigID>
<text>Yet more 123 text</text>
</record>
</records>
into:
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<bigID>123
<text>Contains text for 123</text>
<text>More 123 text</text>
<text>Yet more 123 text</text>
</bigID>
<bigID>456
<text>Some 456 text</text>
</bigID>
</record>
</records>
Right now, I'm just listing the grouped <bigID>s, individually. I'm missing the step after grouping, where I combine the grouped <bigID> nodes. My suspicion is that I need to use the "key" function somehow, but I'm not sure.
Thanks for any help.
Here is the wanted XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kTextforId" match="text"
use="preceding-sibling::bigID[1]"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record">
<record>
<xsl:for-each-group select="bigID" group-by=".">
<bigID>
<xsl:sequence select="current-grouping-key()"/>
<xsl:copy-of select=
"key('kTextforId', current-grouping-key())"/>
</bigID>
</xsl:for-each-group>
</record>
</xsl:template>
</xsl:stylesheet>
When performed on the provided XML document, the wanted result is produced.
In XSLT 2.0 you don't need keys for grouping.
Since you are just copying the text elements in the group, the inner for-each can be removed.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<records>
<record>
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<bigID>
<xsl:value-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</bigID>
</xsl:for-each-group>
</record>
</records>
</xsl:template>
</xsl:stylesheet>
If you instead wanted to output the bigID elements followed by their text elements, then my loop would be replaced by the following.
<xsl:for-each-group select="records/record/bigID" group-by=".">
<xsl:sort select="." data-type="number" />
<xsl:copy-of select="." />
<xsl:copy-of select="current-group()/following-sibling::text[1]" />
</xsl:for-each-group>