How to select unique child nodes of all siblings in XSLT 1 - xslt

I'm looking for the best way to get all unique (no duplicates) nested nodes of all sibling nodes. The node I'm am interested in is "Gases". The sibling nodes are "Content". My simplified XML:
<Collection>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>O2</Gases>
</root>
</Html>
</Content>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>CO</Gases>
<Gases>LEL</Gases>
<Gases>NH3</Gases>
</root>
</Html>
</Content>
</Collection>
Desired result: NO2 CH4 O2 CO LEL NH3
I'm new to XSLT so any help would be much appreciated. I've been trying to use XPATH, similar to here, but with no luck.

This XSLT stylesheet will produce the desired output. Note that it relies on there being no duplicate Gases element inside a single Content element.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Match Gases elements whose value does not appear in a Gases element inside a previous
Content element. -->
<xsl:template match="//Gases[not(. = ancestor::Content/preceding-sibling::Content//Gases)]">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:template>
<!-- Need to override the built-in template for text nodes, otherwise they will still get
printed out. -->
<xsl:template match="text()"/>
</xsl:stylesheet>

Related

How do I match on any node that itself or any child has an attribute with a value in XSLT Template?

Say I have XML data like this:
<root>
<subs>
<sub>
<values>
<value attribute="a">1</value>
<value attribute="a">2</value>
<value attribute="c">3</value>
<value attribute="c">4</value>
</values>
</sub>
<subOther>
<otherValues attribute="c">
<otherValue attribute="a">1</value>
<otherValue attribute="a">2</value>
<otherValue attribute="b">3</value>
<otherValue attribute="a">4</value>
</otherValues>
</subOther>
</subs>
</root>
I am trying to create an XSLT template that matches all the nodes in the path to /root/subs/subOther/otherValues/otherValue[attribute="b"].
So far, this is the closest I have gotten:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="//*[ancestor-or-self::[#attribute='b']]">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But that throws an error saying there is an unexpected token [. I have tried several combinations but they either don't match anything at all, match too much (i.e. everything), or they throw some sort of error.
Edit: I updated the example and expected to be a little more clear. Also note that this is a highly-simplified XML. In my actual file the attribute in question can be at any leaf node on any valid element for that level, so I have to use a more generic path using * and unknown paths with //. So, for instance, one of the value elements could be the one with attribute="b" and it would trigger the same result.
Edit 2: The expected result is to select the nodes that have a path that lead to any left-child w/ an attribute that is equal to a specific value. In my XSD schema there's a total of about 100 possible leaf nodes spread all over the place. The use case is that the attribute in question marks which data elements have had changes, and I need to basically create a "diff" where the full file is whittled down to only nodes where the results are only those items that have changed and their parents. In the small example above, attrubute="b" is the indication I need to copy that node, and thus I would expect this exact result:
<root> <!-- Copied because part of the path -->
<subs> <!-- Copied because part of the path -->
<sub> <!-- Copied because part of the path -->
<values> <!-- Copied because part of the path -->
<value attribute="b">3</value> <!-- Copied because it matches the attribute -->
</values>
</sub>
</subs>
</root>
I hope that makes better sense. Also, I fixed the typo on the xsl:stylesheet being self-closing.
It looks like you have changed the identity template to ignore elements (the change will also drop attributes and text nodes), and added a template to copy the elements you need.
I think you need to reverse your logic. Instead of thinking about things you want to copy, think of it as removing things you don't want to copy.
So, you have the identity template to do the generic copying of elements, and have a second template to remove the things you don't want (the elements which don't have a "b" attribute either on its self or its descendants).
Try this XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--IDENTITY TEMPLATE -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(descendant-or-self::*[#attribute = 'b'])]" />
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/ncntCS6

XPath Recursive Descent behaves differently between match and select

Info
Why is there different behaviour of Recursive Descent operator between
● template's match attribute where it is ignored and only children are selected ignoring their descendants
● for-each's select attribute where it works properly
Two examples of test.xsl are given which both operate on the following test.xml.
test.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<people>
<person id="(1)">
<name>Lucy</name>
</person>
<class>
<person id="(2)">
<name>David</name>
<person id="(21)">
<name>David</name>
</person>
</person>
</class>
</people>
match="//person"
In this example we are trying to use match="//person" to select ALL
person elements from document which doesn't work. Instead of
selecting ALL root descendants person elements, person elements which
are inside other person elements (like id="(21)") are not
included.
test.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()"/>
<xsl:template match="//person">
<xsl:value-of select="#id"/>
</xsl:template>
</xsl:stylesheet>
output.xml
(1)(2)
select="//person"
In this example we are using select="//person" to select ALL person
elements from the document. This will properly select ALL root
descendants person elements including id="(21)". Value of
match="class" is irrelevant since select="//person" uses absolute
path.
test.xsl
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()"/>
<xsl:template match="class">
<xsl:for-each select="//person">
<xsl:value-of select="#id"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
output.xml
(1)(2)(21)
There is a difference between a match pattern and a select expression. A match pattern does not select anything - it is only used to see if the current node matches the pattern. For this reason, the match pattern match="//person" is the same as match="person".
If a node never gets to be the current node (i.e. templates are not applied to it), then it doesn't matter if it matches any template's match pattern or not. This is shown in your first example: the built-in template rules are applied recursively until the <person id="(2)"> node is encountered; this matches a pattern of a more specific template - but that template has no instructions to apply templates to any of its descendants, so the chain breaks at this point, and the node <person id="(21)"> is never examined to see if there is a template matching it.
Well a template with a match on its own does not cause any processing and your template with
<xsl:template match="//person">
<xsl:value-of select="#id"/>
</xsl:template>
outputs the id attribute and does no further processing. So you will need to make sure you have an apply-templates e.g.
<xsl:template match="//person">
<xsl:value-of select="#id"/>
<xsl:apply-templates/>
</xsl:template>
or you will need a template like
<xsl:template match="/">
<xsl:apply-templates select="//person"/>
</xsl:template>
that makes sure all person elements are processed.
Also note that a match="//person" is the same as match="person" so all you need is
<xsl:template match="/">
<xsl:apply-templates select="//person"/>
</xsl:template>
and
<xsl:template match="person">
<xsl:value-of select="#id"/>
</xsl:template>

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>

How to copy all child nodes of any type of a template context element

I am transforming XML into HTML using XSLT.
I have the following XML structure:
<root>
<element>
<subelement>
This is some html text which should be <span class="highlight">displayed highlighted</span>.
</subelement>
</element>
</root>
I use the following template for the transformation:
<xsl:template name="subelement">
<xsl:value-of select="." />
</xsl:template>
Unfortunately, I lose the <span>-tags.
Is there a way to keep them so the HTML is displayed correctly (highlighted)?
The correct way to get the all the contents of the current matching node (text nodes included) is:
<xsl:template match="subelement">
<xsl:copy-of select="node()"/>
</xsl:template>
This will copy everything descendent.
Try using <xsl:copy-of... instead of <xsl:value-of... for example:
<xsl:template name="subelement">
<xsl:copy-of select="*" />
</xsl:template>
Note the * which will stop the <subelement></subelement> bits being output to the results, rather than using . which will include the <subelement></subelement> bits .
For example, the xsl stylesheet:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="root/element">
<output>
<xsl:apply-templates select="subelement"/>
</output>
</xsl:template>
<xsl:template match="subelement">
<xsl:copy-of select="*"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example xml file returns:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<span class="highlight">displayed highlighted</span>
</output>
The <xsl:value-of> declaration takes the concatenated contents of all text nodes within the element, in sequential order, and doesn't output any elements at all.
I'd recommend using <xsl:apply-templates> instead. Where it finds a text node, it will output the contents as-is, but you would need to define a template for handling span tags to convert them to html ones. If that span tag IS an html tag, then strictly speaking, you should have separate namespaces for your own document structure and html.

XSLT node Traversal

Here is a snip-it of the XML:
<?xml version="1.0" encoding="iso-8859-1" ?>
<NetworkAppliance id="S123456">
<Group id="9">
<Probe id="1">
<Value>74.7</Value>
</Probe>
</NetworkAppliance>
I want to get the single point value of 74.7. There are many groups with unique ID's and many Probes under that group with unique ID's each with values.
I am looking for example XSLT code that can get me this one value. Here is what i have that does not work:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version="3.2" />
<xsl:template match="NetworkAppliance">
<xsl:apply-templates select="Group[#id='9']"/>
</xsl:template>
<xsl:template match="Group">
Temp: <xsl:value-of select="Probe[#id='1']/Value"/>
<br/>
</xsl:template>
</xsl:stylesheet>
Here is what worked for me in the end:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="NetworkAppliance/Group[#id=9]/Probe[#id=1]">
Value: <xsl:value-of select="Value" />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Don't forget that you can do select several levels at once. Fixing your XML to:
<?xml version="1.0" encoding="iso-8859-1" ?>
<NetworkAppliance id="S123456">
<Group id="9">
<Probe id="1">
<Value>74.7</Value>
</Probe>
</Group>
</NetworkAppliance>
and using this stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version="3.2" />
<xsl:template match="/">
Temp: <xsl:value-of select="//Group[#id='9']/Probe[#id='1']/Value"/>
<br/>
</xsl:template>
</xsl:stylesheet>
we can pick out that one item you're interested in.
Points to note:
The // part of the expression means that the search for Group nodes takes place throughout the whole tree, finding Group nodes at whatever depth they're at.
The [#id='9'] part selects those Group nodes with id of 9
The Probe[#id='1'] part immediately after that selects those children of the Group nodes it found where the id is 1, and so on.
<xsl:value-of select="/NetworkAppliance/Group[#id=9]/Probe[#id=1]/Value"/>
XSLT is just one of the tools in the box, and nothing without XPath.
the xpath for value of a node is /node/text()
So
<xsl:value-of select="Probe[#id='1']/text()"/>