JavaScript Get Xpath of a Node

Javascript get XPath of a node

There's not a unique XPath to a node, so you'll have to decide what's the most appropriate way of constructing a path. Use IDs where available? Numeral position in the document? Position relative to other elements?

See getPathTo() in this answer for one possible approach.

How do i get the xpath of an element in an X/HTML file

You can extract this functionality from an XPath tool I once wrote:

http://webkitchen.cz/lab/opera/xpath-tool/xpath-tool.js


Edit: here you go:

function getXPath(node) {
var comp, comps = [];
var parent = null;
var xpath = '';
var getPos = function(node) {
var position = 1, curNode;
if (node.nodeType == Node.ATTRIBUTE_NODE) {
return null;
}
for (curNode = node.previousSibling; curNode; curNode = curNode.previousSibling) {
if (curNode.nodeName == node.nodeName) {
++position;
}
}
return position;
}

if (node instanceof Document) {
return '/';
}

for (; node && !(node instanceof Document); node = node.nodeType == Node.ATTRIBUTE_NODE ? node.ownerElement : node.parentNode) {
comp = comps[comps.length] = {};
switch (node.nodeType) {
case Node.TEXT_NODE:
comp.name = 'text()';
break;
case Node.ATTRIBUTE_NODE:
comp.name = '@' + node.nodeName;
break;
case Node.PROCESSING_INSTRUCTION_NODE:
comp.name = 'processing-instruction()';
break;
case Node.COMMENT_NODE:
comp.name = 'comment()';
break;
case Node.ELEMENT_NODE:
comp.name = node.nodeName;
break;
}
comp.position = getPos(node);
}

for (var i = comps.length - 1; i >= 0; i--) {
comp = comps[i];
xpath += '/' + comp.name;
if (comp.position != null) {
xpath += '[' + comp.position + ']';
}
}

return xpath;

}

It might need some changes if you want it to work in IE as well.

Get element's xpath in javascript

There are many ways howto access an element with XPath. For example you can access it by node name or by the value of one of it's attributes or child nodes. So you can not expect that javascript gives you exactly one of them.

But as you have the id, the simplest xpath query to access the element would be:

//*[@id="THE_ID"]

Is there a way to get element by XPath using JavaScript in Selenium WebDriver?

You can use document.evaluate:

Evaluates an XPath expression string and returns a result of the
specified type if possible.

It is w3-standardized and whole documented: https://developer.mozilla.org/en-US/docs/Web/API/Document.evaluate

function getElementByXpath(path) {  return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;}
console.log( getElementByXpath("//html[1]/body[1]/div[1]") );
<div>foo</div>

How to calculate the XPath position of an element using Javascript?

Firebug can do this, and it's open source (BSD) so you can reuse their implementation, which does not require any libraries.

3rd party edit

This is an extract from the linked source above. Just in case the link above will change. Please check the source to benefit from changes and updates or the full featureset provided.

Xpath.getElementXPath = function(element)
{
if (element && element.id)
return '//*[@id="' + element.id + '"]';
else
return Xpath.getElementTreeXPath(element);
};

Above code calls this function.
Attention i added some line-wrapping to avoid horizontal scroll bar

Xpath.getElementTreeXPath = function(element)
{
var paths = []; // Use nodeName (instead of localName)
// so namespace prefix is included (if any).
for (; element && element.nodeType == Node.ELEMENT_NODE;
element = element.parentNode)
{
var index = 0;
var hasFollowingSiblings = false;
for (var sibling = element.previousSibling; sibling;
sibling = sibling.previousSibling)
{
// Ignore document type declaration.
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
continue;

if (sibling.nodeName == element.nodeName)
++index;
}

for (var sibling = element.nextSibling;
sibling && !hasFollowingSiblings;
sibling = sibling.nextSibling)
{
if (sibling.nodeName == element.nodeName)
hasFollowingSiblings = true;
}

var tagName = (element.prefix ? element.prefix + ":" : "")
+ element.localName;
var pathIndex = (index || hasFollowingSiblings ? "["
+ (index + 1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}

return paths.length ? "/" + paths.join("/") : null;
};

How to get Element by XPATH in JavaScript?

Look at document.evaluate()

function getElementByXpath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function myFunction() {
let x = getElementByXpath("//html[1]/body[1]/button[1]");
x.style.fontSize = "25px";
x.style.color = "red";
}
<p id="demo">Click the button to change the layout of this paragraph</p>

<button onclick="myFunction()">Click Me!</button>

Using XPath in node.js

This is most likely caused by the default namespace (xmlns="http://www.w3.org/1999/xhtml") in your HTML (XHTML).

Looking at the xpath docs, you should be able to bind the namespace to a prefix using useNamespaces and use the prefix in your xpath (untested)...

var exampleLookup = `//*[@id='System_Console_WriteLine_System_String_System_Object_System_Object_System_Object_']/parent::x:div/following-sibling::x:div/x:pre[position()>1]/x:code[contains(@class,'lang-csharp')]`;
var doc = new dom().parseFromString(rawHtmlString, 'text/html');
var select = xpath.useNamespaces({"x": "http://www.w3.org/1999/xhtml"});
var sampleNodes = xpath.select(exampleLookup,doc);

Instead of binding the namespace to a prefix, you could also use local-name() in your XPath, but I wouldn't recommend it. This is also covered in the docs.

Example...

//*[@id='System_Console_WriteLine_System_String_System_Object_System_Object_System_Object_']/parent::*[local-name()='div']/following-sibling::*[local-name()='div']/*[local-name()='pre'][position()>1]/*[local-name()='code'][contains(@class,'lang-csharp')]

Node.js XPath example?

The problem is that you're taking the string value of a nodeset, which is by definition the string value of the first node. Iterate over the selected nodes in JavaScript, and only take the string value of each individually rather than call the XPath string() function on the entire nodeset collectively.

It's also unclear why you'd want a nested loop rather than a single loop over all relativePath elements, but perhaps that was an artifact of the reduction of your complete program to a posted example. In any case, if you wish to restrict your inner loop to only those relativePath elements beneath the current content-item context node, use .//relativePath instead.

Finally, your XML is not well-formed.

Node.js xmldom / xpath working example

I've fixed the above problems in the example below (and indented the XML to ease reading):

var xpath = require('xpath')
var dom = require('xmldom').DOMParser

var le = `
<content>
<data>
<content-item>
<relativePath>/abc/</relativePath>
<text>abc</text>
<leaf>false</leaf>
<lastModified>2018-10-16</lastModified>
</content-item>
<content-item>
<relativePath>/defghi/</relativePath>
<text>defghi</text>
<leaf>false</leaf>
<lastModified>2018-06-23</lastModified>
</content-item>
<content-item>
<relativePath>/jklmn/</relativePath>
<text>jklmn</text>
<leaf>false</leaf>
<lastModified>2019-02-27</lastModified>
</content-item>
</data>
</content>`;

var doc = new dom().parseFromString(le);
var nodes = xpath.select("//relativePath", doc);
nodes.forEach( (n, i) => {
console.log(n.textContent);
});

Output

/abc/
/defghi/
/jklmn/

as requested.



Related Topics



Leave a reply



Submit