extract and move nodes via XSLT - xslt

I need to transform the incoming XML so that I can extract all "item" with "categorie" is equal "two" and move these into a separate "records" node with an attribute initialized as type="two".
Here's a sample of the incoming XML.
<datafeed>
<records type="one">
<purchases>
<items>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<exchange/>
<context/>
<pilotage/>
</datafeed>
Here's what I would like:
<datafeed>
<records type="one">
<purchases>
<items>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<records type="two">
<purchases>
<items>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<exchange/>
<context/>
<pilotage/>
</datafeed>
I now have two "records" both initialize with it's predefined type (always one or two). The records that were extracted, were moved, hence deleted from the original record.
Thanks

This transformation:
<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:key name="kitemByCategory" match="item"
use="categorie"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="records">
<xsl:call-template name="identity"/>
<xsl:variable name="vCat2Items" select=
"key('kitemByCategory', 'two')"/>
<xsl:if test="$vCat2Items">
<records type="two">
<purchases>
<items>
<xsl:copy-of select="$vCat2Items"/>
</items>
</purchases>
</records>
</xsl:if>
</xsl:template>
<xsl:template match="item[categorie = 'two']"/>
</xsl:stylesheet>
when applied on the provided XML document, produces the wanted, correct result:
<datafeed>
<records type="one">
<purchases>
<items>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<records type="two">
<purchases>
<items>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<exchange/>
<context/>
<pilotage/>
</datafeed>
Do note:
The use and overriding of the identity rule.
How items of categorie "two" are excluded from processing by using an empty template matching them.
The use of keys for efficient and convenient locationg of items by categorie.

With this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="itemsBycategorie" match="item" use="categorie"/>
<xsl:template match="#*|node()">
<xsl:param name="items"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="items" select="$items"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="records">
<xsl:variable name="me" select="."/>
<xsl:for-each select="*/*/*[count(.|key('itemsBycategorie',categorie)[1])=1]">
<records type="{categorie}">
<xsl:apply-templates select="$me/node()">
<xsl:with-param name="items" select="key('itemsBycategorie',categorie)"/>
</xsl:apply-templates>
</records>
</xsl:for-each>
</xsl:template>
<xsl:template match="items">
<xsl:param name="items"/>
<xsl:copy>
<xsl:apply-templates select="$items"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result:
<datafeed>
<records type="one">
<purchases>
<items>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>one</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<records type="two">
<purchases>
<items>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
<item>
<categorie>two</categorie>
<intrant>String</intrant>
</item>
</items>
</purchases>
</records>
<exchange></exchange>
<context></context>
<pilotage></pilotage>
</datafeed>
Note: Muenchian Method of grouping. And "poor man's tunnel" params (Dimitre quot).

Related

XSLT 1.0: rename elements with same content

