XSLT total per unique value - xslt

Apologies. I asked a question just a few days ago and it was already answered by Sir michael.hor257k.
I have a new requirement though.
The TOTAL field needs to count the sgtin fields per unique value like the sample below.
<xsl:template match="/*">
<MESSAGE>
<PALLET>
<xsl:for-each select="//*[local-name()='ObjectEvent'][substring(epcList/epc,1,16) = 'urn:epc:id:sgtin']">
<MATERIAL>
<BOX>
<TOTAL>
<xsl:value-of select="count(./epcList/epc[substring(.,1,16) = 'urn:epc:id:sgtin'])"/>
</TOTAL>
<xsl:for-each select="./epcList/epc[substring(.,1,16) = 'urn:epc:id:sgtin']">
<SERIES>
<ITEM>
<xsl:value-of select="substring-after(substring-after(.,'.'),'.')"/>
</ITEM>
</SERIES>
</BOX>
</MATERIAL>
</PALLET>
</MESSAGE>
This is a sample input file:
<?xml version="1.0" encoding="UTF-8"?>
<n0:EPCISDocument xmlns:n0="urn:epcglobal:epcis:xsd:1" xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" schemaVersion="1.1">
<ObjectEvent>
<epcList>
<epc>urn:epc:id:sgtin:999999999.0000.0000000001</epc>
<epc>urn:epc:id:sgtin:999999999.0000.0000000002</epc>
</epcList>
<Extension>
<obj>
<BATCH>ABCD_00</BATCH>
</obj>
</Extension>
</ObjectEvent>
<ObjectEvent>
<epcList>
<epc>urn:epc:id:sgtin:999999999.0000.0000000003</epc>
<epc>urn:epc:id:sgtin:999999999.0000.0000000004</epc>
<epc>urn:epc:id:sgtin:999999999.0000.0000000005</epc>
</epcList>
<Extension>
<obj>
<BATCH>ABCD_00</BATCH>
</obj>
</Extension>
</ObjectEvent>
<ObjectEvent>
<epcList>
<epc>urn:epc:id:sgtin:999999999.1111.0000000006</epc>
<epc>urn:epc:id:sgtin:999999999.1111.0000000007</epc>
<epc>urn:epc:id:sgtin:999999999.1111.0000000008</epc>
</epcList>
<Extension>
<obj>
<BATCH>EFGH_11</BATCH>
</obj>
</Extension>
</ObjectEvent>
<ObjectEvent>
<epcList>
<epc>urn:epc:id:sgtin:999999999.2222.0000000009</epc>
</epcList>
<Extension>
<obj>
<BATCH>IJKL_22</BATCH>
</obj>
</Extension>
</ObjectEvent>
</n0:EPCISDocument>
The TSERIES will be counted per GTIN (0000, 1111, 2222).
The expected should be:
<?xml version="1.0" encoding="UTF-8"?>
<MESSAGE>
<PALLET>
<MATERIAL>
<BOX>
<TOTAL>5</TOTAL>
<SERIES>
<ITEM>0000000001</ITEM>
</SERIES>
<SERIES>
<ITEM>0000000002</ITEM>
</SERIES>
</BOX>
</MATERIAL>
<MATERIAL>
<BOX>
<TOTAL>5</TOTAL>
<SERIES>
<ITEM>0000000003</ITEM>
</SERIES>
<SERIES>
<ITEM>0000000004</ITEM>
</SERIES>
<SERIES>
<ITEM>0000000005</ITEM>
</SERIES>
</BOX>
</MATERIAL>
<MATERIAL>
<BOX>
<TOTAL>3</TOTAL>
<SERIES>
<ITEM>0000000006</ITEM>
</SERIES>
<SERIES>
<ITEM>0000000007</ITEM>
</SERIES>
<SERIES>
<ITEM>0000000008</ITEM>
</SERIES>
</BOX>
</MATERIAL>
<MATERIAL>
<BOX>
<TOTAL>1</TOTAL>
<SERIES>
<ITEM>0000000009</ITEM>
</SERIES>
</BOX>
</MATERIAL>
</PALLET>
</MESSAGE>
Is this possible Masters?
edit:
I'm not sure if this can help. I edited the source payload to add BATCH field. that is unique per GTIN. Can it be used for the TSERIES total?

