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>
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 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.
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"/>
Good Day,
I have an XSLT template I'm assembling that look like:
<xsl:for-each select="CarParts/Items">
<div class="columns"><xsl:value-of select="Quantity"/></div>
<div class="columns"><xsl:value-of select="Amount"/></div>
<div class="columns">[SUBTOTAL]</div><br />
</xsl:for-each>
I know that I can define an XSLT variable like this:
<xsl:variable name="totalAmount" select="sum(CarParts/Items/Amount)" />
But I want my XSLT variable to be [SUBTOTAL] which is equal to Quantity * Amount within the for-each select loop. Is this possible? If this was SQL, this would be the equivalent of a computed column.
Any suggestions?
TIA,
coson
What you want to do is cast the value to a number, then you can multiply it as desired:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<results>
<xsl:for-each select="CarParts/Items">
<Item id="{position()}">
<q><xsl:value-of select="Quantity"/></q>
<a><xsl:value-of select="Amount"/></a>
<st><xsl:value-of select="number(Quantity)*number(Amount)"/></st>
</Item>
</xsl:for-each>
</results>
</xsl:template>
</xsl:stylesheet>
I changed the formatting a little since there was no provided input/CSS, but you should see what I was going for. Running it on my sample input of
<CarParts>
<Items>
<Quantity>1</Quantity>
<Amount>100.00</Amount>
</Items>
<Items>
<Quantity>2</Quantity>
<Amount>25.00</Amount>
</Items>
<Items>
<Quantity>3</Quantity>
<Amount>6</Amount>
</Items>
</CarParts>
I get the result of
<?xml version="1.0" encoding="utf-8"?>
<results>
<Item id="1">
<q>1</q>
<a>100.00</a>
<st>100</st>
</Item>
<Item id="2">
<q>2</q>
<a>25.00</a>
<st>50</st>
</Item>
<Item id="3">
<q>3</q>
<a>6</a>
<st>18</st>
</Item>
</results>
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>