今見ているStackOverflowのような、さまざまな種類のタグを持つ大きなHTMLファイルがあるとします。
ここで、ページ上の要素をクリックすると、その特定の要素を参照する最も基本的なXPathを計算するJavascript関数はどのようになりますか?
XPathでその要素を参照する方法は無限にあることは知っていますが、IDやクラスなどに関係なく、DOMツリーだけを見るものを探しています。
例:
<html>
<head><title>Fruit</title></head>
<body>
<ol>
<li>Bananas</li>
<li>Apples</li>
<li>Strawberries</li>
</ol>
</body>
</html>
りんごをクリックしたとしましょう。 Javascript関数は次を返します。
/html/body/ol/li[2]
基本的には、DOMツリーからHTML要素までのすべての方法で機能します。
明確にするために、「クリック時」イベントハンドラーは問題ではありません。私はそれを機能させることができます。 DOMツリー内の要素の位置を計算し、XPathとして表現する方法がわかりません。
PS JQueryライブラリーの使用の有無にかかわらず、どんな回答でも歓迎します。
PPS私はXPathを初めて使用するので、上記の例で間違いを犯したかもしれませんが、あなたはそのアイデアを理解するでしょう。
2010年8月11日編集:他の誰かが同様の質問をしたように見えます: 選択したテキストノードのXpathを生成/取得
Firebugはこれを行うことができ、オープンソース( [〜#〜] bsd [〜#〜] )なので、ライブラリを必要としない それらの実装 を再利用できます。
これは、上記のリンクされたソースからの抜粋です。上記のリンクが変更される場合に備えて。ソースを確認して、変更や更新、または提供されているすべての機能セットをご利用ください。
Xpath.getElementXPath = function(element)
{
if (element && element.id)
return '//*[@id="' + element.id + '"]';
else
return Xpath.getElementTreeXPath(element);
};
上記のコードはこの関数を呼び出します。水平スクロールバーを避けるために行折り返しを追加しました
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;
};
あなたの状況に似たXPathを取得するために使用する関数は、jQueryを使用します。
function getXPath( element )
{
var xpath = '';
for ( ; element && element.nodeType == 1; element = element.parentNode )
{
var id = $(element.parentNode).children(element.tagName).index(element) + 1;
id > 1 ? (id = '[' + id + ']') : (id = '');
xpath = '/' + element.tagName.toLowerCase() + id + xpath;
}
return xpath;
}
要素のxpathおよびxpathの要素反復子を返します。
https://Gist.github.com/iimos/e9e96f036a3c174d0bf4
function xpath(el) {
if (typeof el == "string") return document.evaluate(el, document, null, 0, null)
if (!el || el.nodeType != 1) return ''
if (el.id) return "//*[@id='" + el.id + "']"
var sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName == el.tagName })
return xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '['+([].indexOf.call(sames, el)+1)+']' : '')
}
おそらく、[]。filterメソッドをサポートしないIE8のシムを追加する必要があります。 このMDNページ はそのようなコードを提供します。
var xp = xpath(elementNode)
var iterator = xpath("//h2")
var el = iterator.iterateNext();
while (el) {
// work with element
el = iterator.iterateNext();
}
Firebugの実装をわずかに変更して、domツリーのさらに上のelement.idを確認できます。
/**
* Gets an XPath for an element which describes its hierarchical location.
*/
var getElementXPath = function(element) {
if (element && element.id)
return '//*[@id="' + element.id + '"]';
else
return getElementTreeXPath(element);
};
var getElementTreeXPath = function(element) {
var paths = [];
// Use nodeName (instead of localName) so namespace prefix is included (if any).
for (; element && element.nodeType == 1; element = element.parentNode) {
var index = 0;
// EXTRA TEST FOR ELEMENT.ID
if (element && element.id) {
paths.splice(0, 0, '/*[@id="' + element.id + '"]');
break;
}
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;
}
var tagName = element.nodeName.toLowerCase();
var pathIndex = (index ? "[" + (index+1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}
return paths.length ? "/" + paths.join("/") : null;
};
DanSのソリューションをtextNodesで使用するために変更しました。 HTML範囲オブジェクトをシリアル化するのに非常に便利です。
/**
* Gets an XPath for an node which describes its hierarchical location.
*/
var getNodeXPath = function(node) {
if (node && node.id)
return '//*[@id="' + node.id + '"]';
else
return getNodeTreeXPath(node);
};
var getNodeTreeXPath = function(node) {
var paths = [];
// Use nodeName (instead of localName) so namespace prefix is included (if any).
for (; node && (node.nodeType == 1 || node.nodeType == 3) ; node = node.parentNode) {
var index = 0;
// EXTRA TEST FOR ELEMENT.ID
if (node && node.id) {
paths.splice(0, 0, '/*[@id="' + node.id + '"]');
break;
}
for (var sibling = node.previousSibling; sibling; sibling = sibling.previousSibling) {
// Ignore document type declaration.
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
continue;
if (sibling.nodeName == node.nodeName)
++index;
}
var tagName = (node.nodeType == 1 ? node.nodeName.toLowerCase() : "text()");
var pathIndex = (index ? "[" + (index+1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}
return paths.length ? "/" + paths.join("/") : null;
};
HTML要素のxpathを取得するために組み込まれているものはありませんが、 jQuery xpathセレクター を使用する場合など、その逆が一般的です。
HTML要素のxpathを決定する必要がある場合、これを行うカスタム関数を提供する必要があります。以下に、xpathを計算するための javascript/jQuery implsの例 をいくつか示します。
以下の解決策は、要素の絶対XPathを確実に決定するが必要な場合に適しています。
他のいくつかの回答は、要素IDに部分的に依存するか(同一のIDを持つ複数の要素が存在する可能性があるため信頼できません)、与えられたものよりも多くの要素を実際に指定するXPathを生成します(特定の状況で兄弟インデックスを誤って省略して) 。
このコードは、上記の問題を修正することにより、Firebugのソースコードから修正されています。
getXElementTreeXPath = 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;
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;
}
}
var tagName = element.nodeName.toLowerCase();
// *always* include the sibling index
var pathIndex = "[" + (index+1) + "]";
paths.unshift( tagName + pathIndex );
}
return paths.length ? "/" + paths.join( "/") : null;
};
ただの楽しみのために、XPath 2.0 1行実装:
string-join(ancestor-or-self::*/concat(name(),
'[',
for $x in name()
return count(preceding-sibling::*
[name() = $x])
+ 1,
']'),
'/')
function getPath(event) {
event = event || window.event;
var pathElements = [];
var elem = event.currentTarget;
var index = 0;
var siblings = event.currentTarget.parentNode.getElementsByTagName(event.currentTarget.tagName);
for (var i=0, imax=siblings.length; i<imax; i++) {
if (event.currentTarget === siblings[i] {
index = i+1; // add 1 for xpath 1-based
}
}
while (elem.tagName.toLowerCase() != "html") {
pathElements.unshift(elem.tagName);
elem = elem.parentNode;
}
return pathElements.join("/") + "[" + index + "]";
}
兄弟インデックス情報を追加するために編集