Generate Node XSLT based on unique value - xslt

Have the following input
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
Would now like to have the following output
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a9</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<type>3</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a9</vaue>
</list>
<type>3</type>
</item>
For each item present with type as 1 would like to create a new item if there if the list contains a name which is "a" . However would like to avoid duplicates.
Was able to achieve this output already with a two step process.. first to create items with duplicates and then a separate key match to remove the duplicates.
Is there a way we can achieve this in one step instead of to create duplicates and then filter duplicates

This is just a grouping problem. This stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="item-by-vaue" match="item[type=1]" use="list[name='a']/vaue"/>
<xsl:template match="node()|#*" name="copy">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
<xsl:for-each
select="item[type=1]
[count(.|key('item-by-vaue',list[name='a']/vaue)[1])=1]">
<item>
<xsl:apply-templates select="list[name='a']"/>
<type>3</type>
</item>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Using your input sample it outputs:
<root>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>1</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<list>
<name>b</name>
<vaue>b5</vaue>
</list>
<list>
<name>c</name>
<vaue>c9</vaue>
</list>
<list>
<name>d</name>
<vaue>d66</vaue>
</list>
<type>2</type>
</item>
<item>
<list>
<name>a</name>
<vaue>a1</vaue>
</list>
<type>3</type>
</item>
</root>
Do note: your input sample doesn't match your expected output by the rules you've stated: there is no vaue element with 'a9' string value.

Related

Restructure xml with recursive nodes

I have an issue in SAP PI with an xml with recursive nodes. I have a Container which can have a SubContainer with (another) Container.
Input xml
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Name>INTERIEUR1</Name>
<Number>1</Number>
<SSCC>111</SSCC>
<SubContainers>
<Container>
<Id>I1371851</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>ACCESSOIRE1</Name>
<Barcode>181001371851</Barcode>
<Items>
<Item>
<Id>I8709475</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
<Item>
<Id>I8709476</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
<Container>
<Id>I1371852</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>PANEEL1</Name>
<Barcode>181001371852</Barcode>
<Items>
<Item>
<Id>I8709492</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</SubContainers>
</Container>
Challenge: I want to move the child Container nodes of the SubContainers to the root level and use the value of SSCC in these nodes. Thus getting rid of the SubContainers element.
Required result
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Id>I1371851</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>ACCESSOIRE1</Name>
<SSCC>111</SSCC>
<Barcode>181001371851</Barcode>
<Items>
<Item>
<Id>I8709475</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
<Item>
<Id>I8709476</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
<Container>
<Id>I1371852</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>PANEEL1</Name>
<SSCC>111</SSCC>
<Barcode>181001371852</Barcode>
<Items>
<Item>
<Id>I8709492</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</Container>
</Containers>
</Containers>
My current xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="https://www.uniconcreation.com/2021/IvenzaShippingContainer" exclude-result-prefixes="ext">
<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="ext:Name|ext:Number|ext:SSCC|#*" />
<xsl:template match="ext:Containers/ext:Container/ext:SubContainers[ext:Container]">
<xsl:variable name="sscc" select="/ext:Containers/ext:Container/ext:SSCC"/>
<xsl:for-each select="*">
<xsl:element name="SSCC" namespace="{namespace-uri()}">
<xsl:value-of select="$sscc" /></xsl:element>
<xsl:copy-of select="node()"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'm not doing a real great job if you look at my result on xsltfiddle :(
I'm stuck with the original parent and I don't manage to get the 2 elements around my child nodes.
Kind regards,
Mike D
If I am guessing correctly, all you need to do is simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Containers">
<xsl:variable name="sscc" select="Container/SSCC"/>
<xsl:copy>
<xsl:for-each select="//SubContainers/Container">
<xsl:copy>
<xsl:copy-of select="* except (Barcode, Items)"/>
<xsl:copy-of select="$sscc"/>
<xsl:copy-of select="Barcode, Items"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<Containers xmlns="https://www.uniconcreation.com/2021/IvenzaShippingContainer">
<Container>
<Id>I1371851</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>ACCESSOIRE1</Name>
<SSCC>111</SSCC>
<Barcode>181001371851</Barcode>
<Items>
<Item>
<Id>I8709475</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
<Item>
<Id>I8709476</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
<Container>
<Id>I1371852</Id>
<SalesOrderNumber>2012231</SalesOrderNumber>
<ProductionOrderNumber>I2017658</ProductionOrderNumber>
<Name>PANEEL1</Name>
<SSCC>111</SSCC>
<Barcode>181001371852</Barcode>
<Items>
<Item>
<Id>I8709492</Id>
<SalesOrderRowId>I671068</SalesOrderRowId>
<SalesOrderRowExternalReference>1</SalesOrderRowExternalReference>
<Quantity>1</Quantity>
</Item>
</Items>
</Container>
</Containers>
If the exact order of the child elements of Container does not matter, then it could be even simpler. Instead of:
<xsl:copy-of select="* except (Barcode, Items)"/>
<xsl:copy-of select="$sscc"/>
<xsl:copy-of select="Barcode, Items"/>
you could do:
<xsl:copy-of select="$sscc | *"/>

