文書中のXPathだとかセレクターを取得するGreasemonkey
必要に迫られてようやく作った。
// ==UserScript== // @name XPath Finder // @namespace userscript // @include * // ==/UserScript== function $X(exp, context, type) { if (context && !context.nodeType) { type = context; context = null; } if (!context) context = document; var exp = (context.ownerDocument || context).createExpression(exp, function (prefix) { return document.createNSResolver(context).lookupNamespaceURI(prefix) || (document.contentType === "application/xhtml+xml" ? "http://www.w3.org/1999/xhtml" : ""); }); switch (type) { case Number: return exp.evaluate(context, 1, null).numberValue; case String: return exp.evaluate(context, 2, null).stringValue; case Boolean: return exp.evaluate(context, 3, null).booleanValue; case Array: var result = exp.evaluate(context, 7, null), ret = []; for (var i = 0, iz = result.snapshotLength; i < iz; ++i) { ret[i] = result.snapshotItem(i); } return ret; case void 0: var result = exp.evaluate(context, 0, null); switch (result.resultType) { case 1: return result.numberValue; case 2: return result.stringValue; case 3: return result.booleanValue; case 4: var ret = [], o = null, q = -1; while (o = result.iterateNext()) { ret[++q] = o; } return ret; } return null; default: throw(TypeError("$X: specified type is not valid type.")); } } function $$(selector, context) { return Array.prototype.slice.call((context || document).querySelectorAll(selector)); } function $(id, context) { return (context || document).getElementById(id); } function Finder() { this.xpath_node = ""; this.xpath_root = "/"; } Finder.CSS = <><![CDATA[ #XF_Bar { margin: 0; padding: 2px;; position: fixed; left: 0; width: 100%; fontSize: 13px; background: infobackground; color: black } #XF_Bar.XF_top { top: 0; border-bottom: 2px solid; } #XF_Bar.XF_bottom { bottom: 0; border-top: 2px solid; } #XF_Bar.XF_x #XF_attr, #XF_Bar.XF_s #XF_attr, #XF_Bar.XF_o #XF_node, #XF_Bar.XF_o #XF_res, #XF_Bar.XF_o #XF_env { display: none; } #XF_res { white-space: nowrap; } #XF_node, #XF_attr { width: 100%; } #XF_root { width: 100px; } .XF_highlight { background-color: yellow !important; } ]]></>; Finder.HTML = <><![CDATA[ <table><tbody><tr> <td id="XF_res"></td> <td id="XF_prm" style="width:100%;"> <input type="text" id="XF_node"> <input type="text" id="XF_attr" value="name class href src action"> </td> <td id="XF_env"> <input type="text" id="XF_root"> </td> <td id="XF_sel"><select id="XF_select"> <option value="x" selected="selected">xpath</option> <option value="s">selector</option> <option value="o">option</option> </select></td> </tr></tbody></table> ]]></>; Finder.uniq = function (arr) { for (var i = 0, iz = arr.length; i < iz; ++i) for (var j = 0; j < i; ++j) if (arr[i] === arr[j]) arr.splice(i--, iz-- && 1); return arr; }; Finder.delHL = function () { $$(".XF_highlight").forEach(function (elem) { if (elem.hasAttribute("_class")) { elem.setAttribute("class", elem.getAttribute("_class")); elem.removeAttribute("_class"); } else { elem.removeAttribute("class"); } //elem.className = elem.className.replace(/\bXF_highlight\b/g, ""); }); }; Finder.addHL = function (elem) { if (elem.hasAttribute("class")) { var klass = elem.getAttribute("class"); elem.setAttribute("class", klass + " XF_highlight"); elem.setAttribute("_class", klass); } else { elem.setAttribute("class", "XF_highlight"); } //elem.className += " XF_highlight"; }; Finder.prototype = { show: function (flag) { if (!this.bar) { var link = document.createElement("link"); link.rel = "stylesheet"; link.href = "data:text/css," + escape(Finder.CSS); document.documentElement.firstChild.appendChild(link); this.bar = document.body.appendChild(document.createElement("div")); this.bar.id = "XF_Bar"; this.bar.className = "XF_x"; this.bar.innerHTML = Finder.HTML; var self = this, func = function (evt) { if (evt.keyCode === 27) { self.hide(); evt.preventDefault(); evt.stopPropagation(); } if (evt.keyCode === 13) { self.find(); evt.preventDefault(); evt.stopPropagation(); } }; $("XF_node").addEventListener("keypress", func, false); $("XF_root").addEventListener("keypress", func, false); $("XF_select").addEventListener("change", function () { $("XF_Bar").className = $("XF_Bar").className.replace(/\bXF_[xso]\b/g, "") + " XF_" + $("XF_select").value; }, false); } this.bar.className = this.bar.className.replace(/\bXF_(bottom|top)\b/g, "") + " " + (flag ? "XF_bottom" : "XF_top"); this.bar.style.display = ""; $("XF_node").value = this.xpath_node; $("XF_root").value = this.xpath_root; }, hide: function () { if (this.bar) this.bar.style.display = "none"; }, conv: function (target, mode) { Finder.delHL(); $("XF_select").value = mode; $("XF_Bar").className = $("XF_Bar").className.replace(/\bXF_[xso]\b/g, "") + " XF_" + mode; $("XF_node").value = this["conv_" + mode](target, $("XF_attr").value.replace(/^\s+|\s+$/g, "")); $("XF_root").value = ""; this.find(); }, conv_x: function (target, attrs) { var xpath = [], elem = target; var useid = !!attrs.match(/\bid\b/); attrs = attrs ? attrs.split(/\s+/) : []; while (elem) { if (!elem.parentNode) { xpath.unshift("/"); break; } if (elem.nodeName) { var tmp = elem.nodeName.toLowerCase(); for (var i = 0, attr; attr = attrs[i]; ++i) { if (elem.hasAttribute(attr)) { tmp += "[@" + attr + '="' + elem.getAttribute(attr) + '"]'; } } if (!useid && elem.hasAttribute("id")) { var id = elem.getAttribute("id"); if ($X('//*[@id="' + id + '"]').length === 1) { xpath.unshift('id("' + id + '")'); break; } tmp = tmp.replace(/^(\w+)/, '$1[@id="' + id + '"]'); } var nodes = $X("./" + tmp, elem.parentNode); if (nodes.length > 1) { elem.setAttribute("XF_spy", "1"); for (var i = 0, node; node = nodes[i]; ++i) { if (node.getAttribute("XF_spy")) { tmp += "[" + (i + 1) + "]"; break; } } elem.removeAttribute("XF_spy"); } xpath.unshift(tmp); } elem = elem.parentNode; } return xpath.join("/"); }, conv_s: function (target, attrs) { var selector = [], elem = target; var useid = !!attrs.match(/\bid\b/); attrs = attrs ? attrs.split(/\s+/) : []; while (elem) { if (!elem.parentNode) { selector.unshift(""); break; } if (elem.nodeName) { var tmp = elem.nodeName.toLowerCase(), tid = "", tcl = "", tat = ""; for (var i = 0, attr; attr = attrs[i]; ++i) { if (elem.hasAttribute(attr)) { var p = elem.getAttribute(attr); if (attr === "id") { if (p) tid = "#" + p; } else if (attr === "class") { p = p.replace(/^\s+|\s+$/g, "").replace(/\s+/g, "."); if (p) tcl = "." + p; } else { tat += "[" + attr + '="' + p + '"]'; } } } if (!useid && elem.hasAttribute("id")) { var id = elem.getAttribute("id"); if (id) { if ($X('//*[@id="' + id + '"]').length === 1) { selector.unshift("#" + id); break; } tid = "#" + id; } } tmp += tid + tcl + tat; var nodes = $$(tmp, elem.parentNode); if (nodes.length > 1) { elem.setAttribute("XF_spy", "1"); for (var i = 0, node; node = nodes[i]; ++i) { if (node.getAttribute("XF_spy")) { tmp += ":nth-of-type(" + (i + 1) + ")"; break; } } elem.removeAttribute("XF_spy"); } selector.unshift(tmp); } elem = elem.parentNode; } return selector.join(" "); }, find: function () { Finder.delHL(); this.xpath_node = $("XF_node").value; this.xpath_root = $("XF_root").value; var fx = !($("XF_select").value !== "x"); var $F = fx ? $X : $$; var root, res = []; // ルート取得 if (!this.xpath_root || this.xpath_root === "/" || !this.xpath_root === ".") { $("XF_root").value = this.xpath_root = fx ? "/" : ""; root = [ document ]; } else { try { root = $F(this.xpath_root); } catch (e) { return this.log("context xpath invalid."); } if (!root || !root.length) { return this.log("context node not found."); } } // 子供取得 try { for (var i = 0, iz = root.length, _push = Array.prototype.push; i < iz; ++i) _push.apply(res, $F(this.xpath_node, root[i])); } catch (e) { return this.log("node xpath invalid."); } // 整形 if (res instanceof Array) { Finder.uniq(res); this.log(res.length + " found."); var str = []; res.forEach(Finder.addHL); } else { this.log(res); } return true; }, log: function (str) { $("XF_res").textContent = str; return false; } }; var f = new Finder; //* Event document.addEventListener("click", function (evt) { if (evt.ctrlKey) { f.show(evt.shiftKey); f.conv(evt.target, evt.altKey || evt.metaKey ? "s" : "x"); evt.preventDefault(); evt.stopPropagation(); } }, false); //*/
数時間で適当に。Ctrl+クリックで要素のXPath取得。Ctrl+Alt+クリックで要素のセレクター取得。超適当だけど、あると便利。