Просматривая свои старые записи, наткнулся на код, заимствованный из этой темы. Обнаружил, что предложенная автором функция не работает если xml-узлы имеют полное имя (включают префикс, например, <x:a />) и определение пространства имен для этого префикса.
Это легко исправить, если в запросах поиска узлов слева и справа использовать не nodeName, а baseName. Исправление представлено в функции selectXPath. Причем оно работоспособно и для первоначального примера.
Дополнительно реализовал функцию getXPath. Алгоритм отличается - там производится перебор всех предков и братьев узла средствами самого JS. Эффективность того или иного способа я не проверял.
Также, в обеих функциях добавил поддержку xml-атрибутов.
Функции возвращают корректные, но отличные друг от друга результаты.
var s = '<x:a xmlns:x="something"><b/><b><c/><c id="1"/></b></x:a>';
var xml = new ActiveXObject('Msxml2.DOMDocument.6.0');
//xml.validateOnParse = true;
//xml.setProperty('SelectionLanguage', 'XPath');
xml.loadXML(s);
var a = xml.selectSingleNode('//@id');
var p1 = selectXPath(a); // returns "/x:a/b[2]/c[2]/@id"
var p2 = getXPath(a); // returns "/x:a[1]/b[2]/c[2]/@id"
WScript.Echo(p1);
WScript.Echo(p2);
// http://forum.script-coding.com/viewtopic.php?id=9277
function selectXPath(xmlNode) {
var result = [];
var ancestors = xmlNode.selectNodes('ancestor-or-self::*')
for (var i = 0; i < ancestors.length; i++) {
var node = ancestors[i];
var nodeName = node.nodeName;
var baseName = node.baseName;
var L = node.selectNodes('preceding-sibling::' + baseName).length;
var R = node.selectNodes('following-sibling::' + baseName).length;
if ( L || R ) {
nodeName += '[' + ( L + 1 ) + ']';
}
result.push(nodeName);
}
// It's xml attribute. Store its name manually, because the code
// above doesn't work for xml attributes.
if ( xmlNode.nodeType == 2 ) {
result.push('@' + xmlNode.name);
}
return '/' + result.join('/')
};
// https://stackoverflow.com/a/18184670/3627676
function getXPath(xmlNode) {
// It's xml attribute. Take its owner and continue.
if ( xmlNode.nodeType == 2 ) {
return arguments.callee(xmlNode.selectSingleNode('..')) +
'/@' + xmlNode.name;
}
if ( xmlNode.parentNode == null ) {
return '';
}
var i = 1;
for (var sibling = xmlNode; sibling = sibling.previousSibling; ) {
if ( sibling.nodeName == xmlNode.nodeName ) {
i++;
}
}
return arguments.callee(xmlNode.parentNode) +
'/' + xmlNode.nodeName + '[' + i + ']';
};
( 2 * b ) || ! ( 2 * b )