diff options
Diffstat (limited to 'multi_requester.js')
-rwxr-xr-x | multi_requester.js | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/multi_requester.js b/multi_requester.js new file mode 100755 index 0000000..28bd433 --- /dev/null +++ b/multi_requester.js @@ -0,0 +1,464 @@ +/** + * ==VimperatorPlugin== + * @name multi_requester.js + * @description request, and the result is displayed in the buffer. + * @description-ja リクエストの結果をバッファに出力する。 + * @author suVene suvene@zeromemory.info + * @version 0.1.0 + * @minVersion 1.2 + * @maxVersion 1.2 + * ==/VimperatorPlugin== + * + * Usage: + * :mr alc {ENY_TEXT or SELECTED_TEXT} -> show the buffer. + * :mr goo! {ENY_TEXT or SELECTED_TEXT} -> show the new tab. + * + * custom: + * [COMMAND](default [mr]) + * let g:multi_requester_command = "ANY1, ANY2, ……"; + * or + * liberator.globalVariables.multi_requester_command = [ANY1, ANY2, ……]; + * + * [SITEINFO] + * ex.) + * javascript <<EOM + * liberator.globalVariables.multi_requester_siteinfo = [ + * { + * name: 'lo', // required + * description: 'local', // required + * url: 'http://localhost/index.html?%s', // required, %s <-- replace string + * resultXpath: '//*', // optional(default all) + * srcEncode: 'SHIFT-JIS', // optional(default UTF-8) + * urlEncode: 'SHIFT-JIS', // optional(default srcEncode) + * ignoreTags: 'img', // optional(default script), syntax tag1,tag2,…… + * }, + * ]; + * EOM + * + * [MAPPINGS] + * ex.) + * javascript <<EOM + * liberator.globalVariables.multi_requester_mappings = [ + * [',ml', 'lo'], // <-- :mr lo + * [',ma', 'alc'], // <-- :mr alc + * [',mg', 'goo', '!'], // <-- :mr! goo + * ]; + * EOM + * + */ +(function() { + +var DEFAULT_COMMAND = ['mr']; +var DEFAULT_SITEINFO = [ + { + name: 'alc', + description: 'SPACE ALC (\u82F1\u8F9E\u6717 on the Web)', + url: 'http://eow.alc.co.jp/%s/UTF-8/', + resultXpath: 'id("resultList")' + }, + { + name: 'goo', + description: 'goo \u8F9E\u66F8', + url: 'http://dictionary.goo.ne.jp/search.php?MT=%s&kind=all&mode=0', + resultXpath: '//div[@id="incontents"]/*[@class="ch04" or @class="fs14" or contains(@class, "diclst")]', + srcEncode: 'EUC-JP', + urlEncode: 'EUC-JP', + }, +]; + +// utilities +var $U = { + log: function(msg, level) { + liberator.log(msg, (level || 9)); + }, + debug: function(msg) { + this.log(msg, 9); + liberator.echo(msg); + }, + echo: function(msg) { + liberator.echo(msg, liberator.commandline.FORCE_MULTILINE); + }, + echoerr: function(msg) { + liberator.log(msg, 5); + liberator.echoerr(msg); + }, + extend: function(dst, src) { + for (var prop in src) + dst[prop] = src[prop]; + return dst; + }, + bind: function(obj, func) { + return function() { + return func.apply(obj, arguments); + } + }, + stripTags: function(str, tags) { + var ignoreTags = [].concat(tags); + ignoreTags = '(?:' + ignoreTags.join('|') + ')'; + return str.replace(new RegExp('<' + ignoreTags + '(?:\\s[^>]*|/)?>([\\S\\s]*?)<\/' + ignoreTags + '\\s*>', 'img'), ''); + }, + stripScripts: function(str) { + return this.stripScripts(str, 'script'); + }, + getCommand: function() { + var c = liberator.globalVariables.multi_requester_command; + var ret; + if (typeof c == 'string') { + ret = [c]; + } else if (typeof c == 'Array') { + ret = check; + } else { + ret = DEFAULT_COMMAND; + } + return ret; + }, + getSiteInfo: function() { + var ret = {}; + + DEFAULT_SITEINFO.forEach(function(site) { + if (!ret[site.name]) ret[site.name] = {}; + $U.extend(ret[site.name], site); + }); + + if (liberator.globalVariables.multi_requester_siteinfo) { + liberator.globalVariables.multi_requester_siteinfo.forEach(function(site) { + if (!ret[site.name]) ret[site.name] = {}; + $U.extend(ret[site.name], site); + }); + } + + var result = []; + for (var v in ret) + result.push(ret[v]); + + return result; + }, +}; + +// vimperator plugin command register +var CommandRegister = { + register: function(cmdClass, siteinfo) { + cmdClass.siteinfo = siteinfo; + var options = undefined; + if (typeof cmdClass.createOptions == 'function') options = cmdClass.createOptions(); + liberator.commands.addUserCommand( + cmdClass.name, + cmdClass.description, + $U.bind(cmdClass, cmdClass.action), + { + completer: cmdClass.completer || function(filter, special) { + var allSuggestions = siteinfo.map(function(s) [s.name, s.description] ); + if (!filter) return [0, allSuggestions]; + var suggestions = allSuggestions.filter(function(s) { + return s[0].indexOf(filter) == 0; + }); + return [0, suggestions]; + }, + options: options, + argCount: cmdClass.argCount || undefined, + bang: cmdClass.bang || true, + count: cmdClass.count || false, + } + ); + + if (liberator.globalVariables.multi_requester_mappings) { + this.addUserMaps(cmdClass.name[0], liberator.globalVariables.multi_requester_mappings); + } + }, + addUserMaps: function(prefix, mapdef) { + mapdef.forEach(function([key, command, special]) { + var cmd = prefix + (special ? '! ' : ' ') + command + ' '; + liberator.mappings.addUserMap( + [liberator.modes.NORMAL, liberator.modes.VISUAL], + [key], + "user defined mapping", + function() { + var sel = (new XPCNativeWrapper(window.content.window)).getSelection(); + if (sel.toString().length) { + liberator.execute(cmd + sel.getRangeAt(0)); + } else { + liberator.commandline.open(':', cmd, liberator.modes.EX); + } + }, + { + rhs: ':' + cmd, + norremap: true + } + ); + }); + }, +}; + + +// like prototype.js +var Requester = function() { + this.initialize.apply(this, arguments); +}; +Requester.EVENTS = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; +Requester.requestCount = 0; +Requester.prototype = { + initialize: function(url, headers, options) { + this.url = url; + this.headers = headers || {}; + this.options = $U.extend({ + asynchronous: true, + encoding: 'UTF-8', + }, options || {}); + this.observers = {}; + }, + addEventListener: function(name, func) { + try { + if (typeof this.observers[name] == 'undefined') this.observers[name] = []; + this.observers[name].push(func); + } catch(e) { + if (!this.fireEvent('onException', e)) throw e; + } + }, + fireEvent: function(name, args) { + if (!(this.observers[name] instanceof Array)) return false; + this.observers[name].forEach(function(event) { + setTimeout(function() { event(args) }, 10); + }); + return true; + }, + _complete: false, + _request: function(method) { + + Requester.requestCount++; + this.transport = new XMLHttpRequest(); + this.transport.open(method, this.url, this.options.asynchronous); + + this.transport.onreadystatechange = $U.bind(this, this._onStateChange); + this.setRequestHeaders(); + this.transport.overrideMimeType('text/html; charset=' + this.options.encoding); + + this.body = this.method == 'POST' ? this.options.postBody : null; + + this.transport.send(this.body); + }, + _onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0; } + }, + isSuccess: function() { + return !status || (status >= 200 && status < 300); + }, + respondToReadyState: function(readyState) { + var state = Requester.EVENTS[readyState]; + var response = new Response(this); + + if (state == 'Complete') { + Requester.requestCount--; + try { + this._complete = true; + this.fireEvent((this.isSuccess() ? 'onSuccess' : 'onFailure'), response); + } catch (e) { + if (!this.fireEvent('onException', e)) throw e; + } + } + }, + setRequestHeaders: function() { + var headers = { + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'POST') { + headers['Content-type'] = 'application/x-www-form-urlencoded' + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + for (var key in this.headers) + if (this.headers.hasOwnProperty(key)) headers[key] = this.headers[key]; + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + + }, + get: function() { + this._request('GET'); + }, + post: function() { + this._request('POST'); + }, +}; + +var Response = function() { + this.initialize.apply(this, arguments); +}; +Response.prototype = { + initialize: function(request) { + this.request = request; + this.transport = request.transport; + this.isSuccess = request.isSuccess(); + this.readyState = this.transport.readyState; + + if (this.readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = (this.transport.responseText == null) ? '' : this.transport.responseText; + } + + this.doc = null; + this.htmlFragmentstr = ''; + }, + status: 0, + statusText: '', + getStatus: Requester.prototype.getStatus, + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return ''; } + }, + + getHTMLDocument: function(xpath, xmlns) { + if (!this.doc) { + this.htmlFragmentstr = this.responseText.replace(/^[\s\S]*?<html(?:\s[^>]+?)?>|<\/html\s*>[\S\s]*$/ig, '').replace(/[\r\n]+/g,' '); + var ignoreTags = ['script']; + if (this.request.options.siteinfo.ignoreTags) { + ignoreTags.concat(this.request.options.siteinfo.ignoreTags.split(',')); + } + this.htmlStripScriptFragmentstr = $U.stripTags(this.htmlFragmentstr, 'script'); + this.doc = this._createHTMLDocument(this.htmlStripScriptFragmentstr, xmlns); + } + + var ret = this.doc; + if (xpath) { + ret = this.getNodeFromXPath(xpath, this.doc); + } + return ret; + }, + _createHTMLDocument: function(str, xmlns) { + //str = '<html><title>hoge</title><body><span id="resultList">fuga</span></body></html>'; + var doc = (new DOMParser).parseFromString( + '<root' + (xmlns ? ' xmlns="' + xmlns + '"' : '') + '>' + str + '</root>', + 'application/xml'); + var imported = document.importNode(doc.documentElement, true); + var range = document.createRange(); + range.selectNodeContents(imported); + var fragment = range.extractContents(); + range.detach(); + var dom = fragment.lastChild; + if (dom.tagName == 'parserError' || dom.namespaceURI == 'http://www.mozilla.org/newlayout/xml/parsererror.xml') { + return this._createHTMLDocument2(str); + } else { + return fragment.childNodes.length > 1 ? fragment : fragment.firstChild; + } + }, + _createHTMLDocument2: function(str) { + var htmlFragment = document.implementation.createDocument(null, 'html', null); + var range = document.createRange(); + range.setStartAfter(window.content.document.body); + htmlFragment.documentElement.appendChild(htmlFragment.importNode(range.createContextualFragment(str), true)); + return htmlFragment; + }, + getNodeFromXPath: function(xpath, doc, parentNode) { + if (!xpath || !doc) return doc; + var node = doc || document; + var nodesSnapshot = (node.ownerDocument || node).evaluate(xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + parentNode = parentNode || document.createElementNS(null, 'div'); + for(var i = 0, l = nodesSnapshot.snapshotLength; i < l; parentNode.appendChild(nodesSnapshot.snapshotItem(i++))); + return parentNode; + }, +}; + +// main controller +var MultiRequester = { + name: $U.getCommand(), + description: 'request, and show the result in the buffer', + action: function(args, special, count) { + + var arg = this.parseArgs(args); + if (!arg.str) { return; } // do nothing + + var site = arg.site; + + var url = site.url; + // see: http://fifnel.com/2008/11/14/1980/ + var srcEncode = site.srcEncode || 'UTF-8'; + var urlEncode = site.urlEncode || srcEncode; + + // via. lookupDictionary.js + var ttbu = Components.classes['@mozilla.org/intl/texttosuburi;1'] + .getService(Components.interfaces.nsITextToSubURI); + url = url.replace(/%s/g, ttbu.ConvertAndEscape(urlEncode, arg.str)); + $U.debug(url); + + if (special) { + liberator.open(url, liberator.NEW_TAB) + } else { + var req = new Requester(url, null, { + encoding: srcEncode, + siteinfo: site, + args: { + args: args, + special: special, + count: count, + } + }); + req.addEventListener('onException', $U.bind(this, this.onException)); + req.addEventListener('onSuccess', $U.bind(this, this.onSuccess)); + req.addEventListener('onFailure', $U.bind(this, this.onFailure)); + + req.get(); + } + }, + // return {name: '', site: {}, str: ''} or null + parseArgs: function(args) { + var ret = null; + if (!args) return ret; + + ret = {}; + var ary = args.split(/ +/); + ret.name = ary.shift(); + if (ary.length >= 1) { + ret.site = this.getSite(ret.name); + } + ret.str = ary.join(' '); + + return ret; + }, + getSite: function(name) { + if (!name) this.siteinfo[0]; + var ret = null; + this.siteinfo.forEach(function(s) { + if (s.name == name) ret = s; + }); + return ret ? ret : this.siteinfo[0]; + }, + onSuccess: function(res) { + if (!res.isSuccess || res.responseText == '') { + return $U.echoerr('request error.'); + } + try { + var doc = res.getHTMLDocument(res.request.options.siteinfo.resultXpath); + var args = res.request.options.args; + var xs = new XMLSerializer(); + $U.echo('<base href="' + res.request.url + '"/>' + xs.serializeToString(doc)); + + } catch(e) { + $U.echoerr('parse error'); + } + + }, + onFailure: function(res) { + $U.echoerr('request error!!: ' + res.statusText); + }, + onException: function(e) { + $U.echoerr('error!!: ' + e); + }, +}; + +CommandRegister.register(MultiRequester, $U.getSiteInfo()); +return MultiRequester; + +})(); +// vim: set fdm=marker sw=4 ts=4 sts=0 et: |