Today's challenge was grouping in XSLT 1.0. Found out there are something called keys and the Muenchian grouping.
Input XML:
<Items>
<Item>
<ID>1</ID>
<Name>A</Name>
<Country>Sweden</Country>
<Region>Småland</Region>
</Item>
<Item>
<ID>2</ID>
<Name>B</Name>
<Country>Sweden</Country>
<Region>Norrland</Region>
</Item>
<Item>
<ID>3</ID>
<Name>C</Name>
<Country>USA</Country>
<Region>Alaska</Region>
</Item>
<Item>
<ID>4</ID>
<Name>D</Name>
<Country>USA</Country>
<Region>Texas</Region>
</Item>
<Item>
<ID>5</ID>
<Name>E</Name>
<Country>Sweden</Country>
<Region>Norrland</Region>
</Item>
</Items>
I need to make thins XML into a better structure, and from this sample XML I't like to get items structured by country and region. Below is wanted result where country and region gets sorted as well:
<Items>
<Country Name="Sweden">
<Region Name="Norrland">
<Item>
<ID>2</ID>
<Name>B</Name>
</Item>
<Item>
<ID>5</ID>
<Name>E</Name>
</Item>
</Region>
<Region Name="Småland">
<Item>
<ID>1</ID>
<Name>A</Name>
</Item>
</Region>
</Country>
<Country Name="USA">
<Region Name="Alaska">
<Item>
<ID>3</ID>
<Name>C</Name>
</Item>
</Region>
<Region Name="Texas">
<Item>
<ID>4</ID>
<Name>D</Name>
</Item>
</Region>
</Country>
</Items>
EDIT:
I also want to make sure regions end up in their own country, even if there are duplicates. I edited the answer accordingly.
Also, I'd like to hint about xsltfiddle.liberty-development.net as an easy way of doing trial-and-error XSLT development...
Inspired by this article, I found a neat solution to this problem:
I have included comments for using it for single or double grouping, see comments in the code. Notice how I use first key (index) as input to the secon for-each loop:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="country" match="Item" use="Country" />
<xsl:key name="region" match="Item" use="concat(Region, '|', Country)" />
<xsl:template match="/Items">
<Items>
<xsl:for-each select="Item[generate-id(.) = generate-id(key('country', Country))]">
<xsl:sort select="Country" />
<xsl:variable name="_country" select="Country" />
<xsl:element name="Country">
<xsl:attribute name="Name"><xsl:value-of select="$_country" /></xsl:attribute>
<!-- single level grouping -->
<!--<xsl:apply-templates select="key('country', Country)" />-->
<!-- double grouping -->
<!-- START -->
<xsl:for-each select="key('country', Country)[generate-id(.) = generate-id(key('region', concat(Region, '|', Country)))]">
<xsl:sort select="Region" />
<xsl:variable name="_region" select="Region" />
<xsl:element name="Region">
<xsl:attribute name="Name"><xsl:value-of select="$_region" /></xsl:attribute>
<xsl:apply-templates select="key('region', concat(Region, '|', Country))" />
</xsl:element>
</xsl:for-each>
<!-- END -->
</xsl:element>
</xsl:for-each>
</Items>
</xsl:template>
<xsl:template match="Item">
<xsl:element name="Item">
<xsl:element name="ID"><xsl:value-of select="ID" /></xsl:element>
<xsl:element name="Name"><xsl:value-of select="Name" /></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Related
I have a xml with multiple items, which are Multilevel BOM (in the example 2 items, with both three levels). I need this to convert to xml with each record only the father and the Childs (first record of each item has no father).
We use XSLT 1.0 and we can't use Muenchian grouping because the processor in use don't know the key function.
I hope someone can help me out.
XML example:
<Items>
<level01>
<itemcode>L100</itemcode>
<quantity>1</quantity>
<whs>30</whs>
<level02>
<row>
<itemcode>L201</itemcode>
<quantity>5</quantity>
<whs>02</whs>
</row>
<row>
<itemcode>L202</itemcode>
<quantity>8</quantity>
<whs>01</whs>
</row>
<row>
<itemcode>L203</itemcode>
<quantity>1</quantity>
<whs>01</whs>
<level03>
<row>
<itemcode>L301</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</row>
<row>
<itemcode>L302</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</row>
</level03>
</row>
</level02>
</level01>
<level01>
<itemcode>M100</itemcode>
<quantity>1</quantity>
<whs>30</whs>
<level02>
<row>
<itemcode>M201</itemcode>
<quantity>3</quantity>
<whs>01</whs>
</row>
<row>
<itemcode>M202</itemcode>
<quantity>2</quantity>
<whs>01</whs>
</row>
<row>
<itemcode>M203</itemcode>
<quantity>2</quantity>
<whs>01</whs>
<level03>
<row>
<itemcode>M301</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</row>
<row>
<itemcode>M302</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</row>
</level03>
</row>
</level02>
</level01>
</Items>
desired result:
<?xml version="1.0" encoding="UTF-8"?>
<Items>
<Item>
<itemcode>L100</itemcode>
<quantity>1</quantity>
<whs>02</whs>
</Item>
<Item>
<father>L100</father>
<itemcode>L201</itemcode>
<quantity>5</quantity>
<whs>02</whs>
</Item>
<item>
<father>L100</father>
<itemcode>L202</itemcode>
<quantity>8</quantity>
<whs>01</whs>
</item>
<item>
<father>L100</father>
<itemcode>L203</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</item>
<item>
<father>L203</father>
<itemcode>L301</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</item>
<item>
<father>L203</father>
<itemcode>L302</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</item>
</Items>
<Items>
<item>
<itemcode>M100</itemcode>
<quantity>1</quantity>
<whs>02</whs>
</item>
<item>
<father>M100</father>
<itemcode>M201</itemcode>
<quantity>3</quantity>
<whs>01</whs>
</item>
<item>
<father>M100</father>
<itemcode>M202</itemcode>
<quantity>2</quantity>
<whs>01</whs>
</item>
<item>
<father>M100</father>
<itemcode>M203</itemcode>
<quantity>2</quantity>
<whs>01</whs>
</item>
<item>
<father>M203</father>
<itemcode>M301</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</item>
<item>
<father>M203</father>
<itemcode>M302</itemcode>
<quantity>1</quantity>
<whs>01</whs>
</item>
</items>
<?bpc.pltype.out bpm.pltype=xml?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:b1e="urn:com.sap.b1i.sim:b1event" xmlns:b1ie="urn:com.sap.b1i.sim:b1ievent" xmlns:b1im="urn:com.sap.b1i.sim:b1imessage" xmlns:bfa="urn:com.sap.b1i.bizprocessor:bizatoms" xmlns:exslt="http://exslt.org/common"
xmlns:jdbc="urn:com.sap.b1i.adapter:jdbcadapter" xmlns:js="com.sap.b1i.bpc_tools.Javascript" xmlns:rev="urn:com.sap.b1i.adapter:revaadapter" xmlns:rfc="urn:sap-com:document:sap:rfc:functions" xmlns:sim="urn:com.sap.b1i.sim:entity" xmlns:utils="com.sap.b1i.bpc_tools.Utilities"
xmlns:vpf="urn:com.sap.b1i.vplatform:entity" xmlns:xci="urn:com.sap.b1i.xcellerator:intdoc" version="1.0" exclude-result-prefixes="b1e b1ie b1im bfa jdbc js rfc utils xci vpf exslt sim rev" b1e:force="" b1ie:force="" b1im:force="" bfa:force="" jdbc:force=""
js:force="" rfc:force="" utils:force="" xci:force="" vpf:force="" exslt:force="" sim:force="" rev:force="">
<?prodver 1.0.0?>
<!--<xsl:include href="../../com.sap.b1i.dev.repository/IDE/init.xsl" />-->
<xsl:variable name="msg" select="/vpf:Msg/vpf:Body/vpf:Payload[./#Role='S']" />
<xsl:template match="/">
<Msg xmlns="urn:com.sap.b1i.vplatform:entity">
<xsl:copy-of select="/vpf:Msg/#*" />
<xsl:copy-of select="/vpf:Msg/vpf:Header" />
<Body>
<xsl:copy-of select="/vpf:Msg/vpf:Body/*" />
<Payload Role="X" id="999999">
<xsl:call-template name="transform" />
</Payload>
</Body>
</Msg>
</xsl:template>
<xsl:template name="transform">
This is the space we usually add our code
</xsl:template>
</xsl:stylesheet>
As I mentioned in the comments, I see no need for grouping here. The only complication is the irregularity of your input (no row wrapper at the top level) and of the output (no father element for the items with no parent). Otherwise this could be even simpler.
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:template match="/Items">
<xsl:copy>
<xsl:apply-templates select="level01"/>
</xsl:copy>
</xsl:template>
<xsl:template match="level01">
<Item>
<xsl:copy-of select="itemcode|quantity|whs"/>
</Item>
<xsl:apply-templates select="*/row"/>
</xsl:template>
<xsl:template match="row">
<Item>
<father>
<xsl:value-of select="(ancestor::*/itemcode)[last()]"/>
</father>
<xsl:copy-of select="itemcode|quantity|whs"/>
</Item>
<xsl:apply-templates select="*/row"/>
</xsl:template>
</xsl:stylesheet>
Do note that the output is somewhat different from the one in your question: the Items element is the root element of the entire output tree. Without this, you would receive an XML fragment instead of a well-formed XML document.
If you want an additional wrapper for each main branch, change the template matching level01 to:
<xsl:template match="level01">
<Branch>
<Item>
<xsl:copy-of select="itemcode|quantity|whs"/>
</Item>
<xsl:apply-templates select="*/row"/>
</Branch>
</xsl:template>
I want to find distinct values across each node. but when i use muenchian method it will select distinct values only at top level.
Currently i am getting output as:
Bag 20
Tray 30
But i want to group type and quantity for each item
<!--for 1st item-->
Bag 20
Tray 30
<!--for 2nd item-->
Bag 20
Box 20
Tray 30
I am doing some research to make it work but did not succeed.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
<Items>
<Item>
<name>A</name>
<type>Bag</type>
<input quantity="20"/>
</Item>
<Item>
<name>A</name>
<type>Bag</type>
<input quantity="20"/>
</Item>
<Item>
<name>B</name>
<type>Metal</type>
<input quantity="20"/>
</Item>
<Item>
<name>A</name>
<type>Tray</type>
<input quantity="30"/>
</Item>
</Items>
<Items>
<Item>
<name>A</name>
<type>Bag</type>
<input quantity="20"/>
</Item>
<Item>
<name>A</name>
<type>Box</type>
<input quantity="20"/>
</Item>
<Item>
<name>B</name>
<type>Metal</type>
<input quantity="20"/>
</Item>
<Item>
<name>A</name>
<type>Tray</type>
<input quantity="30"/>
</Item>
</Items>
</Test>
Code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:date="http://exslt.org/dates-and-times"
xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="exsl"
extension-element-prefixes="date"
>
<xsl:key name="item-key" match="Items/Item[name='A']" use="input/#quantity" />
<xsl:template match="/" name="Barcode">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="ManufacturLabelSize-first" page-height="297mm" page-width="210mm" margin-top="25.4mm" margin-right="25.4mm" margin-left="25.4mm" margin-bottom="25.4mm">
<fo:region-body margin-top="15mm" />
<fo:region-before />
<fo:region-after />
</fo:simple-page-master>
<fo:simple-page-master master-name="ManufacturLabelSize-rest" page-height="297mm" page-width="210mm" margin-top="25.4mm" margin-right="25.4mm" margin-left="25.4mm" margin-bottom="25.4mm">
<fo:region-body margin-top="15mm"/>
<fo:region-before />
<fo:region-after />
</fo:simple-page-master>
<fo:page-sequence-master master-name="ManufacturLabelSize-portrait">
<fo:repeatable-page-master-alternatives>
<fo:conditional-page-master-reference master-reference="ManufacturLabelSize-first"
page-position="first"/>
<fo:conditional-page-master-reference master-reference="ManufacturLabelSize-rest"
page-position="rest"/>
</fo:repeatable-page-master-alternatives>
</fo:page-sequence-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="ManufacturLabelSize-portrait" id="pSeqID">
<fo:flow flow-name="xsl-region-body">
<fo:table >
<fo:table-body border="solid" border-width="0.5pt">
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:for-each select="Test">
<xsl:for-each select="Items">
<xsl:for-each select="Item[name='A'][count(. | key('item-key',input/#quantity)[1])=1]">
<fo:block>
<xsl:value-of select="type"/> <fo:inline color="white">XXX</fo:inline><xsl:value-of select="input/#quantity"/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
Appreciate your help!
If - as it seems - you want to find distinct Items whose name is A by their type separately for each Items, then you need to include the parent Items' id in the key. Here's a simplified example:
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="k" match="Item[name='A']" use="concat(type, '|', generate-id(..))"/>
<xsl:template match="/Test">
<root>
<xsl:for-each select="Items">
<xsl:comment>
<xsl:value-of select="position()" />
</xsl:comment>
<xsl:for-each select="Item[name='A'][count(. | key('k', concat(type, '|', generate-id(..)))[1]) = 1]">
<item>
<type>
<xsl:value-of select="type"/>
</type>
<quantity>
<xsl:value-of select="input/#quantity"/>
</quantity>
</item>
</xsl:for-each>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--1-->
<item>
<type>Bag</type>
<quantity>20</quantity>
</item>
<item>
<type>Tray</type>
<quantity>30</quantity>
</item>
<!--2-->
<item>
<type>Bag</type>
<quantity>20</quantity>
</item>
<item>
<type>Box</type>
<quantity>20</quantity>
</item>
<item>
<type>Tray</type>
<quantity>30</quantity>
</item>
</root>
I am trying to remove both the duplicate records in an XML
I already can remove the second occurrence but I need to remove both records in this case.
This is the XSLT mapping that I have
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="/ZTABLE/Record">
<ZTABLE>
<Record>
<xsl:apply-templates select="item[not(ID=preceding-sibling::item/ID)]" />
</Record>
</ZTABLE>
</xsl:template>
</xsl:transform>
The input XML is:
<ZTABLE>
<Record>
<item>
<ID>400400</ID>
</item>
<item>
<ID>100100</ID>
</item>
<item>
<ID>200200</ID>
</item>
<item>
<ID>300300</ID>
</item>
<item>
<ID>400400</ID>
</item>
</Record>
</ZTABLE>
The expected output is
<ZTABLE>
<Record>
<item>
<ID>100100</ID>
</item>
<item>
<ID>200200</ID>
</item>
<item>
<ID>300300</ID>
</Record>
</ZTABLE>
Try it this way:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="item" match="item" use="ID" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[count(key('item', ID)) > 1]"/>
</xsl:stylesheet>
For explanation, read about Muenchian grouping.
I need to convert a name value pair into XML. I'm able to generate an XML, but the element name should be grouped and it should not be duplicated. Please see below. The FieldValue element contains 2 OrderItem values in the Detail node. If the FieldValue with OrderItem repeats, then the result should be grouped into one OrderItem node. Please help.
Source XML:
<SC>
<Header>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderHeader</FieldValue>
</Record>
<Record>
<FieldName>Order</FieldName>
<FieldValue>1234</FieldValue>
</Record>
</Header>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>1</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>10</FieldValue>
</Record>
</Detail>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>2</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>20</FieldValue>
</Record>
</Detail>
</SC>
Target XML:
<Order>
<OrderItem>
<Item>
<Item>1</Item>
<Qty>10</Qty>
</Item>
<Item>
<Item>2</Item>
<Qty>20</Qty>
</Item>
</OrderItem>
</Order>
XSLT:
<xsl:template match="#*|node()">
<Order>
<xsl:for-each select="Detail">
<Item>
<xsl:apply-templates select="Record[position()>1]"/>
</Item>
</xsl:for-each>
</Order>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
The grouping can be done as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="SC">
<Order>
<xsl:for-each-group select="Detail" group-by="Record[1]/FieldValue">
<xsl:element name="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:for-each-group>
</Order>
</xsl:template>
<xsl:template match="Detail">
<Item>
<xsl:apply-templates select="Record[position() gt 1]"/>
</Item>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It appears that you are trying to define two XSLT templates, when one should be sufficient. You want to match on the root and then that you want to iterate over each SC/Detail.
Then, you want to take the FieldValue of the sibling of the FieldName node that is 'Item' (for item value) and 'Qty' (for quantity value), but only those listed under 'Record'.
Note: You have specified a doubly-nested <Item> in your transformed output and this solution reflects that requirement.
This XSLT should do what you are requesting:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="SC/Detail">
<Order>
<OrderItem>
<Item>
<Item>
<xsl:value-of select="Record[FieldName[text()='Item']]/FieldValue" />
</Item>
<Qty>
<xsl:value-of select="Record[FieldName[text()='Qty']]/FieldValue" />
</Qty>
</Item>
</OrderItem>
</Order>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'm using the DataMapper component in MuleStudio. I want to transform data that I have in this format
<item type="1" name="data">
<children name="action">
<values>login.01</values>
<children>
</item>
to something like this
<item>
<action>login.01</action>
</item>
Is this possible through Mule? Or will I need to make a custom Java parser?
Assuming the source is XML, no need to use DataMapper: a simple XSL-T transformer will do the trick:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="item">
<item>
<xsl:apply-templates />
</item>
</xsl:template>
<xsl:template match="children">
<xsl:element name="{#name}">
<xsl:apply-templates select="values/text()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>