Group XML using xsl1.0 based on header value and lineItems - xslt

I have an Orders XML input file where based on the Matching 'ref' XML tag and 'Shipto' XML tag, the order should be split with multiple batch orders.
Input xml:
<Purchase>
<Order>
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>AAA</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Order>
<Order>
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>BBB</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Order>
<Order>
<Header>
<Ref>13401</Ref>
<ShipToDetails>
<ShipTo>CCC</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Order>
<Order>
<Header>
<Ref>13401</Ref>
<ShipToDetails>
<ShipTo>CCC</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800101</LineItem>
</Items>
</Order>
</Purchase>
Expected output:
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>AAA</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Batchorder>
<Batchorder>
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>BBB</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Batchorder>
<Batchorder>
<Header>
<Ref>13401</Ref>
<ShipToDetails>
<ShipTo>CCC</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
<Items>
<LineItem>800101</LineItem>
</Items>
</Batchorder>
I have tried with below XSL1.0, but I get only the Partial output
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="Orders" match="Header" use="Ref"/>
<xsl:template match="Purchase">
<xsl:for-each select="/Purchase/Order/Header[generate-id() = generate-id(key('Orders',Ref)[1])]">
<Batchorder>
<header>
<OrderType>
<xsl:value-of select="../Ref"/>
</OrderType>
<ShipToDetails>
<ShipTo>
<xsl:value-of select="ShipTo"/>
</ShipTo>
</ShipToDetails>
</header>
<Items>
<LineItem>
<xsl:value-of select="../../Items/LineItem"/>
</LineItem>
</Items>
</Batchorder>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I am looking for an xsl1.0 solution, any help will be appreciated. Thanks!

Change your XSLT code to the following. It uses a concatenated string for the xsl:key to combine the Ref and the ShipTo value as an index.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="Orders" match="Order" use="concat(Header/Ref,'_',Header/ShipToDetails/ShipTo)"/>
<xsl:template match="Purchase">
<xsl:for-each select="Order[generate-id() = generate-id(key('Orders',concat(Header/Ref,'_',Header/ShipToDetails/ShipTo))[1])]">
<Batchorder>
<Header>
<xsl:copy-of select="Header/Ref | Header/ShipToDetails"/>
</Header>
<xsl:for-each select="key('Orders',concat(Header/Ref,'_',Header/ShipToDetails/ShipTo))">
<Items>
<LineItem>
<xsl:value-of select="Items/LineItem"/>
</LineItem>
</Items>
</xsl:for-each>
</Batchorder>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Its output is
<?xml version="1.0"?>
<Batchorder>
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>AAA</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Batchorder>
<Batchorder>
<Header>
<Ref>13400</Ref>
<ShipToDetails>
<ShipTo>BBB</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
</Batchorder>
<Batchorder>
<Header>
<Ref>13401</Ref>
<ShipToDetails>
<ShipTo>CCC</ShipTo>
</ShipToDetails>
</Header>
<Items>
<LineItem>800100</LineItem>
</Items>
<Items>
<LineItem>800101</LineItem>
</Items>
</Batchorder>
The output is not XML-1.0 conform because it doesn't have a single root element. Add an <xsl:copy> to the template to replicate the <Purchase> element if desired.

Related

XSLT split keeping a section static

