4

Given XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <c cid="c0" v="100">
        <d did="c00" v="0" dt="2016-01-01" />
        <d did="c01" v="0" dt="2016-02-03" />
        <d did="c02" v="0" dt="2016-01-15" />
    </c>
    <c cid="c1" v="100">
        <d did="c10" v="1" dt="2016-01-01" />
        <d did="c11" v="0" dt="2015-03-03" />
        <d did="c12" v="0" dt="2015-04-15" />
    </c>
</root>

I have to find XPath 2.0 or 3.0, which will return to me list of c nodes by expression: find maximum d node of this c children by dt attribute (parse it as date), and if its v attribute value is "0", return the c node.

I expect to get a one c node (with @cid="c0") as a result, because /root/c[cid="c1"]/d[@dt="2016-01-01"]/@v is not equal to 0 (is equal to 1).

I have stuck on this XPath sofar:

/root/c[d[max(xs:date(@dt))]/@v="0"]

It gives me an error (I'm using Oxygen XML Editor, XPath 3.0):

XPath failed due to: Effective boolean value is not defined for a sequence starting with an atomic value other than a boolean, number, string, or URI

I suppose this is due to misused max function, but cannot get it working.

UPDATES:

  1. Lets assume that c nodes will have d children with unique maximum value of their dt attributes.
  2. I also tried XPath 3.0 expression:

    /root/c[d[@dt=max(.//@dt/xs:date(.))]/@v="0"]

but it returned me all nodes (wrong result).

lospejos
  • 1,976
  • 3
  • 19
  • 35
  • 2
    What to do if node c will have more than one d node with the same maximal dt attribute value and different v attribute values? – M.Void Oct 05 '16 at 10:09

2 Answers2

2

In XQuery the query is quite straight-forward and can be written to only calculate the maximum date once per c node:

for $c in /root/c
let $max := max($c/d/@dt/xs:date(.))
where $c/d[@dt = $max]/@v = '0'
return $c

In XPath you can write the query to (at least conceptually) calculate it once per d node and hope that the result is either optimized by the processor or fast enough anyway:

/root/c[d[@dt = max(../d/@dt/xs:date(.))]/@v = '0']

You can also use the let expression of XPath to recover the explicit pre-computation:

/root/c[
  let $max := max(d/@dt/xs:date(.))
  return d[@dt = $max]/@v = '0'
]
Leo Wörteler
  • 4,191
  • 13
  • 10
  • 1
    Leo Wörteler, there is in fact an XPath 2.0 expression that calculates the maximum just once per a `` element. – Dimitre Novatchev Oct 06 '16 at 03:14
  • 1
    You could abuse a `for`, `some`, or `every` expression to iterate over a single item and thus emulate the `let`, is that what you're refering to? **Edit:** Oh, I see. Very nice! – Leo Wörteler Oct 06 '16 at 08:37
2

Here is a shorter and probably more efficient (the max per a <c> element is calculated just once) XPath 2.0 one-liner:

/*/c[max(d/xs:date(@dt)) = d[@v eq '0']/@dt]

Enjoy!

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • BTW, when I used equal sign `=` instead of `eq` literal in this expression in Oxygen 18 XPath 3 editor, it also worked fine. – lospejos Oct 07 '16 at 07:47
  • 1
    @lospejos, Yes, but in XPath 2.0 the `eq` operator is specifically designed to handle comparissons between atomic values. The purpose of the general comparison operator `=` is to be used in comparissons where at least one of the operands is a sequence. – Dimitre Novatchev Oct 07 '16 at 14:02