Select distinct in XSLT relative to each elements - xslt

I try to retrieve a list of the attribute values of the children of an element but I want that the values only appears once.
For instance, I have the following XML
<root>
<sec>
<nom-title>
<nom-chapter>
<nom-article><data att="1.1"/></nom-article>
<nom-article>
<nom-item><data att="1.1"/></nom-item>
<nom-item><data att="1.2"/></nom-item>
</nom-article>
</nom-chapter>
<nom-chapter>
<nom-article><data att="2.1"/></nom-article>
<nom-article><data att="1.1"/></nom-article>
</nom-chapter>
</nom-title>
<nom-title>
<nom-chapter>
<nom-article><data att="1.1"/></nom-article>
</nom-chapter>
</nom-title>
</sec>
</root>
And I want a result like that:
<root>
<nom-title>
<att>1.1</att>
<att>1.2</att>
<att>2.1</att>
<nom-chapter>
<att>1.1</att>
<att>1.2</att>
<nom-article>
<att>1.1</att>
</nom-article>
<nom-article>
<att>1.1</att>
<att>1.2</att>
<nom-item><att>1.1</att></nom-item>
<nom-item><att>1.2</att></nom-item>
</nom-article>
</nom-chapter>
</nom-title>
<nom-title>
<att>1.1</att>
<nom-chapter>
<att>1.1</att>
<nom-article>
<att>1.1</att>
</nom-article>
</nom-chapter>
</nom-title>
</root>
I've tried to use the xsl:key element but it only returns the value for one element. In the example, it only returns 1.1 for the first title but not the second. The xsl I've used:
<xsl:key name="allAtt"
match="//*[starts-with(name(.),'nom-')]/data"
use="#att"/>
<xsl:template match="nom-title|nom-chapter|nom-article|nom-item">
<xsl:element name="name(.)">
<xsl:apply-templates select=".//*[starts-with(name(.),'nom-')]/data
</xsl:element>
</xsl:template>
<xsl:template match="data">
<xsl:variable name="att" select="#att"/>
<xsl:if test="generate-id(.)=generate-id(key('allAtt',$att)[1]">
<xsl:element name="att"><xsl:value-of select="$att"></xsl:element>
</xsl:if>
</xsl:template>

This transformation:
<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:key name="kData-nom-article" match="data" use=
"concat(generate-id(ancestor::nom-article[1]),
'+', #att)"/>
<xsl:key name="kData-nom-chapter" match="data" use=
"concat(generate-id(ancestor::nom-chapter[1]),
'+', #att)"/>
<xsl:key name="kData-nom-title" match="data" use=
"concat(generate-id(ancestor::nom-title[1]),
'+', #att)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sec"><xsl:apply-templates/></xsl:template>
<xsl:template match="nom-title|nom-article|nom-chapter">
<xsl:copy>
<xsl:apply-templates mode="list" select=
".//data[generate-id()
=
generate-id(key(concat('kData-', name(current())),
concat(generate-id(current()),
'+', #att
)
)
[1]
)
]"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="data" mode="list">
<att><xsl:value-of select="#att"/></att>
</xsl:template>
<xsl:template match="non-item/data">
<att><xsl:value-of select="#att"/></att>
</xsl:template>
<xsl:template match="*[not(self::nom-item)]/data"/>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<sec>
<nom-title>
<nom-chapter>
<nom-article>
<data att="1.1"/>
</nom-article>
<nom-article>
<nom-item>
<data att="1.1"/>
</nom-item>
<nom-item>
<data att="1.2"/>
</nom-item>
</nom-article>
</nom-chapter>
<nom-chapter>
<nom-article>
<data att="2.1"/>
</nom-article>
<nom-article>
<data att="1.1"/>
</nom-article>
</nom-chapter>
</nom-title>
<nom-title>
<nom-chapter>
<nom-article>
<data att="1.1"/>
</nom-article>
</nom-chapter>
</nom-title>
</sec>
</root>
produces the wanted, correct result:
<root>
<nom-title>
<att>1.1</att>
<att>1.2</att>
<att>2.1</att>
<nom-chapter>
<att>1.1</att>
<att>1.2</att>
<nom-article>
<att>1.1</att>
</nom-article>
<nom-article>
<att>1.1</att>
<att>1.2</att>
<nom-item>
<data att="1.1"/>
</nom-item>
<nom-item>
<data att="1.2"/>
</nom-item>
</nom-article>
</nom-chapter>
<nom-chapter>
<att>2.1</att>
<att>1.1</att>
<nom-article>
<att>2.1</att>
</nom-article>
<nom-article>
<att>1.1</att>
</nom-article>
</nom-chapter>
</nom-title>
<nom-title>
<att>1.1</att>
<nom-chapter>
<att>1.1</att>
<nom-article>
<att>1.1</att>
</nom-article>
</nom-chapter>
</nom-title>
</root>
Explanation: Expressing three different Muenchian groupings as one, by dynamically constructing the name of the key for the actual grouping to be performed.
Remember: The key name is a string and when necessary (as in this case), the name can be dynamically constructed, or passed as a parameter.

