XSLT 1.0: apply-templates and template mode - xslt

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Order>
<Item>
<RECORD_ID>RECORD_ID</RECORD_ID>
<ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
<USER_CODE>USER_CODE</USER_CODE>
<RECORD_DATE>RECORD_DATE</RECORD_DATE>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
<LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
<LINE_PRICE>LINE_PRICE</LINE_PRICE>
<LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008165</ITEM_CODE>
<LINE_QUANTITY>2</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008161</ITEM_CODE>
<LINE_QUANTITY>1</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008225</ITEM_CODE>
<LINE_QUANTITY>5</LINE_QUANTITY>
</Item>
</Order>
Sometimes within the item tag I have the element <LINE_FREE_STOCK>. If that occurs I have to create an additional position in the output XML.
Now I came up with this style sheet:
<?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:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[position() >1]"/>
<xsl:apply-templates select="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock"/>
</xsl:template>
<xsl:template match="Item">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:template>
<xsl:template match="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:template>
</xsl:stylesheet>
It creates this (simplified) wanted output:
<?xml version="1.0" encoding="UTF-8"?>
<ORDERS05>
<IDOC BEGIN="1">
<Header>some header data</Header>
<position>
<item>
<number>804-008165</number>
<quantity>2</quantity>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<quantity>1</quantity>
</item>
</position>
<position>
<item>
<number>804-008225</number>
<quantity>5</quantity>
</item>
</position>
<position>
<item>
<number>804-008165</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
</IDOC>
</ORDERS05>
804-008165 and 804-008161 show up twice - once as a standard item and once as the free stock item with the respective quantities.
But did I forget anything here? Is there some sort of pitfall I don't see?
Is that XSLT robust enough?

As others have noted, the problem is in this code:
<xsl:apply-templates select="Item"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>
If there is a child Item that has a child LINE_FREE_STOCK, templates would be applied on this Item element twice -- here is how you get the repetitions in the output.
The transformation can be significantly shortened and it doesn't need modes or explicit conditional instructions at all:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[position() >1]"/>
</xsl:template>
<xsl:template match="Item">
<position>
<item>
<number>
<xsl:value-of select="ITEM_CODE"/>
</number>
<xsl:apply-templates select=
"self::node()[not(LINE_FREE_STOCK)]/LINE_QUANTITY
|
LINE_FREE_STOCK"/>
</item>
</position>
</xsl:template>
<xsl:template match="LINE_QUANTITY">
<quantity>
<xsl:value-of select="."/>
</quantity>
</xsl:template>
<xsl:template match="LINE_FREE_STOCK">
<freestock_quant>
<xsl:value-of select="."/>
</freestock_quant>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Order>
<Item>
<RECORD_ID>RECORD_ID</RECORD_ID>
<ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
<USER_CODE>USER_CODE</USER_CODE>
<RECORD_DATE>RECORD_DATE</RECORD_DATE>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
<LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
<LINE_PRICE>LINE_PRICE</LINE_PRICE>
<LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008165</ITEM_CODE>
<LINE_QUANTITY>2</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008161</ITEM_CODE>
<LINE_QUANTITY>1</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008225</ITEM_CODE>
<LINE_QUANTITY>5</LINE_QUANTITY>
</Item>
</Order>
the wanted, correct result is produced:
<ORDERS05>
<IDOC BEGIN="1">
<Header>some header data</Header>
<position>
<item>
<number>804-008165</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008225</number>
<quantity>5</quantity>
</item>
</position>
</IDOC>
</ORDERS05>

This is because you have two match templates for Item:
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:if>
</xsl:template>
<xsl:template match="Item[child::LINE_FREE_STOCK]" mode="freestock">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:if>
</xsl:template>
First the default Item template matches and then the Item's with LINE_FREE_STOCK also matches the Item with child LINE_FREE_STOCK template, hence the duplicate for Item's with LINE_FREE_STOCK.
Instead why not just use one template, like this:
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<xsl:choose>
<xsl:when test="child::LINE_FREE_STOCK">
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</xsl:when>
<xsl:otherwise>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</xsl:otherwise>
</xsl:choose>
</item>
</position>
</xsl:if>
</xsl:template>
Using the single template your Order template is also simplified:
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item"/>
</xsl:template>
This way you do not need to use Modes either.

