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>
Related
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.
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>
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>
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>
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>