Related

How to create a merged xml node from either a list of xpaths (OR how to merge individual inconsistent nodes)

How would I create a collapsed xml structure from of an inconsistent list of xpaths?
xslt 3.0 / 2.0 is preferred.
Input xml
<root>
<accepted>
<x xp="vehicle/car/models/model/part/partnumber"/>
<x xp="vehicle/car/models/model/part/vendor"/>
<x xp="vehicle/car/models/model/part/vendor/name"/>
<x xp="vehicle/car/models/model/part/vendor/email"/>
</accepted>
<rejected>
<x xp="vehicle/car/models/model/part/partnumber"/>
<x xp="vehicle/car/models/model/part/vendor"/>
<x xp="vehicle/car/models/model/part/vendor/name"/>
<x xp="vehicle/car/models/model/part/vendor/email"/>
<x xp="vehicle/car/models/model/part/vendor/telephone"/>
</rejected>
<offices>
<x xp="country/city/name"/>
<x xp="country/city/district/name"/>
<x xp="country/city/district/numberofstores"/>
<x xp="country/city/district/totalrevenue"/>
</offices>
</root>
Desired output:
<xml>
<vehicle>
<car>
<models>
<model>
<part>
<partnumber/>
<vendor>
<name/>
<email/>
<telephone/>
</vendor>
</part>
</model>
</models>
</car>
</vehicle>
<country>
<city>
<district>
<name/>
<numberofstores/>
<totalrevenue/>
</district>
</city>
</country>
</xml>
What I tried:
I removed the duplicate xpaths using distinct-values() and then looped over this list of unique strings. For each unique string I applied tokenize() and created a nested xml element for each delimited portion of the string. The result is an xml node which I stored in a variable. But the problem now is that I end up with with a child node for each unique xpath and I couldn't figure out how to merge these nodes.
The alternative question would be how would I merge the below xml structure into a collapsed tree? (keeping in mind that this source xml comes from a variable)
<xml>
<vehicle>
<car>
<models>
<model>
<part>
<partnumber/>
</part>
</model>
</models>
</car>
</vehicle>
<vehicle>
<car>
<models>
<model>
<part>
<vendor>
<name/>
</vendor>
</part>
</model>
</models>
</car>
</vehicle>
...
<country>
<city>
<district>
<name/>
<numberofstores/>
<totalrevenue/>
</district>
</city>
</country>
</xml>
You can use a recursive grouping function:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:function name="mf:group" as="element()*">
<xsl:param name="paths" as="xs:string*"/>
<xsl:for-each-group select="$paths" group-by="if (contains(., '/')) then substring-before(., '/') else if (. != '') then . else ()">
<xsl:element name="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group() ! substring-after(., '/'))"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xml>
<xsl:sequence select="mf:group(//#xp)"/>
</xml>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNvtBGK

XSLT: iteration on string with indexes

