I feel, this is a basic XSLT question. From how I understand, a node-set is data structure in itself. From a node-set() I cannot navigate to any node that is outside of the set. i.e I cannot reach the parent, the beginning of the xml nor its siblings. Is this right?
or
Is there a way to get a parent of a node-set()
Code
<neighbourhood>
<parent name = "xyz">
<child address=10> a </child>
<child address=10> b </child>
<child address=15> c </child>
</parent>
</neighbourhood>
I have a set of child nodes. I need to eliminate nodes with duplicate 'address'.
There can be n number of 'parent' and m number of 'child' and there could be a grandparent node for the 'child'.
The best and the logical way is probably go by each parent and process their children. But, it is an existing code base and this is an oversimplified example. I do not want to break too many things by touching the caller function and other templates.
So, my question was if I could get the 'parent' with the set of 'child' nodes I have
Thanks for all your responses
Does node-set() have an ancestor node?
node-set() is a (extension) function -- not a node. A function cannot have ancestor, because it is not a node.
I suppose that by "node-set()" in the question, you mean the value that is returned by the xxx:node-set() function (where the prefix "xxx" is bound to a vendor-specific namespace). If so, here is the wanted answer:
By definition, the xxx:node-set() function returns the document-node() (also known as root-node in XPath 1.0) of a temporary tree, which is obtained by converting the RTF (Result Tree Fragment), passed as the only argument to this function.
A document-node by definition is at the top of the document hierarchy and is the only node in an XML document, that doesn't have a parent.
Therefore, the node returned by a called xxx:node-set() function doesn't have any ancestors.
From a node-set() I cannot navigate to any node that is outside of the set. i.e I cannot reach the parent, the beginning of the xml nor its siblings. Is this right?
Yes, without calling another function that returns a node from another document (such as the standard XPath function id() or the standard XSLT function document()), or referencing a variable/parameter, it is not possible to navigate to a node from another document only by using XPath location steps.
or
Is there a way to get a parent of a node-set()
No, the node returned by the xxx:node-set() function is a document node and a document node doesn't have a parent (or any other ancestor) node.
Don't mix up node sets and node-set()s.
What do I mean by this? Well, a node set is a set of nodes. In normal, unextended XSLT 1.0, this means a selection of nodes from your input document. If I do this:
<!-- a node set -->
<xsl:variable name="my-node-set"
select="/indoc/level1/level2"/>
the variable $my-node-set contains a set of level2 nodes, but those nodes still live in the input document. If I subsequently do a for-each like so:
<nodeset-from-indoc>
<xsl:for-each select="$my-node-set/level3">
<parent>
<xsl:value-of select="local-name(..)"/>
</parent>
<grandparent>
<xsl:value-of select="local-name(../..)"/>
</grandparent>
</xsl:for-each>
</nodeset-from-indoc>
I will get the names of the parents and grandparents of each node:
<nodeset-from-indoc>
<parent>level2</parent><grandparent>level1</grandparent>
<parent>level2</parent><grandparent>level1</grandparent>
<parent>level2</parent><grandparent>level1</grandparent>
</nodeset-from-indoc>
If, however, I hard-code nodes into a variable:
<!-- a result-tree fragment -->
<xsl:variable name="my-rtf">
<level2>
<level3>1</level3>
</level2>
<level2>
<level3>2</level3>
</level2>
<level2>
<level3>3</level3>
</level2>
</xsl:variable>
this is not a node set, but a result tree fragment, since they weren't selected from the input document. The problem with result tree fragments is that you can't use XPath on them. I can't, for example, do this:
<xsl:for-each select="$my-rtf/level3">
This is where the node-set() function comes in. It is an extension to XSLT 1.0, which comes from some extension namespace, depending on your XSLT processor. Many processors choose to implement this in the namespace defined by EXSLT.
As Dmitre points out, the node-set() function returns a magic document node of a temporary tree, allowing you to use XPath. However, this causes a subtle shift in how the select needs to be done. Because of the magic document node, I have to include level2 in my selection:
<nodeset-from-rtf>
<xsl:for-each select="exsl:node-set($my-rtf)/level2/level3">
<parent>
<xsl:value-of select="local-name(..)"/>
</parent>
<grandparent>
<xsl:value-of select="local-name(../..)"/>
</grandparent>
</xsl:for-each>
</nodeset-from-rtf>
And in this case, the level3 nodes will have parents, but no grandparents:
<nodeset-from-rtf>
<parent>level2</parent><grandparent/>
<parent>level2</parent><grandparent/>
<parent>level2</parent><grandparent/>
</nodeset-from-rtf>
A node-set is a set of nodes. Each node in the node-set has ancestors. The node-set itself does not. If $NS is a node-set, you can do $NS/ancestor::node(): this will give you all the ancestors of all the nodes in the node-set, with duplicates eliminated.
Related
I have an XML with the structure similar to below.
<root>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<parent2>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<parent>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<randomElement>..</randomElement>
<ITEM>..</ITEM>
</parent>
</parent2>
</root>
Note: there can be any number of <randomElement>s at the places
where it's specified.
So, right now, my pointer is at the <ITEM> tag. I need to return the value inside of the <EFFECT> tag, but, here's the catch.
If it's present, I must return the value of the <EFFECT> tag which is inside <parent> tag. If it's not present there, I must return value of <EFFECT> tag which is inside the <parent2> tag. Again, if it is not present there too, I need to finally return the value of the <EFFECT> tag which is inside <root>. The <EFFECT> inside the <root> will always be present and there can be any number of parents for the <ITEM> element.
Sorry if it's confusing.
To go to any <ITEM>, this is sufficient.
//ITEM
Now, <EFFECT> is a sibling to <ITEM>, i.e. it's on the same level. Another way of thinking about siblings is that they are children of the same parent.
In fact, all <EFFECT> elements in question are children of some ancestor of <ITEM>. This means we can move upwards along the ancestor:: axis and grab all those ancestor elements in one step:
//ITEM/ancestor::*
This will give us <parent>, <parent2> and <root>, in this order.
And from those we only need to take one step down to grab all <EFFECT> elements:
//ITEM/ancestor::*/EFFECT
This will give us three EFFECT elements, this time again in document order (only the ancestor:: type of axis works inside out).
We are interested in the last one of those, because this will be closest to the <ITEM> we started from. The last() function will help here:
(//ITEM/ancestor::*/EFFECT)[last()]
The parentheses around the path are necessary, because otherwise the condition ([last()]) is tested on every <EFFECT> individually, and each one of them is the last one of its kind for its parent, giving us three matches. The parentheses make it so that first a node-set of three <EFFECT> elements is constructed and then the last one is taken, giving us only one match.
When you already currently are at the <ITEM> element, the relative version of the path selects the last effect for that particular item:
(ancestor::*/EFFECT)[last()]
I want to retrieve any Node with the child nodes childTypeA, childTypeB or childTypeC, but not return nodes with only other child nodes (like stepChildA).
Once I have that node, I can retrieve any of the child nodes and their attributes. But I can't figure how to filter out those nodes that do not have any child nodes matching childTypeA, childTypeB or childTypeC.
My efforts either return all nodes with children, or return a node for each matching child, which means the same node is returned one, two or three times, depending upon if there is one, two or all three of the desired child nodes.
With xml data as shown
<parent Name="Item one">
<OtherData Name="Data one">
<childTypeA>
<someData Name="Child A">
</childTypeA>
<childTypeB>
<someData Name="Child B">
</childTypeB>
</parent>
<parent Name="Item two">
<OtherData Name="Data two">
<childTypeB>
<someData Name="Child B">
</childTypeB>
<childTypeC>
<someData Name="Child C">
</childTypeC>
</parent>
<parent Name="Item three">
<OtherData Name="Data three">
<stepChildA>
<someData Name="Step Child A">
</stepChildA>
</parent>
The actual data under each child type is different and I'm trying to assemble it into a table, where each parent node with the desired child type appear on a single row and the child data align under the appropriate columns. Currently I've either had all parent nodes where the data desired appears as intended, but also includes rows with the other parent nodes which have no data, or, I am getting multiple rows when there is more than one of the desired child type. The specific child type data fall into the proper columns, but are not on one row.
My approach was correct, but I needed to re-order my code. I had the "if test" before the "for-each". By swapping them, I was able to return all parent nodes, but then use the "xsl:if test=..." to ignore the unwanted parents and build each table row as I parse through the child nodes. Note I added a sort to the returned parent nodes, based upon the value of their attribute #Name.
<xsl:for-each select="parent">
<xsl:sort select="#Name">
<xsl:if test="childTypeA or childTypeB or childTypeC">
<tr>.........</tr>
</xsl:if>
</xsl:for-each>
My XML data is very verbose and I am trying not to overwhelm the post. I am also struggling with the formatting rules.
As far as I can tell, you simply need to use the xpath parent[childTypeA | childTypeB | childTypeC] like this:
<xsl:for-each select="parent[childTypeA | childTypeB | childTypeC]">
<xsl:sort..etc.
</xsl:for-each>
I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.
I'm working on an XSLT stylesheet. I have a a node (node A) with a bunch of children, and I'm looping through another node's (node B) children. I'm trying to do something each time a child of node B is also a child of node A, so I have this code:
<xsl:if test="$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]">
But that doesn't work (the test fails; the expression returns false) even though the left and right side of the expression, when evaluated separately, are equal.
But when I do this:
<xsl:variable name="curbin" select="/root/Line[1]/Element[6]/text()"/>
<xsl:if test="$prodbins/bin[./text()=$curbin]">
The expression evaluates to true. Why do I have to use the $curbin variable to get the result I'm expecting?
Can you try <xsl:if test="$prodbins/bin[./text()=current()/root/Line[1]/Element[6]/text()]"> (notice current() function). The reason why it does not work in your original expression is that because you query a variable and / looks up the root node of the content of the variable and not the source document you are transforming. current() should return the context element for the template you are in.
My guess is that $prodbins/bin is a node(-set) belonging to a different document than the document that contains the nodes that are being compared to.
In the expression:
$prodbins/bin[./text()=/root/Line[1]/Element[6]/text()]
the subexpression
/root/Line[1]/Element[6]/text()
selects from the same document as the one that is the document from which the $prodbins/bin nodes are selected.
One way to specify successfully the wanted comparisson is:
<xsl:variable name="vDoc" select="/"/>
<xsl:if test="$prodbins/bin[./text()=$vDoc/root/Line[1]/Element[6]/text()]">
Within an XSL sheet, I have a nodeset in a variable, $context.
How do I properly query an attribute of the topmost node in $context? Obviously, if $context was the current node, I would just write "#attname", but it is not. Currently I do $context/../#attname which is doesn't look great to me.
EDIT
Ok, the data.
Here is what I see in the VS.
In case you wonder how I got this strange thing: I UNION'ed a node with it's subnodes and an attribute selected out from a different node:
<xsl:with-param name="context" select=". | ../#res" />.
I'm not completely aware what results from this, but I can see that it works. Highlighted is the attribute I'm interested in.
Maybe that creates an attribute attached to nothing, if that makes sence at all :|
$context/../#attname
does not make too much sense. You can't go "up" here, as this would bring you "outside of" $context.
If the node-set contains something like this (a single node)
<node attname="foo">
<bar />
</node>
then:
$context/#attname
If it is like this (a list of nodes):
<node attname="foo">
<bar />
</node>
<node attname="foo">
<bar />
</node>
then:
$context[1]/#attname
All of this does not work if the variable contains a result tree fragment (RTF). In this case, you need to employ an extension function called node-set(). Most XSLT processors provide this function.
EDIT: Your variable holds a union of the current node and a naked attribute node from its parent:
<xsl:with-param name="context" select=". | ../#res" />
The result of a union will always be in document order, so even though you selected the attribute after the context node in the XPath, in the resulting node set it will come before - the parent of the context node is before the context node in document order.
You need $context[1] to grab the attribute node, and $context[2] to grab the other node.
I must say that this is some strange and probably unnecessary complicated use of variables. I'm sure there is a way to do this in a less painful fashion. For example you could do
<xsl:with-param name="context" select="." />
and then use $context/../#res in the called template. That would be a lot more straight-forward than what you are trying now.
Also, if the <xsl:with-param> you show here is part of an <xsl:call-template>, you can drop that param entirely. When a template is called (instead of applied), then the context node does not change, passing it in is redundant.
In addition to tomalak's answer if you ever do need to propagate back up to the root you could try:
ancestor::*[not(..)]
Would love to hear of a situation where you might want/need this though.
Try it yourself by pasting:
//pet/ancestor::*[not(..)]
in this online Xpath test tool.
As you have <xsl:with-param name="context" select=". | ../#res" /> the 'res' attribute is part of the node-set (XPath 1.0) or sequence (XPath 2.0) the variable named 'context' is bound to. With XPath 2.0 you could use $context/self::attribute(res) but XPath 1.0 has no comparable expression so what you have already ($context[1] or $context/../#res) is all you can do in my view.