sjngm
sjngm

Reputation: 12861

XPath Doesn't Work in Dynamic HTML-Document

Note: This question and its answer are valid for most/all programming languages and libraries that support XPath, not just JavaScript!

With the following code that creates a very simple HTML-page (the actual code loads a remote page, but I'm trying to put your focus on the main problem here):

var dt = document.implementation.createDocumentType("html", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd");
var doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml", "html", dt);
var src = "<head></head><body></body>";
doc.documentElement.innerHTML = src;

alert(doc.evaluate(".", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue);
alert(doc.evaluate("/body", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue);
alert(doc.evaluate("//body", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue);
alert(doc.evaluate("/html", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue);

The first alert() shows "[object HTMLDocument]", the other alert() shows "null". Why is that? What am I missing to make XPath queries work and have it find the body-element?


EDIT:

Upvotes: 2

Views: 673

Answers (1)

helderdarocha
helderdarocha

Reputation: 23637

The first XPath selects the document root (. is the current context).

The second one is null because there is no body at the root context. You could use:

/html/body

or

//body

This will get you the nodes. From there you can get child nodes in context using contextual XPath expressions or DOM methods and properties. To see the node names you can use the nodeName property on the node you selected:

doc.evaluate(".", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null)
   .singleNodeValue.nodeName;
doc.evaluate("//body", doc, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null)
   .singleNodeValue.nodeName;

JSFiddle 1

This alternative version uses DOM to create the nodes.

var head = document.createElement("head");
var body = document.createElement("body");
doc.documentElement.appendChild(head);
doc.documentElement.appendChild(body);

It also enforces a namespace (which is ignored in Chrome, in the first example), so the XPath expressions either need to include a namespace mapping function (as the third parameter of the evaluate method, or ignore them (using wildcards and local name testing as in the example below).

doc.evaluate(".//*[local-name()='body']", doc.documentElement, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.nodeName

Note that I also used doc.documentElement as the context node.

Try it in your browser:

JSFiddle 2

Upvotes: 2

Related Questions