1
votes

Given a node list and a current node within that list, is there a way to use javax.xml.xpath (or org.apache.xpath perhaps) to evaluate position dependent XPath expressions, for example:

position()=last()-1

javax.xml.xpath contains:

evaluate(String expression, Object item, QName returnType) Evaluate an XPath expression in the specified context and return the result as the specified type.

I guess I could create a temporary node, add the nodes in the node list as children, and pass the child corresponding to the current node to evaluate as the context, but (assuming that even works) is there a better way?

Alternatively, in XSLT 1.0, suppose I have these three things (the node list, the node, and the xpath expression (as a string) ) as variables. Is it possible to apply this expression to the node, using the node list as context, and get the result as a variable?

3

3 Answers

3
votes

Most XPath APIs, and certainly the JAXP API, only allow you to set a singleton focus, that is, a focus in which you can choose any item as the context item, but the context position and size are fixed at 1.

1
votes

Xalan Java supports http://www.exslt.org/dyn/functions/evaluate/index.html so with that you should be able to use e.g.

<xsl:variable name="nodes" select="/root/foo/bar"/>
<xsl:variable name="expression" select="concat('$nodes/', $yourPath)"/>
<xsl:variable name="result" select="dyn:evaluate($expression)"/>

(with xmlns:dyn="http://exslt.org/dynamic" defined of course).

Does that help? I am not sure I understand the section of having a node list and a single node and wanting to use both as a context.

As for the Java side and finding the node before the last one in a DOM NodeList, shouldn't nodeList.getItem(nodeList.getLength() - 2) suffice?

0
votes

This post from 7 years ago suggests a Xalan specific non-XSLT solution might be available, but implementation independence and Michael's response pushed me in the direction of looking for a working XSLT1-based solution.

Here it is; I'm not sure whether it'll be useful to anyone else. I wonder whether there is a simpler way?

You can rely on XSLT, provided you set everything up correctly. From the spec:

In XSLT, an outermost expression (i.e. an expression that is not part of another expression) gets its context as follows:

the context node comes from the current node

the context position comes from the position of the current node in the current node list; the first position is 1

Starting at the end, I get the result of applying expression $expression into a variable $result:

        <xsl:variable name="result" >
            <xsl:apply-templates select="$vNodeSet" mode="myeval">
                <xsl:with-param name="expression" ><xsl:value-of select="$expression"/></xsl:with-param>
                <xsl:with-param name="pos" ><xsl:value-of select="$pos"/></xsl:with-param>
            </xsl:apply-templates>
        </xsl:variable> 

That apply-templates pushes a suitable "current node list" to the following template:

  <xsl:template match="*" mode="myeval">
    <xsl:param name="expression">1. </xsl:param>
    <xsl:param name="pos">3</xsl:param>

    <xsl:choose>
        <xsl:when test="position()=$pos">
            <xsl:value-of select="dyn:evaluate($expression)" /></xsl:when>
        <xsl:otherwise /> 
    </xsl:choose>

  </xsl:template>

This template evaluates the expression for the node I want to be the "current node". Notice:

  1. the use of dy:evaluate (thanks Martin!)
  2. $pos which identifies what I want as the current node.

I was able to calculate $pos using Dimitre's answer to an earlier question; I've also used his variable name vNodeSet

Thanks Dimitre, Martin, and Michael!