maxschlepzig
maxschlepzig

Reputation: 39095

How to restrict an XPath via xmlXPathNodeEval() to a subtree?

To evaluate an XPath expression only inside a certain subtree the libxml2 function xmlXPathNodeEval() seems to be the way to go. The documentation specifies that the XPath expression is evaluated 'in the given context'. But what does this exactly mean?

Consider following small example:

#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xmlstring.h>
#include <stdio.h>
int main(int argc, char **argv)
{
  const char inp[] =
    "<root>\n"
    " <sub>\n"
    "  <e>0</e>\n"
    "  <Foo>\n"
    "    <e>1</e><e>2</e><e>3</e>\n"
    "    <FooSub><e>3a</e></FooSub>\n"
    "  </Foo>\n"
    "  <Bar>\n"
    "    <e>4</e><e>5</e><e>6</e>\n"
    "  </Bar>\n"
    " </sub>\n"
    " <e>7</e>\n"
    "</root>\n";
  xmlDoc *doc = xmlParseMemory(inp, sizeof(inp)-1);
  xmlXPathContext *ctx = xmlXPathNewContext(doc);
  xmlXPathObject *p = xmlXPathEval(BAD_CAST "//Foo[1]", ctx);

  xmlNode *new_root = *p->nodesetval->nodeTab;
  printf("New root: %s\n", BAD_CAST new_root->name);
  xmlXPathObject *q = xmlXPathNodeEval(new_root, BAD_CAST argv[1], ctx);

  for (int i = 0; i<q->nodesetval->nodeNr; ++i) {
    const xmlChar *cnt = xmlNodeGetContent(q->nodesetval->nodeTab[i]);
    printf("%s ", BAD_CAST cnt);
    xmlFree((xmlChar*)cnt);
  }
  puts("");

  xmlXPathFreeObject(q);
  xmlXPathFreeObject(p);
  xmlXPathFreeContext(ctx);
  xmlFreeDoc(doc);
  return 0;
}

Compiled via (in my case using version 2.9.1 of libxml):

$ gcc -g -std=c99 -Wall -I/usr/include/libxml2 -lxml2 relative.c

For the call

$ ./a.out '//e'

I would expect the following output:

New root: Foo
1 2 3 3a

But instead I get:

New root: Foo
0 1 2 3 3a 4 5 6 7

It seems that I have to use the self::node() axis specifier (short .) to get the result I want, i.e.:

$ ./a.out './/e'
New root: Foo
1 2 3 3a 

Basically I was interpreting the sentence 'Evaluate the XPath Location Path in the given context' from the documentation as: the XPath expression is evaluated in the self::node() context at that given node - but since one has to explicitly specify self::node() this is not the case.

Thus, a related question is perhaps: is the behavior of libxml2 and its usage of the term 'context' consistent to the XPath specification?

Upvotes: 2

Views: 1305

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167601

XPath selects nodes in an input tree and a path starting with the slash / selects nodes downwards from the document node of the context node. So as you have found out correctly, if you want to select descendants relative to your context node, you need .//foo. As an alternative, you can use descendant::foo.

Upvotes: 5

Related Questions