As I said in the comment, this is a grouping problem and the preferred solution in XSLT 1.0 is to use the Muenchian grouping method:
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="batch" match="ObjectEvent" use="Extension/obj/BATCH" />
<xsl:template match="/*">
<MESSAGE>
<PALLET>
<!-- for each distinct batch -->
<xsl:for-each select="ObjectEvent[count(. | key('batch', Extension/obj/BATCH)[1]) = 1]">
<xsl:variable name="current-group" select="key('batch', Extension/obj/BATCH)" />
<xsl:variable name="group-total" select="count($current-group/epcList/epc)"/>
<!-- for each ObjectEvent in this batch -->
<xsl:for-each select="$current-group">
<MATERIAL>
<BOX>
<TOTAL>
<xsl:value-of select="$group-total"/>
</TOTAL>
<xsl:for-each select="epcList/epc">
<SERIES>
<ITEM>
<xsl:value-of select="substring(., 33)"/>
</ITEM>
</SERIES>
</xsl:for-each>
</BOX>
</MATERIAL>
</xsl:for-each>
</xsl:for-each>
</PALLET>
</MESSAGE>
</xsl:template>
</xsl:stylesheet>
Note that this assumes all SGTINs have the same number of characters.

Related

Merge (Left Join) 2 XML files based on an Element attribute using xslt 3.0

I am new to xslt and I was trying to merge 2 xml files - expecting something like Left join but its not working as expected. It would be great and much appreciated if you guys could help me on this.
The matching ID's in the second file should merge their data to the first file and ignore the rest of the non-matching Id's.
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:map="http://www.w3.org/2005/xpath-functions/map" exclude-result-prefixes="array fn map math xs">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="doc1">
<RECORDS>
<RECORD>
<PROP NAME="A">
<PVAL>text A</PVAL>
</PROP>
<PROP NAME="B">
<PVAL>text B</PVAL>
</PROP>
<PROP NAME="C">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="Id">
<PVAL>1</PVAL>
</PROP>
</RECORD>
<RECORD>
<PROP NAME="D">
<PVAL>text D</PVAL>
</PROP>
<PROP NAME="E">
<PVAL>text E</PVAL>
</PROP>
<PROP NAME="F">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="Id">
<PVAL>2</PVAL>
</PROP>
</RECORD>
<RECORD>
<PROP NAME="G">
<PVAL>text G</PVAL>
</PROP>
<PROP NAME="H">
<PVAL>text H</PVAL>
</PROP>
<PROP NAME="I">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="Id">
<PVAL>3</PVAL>
</PROP>
</RECORD>
</RECORDS>
</xsl:param>
<xsl:param name="doc2">
<RECORDS>
<RECORD>
<PROP NAME="J">
<PVAL>text J</PVAL>
</PROP>
<PROP NAME="K">
<PVAL>text K</PVAL>
</PROP>
<PROP NAME="L">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="Id">
<PVAL>1</PVAL>
</PROP>
</RECORD>
<RECORD>
<PROP NAME="M">
<PVAL>text M</PVAL>
</PROP>
<PROP NAME="N">
<PVAL>text N</PVAL>
</PROP>
<PROP NAME="O">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="Id">
<PVAL>4</PVAL>
</PROP>
</RECORD>
</RECORDS>
</xsl:param>
<xsl:template match="/" name="xsl:initial-template">
<xsl:merge>
<xsl:merge-source select="$doc1/RECORDS/RECORD">
<xsl:merge-key select="PROP/#Id"></xsl:merge-key>
</xsl:merge-source>
<xsl:merge-source select="$doc2/RECORDS/RECORD">
<xsl:merge-key select="PROP/#Id"></xsl:merge-key>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:apply-templates select="current-merge-group()[1]/*, fn:current-merge-group()[2]/*"/>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</xsl:template>
</xsl:stylesheet>
Expected Result:
<?xml version="1.0" encoding="UTF-8"?>
<RECORDS>
<RECORD>
<PROP NAME="Id">
<PVAL>1</PVAL>
</PROP>
<PROP NAME="A">
<PVAL>text A</PVAL>
</PROP>
<PROP NAME="B">
<PVAL>text B</PVAL>
</PROP>
<PROP NAME="C">
<PVAL>10</PVAL>
</PROP>
<PROP NAME="J">
<PVAL>text J</PVAL>
</PROP>
<PROP NAME="K">
<PVAL>text K</PVAL>
</PROP>
<PROP NAME="L">
<PVAL>10</PVAL>
</PROP>
</RECORD>
<RECORD>
<PROP NAME="Id">
<PVAL>2</PVAL>
</PROP>
<PROP NAME="D">
<PVAL>text D</PVAL>
</PROP>
<PROP NAME="E">
<PVAL>text E</PVAL>
</PROP>
<PROP NAME="F">
<PVAL>10</PVAL>
</PROP>
</RECORD>
<RECORD>
<PROP NAME="Id">
<PVAL>3</PVAL>
</PROP>
<PROP NAME="G">
<PVAL>text G</PVAL>
</PROP>
<PROP NAME="H">
<PVAL>text H</PVAL>
</PROP>
<PROP NAME="I">
<PVAL>10</PVAL>
</PROP>
</RECORD>
</RECORDS>
Your paths don't match the input structure and you haven't checked whether there is a match in the first merge source so the following change should work:
<xsl:template match="/" name="xsl:initial-template">
<xsl:merge>
<xsl:merge-source name="doc1" select="$doc1/RECORDS/RECORD">
<xsl:merge-key select="PROP[#NAME = 'Id']/PVAL"></xsl:merge-key>
</xsl:merge-source>
<xsl:merge-source name="doc2" select="$doc2/RECORDS/RECORD">
<xsl:merge-key select="PROP[#NAME = 'Id']/PVAL"></xsl:merge-key>
</xsl:merge-source>
<xsl:merge-action>
<xsl:if test="current-merge-group('doc1')">
<xsl:copy>
<xsl:apply-templates select="*, current-merge-group('doc2')/*"/>
</xsl:copy>
</xsl:if>
</xsl:merge-action>
</xsl:merge>
</xsl:template>
https://xsltfiddle.liberty-development.net/6q1SDkj