I am having a trouble with very easy XSLT transformation. Let's assume that this is the input XML document:
<root>
<myString>ABBCD</myString>
</root>
The output XML should be:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Is there an easy way to split such string and increment index over it?
It's pretty easy in XSLT 2.0...
XML Input
<root>
<myString>ABBCD</myString>
</root>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:analyze-string select="." regex=".">
<xsl:matching-substring>
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="."/></value>
</character>
</xsl:matching-substring>
</xsl:analyze-string>
</myCharacters>
</xsl:template>
</xsl:stylesheet>
XML Output
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
It's not terrible in 1.0 either. You can use a recursive template. The following will produce the same output:
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="myString">
<myCharacters>
<xsl:call-template name="analyzeString">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</myCharacters>
</xsl:template>
<xsl:template name="analyzeString">
<xsl:param name="pos" select="1"/>
<xsl:param name="string"/>
<character>
<id><xsl:value-of select="$pos"/></id>
<value><xsl:value-of select="substring($string,1,1)"/></value>
</character>
<xsl:if test="string-length($string)>=2">
<xsl:call-template name="analyzeString">
<xsl:with-param name="pos" select="$pos+1"/>
<xsl:with-param name="string" select="substring($string,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 Solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStyle" select="document('')"/>
<xsl:template match="myString">
<xsl:variable name="vStr" select="."/>
<root>
<myCharacters>
<xsl:for-each select=
"($vStyle//node()|$vStyle//#*|$vStyle//namespace::*)
[not(position() > string-length($vStr))]">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="substring($vStr,position(),1)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<myString>ABBCD</myString>
</root>
the wanted, correct result is produced:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>
Alternatively, with FXSL one can use the str-map template like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:testmap="testmap" xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="xsl f ext testmap">
<xsl:import href="str-dvc-map.xsl"/>
<testmap:testmap/>
<xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<xsl:variable name="vrtfChars">
<xsl:call-template name="str-map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pStr" select="."/>
</xsl:call-template>
</xsl:variable>
<myCharacters>
<xsl:apply-templates select="ext:node-set($vrtfChars)/*"/>
</myCharacters>
</xsl:template>
<xsl:template name="enumChars" match="*[namespace-uri() = 'testmap']"
mode="f:FXSL">
<xsl:param name="arg1"/>
<character>
<value><xsl:value-of select="$arg1"/></value>
</character>
</xsl:template>
<xsl:template match="character">
<character>
<id><xsl:value-of select="position()"/></id>
<xsl:copy-of select="*"/>
</character>
</xsl:template>
</xsl:stylesheet>
to produce the same correct result:
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
II. XSLT 2.0 solution -- shorter and simpler than other answers:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="myString">
<root>
<myCharacters>
<xsl:for-each select="string-to-codepoints(.)">
<character>
<id><xsl:value-of select="position()"/></id>
<value><xsl:value-of select="codepoints-to-string(.)"/></value>
</character>
</xsl:for-each>
</myCharacters>
</root>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document (above), produces the wanted, correct result:
<root>
<myCharacters>
<character>
<id>1</id>
<value>A</value>
</character>
<character>
<id>2</id>
<value>B</value>
</character>
<character>
<id>3</id>
<value>B</value>
</character>
<character>
<id>4</id>
<value>C</value>
</character>
<character>
<id>5</id>
<value>D</value>
</character>
</myCharacters>
</root>

Joining data of the same type with XSLT

I would like to join all data of the same type with XSLT. I have the following XML:
<ZE1MARAM>
<ZE1KONDM SEGMENT="1">
<VKORG>NL01</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NL01</KONDWERT>
<MENGE> 70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE> 70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKA0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE> 33.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
</ZE1MARAM>
so each ZE1KONDM with the same VKORG value in the result xml has to be appended to the same element. So the result would be something like that:
<result>
<prices value="NL01">
<price type="VKP0">
70.00
</price>
</prices>
<prices value="NLWS">
<price type="VKP0">
70.00
</price>
<price type="VKA0">
55.00
</price>
</prices>
I tried to work with keys, and do something like that:
<xsl:key name="myKey" match="ZE1KONDM" use="normalize-space(VKORG)" />
<xsl:for-each select="ZE1KONDM">
<xsl:choose>
<xsl:when test="KONDART='VKP0'">
<xsl:element name="prices">
<xsl:element name="price">
<xsl:value-of select="key('myKey', normalize-space(VKORG))/MENGE"/>
</xsl:element>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:for-each>
but it does not work because it takes just one key..
There is some way to solve this problem with xslt?
There's probably a better way, but try this:
http://www.xmlplayground.com/2A3C7H
(see output source)
I. XSLT 1.0 Solution:
Here is a classical application of the Muenchian grouping method:
<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:key name="kZByM" match="ZE1KONDM" use="VKORG"/>
<xsl:template match="/*">
<result>
<xsl:apply-templates select=
"*[generate-id() = generate-id(key('kZByM', VKORG)[1])]"/>
</result>
</xsl:template>
<xsl:template match="ZE1KONDM">
<prices value="{VKORG}">
<xsl:apply-templates select="key('kZByM', VKORG)" mode="inGroup"/>
</prices>
</xsl:template>
<xsl:template match="ZE1KONDM" mode="inGroup">
<price type="{KONDART}">
<xsl:value-of select="MENGE"/>
</price>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<ZE1MARAM>
<ZE1KONDM SEGMENT="1">
<VKORG>NL01</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NL01</KONDWERT>
<MENGE>70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKP0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE>70.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
<ZE1KONDM SEGMENT="1">
<VKORG>NLWS</VKORG>
<KONDART>VKA0</KONDART>
<BEGINDATUM>99991231</BEGINDATUM>
<ENDDATUM>20120605</ENDDATUM>
<KONDWERT>NLWS</KONDWERT>
<MENGE>33.00</MENGE>
<CURRENCY>EUR</CURRENCY>
</ZE1KONDM>
</ZE1MARAM>
the wanted, correct result is produced:
<result>
<prices value="NL01">
<price type="VKP0">70.00</price>
</prices>
<prices value="NLWS">
<price type="VKP0">70.00</price>
<price type="VKA0">33.00</price>
</prices>
</result>
Do note: The Muenchian grouping method is probably the fastest XSLT 1.0 grouping method, because it uses keys. Other methods (such as comparing siblings values) are way too slower (O(N^2)) which is prohibitive fro using them on large data sizes.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.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="/*">
<result>
<xsl:for-each-group select="*" group-by="VKORG">
<prices value="{VKORG}">
<xsl:apply-templates select="current-group()"/>
</prices>
</xsl:for-each-group>
</result>
</xsl:template>
<xsl:template match="ZE1KONDM">
<price type="{KONDART}">
<xsl:value-of select="MENGE"/>
</price>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced:
<result>
<prices value="NL01">
<price type="VKP0">70.00</price>
</prices>
<prices value="NLWS">
<price type="VKP0">70.00</price>
<price type="VKA0">33.00</price>
</prices>
</result>
Explanation:
Proper use of xsl:for-each-group with the group-by attribute, and the current-group() function.

