|
Summary
Imagine you're developing an XSLT debugger and need some way of finding and displaying all "xsl:variable"-s that are "visible" from the current node.
Is it at all possible to specify them using a single XPath expression? Read further to find out...
This is a real problem and here’s how it was specified by its author in the xslt mailing list:
Well it seems I've hit a small snag what I want is to get a list of variables in a scope so the closest is taken and passed on. The XML looks something like:
<A> <P name=hello value=blue/> <P name=aval value=uppest/> <B> <P name=bval value=upper/> <P name=goodbye value=red/> <P name=hello value=green/> <C> <P name=goodbye value=yellow/> </C> <P name=abcdef value=orange/> <xxx> <P name=goodbye value=purple/> </xxx> </B> <D> <P name=goodbye value=red/> </D> </A>
And my current context is C. With my output looking something like goodbye=yellow;hello=green;bval=upper;aval=uppest;
So far I've got <xsl:for-each select=ancestor-or-self::*/p[ what on earth can I put here ]> <xsl:value-of select=@name/>=<xsl:value-of select=@value/>; </xsl:for-each>
But I've got stuck in the way that MSXML3 seems to be serving up the axis in document order not reverse document order like it says on P717 or Michael Kays excellent book. Any and all help would be appreciated.
A number of persons (actually some of the best experts) sent long XSLT codeor said some kind of recursive processing had to be used. Let’s now concentrate on a pure XPath solution.
It would be useful to have the XPath Visualizer already started (never heard of the XPath Visualizer? Hmmm… now I understand why XPath is so difficult to you… just give it a try and you’ll know what I mean.
To verify that the sub-expressions we’d be building incrementally really behave as we’d expected them to. First of all, what does in scope mean? 1. Well, any P that’s defined as a child of C-s parent or as a child of any of C-s ancestor nodes.
Why ancestor node and not just element? Because the root node / is a node – not an element –we’d like not to exclude any of the top-level <P>s. Do you agree with the definition in 1.? Hmmm… not entirely – C will not be able to see any P children of its ancestors that are defined (follow) after C. Therefore: 2. Exclude from 1. all nodes following C
Now the most important requirement: in case there are several P-s with the same nameattribute, we want just the innermost of these: 3. Take from (1. and 2.) only such nodes, whose name is not duplicated in any of their descendents.
Now we are ready to construct the whole XPath expression: For 1. we’ll have: //P[parent::C or count(.. | //C/ancestor::node())=count(//C/ancestor::node() )]
For 3. we have: and not(@name=../descendant::node()/P/@name )
For 2. we have: and count(. | //C/following::P) != count(//C/following::P )
So, let’s combine these together: //P[parent::C or count(.. | //C/ancestor::node()) = count(//C/ancestor::node() ) and not(@name=../descendant::node()/P/@name ) and count(. | //C/following::P) != count(//C/following::P )]
Here I’m using the following to specify the fact that a node nd belongs to a node-set ns: count(nd | ns) = count(ns) and, similarly, to specify that nd does not belong to ns: count(nd | ns) != count(ns)
In case you find it difficult to understand the last two expressions, do have a look at another snippet that explains the Kaysian method of intersecting two node-sets.
A last remark: We’re using //C here – just because we’re solving a particular example problem. For the general case simply replace this with current().
|