I have a XML document with chapters and nested sections.
I am trying to find, for any section, the first second-level section ancestor.
That is the next-to-last section in the ancestor-or-self axis.
pseudo-code:
<chapter><title>mychapter</title>
<section><title>first</title>
<section><title>second</title>
<more/><stuff/>
</section>
</section>
</chapter>
my selector:
<xsl:apply-templates
select="ancestor-or-self::section[last()-1]" mode="title.markup" />
Of course that works until last()-1 isn't defined (the current node is the first section).
If the current node is below the second section, i want the title second.
Otherwise I want the title first.
Replace your xpath with this:
ancestor-or-self::section[position()=last()-1 or count(ancestor::section)=0][1]
Since you can already find the right node in all cases except one, I updated your xpath to also find the first section (or count(ancestor::section)=0), and then select ([1]) the first match (in reverse document order, since we are using the ancestor-or-self axis).
Here is a shorter and more efficient solution:
(ancestor-or-self::section[position() > last() -2])[last()]
This selects the last of the possibly first two topmost ancestors named section. If there is only one such ancestor, then it itself is the last.
Here is a complete transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="section">
<xsl:value-of select="title"/>
<xsl:text> --> </xsl:text>
<xsl:value-of select=
"(ancestor-or-self::section[position() > last() -2])[last()]/title"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following document (based on the provided, but added more nested section elements):
<chapter>
<title>mychapter</title>
<section>
<title>first</title>
<section>
<title>second</title>
<more/>
<stuff/>
<section>
<title>third</title>
</section>
</section>
</section>
</chapter>
the correct results are produced:
first --> first
second --> second
third --> second
Related
Originally: **How to apply XPath query to a XML variable typed as element()* **
I wish to apply XPath queries to a variable passed to a function in XSLT 2.0.
Saxon returns this error:
Type error at char 6 in xsl:value-of/#select on line 13 column 50 of stackoverflow_test.xslt:
XTTE0780: Required item type of result of call to f:test is element(); supplied value has item type text()
This skeleton of a program is simplified but, by the end of its development, it is meant to pass an element tree to multiple XSLT functions. Each function will extract certain statistics and create reports from the tree.
When I say apply XPath queries, I mean I wish to have the query consider the base element in the variable... if you please... as if I could write {count(doc("My XSLT tree/element variable")/a[1])}.
Using Saxon HE 9.7.0.5.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="f:f">
<xsl:template match="/root">
<xsl:variable name="first" as="element()*">
<xsl:copy-of select="(./a[1])" />
</xsl:variable>
<html>
<xsl:copy-of select="f:test($first)" />
</html>
</xsl:template>
<xsl:function name="f:test" as="element()*">
<xsl:param name="frstElem" as="element()*" />
<xsl:value-of select="count($frstElem/a)" />
<!-- or any XPath expression -->
</xsl:function>
</xsl:stylesheet>
Some example data
<root>
<a>
<b>
<c>hi</c>
</b>
</a>
<a>
<b>
<c>hi</c>
</b>
</a>
</root>
Possibly related question: How to apply xpath in xsl:param on xml passed as input to xml
What you are doing is perfectly correct, except that you have passed an a element to the function, and the function is looking for an a child of this element, and with your sample data this will return an empty sequence.
If you want f:test() to return the number of a elements in the sequence that is the value of $frstElem, you can use something like
<xsl:value-of select="count($frstElem/self::a)" />
instead of using the (implicit) child:: axis.
How to exclude specific descendants of a node? In this direction, the expression *[not(self::nodetag)] seems just to discriminate at a child level of the node, accepting all other descedants in the returned node set. I want a expression to select all under div but those nodes that are not a, see example below. The tree structure must remain the same.
The approach poste by #Dimitri Novatchev seems to be right but not for HAP implementation:
Using this example document:
<div>
<span>
<a>lala</a>
</span>
</div>
The HAP would return the following structure with his suggested expression /div/descendant::node()[not(self::a)]
<div>
<span>
<a>lala</a>
</span>
</div>
<span>
<a>lala</a>
</span>
If there would be another tag other than a nested on span, it would also return it as a separte tree, any one know about this strange behavior? Is it a HAP bug?
Thanks
I want a expression to select all under div but those nodes that are
not a. The tree structure must remain the same.
Use:
/div/descendant::node()[not(self::a)]
This selects any descendant of the top element div that (the descendant) is not an a.
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/div/descendant::node()[not(self::a)]">
<xsl:value-of select="concat('
', position(), '. "')"/>
<xsl:copy-of select="."/>"
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<div>
<span>
<a>lala</a>
</span>
</div>
the XPath expression is evaluated and all selected nodes are output with proper formatting to make them well-visible:
1. "
"
2. "<span>
<a>lala</a>
</span>"
3. "
"
4. "lala"
5. "
"
6. "
"
As we can see, 6 nodes are selected -- one span element, four whitespace-only text nodes and one non-whitespace-only text node -- and none of them is an a.
Update:
In a comment the OP has clarified that he actually wants the XML document to be transformed into another, in which any a descendant of a div is omitted.
Here is one such transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div//a"/>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the (wwhat I guess is) wanted result is produced:
<div>
<span/>
</div>
If we want to produce only the descendants of any div that has an a descendant, then we need almost the same transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div[.//a]"><xsl:apply-templates/></xsl:template>
<xsl:template match="div//a"/>
</xsl:stylesheet>
The result of this applied to the same XML document as above is:
<span/>
#Devela: you are confusing the set of nodes selected by the XPath expression with the way that they are then displayed by the application that issued the request. It's quite common for an application to display a node by showing the whole subtree rooted at that node. So if your query is //div, and one of the selected div elements contains an <a> node as a descendant, the results will be shown including that <a> element. You can't change that by changing the XPath expression, because the XPath expression didn't select the <a> element; you can only change it by changing the way the results are displayed.
Now, if you want to display a <div> element that is like the <div> element in your source except that the <a> is omitted, then you are outside the scope of what XPath can do. XPath can only choose a subset of the nodes in your input tree, it can't create a modified tree. For that, you need XSLT or XQuery.
<merge>
<text>
<div begin="A" end="B" />
<div begin="C" end="D" />
<div begin="E" end="F" />
<div begin="G" end="H" />
</text>
</merge>
I need a UNIONed set of attribute nodes, in the order A,B,C,D,E,F,G,H, and this will work:
/merge/text/div/#begin | /merge/text/div/#end
but only if each #begin comes before each #end, since the UNION operator is spec'd to return nodes in document order. (Yes?)
I need the nodeset to be in the same order, even if the attributes appear in a different order in the document, as here:
<merge>
<text>
<div end="B" begin="A" />
<div begin="C" end="D" />
<div end="F" begin="E" />
<div begin="G" end="H" />
</text>
</merge>
That is, I need elements to follow document order, but the attributes in each element to follow a determined order (either specified or alphabetical by attribute name).
This simply isn't possible in pure XPath. First of all, attributes in XML are unordered. From the XML 1.0 Recommendation:
Note that the order of attribute specifications in a start-tag or
empty-element tag is not significant.
An XPath engine might be reading and storing them in the order they appear in the document, but in terms of the spec, this is just a happy coincidence that cannot be relied upon.
Second, XPath has no sorting functionality. So, your best option is to sort the elements in your host language (e.g. XSLT or a general-purpose PL) after they've been selected.
Here's how to sort those attributes by value in XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates
select="/merge/text/div/#*[name()='begin' or name()='end']">
<xsl:sort select="."/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Note that I also merged your two expressions into one.
Edit: Use the following to output begin/end pairs in document order (as described in the comments):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="div">
<xsl:value-of select="concat(#begin, #end)"/>
</xsl:template>
</xsl:stylesheet>
His,
I think I've got a tricky questions for XPath experts. There is a node structure like this:
A(1)-|
|-B(1)
|-B(2)
|-B(3)
A(2)-|
|-B(2.1)
|-B(2.2)
|-B(2.3)
...
How to, with a single XPath-expression, extract only the following nodes
A(1)-|
|-B(2)
|-B(3)
A(2)-|
|-B(2.2)
|-B(2.3)
...
That is for every parent node its first child element should be excluded.
I tried A/B[position() != 1] but this would filter out only B(1.1) and select B(2.1).
Thanks
This XPath expression (no preceding-sibling:: axis used):
/*/a/*[not(position()=1)]
when applied on this XML document:
<t>
<a>
<b11/>
<b12/>
<b13/>
</a>
<a>
<b21/>
<b22/>
<b23/>
</a>
</t>
selects the wanted nodes:
<b12 />
<b13 />
<b22 />
<b23 />
This can be verified with this XSLT transformation, producing the above result:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select="/*/a/*[not(position()=1)]"/>
</xsl:template>
</xsl:stylesheet>
Tricky. You could select nodes that have preceding siblings:
A/B[preceding-sibling::*]
This will fail for the first element and succeed for the rest.
I'm trying to select elements (a) with XPath 1.0 (or possibly could be with Regex) that are following siblings of particular element (b) but only preceed another b element.
<img><b>First</b><br>
<img> First Href - 19:30<br>
<img><b>Second</b><br>
<img> Second Href - 19:30<br>
<img> Third Href - 19:30<br>
I tried to make the sample as close to real world as possible. So in this scenario when I'm at element
<b>First</b>
I need to select
First Href
and when I'm at
<b>Second</b>
I need to select
Second Href
Third Href
Any idea how to achieve that? Thank you!
Dynamically create this XPath:
following-sibling::a[preceding-sibling::b[1][.='xxxx']]
where 'xxxx' is the replaced with the text of the current <b>.
This is assuming that all the elements actually are siblings. If they are not, you can try to work with the preceding and following axes, or you write a more specific XPath that better resembles document structure.
In XSLT you could also use:
following-sibling::a[
generate-id(preceding-sibling::b[1]) = generate-id(current())
]
Here is a solution which is just a single XPath expression.
Using the Kaysian formula for intersection of two nodesets $ns1 and $ns2:
$ns1[count(. | $ns2) = count($ns2)]
We simply substitute $ns1 with the nodeset of <a> siblings that follow the current <b> node, and we substitute $ns2 with the nodeset of <a> siblings that precede the next <b> node.
Here is a complete transformation that uses this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="*/b"/>
</xsl:template>
<xsl:template match="b">
At: <xsl:value-of select="."/>
<xsl:variable name="vNextB" select="following-sibling::b[1]"/>
<xsl:variable name="vA-sAfterCurrentB" select="following-sibling::a"/>
<xsl:variable name="vA-sBeforeNextB" select=
"$vNextB/preceding-sibling::a
|
$vA-sAfterCurrentB[not($vNextB)]
"/>
<xsl:copy-of select=
"$vA-sAfterCurrentB
[count(.| $vA-sBeforeNextB)
=
count($vA-sBeforeNextB)
]
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<img/>
<b>First</b>
<br />
<img/>
First Href - 19:30
<br />
<img/>
<b>Second</b>
<br />
<img/>
Second Href - 19:30
<br />
<img/>
Third Href - 19:30
<br />
</t>
the correct result is produced:
At: First First Href
At: Second Second Href
Third Href