How can we achieve Conditional increment in XSLT - xslt

Input:
<Order>
<HeaderValue1>ORDER1</HeaderValue1>
<OrderLineItems>
<Item>`A`</Item>
</OrderLineitems>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>`A`</Item>`
</OrderLineItems>
<OrderLineItems>`
<Item>B</Item>`
</OrderLineItems>
</Order>
<Order>
<HeaderValue1>`ORDER2`</HeaderValue1>
<OrderLineItems>
<Item>B`</Item>
</OrderLineItems>
</Order>
for each order there is a number of orderLineItems are present.For each order and items we need to assign a increment value.if the previous item value is equal to current item value.there is no need to increment just pass the value.otherwise increment value is assign to item element.
I need the output is like below.
<Order>
<HeaderValue1>1`</HeaderValue1>
<OrderLineItems>`
<Item>2`</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A`</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A`</Item>
</OrderLineItems>
<OrderLineItems>
<Item>3`</Item>
</OrderLineItems>
</Order>
<Order>
<HeaderValue1>4</HeaderValue1>
<OrderLineItems>
<Item>5</Item>
</OrderLineItems>
</Order>

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:template match="node()|#*">
<xsl:param name="pNum" select="0"/>
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*">
<xsl:with-param name="pNum" select="$pNum"/>
</xsl:apply-templates>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pNum" select="$pNum"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="HeaderValue1">
<xsl:param name="pNum" select="0"/>
<HeaderValue1><xsl:value-of select="$pNum+1"/></HeaderValue1>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pNum" select="$pNum+1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="OrderLineItems[not(. = preceding-sibling::*[1])]">
<xsl:param name="pNum" select="0"/>
<OrderLineItems>
<xsl:apply-templates select="node()[1]|#*">
<xsl:with-param name="pNum" select="$pNum+1"/>
</xsl:apply-templates>
</OrderLineItems>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pNum" select="$pNum+1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Item[not(. = ../preceding-sibling::*[1])]">
<xsl:param name="pNum" select="0"/>
<Item><xsl:value-of select="$pNum"/></Item>
</xsl:template>
<xsl:template match="Order">
<xsl:param name="pNum" select="0"/>
<Order>
<xsl:apply-templates select="node()[1]|#*">
<xsl:with-param name="pNum" select="$pNum"/>
</xsl:apply-templates>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pNum" select=
"$pNum +1 + count(OrderLineItems[not(. = preceding-sibling::*[1])])"/>
</xsl:apply-templates>
</Order>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to be well-formed):
<t>
<Order>
<HeaderValue1>ORDER1</HeaderValue1>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>B</Item>
</OrderLineItems>
</Order>
<Order>
<HeaderValue1>ORDER2</HeaderValue1>
<OrderLineItems>
<Item>B</Item>
</OrderLineItems>
</Order>
</t>
produces the wanted, correct result:
<t>
<Order>
<HeaderValue1>1</HeaderValue1>
<OrderLineItems>
<Item>2</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>A</Item>
</OrderLineItems>
<OrderLineItems>
<Item>3</Item>
</OrderLineItems>
<Order>
<HeaderValue1>4</HeaderValue1>
<OrderLineItems>
<Item>5</Item>
</OrderLineItems>
</Order>
</Order>
</t>

Related

Need help in forming target xml using XSLT

