I am trying to create a counter to grouped elements.
Source:
<?xml version="1.0" encoding="UTF-8"?>
<root_com>
<root_por-out>
<is_globalprocessid>1370284</is_globalprocessid>
<is_processid>1370284</is_processid>
<partneridentcode>123456</partneridentcode>
<por-out>
<por_number>320060916</por_number>
<order_pos>10</order_pos>
<order_pos_partner>10</order_pos_partner>
</por-out>
<por-out>
<por_number>320060916</por_number>
<order_number_partner>875421</order_number_partner>
<order_pos>20</order_pos>
<order_pos_partner>20</order_pos_partner>
</por-out>
<por-out>
<por_number>320060916</por_number>
<order_pos>30</order_pos>
<order_pos_partner>10</order_pos_partner>
</por-out>
<por-out>
<por_number>320060916</por_number>
<order_pos>40</order_pos>
<order_pos_partner>30</order_pos_partner>
</por-out>
<por-out>
<por_number>320060916</por_number>
<order_pos>50</order_pos>
<order_pos_partner>10</order_pos_partner>
</por-out>
</root_por-out>
</root_com>
Desired output:
<Confirmation>
<Settings>
<DecimalSymbol>.</DecimalSymbol>
</Settings>
<Orders>
<Order>
<OrderIdSupplier>320060916</OrderIdSupplier>
<OrderItems>
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNo>1</ItemSubNo>
</OrderItem>
<OrderItem>
<LineNumber>20</LineNumber>
<ItemSubNo>1</ItemSubNo>
</OrderItem>
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNo>2</ItemSubNo>
</OrderItem>
<OrderItem>
<LineNumber>30</LineNumber>
<ItemSubNo>1</ItemSubNo>
</OrderItem>
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNo>3</ItemSubNo>
</OrderItem>
</OrderItems>
</Order>
</Orders>
</Confirmation>
The Code i currently have:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:formatter="com.inubit.ibis.xsltext.Formatter" version="2.0" exclude-result-prefixes="formatter">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/"><xsl:for-each select="root_com/root_por-out"><Confirmation>
<Settings>
<DecimalSymbol>.</DecimalSymbol>
</Settings>
<Orders>
<Order>
<PurchaseNo><xsl:value-of select="por-out[1]/order_number_partner"/></PurchaseNo>
<SupplierId><xsl:value-of select="partneridentcode"/></SupplierId>
<OrderIdSupplier><xsl:value-of select="por-out[1]/por_number"/></OrderIdSupplier>
<OrderItems><xsl:for-each select="por-out/order_pos"><xsl:for-each-group select="../order_pos_partner" group-by="text()"><OrderItem>
<DeliveryDate><xsl:value-of select="delivery_date"/></DeliveryDate>
<LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber><ItemSubNo><xsl:value-of select="last()"/></ItemSubNo>
<ProductId><xsl:value-of select="article_partner"/></ProductId>
<Price><xsl:value-of select="price"/></Price>
<PriceFactor>1</PriceFactor>
<Quantity><xsl:value-of select="quantity"/></Quantity>
</OrderItem></xsl:for-each-group></xsl:for-each>
</OrderItems>
</Order>
</Orders>
</Confirmation></xsl:for-each></xsl:template>
</xsl:stylesheet>
Code reduced to the core issue:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:formatter="com.inubit.ibis.xsltext.Formatter" version="2.0" exclude-result-prefixes="formatter">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/"><xsl:for-each select="root_com/root_por-out"><Confirmation>
<Settings>
<DecimalSymbol>.</DecimalSymbol>
</Settings>
<Orders>
<Order>
<OrderItems><xsl:for-each select="por-out/order_pos"><xsl:for-each-group select="../order_pos_partner" group-by="text()"><OrderItem>
<LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber><ItemSubNo><xsl:value-of select="last()"/></ItemSubNo>
</OrderItem></xsl:for-each-group></xsl:for-each>
</OrderItems>
</Order>
</Orders>
</Confirmation></xsl:for-each></xsl:template>
</xsl:stylesheet>
What the point is:
The customer sends an order. We provide a response. Since high amounts are ordered, we need to delivere the ordered parts on different dates. We split original positions.
In our reply customer gets a reference to his original order position(order_pos_partner) and customer also needs a counter for amount of split positions(destination: "ItemSubNo")
how can I do that?
If output is sorted by positions, that is fine, but not needed.
Thank you
If you use
<xsl:template match="root_por-out">
<Confirmation>
<Orders>
<Order>
<OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
<xsl:for-each-group select="por-out" group-by="order_pos_partner">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</Order>
</Orders>
</Confirmation>
</xsl:template>
<xsl:template match="por-out">
<OrderItem>
<LineNumber>{current-grouping-key()}</LineNumber>
<ItemSubNumber>{position()}</ItemSubNumber>
</OrderItem>
</xsl:template>
you will get
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNumber>1</ItemSubNumber>
</OrderItem>
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNumber>2</ItemSubNumber>
</OrderItem>
<OrderItem>
<LineNumber>10</LineNumber>
<ItemSubNumber>3</ItemSubNumber>
</OrderItem>
<OrderItem>
<LineNumber>20</LineNumber>
<ItemSubNumber>1</ItemSubNumber>
</OrderItem>
<OrderItem>
<LineNumber>30</LineNumber>
<ItemSubNumber>1</ItemSubNumber>
</OrderItem>
So I think that has the right numbers, although not the ordering you showed.
To preserve the original input order, you could use the grouping only to store the right sequences and them map then later on:
<xsl:template match="root_por-out">
<Confirmation>
<Orders>
<Order>
<OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
<xsl:variable name="groups" as="map(*)">
<xsl:map>
<xsl:for-each-group select="por-out" group-by="order_pos_partner">
<xsl:map-entry key="current-grouping-key()" select="current-group() ! generate-id()"/>
</xsl:for-each-group>
</xsl:map>
</xsl:variable>
<xsl:apply-templates select="*">
<xsl:with-param name="groups" select="$groups"/>
</xsl:apply-templates>
</Order>
</Orders>
</Confirmation>
</xsl:template>
<xsl:template match="por-out">
<xsl:param name="groups"/>
<OrderItem>
<LineNumber>{order_pos_partner}</LineNumber>
<ItemSubNumber>{index-of($groups(order_pos_partner), generate-id())}</ItemSubNumber>
</OrderItem>
</xsl:template>
Both examples use XSLT 3 although with some more verbosity (<LineNumber>{order_pos_partner}</LineNumber> instead of <LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber>or XML data structures instead of light-weight maps
<xsl:key name="group" match="group" use="#key"/>
<xsl:template match="root_por-out">
<Confirmation>
<Orders>
<Order>
<OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
<xsl:variable name="groups">
<xsl:for-each-group select="por-out" group-by="order_pos_partner">
<group key="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<item>
<xsl:value-of select="generate-id()"/>
</item>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="*">
<xsl:with-param name="groups" select="$groups"/>
</xsl:apply-templates>
</Order>
</Orders>
</Confirmation>
</xsl:template>
<xsl:template match="por-out">
<xsl:param name="groups"/>
<OrderItem>
<LineNumber>
<xsl:value-of select="order_pos_partner"/>
</LineNumber>
<ItemSubNumber>
<xsl:value-of select="index-of(key('group', order_pos_partner, $groups)/item, generate-id())"/>
</ItemSubNumber>
</OrderItem>
</xsl:template>
it could be done in XSLT 2.
Related
I need to sum the multiplication of 2 numbers based on this example
<test>
<stop>
<id>1</id>
<unit_id>1</unit_id>
<unit_id>2</unit_id>
</stop>
<stop>
<id>2</id>
<unit_id>1</unit_id>
<unit_id>3</unit_id>
</stop>
<unit>
<id>1</id>
<count>2</count>
<value>1</value>
</unit>
<unit>
<id>2</id>
<count>4</count>
<value>1</value>
</unit>
<unit>
<id>3</id>
<count>2</count>
<value>3</value>
</unit>
The result i want to get is the one below
<test>
<stop>
<id>1</id>
<sum>6</sum>
</stop>
<stop>
<id>2</id>
<sum>10</sum>
</stop>
Any tips how to get it?
I tried with this example but the sum of the moltiplication doesn't work, it is ok for only the sum or the multiplication but not both
<xsl:template match="stop">
<xsl:variable name="ship_unit" select="id"/>
<xsl:value-of select="sum(following-sibling::unit[id=$ship_unit]/count*following-sibling::unit[id=$ship_unit]/value)"/>
If I am guessing correctly, you want to do something like:
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:key name="unit" match="unit" use="id" />
<xsl:template match="/test">
<xsl:copy>
<xsl:for-each select="stop">
<xsl:variable name="unit1" select="key('unit', unit_id[1])" />
<xsl:variable name="unit2" select="key('unit', unit_id[2])" />
<xsl:copy>
<xsl:copy-of select="id"/>
<sum>
<xsl:value-of select="$unit1/count * $unit1/value + $unit2/count * $unit2/value" />
</sum>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, the result of applying this to your input example will be:
<?xml version="1.0" encoding="utf-8"?>
<test>
<stop>
<id>1</id>
<sum>6</sum>
</stop>
<stop>
<id>2</id>
<sum>8</sum>
</stop>
</test>
and not what you posted.
I have below input, I have to update the <price> of <orderitem> with <name> = Desktop with the <price> of another <orderitem> (present anywhere in the xml) which has same value of <associationid> as its <objectid>.
Here for example, i have <associationid> as 2 for Desktop, now I look for <objectid> with value as 2 and get its price and update it here. Sample output below.
Kindly let me know the approach to solve such problems involving traversing, I am new to XSL and trying to refer the XSLT cook book and SO, but not getting proper reference. Thanks.
<listoforders>
<Orderitem>
<name>Desktop</name>
<place>NZ</place>
<price>120ass</price>
<associationid>2</associationid>
<Orderitem>
<name>Desktop2</name>
<place>NZ</place>
<price>130</price>
</Orderitem>
<Orderitem>
<name>Desktop3</name>
<place>NZ</place>
<price>130obj1</price>
<objectid>1</objectid>
<price>130</price>
</Orderitem>
</Orderitem>
<Orderitem>
<name>laptop</name>
<place>NZ</place>
<price>120</price>
<Orderitem>
<name>laptop2</name>
<place>NZ</place>
<price>130</price>
</Orderitem>
<Orderitem>
<name>laptop3</name>
<place>NZ</place>
<price>130obj2</price>
<objectid>2</objectid>
</Orderitem>
</Orderitem>
</listoforders>
Output
<listoforders>
<Orderitem>
<name>Desktop</name>
<place>NZ</place>
<price>130obj2</price>
<associationid>2</associationid>
<Orderitem>
<name>Desktop2</name>
<place>NZ</place>
<price>130</price>
</Orderitem>
<Orderitem>
<name>Desktop3</name>
<place>NZ</place>
<price>130obj1</price>
<objectid>1</objectid>
<price>130</price>
</Orderitem>
</Orderitem>
<Orderitem>
<name>laptop</name>
<place>NZ</place>
<price>120</price>
<Orderitem>
<name>laptop2</name>
<place>NZ</place>
<price>130</price>
</Orderitem>
<Orderitem>
<name>laptop3</name>
<place>NZ</place>
<price>130obj2</price>
<objectid>2</objectid>
</Orderitem>
</Orderitem>
</listoforders>
XSL:
<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="price[../name='Desktop']">
<xsl:copy-of select="price[//objectid=.//associationid]" />
</xsl:template>
</xsl:stylesheet>
It's always best to use a key to resolve cross-references:
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="*"/>
<xsl:key name="ord" match="Orderitem" use="objectid" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="price[../name='Desktop']">
<xsl:copy-of select="key('ord', ../associationid)/price"/>
</xsl:template>
</xsl:stylesheet>
you can use key for this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="keyitem" match="price" use="../objectid"/>
<!-- Identity Transformation -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="price[../name eq 'Desktop'][../associationid]">
<xsl:variable name="id" select="../associationid"/>
<xsl:copy>
<xsl:value-of select="key('keyitem',$id)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I noticed three things in your XSLT:
In the xsl:copy-of, you are currently not traversing through all the prices like you intended, for that you need to use //price.
In the condition, you want to test the element objectid that belongs to the price, so you should use ../objectid rather than //objectid.
Therefor, in the condition you implicitly "leave" the context of the desktop price element, so you no longer have direct access to .//associationid. However, you can use the current() function to refer to the object (Thanks michael-hor257k)
This XSLT produces the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<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="price[../name='Desktop']">
<xsl:copy-of select="//price[../objectid=current()/../associationid]"/>
</xsl:template>
</xsl:stylesheet>
Another possibility would be to use a variable to store the id for later comparison.
<xsl:template match="price[../name='Desktop']">
<xsl:variable name="var.associationid" select="../associationid"/>
<xsl:copy-of select="//price[../objectid=$var.associationid]"/>
</xsl:template>
I would like to add an element to existing XML file with normalized value of existing element in the XML.
Any help would be extremely appreciated.
regards
<?xml version="1.0" encoding="UTF-8"?>
<top>
<Results>
<a>no</a>
<b>10</b>
<c>12</c>
<d>9</d>
</Results>
<Results>
<a>no</a>
<b>8</b>
<c>50</c>
<d>12</d>
</Results>
<Results>
<a>no</a>
<b>6</b>
<c>55</c>
<d>56</d>
</Results>
<Results>
<a>yes</a>
<b>23</b>
<c>32</c>
<d>34</d>
</Results>
</top>
In the sample input xml above, I would like to add "b_nom" elements to each of the results where the value is (b)/(minimum of 'b' grouped with a). The expected output is as below
<?xml version="1.0" encoding="UTF-8"?>
<top>
<Results>
<a>no</a>
<b>10</b>
<b_nom>1.66</b_nom>
<c>12</c>
<d>9</d>
</Results>
<Results>
<a>no</a>
<b>8</b>
<b_nom>1.33</b_nom>
<c>50</c>
<d>12</d>
</Results>
<Results>
<a>no</a>
<b>6</b>
<b_nom>1</b_nom>
<c>55</c>
<d>56</d>
</Results>
<Results>
<a>yes</a>
<b>23</b>
<b_nom>1</b_nom>
<c>32</c>
<d>34</d>
</Results>
</top>
I think you want
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="Results" use="a"/>
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b">
<xsl:call-template name="identity"/>
<xsl:variable name="min">
<xsl:for-each select="key('k1', ../a)">
<xsl:sort select="b" data-type="number"/>
<xsl:if test="position() = 1">
<xsl:value-of select="b"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<b_nom>
<xsl:value-of select="format-number(. div $min, '0.##')"/>
</b_nom>
</xsl:template>
</xsl:stylesheet>
Here is a changed version of the stylesheet that takes a couple of values identifying a group into account:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:data="http:://example.com/data"
exclude-result-prefixes="data">
<data:data xmlns="">
<group>
<key>no</key>
<values>
<value>no</value>
<value>n</value>
<value>0</value>
</values>
</group>
<group>
<key>yes</key>
<values>
<value>yes</value>
<value>y</value>
<value>1</value>
</values>
</group>
</data:data>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="main-input" select="/"/>
<xsl:variable name="groups" select="document('')/xsl:stylesheet/data:data/group"/>
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b">
<xsl:call-template name="identity"/>
<xsl:variable name="this" select="."/>
<xsl:variable name="min">
<xsl:for-each select="$main-input//Results[a = $groups/values[value = $this/../a]/value]">
<xsl:sort select="b" data-type="number"/>
<xsl:if test="position() = 1">
<xsl:value-of select="b"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<b_nom>
<xsl:value-of select="format-number(. div $min, '0.##')"/>
</b_nom>
</xsl:template>
</xsl:stylesheet>
That transforms the input
<?xml version="1.0" encoding="UTF-8"?>
<top>
<Results>
<a>no</a>
<b>10</b>
<c>12</c>
<d>9</d>
</Results>
<Results>
<a>n</a>
<b>8</b>
<c>50</c>
<d>12</d>
</Results>
<Results>
<a>0</a>
<b>6</b>
<c>55</c>
<d>56</d>
</Results>
<Results>
<a>yes</a>
<b>23</b>
<c>32</c>
<d>34</d>
</Results>
</top>
into the output
<top>
<Results>
<a>no</a>
<b>10</b>
<b_nom>1.67</b_nom>
<c>12</c>
<d>9</d>
</Results>
<Results>
<a>n</a>
<b>8</b>
<b_nom>1.33</b_nom>
<c>50</c>
<d>12</d>
</Results>
<Results>
<a>0</a>
<b>6</b>
<b_nom>1</b_nom>
<c>55</c>
<d>56</d>
</Results>
<Results>
<a>yes</a>
<b>23</b>
<b_nom>1</b_nom>
<c>32</c>
<d>34</d>
</Results>
</top>
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.
I am trying to create an xslt style sheet to transform xml. I have researched the muenchian method but am not familiar with how it all works. I am having difficulties because every ManifestNo can have any number of SequenceNo's. I am trying to group the flat order in the source xml to an order in the destination xml with nested Stop details.
<Orders>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>3524771-01</CustomerOrderNo>
<Weight>180</Weight>
</Order>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>3524771-02</CustomerOrderNo>
<Weight>250</Weight>
</Order>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>3524728-01</CustomerOrderNo>
<Weight>25</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>5464565-01</CustomerOrderNo>
<Weight>150</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>5874565-02</CustomerOrderNo>
<Weight>125</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>3524888-01</CustomerOrderNo>
<Weight>95</Weight>
</Order>
</Orders>
to
<Orders>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<Stop>
<SequenceNo>1</SequenceNo>
<Freight>
<CustomerOrderNo>3524771-01</CustomerOrderNo>
<Weight>180</Weight>
</Freight>
<Freight>
<CustomerOrderNo>3524771-02</CustomerOrderNo>
<Weight>250</Weight>
</Freight>
</Stop>
<Stop>
<SequenceNo>2</SequenceNo>
<Freight>
<CustomerOrderNo>3524728-01</CustomerOrderNo>
<Weight>25</Weight>
</Freight>
<Freight>
<CustomerOrderNo>3524771-02</CustomerOrderNo>
<Weight>250</Weight>
</Freight>
</Stop>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<Stop>
<SequenceNo>1</SequenceNo>
<Freight>
<CustomerOrderNo>5464565-01</CustomerOrderNo>
<Weight>150</Weight>
</Freight>
</Stop>
<Stop>
<SequenceNo>2</SequenceNo>
<Freight>
<CustomerOrderNo>5874565-02</CustomerOrderNo>
<Weight>125</Weight>
</Freight>
<Freight>
<CustomerOrderNo>3524888-0</CustomerOrderNo>
<Weight>95</Weight>
</Freight>
</Stop>
</Order>
</Orders>
I. This XSLT 1.0 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="kOrderByNum" match="Order"
use="ManifestNo"/>
<xsl:key name="kSeqInOrder" match="SequenceNo"
use="concat(../ManifestNo, '+', .)"/>
<xsl:key name="kSeqByOrderNo" match="SequenceNo"
use="../ManifestNo"/>
<xsl:template match="Order"/>
<xsl:template match="SequenceNo"/>
<xsl:template match="/*">
<Orders>
<xsl:apply-templates/>
</Orders>
</xsl:template>
<xsl:template match=
"Order
[generate-id()
=
generate-id(key('kOrderByNum', ManifestNo)[1])
]">
<Order>
<xsl:copy-of select="ManifestNo|Warehouse"/>
<xsl:apply-templates select=
"key('kSeqByOrderNo', ManifestNo)
[generate-id()
=
generate-id(key('kSeqInOrder',
concat(../ManifestNo, '+', .)
)[1]
)
]
"/>
</Order>
</xsl:template>
<xsl:template match="SequenceNo[true()]">
<Stop>
<xsl:copy-of select="."/>
<xsl:apply-templates mode="inGroup" select=
"key('kSeqInOrder',
concat(../ManifestNo, '+', .))"/>
</Stop>
</xsl:template>
<xsl:template match="SequenceNo" mode="inGroup">
<Freight>
<xsl:copy-of select="../CustomerOrderNo|../Weight"/>
</Freight>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Orders>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>3524771-01</CustomerOrderNo>
<Weight>180</Weight>
</Order>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>3524771-02</CustomerOrderNo>
<Weight>250</Weight>
</Order>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>3524728-01</CustomerOrderNo>
<Weight>25</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>1</SequenceNo>
<CustomerOrderNo>5464565-01</CustomerOrderNo>
<Weight>150</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>5874565-02</CustomerOrderNo>
<Weight>125</Weight>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<SequenceNo>2</SequenceNo>
<CustomerOrderNo>3524888-01</CustomerOrderNo>
<Weight>95</Weight>
</Order>
</Orders>
produces the wanted, correct result:
<Orders>
<Order>
<ManifestNo>283749</ManifestNo>
<Warehouse>35</Warehouse>
<Stop>
<SequenceNo>1</SequenceNo>
<Freight>
<CustomerOrderNo>3524771-01</CustomerOrderNo>
<Weight>180</Weight>
</Freight>
<Freight>
<CustomerOrderNo>3524771-02</CustomerOrderNo>
<Weight>250</Weight>
</Freight>
</Stop>
<Stop>
<SequenceNo>2</SequenceNo>
<Freight>
<CustomerOrderNo>3524728-01</CustomerOrderNo>
<Weight>25</Weight>
</Freight>
</Stop>
</Order>
<Order>
<ManifestNo>283750</ManifestNo>
<Warehouse>50</Warehouse>
<Stop>
<SequenceNo>1</SequenceNo>
<Freight>
<CustomerOrderNo>5464565-01</CustomerOrderNo>
<Weight>150</Weight>
</Freight>
</Stop>
<Stop>
<SequenceNo>2</SequenceNo>
<Freight>
<CustomerOrderNo>5874565-02</CustomerOrderNo>
<Weight>125</Weight>
</Freight>
<Freight>
<CustomerOrderNo>3524888-01</CustomerOrderNo>
<Weight>95</Weight>
</Freight>
</Stop>
</Order>
</Orders>
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:key name="kSeqByOrderNo" match="SequenceNo"
use="../ManifestNo"/>
<xsl:template match="/*">
<Orders>
<xsl:for-each-group select="Order" group-by="ManifestNo">
<Order>
<xsl:copy-of select="ManifestNo|Warehouse"/>
<xsl:for-each-group select="key('kSeqByOrderNo', ManifestNo)"
group-by=".">
<xsl:copy-of select="."/>
<Stop>
<xsl:apply-templates select="current-group()"/>
</Stop>
</xsl:for-each-group>
</Order>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template match="SequenceNo">
<Freight>
<xsl:copy-of select="../CustomerOrderNo|../Weight"/>
</Freight>
</xsl:template>
</xsl:stylesheet>