How to restrict which nodes produce output in stylesheet - templates

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>

Related

Usage of the Variable inside the select-value clause to traverse through the XML path

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

Merging two different parent nodes content based on the same child value (ID) - XSL Transform

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>

Issue while using Preceding axis in XPath and XSLT

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.

XPath relative path in expression

I am in 'group' node. From it, I want to find such 'item' node, that has 'id' attribute equals to current's 'group' node 'ref_item_id' attribute value. So in my case, by being in 'group' node B, I want 'item' node A as output. This works:
<xsl:value-of select="preceding-sibling::item[#id='1']/#description"/>
But this doesn't (gives nothing):
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
When I type:
<xsl:value-of select="#ref_item_id"/>
I have '1' as result. So this attribute is for sure accessible, but I can't find path to it from XPath expression above. I tried many '../' combinations, but couldn't get it work.
Code to test: http://www.xmlplayground.com/7l42fo
Full XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
</root>
Full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This has to do with context. As soon as you enter a predicate, the context becomes the node currently being filtered by the predicate, and no longer the node matched by the template.
You have two options - use a variable to cache the outer scope data and reference that variable in your predicate
<xsl:variable name='ref_item_id' select='#ref_item_id' />
<xsl:value-of select="preceding-sibling::item[#id=$ref_item_id]/#description"/>
or make use of the current() function
<xsl:value-of select="preceding-sibling::item[#id=current()/#ref_item_id]/#description"/>
Your expression searches for an item whose id attribute matches its own ref_item_id. You need to capture the current ref_item_id in an xsl:variable and refer to that xsl:variable in the expression.
One more possible solution using xsl:key
<xsl:key name="kItemId" match="item" use="#id" />
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="key('kItemId', #ref_item_id)[1]/#description"/>
</xsl:for-each>
</xsl:template>
Looking at the XML, if I assume that you have <item> and <group> as siblings and in any order.
Then a sample input XML would look like the following.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
<item description="D" id="1"/>
<group description="E" ref_item_id="2"/>
</root>
Now, if the goal is to extract the description of all the <item> nodes whose id is matching with corresponding <group> *nodes ref_item_id*. Then we can simply loop over only such <item> nodes and get their description.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[(./#id=following-sibling::group/#ref_item_id) or (./#id=preceding-sibling::group/#ref_item_id)]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Since you say that nodes are having unique id and all nodes are placed before nodes.
I would recommend you to use the following XSL and loop over specific nodes instead of nodes.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[./#id=following-sibling::group/#ref_item_id]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 text nodes printing by default

I have looked at XSL xsl:template match="/" but the match pattern that triggered my question is not mentioned there.
I have a rather complex XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<MATERIAL_DATA>
<LOG>
<USER>Peter</USER>
<DATE>2011-02-18</DATE>
<MATERIALS>
<item>
<MATNR>636207</MATNR>
<TEXTS>
<item>
<TEXT>granola bar 40gx24</TEXT>
</item>
</TEXTS>
<PRICES>
<item>
<MATNR>636207</MATNR>
<COST>125.78</COST>
</item>
</PRICES>
<SALESPRICES>
<item>
<B01>
<MATNR>636207</MATNR>
<CURR>CZK</CURR>
<DATBI>9999-12-31</DATBI>
<DATAB>2010-10-05</DATAB>
</B01>
<B02>
<item>
<PRICE>477.60</PRICE>
<KUNNR>234567</KUNNR>
</item>
</B02>
</item>
</SALESPRICES>
</item>
</MATERIALS>
</LOG>
</MATERIAL_DATA>
Now if I apply the following XSLT, my output looks correct:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I get the output:
<?xml version="1.0" encoding="UTF-8"?>
<Mi>234567</Mi>
But if I apply the XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:element name="MenuItems">
<xsl:apply-templates select="LOG/MATERIALS/item/SALESPRICES/item"/>
</xsl:element>
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
the output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems>
636207
CZK
9999-12-31
2010-10-05
<Mi>234567</Mi>
</MenuItems>
All values from the element <B01> are in the output! But why - I am not matching <B01>!?
How does
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
make the output come out correctly? All I am doing with this is match all nodes or attributes and apply-templates to everything or all attributes.
But in my opinion it should not make a difference to when I exactly match <B01>!
Why is this happening?
XSLT includes the following default templates (among others):
<!-- applies to both element nodes and the root node -->
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
<!-- copies values of text and attribute nodes through -->
<xsl:template match="text()|#*">
<xsl:value-of select="."/>
</xsl:template>
In your first stylesheet you're implicitly matching all text nodes with node(), thus overriding the default action. Then, in the B2 template, you output your target value and apply no further templates, which stops processing.
In the second stylesheet, you explicitly apply templates to all children of LOG/MATERIALS/item/SALESPRICES/item, causing the default templates to process the nodes you don't explicitly handle. Because you explicitly handle B2 without applying templates to its children, the default templates are never invoked for those nodes. But the default templates are applied to the children of B1.
Adding the following template to your second stylesheet would override the default action for text nodes:
<xsl:template match="text()|#*"></xsl:template>
With the following result:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems><Mi>234567</Mi></MenuItems>
More:
http://www.w3.org/TR/xslt#built-in-rule
Looks like you are running into the built in template rules.
Specifically the text rule - this will copy text nodes if not overridden.