I am trying to split the file below to 3 items each and also keep the last section the same. Additionally, I also need to Header block that appears at the beginning with each split (except the last).
Input file
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Root>
<List>
<Header>
<test1>a</test1>
</Header>
<Item>
<ItemNumber>
<Number>1</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>2</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>3</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>4</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>5</Number>
</ItemNumber>
</Item>
</List>
<List>
<EOF MaxMsgPerFile="3" >
</EOF>
</List>
</Root>
Here is what I have tried
<?xml version="1.0" encoding="UTF-8"?>
<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:variable name="tag">
<xsl:value-of select="/*/*/EOF/#MaxMsgPerFile"/>
</xsl:variable>
<xsl:template match="/Root">
<xsl:copy>
<xsl:for-each select="List[not (EOF)]/Item[position() mod $tag = 1]">
<List>
<xsl:copy-of select="Header"/>
<xsl:copy-of select=". | following-sibling::Item[position() < $tag]"/>
</List>
</xsl:for-each>
<xsl:copy-of select="List[EOF]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I get everything I wanted but I cant see the Header appearing at each split
Here is what is expected
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<List>
<Header>
<test1>a</test1>
</Header>
<Item>
<ItemNumber>
<Number>1</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>2</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>3</Number>
</ItemNumber>
</Item>
</List>
<List>
<Header>
<test1>a</test1>
</Header>
<Item>
<ItemNumber>
<Number>4</Number>
</ItemNumber>
</Item>
<Item>
<ItemNumber>
<Number>5</Number>
</ItemNumber>
</Item>
</List>
<List>
<EOF MaxMsgPerFile="3">
</EOF>
</List>
</Root>
Any help is greatly appreciated
Thanks
How about:
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:template match="/Root">
<xsl:variable name="groupSize" select="List/EOF/#MaxMsgPerFile" />
<xsl:copy>
<xsl:for-each select="List/Item[position() mod $groupSize = 1]">
<List>
<xsl:copy-of select="../Header | . | following-sibling::Item[position() < $groupSize]"/>
</List>
</xsl:for-each>
<xsl:copy-of select="List[EOF]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Generic XSLT transform to manipulate similar XML data

We have a tool which is producing an unwanted XML element and we are using XSLT to translate it to the required format.
We are writing a different XSLT for every XML generated by the file. e.g. one for the Customers XML, one for the Orders XML and so on.
Below are a couple of XML data files produced by the tool and the actual output expected
Customers
Tool-generated XML
<Message>
<Data>
<CustomerArray>
<Customer>
<X>
<Name>John</Name>
<Id>100</Id>
<Roles>
<Role>
<X>Manager</X>
<X>Architect</X>
</Role>
</Roles>
</X>
<X>
<Name>Doe</Name>
<Id>102</Id>
<Roles>
<Role>
<X>Supervisor</X>
<X>Admin</X>
</Role>
</Roles>
</X>
</Customer>
</CustomerArray>
</Data>
</Message>
Required XML data
<Message>
<Data>
<CustomerArray>
<Customer>
<Name>John</Name>
<Id>100</Id>
<Roles>
<Role>Manager</Role>
<Role>Architect</Role>
</Roles>
</Customer>
<Customer>
<Name>Doe</Name>
<Id>102</Id>
<Roles>
<Role>Supervisor</Role>
<Role>Admin</Role>
</Roles>
</Customer>
</CustomerArray>
</Data>
</Message>
Orders
Tool-generated XML
<Message>
<Orders>
<Order>
<X>
<OrderNumber>O123</OrderNumber>
<CustomerID>C100</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>10.0</UnitPrice>
</X>
<X>
<OrderNumber>O456</OrderNumber>
<CustomerID>C107</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>5.0</UnitPrice>
</X>
</Order>
</Orders>
</Message>
Required XML data
<Message>
<Orders>
<Order>
<OrderNumber>O123</OrderNumber>
<CustomerID>C100</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>10.0</UnitPrice>
</Order>
<Order>
<OrderNumber>O456</OrderNumber>
<CustomerID>C107</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>5.0</UnitPrice>
</Order>
</Orders>
</Message>
The unwanted element X can come at any level.
Is it possible to write a generic XSLT transform to achieve this result across all XML input? For instance, where an X is found, replace it with the parent tag and then delete the parent tag.
You need to write an identity transform with an explicit template for all nodes that have any X children so that they can be replicated.
This transform does what you asked. It uses the variable name to save the name of the element that is parent to the X to avoid writing the more obscure name(current()) when it comes to generating each output element.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[X]">
<xsl:variable name="name" select="name()"/>
<xsl:for-each select="X">
<xsl:element name="{$name}">
<xsl:apply-templates/>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
output
Customers
<Message>
<Data>
<CustomerArray>
<Customer>
<Name>John</Name>
<Id>100</Id>
<Roles>
<Role>Manager</Role>
<Role>Architect</Role>
</Roles>
</Customer>
<Customer>
<Name>Doe</Name>
<Id>102</Id>
<Roles>
<Role>Supervisor</Role>
<Role>Admin</Role>
</Roles>
</Customer>
</CustomerArray>
</Data>
</Message>
Orders
<Message>
<Orders>
<Order>
<OrderNumber>O123</OrderNumber>
<CustomerID>C100</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>10.0</UnitPrice>
</Order>
<Order>
<OrderNumber>O456</OrderNumber>
<CustomerID>C107</CustomerID>
<Quantity>100</Quantity>
<UnitPrice>5.0</UnitPrice>
</Order>
</Orders>
</Message>
Here is a slightly simpler/shorter solution, which also handles correctly the case when a X can have non X sibling elements:
<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:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[X]"><xsl:apply-templates/></xsl:template>
<xsl:template match="X">
<xsl:element name="{name(..)}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the following XML document (the first of the provided ones, with one non-X sibling added to the X elements:
<Message>
<Data>
<CustomerArray>
<Customer>
<X>
<Name>John</Name>
<Id>100</Id>
<Roles>
<Role>
<X>Manager</X>
<X>Architect</X>
</Role>
</Roles>
</X>
<somethingElse/>
<X>
<Name>Doe</Name>
<Id>102</Id>
<Roles>
<Role>
<X>Supervisor</X>
<X>Admin</X>
</Role>
</Roles>
</X>
</Customer>
</CustomerArray>
</Data>
</Message>
the wanted, correct result is produced:
<Message>
<Data>
<CustomerArray>
<Customer>
<Name>John</Name>
<Id>100</Id>
<Roles>
<Role>Manager</Role>
<Role>Architect</Role>
</Roles>
</Customer>
<somethingElse/>
<Customer>
<Name>Doe</Name>
<Id>102</Id>
<Roles>
<Role>Supervisor</Role>
<Role>Admin</Role>
</Roles>
</Customer>
</CustomerArray>
</Data>
</Message>
Do note that the solution by Borodin loses the somethingElse element.

