Is it possible to write an XPath expression that gets the root node of a node within a node-set with only a reference to the node?
Using "/" won't work for me because that only refers to the input document root. Also I would like it to work without a context and to use it for a general node-set that may be created dynamically during processing.
For example...
<xsl:function name="my:getRoot">
<xsl:param name="n" />
<xsl:variable name="rootnode" select="some_solution($n)"/>
</xsl:function>
Thanks for the help.
In XPath 1.0 use:
ancestor-or-self::node()[last()]
This selects the most distant of the ancestors of the current node -- which is its document-node.
In XPath 2.0 use:
root(.)
Related
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 have faced an issue when using a variable as a condition for XPath evaluation. I have the following template which works fine:
<xsl:template name="typeReasonDic">
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
<!-- do something with the variable -->
</xsl:template>
However, when I change it to look like this:
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
it fails to find the desired node.
What I wish to get is a template with a default value for $choose_dic which can be overriden where necessary.
What am I missing here?
UPD: there is this link I found with the description of what I'm trying to do, but it doesn't seem to work for me.
You can't do this directly in XSLT 1.0 or 2.0 without an extension function. The problem is that with
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
the <xsl:param> will evaluate its select expression a single time in the current context and store the true/false result of this evaluation in the $choose_dic variable. The <xsl:variable> will therefore select either all xs:simpleType elements under the $schema (if $choose_dic is true) or none of them (if $choose_dic) is false. This is very different from
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
which will evaluate #name = 'type_reason_et' repeatedly, in the context of each xsl:simpleType, and select those elements for which the expression evaluated to true.
If you store the XPath expression as a string you can use an extension function such as dyn:evaluate or the XSLT 3.0 xsl:evaluate element if you're using Saxon.
By doing
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
the XSL engine will try to evaluate "#name = 'type_reason_et'" as an XPath expression, and will assign the RESULT to your variable.
You should use the following variable declaration instead:
<xsl:param name="choose_dic">#name = 'type_reason_et'</xsl:param>
This is the default value, but you can override it when you call your template by using xsl:with-param.
XSLT is not a macro language where you might be able to concatenate your code at run-time from strings and then evaluate them dynamically. So in general for your purpose you would need an extension function to evaluate an XPath expression stored in a string or you need to look into a new XSLT 3.0 features like http://www.saxonica.com/documentation/xsl-elements/evaluate.xml.
What is possible in the scope of XSLT 1.0 or 2.0 is doing e.g.
<xsl:param name="p1" select="'foo'"/>
<xsl:variable name="v1" select="//bar[#att = $p1]"/>
where the param holds a value you compare to other value, for instance those in a node like an attribute or element node.
I've got an xsl:variable that contains a pre-processed set (DoesNotContainChildElement). I think msxsl:node-set() is adding a root element so position is always 1. But what I need it the top 15 elements.
<xsl:variable name="Summary">
<xsl:for-each select="msxsl:node-set($DoesNotContainChildElement)">
<xsl:if test="position() < 16">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
</xsl:for-each>
No, the function msxsl:node-set does not add any root node, it is simply that with XSLT 1.0 a sample like
<xsl:variable name="rtf1">
<item>1</item>
<item>2</item>
<item>3</item>
</xsl:variable>
creates a result tree fragment and "A result tree fragment is treated equivalently to a node-set that contains just a single root node". So in the sample above we have a result tree fragment with a single root node containing three item child elements.
Applying the msxsl:node-set(rtf1) extension function then gives you a node-set instead of a result tree fragment where now the node-set contains a single root node with three item child elements. Thus if you want to access the item elements you need msxsl:node-set($rtf1)/* or more general msxsl:node-set($rtf1)/node() to access all child nodes.
You may try msxsl:node-set($DoesNotContainChildElement)/*. If it's true that msxsl:node-set() adds a root node, then adding /* to your path will iterate over the children, where you can test for position.
Alternatively you could simply use <xsl:apply-templates select="msxsl:node-set($DoesNotContainChildElement)/*" mode="testposition"/> and <xsl:template match="*" mode="testposition">….
There's only one variable $DoesNotContainChildElement so a for-each will only yield value 1 for position().
You can check that by running the following:
<xsl:for-each select="$x">
<pos1><xsl:value-of select="position()"/></pos1>
</xsl:for-each>
<xsl:for-each select="$x/*">
<pos2><xsl:value-of select="position()"/></pos2>
</xsl:for-each>
Where x is a (node-set type) variable.
The result will look something like
<pos1>1</pos1>
<pos2>1</pos2>
<pos2>2</pos2>
<pos2>3</pos2>
<pos2>...</pos2>
Adding <xsl:copy-of select="."/> will result in the output of the entire variable contents in case of the first for-each above, whereas for the second for-each it will result in the output of each sub-element of the variable one-by-one.
The second form is the one to use if you wish to output only selected sub-elements.
The same holds when you first apply the node-set function to change an rtf into a node-set.
Is it possible to tell whether a node is contained within (or equal to) another node in XSLT? For example, consider this code snippet:
<xsl:variable name="itemSection" select=".."/>
<xsl:for-each select="key('enemyItems', #key)">
<xsl:variable name="enemyList" select="./attributes/#value"/>
<xsl:variable name="enemyListSection" select="../../.."/>
.
.
.
</xsl:for-each>
Is it possible to tell whether itemSection is contained within (or equal to) enemyListSection?
In XPath 1.0
$itemSection[ancestor::*[generate-id()=generate-id($enemyListSection)]]
In XPath 2.0
$itemSection[ancestor::*[. is $enemyListSection]]
Just a small adjustment to Alejandro's answer:
In XPath 1.0
$itemSection[ancestor-or-self::*[generate-id()=generate-id($enemyListSection)]]
In XPath 2.0
$itemSection[ancestor-or-self::*[. is $enemyListSection]]
Because the original question asked:
Is it possible to tell whether
itemSection is contained within (or
equal to) enemyListSection?
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.