ancestor-or-self wont select self - xslt

There should be a fairly simple solution but I cant get it to work.
This is my code:
<xsl:value-of select="$currentPage/ancestor-or-self::* [#isDoc and backgroundImage != '' and string(umbracoNaviHide) != '1']/backgroundImage"/>
What I want is if the node has a background image attached to it by the property backgroundImage, then use that image. If it does not have an background image, check the parent node for the background image and use that.
What happends for me is that it always checks for the parents background image, even if the node itself has a background image.
Any suggestions on how I could resolve this?
Update
<site id="1000" ...>
<backgroundImage>/media/image.jpg</backgroundImage>
<textPage id="1001" ...>
<backgroundImage>/media/image2.jpg</backgroundImage>
</textPage>
<textPage id="1002" ...>
<backgroundImage />
</textPage>
</site>
If I'm on <textPage id="1002"> where backgroundImage is null I would like to get image.jpg from <site id="1000">.
If I'm on <textPage id="1001"> where backgroundImage is not null, I would like to get image2.jpg.

Numeric predicates applied to an XPath address are processed in proximity order, while the resulting nodes from the addressed are handed to an XSLT processor in document order.
In XSLT 1.0, <xsl:value-of> returns the first of the nodes returned, so the first of the nodes in document order, so if you are addressing all of the ancestors or self with given properties, you will get the farthest one, not the closest one. In XSLT 2.0 you get all of them.
Since predicates are applied in proximity order, just use:
<xsl:value-of select="$currentPage/ancestor-or-self::*[...whatever...][1]"/>
... to get the closest of those with the properties indicated by the "whatever" predicate.
The reason [last()] works in Frank's solution is that the parentheses return the enclosed nodes in document order, so the closest one of those will be the last.
But from a semantics perspective, I think it reads better to simply address the first in proximity order.

Try
<xsl:value-of select="($currentPage/ancestor-or-self::* [#isDoc and backgroundImage != '' and string(umbracoNaviHide) != '1'])[last()]/backgroundImage"/>
You get the sequence of all ancestors including the current element that fulfill the conditions with your code. If you add the last() function, you will get the last item of this sequence that fulfills the condition.

Related

XSLT 2.0 using key with except returns unexpected result

NB: title changed to reflect the problem better.
My xml documents contain an element <tei:seg #type #xml:id #corresp> which wrap little 'stories'. The attribute #corresp allows me to connect these stories to a master story. For example, these seg are all connected by their #corresp:
doc1.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc1-05']
doc2.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc2-06']
doc6.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc6-03']
My objective is: when the XSLT template finds a #corresp, find other seg in other documents with the same #corresp and output their respective `#xml:id``
So, in the above example, if the current seg was #xml:id='doc1-05', the template outputs a list: Corresponds to doc2-06, doc6-03
Until I can solve the current problems with XSLT collection() in eXist-DB, I'm falling back on my previous solution: a 'TEI corpus' xml document which maintains a master list of all related tei-xml documents via xi:include. This way I provide a single document node whereby the processor can access and search all the xml documents.
So, I declare the corpus document:
<xsl:variable name="corpus" select="doc('ms609_corpus.xml')"/>
Then create a key for the #corresp:
<xsl:key name="correspkey" match="//tei:seg[#type='dep_event' and #corresp]" use="#corresp"/>
Then I use the key with the doc() to search:
<xsl:when test="tei:seg[#type='dep_event' and #corresp]">
<xsl:variable name="correspvar"
select="data(self::seg[#type='dep_event' and #corresp]/#corresp)"/>
<xsl:text>Corresponds to </xsl:text>
<xsl:value-of select="data($corpus/(key('correspkey',$correspvar) except $correspvar)/#xml:id)" separator=", "/>
</xsl:when>
It returns the results, but the except should exclude the current #corresp. Yet it is included in the results.
The except operator works on sequences of nodes based on node identity, see https://www.w3.org/TR/xpath20/#combining_seq defining
The except operator takes two node sequences as operands and returns a
sequence containing all the nodes that occur in the first operand but
not in the second operand ... All these operators eliminate duplicate
nodes from their result sequences based on node identity
Based on that I think you simply want
<xsl:value-of select="$corpus/(key('correspkey', current()/#corresp) except current())/#xml:id)" separator=", "/>
Using data on nodes which atomizes nodes to values and then trying to use except which works on nodes doesn't seem to make sense to me.

Does node-set() have an ancestor node?

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.

Why do I have to assign this node to a variable in my stylesheet?

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()]">

identifying the null element in XSLT which has no value even in child elements

checking for an Null element
<image><a><img src="abcd"/></a></image>
XSLT template:
<xsl:if test="image!=''">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
I am getting blank output though the "image" element has child elements. and Ideally it is not null.
I have to check the condition that it should have value or the child elements. The child elements can be empty.
How to rectify this.
Thank you
Use:
image[string() or node()]
This evaluates to true() only if there is at least one image child of the current node, such that its string value is non-empty, or it has children (or both).
This can be simplified just to:
image[node()]
taking into account that in order to have string value, an element must have a text node descendant in its sub-tree.
If you want the string value of image (if any) to be not all-whitespace, modify the first of the above XPath expressions to:
image[normalize-space() or node()]
Use <xsl:if test="image/node()">...</xsl:if> to check whether the image element has any kind of child node or <xsl:if test="image/*">...</xsl:if> to test whether the image element has at least one child element.
You can use this to check for any child nodes (text, elements etc)
<xsl:template match="image">
<xsl:if test="node()">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
</xsl:template>
Or you can be more specific:
<xsl:template match="image">
<xsl:if test="a | text()">
IMAGE HAS TEXT OR INNER ELEMENT
</xsl:if>
</xsl:template>

Go up one node and see if the parent node equates a particular node in XSLT

I want to see if the parent node of the current node I am referring to is equal to a particular value. I did it as follows but no use.
eg.
<v:name>
<v:age>
when at "age" I tried <xsl:if test='.. = v:name'>. But it is not correct. What is the correct way? Can someone help?
I am inside a template which is true for both v:name and v:age. There are v:age's which are not children of v:name. I want to ensure that v:age I am referring to is a child of a v:name. That is what I want inside the test attribute.
From a comment by the OP:
I am inside a template which is true for both v:name and v:age.
There are v:age's which are not children of v:name. I want to
ensure that v:age I am referring to is a child of a v:name. That
is what I want inside the test attribute
Use:
parent::v:name
And this in a xsl:if becomes:
<xsl:if test="parent::v:name">
<!-- Whatever processing is necessary. -->
</xsl:if>
Use <xsl:if test="../v:name = 'somevalue'></xsl:if> or <xsl:if test="parent::v:name = 'somevalue'></xsl:if>
I suspect that when you say you want to know if the parent node is "equal" to a particular value, you really mean that you want to know whether its name is equal to a particular value (this would be immediately clear if you gave examples of your input and output).
If my conjecture is correct, use <xsl:if test="parent::xyz">