How to sort min value using xslt?

below is my xml file called test.xml
<products>
<supplier>
<supplierid>1001</supplierid>
<totalprice>30</totalprice>
<items>
<item>Pen</item>
<price>10</price>
</items>
<items>
<item>Pencil</item>
<price>5</price>
</items>
<items>
<item>Bag</item>
<price>15</price>
</items>
</supplier>
<supplier>
<supplierid>1001</supplierid>
<totalprice>23</totalprice>
<items>
<item>Pencil</item>
<price>8</price>
</items>
<items>
<item>Pen</item>
<price>5</price>
</items>
<items>
<item>Bag</item>
<price>10</price>
</items>
</supplier>
<supplier>
<supplierid>1001</supplierid>
<totalprice>24</totalprice>
<items>
<item>Paper Box</item>
<price>7</price>
</items>
<items>
<item>Pen</item>
<price>4</price>
</items>
<items>
<item>Bag</item>
<price>13</price>
</items>
</supplier>
<supplier>
<supplierid>1002</supplierid>
<totalprice>26</totalprice>
<items>
<item>Sharpner Box</item>
<price>7</price>
</items>
<items>
<item>Pen</item>
<price>4</price>
</items>
<items>
<item>Bag</item>
<price>15</price>
</items>
</supplier>
</products>
I need to take output like below using xsl 1.0 or 2.0
<products>
<supplier>
<supplierid>1001</supplierid>
<totalprice>23</totalprice>
<items>
<item>Pencil</item>
<price>8</price>
</items>
<items>
<item>Pen</item>
<price>5</price>
</items>
<items>
<item>Bag</item>
<price>10</price>
</items>
</supplier>
<supplier>
<supplierid>1001</supplierid>
<totalprice>24</totalprice>
<items>
<item>Paper Box</item>
<price>7</price>
</items>
<items>
<item>Pen</item>
<price>4</price>
</items>
<items>
<item>Bag</item>
<price>13</price>
</items>
</supplier>
<supplier>
<supplierid>1002</supplierid>
<totalprice>26</totalprice>
<items>
<item>Sharpner Box</item>
<price>7</price>
</items>
<items>
<item>Pen</item>
<price>4</price>
</items>
<items>
<item>Bag</item>
<price>15</price>
</items>
</supplier>
</products>
Sort less price from "same suppliers" for example <supplierid>1001</supplierid>
Based on there sub nodes <items>
1001/23 and 1001/30 is same based on there <items><item>. we need to remove 1001/30 from the list. because it is high price
Based on the assumption that you like to sort suppliers based on their totalprice:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/">
<products>
<xsl:for-each select="products/supplier">
<xsl:sort select="totalprice" data-type="number" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</products>
</xsl:template>
</xsl:transform>
This will first sort based on supplierid and secondly based on their totalprice
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:sort select="supplierid"/> <!-- 1st level sorting -->
<xsl:sort select="totalprice"/> <!-- 2nd level sorting -->
<xsl:sort select="price"/> <!-- 3rd level sorting -->
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note: You can use a free online tool called Online XSLT Test Tool to test your xml/xslt files.

