I am learning XSLT 2.0 and XPath. While creating the examples I came across to preceding axis available in XPath and created the below example.
Please find the below order.xml file used as input.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Order id="12345">
<Item>
<ItemId>007</ItemId>
<ItemName>iPhone 5</ItemName>
<Price>500</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemId>456</ItemId>
<ItemName>Ipad</ItemName>
<Price>600</Price>
<Quantity>2</Quantity>
</Item>
<Item>
<ItemId>7864567</ItemId>
<ItemName>Personal Development Book</ItemName>
<Price>10</Price>
<Quantity>10</Quantity>
</Item>
<Item>
<ItemId>123</ItemId>
<ItemName>Java Book</ItemName>
<Price>20</Price>
<Quantity>12</Quantity>
</Item>
</Order>
Please find the below XSLT testaxis.xsl file used for transformation of the above XML.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/Order/Item[ItemName='Ipad']/ItemName/preceding::*)" />
</xsl:template>
The output after transformation is
6
Here context node is below one if I am not wrong.
<ItemName>Ipad</ItemName>
If we count all the nodes which are before the context node then counts come to 5 .
Now coming to the question, why it is showing the count of the nodes as 6 in the output?
Please do let me know if I misunderstood anything
Thanks in advance.
You are correct about which node is the context node, and that node does have 6 preceding elements:
The first <Item> element.
The four elements inside that.
The <ItemId> element immediately before the context node.
That makes six. You can verify this by doing the following:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="/Order/Item[ItemName='Ipad']/ItemName/preceding::*">
<xsl:value-of select="concat(name(), '
')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The preceding elements are the ones marked below with a "*"
<*Item>
<*ItemId>007</ItemId>
<*ItemName>iPhone 5</ItemName>
<*Price>500</Price>
<*Quantity>1</Quantity>
</Item>
<Item>
<*ItemId>456</ItemId>
You will see that there are six of them.
Related
I am trying to fetch the XML value based on a condition, if the variable value matches the value of the XML path mentioned then to obtain the value of its own sub elements.
The Input XML looks like below
<ns1:productSpecificationFullDTO xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple">
<ns1:product>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:product>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:specification>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test1</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
<ns1:specification>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test2</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
My XSLT Definition is below
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple" exclude-result-prefixes="ns1 ns1">
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product/ns1:parent[text() != 'true']/../ns1:name[text() != 'Parent']/..">
<xsl:variable name="subItem" select="ns1:name/text()"/>
<Item>
<name>
<xsl:value-of select="$subItem"/>
</name>
<LongDescription>
<xsl:value-of select="normalize-space(ns1:productSpecificationFullDTO/ns1:specification/ns1:parent[text() != 'true']/../ns1:name[text() = '''$subItem''']/../ns1:Labeling/ns1:mainProductTitle/text())"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
The output is as below
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription/>
</Item>
Desired Output is
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription>Test1<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription>Test2<LongDescription/>
</Item>
As Seen above i'm unable to fetch the value of that variable's sub element.
Please advise, Thanks
I think this solves what you are trying to accomplish, simplifying your XPath expressions and using a key to get to the linked descriptions.
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full"
xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple"
exclude-result-prefixes="ns1 ns2">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="keySpec" match="ns1:specification" use="ns1:name"/>
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product[not(ns1:parent='true') and not(ns1:name='Parent')]">
<Item>
<name>
<xsl:value-of select="ns1:name"/>
</name>
<LongDescription>
<xsl:value-of select="key('keySpec',ns1:name)/ns1:Labeling/ns1:mainProductTitle"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/6qjt5Sw/1
I couldn't find this specific scenario (guess I can`t describe it right in english).
The input XML looks something like this (multiple items inside "listOne" and "listTwo" of course:
<root>
<listOne>
<listOneItem>
<ID>1</ID> // Always Unique
<SKU>ABC</SKU>
</listOneItem>
</listOne>
<listTwo>
<listTwoItem>
<ID>1</ID> / Identical to node in listOneItem, but unique (no scenario of a third element with such ID)
<STOCK>10</STOCK>
</listTwoItem>
</listTwo>
</root>
The desired output should merge the "listOne" and "listTwo" items based on the same child "ID" nodes value:
<root>
<finalItemsList>
<item>
<ID>1</ID>
<SKU>ABC</SKU>
<STOCK>10</STOCK>
</item>
</finalItemsList>
</root>
XSLT has a built-in key mechanism to resolve cross-references:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="list2" match="listTwoItem" use="ID" />
<xsl:template match="/root">
<root>
<finalItemsList>
<xsl:for-each select="listOne/listOneItem">
<item>
<xsl:copy-of select="*"/>
<xsl:copy-of select="key('list2', ID)/STOCK"/>
</item>
</xsl:for-each>
</finalItemsList>
</root>
</xsl:template>
</xsl:stylesheet>
I have the following simplified XML structure:
<?xml version="1.0" encoding="utf-8"?>
<list>
<INVOIC>
<M_INVOIC>
<G_SG25>
<S_LIN>
<id>LIN</id>
<D_1082>1</D_1082>
<C_C212>
<D_7140>7610400271943</D_7140>
<D_7143_3>EN</D_7143_3>
</C_C212>
</S_LIN>
</G_SG25>
<G_SG25>
<S_LIN>
<id>LIN</id>
<D_1082>2</D_1082>
<C_C212>
<D_7140>1234567890123</D_7140>
<D_7143_3>EN</D_7143_3>
</C_C212>
</S_LIN>
</G_SG25>
</M_INVOIC>
</INVOIC>
<INVOIC>
<SALESORDER>
<ET_VBAP>
<item>
<VBELN>0010002695</VBELN>
<POSNR>000010</POSNR>
<MATNR>000000000000400487</MATNR>
<EAN11>1234567890123</EAN11>
</item>
<item>
<VBELN>0010002695</VBELN>
<POSNR>000020</POSNR>
<MATNR>000000000000002054</MATNR>
<EAN11>5012454920549</EAN11>
</item>
<item>
<VBELN>0010002695</VBELN>
<POSNR>000030</POSNR>
<MATNR>000000000000392104</MATNR>
<EAN11>3046920921046</EAN11>
</item>
<item>
<VBELN>0010002695</VBELN>
<POSNR>000040</POSNR>
<MATNR>000000000000859146</MATNR>
<EAN11>8003340591469</EAN11>
</item>
<item>
<VBELN>0010002695</VBELN>
<POSNR>000050</POSNR>
<MATNR>000000000000727194</MATNR>
<EAN11>7610400271943</EAN11>
</item>
</ET_VBAP>
</SALESORDER>
</INVOIC>
</list>
my XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="kByEanPos" match="G_SG25" use="S_LIN/C_C212/D_7140"/>
<xsl:template match="/">
<xsl:variable name="uniqueSet" select="G_SG25[generate-id()=generate-id(key('kByEanPos',S_LIN/C_C212/D_7140))]"/>
<list>
<xsl:for-each select="list/INVOIC/M_INVOIC/G_SG25[generate-id()=
generate-id(key('kByEanPos',S_LIN/C_C212/D_7140))]">
<ean>
<xsl:value-of select="parent::M_INVOIC/parent::INVOIC/parent::list/INVOIC/SALESORDER/ET_VBAP/item/MATNR"/>
</ean>
</xsl:for-each>
</list>
</xsl:template>
</xsl:stylesheet>
gives me this XML output:
<?xml version="1.0" encoding="UTF-8"?>
<list>
<ean>000000000000400487</ean>
<ean>000000000000400487</ean>
</list>
But my expected XML output is:
<?xml version="1.0" encoding="UTF-8"?>
<list>
<ean>000000000000727194</ean>
<ean>000000000000400487</ean>
</list>
I am not sure of what I am doing wrong, I can't find my mistake. I think it has to do with the key that I defined.
Basically I need a key on <D_7140> and then look for that number in the structure below in EAN11 and output the MATNR right before.
In this line:
<xsl:value-of
select="parent::M_INVOIC/parent::INVOIC/parent::list/INVOIC/SALESORDER/ET_VBAP/item/MATNR"/>
You're climbing all the way up the node tree and back down to MATNR, so the only thing this is ever going to find is the first MATNR in the document. To locate the MATNR that corresponds to the current D_7140 in your for-each, this should work:
<xsl:value-of
select="/list/INVOIC/SALESORDER/ET_VBAP/item[EAN11 = current()/S_LIN/C_C212/D_7140]/MATNR"/>
If you are trying to look up item elements based on their EAN11 value, it might be worth considering using a key to do this too
<xsl:key name="item" match="item" use="EAN11" />
That way, you can reduce your xsl:value-of to just this
<xsl:value-of select="key('item', S_LIN/C_C212/D_7140)/MATNR"/>
I am working on a transform. The goal is to transform nodes into key/value pairs. Found a great stylesheet recommendation on this forum but I could use some help to tweak it a bit. For any node that has no children, the node name should become the value of <name> and the value should become the value of <value>. The source document may have some hierarchical structure to it, but I want to ignore that and only return the bottom nodes, transformed of course.
Here is my source data:
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<Technical_Spec__c>
<Id>a0e30000000vFmbAAE</Id>
<F247__c>4.0</F247__c>
<F248__c xsi:nil="true"/>
<F273__c>Bronx</F273__c>
...
</Technical_Spec__c>
</objects>
Here is the stylesheet:
<?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:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[count(*) = 0]">
<item>
<name>
<xsl:value-of select="name(.)" />
</name>
<value>
<xsl:value-of select="." />
</value>
</item>
</xsl:template>
<xsl:template match="*[count(*) > 0]">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
</xsl:stylesheet>
DESIRED OUTPUT - The stylesheet should transform these nodes to key value pairs like this:
<items>
<item>
<name>F247__c</name>
<value>4.0</value>
</item>
<item>
<name>F248__c</name>
<value></value>
</item>
<item>
<name>F273__c</name>
<value>Bronx</value>
</item>
...
</items>
CURRENT OUTPUT - But it creates nested 'items' elements like this:
<items>
<items>
<item><name></name><value></value></item>
...
</items>
</items>
I understand (I think) that it is matching all the parent nodes including the top node 'objects' and nesting the 'matches count 0' template. So I tried altering the matches attribute to exclude 'objects' and start at 'Technical_Spec__c' like this (just the template lines):
<xsl:template match="objects/Technical_Spec__c/*">
<xsl:template match="*[count(*) = 0]">
<xsl:template match="objects/*[count(*) > 0]">
In my mind this says "First (master) template only matches nodes with parents 'objects/Tech_Spec'. Second (inner) template matches any node with no children. Third (outer) template matches nodes with parent 'objects' " - which should limit me to one .
OUTPUT AFTER ALTERING MATCH - Here is what I get:
<?xml version="1.0" encoding="UTF-8"?>
- <items xmlns=""><?xml version="1.0"?>
<item><name>Id</name><value>a0e30000000vFmbAAE</value></item>
<item><name>F247__c</name><value>4.0</value></item>
...
</items>
The extra <items> block is gone but there is an extra <?xml> block stuck in the middle so it's not recognized as valid xml anymore.
Any ideas? Why the extra <?xml>; How to restrict template to particular parts of the tree?
Through a great deal of trial and error, I stumbled on the following solution: I added a root anchor to the third template match criteria.
Instead of match="*[count(*) > 0]", I now have /*[count(*) > 0]. This appears to eliminate the outer <items> element. If anyone can tell me why, I'd appreciate it. Why would this be different than /objects/*[count(*) > 0] ?
I do think Dimitre is right about the processor (which is IBM Cast Iron) so I did open a ticket. I tested the same stylesheet from above on an online XSLT tester and did not get the extra <?xml ?> tag.
<?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:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[count(*) = 0]">
<item>
<name>
<xsl:value-of select="name(.)" />
</name>
<value>
<xsl:value-of select="." />
</value>
</item>
</xsl:template>
<xsl:template match="/*[count(*) > 0]">
<items>
<xsl:apply-templates/>
</items>
I have a xml need to group by category
I try to use
<xsl:for-each-group select="ItemList/Item" group-by="CategoryID">
How can I get the CategoryName, CategoryID or any information related to Category
during the transformation?
Original XML
<ItemList>
<Item>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemName>ASP.NET</ItemName>
<ItemDetails>ASP.NET12345</ItemDetails>
</Item>
<Item>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemName>PHP</ItemName>
<ItemDetails>PHP12345</ItemDetails>
</Item>
<Item>
<CategoryID>2</CategoryID>
<CategoryName>Tool</CategoryName>
<ItemName>ToolAbcde</ItemName>
<ItemDetails>sth details</ItemDetails>
</Item></ItemList>
New XML
<NewXML>
<Category>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemList>
<Item>
<ItemName>ASP.NET</ItemName>
<ItemDetails>ASP.NET12345</ItemDetails>
</Item>
<Item>
<ItemName>PHP</ItemName>
<ItemDetails>PHP12345</ItemDetails>
</Item>
<ItemName>PHP</ItemName>
</ItemList>
</Category>
<Category>
<CategoryID>2</CategoryID>
<CategoryName>Tool</CategoryName>
<ItemList>
<Item>
<ItemName>ToolAbcde</ItemName>
<ItemDetails>sth details</ItemDetails>
</Item>
</ItemList>
</Category></NewXML>
The xslt spec has an extensive doucmentation on grouping, complete with examples at http://www.w3.org/TR/xslt20/#xsl-for-each-group. You are probably looking for the current-group() and current-grouping-key() functions. Also
Within the sequence constructor, the context item is the initial item of the relevant group, the context position is the position of this item among the sequence of initial items (one item for each group) arranged in processing order of the groups, the context size is the number of groups, the current group is the group being processed, and the current grouping key is the grouping key for that group.
Meaning that these two expressions would be equivalent inside xsl:for-each-group: CategoryId and current-group()[1]/CategoryId. Since CategoryId is the grouping key this is also the same as current-grouping-key(). Here is a complete example for your use case:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" />
<xsl:template match="/">
<NewXml>
<xsl:for-each-group select="ItemList/Item" group-by="CategoryID">
<Category>
<CategoryID><xsl:value-of select="CategoryID" /></CategoryID>
<CategoryName><xsl:value-of select="CategoryName" /></CategoryName>
<ItemList>
<xsl:for-each select="current-group()">
<Item>
<ItemName><xsl:value-of select="ItemName" /></ItemName>
<ItemDetails><xsl:value-of select="ItemDetails" /></ItemDetails>
</Item>
</xsl:for-each>
</ItemList>
</Category>
</xsl:for-each-group>
</NewXml>
</xsl:template>
</xsl:stylesheet>