It's not clear what is the wanted output. Perhaps you want:
<xsl:apply-templates select="Item[not(LINE_FREE_STOCK)"/>
<xsl:apply-templates select="Item[LINE_FREE_STOCK]" mode="freestock"/>
in place of your
<xsl:apply-templates select="Item"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>

You would need an addition filtering
<?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:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[not(child::LINE_FREE_STOCK)]"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>
</xsl:template>
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:if>
</xsl:template>
<xsl:template match="Item[child::LINE_FREE_STOCK]" mode="freestock">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Related

xslt order and grouping with for-each-group

I'm trying to get my head over nested grouping and sorting while using for-each-group.
My idea is to order and group items at first by producer. Then when I have this producer groups I'd like to sort each of them by code. However currently the order of code doesn't work as I'd like to. In following example the problem is with item with code=01001-064-03. It should be grouped together with all other items whose code starts with 01001 but it isn't. If I move entire item/code[text()='01001-064-03'] (the last one) to the beginning of xml then grouping works ok.
Please what is my issue here?
Thanks
<items>
<change_date>#11.11.2020 7:42:13</change_date>
<result>
<item>
<code>01001-064-01</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-02</code>
<producer>prod1</producer>
</item>
<item>
<code>def</code>
<producer>prod1</producer>
</item>
<item>
<code>ghi</code>
<producer>prod2</producer>
</item>
<item>
<code>jkl</code>
<producer>prod3</producer>
</item>
<item>
<code>abc</code>
<producer>prod3</producer>
</item>
<item>
<code>def</code>
<producer>prod4</producer>
</item>
<item>
<code>ghi</code>
<producer>prod4</producer>
</item>
<item>
<code>jkl</code>
<producer>prod5</producer>
</item>
<item>
<code>01001-064-03</code>
<producer>prod1</producer>
</item>
</result>
</items>
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" html-version="5"/>
<xsl:function name="mf:same-product" as="xs:boolean">
<xsl:param name="left" as="xs:string"/>
<xsl:param name="right" as="xs:string"/>
<xsl:variable name="leftParsed" select="mf:get-regexp-group($left, 1)"/>
<xsl:variable name="rightParsed" select="mf:get-regexp-group($right, 1)"/>
<xsl:sequence select="matches($leftParsed, $rightParsed)"/>
</xsl:function>
<xsl:function name="mf:get-regexp-group" as="xs:string">
<xsl:param name="text" as="xs:string"/>
<xsl:param name="groupNumber" as="xs:integer"/>
<xsl:variable name="result">
<xsl:analyze-string select="$text" regex="(^[a-zA-Z0-9]+)(.*)">
<xsl:matching-substring>
<xsl:value-of select="regex-group($groupNumber)"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>
<xsl:template match="items">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="change_date"/>
<xsl:template match="result">
<data>
<xsl:for-each-group select="item" group-by="producer">
<xsl:sort select="producer"/>
<xsl:for-each-group select="current-group()" group-starting-with="item[not(mf:same-product(code, preceding-sibling::item[1]/code))]">
<xsl:sort select="code"/>
<group>
<xsl:apply-templates select="current-group()" />
</group>
</xsl:for-each-group>
</xsl:for-each-group>
</data>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
fiddle example here
I'm using xslt 2.0 with saxon-he 10.3.
EDIT:
So as #michael.hor257k asked for better explanation I'll try to do my best:
Each item is product. This product has producer and has code (product code). I want to group all products of producer by code. However codes are not same for similar products so the similarity is matched by function mf:same-product. For example two similar products could be 01001-064-01 and 01001-064-02 here I check the first prefix 01001 and if it matches it means both products should be added to same group.
expected result should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group>
<item>
<code>01001-064-01</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-02</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-03</code>
<producer>prod1</producer>
</item>
</group>
<group>
<item>
<code>def</code>
<producer>prod1</producer>
</item>
</group>
<group>
<item>
<code>ghi</code>
<producer>prod2</producer>
</item>
</group>
<group>
<item>
<code>abc</code>
<producer>prod3</producer>
</item>
</group>
<group>
<item>
<code>jkl</code>
<producer>prod3</producer>
</item>
</group>
<group>
<item>
<code>def</code>
<producer>prod4</producer>
</item>
</group>
<group>
<item>
<code>ghi</code>
<producer>prod4</producer>
</item>
</group>
<group>
<item>
<code>jkl</code>
<producer>prod5</producer>
</item>
</group>
</data>
Perhaps a composite group-by suffices:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="change_date"/>
<xsl:template match="result">
<data>
<xsl:for-each-group select="item" composite="yes" group-by="producer, code => replace('[^a-z0-9].*$', '', 'i')">
<xsl:sort select="producer"/>
<xsl:sort select="code"/>
<group>
<xsl:apply-templates select="current-group()" />
</group>
</xsl:for-each-group>
</data>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ei5R4uT/10
That is XSLT 3 which Saxon 9.8 and later (e.g. Saxon 10) support, if you really need to do it with an XSLT 2.0 processor then a nested for-each-group group-by or a concatenated grouping key can achieve the same as the composite grouping key in the above XSLT 3.
My idea is to order and group items at first by producer. Then when I have this producer groups I'd like to sort each of them by code.
If that's all you want to do, why isn't it sufficient to do:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="result">
<xsl:for-each-group select="item" group-by="producer">
<xsl:sort select="producer"/>
<group>
<xsl:apply-templates select="current-group()">
<xsl:sort select="code"/>
</xsl:apply-templates>
</group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I'm using xslt 2.0 with saxon-he 10.3.
Actually, you are using XSLT 3.0.