XSLT selecting unique values using for-each

I want to extract unique values of element i tried for-each with following-sibling but it is not working
Main problem is that I don't know how to get distinct values using XSLT 1.0 or 2.0
Here is Source XML example i am using
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>5</qun>
<loc>US</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>6</qun>
<loc>UK</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<qun>11</qun>
<loc>GER</loc>
</Item>
</Items>
<Items>
<Item>
<Itemno>114</Itemno>
<itemname>lkj</itemname>
<qun>1</qun>
<loc>IND</loc>
</Item>
</Items>
Required Output
<Order>
<Item>
<Itemno>112</Itemno>
<itemname>abc</itemname>
<Desc>
<qun>5</qun>
<loc>US</loc>
</Desc>
<Desc>
<qun>6</qun>
<loc>UK</loc>
</Desc>
<Desc>
<qun>11</qun>
<loc>GER</loc>
</Desc>
</Item>
<Item>
<Itemno>114</Itemno>
<itemname>lkj</itemname>
<Desc>
<qun>1</qun>
<loc>IND</loc>
</Desc>
</Item>
</Order>
In xslt 2.0 you can for grouping by Itemno use for-each-group. As example you can use this code
<xsl:template match="Items">
<Order>
<xsl:for-each-group select="//Item" group-by="Itemno">
<Item>
<xsl:copy-of select="Itemno"/>
<xsl:copy-of select="itemname"/>
<xsl:for-each select="current-group()">
<Desc>
<xsl:copy-of select="qun"/>
<xsl:copy-of select="loc"/>
</Desc>
</xsl:for-each>
</Item>
</xsl:for-each-group>
</Order>
</xsl:template>
The result of it is xml that you need

How to get last 2 nodes previous specific node?

This is my xml:
<Line>
<Item>
<Id>1</Id>
<Name>A</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>2</Id>
<Name>B</Name>
<Unit>Test</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>3</Id>
<Name>C</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>4</Id>
<Name>D</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>5</Id>
<Name>E</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
How to get all nodes which are at first and second position after nodes with Unit= Test. In this case, node with Id= 2 have Unit = Test so I want to display nodes with Id = 3 and Id = 4.
Thanks
The expression you want is this...
<xsl:copy-of select="//Line[Item/Unit='Test']/following-sibling::Line[position() <= 2]" />
This will work regardless of what the current node is.
Alternatively, you could split it out in to templates. For example
<xsl:template match="/*">
<xsl:apply-templates select="//Line[Item/Unit='Test']" />
</xsl:template>
<xsl:template match="Line">
<xsl:copy-of select="following-sibling::Line[position() <= 2]" />
</xsl:template>
If you want to get all nodes except 3 and 4, try this expression instead
<xsl:copy-of select="//Line[not(preceding-sibling::Line[position() <= 2][Item/Unit = 'Test'])]" />

XSLT Group and Merge