It seemed like an easy task but I am totally stuck now. I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Items>
<Item>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<ITEM_NAME>ITEM_NAME</ITEM_NAME>
<ITEM_ALTERNATE_NAME>ITEM_ALTERNATE_NAME</ITEM_ALTERNATE_NAME>
<ITEM_CATEGORY_CODE>ITEM_CATEGORY_CODE</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>15031</ITEM_CODE>
<ITEM_NAME>Outer Carton</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150529</ITEM_CODE>
<ITEM_NAME>Outer Carton</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150999</ITEM_CODE>
<ITEM_NAME>Outer Carton</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150988</ITEM_CODE>
<ITEM_NAME>test</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
</Items>
If <ITEM_NAME> elements have duplicate contents those should be renamed with a suffix, e.g. a counter value. I came up with this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" method="xml" indent="yes"/>
<xsl:key name="keyItemName" match="Item" use="concat(ITEM_CODE , '|', ITEM_NAME)"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Items">
<Items>
<xsl:apply-templates select="#*|node()"/>
</Items>
</xsl:template>
<xsl:template match="ITEM_NAME">
<xsl:for-each select="parent::Item[generate-id()=generate-id(key('keyItemName',concat(ITEM_CODE , '|', ITEM_NAME))[1])]">
<xsl:variable name="number">
<xsl:number/>
</xsl:variable>
<ITEM_NAME>
<xsl:value-of select="concat(ITEM_NAME,'-',$number)"/>
</ITEM_NAME>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It gives me this output:
<?xml version="1.0" encoding="UTF-8"?>
<Items>
<Item>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<ITEM_NAME>ITEM_NAME-1</ITEM_NAME>
<ITEM_ALTERNATE_NAME>ITEM_ALTERNATE_NAME</ITEM_ALTERNATE_NAME>
<ITEM_CATEGORY_CODE>ITEM_CATEGORY_CODE</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>15031</ITEM_CODE>
<ITEM_NAME>Outer Carton-2</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150529</ITEM_CODE>
<ITEM_NAME>Outer Carton-3</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150999</ITEM_CODE>
<ITEM_NAME>Outer Carton-4</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150988</ITEM_CODE>
<ITEM_NAME>test-5</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
</Items>
But I expect this output:
<?xml version="1.0" encoding="UTF-8"?>
<Items>
<Item>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<ITEM_NAME>ITEM_NAME</ITEM_NAME>
<ITEM_ALTERNATE_NAME>ITEM_ALTERNATE_NAME</ITEM_ALTERNATE_NAME>
<ITEM_CATEGORY_CODE>ITEM_CATEGORY_CODE</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>15031</ITEM_CODE>
<ITEM_NAME>Outer Carton-2</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150529</ITEM_CODE>
<ITEM_NAME>Outer Carton-3</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150999</ITEM_CODE>
<ITEM_NAME>Outer Carton-4</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150988</ITEM_CODE>
<ITEM_NAME>test</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
</Items>
In the last <Item> the ITEM_NAME should not be renamed because it is not called "Outer Carton". Also in the first <Item> element no renaming should be happening.
Using preceding:: or preceding-sibling:: to count prior instances is not very efficient computationally, but I don't see a way around it here. The approach below does have the benefit that it only counts preceding instances when after checking (with a key, which is very quick) that there are other items with the same name:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" method="xml" indent="yes"/>
<xsl:key name="keyItemName" match="ITEM_NAME" use="."/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Items">
<Items>
<xsl:apply-templates select="#*|node()"/>
</Items>
</xsl:template>
<xsl:template match="ITEM_NAME">
<xsl:copy>
<xsl:value-of select="." />
<xsl:if test="count(key('keyItemName', .)) > 1">
<xsl:value-of select="concat('-', count(preceding::ITEM_NAME[. = current()]) + 2)"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<Items>
<Item>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<ITEM_NAME>ITEM_NAME</ITEM_NAME>
<ITEM_ALTERNATE_NAME>ITEM_ALTERNATE_NAME</ITEM_ALTERNATE_NAME>
<ITEM_CATEGORY_CODE>ITEM_CATEGORY_CODE</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>15031</ITEM_CODE>
<ITEM_NAME>Outer Carton-2</ITEM_NAME>
<ITEM_ALTERNATE_NAME />
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150529</ITEM_CODE>
<ITEM_NAME>Outer Carton-3</ITEM_NAME>
<ITEM_ALTERNATE_NAME />
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150999</ITEM_CODE>
<ITEM_NAME>Outer Carton-4</ITEM_NAME>
<ITEM_ALTERNATE_NAME />
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150988</ITEM_CODE>
<ITEM_NAME>test</ITEM_NAME>
<ITEM_ALTERNATE_NAME />
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
</Items>
You current key seems to join ITEM_NAME and ITEM_CODE, but it looks like you only want ITEM_NAME here
<xsl:key name="keyItemName" match="ITEM_NAME" use="."/>
It also looks like you want the numbering for the suffix to be based on the position of the parent item element. One way to achieve this is to have a template to match the item element, and then pass the number as a parameter to the subsequent mathching templates
<xsl:template match="Item">
<Item>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="number">
<xsl:number/>
</xsl:with-param>
</xsl:apply-templates>
</Item>
</xsl:template>
Then, you need a template to match ITEM_NAME elements for which duplicate occurs. This can be done simply by checking there is at least a second element defined in the group for the key:
<xsl:template match="ITEM_NAME[key('keyItemName', .)[2]]">
<xsl:param name="number"/>
Then, you can just output the element with the suffix.
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" method="xml" indent="yes"/>
<xsl:key name="keyItemName" match="ITEM_NAME" use="."/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Item">
<Item>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="number">
<xsl:number/>
</xsl:with-param>
</xsl:apply-templates>
</Item>
</xsl:template>
<xsl:template match="ITEM_NAME[key('keyItemName', .)[2]]">
<xsl:param name="number"/>
<ITEM_NAME>
<xsl:value-of select="concat(.,'-',$number)"/>
</ITEM_NAME>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Items>
<Item>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<ITEM_NAME>ITEM_NAME</ITEM_NAME>
<ITEM_ALTERNATE_NAME>ITEM_ALTERNATE_NAME</ITEM_ALTERNATE_NAME>
<ITEM_CATEGORY_CODE>ITEM_CATEGORY_CODE</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>15031</ITEM_CODE>
<ITEM_NAME>Outer Carton-2</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150529</ITEM_CODE>
<ITEM_NAME>Outer Carton-3</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150999</ITEM_CODE>
<ITEM_NAME>Outer Carton-4</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
<Item>
<ITEM_CODE>150988</ITEM_CODE>
<ITEM_NAME>test</ITEM_NAME>
<ITEM_ALTERNATE_NAME/>
<ITEM_CATEGORY_CODE>52401</ITEM_CATEGORY_CODE>
</Item>
</Items>

