Flatten XML and Access Previous Node - xslt-1.0

I'm trying to output data in the <item> node and also the previous <item> node. I can access the previous <item> node when in the same <group> but once it moves to next group I loose the previous.
<?xml version="1.0"?>
<?xml-stylesheet href="flatten.xslt" type="text/xsl"?>
<data>
<group id="1">
<title>Group Title 1</title>
<category>A</category>
<items>
<item id="11">
<title>Title 11</title>
</item>
<item id="12">
<title>Title 12</title>
</item>
<item id="13">
<title>Title 13</title>
</item>
<item id="14">
<title>Title 14</title>
</item>
<item id="16">
<title>Title 15</title>
</item>
<item id="17">
<title>Title 16</title>
</item>
</items>
</group>
<group id="2">
<title>Group Title 2</title>
<category>A</category>
<items>
<item id="21">
<title>Title 21</title>
</item>
<item id="23">
<title>Title 22</title>
</item>
<item id="22">
<title>Title 23</title>
</item>
<item id="24">
<title>Title 24</title>
</item>
<item id="25">
<title>Title 25</title>
</item>
<item id="27">
<title>Title 27</title>
</item>
</items>
</group>
<group id="3">
<title>Group Title 3</title>
<category>B</category>
<items>
<item id="31">
<title>Title 31</title>
</item>
<item id="32">
<title>Title 32</title>
</item>
<item id="33">
<title>Title 33</title>
</item>
<item id="34">
<title>Title 34</title>
</item>
<item id="36">
<title>Title 36</title>
</item>
<item id="37">
<title>Title 37</title>
</item>
</items>
</group>
</data>
I'm looking for this:
<ul>
<li>
<h1>Item Title 11</h1>
<h2>Previous: </h2>
</li>
<li>
<h1>Item Title 12</h1>
<h2>Previous: Item Title 11</h2>
</li>
<li>
<h1>Item Title 13</h1>
<h2>Previous: Item Title 12</h2>
</li>
<li>
<h1>Item Title 14</h1>
<h2>Previous: Item Title 13</h2>
</li>
<li>
<h1>Item Title 15</h1>
<h2>Previous: Item Title 14</h2>
</li>
<li>
<h1>Item Title 16</h1>
<h2>Previous: Item Title 15</h2>
</li>
<li>
<h1>Item Title 21</h1>
<h2>Previous: Item Title 16</h2>
</li>
<li>
<h1>Item Title 22</h1>
<h2>Previous: Item Title 21</h2>
</li>
<li>
<h1>Item Title 23</h1>
<h2>Previous: Item Title 22</h2>
</li>
<li>
<h1>Item Title 24</h1>
<h2>Previous: Item Title 23</h2>
</li>
</ul>

OK, I came up with this:
Create a new variable pulling all <item> nodes out into flat structure.
Loop through variable
If you know a better way please share.
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title>XSLT Flatten</title>
</head>
<body>
<xsl:variable name="flat-items">
<xsl:element name="items">
<xsl:for-each select="data/group/items/item">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:element>
</xsl:variable>
<p>
<h2>Total Items: <xsl:value-of select="count(exsl:node-set($flat-items)/items/item)"/></h2>
</p>
<ul>
<xsl:for-each select="exsl:node-set($flat-items)/items/item">
<li>
<h2><xsl:value-of select="title"/> (<xsl:value-of select="#id"/>)</h2>
<h3>Previous: <xsl:value-of select="./preceding-sibling::item[1]/title"/> (<xsl:value-of select="./preceding-sibling::item[1]/#id"/>)
</h3>
</li>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Related

sort an XML file into a tree structure by XSL 1.0

