文書中の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+クリックで要素のセレクター取得。超適当だけど、あると便利。