XSLT to remove elements from xml

I have a following xml.
<?xml version="1.0" encoding="windows-1252"?>
<Person>
<Header>
<Header>1</Header>
</Header>
<Details Id="2">
<First>GERRARD</First>
<Last>STEVE1 </Last>
</Details>
<Details Id="3">
<First>GERRARD</First>
<Last>STEVE2 </Last>
</Details>
<Details Id="3">
<First>GERRARD</First>
<Last>STEVE3 </Last>
</Details>
<Footer>
<Footer>liverpool</Footer>
</Footer>
</Person>
I need to delete the Details element and generate another xml which looks as follows
<?xml version="1.0" encoding="windows-1252"?>
<Person>
<Header>
<Header>1</Header>
</Header>
<Footer>
<Footer>liverpool</Footer>
</Footer>
</Person>
Thanks in advance.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Details"/>
</xsl:stylesheet>

Doing a pivot using XSLT

I have an xml file like this:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
I want to transform this using XSLT to get something like:
<root>
<items><status>good</status>
<name>one</name>
<name>two</name>
</items>
<items><status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items><status>ugly</status>
<name>four</name>
</items>
</root>
In other words, I get a list of items, each with a status, and I want to turn it into a list of statuses, each with a list of items.
My initial thought was to do apply-templates matching each status type in turn, but that means I have to know the complete list of statuses. Is there a better way to do it?
Thanks for any help.
Muench to the rescue!
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each select="/root/item/status[generate-id() = generate-id(key('muench',.)[1])]">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
Yes, this can be done in XSLT 1.0
<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="kStatByVal"
match="status" use="."/>
<!-- -->
<xsl:key name="kItemByStat"
match="item" use="status"/>
<!-- -->
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="/">
<top>
<xsl:for-each select=
"/*/*/status[generate-id()
=
generate-id(key('kStatByVal',.)[1])
]">
<items>
<status><xsl:value-of select="."/></status>
<xsl:for-each select="key('kItemByStat', .)">
<xsl:copy-of select="name"/>
</xsl:for-each>
</items>
</xsl:for-each>
</top>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the original XML document:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
The wanted result is produced:
<top>
<items>
<status>good</status>
<name>one</name>
<name>two</name>
</items>
<items>
<status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items>
<status>ugly</status>
<name>four</name>
</items>
</top>
Do note the use of:
The Muenchian method for grouping
The use of <xsl:key> and the key() function
It depends about your xslt engine. If you're using xslt 1.0 without any extension, then your approach is certainly the best.
On the other side, if you're allowed to use exslt (especially the node-set extension) or xslt 2.0, then you could do it in a more generic way:
Collect all the available statuses
Create a node-set from the obtained result
Iterating on this node set, create your pivot by filtering you status base on the current element in your iteration.
But before doing that, consider that it may be overkill if you only have a few set of statuses and that adding another status is quite rare.
In XSLT 2.0 you can replace the muenchian grouping by its standard grouping mechanism. Applied to the given answer the xslt would look like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="/root/item/status" group-by="key('muench', .)">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>