XSLT Function Return Type - xpath

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.

Related

xpath with node(), how to express `node()[.//x]` condition?

I have a XPath that must match text and tags, except the tag <aa>; so,
./node()[name()!='aa']
is the correct xpath.
But it is insufficient for cases where tag aa is into the node, I need something like,
./node()[name()!='aa' and not(.//aa)]
but this xpath not works (!).
NOTE
I used
./*[not(self::aa or .//aa)] | ./text()
but it lost the original sequence order of the nodes. This problem is more evident when working with XSLT, example:
<xsl:for-each select="./*[not(self::aa or .//aa)] | ./text()">
<xsl:copy-of select="."/>
<xsl:for-each>
not works as expected (the order of nodes is not ensured). When using ./node() the order is always correct.
PS: with XSLT we have a solution using all the explained xpaths,
<xsl:for-each select="./node()[name()!='aa']">
<xsl:if test="not(.//aa)"><xsl:copy-of select="."/><xsl:if>
<xsl:for-each>
but the ideal/simplest one not works with the same result (when processing big and complex inputs),
<xsl:copy-of select="*[not(self::aa or .//aa)] | ./text()"/>
I'm imagining your file looks like:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<aa/>
<b>
<aa/>
</b>
<c>
<b>
<aa/>
</b>
</c>
<d/>
<e>
<b/>
</e>
</root>
Then the expression
//node()[not(descendant-or-self::aa)]
returns all nodes (including the whitespace text nodes) that are not themselves an <aa> element or have an <aa> descendant. Children of <aa> are matched as well.
You'll probably want to do something like
<xsl:copy-of select="node()[not(descendant-or-self::aa)]"/>

find next-to-last node with xpath

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

XPath 1.0 Order of returned attributes in a UNION

<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>

xslt 1.0 preceding-sibling for sorted group

I need to run a conditional action based on the preceding sibling in a sorted group. I know that the preceding-sibling function acts on the original document, not the sorted results. Is there a way to operate on the sorted results list? I do not think the muenchian grouping method is what I need because I do not want to group based on the preceding-sibling.
Given the xml below I want to sort by the value of the container, and then test to see if the type attribute of the preceding-sibling (within the sorted results) is different, if it is I need to output the value of the new #type, but I do not want the results sorted by #type.
XML
<c>
<did>
<container id="cid1059023" type="Box">C 3</container>
<container id="cid1059004" type="Map-case">C 1</container>
<container id="cid1059002" type="Binder">OSxxx-3</container>
<container id="cid1059042" type="Box">OSxxx-1</container>
</did>
</c>
<c>
<did>
<container id="cid1059025" type="Box">C 4</container>
<container id="cid1059006" type="Map-case">C 2</container>
</did>
</c>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" version="1.0">
<xsl:template match="/">
<table>
<xsl:for-each select="child::*/container[#id]">
<xsl:sort select="."/>
<tr>
<td class="container">
<xsl:if test="#type != preceding-sibling::*/#type">
<xsl:value-of select="#type"/>
</xsl:if>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Thanks.
I don't see how you can do this with XSLT 1.0 without using extension. So I would either use XSLT 2.0, or, if you have someone with a gun pointed at you yelling you shall use XSLT 1.0, then you could create a pipeline with two XSLT steps, do the sorting in a first step, and the filtering in a second.

XPath "following siblings before"

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

Resources