Group XML using xslt based on Header or Document Entry - xslt

How to group the XML using xlst for the below xml code.
Following are the input XML: I'm using this input xml in order to import into an ERP system.
<row>
<Ref>1</Ref>
<Code>IT001</Code>
<Qty>11</Qty>
</row>
<row>
<Ref>1</Ref>
<Code>IT002</Code>
<Qty>21</Qty>
</row>
<row>
<Ref>2</Ref>
<Code>IT002</Code>
<Qty>12</Qty>
</row>
following are the Output or expected XML: ERP system generally accepts one line per document and it's siblings of document lines. Thus the following desired output is required.
<Document>
<Ref>1</Ref><Lines>
<Item>
<Code>IT001</Code>
<Qty>11</Qty>
</Item>
<Item>
<Code>IT002</Code>
<Qty>21</Qty>
</Item>
</Lines>
</Document>
<Document>
<OrderRef>2</OrderRef>
<Lines>
<Item>
<Code>IT002</Code>
<Qty>12</Qty>
</Item>
</Lines>
</Document>

Let's start from a correction of your source XML:
There must be only one root element (I called it Root)
and inside it there can be multiple (e.g. Document) elements.
The template performing the transformation should match the
Root element.
As I see from your expected output, you want to group Document
elements on DocumentRef, so in the script below there is
corresponding xsl:for-each-group instruction.
For each such group there should be Document output element
and inside it Ref element with the value of the current
grouping key.
Then there should be a Lines element and inside it, for each
member of the current group, there should be Item element
and inside it 2 child elements with required values from the
source element.
So the whole script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Root">
<xsl:copy>
<xsl:for-each-group select="Document" group-by="DocumentRef">
<Document>
<Ref><xsl:value-of select="current-grouping-key()"/></Ref>
<Lines>
<xsl:for-each select="current-group()">
<Item>
<ItemCode><xsl:value-of select="DocumentLines/ItemCode"/></ItemCode>
<Qty><xsl:value-of select="DocumentLines/ItemQty"/></Qty>
</Item>
</xsl:for-each>
</Lines>
</Document>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
For a working example, including corrected input, see http://xsltransform.net/eieE3PX
XSLT 1.0 version
In XSLT 1.0 it is also possible, using Muenchian Grouping:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="groups" match="row" use="OrderRef"/>
<xsl:template match="Payload">
<xsl:copy>
<xsl:apply-templates select="row[generate-id() = generate-id(
key('groups', OrderRef)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<Document>
<Ref><xsl:value-of select="OrderRef"/></Ref>
<CardCode><xsl:value-of select="CustomerCode"/></CardCode>
<Lines>
<xsl:for-each select="key('groups', OrderRef)">
<Item>
<ItemCode><xsl:value-of select="ItemCode"/></ItemCode>
<Qty><xsl:value-of select="Quantity"/></Qty>
</Item>
</xsl:for-each>
</Lines>
</Document>
</xsl:template>
</xsl:transform>
The initial step is to create an xsl:key. Each key must have a name to
refer to it later. match defines which elements to include in this key
and use defines the grouping key.
Then look at:
<xsl:apply-templates select="row[generate-id() = generate-id(
key('groups', OrderRef)[1])]"/>
It "calls an action" (in this case xsl:apply-templates) on
the first object in each group.
The rest of code from my initial solution has been moved to
a template matching row.
The initial part of it performs actions for the current group
(generate output Document, Ref, CardCode and Lines
elements).
The rest (xsl:for-each) performs actions for individual
members of the current group, generating Item, ItemCode
and Qty elements.
I updated your solution in xsltransform, so you can view
it on http://xsltransform.net/jxWYjW2/2
Note that I changed the XSLT engine to Saxon 6.5.5. You can also
switch it to Xalan, although then you loose indentation.
If this approach is new to you, maybe you should read a little about
generate-id and Muenchian Grouping itself. Even StackOverflow contains
a lot of posts about these issues.

Related

Merging two different parent nodes content based on the same child value (ID) - XSL Transform

