あまりちゃんとテストしてないが、Googleの検索結果、@IT、Bloggerあたりで正常動作確認済み。動かないサイトはITProなど。
直し方はかなり「無理やり」であり、ホントに「とりあえず動く」だけなので、公式(https://github.com/vimpr/vimperator-plugins)へのpull requestはしないつもり。versionは"0.3.9 altered"としておいた。
HTMLをDOMとしてパースする処理はhttp://jsdo.it/kjunichi/qDCfあたりを参考にさせていただいた。
根本的に直すのであれば_libly.jsの修正が必要だと思うが、流石に影響範囲が広すぎるし、そもそもそういう直し方でいいのかという議論もあると思うので、俺みたいな素人ではなくvimprのコアなメンバーの皆様のご判断が必要と考えている。
/*** BEGIN LICENSE BLOCK {{{ Copyright (c) 2008 suVeneReleased under the GPL license http://www.gnu.org/copyleft/gpl.html }}} END LICENSE BLOCK ***/ // PLUGIN_INFO//{{{ var PLUGIN_INFO = xml` `; //}}} liberator.plugins.nextlink = (function() { // initialize //{{{ if (!liberator.plugins.libly) { liberator.log("nextlink: needs _libly.js"); return; } var libly = liberator.plugins.libly; var $U = libly.$U; var logger = $U.getLogger("nextlink"); var $H = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIGlobalHistory2); const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; const UUID = "{3b72c049-a347-4777-96f6-b128fc76ed6a}"; // siteinfo cache key const DEFAULT_PREVMAP = "[["; const DEFAULT_NEXTMAP = "]]"; var prevMap = liberator.globalVariables.nextlink_prevmap || DEFAULT_PREVMAP; var nextMap = liberator.globalVariables.nextlink_nextmap || DEFAULT_NEXTMAP; var isFollowLink = typeof liberator.globalVariables.nextlink_followlink == "undefined" ? false : $U.eval(liberator.globalVariables.nextlink_followlink); const MICROFORMAT = { url: "^https?://.", nextLink: '//a[translate(normalize-space(@rel), "NEXT", "next")="next"] | //link[translate(normalize-space(@rel), "NEXT", "next")="next"]', insertBefore: '//*[contains(concat(" ", @class, " "), " autopagerize_insert_before ")]', pageElement: '//*[contains(concat(" ", @class, " "), " autopagerize_page_element ")]', } const nositeinfoActions = { // vimperator [[, ]] action f: function(doc, count) { if (count < 0) { return buffer.followDocumentRelationship("previous"); } return buffer.followDocumentRelationship("next"); }, e: function(doc, count) { var url = doc.location.href; return liberator.echo("No SITEINFO match " + url); }, n: function() true, }; var actpattern = liberator.globalVariables.nextlink_nositeinfo_act || "e"; var nositeinfoAct = nositeinfoActions[actpattern]; var localSiteinfo = storage.newMap("nextlink-local-siteinfo", {store: false}); if (localSiteinfo) localSiteinfo = [ info for ([ i, info ] in localSiteinfo) ]; var pageNaviCss = xml` `; //}}} var NextLink = function() {//{{{ this.initialize.apply(this, arguments); }; NextLink.prototype = { initialize: function(pager) { this.initialized = false; this.siteinfo = []; this.pager = pager; this.browserModes = config.browserModes || [ modes.NORMAL, modes.VISUAL ]; this.is2_0later = config.autocommands.some(function ([ k, v ]) k == "DOMLoad"); // toriaezu var wedata = new libly.Wedata("AutoPagerize"); wedata.getItems(24 * 60 * 60 * 1000, null, $U.bind(this, function(isSuccess, data) { if (!isSuccess) return; this.siteinfo = data.map(function(item) item.data); if (localSiteinfo) this.siteinfo = this.siteinfo.concat(localSiteinfo); this.siteinfo = this.siteinfo.sort(function(a, b) b.url.length - a.url.length); // sort url.length desc this.initialized = true; }) ); this.customizeMap(this); }, initDoc: function(context, doc) { var value = doc[UUID] = {}; value.siteinfo = this.getSiteinfo(doc); this.pager.initDoc(context, doc); }, getSiteinfo: function(doc) { function valid(prop) $U.getNodesFromXPath(MICROFORMAT[prop], doc).length > 0; if (valid("nextLink") && valid("pageElement")) return MICROFORMAT; var url = doc.location.href; for (let i = 0, len = this.siteinfo.length; i < len; i++) { if (url.match(this.siteinfo[i].url) && this.siteinfo[i].url != "^https?://.") { return this.siteinfo[i]; } } return null; }, nextLink: function(count) { if (!this.initialized) { liberator.echo("before initialized."); return false; } var doc = window.content.document; if (!doc[UUID]) this.initDoc(this, doc); this.pager.nextLink(doc, count); }, customizeMap: function(context) { mappings.addUserMap(context.browserModes, [ prevMap ], "customize by nextlink.js", function(count) context.nextLink(count > 0 ? -1 * count : -1), { count: true }); mappings.addUserMap(context.browserModes, [ nextMap ], "customize by nextlink.js", function(count) context.nextLink(count > 0 ? count : 1), { count: true }); }, };//}}} var Autopager = function() {};//{{{ Autopager.prototype = { initDoc: function(context, doc) { doc[UUID].loadURLs = []; if (context.is2_0later) { let css = $U.xmlToDom(pageNaviCss, doc); let node = doc.importNode(css, true); doc.body.insertBefore(node, doc.body.firstChild); //doc.body.appendChild(css); } }, nextLink: function(doc, count) { var value = doc[UUID]; // TODO: support MICROFORMAT // rel="next", rel="prev" if (!value.siteinfo && nositeinfoAct) { return nositeinfoAct(doc, count); } var curPage = this.getCurrentPage(doc); logger.log(curPage); var page = (count < 0 ? Math.round : Math.floor)(curPage + count); if (page <= 1) { value.isLoading = false; doc.defaultView.scrollTo(0, 0); return true; } if (this.focusPagenavi(doc, page)) { value.isLoading = false; return true; } if (value.isLoading) { logger.echo("loading now..."); return false; } value.isLoading = true; if (value.terminate) { value.isLoading = false; logger.echo("terminated."); return false; } var req = this.createNextRequest(doc); if (!req) { value.isLoading = false; let win = doc.defaultView; win.scrollTo(0, win.scrollMaxY); logger.echo("end of pages."); return true; } req.addEventListener("success", $U.bind(this, this.onSuccess)); req.addEventListener("failure", $U.bind(this, this.onFailure)); req.addEventListener("exception", $U.bind(this, this.onFailure)); req.get(); }, onSuccess: function(res) { var doc = res.req.options.doc; var url = doc.location.href; var value = doc[UUID]; var pages = this.getHTMLDocument(value.siteinfo.pageElement, res) var resDoc = res.doc; var reqUrl = res.req.url; var [ next ] = $U.getNodesFromXPath(value.siteinfo.nextLink, resDoc); value.loadURLs.push(reqUrl); value.next = next; value.isLoading = false; // set reqUrl link-state visited $H.addURI(makeURI(reqUrl), false, true, makeURI(url)); if (!pages || pages.length == 0) return; var addPageNum = this.getPageNum(doc) + 1; this.addPage(doc, resDoc, pages, reqUrl, addPageNum); this.focusPagenavi(doc, addPageNum); }, addPage: function(doc, resDoc, pages, reqUrl, addPageNum) { var url = doc.location.href; var value = doc[UUID]; if (!value.insertPoint) value.insertPoint = this.getInsertPoint(doc, value.siteinfo); var insertPoint = value.insertPoint; this.insertRule(doc, addPageNum, reqUrl, pages[0], insertPoint); pages.forEach(function(elem) { var pe = resDoc.importNode(elem, true); insertPoint.parentNode.insertBefore(pe, insertPoint); }); return true; }, onFailure: function(res) { logger.log("onFailure"); var doc = res.req.options.doc; var url = doc.location.href; var value = doc[UUID]; value.isLoading = false; value.terminate = true; logger.echoerr("nextlink: loading failed. " + "[" + res.status + "]" + res.statusText + " > " + res.req.url); }, focusPagenavi: function(doc, page) { var xpath = '//*[@id="vimperator-nextlink-' + page + '"]'; var [ elem ] = $U.getNodesFromXPath(xpath, doc); var win = doc.defaultView; if (elem) { let p = $U.getElementPosition(elem); win.scrollTo(0, p.top); return true; } return false; }, createNextRequest: function(doc) { var value = doc[UUID]; var url = doc.location.href; var next = value.next; if (!next) [ next ] = $U.getNodesFromXPath(value.siteinfo.nextLink, doc); if (!next) return false; var reqUrl = $U.pathToURL(next, url, doc); if (value.loadURLs.some(function(url) url == reqUrl)) return false; var req = new libly.Request( reqUrl, null, { asynchronous: true, encoding: doc.characterSet, doc: doc } ); return req; }, getPageNum: function(doc) { var xpath = '//*[@class="vimperator-nextlink-page"]'; var page = 1 + $U.getNodesFromXPath(xpath, doc).length; return page; }, getCurrentPage: function(doc) { var xpath = '//*[@class="vimperator-nextlink-page"]'; var markers = $U.getNodesFromXPath(xpath, doc); var win = doc.defaultView; var curPos = win.scrollY; // top of page if (curPos <= 0) return 1.0; // bottom of page if (curPos >= win.scrollMaxY) { if (markers.length > 0) { let lastMarker = $U.getElementPosition(markers[markers.length-1]).top; if (curPos <= lastMarker) return markers.length + 1; } return markers.length + 1.5; } // return n.5 if between n and n+1 var page = 1.0; for (let i = 0, len = markers.length; i < len; i++) { let pos = $U.getElementPosition(markers[i]).top; if (curPos == pos) return page + 1; if (curPos < pos) return page + 0.5; ++page; } return page + 0.5; }, getInsertPoint: function(doc, siteinfo) { var insertPoint, lastPageElement; if (siteinfo.insertBefore) [ insertPoint ] = $U.getNodesFromXPath(siteinfo.insertBefore, doc); if (!insertPoint) { let elems = $U.getNodesFromXPath(siteinfo.pageElement, doc); if (elems.length > 0) lastPageElement = elems.pop(); } if (lastPageElement) insertPoint = lastPageElement.nextSibling || lastPageElement.parentNode.appendChild(doc.createTextNode(" ")); return insertPoint; }, insertRule: function(doc, addPageNum, reqUrl, page, insertPoint) { var p = doc.createElementNS(HTML_NAMESPACE, "p"); p.id = "vimperator-nextlink-" + addPageNum; p.innerHTML = 'page: ' + addPageNum + ""; p.className = "vimperator-nextlink-page"; var tagName; if (page && page.tagName) tagName = page.tagName.toLowerCase(); if (tagName == "tr") { let insertParent = insertPoint.parentNode; let colNodes = getElementsByXPath("child::tr[1]/child::*[self::td or self::th]", insertParent); let colums = 0; for (let i = 0, l = colNodes.length, f = function(col) { colums += parseInt(col, 10) || 1; }; i < l; f(colNodes[i++].getAttribute("colspan"))); let td = insertParent.insertBefore(doc.createElement("tr"), insertPoint) .appendChild(doc.createElement("td")); td.setAttribute("colspan", colums); td.appendChild(p); } else if (tagName == "li") { insertPoint.parentNode.insertBefore(doc.createElementNS(HTML_NAMESPACE, "li"), insertPoint) .appendChild(p); } else { insertPoint.parentNode.insertBefore(p, insertPoint); } }, // alternative function of 'createHTMLDocument' in _libly.js createHTMLDocument: function(str, xmlns) { var createXSLTProcessor = function() { var xsl = (new DOMParser()).parseFromString( ['', ' nextlink mapping "[[", "]]" by AutoPagerize XPath. AutoPagerize 用の XPath より "[[", "]]" をマッピングします。 suVene hogelog dsp74118の補完庫 0.3.9 altered GPL 2.2pre https://github.com/vimpr/vimperator-plugins/raw/master/nextlink.js <![CDATA[ == Needs Library == - _libly.js(ver.0.1.38) @see http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/trunk/_libly.js == Option == >|| let g:nextlink_followlink = "true" ||< と設定することにより、"[[", "]]" の動作は、カレントのタブに新しくページを読み込むようになります。 >|| let g:nextlink_prevmap = "[n" let g:nextlink_nextmap = "]n" ||< のように設定することにより、"[[", "]]" 以外のキーに割り当てることができます。 SITEINFOが無い場合の処理を >|| let g:nextlink_nositeinfo_act = "f" ||< のように設定できます。現在は f: Vimperatorの"[[", "]]"の動作 e: マッチするSITEINFOが無いことを知らせる(デフォルト設定) n: 何もしない が設定可能です /info/ /nextlink-local-siteinfo に >|| [ { "url": "^http://[^.]+\\.google\\.(?:[^.]+\\.)?[^./]+/search\\b", "nextLink": "id('navbar')//td[last()]/a", "pageElement": "id('res')/div", "exampleUrl": "http://www.google.com/search?q=nsIObserver", }, ] ||< のような JSON を置くことでローカルで SITEINFO を設定できます == TODO == ]]> ', ' '].join("\n"), "text/xml"); var xsltp = new XSLTProcessor(); xsltp.importStylesheet(xsl); var doc = xsltp.transformToDocument( document.implementation.createDocument("", "", null)); return doc; }; var doc = createXSLTProcessor(); var range={}; if (!document.caretRangeFromPoint) { range= doc.createRange(); } else { range = doc.caretRangeFromPoint(0, 0); } let text = str.replace(/^[\s\S]*?]*)?>[\s]*|<\/body[ \t\r\n]*>[\S\s]*$/ig, ''); let fragment = range.createContextualFragment(text); let doctype = document.implementation.createDocumentType('html', '-//W3C//DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd'); let htmlFragment = document.implementation.createDocument(null, 'html', doctype); htmlFragment.documentElement.appendChild(htmlFragment.importNode(fragment,true)); return htmlFragment; }, // alternative function of 'getHTMLDocument' in _libly.js getHTMLDocument: function(xpath, res, xmlns, ignoreTags, callback, thisObj) { if (!res.doc) { res.htmlFragmentstr = libly.$U.getHTMLFragment(res.responseText); res.htmlStripScriptFragmentstr = libly.$U.stripTags(res.htmlFragmentstr, ignoreTags); res.doc = this.createHTMLDocument(res.htmlStripScriptFragmentstr, xmlns); } if (!xpath) xpath = '//*'; return libly.$U.getNodesFromXPath(xpath, res.doc, callback, thisObj); } }; //}}} var FollowLink = function() {};//{{{ FollowLink.prototype = { initDoc: function(context, doc) { }, nextLink: function(doc, count) { var url = doc.location.href; var value = doc[UUID]; function followXPath(xpath) { var [ elem ] = $U.getNodesFromXPath(xpath, doc); if (elem) { let tagName = elem.tagName.toLowerCase(); if (tagName == "link") { liberator.open(elem.href); } else { buffer.followLink(elem, liberator.CURRENT_TAB); } return true; } return false; } if (count < 0) { let xpath = [ "link", "a" ].map(function(e) "//" + e + '[translate(normalize-space(@rel), "PREV", "prev")="prev"]') .join(" | "); if (followXPath(xpath)) return; buffer.followDocumentRelationship("previous"); } else { let xpath = [ "link", "a" ].map(function(e) "//" + e + '[translate(normalize-space(@rel), "NEXT", "next")="next"]') .join(" | "); if (followXPath(xpath)) return; if (value.siteinfo && followXPath(value.siteinfo.nextLink)) return; buffer.followDocumentRelationship("next"); } } }; //}}} var instance = new NextLink((isFollowLink ? new FollowLink() : new Autopager())); return instance; })(); // vim: set fdm=marker sw=2 ts=2 sts=0 et:
2013.05.07 追記
ITProで動かなかったのは、wedataのこのエントリのせいっぽい。
ITPro Active内の記事はこれでいいみたいだけど、他のページはこの設定だとマッチしない。後で直しておく。
0 コメント:
コメントを投稿