getElementXPath = function(elm) {
for (segs = []; elm && elm.nodeType == 1; elm = elm.parentNode) {
if (elm.hasAttribute('id')) {
segs.unshift('id("' + elm.getAttribute('id') + '")')
return segs.join('/')
}
else if (elm.hasAttribute('class'))
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]')
else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling)
if (sib.localName == elm.localName) i++
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']')
}
}
return segs.length ? '/' + segs.join('/') : null
}
Refactorings
No refactoring yet !
latcho
March 2, 2011, March 02, 2011 06:21, permalink
Hi thanks for this !
I was implementing this in Adobe Air's htmlcomponent.
What I discovered was that you stop the xpath tree immediatly when the element has an id.
But what if a bunch of containers in a table all share the same id ? Then I can never get the path to a unique "Prijs" node.
It isn't my html content, it was just a case I met.
So is it valid and possible (i'm no xpath connoisseur) to just disable line #5) // return segs.join('/')
so that we keep on going building the xpath tree when we have an id?
I did so but wonder or this will result in a valid xpath like I pasted below 2 lines (the td's en tr's have different indexes) ?
cheers,
Latcho
/html[1]/id("webshop")/id("container")/table[1]/tbody[1]/tr[4]/tr[3]/tr[3]/tr[2]/tr[2]/tr[1]/td[1]/id("productwrap")/id("productPrijs")/id("Prijs")/span[@class="Prijs"]
/html[1]/id("webshop")/id("container")/table[1]/tbody[1]/tr[3]/tr[2]/tr[2]/tr[1]/td[2]/td[2]/td[1]/id("productwrap")/id("productPrijs")/id("Prijs")/span[@class="Prijs"]
latcho
March 2, 2011, March 02, 2011 10:32, permalink
hey i fixed some bugs if the page uses multiple nodes with the same id's
it was only tested on safari's webkit so I don't know or the test with 'document.all[id]' is crossbrowser and always returns a collection if multiple of the same id's are there.
I added a test function getElementFromXPath(generatedPath) and it seems to work flawless now!
At least I know some more about xpath now :) So Ignore my previous post !
Cheers,
Latcho
function createElementXPath(elm) {
for (segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
{
if (elm.hasAttribute('id')) {
if (document.all[elm.getAttribute('id')].length == 1 || document.all[elm.getAttribute('id')].length == undefined) {
segs.unshift('id("' + elm.getAttribute('id') + '")');
return segs.join('/');
} else {
segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
}
} else if (elm.hasAttribute('class')) {
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
} else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
if (sib.localName == elm.localName) i++;
};
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
};
};
return segs.length ? '/' + segs.join('/') : null;
};
function getElementFromXPath(path) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue;
};
la
March 7, 2011, March 07, 2011 16:26, permalink
i wish i could delete stuff here...
this is the final version that doesn't uses the deprecated document.all to identify uniqe id's to shorten the expression
function createXPathFromElement(elm) {
var allNodes = document.getElementsByTagName('*');
for (segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
{
if (elm.hasAttribute('id')) {
var uniqueIdCount = 0;
for (var n=0;n < allNodes.length;n++) {
if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
if (uniqueIdCount > 1) break;
};
if ( uniqueIdCount == 1) {
segs.unshift('id("' + elm.getAttribute('id') + '")');
return segs.join('/');
} else {
segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
}
} else if (elm.hasAttribute('class')) {
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
} else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
if (sib.localName == elm.localName) i++; };
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
};
};
return segs.length ? '/' + segs.join('/') : null;
};
function lookupElementByXPath(path) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue;
}
stijn
March 7, 2011, March 07, 2011 16:27, permalink
i wish i could delete stuff here...
this is the final version that doesn't uses the deprecated document.all to identify uniqe id's to shorten the expression
function createXPathFromElement(elm) {
var allNodes = document.getElementsByTagName('*');
for (segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
{
if (elm.hasAttribute('id')) {
var uniqueIdCount = 0;
for (var n=0;n < allNodes.length;n++) {
if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
if (uniqueIdCount > 1) break;
};
if ( uniqueIdCount == 1) {
segs.unshift('id("' + elm.getAttribute('id') + '")');
return segs.join('/');
} else {
segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
}
} else if (elm.hasAttribute('class')) {
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
} else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
if (sib.localName == elm.localName) i++; };
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
};
};
return segs.length ? '/' + segs.join('/') : null;
};
function lookupElementByXPath(path) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue;
}
clonecd930
May 9, 2011, May 09, 2011 00:29, permalink
Every man is the architect of his own fortunes.
yup yup