Remove duplicates based on condition

I am trying to remove duplicates from my xml based on a condition in XSLT1.0
Here is the input xml.
<?xml version="1.0" encoding="UTF-8"?>
<Envelope
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{D5B72T7A-58E0-4930-9CEB-A06RT56AR21B0}</MessageId>
<Action>http://tempuri.org/TRH_FinalQueryService/find</Action>
</Header>
<Body>
<MessageParts
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<TRH_FinalQuery
xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery">
<TRH_UnionView class="entity">
<Company>1</Company>
<CS/>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>1</Company>
<CS>1</CS>
<Text_1>1</Text_1>
<Text_2>Soap</Text_2>
<WS>6</WS>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
</TRH_FinalQuery>
</MessageParts>
</Body>
</Envelope>
Here is the xslt that I have applied.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message" xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery" exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="r:TRH_FinalQuery" match="r:TRH_FinalQuery" use="concat(r:Text_1, '|', r:Company)" />
<!-- move all elements to no namespace -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="r:TRH_FinalQuery[r:TRH_UnionView[#class='entity']/r:WessexCostCenter=''][key('r:TRH_FinalQuery',concat(r:Text_1, '|', r:Company))[1]]"/>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<!-- removes Envelope -->
<xsl:template match="m:Envelope">
<xsl:apply-templates />
</xsl:template>
<!-- removes Header,MessageId,Action and Body -->
<xsl:template match="m:*">
<xsl:apply-templates select="*" />
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<DocumentElement>
<xsl:apply-templates select="r:TRH_FinalQuery/*" />
</DocumentElement>
</xsl:template>
<!-- rename RunObject to Item -->
<xsl:template match="r:TRH_UnionView[#class='entity']">
<xsl:choose>
<xsl:when test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:when>
<xsl:otherwise>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<xsl:apply-templates select="r:Company" />
</Item>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Below is the output I am getting
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
Below is the expected output
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
I am trying to remove all duplicates based on condition
If the Text_1 and Company are same.
If the point 1 is true then retain all records having value in WS tag and remove records where there no value in WS tag.
Can you please suggest what I am doing wrong
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="myKey" match="r:TRH_UnionView" use="concat(r:Text_1, '|', r:Company)" />
<!-- Simplify things by not having an identity. Using this approach, you will not have to suppress
any elements.
-->
<xsl:template match="node()">
<xsl:apply-templates select="node()"/>
</xsl:template>
<!-- Start at the root. -->
<xsl:template match="/">
<DocumentElement>
<xsl:apply-templates select="node()" />
</DocumentElement>
</xsl:template>
<xsl:template match="r:TRH_UnionView">
<xsl:choose>
<!-- Handle the duplicates with no value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1 and
count((key('myKey',concat(r:Text_1, '|', r:Company)))[r:WS!='']) = 0">
<!-- Is this the first of the duplicates? -->
<xsl:if test="generate-id(.) = generate-id(key('myKey',concat(r:Text_1, '|', r:Company))[1])">
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<!-- Handle the duplicates with value at least one value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1">
<xsl:if test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Find unique child nodes of an element