I couldn't find this specific scenario (guess I can`t describe it right in english).
The input XML looks something like this (multiple items inside "listOne" and "listTwo" of course:
<root>
<listOne>
<listOneItem>
<ID>1</ID> // Always Unique
<SKU>ABC</SKU>
</listOneItem>
</listOne>
<listTwo>
<listTwoItem>
<ID>1</ID> / Identical to node in listOneItem, but unique (no scenario of a third element with such ID)
<STOCK>10</STOCK>
</listTwoItem>
</listTwo>
</root>
The desired output should merge the "listOne" and "listTwo" items based on the same child "ID" nodes value:
<root>
<finalItemsList>
<item>
<ID>1</ID>
<SKU>ABC</SKU>
<STOCK>10</STOCK>
</item>
</finalItemsList>
</root>
XSLT has a built-in key mechanism 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:key name="list2" match="listTwoItem" use="ID" />
<xsl:template match="/root">
<root>
<finalItemsList>
<xsl:for-each select="listOne/listOneItem">
<item>
<xsl:copy-of select="*"/>
<xsl:copy-of select="key('list2', ID)/STOCK"/>
</item>
</xsl:for-each>
</finalItemsList>
</root>
</xsl:template>
</xsl:stylesheet>

XSL to Parse and Re-order XML Nodes

I need assistance with creating a XSL stylesheet to parse data and re-order based on values within certain nodes. My original XML is being exported by a roster program in a undesirable structure which is causing issues when converting to JSON.
This is a Fire Department roster that will be converted into JSON to be processed by Station Status Boards. I'm looking to format the XML so that when converted into JSON each Station has a crew list. I've attempted to create a XSL without success. I have zero background in XSL (Fire Fighter).
Section of Original XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<RscPayrollIDCh>12345678</RscPayrollIDCh>
<RscEmployeeIDCh>12345678</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>B19</StationAbrvCh>
<PUnitAbrvCh>BAT19</PUnitAbrvCh>
<PosJobAbrvCh>BC-S</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>12345</RscPayrollIDCh>
<RscEmployeeIDCh>12345</RscEmployeeIDCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>CAPT</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>123456</RscPayrollIDCh>
<RscEmployeeIDCh>123456</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>ENG</PosJobAbrvCh>
</Record>
</Records>
</Data>
I would like to format the XML so that it looks something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<StationAbrvCh>B19</StationAbrvCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
</Record>
<Record>
<StationAbrvCh>S15</StationAbrvCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
</Record>
</Records>
I would like my roster to list each crew member under the Station they are assigned to for the day.
If you are using XSLT 1.0, Muenchian grouping is the best approach to achieve it as below:
<?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" />
<xsl:key name="groups" match="/Data/Records/Record" use="StationAbrvCh" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Data/Records">
<xsl:copy>
<xsl:for-each select="Record[generate-id() = generate-id(key('groups', StationAbrvCh)[1])]">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="StationAbrvCh" /></StationAbrvCh>
<xsl:for-each select="key('groups', StationAbrvCh)">
<RscMasterNameCh><xsl:value-of select="RscMasterNameCh" /></RscMasterNameCh>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See demo here: https://xsltfiddle.liberty-development.net/pPzifpM
Using XSLT 2.0 it is quite easy.
In the template maching Records, you should use for-each-group
selecting Record elements and grouping them by StationAbrvCh.
Within each group you should:
Generate StationAbrvCh element, filled with the current grouping key
(also StationAbrvCh).
Run a for-each loop for the current group, copying to the output the
current RscMasterNameCh.
The script should contain also the identity template.
Below you have an example script:
<?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"/>
<xsl:template match="Records">
<xsl:copy>
<xsl:for-each-group select="Record" group-by="StationAbrvCh">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="current-grouping-key()"/></StationAbrvCh>
<xsl:for-each select="current-group()">
<xsl:sequence select="RscMasterNameCh"/>
</xsl:for-each>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
Maybe, to fully understand each detail of the above solution,
you should search the Web for description of for-each-group
and related functions (current-grouping-key() and current-group).

Transform to output xml based on the value of an element in the input xml

I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
XSLT 2.0
<xsl:stylesheet version="2.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="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>

How to restrict which nodes produce output in stylesheet

I am working on a transform. The goal is to transform nodes into key/value pairs. Found a great stylesheet recommendation on this forum but I could use some help to tweak it a bit. For any node that has no children, the node name should become the value of <name> and the value should become the value of <value>. The source document may have some hierarchical structure to it, but I want to ignore that and only return the bottom nodes, transformed of course.
Here is my source data:
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<Technical_Spec__c>
<Id>a0e30000000vFmbAAE</Id>
<F247__c>4.0</F247__c>
<F248__c xsi:nil="true"/>
<F273__c>Bronx</F273__c>
...
</Technical_Spec__c>
</objects>
Here is the stylesheet:
<?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"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[count(*) = 0]">
<item>
<name>
<xsl:value-of select="name(.)" />
</name>
<value>
<xsl:value-of select="." />
</value>
</item>
</xsl:template>
<xsl:template match="*[count(*) > 0]">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
</xsl:stylesheet>
DESIRED OUTPUT - The stylesheet should transform these nodes to key value pairs like this:
<items>
<item>
<name>F247__c</name>
<value>4.0</value>
</item>
<item>
<name>F248__c</name>
<value></value>
</item>
<item>
<name>F273__c</name>
<value>Bronx</value>
</item>
...
</items>
CURRENT OUTPUT - But it creates nested 'items' elements like this:
<items>
<items>
<item><name></name><value></value></item>
...
</items>
</items>
I understand (I think) that it is matching all the parent nodes including the top node 'objects' and nesting the 'matches count 0' template. So I tried altering the matches attribute to exclude 'objects' and start at 'Technical_Spec__c' like this (just the template lines):
<xsl:template match="objects/Technical_Spec__c/*">
<xsl:template match="*[count(*) = 0]">
<xsl:template match="objects/*[count(*) > 0]">
In my mind this says "First (master) template only matches nodes with parents 'objects/Tech_Spec'. Second (inner) template matches any node with no children. Third (outer) template matches nodes with parent 'objects' " - which should limit me to one .
OUTPUT AFTER ALTERING MATCH - Here is what I get:
<?xml version="1.0" encoding="UTF-8"?>
- <items xmlns=""><?xml version="1.0"?>
<item><name>Id</name><value>a0e30000000vFmbAAE</value></item>
<item><name>F247__c</name><value>4.0</value></item>
...
</items>
The extra <items> block is gone but there is an extra <?xml> block stuck in the middle so it's not recognized as valid xml anymore.
Any ideas? Why the extra <?xml>; How to restrict template to particular parts of the tree?
Through a great deal of trial and error, I stumbled on the following solution: I added a root anchor to the third template match criteria.
Instead of match="*[count(*) > 0]", I now have /*[count(*) > 0]. This appears to eliminate the outer <items> element. If anyone can tell me why, I'd appreciate it. Why would this be different than /objects/*[count(*) > 0] ?
I do think Dimitre is right about the processor (which is IBM Cast Iron) so I did open a ticket. I tested the same stylesheet from above on an online XSLT tester and did not get the extra <?xml ?> tag.
<?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"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[count(*) = 0]">
<item>
<name>
<xsl:value-of select="name(.)" />
</name>
<value>
<xsl:value-of select="." />
</value>
</item>
</xsl:template>
<xsl:template match="/*[count(*) > 0]">
<items>
<xsl:apply-templates/>
</items>

XSLT 1.0 text nodes printing by default

I have looked at XSL xsl:template match="/" but the match pattern that triggered my question is not mentioned there.
I have a rather complex XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<MATERIAL_DATA>
<LOG>
<USER>Peter</USER>
<DATE>2011-02-18</DATE>
<MATERIALS>
<item>
<MATNR>636207</MATNR>
<TEXTS>
<item>
<TEXT>granola bar 40gx24</TEXT>
</item>
</TEXTS>
<PRICES>
<item>
<MATNR>636207</MATNR>
<COST>125.78</COST>
</item>
</PRICES>
<SALESPRICES>
<item>
<B01>
<MATNR>636207</MATNR>
<CURR>CZK</CURR>
<DATBI>9999-12-31</DATBI>
<DATAB>2010-10-05</DATAB>
</B01>
<B02>
<item>
<PRICE>477.60</PRICE>
<KUNNR>234567</KUNNR>
</item>
</B02>
</item>
</SALESPRICES>
</item>
</MATERIALS>
</LOG>
</MATERIAL_DATA>
Now if I apply the following XSLT, my output looks correct:
<?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" indent="yes" encoding="UTF-8"/>
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I get the output:
<?xml version="1.0" encoding="UTF-8"?>
<Mi>234567</Mi>
But if I apply the XSLT:
<?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" indent="yes" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:element name="MenuItems">
<xsl:apply-templates select="LOG/MATERIALS/item/SALESPRICES/item"/>
</xsl:element>
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
the output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems>
636207
CZK
9999-12-31
2010-10-05
<Mi>234567</Mi>
</MenuItems>
All values from the element <B01> are in the output! But why - I am not matching <B01>!?
How does
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
make the output come out correctly? All I am doing with this is match all nodes or attributes and apply-templates to everything or all attributes.
But in my opinion it should not make a difference to when I exactly match <B01>!
Why is this happening?
XSLT includes the following default templates (among others):
<!-- applies to both element nodes and the root node -->
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
<!-- copies values of text and attribute nodes through -->
<xsl:template match="text()|#*">
<xsl:value-of select="."/>
</xsl:template>
In your first stylesheet you're implicitly matching all text nodes with node(), thus overriding the default action. Then, in the B2 template, you output your target value and apply no further templates, which stops processing.
In the second stylesheet, you explicitly apply templates to all children of LOG/MATERIALS/item/SALESPRICES/item, causing the default templates to process the nodes you don't explicitly handle. Because you explicitly handle B2 without applying templates to its children, the default templates are never invoked for those nodes. But the default templates are applied to the children of B1.
Adding the following template to your second stylesheet would override the default action for text nodes:
<xsl:template match="text()|#*"></xsl:template>
With the following result:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems><Mi>234567</Mi></MenuItems>
More:
http://www.w3.org/TR/xslt#built-in-rule
Looks like you are running into the built in template rules.
Specifically the text rule - this will copy text nodes if not overridden.