I want to convert an XML file with scattered element to an tree struct XML file.
if the 'identifier' of a menu is exist in another menu's items in input XML file, the menu context should be copied into the 'menu_list' element in output XML file.
Can any one please help me how do I achieve this using XSL1.0.
Input XML file:
<input>
<menu>
<identifier>1</identifier>
<items>
<item>2</item>
<item>3</item>
</items>
</menu>
<menu>
<identifier>2</identifier>
<items>
<item>21</item>
<item>22</item>
</items>
</menu>
<menu>
<identifier>3</identifier>
<items>
<item>31</item>
<item>32</item>
</items>
</menu>
<menu>
<identifier>21</identifier>
<items>
<item>211</item>
<item>212</item>
</items>
</menu>
<menu>
<identifier>22</identifier>
<items>
<item>221</item>
<item>222</item>
</items>
</menu>
<menu>
<identifier>31</identifier>
<items>
<item>311</item>
<item>312</item>
</items>
</menu>
<menu>
<identifier>32</identifier>
<items>
<item>321</item>
<item>322</item>
</items>
</menu>
</input>
Output XML file:
<input>
<menu>
<identifier>1</identifier>
<items>
<item>2</item>
<item>3</item>
</items>
<menu_list>
<menu>
<identifier>2</identifier>
<items>
<item>21</item>
<item>22</item>
</items>
<menu_list>
<menu>
<identifier>21</identifier>
<items>
<item>211</item>
<item>212</item>
</items>
</menu>
<menu>
<identifier>22</identifier>
<items>
<item>221</item>
<item>222</item>
</items>
</menu>
</menu_list>
</menu>
<menu>
<identifier>3</identifier>
<items>
<item>31</item>
<item>32</item>
</items>
<menu_list>
<menu>
<identifier>31</identifier>
<items>
<item>311</item>
<item>312</item>
</items>
</menu>
<menu>
<identifier>32</identifier>
<items>
<item>321</item>
<item>322</item>
</items>
</menu>
</menu_list>
</menu>
</menu_list>
</menu>
</input>
Try it this way:
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:key name="children" match="menu" use="identifier" />
<xsl:key name="parent" match="menu" use="items/item" />
<xsl:template match="/input">
<xsl:copy>
<xsl:apply-templates select="menu[not(key('parent', identifier))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="menu">
<xsl:copy>
<xsl:copy-of select="*"/>
<xsl:variable name="children" select="key('children', items/item)"/>
<xsl:if test="$children">
<menu_list>
<xsl:apply-templates select="$children"/>
</menu_list>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Transfer multilevel element XML to one level

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>

Find distinct values across each node in xslt1.0

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>

XSLT 1.0 grouping on one or multiple levels

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>

XPATH: Filtering (rather than selecting nodes), preserving hierarachy

Given the following XML (with many hierarchy levels), I need to return the nodes after filtering all nodes with Hide"True" attribute and a certain name. Note that hide could be at any hierarchy level:
<items>
<item id="1" >
<item id="2" />
<item id="3" />
<item id="4" hide="true"/>
</item>
<item id="5" hide="true">
<item id="6" >
<item id="7">
<item id="8" hide="true"/>
<item id="9"/>
</item>
</item>
</items>
I would need to get this back:
<items>
<item id="1"">
<item id="2" />
<item id="3" />
</item>
<item id="6" >
<item id="7">
<item id="9"/>
</item>
</item>
</items>
The 'filtered' XML is then assigned to a treeview, so I need to preserve the hierarchical nature of the nodes. I have tried: "items//item[#hide='false' or not(#hide)]", but this returns a flattened (as well as duplicative) data, repeating nodes at the lower level pushed up to the top level as follows. Is there a way to use Xpath to do what I want? I understand that I can Xslt the data first, then display it, but it just seems that there is got to be an easier way. I am using c#/.net4.0 MSXML
<items>
<item id="1"">
<item id="2" />
<item id="3" />
</item>
<item id="6" >
<item id="7">
<item id="9"/>
</item>
</item>
<item id="2" />
<item id="3" />
<item id="7">
<item id="9"/>
</item>
<item id="9"/>
</items>