Need to write an xslt file. Below is the input file:
<assets>
<item>
<child1>some text</child1>
<child2>some text</child2>
<child3>some text</child3>
<child4>some text</child4>
</item>
<item>
<child1>some text</child1>
<child2>some text</child2>
<childx>some text</childx>
</item>
<item>
<child1>some text</child1>
<childx>some text</childx>
<childy>some text</childy>
<childz>some text</childz>
</item>
</assets>
I need to find out all the unique child names of assets/item. The number of children and child name is dynamic under the element (item)
The Output should be as below:
<item>
<columns>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</columns>
</item>
You can use Muenchian grouping on the element names - something like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="elements" match="*" use="local-name()" />
<xsl:template match="/">
<item>
<xsl:apply-templates select="assets/item" />
</item>
</xsl:template>
<xsl:template match="item">
<xsl:for-each select="*[count(.|key('elements', local-name())[1]) = 1]">
<columnname>
<xsl:value-of select="name()"/>
</columnname>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Will get you this:
<item>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</item>
you can use this
<xsl:key name="child" match="item/*" use="name()"/>
<xsl:template match="/">
<item>
<columns>
<xsl:for-each select="//item/*[count(.|key('child', name())[1]) = 1]">
<columnname><xsl:value-of select="name()"/></columnname>
</xsl:for-each>
</columns>
</item>
</xsl:template>
output
<item>
<columns>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</columns>
</item>

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>

Doing a pivot using XSLT

I have an xml file like this:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
I want to transform this using XSLT to get something like:
<root>
<items><status>good</status>
<name>one</name>
<name>two</name>
</items>
<items><status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items><status>ugly</status>
<name>four</name>
</items>
</root>
In other words, I get a list of items, each with a status, and I want to turn it into a list of statuses, each with a list of items.
My initial thought was to do apply-templates matching each status type in turn, but that means I have to know the complete list of statuses. Is there a better way to do it?
Thanks for any help.
Muench to the rescue!
<?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" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each select="/root/item/status[generate-id() = generate-id(key('muench',.)[1])]">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
Yes, this can be done in XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- -->
<xsl:key name="kStatByVal"
match="status" use="."/>
<!-- -->
<xsl:key name="kItemByStat"
match="item" use="status"/>
<!-- -->
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="/">
<top>
<xsl:for-each select=
"/*/*/status[generate-id()
=
generate-id(key('kStatByVal',.)[1])
]">
<items>
<status><xsl:value-of select="."/></status>
<xsl:for-each select="key('kItemByStat', .)">
<xsl:copy-of select="name"/>
</xsl:for-each>
</items>
</xsl:for-each>
</top>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the original XML document:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
The wanted result is produced:
<top>
<items>
<status>good</status>
<name>one</name>
<name>two</name>
</items>
<items>
<status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items>
<status>ugly</status>
<name>four</name>
</items>
</top>
Do note the use of:
The Muenchian method for grouping
The use of <xsl:key> and the key() function
It depends about your xslt engine. If you're using xslt 1.0 without any extension, then your approach is certainly the best.
On the other side, if you're allowed to use exslt (especially the node-set extension) or xslt 2.0, then you could do it in a more generic way:
Collect all the available statuses
Create a node-set from the obtained result
Iterating on this node set, create your pivot by filtering you status base on the current element in your iteration.
But before doing that, consider that it may be overkill if you only have a few set of statuses and that adding another status is quite rare.
In XSLT 2.0 you can replace the muenchian grouping by its standard grouping mechanism. Applied to the given answer the xslt would look like:
<?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" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="/root/item/status" group-by="key('muench', .)">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>