I am facing difficult while creating child elements to parent node using XSLT , as the format of the input message coming from external source is bit different. requesting you kindly help on the XSLT code. Please also let me know if i can optimize the existing xslt copied below.
Input Message coming from external source:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
<attr attr-name="A">
<new-value>adi00010210</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="C">
<new-value>active</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>iop00010538</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>cid2313213</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>hri00075562</child1>
<child2>1</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>spi00010021</child3>
<child4>1</child4>
</new-value>
<new-value>
<child3>vuh000123</child3>
<child4>1</child4>
</new-value>
</attr>
</PurchaseOrder>
XSLT Code written to transform The XSLT code also covers separation of values with | symbol if values are coming from same source multiple times.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:stylesheet version="2.0" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:template match="/">
<ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
<Orders>
<Order>
<ns0:attribute name="id" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:value-of select="/*/#id"/>
</ns0:attribute>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">A</ns0:with-param>
<ns0:with-param name="New_Attr">A</ns0:with-param>
</ns0:call-template>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">B</ns0:with-param>
<ns0:with-param name="New_Attr">B</ns0:with-param>
</ns0:call-template>
<ns0:call-template name="copy_attr" xmlns:ns0="http://www.w3.org/1999/XSL/Transform">
<ns0:with-param name="Attr_value">C</ns0:with-param>
<ns0:with-param name="New_Attr">C</ns0:with-param>
</ns0:call-template>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</ns0:template>
<ns0:template name="copy_attr">
<ns0:param name="Attr_value"/>
<ns0:param name="New_Attr" select="$Attr_value"/>
<ns0:param name="length" select="100000"/>
<ns0:param name="values">
<ns0:for-each select="//attr[#attr-name = $Attr_value]/new-value">
<ns0:if test="position()!=1">
<ns0:text>|</ns0:text>
</ns0:if>
<ns0:value-of select="."/>
</ns0:for-each>
</ns0:param>
<ns0:element name="{$New_Attr}">
<ns0:value-of select="substring($values,1,number($length))"/>
</ns0:element>
</ns0:template>
</ns0:stylesheet>
I am facing difficult write code to add child1/child2 (both child can be repeated multiple times) field to parentnode D (occurance 0 to unbounded) and child3/child4 to parentnode E respectively.
Expected Result:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A/>
<B/>
<C/>
<D>
<Child1/>
<Child2/>
</D>
<E>
<Child3/>
<Child4/>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
please find the changed input..I have retained old input as well
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="aoi00037607">
<attr attr-name="A">
<new-value>adi00010210</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="B">
<new-value>101</new-value>
</attr>
<attr attr-name="C">
<new-value>active</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>iop00010538</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>cid2313213</child1>
<child2>2</child2>
</new-value>
<new-value>
<child1>hri00075562</child1>
<child2>1</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>spi00010021</child3>
<child4>1</child4>
</new-value>
<new-value>
<child3>vuh000123</child3>
<child4>1</child4>
</new-value>
</attr>
<attr attr-name="C">
<new-value>inactive</new-value>
</attr>
</PurchaseOrder>
Please find the expected output
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>adi00010210</A>
<B>99|101</B>
<C>active|inactive</C>
<D>
<Child1>iop00010538</Child1>
<Child2>2</Child2>
</D>
<D>
<child1>cid2313213</child1>
<child2>2</child2>
</D>
<D>
<child1>hri00075562</child1>
<child2>1</child2>
</D>
<E>
<Child3>spi00010021</Child3>
<Child4>1</Child4>
</E>
<E>
<child3>vuh000123</child3>
<child4>1</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
Before we go into the solution, I would like to suggest one thing about retaining the prefix of namespace http://www.w3.org/1999/XSL/Transform as xsl instead of ns0 since it is widely accepted and easier to understand. However it is not mandatory and a personal choice. The solution below uses xsl as the prefix.
To start with, we need to prepare a list of nodes based on the value of attr-name of <attr> elements. This can be achieved by using <xsl:for-each> on the <attr> element and using attribute value templates {} for the element name.
<xsl:for-each select="attr">
<xsl:element name="{#attr-name}">
....
</xsl:element>
</xsl:for-each>
Next is the grouping of the values for the parent node and separating them using | separator. This can be achieved by defining <xsl:key> in XSLT 1.0).
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
If using XSLT 2.0, <xsl:for-each-group> can be used.
<xsl:for-each-group select="attr" group-by="#attr-name">
XSLT 1.0 solution
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns1:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$nodeName}">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:value-of select="new-value" />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0 solution
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="PurchaseOrder">
<ns1:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each-group select="attr" group-by="#attr-name">
<xsl:choose>
<xsl:when test="current-group()/new-value/*/node()">
<xsl:for-each select="current-group()/new-value">
<xsl:element name="{current-grouping-key()}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="current-group()/new-value" separator="|" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
Both solutions transform the updated input XML in the output shown below.
<ns1:PurchaseOrderMSG xmlns:ns1="urn:demo:PurchaseOrder">
<Orders>
<Order id="aoi00037607">
<A>adi00010210</A>
<B>99|101</B>
<C>active|inactive</C>
<D>
<child1>iop00010538</child1>
<child2>2</child2>
</D>
<D>
<child1>cid2313213</child1>
<child2>2</child2>
</D>
<D>
<child1>hri00075562</child1>
<child2>1</child2>
</D>
<E>
<child3>spi00010021</child3>
<child4>1</child4>
</E>
<E>
<child3>vuh000123</child3>
<child4>1</child4>
</E>
</Order>
</Orders>
</ns1:PurchaseOrderMSG>

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>