XSLT Grouping elements inside two siblings

I'm trying to wrap my head around this and I think the easiest way to explain it is to just show you, below. I've seen this but it doesn't always apply due to the fact I have standalone items also at the end which would match.
The seemingly tricky part is Whatever3 to Whatever6, and then Whatever7 and Whatever8, then finally the new position of Whatever9 - they need to be grouped and the original sequence maintained. (Ignore my naming, there is no way to use xsl:sort)
I've considered xsl:for-each with xsl:if inside, but issue is you can't guarantee how many "groups" vs "non-group" items there are.
Thanks!
XML
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
Result
<structure>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<group position="3" id="unique1">
<item>
<position>1</position>
<label>Whatever3</label>
</item>
<item>
<position>2</position>
<label>Whatever4</label>
</item>
<item>
<position>3</position>
<label>Whatever5</label>
</item>
<item>
<position>4</position>
<label>Whatever6</label>
</item>
</group>
<group position="4" id="uniqueid2">
<item>
<position>1</position>
<label>Whatever7</label>
</item>
<item>
<position>2</position>
<label>Whatever8</label>
</item>
</group>
<item>
<position>**5**</position>
<label>Whatever9</label>
</item>
</structure>
======================
Here is what I have so far, the only problem I have (besides it being messy) is Whatever4 and Whatever5 are showing up outside the Group.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="root">
<structure>
<xsl:apply-templates select="item[not(marker)] | item[marker='start_group']"/>
</structure>
</xsl:template>
<xsl:template match="item[marker='start_group']">
<group>
<xsl:variable name="currentPosition" select="number(position/text())"/>
<xsl:variable name="lastGroup" select="count(following-sibling::*[local-name() = 'item' and marker='last_in_group'][1]/preceding-sibling::*) + 1"/>
<xsl:attribute name="position">
<xsl:value-of select="$currentPosition"/>
</xsl:attribute>
<xsl:attribute name="id">
<xsl:value-of select="marker/#id"/>
</xsl:attribute>
<item>
<position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
<!-- position() gets reset in for-loop, so need to adjust with outer position -->
<xsl:for-each select="following-sibling::item[(position() + $currentPosition) <= $lastGroup]">
<item>
<position><xsl:value-of select="number(position/text()) - $currentPosition + 1"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template match="item[not(marker)]">
<item>
<position><xsl:value-of select="position/text()"/></position>
<label><xsl:value-of select="label/text()"/></label>
</item>
</xsl:template>
</xsl:stylesheet>
I. XSLT 1.0 Solution:
This transformation:
<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:key name="kFollowing"
match="item[not(marker[. = 'start_group'])
and
preceding-sibling::*[marker][1]/marker = 'start_group'
]"
use="generate-id(preceding-sibling::*
[marker[. = 'start_group']]
[1])"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[marker[. = 'start_group']]">
<group position="{1 +count(preceding-sibling::*[. = 'start_group'])}"
id="{marker/#id}">
<xsl:copy-of select=".|key('kFollowing', generate-id())"/>
</group>
</xsl:template>
<xsl:template match=
"item[not(marker[. = 'start_group'])
and
preceding-sibling::*[marker][1]/marker = 'start_group'
]"/>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
produces the wanted, correct result:
<root>
<item>
<position>1</position>
<label>Whatever1</label>
</item>
<item>
<position>2</position>
<label>Whatever2</label>
</item>
<group position="1" id="unique1">
<item>
<position>3</position>
<label>Whatever3</label>
<marker id="unique1">start_group</marker>
</item>
<item>
<position>4</position>
<label>Whatever4</label>
</item>
<item>
<position>5</position>
<label>Whatever5</label>
</item>
<item>
<position>6</position>
<label>Whatever6</label>
<marker>last_in_group</marker>
</item>
</group>
<group position="1" id="unique2">
<item>
<position>7</position>
<label>Whatever7</label>
<marker id="unique2">start_group</marker>
</item>
<item>
<position>8</position>
<label>Whatever8</label>
<marker>last_in_group</marker>
</item>
</group>
<item>
<position>9</position>
<label>Whatever9</label>
</item>
</root>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<root>
<xsl:for-each-group select="item" group-starting-with=
"*[marker eq 'start_group'
or
not(marker)
and
preceding-sibling::*[marker][1]/marker eq 'last_in_group'
]
">
<xsl:choose>
<xsl:when test="current-group()[1]/marker">
<group position=
"{1 +count(current-group()[1]
/preceding-sibling::*
[marker = 'start_group'])}"
id="{marker/#id}">
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
When this XSLT 2.0 transformation is applied on the same XML document (above), the same correct result is produced.