Nested Conditional Looping (One to Many )

The Input has two xml in it , The InputMessagePart_0 have Multiple Location id and InputMessagePart_1 have Multiple ItemMaster then i need to create an Output where for every location id i need to have the Item Master.
I have written the Xslt where its not looping on the InputMessagePart_0(Record) level and its taking only the first Location id ,
XSlT1.0 :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s2:Root" />
</xsl:template>
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_1/s0:Root/ItemMaster">
<xsl:variable name="var:v1" select="../../../InputMessagePart_0/s1:Root/Record/LocationId" />
<xsl:variable name="var:v2" select="ItemId" />
<xsl:variable name="var:v3" select="ItemName" />
<xsl:variable name="var:v4" select="Quantity" />
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="$var:v2" />
</ItemId>
<ItemName>
<xsl:value-of select="$var:v3" />
</ItemName>
<Qty>
<xsl:value-of select="$var:v4" />
</Qty>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Input XML :
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<ns0:Root xmlns:ns0="http://Test.Lookup">
<Record>
<LocationId>12</LocationId>
</Record>
<Record>
<LocationId>13</LocationId>
</Record>
<Record>
<LocationId>14</LocationId>
</Record>
</ns0:Root>
</InputMessagePart_0>
<InputMessagePart_1>
<ns0:Root xmlns:ns0="http://Test.ItemMaster">
<ItemMaster>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
</ns0:Root>
</InputMessagePart_1>
</ns0:Root>
Current OutPut:
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
You need to have an xsl:for-each (or xsl:apply-templates) to get the Record elements with the locations. (And this xsl:for-each would contain the current xsl:for-each on the ItemMaster)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes" version="1.0" />
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_0/s1:Root/Record">
<xsl:variable name="var:v1" select="LocationId" />
<xsl:for-each select="../../../InputMessagePart_1/s0:Root/ItemMaster">
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="ItemId" />
</ItemId>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Qty>
<xsl:value-of select="Quantity" />
</Qty>
</Detail>
</xsl:for-each>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Note, the template matching / wasn't really necessary here, as XSLT's built-in template does the same thing.

Multiply 2 filtered numbers and sum

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.

Calculating counts when working with elements using name/value pairs

