mahdaeng
mahdaeng

Reputation: 791

XQuery ancestor axis doesn't work, but explicit XPath does

Consider the following XML snippet:

<doc>
    <chapter id="1">
        <item>
            <para>some text here</para>
        </item>
    </chapter>
</doc>

In XQuery, I have a function that needs to do some things based on the ancestor chapter of a given "para" element that is passed in as a parameter, as shown in the stripped down example below:

declare function doSomething($para){
    let $chapter := $para/ancestor::chapter
    return "some stuff"
};

In that example, $chapter keeps coming up empty. However, if I write the function similar to the follwing (i.e., without using the ancestor axis), I get the desired "chapter" element:

declare function doSomething($para){
    let $chapter := $para/../..
    return "some stuff"
};

The problem is that I cannot use explicit paths as in the latter example because the XMl I will be searching is not guaranteed to have the "chapter" element as a grandparent every time. It may be a great-grandparent or great-great-grandparent, and so on, as shown below:

<doc>
    <chapter id="1">
        <item>
            <subItem>
                <para>some text here</para>
            </subItem>
        </item>
    </chapter>
</doc>

Does anyone have an explanation as to why the axis doesn't work, while the explicit XPath does? Also, does anyone have any suggestions on how to solve this problem?

Thank you.


SOLUTION:

The mystery is now solved.

The node in question was re-created in another function, which had the result of stripping it of all of its ancestor information. Unfortunately, the previous developer did not document this wonderful, little function and has cost us all a good deal of time.

So, the ancestor axis worked exactly as it should - it was just being applied to a deceptive node.

I thank all of you for your efforts in answering my questions.

Upvotes: 0

Views: 2563

Answers (4)

Clark Richey
Clark Richey

Reputation: 392

The ancestor axis does work fine. I suspect your problem is namespaces. The example you showed and that I ran (below) has XML without any namespaces. If your XML have a namespace then you would need to provide that in the ancestor XPath, like this: $para/ancestor:foo:chapter where in this case the prefix _foo_ is bound to the correct namespace for the chapter element.

let $doc := <doc>
    <chapter id="1">
        <item>
            <para>some text here</para>
        </item>
    </chapter>
</doc>

let $para := $doc//para
return $para/ancestor::chapter

RESULT:

<?xml version="1.0" encoding="UTF-8"?>
<chapter id="1">
  <item>
    <para>some text here</para>
  </item>
</chapter>

Upvotes: 5

Oliver Hallam
Oliver Hallam

Reputation: 4262

These things almost always boil down to namespaces! As a daignostic to confirm 100% that namespace are not the issue, can you try:

declare function local:doSomething($para) {
    let $chapter := $para/ancestor::*[local-name() = 'chapter']
    return $chapter
};

Upvotes: 2

Evan Lenz
Evan Lenz

Reputation: 4136

I suspect namespaces too. If $para/../.. works but $para/parent::item/parent::chapter turns up empty, then you know it's a question of namespaces.

Look for an xmlns declaration at the top of your content, e.g.:

<doc xmlns="http://example.com">
  ...
</doc>

In your XQuery, you then need to bind that namespace to a prefix and use that prefix in your XQuery/XPath expressions, like this:

declare namespace my="http://example.com";
declare function doSomething($para){
  let $chapter := $para/ancestor::my:chapter
  return "some stuff"
};

What prefix you use doesn't matter. The important thing is that the namespace URI (http://example.com in the above example) matches up.

It makes sense that ../.. selects the element you want, because .. is short for parent::node() which selects the parent node regardless of its name (or namespace). Whereas ancestor::chapter will only select <chapter> elements that are not in a namespace (unless you have declared a default element namespace, which is usually not a good idea in XQuery because it affects both your input and your output).

Upvotes: 1

Christian Gr&#252;n
Christian Gr&#252;n

Reputation: 6229

This seems surprising to me; which XQuery implementation are you using? With BaseX, the following query...

declare function local:doSomething($para) {
    let $chapter := $para/ancestor::chapter
    return $chapter
};

let $xml :=
  <doc>
    <chapter id="1">
        <item>
            <para>some text here</para>
        </item>
    </chapter>
</doc>
return local:doSomething($xml//para)

...returns...

<chapter id="1">
  <item>
    <para>some text here</para>
  </item>
</chapter>

Upvotes: 1

Related Questions