xslt grouping by calling template

I am new to xslt and trying to learn how to learn grouping using keys and using templates.
Can somebody help me on how can do the following in xslt.
I have to call a template from another template to do the transformation.
Thanks
here is my xml.
<Doc>
<ExOrder>121</ExOrder>
<Line>
<Ordernumber>225</Ordernumber>
<OrderID>1</OrderID>
<Quantity>5</Quantity>
</Line>
<Line>
<Ordernumber>225</Ordernumber>
<OrderID>5</OrderID>
<Quantity>5</Quantity>
</Line>
<Line>
<Ordernumber>226</Ordernumber>
<OrderID>2</OrderID>
<Quantity>5</Quantity>
</Line>
And here is how it should look like after.
<Doc>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
</Doc>
I'm going to assume the output you actually want is:
<Doc>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
<Order>
<Ordernumber>226</Ordernumber>
<Line>
<ID>2</ID>
</Line>
</Order>
</Doc>
Since the sample output you provided makes no sense. This XSLT will produce the output above when run on your sample input:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kOrder" match="Line" use="Ordernumber"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<Doc>
<xsl:apply-templates select="Line[generate-id() =
generate-id(key('kOrder', Ordernumber)[1])]" />
</Doc>
</xsl:template>
<xsl:template match="Line">
<Order>
<xsl:apply-templates select="Ordernumber" />
<Line>
<xsl:apply-templates select="key('kOrder', Ordernumber)/OrderID" />
</Line>
</Order>
</xsl:template>
<xsl:template match="OrderID">
<ID>
<xsl:value-of select="."/>
</ID>
</xsl:template>
</xsl:stylesheet>

XSLT style sheet to transform xml

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>

how to sum subdocument properties

There is list
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and document:
<udata id='1'>
<price>10</price>
</udata>
<udata id='1'>
<price>20</price>
</udata>
How to sum price's all document's?
To sum count I use'd:
<xsl:value-of select="sum(items/item/amount)"/>
I'd use:
<xsl:apply-templates select="udata/items/item" mode='price2' />
<xsl:template mode='price2' match='item'>
<xsl:apply-templates select="document(concat('upage://', page/#parentId))" mode='price'>
<xsl:with-param select='amount' name='count'/>
</xsl:apply-templates>
</xsl:template>
<xsl:template mode='price' match='/'>
<xsl:param name='count'/>
<xsl:value-of select="$count * /udata/page/properties/group[#name='price_prop']/property[#name='price']/value"/>
</xsl:template>
In result i had:
3020
I need 50. How to do this?
Here is a sample assuming XSLT 2.0 (e.g. as possible with Saxon 9 or AltovaXML):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:value-of select="sum(for $item in item return $item/amount * key('k1', $item/#parentid, $data-doc)/price)"/>
</xsl:template>
</xsl:stylesheet>
Example documents are
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
Output is 50.
[edit]Here is an XSLT 1.0 stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="item"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="items" select="/.."/>
<xsl:param name="total" select="0"/>
<xsl:choose>
<xsl:when test="not($items)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="price">
<xsl:for-each select="$data-doc">
<xsl:value-of select="$items[1]/amount * key('k1', $items[1]/#parentid)/price"/>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="sum">
<xsl:with-param name="items" select="$items[position() > 1]"/>
<xsl:with-param name="total" select="$total + $price"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Here is a shorter solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vPrices" select=
"document('file:///c:/temp/delete/priceData.xml')/*/*"/>
<xsl:template match="/*">
<xsl:apply-templates select="item[1]"/>
</xsl:template>
<xsl:template match="item" name="sumProducts">
<xsl:param name="pAccum" select="0"/>
<xsl:variable name="vNewAccum" select=
"$pAccum +amount * $vPrices[#id = current()/#parentid]/price"/>
<xsl:if test="not(following-sibling::*)">
<xsl:value-of select="$vNewAccum"/>
</xsl:if>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pAccum" select="$vNewAccum"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and having the file c:\temp\delete\priceData.xml contain:
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
then the wanted, correct result is produced:
50