Created
June 24, 2011 10:29
-
-
Save Griever/1044551 to your computer and use it in GitHub Desktop.
SITEINFOを書く.uc.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name SITEINFO_Writer.uc.js | |
// @description AutoPagerize の SITEINFO を書くためのスクリプト | |
// @namespace http://d.hatena.ne.jp/Griever/ | |
// @author Griever | |
// @include main | |
// @compatibility Firefox 5 | |
// @charset UTF-8 | |
// @version 下書き1 | |
// @note まだこれからつくり込む段階 | |
// @note ツールメニューから起動する | |
// ==/UserScript== | |
(function(css){ | |
if (window.siteinfo_writer) { | |
window.siteinfo_writer.destroy(); | |
delete window.siteinfo_writer; | |
} | |
window.siteinfo_writer = { | |
init: function() { | |
this.style = addStyle(css); | |
document.documentElement.appendChild($E( | |
<vbox id="sw-container" class="sw-add-element" hidden="true"> | |
<hbox id="sw-hbox"> | |
<toolbarbutton label="JSON" oncommand="siteinfo_writer.toJSON();"/> | |
<toolbarbutton id="sw-launch" label="launch" tooltiptext="uAutoPagerize で実行してみます" oncommand="siteinfo_writer.launch();"/> | |
<spacer flex="1"/> | |
<toolbarbutton class="tabs-closebutton" oncommand="siteinfo_writer.hide();"/> | |
</hbox> | |
<grid id="sw-grid"> | |
<columns> | |
<column /> | |
<column flex="1"/> | |
<column /> | |
<column /> | |
</columns> | |
<rows> | |
<row> | |
<label value="url" /> | |
<textbox id="sw-url" oninput="siteinfo_writer.onInput(event);"/> | |
<hbox /> | |
<toolbarbutton class="inspect" | |
tooltiptext="url を取得します" | |
oncommand="siteinfo_writer.url.value = '^' + content.location.href.replace(/[()\[\]{}|+.,^$?\\]/g, '\\$&');"/> | |
</row> | |
<row> | |
<label value="nextLink" /> | |
<textbox id="sw-nextLink" multiline="true"/> | |
<toolbarbutton class="check" | |
tooltiptext="XPath をテストします" | |
oncommand="siteinfo_writer.xpathTest('nextLink');"/> | |
<toolbarbutton class="inspect" | |
tooltiptext="XPath を取得します" | |
oncommand="siteinfo_writer.inspect('nextLink');"/> | |
</row> | |
<row> | |
<label value="pageElement" /> | |
<textbox id="sw-pageElement" multiline="true"/> | |
<toolbarbutton class="check" | |
tooltiptext="XPath をテストします" | |
oncommand="siteinfo_writer.xpathTest('pageElement');"/> | |
<toolbarbutton class="inspect" | |
tooltiptext="XPath を取得します" | |
oncommand="siteinfo_writer.inspect('pageElement');"/> | |
</row> | |
<row> | |
<label value="insertBefore" /> | |
<textbox id="sw-insertBefore" multiline="true"/> | |
<toolbarbutton class="check" | |
tooltiptext="XPath をテストします" | |
oncommand="siteinfo_writer.xpathTest('insertBefore');"/> | |
<toolbarbutton class="inspect" | |
tooltiptext="XPath を取得します" | |
oncommand="siteinfo_writer.inspect('insertBefore');"/> | |
</row> | |
</rows> | |
</grid> | |
</vbox> | |
)); | |
this.popup = $("mainPopupSet").appendChild($E( | |
<menupopup id="sw-popup" class="sw-add-element"/> | |
)); | |
var menuitem = $E(<menuitem class="sw-add-element" label="SITEINFO Writer を起動" oncommand="siteinfo_writer.show();"/>) | |
$("devToolsSeparator").parentNode.insertBefore(menuitem, $("devToolsSeparator")); | |
setTimeout(function() { | |
if (!window.uAutoPagerize) { | |
$("sw-launch").hidden = true; | |
return; | |
}; | |
let aupPopup = $("uAutoPagerize-popup"); | |
if (aupPopup) { | |
aupPopup.appendChild(document.createElement("menuseparator")).setAttribute("class", "sw-add-element"); | |
aupPopup.appendChild(menuitem.cloneNode(false)); | |
} | |
}, 2000); | |
this.container = $("sw-container"); | |
this.url = $('sw-url'); | |
this.nextLink = $('sw-nextLink'); | |
this.pageElement = $('sw-pageElement'); | |
this.insertBefore = $('sw-insertBefore'); | |
this.nextLink.addEventListener("popupshowing", pps, false); | |
this.pageElement.addEventListener("popupshowing", pps, false); | |
this.insertBefore.addEventListener("popupshowing", pps, false); | |
function pps(event) { | |
event.currentTarget.removeEventListener(event.type, arguments.callee, false); | |
var popup = event.originalTarget; | |
var type = event.currentTarget.id.replace("sw-", ""); | |
popup.appendChild($E(<> | |
<menuseparator /> | |
<menuitem label={'@class="xxx" → contains()'} | |
oncommand={"siteinfo_writer.class2contains('"+ type +"')"} /> | |
<menuitem label={'contains() → @class="xxx"'} | |
oncommand={"siteinfo_writer.contains2class('"+ type +"')"} /> | |
</>)); | |
} | |
}, | |
uninit: function() { | |
}, | |
destroy: function() { | |
$A(document.getElementsByClassName("sw-add-element")).forEach(function(e){ | |
e.parentNode.removeChild(e); | |
}) | |
this.style.parentNode.removeChild(this.style); | |
}, | |
setUrl: function() { | |
this.url.value = "^" + content.location.href.replace(/[()\[\]{}|+.,^$?\\]/g, '\\$&'); | |
this.url.className = ""; | |
}, | |
show: function() { | |
this.setUrl(); | |
this.nextLink.value = ""; | |
this.pageElement.value = ""; | |
this.insertBefore.value = ""; | |
this.container.hidden = false; | |
}, | |
hide: function() { | |
this.container.hidden = true; | |
}, | |
toJSON: function() { | |
var json = "{\n"; | |
json += "\turl : '" + this.url.value.replace(/\\/g, "\\\\") + "'\n"; | |
json += "\t,nextLink : '" + this.nextLink.value + "'\n"; | |
json += "\t,pageElement : '" + this.pageElement.value + "'\n"; | |
json += "\t,insertBefore: '" + this.insertBefore.value + "'\n"; | |
json += "\t,exampleUrl : '" + content.location.href + "'\n"; | |
json += "}"; | |
alert(json); | |
}, | |
launch: function() { | |
if (content.ap) return alert("既に実行されています"); | |
var i = {}; | |
["url", "nextLink", "pageElement", "insertBefore"].forEach(function(type) { | |
if (this[type].value) | |
i[type] = this[type].value | |
}, this); | |
if (!i.url || !i.nextLink || !i.pageElement) | |
return alert("入力値が不正です"); | |
let [index, info] = uAutoPagerize.getInfo([i], content); | |
if (index === 0) { | |
if (content.AutoPagerize && content.AutoPagerize.launchAutoPager) | |
content.AutoPagerize.launchAutoPager([i]); | |
else alert("SITEINFO は正常ですが、uAutoPagerize は実行できませんでした"); | |
} else { | |
alert("この SITEINFO にはマッチしませんでした"); | |
} | |
}, | |
inspect: function(aType) { | |
if (this._inspect) | |
this._inspect.uninit(); | |
var self = this; | |
this._inspect = new Inspector(content, function(xpathArray) { | |
if (xpathArray.length) { | |
let range = document.createRange(); | |
range.selectNodeContents(self.popup); | |
range.deleteContents(); | |
range.detach(); | |
for (let [i, x] in Iterator(xpathArray)) { | |
let menuitem = document.createElement("menuitem"); | |
menuitem.setAttribute("label", x); | |
menuitem.setAttribute("oncommand", "siteinfo_writer['"+ aType +"'].value = this.getAttribute('label')"); | |
self.popup.appendChild(menuitem); | |
} | |
self.popup.openPopup(self.container, "before_start"); | |
} | |
self._inspect = null; | |
}); | |
}, | |
xpathTest: function(aType) { | |
var textbox = this[aType] | |
if (!textbox || !textbox.value) return; | |
try { | |
var elements = getElementsByXPath(textbox.value, content.document); | |
} catch (e) { | |
alert(e); | |
} | |
for (let [i, elem] in Iterator(elements)) { | |
if (!("orgcss" in elem)) | |
elem.orgcss = elem.style.cssText; | |
elem.style.backgroundImage = "-moz-linear-gradient(magenta, plum)"; | |
elem.style.outline = "1px solid magenta"; | |
} | |
var self = this; | |
if (this.timer) { | |
clearTimeout(this.timer); | |
this.timer = null; | |
} | |
this.timer = setTimeout(function() { | |
for (let [i, elem] in Iterator(elements)) { | |
if ("orgcss" in elem) { | |
elem.orgcss? | |
elem.style.cssText = elem.orgcss: | |
elem.removeAttribute("style"); | |
delete elem.orgcss; | |
} | |
} | |
}, 5000); | |
}, | |
urlTest: function() { | |
var url = this.url.value; | |
try { | |
var regexp = new RegExp(this.url.value); | |
if (regexp.test(content.location.href)) | |
this.url.classList.remove("error"); | |
else | |
this.url.classList.add("error"); | |
} catch (e) { | |
this.url.classList.add("error"); | |
} | |
}, | |
inputTimer: null, | |
onInput: function(event) { | |
if (this.inputTimer) { | |
clearTimeout(this.inputTimer); | |
} | |
var self = this; | |
this.inputTimer = setTimeout(function() { | |
self.urlTest(); | |
}, 100); | |
}, | |
class2contains: function(aType) { | |
this[aType].value = this.splitClass(this[aType].value); | |
}, | |
contains2class: function(aType) { | |
this[aType].value = this.normalClass(this[aType].value); | |
}, | |
splitClass: function(xpath) { | |
return xpath.replace(/@class=\"(.+?)\"/g, function(str, cls) { | |
cls = cls.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "").split(" "); | |
for (var i = 0, l = cls.length; i < l; i++) { | |
cls[i] = 'contains(concat(" ",normalize-space(@class)," "), " '+ cls[i] +' ")'; | |
} | |
return cls.join(" and "); | |
}); | |
}, | |
normalClass: function(xpath) { | |
let r = /(?:contains\(concat\(\" \"\,normalize\-space\(@class\)\,\" \"\)\, \" .+? \"\)(?: and )?)+/g; | |
return xpath.replace(r, function(str) { | |
let cls = str.split(' and ').map(function(c) c.replace(/.*\" (.*) \".*/i, '$1') ); | |
return '@class="'+ cls.join(' ') +'"'; | |
}); | |
}, | |
}; | |
window.siteinfo_writer.init(); | |
function Inspector(aWindow, aCallback) { | |
this.win = aWindow; | |
this.doc = this.win.document; | |
this.callback = aCallback; | |
this.init(); | |
} | |
Inspector.prototype = { | |
init : function(){ | |
this.doc.addEventListener('mousedown', this, true); | |
this.doc.addEventListener('click', this, true); | |
this.doc.addEventListener('mouseover', this, true); | |
}, | |
uninit : function(){ | |
var self = this; | |
this.lowlight(); | |
this.doc.removeEventListener('mouseover', this, true); | |
setTimeout(function(){ | |
self.doc.removeEventListener('mousedown', self, true); | |
self.doc.removeEventListener('click', self, true); | |
}, 500); | |
XULBrowserWindow.statusTextField.label = ""; | |
}, | |
handleEvent : function(event){ | |
switch(event.type){ | |
case 'mousedown': | |
event.preventDefault(); | |
event.stopPropagation(); | |
this.uninit(); | |
if (event.button == 0) | |
this.callback(this.getXPath(this.target)); | |
break; | |
case 'mouseover': | |
if (this.target) | |
this.lowlight(); | |
this.target = event.target; | |
this.highlight(); | |
break; | |
case "click": | |
event.preventDefault(); | |
event.stopPropagation(); | |
break; | |
} | |
}, | |
highlight : function(){ | |
if (this.target.mozMatchesSelector('html, body')) | |
return; | |
if (!("orgcss" in this.target)) | |
this.target.orgcss = this.target.style.cssText; | |
this.target.style.outline = "magenta 2px solid"; | |
this.target.style.outlineOffset = "-1px"; | |
XULBrowserWindow.statusTextField.label = this.getElementXPath(this.target, this.ATTR_FULL); | |
}, | |
lowlight : function() { | |
if ("orgcss" in this.target) { | |
this.target.orgcss? | |
this.target.style.cssText = this.target.orgcss: | |
this.target.removeAttribute("style"); | |
delete this.target.orgcss; | |
} | |
}, | |
ATTR_CLASSID: 0, | |
ATTR_ID: 1, | |
ATTR_CLASS: 2, | |
ATTR_NOT_CLASSID: 3, | |
ATTR_FULL: 4, | |
getXPath: function(originalTarget) { | |
var nodes = getElementsByXPath( | |
'ancestor-or-self::*[not(local-name()="html" or local-name()="HTML" or local-name()="body" or local-name()="BODY")]', originalTarget).reverse(); | |
if (nodes.length == 0) return []; | |
var current = nodes.shift(); | |
var obj = {}; | |
obj.localnames = current.localName; | |
obj.self_classid = this.getElementXPath(current, this.ATTR_CLASSID); | |
obj.self_id = this.getElementXPath(current, this.ATTR_ID); | |
obj.self_class = this.getElementXPath(current, this.ATTR_CLASS); | |
obj.self_attr = this.getElementXPath(current, this.ATTR_NOT_CLASSID); | |
obj.self_full = this.getElementXPath(current, this.ATTR_FULL); | |
obj.ancestor_classid = obj.self_classid; | |
obj.ancestor_id = obj.self_id; | |
obj.ancestor_class = obj.self_class; | |
obj.ancestor_attr = obj.self_attr; | |
obj.ancestor_full = obj.self_full; | |
var hasId = current.getAttribute("id"); | |
for (let [i, elem] in Iterator(nodes)) { | |
obj.localnames = elem.localName + "/" + obj.localnames; | |
if (!hasId) { | |
hasId = elem.getAttribute("id"); | |
obj.ancestor_classid = this.getElementXPath(elem, this.ATTR_CLASSID) + "/" + obj.ancestor_classid; | |
obj.ancestor_id = this.getElementXPath(elem, this.ATTR_ID) + "/" + obj.ancestor_id; | |
obj.ancestor_full = this.getElementXPath(elem, this.ATTR_FULL) + "/" + obj.ancestor_full; | |
} | |
obj.ancestor_class = this.getElementXPath(elem, this.ATTR_NOT_CLASS) + "/" + obj.ancestor_class; | |
obj.ancestor_attr = this.getElementXPath(elem, this.ATTR_NOT_CLASSID) + "/" + obj.ancestor_attr; | |
} | |
for (let [key, val] in Iterator(obj)) { | |
if (val.substr(0, 4) !== 'id("') | |
obj[key] = val = "//" + val; | |
} | |
var res = [x for each(x in obj)].filter(function(e, i, a) a.indexOf(e) === i).sort(function(a, b){ | |
let aa = a.substr(0, 4) == 'id("'; | |
let bb = b.substr(0, 4) == 'id("'; | |
if ((aa && bb) || (!aa && !bb)) | |
return b.length - a.length; | |
return bb? 1 : -1; | |
}); | |
//var res = [[x, obj[x]] for(x in obj)].sort(function([a,], [b,]) a >= b); | |
////inspectObject([x for each(x in res)]); | |
//res = [x for each([,x] in res)].filter(function(e, i, a) a.indexOf(e) === i); | |
////inspectObject(res); | |
return res; | |
}, | |
getElementXPath: function(elem, constant) { | |
if (!elem.getAttribute) | |
return ""; | |
if (this.ATTR_CLASSID == constant) { | |
if (elem.getAttribute("id")) | |
return 'id("'+ elem.getAttribute('id') +'")'; | |
if (elem.getAttribute("class")) | |
return elem.nodeName.toLowerCase() + '[@class="' + elem.getAttribute("class") + '"]'; | |
return elem.nodeName.toLowerCase(); | |
} | |
if (this.ATTR_ID == constant) { | |
if (elem.getAttribute("id")) | |
return 'id("'+ elem.getAttribute('id') +'")'; | |
return elem.nodeName.toLowerCase(); | |
} | |
if (this.ATTR_CLASS == constant) { | |
if (elem.getAttribute("class")) | |
return elem.nodeName.toLowerCase() + '[@class="' + elem.getAttribute("class") + '"]'; | |
return elem.nodeName.toLowerCase(); | |
} | |
var xpath = elem.nodeName.toLowerCase(); | |
if (this.ATTR_FULL == constant) { | |
let x = []; | |
if (elem.hasAttribute("id")) | |
x[x.length] = '@id="' + elem.getAttribute("id") + '"'; | |
if (elem.hasAttribute("class")) | |
x[x.length] = '@class="' + elem.getAttribute("class") + '"'; | |
if (x.length) | |
xpath += '['+ x.join(" and ") +']'; | |
} | |
/* | |
var attrs = elem.attributes; | |
var arr = []; | |
for (var i = 0, len = attrs.length; i < len; i++) { | |
var name = attrs[i].nodeName; | |
if (name === "style" || name === "id" || name === "class") continue; | |
var value = attrs[i].nodeValue; | |
arr[arr.length] = '@' + name + '="' + value + '"'; | |
}; | |
*/ | |
var arr = [ | |
"@"+ x.nodeName +'="'+ x.nodeValue +'"' | |
for each(x in $A(elem.attributes)) | |
if (!/^(?:id|class|style)$/i.test(x.nodeName)) | |
]; | |
if (arr.length > 0) | |
xpath += '[' + arr.join(" and ") + ']'; | |
return xpath; | |
}, | |
}; | |
function log(){ Application.console.log($A(arguments)); } | |
function $(id, doc) (doc || document).getElementById(id); | |
function $A(arr) Array.slice(arr); | |
function $E(xml, doc) { | |
doc = doc || document; | |
xml = <root xmlns={doc.documentElement.namespaceURI}/>.appendChild(xml); | |
var settings = XML.settings(); | |
XML.prettyPrinting = false; | |
var root = new DOMParser().parseFromString(xml.toXMLString(), 'application/xml').documentElement; | |
XML.setSettings(settings); | |
doc.adoptNode(root); | |
var range = doc.createRange(); | |
range.selectNodeContents(root); | |
var frag = range.extractContents(); | |
range.detach(); | |
return frag.childNodes.length < 2 ? frag.firstChild : frag; | |
} | |
function addStyle(css) { | |
var pi = document.createProcessingInstruction( | |
'xml-stylesheet', | |
'type="text/css" href="data:text/css;utf-8,' + encodeURIComponent(css) + '"' | |
); | |
return document.insertBefore(pi, document.documentElement); | |
} | |
function getElementsByXPath(xpath, node) { | |
var nodesSnapshot = getXPathResult(xpath, node, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); | |
var data = []; | |
for (var i = 0, l = nodesSnapshot.snapshotLength; i < l; i++) { | |
data[i] = nodesSnapshot.snapshotItem(i); | |
} | |
return data; | |
} | |
function getFirstElementByXPath(xpath, node) { | |
var result = getXPathResult(xpath, node, XPathResult.FIRST_ORDERED_NODE_TYPE); | |
return result.singleNodeValue; | |
} | |
function getXPathResult(xpath, node, resultType) { | |
var doc = node.ownerDocument || node | |
var resolver = doc.createNSResolver(node.documentElement || node) | |
// Use |node.lookupNamespaceURI('')| for Opera 9.5 | |
var defaultNS = node.lookupNamespaceURI(null) | |
if (defaultNS) { | |
const defaultPrefix = '__default__' | |
xpath = addDefaultPrefix(xpath, defaultPrefix) | |
var defaultResolver = resolver | |
resolver = function (prefix) { | |
return (prefix == defaultPrefix) | |
? defaultNS : defaultResolver.lookupNamespaceURI(prefix) | |
} | |
} | |
return doc.evaluate(xpath, node, resolver, resultType, null) | |
} | |
function addDefaultPrefix(xpath, prefix) { | |
const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g | |
const TERM = 1, OPERATOR = 2, MODIFIER = 3 | |
var tokenType = OPERATOR | |
prefix += ':' | |
function replacer(token, identifier, suffix, term, operator, modifier) { | |
if (suffix) { | |
tokenType = | |
(suffix == ':' || (suffix == '::' && | |
(identifier == 'attribute' || identifier == 'namespace'))) | |
? MODIFIER : OPERATOR | |
} | |
else if (identifier) { | |
if (tokenType == OPERATOR && identifier != '*') { | |
token = prefix + token | |
} | |
tokenType = (tokenType == TERM) ? OPERATOR : TERM | |
} | |
else { | |
tokenType = term ? TERM : operator ? OPERATOR : MODIFIER | |
} | |
return token | |
} | |
return xpath.replace(tokenPattern, replacer) | |
}; | |
})(<![CDATA[ | |
#sw-grid row > label { | |
margin: 1px !important; | |
} | |
#sw-grid row > textbox { | |
-moz-appearance: none !important; | |
margin: 1px !important; | |
padding: 1px !important; | |
border-width: 1px !important; | |
} | |
#sw-grid row > textbox[multiline="true"] { | |
height: 4em; | |
} | |
#sw-grid .inspect { | |
list-style-image: url("chrome://global/skin/icons/Search-glass.png"); | |
-moz-image-region: rect(0px, 16px, 16px, 0px); | |
} | |
#sw-grid .check { | |
list-style-image: url("chrome://global/skin/icons/find.png"); | |
-moz-image-region: rect(0px, 48px, 16px, 32px); | |
} | |
#sw-url.error { | |
outline: 1px solid red !important; | |
} | |
#sw-popup > * { | |
max-width: none !important; | |
} | |
]]>.toString()); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment