Determining whether a node is contained within another node in XSLT - xslt

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?

Related

Constructing, not selecting, XSL node set variable

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.

XSLT/Xpath -- sum function performance

I often use this xpath sum(preceding::*/string-length())
It does what I need it to do (provides a character count of all text up to this context in the XML file).
Problem: it is slow.
Is there a different built in function that I should be using instead? Or an extension?
UPDATE:
Based on Michael Kay's comment, I explored XSLT 3.0 <accumulator>. It was my first try with 3.0 (I had to update OxygenXML to make it work). I haven't fully adapted it to my needs, but initial test below shows promise.
<xsl:output method="xml" />
<xsl:accumulator
name="f:string-summ"
post-descent="f:accum-string-length"
as="xs:integer"
initial-value="0">
<xsl:accumulator-rule
match="text/*"
new-value="$value + string-length()"/>
</xsl:accumulator>
<xsl:template match="text/*">
<xsl:value-of select="f:accum-string-length()" />
</xsl:template>
Off topic: Stack Overflow needs an "XSLT-3.0" tag.
If you call this function on every node, then you stylesheet performance will be O(n^2) in the number of nodes.
The function is incorrect anyway. The preceding axis gives you your parent's preceding siblings and also the children of your parent's preceding siblings, so the string length of your cousins is being counted more than once.
Try defining a memo function something like this:
<xsl:function name="f:preceding-string-length" saxon:memo-function="yes">
<xsl:param name="n" as="element()"/>
<xsl:sequence select="sum(ancestor::*/preceding-sibling::*[1]/(f:preceding-string-length(.) + string-length(.)))"/>
</xsl:function>
Or use an XSLT 3.0 accumulator, which amounts to much the same thing.
I don't think the sum function is slow, the navigation to all preceding elements and the computation of the string length of all contents is expensive. As for optimizing it, which XSLT 2.0 processor do you use?

XPath: Get root node of a node-set from a specified node

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(.)

XSLT: Check if a value exists in a list

So, I have a variable containing a nodeset with several Size nodes
<xsl:variable name="sizes" select="$filter/Size" />
I then, need to do a sum on another nodeset, where the Size/#ID exists in this $sizes variable
<xsl:value-of select="sum(Sizes/Size[ **where #ID in $sizes/#ID** ]/#Value)"/>
But I'm struggling on how I write this XPath...in xslt 1.0
<xsl:value-of select="sum(Sizes/Size[#ID = $sizes/#ID]/#Value)"/>
if I understand your spec correctly.
This works because of "existential quantification": A = B means "some member of node set A is equal to some member of node set B". (In your case, A has at most only one member anyway.)

Using dynamic xpath in XSLT

I am using a variable say xpathvar in the XSLT whose value will be supplied at the time of call to XSLT. How can i achieve this?
My XSLT file looks like -
<xsl:param name="xpathvar"/>
<xsl:param name="keyxpath"/>
<xsl:template match="/">
<listofResults>
<xsl:for-each select="$xpathvar">
<keyvalues><xsl:value-of select="xalan:evaluate(substring-before($keyxpath,'||'))"/></keyvalues>
<keyvalues><xsl:value-of select="xalan:evaluate(substring-after($keyxpath,'||'))"/></keyvalues>
</xsl:for-each>
</listofResults>
</xsl:template>
</xsl:stylesheet>
If I mention the variable in the for-each, it throws error. Please guide how can I achieve this.
Thanks!
According to Global xsl:param containing xpath expression string it can't be done purely with plain old XSLT, you need to use the evaluate extension see: Xalan evaluate expression
I dont think this can be done like this you must use xpath so example would be
<template match="Node[name=$xpathvar]" />
<xsl:for-each select="*[name()=$xpathvar]">
</xsl:for-each>
I think it does not let you use the variable directly because it is not a nodeset.
If I mention the variable in the
for-each, it throws error
In XSLT 1.0 (it looks you are using Xalan), you can iterate over node sets, only. So $xpathvar should be an instance of node set data type.
In XSLT 2.0 you can iterate over sequence (including scalar values).
Also, if the string containing the "dynamic" XPath expression is simple enough (only QName test step, maybe positional predicates) this could be done (as is already answered in SO) with standar XSLT.