How to find the lowest common ancestor of two nodes in XQuery?

217 views Asked by At

Suppose the input XML is

<root>
<entry>
    <title>Test</title>
    <author>Me</author>
  </entry>
</root>

I would like to find the lowest common ancestor of title and author. I tried the following code in BaseX:

 let $p := doc('t.xq')//title, 
 $q := doc('t.xq')//author, 
 $cla := ($p/ancestor-or-self::node() intersect $q/ancestor-or-self::node()) 
 return 
    $cla

But it returns nothing (blank output).

3

There are 3 answers

3
Jens Erat On BEST ANSWER

Your code works totally fine for me, apart from returning all common ancestors.

The Last Common Ancestor

Since they're returned in document order and the last common ancestor must also be the last node, simply extend with a [last()] predicate.

declare context item := document {
  <root>
    <entry>
      <title>Test</title>
      <author>Me</author>
    </entry>
  </root>
};

let $p := //title, 
    $q := //author, 
    $cla := ($p/ancestor-or-self::node() intersect $q/ancestor-or-self::node())[last()]
return 
    $cla

Files and Databases

If the query you posted does not return anything, you might be working on a file t.xq. intersect requires all nodes to be compared in the same database, each invocation of doc(...) on a file creates a new in-memory database. Either create a database in BaseX with the contents, or do something like

declare variable $doc := doc('t.xq');

and replace subsequent doc(...) calls by $doc (which now references a single in-memory database created for the file).

3
har07 On

This is one possible way :

let $db := doc('t.xq'), 
$q := $db//*[.//title and .//author][not(.//*[.//title and .//author])]
return 
  $q

brief explanation :

  • [.//title and .//author] : The first predicate take into account elements having descendant of both title and author.

  • [not(.//*[.//title and .//author])] : Then the 2nd predicate applies the opposite criteria to the descendant elements, meaning that overall we only accept the inner-most elements matching the first predicate criteria.

output :

<entry>
  <title>Test</title>
  <author>Me</author>
</entry>
0
Ahmad On

I changed doc('t.xq') in front of the variables $p and $q with the variable $db as follows. Now it works (plus, I used the last() to have the last (lowest) common ancestor).

let 
$db := doc('t.xq'),
$p := $db//title, 
$q := $db//author, 
$cla := ($p/ancestor-or-self::node() intersect $q/ancestor-or-self::node())[last()] 
return $cla