I have an xml in this format. I am looking to generate an output in the output format given below.
I am trying to calculate the total number of SkuID's associated with each unique SerialNum. The same SerialNum can be present more than once within the xml. Could you please suggest how I can get the count in this scenario?
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>12345678</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>5729305323</SkuID>
</Box>
<Box>
<SkuID>2334923423</SkuID>
</Box>
</Skid>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>22334455</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>42342234234</SkuID>
</Box>
<Box>
<SkuID>3243325352</SkuID>
</Box>
</Skid>
<Skid>
<NameValuePairs>
<NameValue>
<ID>SerialNum</ID>
<Value>12345678</Value>
</NameValue>
<NameValue>
<ID>calcTotals</ID>
<Value>true</Value>
</NameValue>
</NameValuePairs>
<Box>
<SkuID>75547453333</SkuID>
</Box>
<Box>
<SkuID>235356233266</SkuID>
</Box>
</Skid>
</root>
Output:
<Skids>
<Box>
<TrackingNumber>12345678</TrackingNumber>
<Total>4</Total>
</Box>
<Box>
<TrackingNumber>22334455</TrackingNumber>
<Total>2</Total>
</Box>
</Skids>
Using XSLT 2.0 with for-each-group
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="root">
<Skids>
<xsl:for-each-group select="Skid" group-by="NameValuePairs/NameValue[ID='SerialNum']/Value">
<Box>
<TrackingNumber><xsl:value-of select=".//NameValue[ID='SerialNum']/Value"/></TrackingNumber>
<Total><xsl:value-of select="count(current-group()//Box)"/></Total>
</Box>
</xsl:for-each-group>
</Skids>
</xsl:template>
</xsl:stylesheet>
Using XSLT 1.0 with Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="Tracking" match="Skid" use=".//NameValue[ID='SerialNum']/Value"/>
<xsl:template match="root">
<Skids>
<xsl:for-each select="Skid[generate-id() = generate-id(key('Tracking', .//NameValue[ID='SerialNum']/Value))]" >
<Box>
<TrackingNumber><xsl:value-of select=".//NameValue[ID='SerialNum']/Value"/></TrackingNumber>
<Total><xsl:value-of select="count(//Skid[.//NameValue[ID='SerialNum']/Value = current()//NameValue[ID='SerialNum']/Value]//Box)"/></Total>
</Box>
</xsl:for-each>
</Skids>
</xsl:template>
</xsl:stylesheet>
Output
<Skids>
<Box>
<TrackingNumber>12345678</TrackingNumber>
<Total>4</Total>
</Box>
<Box>
<TrackingNumber>22334455</TrackingNumber>
<Total>2</Total>
</Box>
</Skids>
The first part of my solution is a temporary variable, named ids,
and containing (no surprise) ID elements.
Each of them contains content of a Value element, which has ID sibling
with SerialNum content.
Additionally, this ID element has cnt attribute - the number of SkuID
elements in the "current" Skid.
Then ID elements in this variable are processed in a for-each-group loop,
grouping them by the content (ID value).
So the whole script looks like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<Skids>
<xsl:variable name="ids">
<xsl:for-each select="//Value[../ID = 'SerialNum']">
<ID>
<xsl:attribute name="cnt" select="count(../../../Box/SkuID)"/>
<xsl:value-of select="."/>
</ID>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$ids"/>
<xsl:for-each-group select="$ids/ID" group-by=".">
<Box>
<ID><xsl:value-of select="."/></ID>
<Total><xsl:value-of select="sum(current-group()/#cnt)"/></Total>
</Box>
</xsl:for-each-group>
</Skids>
</xsl:template>
</xsl:transform>
Note: <xsl:copy-of select="$ids"/> instruction was placed for
demonstration purpose only. In the final version remove it.

XPath expression to exclude some child nodes

I have this source XML tree:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>
<bar>
<baz>
<item>
<methods>
<item>
<id>1</id>
</item>
</methods>
<id>1</id>
</item>
<item>
<methods>
<item>
<id>19</id>
</item>
</methods>
<id>2</id>
</item>
</baz>
</bar>
</foo>
<bar_method>
<root>
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
<method id="2">
<calc md="qck" />
<tm m="4" />
<price_list>
<price mse="1">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
</root>
</bar_method>
</root>
Now I need to place fragment of this tree in variable using XPath. The fragment should look like this:
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
These are bla nodes excluding method nodes, id attributes of which missing in /root/foo/bar/baz/item/methods/item/id. I use following expression but it selects all nodes with duplicates:
<xsl:variable name="meth" select="/root/bar_method/root//*[not(name() = 'method' and count(/root/foo/bar/baz//methods/item[id = #id]) = 0)]" />
XPath can only select nodes, it cannot change them. That is to say, the children and descendants of the nodes you select will always be exactly as they were in the source document.
If you want to create a tree that's different from the input tree, you need XSLT or XQuery.
Looks like you want all the bla elements and just the first methods/method element within each of them. Is that right?
You can't do that in a single XPath expression because you can only restrict the elements being selected - you can't filter out some of their descendants as well. But it is possible using templates.
This stylesheet creates the variable $meth and outputs it using copy-of.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="meth">
<xsl:apply-templates select="root/bar_method/root/bla"/>
</xsl:variable>
<xsl:copy-of select="$meth"/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="methods">
<xsl:copy>
<xsl:apply-templates select="method[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
output
<bla id="1">
<methods>
<method id="1">
<calc md="ck"/>
<tm m="14"/>
<price_list>
<price mse="0">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd"/>
<tm m="3"/>
<price_list>
<price mse="01">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>