copy an xml to another but changing node attribute and edge attributes

I have an xml with the format
<graph id=1>
<nodes>
<node id =2>
<name value=node1/>
</node>
<node id =3>
<name value=node3/>
</node>
<edges>
<edge id=11 source=2 target=3/>
</edges>
</graph>
Now i want to change the id of node using generate-id() but that should change in all edges too.Eg i change the id of node1 to '1a1' so it should change the source of edge to '1a1' everwhere in xml.
It should do this for all nodes and edges.The remaining xml should be as it is.
My xsl
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#id[parent::node]">
<xsl:attribute name="id">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
</xsl:template>
this changes the node id but i want to compare the edges source and target and change them too.
The edge source and target are some nodes id .
Any help would be greatly appreciated.
Thanks
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kElementById" match="*[#id]" use="#id"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#id">
<xsl:attribute name="id">
<xsl:value-of select="generate-id(..)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#source|#target">
<xsl:attribute name="{name()}">
<xsl:value-of select="generate-id(key('kElementById',.))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this well formed input:
<graph id="1">
<nodes>
<node id ="2">
<name value="node1"/>
</node>
<node id ="3">
<name value="node3"/>
</node>
<edges>
<edge id="11" source="2" target="3"/>
</edges>
</nodes>
</graph>
Output:
<graph id="IDAEQBBB">
<nodes>
<node id="IDAHQBBB">
<name value="node1"></name>
</node>
<node id="IDALQBBB">
<name value="node3"></name>
</node>
<edges>
<edge id="IDAQQBBB" source="IDAHQBBB" target="IDALQBBB"></edge>
</edges>
</nodes>
</graph>
Add this section to the XSL that you already have.
<xsl:template match="#source[parent::edge]|#target[parent::edge]">
<xsl:attribute name="{name()}">
<xsl:value-of select="generate-id(//node[#id=current()]/#id)"/>
</xsl:attribute>
</xsl:template>

Grouping elements and deleting duplicate nodes - XSLT 1.0

I have looked at Muenchian Grouping - group within a node, not within the entire document but it is not quite working for me. The Muenchian method alone does not do it either for me.
I have also looked at XSLT 1.0: grouping and removing duplicate but cannot follow it completely.
I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<items item="475053">
<Recordset>
<CodeBusinessUnit>99</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
</items>
<items item="475054">
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>255.34</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>10</CodeBusinessUnit>
<PriceValue>299</PriceValue>
</Recordset>
</items>
</MT_MATERIALDATA>
The outcome should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<Mi item="475053">
<PriceList>
<Prices>
<Price Value="250"/>
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</Mi>
<Mi item="475054">
<PriceList>
<Prices>
<Price Value="255.34"/>
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299"/>
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</Mi>
</MT_MATERIALDATA>
So for matching <PriceValue> elements
in <Recordset>, all respective <CodeBusinessUnits> need to be listed in <Stores>.
If not, an extra <Prices> node needs to be created.
I have been trying for hours but either the Store-numbers are always duplicate or they are not aggregated even if the PriceValue is the same.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kPriceByValAndItem" match="PriceValue"
use="concat(../../#item, '|', .)"/>
<xsl:template match="/*">
<MT_MATERIALDATA>
<xsl:apply-templates/>
</MT_MATERIALDATA>
</xsl:template>
<xsl:template match="items">
<MI item="{#item}">
<PriceList>
<xsl:for-each select=
"*/PriceValue
[generate-id()
=
generate-id(key('kPriceByValAndItem',
concat(../../#item, '|', .)
)[1]
)
]
">
<Prices>
<Price Value="{.}"/>
<PriceConfig>
<Stores>
<xsl:for-each select=
"key('kPriceByValAndItem',
concat(../../#item, '|', .)
)">
<xsl:value-of select="../CodeBusinessUnit"/>
<xsl:if test="not(position()=last())">,</xsl:if>
</xsl:for-each>
</Stores>
</PriceConfig>
</Prices>
</xsl:for-each>
</PriceList>
</MI>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<MT_MATERIALDATA>
<items item="475053">
<Recordset>
<CodeBusinessUnit>99</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>250</PriceValue>
</Recordset>
</items>
<items item="475054">
<Recordset>
<CodeBusinessUnit>1</CodeBusinessUnit>
<PriceValue>255.34</PriceValue>
</Recordset>
<Recordset>
<CodeBusinessUnit>10</CodeBusinessUnit>
<PriceValue>299</PriceValue>
</Recordset>
</items>
</MT_MATERIALDATA>
produces the wanted, correct result:
<MT_MATERIALDATA>
<MI item="475053">
<PriceList>
<Prices>
<Price Value="250"/>
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
<MI item="475054">
<PriceList>
<Prices>
<Price Value="255.34"/>
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299"/>
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
</MT_MATERIALDATA>
I think the following solves the problem, at least for the grouping:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="items/Recordset" use="concat(generate-id(..), '|', PriceValue)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<Mi item="{#item}">
<PriceList>
<xsl:apply-templates select="Recordset[generate-id() = generate-id(key('k1', concat(generate-id(..), '|', PriceValue))[1])]"/>
</PriceList>
</Mi>
</xsl:template>
<xsl:template match="Recordset">
<Prices>
<Price Value="{PriceValue}"/>
<PriceConfig>
<Stores>
<xsl:apply-templates select="key('k1', concat(generate-id(..), '|', PriceValue))/CodeBusinessUnit"/>
</Stores>
</PriceConfig>
</Prices>
</xsl:template>
<xsl:template match="CodeBusinessUnit">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
I'm also going to post an stylesheet, because everybody do it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kBUnitByItem-Price"
match="CodeBusinessUnit"
use="concat(../../#item, '++', ../PriceValue)"/>
<xsl:template match="/">
<MT_MATERIALDATA>
<xsl:apply-templates/>
</MT_MATERIALDATA>
</xsl:template>
<xsl:template match="items">
<MI item="{#item}">
<PriceList>
<xsl:apply-templates/>
</PriceList>
</MI>
</xsl:template>
<xsl:template match="CodeBusinessUnit[
count(.|key('kBUnitByItem-Price',
concat(../../#item,'++',../PriceValue)
)[1]
) = 1
]">
<Prices>
<Price Value="{../PriceValue}"/>
<PriceConfig>
<Stores>
<xsl:apply-templates
select="key('kBUnitByItem-Price',
concat(../../#item,'++',../PriceValue))"
mode="sequence"/>
</Stores>
</PriceConfig>
</Prices>
</xsl:template>
<xsl:template match="text()"/>
<xsl:template match="node()" mode="sequence">
<xsl:if test="position()!=1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Note: Grouping stores by item and price. A little more pull than push style (That's because there is no duplicate #item.)
Output:
<MT_MATERIALDATA>
<MI item="475053">
<PriceList>
<Prices>
<Price Value="250" />
<PriceConfig>
<Stores>99,1</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
<MI item="475054">
<PriceList>
<Prices>
<Price Value="255.34" />
<PriceConfig>
<Stores>1</Stores>
</PriceConfig>
</Prices>
<Prices>
<Price Value="299" />
<PriceConfig>
<Stores>10</Stores>
</PriceConfig>
</Prices>
</PriceList>
</MI>
</MT_MATERIALDATA>
I think we cover all the variations: key value, push-pull, sequence separator condition.