XSLT conditions matching with list stored in external file

I am trying to write a XSLT which extracts items matches the conditions listed in another file.
INPUT FILE (input.xml)
<ItemList>
<Item>
<Product>ABC</Product>
<Price>10.00</Price>
</Item>
<Item>
<Product>DEF</Product>
<Price>20.00</Price>
</Item>
<Item>
<Product>GHI</Product>
<Price>30.00</Price>
</Item>
<Item>
<Product>JKL</Product>
<Price>40.00</Price>
</Item>
</ItemList>
External List File (Codes.xml)
<ProductCodeList>
<ProductCode>ABC</ProductCode>
<ProductCode>JKL</ProductCode>
</ProductCodeList>
Expected Output (output.xml)
<ItemList>
<Item>
<Product>ABC</Product>
<Price>10.00</Price>
</Item>
<Item>
<Product>JKL</Product>
<Price>40.00</Price>
</Item>
</ItemList>
Could you please show me which one is not working?
<xsl:variable name="productCodeList" select="document('Codes.xml')/ProductCodeList/ProductCode" />`
<xsl:template match="/">
<xsl:apply-templates select="/ItemList/Item[Product=$productCodeList]"/>
</xsl:template>
<xsl:template match="/ItemList/Item">
<xsl:copy-of select="."/>
</xsl:template>
This simple (no conditionals, no current()) transformation:
<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:variable name="vProds" select=
"document('file:///c:/temp/delete/ProductList.xml')"/>
<xsl:template match="/">
<ItemList>
<xsl:copy-of select=
"/*/Item
[Product
=
$vProds/*/ProductCode
]
"/>
</ItemList>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<ItemList>
<Item>
<Product>ABC</Product>
<Price>10.00</Price>
</Item>
<Item>
<Product>DEF</Product>
<Price>20.00</Price>
</Item>
<Item>
<Product>GHI</Product>
<Price>30.00</Price>
</Item>
<Item>
<Product>JKL</Product>
<Price>40.00</Price>
</Item>
</ItemList>
and having the provided Productlist.xml stored at c:\temp\delete:
<ProductCodeList>
<ProductCode>ABC</ProductCode>
<ProductCode>JKL</ProductCode>
</ProductCodeList>
produces the wanted, correct result:
<ItemList>
<Item>
<Product>ABC</Product>
<Price>10.00</Price>
</Item>
<Item>
<Product>JKL</Product>
<Price>40.00</Price>
</Item>
</ItemList>
Does that maybe work better?
<xsl:variable name="productCodeList" select="document('Codes.xml')/ProductCodeList/ProductCode" />
<xsl:template match="/">
<xsl:apply-templates select="/ItemList/Item"/>
</xsl:template>
<xsl:template match="/ItemList/Item">
<xsl:if test="$productCodeList[.=current()/Product]">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>

SplitByValue function using XSLT mapping

How to do splitbyvalue function using XSLT?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<ACCOUNT>
<xsl:apply-templates select="//RefCoded/RefCode[. = 'WBS']"/>
</ACCOUNT>
</xsl:template>
<xsl:template match="RefCode">
<item>
<BItemNum>
<xsl:value-of select="../../../../LineItemNum/BLineItemNum"/>
</BItemNum>
</item>
</xsl:template>
</xsl:stylesheet>
Output:
<ACCOUNT>
<item>
<BItemNum>00001</BItemNum>
</item>
<item>
<BItemNum>00001</BItemNum>
</item>
<item>
<BItemNum>00002</BItemNum>
</item>
<item>
<BItemNum>00002</BItemNum>
</item>
</ACCOUNT>
<xsl:template match="/">
<ACCOUNT>
<xsl:for-each select="descendant::RefCode[text() = 'WBS']">
<item>
<BItemNum><xsl:value-of select="ancestor::ItemDetail/descendant::BLineItemNum"/></BItemNum>
</item>
</xsl:for-each>
</ACCOUNT>
</xsl:template>

Merging duplicate nodes in an to form a new XML using XSLT

i have an XML as below. Where differenct items come for delete or add. each item will have serial number. what i trying to do is i want to prepare a new xml from these input in such a way that, if an item with an item code has come for add and delete i want to merge them into a single item node. The new item formed will have action code as update and sino will be add item sino and oldsino will be delete item sino.
INPUT
`
<ITEM>
<SINO>1</SINO>
<ITEMCODE>101</ITEMNAME>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>2</SINO>
<ITEMCODE>101</ITEMNAME>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>3</SINO>
<ITEMCODE>102</ITEMNAME>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>4</SINO>
<ITEMCODE>103</ITEMNAME>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>5</SINO>
<ITEMCODE>103</ITEMNAME>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>6</SINO>
<ITEMCODE>104</ITEMNAME>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
`
OUTPUT
`
<ITEM>
<SINO>1</SINO>
<ITEMCODE>101</ITEMNAME>
<ACTION>UPDATE</ACTION>
<OLDSINO>2</OLDSINO>
</ITEM>
<ITEM>
<SINO>3</SINO>
<ITEMCODE>102</ITEMNAME>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>4</SINO>
<ITEMCODE>103</ITEMNAME>
<ACTION>DELETE</ACTION>
<OLDSINO>5</OLDSINO>
</ITEM>
<ITEM>
<SINO>6</SINO>
<ITEMCODE>104</ITEMNAME>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
`
Any ideas how to acheive this using XSLT.
if an item with an item code has come for add and delete i want to merge them into a single item node. The new item formed will have action code as update and sino will be add item sino and oldsino will be delete item sino
This solution mainly plays with template match patterns on siblings, and exploits the identity rule.
XSLT 1.0
<xsl:stylesheet 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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM[
ITEMCODE[../ACTION='ADD']
=
../ITEM/ITEMCODE[../ACTION='DELETE']]">
<xsl:copy>
<SINO><xsl:value-of select="../ITEM[ITEMCODE
=current()/ITEMCODE and ACTION='ADD']/
SINO"/>
</SINO>
<xsl:copy-of select="ITEMCODE"/>
<ACTION>UPDATE</ACTION>
<OLDSINO><xsl:value-of select="../ITEM[ITEMCODE
=current()/ITEMCODE and ACTION='DELETE']/
SINO"/>
</OLDSINO>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM[
ITEMCODE[../ACTION='DELETE']
=
../ITEM/ITEMCODE[../ACTION='ADD']]"/>
</xsl:stylesheet>
The solution produces:
<?xml version="1.0" encoding="UTF-16"?>
<ITEMS>
<ITEM>
<SINO>1</SINO>
<ITEMCODE>101</ITEMCODE>
<ACTION>UPDATE</ACTION>
<OLDSINO>2</OLDSINO>
</ITEM>
<ITEM>
<SINO>3</SINO>
<ITEMCODE>102</ITEMCODE>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>4</SINO>
<ITEMCODE>103</ITEMCODE>
<ACTION>UPDATE</ACTION>
<OLDSINO>5</OLDSINO>
</ITEM>
<ITEM>
<SINO>6</SINO>
<ITEMCODE>104</ITEMCODE>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
</ITEMS>
Here is a short and simple solutions that overrides the identity rule/template:
<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:key name="kItemByCode" match="ITEM"
use="ITEMCODE"/>
<xsl:key name="kActionByCode" match="ACTION"
use="../ITEMCODE"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"ITEM[not(generate-id() =
generate-id(key('kItemByCode', ITEMCODE)[1])
)
]"/>
<xsl:template match=
"ITEM[generate-id() =
generate-id(key('kItemByCode', ITEMCODE)[1])
and
key('kActionByCode', ITEMCODE) = 'ADD'
and
key('kActionByCode', ITEMCODE) = 'DELETE'
]
/ACTION/text()
">
<xsl:text>UPDATE</xsl:text>
</xsl:template>
<xsl:template match=
"ITEM[generate-id() =
generate-id(key('kItemByCode', ITEMCODE)[1])
and
key('kActionByCode', ITEMCODE) = 'ADD'
and
key('kActionByCode', ITEMCODE) = 'DELETE'
]
/OLDSINO
">
<OLDSINO>
<xsl:apply-templates mode="List"
select="key('kItemByCode', ../ITEMCODE)[position()>1]" />
</OLDSINO>
</xsl:template>
<xsl:template match="ITEM" mode="List">
<xsl:if test="not(position()=1)">,</xsl:if>
<xsl:value-of select="SINO"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document (based on and correcting the provided severely malformed XML fragment):
<t>
<ITEM>
<SINO>1</SINO>
<ITEMCODE>101</ITEMCODE>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>2</SINO>
<ITEMCODE>101</ITEMCODE>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>3</SINO>
<ITEMCODE>102</ITEMCODE>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>4</SINO>
<ITEMCODE>103</ITEMCODE>
<ACTION>ADD</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>5</SINO>
<ITEMCODE>103</ITEMCODE>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
<ITEM>
<SINO>6</SINO>
<ITEMCODE>104</ITEMCODE>
<ACTION>DELETE</ACTION>
<OLDSINO></OLDSINO>
</ITEM>
</t>
the wanted, correct result is produced:
<t>
<ITEM>
<SINO>1</SINO>
<ITEMCODE>101</ITEMCODE>
<ACTION>UPDATE</ACTION>
<OLDSINO>2</OLDSINO>
</ITEM>
<ITEM>
<SINO>3</SINO>
<ITEMCODE>102</ITEMCODE>
<ACTION>ADD</ACTION>
<OLDSINO />
</ITEM>
<ITEM>
<SINO>4</SINO>
<ITEMCODE>103</ITEMCODE>
<ACTION>UPDATE</ACTION>
<OLDSINO>5</OLDSINO>
</ITEM>
<ITEM>
<SINO>6</SINO>
<ITEMCODE>104</ITEMCODE>
<ACTION>DELETE</ACTION>
<OLDSINO />
</ITEM>
</t>
Explanation:
The identity rule/template copies every node "as-is".
A template with empty body overrides the identity rule for every non-first ITEM of a group of ITEM elements with the same ITEMCODE -- any such node isn't copied to the output (aka deleted).
A third template overrides the identity rule for every ITEM element that is the first in a group of ITEM elements with the same ITEMCODE and for whose ITEMCODE there are items with ACTION both "ADD" and "DELETE". It constructs the necessary OLDSINO element by applying templates in mode "List" to all ITEM elements in the group, but the first one.
The Muenchian method for grouping and generally keys are used so that an efficient and compact implementation is achieved.
This solution works correctly with any arbitrary mixture of ITEM elements that may have the same ITEMCODE.
Correcting for the typo (opening ITEMCODE tag and closing ITEMNAME tag) and working with a root element of ITEMS a complete XSLT could be:
<?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="deletes" match="ITEM[ACTION='DELETE']" use="ITEMCODE/text()"/>
<xsl:key name="adds" match="ITEM[ACTION='ADD']" use="ITEMCODE/text()"/>
<xsl:template match="ITEMS">
<ITEMS>
<xsl:apply-templates select="ITEM" />
</ITEMS>
</xsl:template>
<xsl:template match="ITEM[ACTION='ADD' and key('deletes',ITEMCODE/text())]">
<xsl:variable name="current" select="ITEMCODE/text()"/>
<xsl:copy>
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="name()='ACTION'">
<ACTION>UPDATE</ACTION>
</xsl:when>
<xsl:when test="name()='OLDSINO'">
<OLDSINO><xsl:value-of select="key('deletes',$current)/SINO/text()"/></OLDSINO>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM[ACTION='ADD' and not(key('deletes',ITEMCODE/text()))]">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="ITEM[ACTION='DELETE' and not(key('adds',ITEMCODE/text()))]">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="ITEM" />
</xsl:stylesheet>