Trying to merge the following so that:
<?xml version="1.0" encoding="utf-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes">
<item>
<Detail>First</Detail>
</item>
<item>
<Detail>Second</Detail>
</item>
</Details>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Status></Status>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>
becomes: (all Items/Details within any matching Title are merged)
<?xml version="1.0" encoding="utf-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes">
<item>
<Detail>First</Detail>
</item>
<item>
<Detail>Second</Detail>
</item>
</Details>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Status></Status>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>
Using XSLT 2.0 but not quite getting the group-by spot on as it appears to duplicate the entries.
Thanks in advance.
Sorry forget my attempt:
<xsl:template match="/">
<Selections repeat="yes">
<xsl:for-each-group select="/Selections/Item" group-by="Title">
<Item>
<Title>
<xsl:value-of select="current-grouping-key()"/>
</Title>
<Details repeat="yes">
<xsl:for-each select="current-group()">
<xsl:copy-of select="current-group() /Details/Item"/>
</xsl:for-each>
</Details>
</Item>
</xsl:for-each-group>
</Selections>
</xsl:template>
which gives:
<?xml version="1.0" encoding="UTF-8"?>
<Selections repeat="yes">
<Item>
<Title>One</Title>
<Details repeat="yes"/>
</Item>
<Item>
<Title>Two</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Three</Title>
<Details repeat="yes">
<Item>
<Detail>Third</Detail>
</Item>
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
</Details>
</Item>
<Item>
<Title>Four</Title>
<Details repeat="yes">
<Item>
<Detail>First</Detail>
</Item>
<Item>
<Detail>Second</Detail>
</Item>
<Item>
<Detail>Third</Detail>
</Item>
</Details>
</Item>
</Selections>
The problem is with this line:
<xsl:for-each select="current-group()">
<xsl:copy-of select="current-group()/Details/Item"/>
</xsl:for-each>
With the xsl:for-each you are already iterating over the current-group(), so effectively for each item in the group, you then copy all items in the group.
To solve, just change it to this:
<xsl:for-each select="current-group()">
<xsl:copy-of select="Details/Item"/>
</xsl:for-each>
Alternatively, you don't need the xsl:for-each at all. The existing line does the job with the need for the xsl:for-each too.
<Details repeat="yes">
<xsl:copy-of select="current-group()/Details/Item"/>
</Details>

Need Help ... Grouping in XSLT

Please provide the code for Muenchian Grouping using XSLT1.0....
<list numOfItems="3">
<item>
<field dataType="double" name="DOWNPAYMENT">0</field>
</item>
<item>
<field dataType="double" name="DOWNPAYMENT">1</field>
</item>
<item>
<field dataType="double" name="DOWNPAYMENT">2</field>
</item>
</list>
<list numOfItems="3">
<item>
<field dataType="double" name="BALLOONPAYMENT">0</field>
</item>
<item>
<field dataType="double" name="BALLOONPAYMENT">1</field>
</item>
<item>
<field dataType="double" name="BALLOONPAYMENT">2</field>
</item>
</list>
Output should be...
<campaigns>
<downpayment>0</downpayment>
<Ballonpayment>0</Ballonpayment>
</campaigns>
<campaigns>
<downpayment>1</downpayment>
<Ballonpayment>1</Ballonpayment>
</campaigns>
<campaigns>
<downpayment>2</downpayment>
<Ballonpayment>2</Ballonpayment>
</campaigns>
Define your key:
<xsl:key name="value" match="item/field" use="."/>
then process
<xsl:template match="/">
<xsl:apply-templates select="//list/item/field[generate-id() = generate-id(key('value', .)[1])]" mode="group"/>
</xsl:template>
<xsl:template match="item/field" mode="group">
<campaigns>
<xsl:apply-templates select="key('value', .)"/>
</campaigns>
</xsl:template>
<xsl:template match="item/field[#name= 'BALLOONPAYMENT']">
<Ballonpayment>
<xsl:value-of select="."/>
</Ballonpayment>
</xsl:template>
<xsl:template match="item/field[#name= 'DOWNPAYMENT']">
<downpayment>
<xsl:value-of select="."/>
</downpayment>
</xsl:template>