diff options
author | anekos | 2008-11-19 16:44:54 +0000 |
---|---|---|
committer | anekos | 2008-11-19 16:44:54 +0000 |
commit | b098faf0edd12d376d5ffae556d932865790d70a (patch) | |
tree | 4b4465ba772eedd82543b7eb9e3739667ed26116 /multi_requester.js | |
parent | 68ab7cc94b606aaa312297a6cde162bef46a3529 (diff) | |
download | vimperator-plugins-b098faf0edd12d376d5ffae556d932865790d70a.tar.bz2 |
2.0pre 対応
git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk@24361 d0d07461-0603-4401-acd4-de1884942a52
Diffstat (limited to 'multi_requester.js')
-rw-r--r-- | multi_requester.js | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/multi_requester.js b/multi_requester.js new file mode 100644 index 0000000..f7e9db6 --- /dev/null +++ b/multi_requester.js @@ -0,0 +1,569 @@ +/** + * ==VimperatorPlugin== + * @name multi_requester.js + * @description request, and the result is displayed to the buffer. + * @description-ja リクエストの結果をバッファに出力する。 + * @author suVene suvene@zeromemory.info + * @version 0.2.1 + * @minVersion 2.0pre + * @maxVersion 2.0pre + * ==/VimperatorPlugin== + * + * Usage: + * command[!] subcommand [ANY_TEXT | -s] + * + * ! create new tab. + * ANY_TEXT your input text + * FLAGS: + * -s use selected text + * + * :mr alc ANY_TEXT -> request by the input text, and display to the buffer. + * :mr! goo -s -> request by the selected text, and display to the new tab. + * + * + * CUSTOMIZE .vimperatorrc: + * + * [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: 'ex', // required + * description: 'example', // required + * url: 'http://example.com/?%s', // required, %s <-- replace string + * xpath: '//*', // 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 + * + * other siteinfo by wedata. + * @see http://wedata.net/databases/Multi%20Requester/items + * + * [MAPPINGS] + * ex.) + * javascript <<EOM + * liberator.globalVariables.multi_requester_mappings = [ + * [',ml', 'ex'], // == :mr ex + * [',mg', 'goo', '!'], // == :mr! goo + * [',ma', 'alc', , 'args'], // == :mr alc args (however, it uses a selected_text with precedence.) + * ]; + * EOM + * + * [OTHER OPTIONS] + * let g:multi_requester_use_wedata = "false" // true by default + * + * + * TODO: + * - wedata local cache. + * - 複数リクエスト対応。 + */ +(function() { + +var DEFAULT_COMMAND = ['mr']; +var SITEINFO = [ + { + name: 'alc', + description: 'SPACE ALC (\u82F1\u8F9E\u6717 on the Web)', + url: 'http://eow.alc.co.jp/%s/UTF-8/', + xpath: 'id("resultList")' + }, + { + name: 'goo', + description: 'goo \u8F9E\u66F8', + url: 'http://dictionary.goo.ne.jp/search.php?MT=%s&kind=all&mode=0&IE=UTF-8', + xpath: 'id("incontents")/*[@class="ch04" or @class="fs14" or contains(@class, "diclst")]', + srcEncode: 'EUC-JP', + urlEncode: 'UTF-8' + }, +]; + +var mergedSiteinfo = {}; + +// 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, flg) { + flg = flg || commandline.FORCE_MULTILINE + liberator.echo(msg, flg); + }, + echoerr: function(msg) { + liberator.log(msg, 5); + liberator.echoerr(msg); + }, + extend: function(dst, src) { + for (let prop in src) + dst[prop] = src[prop]; + return dst; + }, + A: function(hash) { + var ret = []; + for (let v in hash) ret.push(hash[v]); + return ret; + }, + 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 + '(?:[ \\t\\n\\r][^>]*|/)?>([\\S\\s]*?)<\/' + ignoreTags + '[ \\t\\r\\n]*>', 'ig'), ''); + }, + stripScripts: function(str) { + return this.stripScripts(str, 'script'); + }, + eval: function(text) { + var fnc = window.eval; + var sandbox; + try { + sandbox = new Components.utils.Sandbox(window); + if (Components.utils.evalInSandbox("true", sandbox) === true) { + fnc = function(text) { return Components.utils.evalInSandbox(text, sandbox); }; + } + } catch (e) { $U.log('warning: multi_requester.js is working with unsafe sandbox.'); } + + return fnc(text); + }, + // via. sbmcommentsviwer.js + evalJson: function(str, toRemove) { + var json; + try { + json = Components.classes['@mozilla.org/dom/json;1'].getService(Components.interfaces.nsIJSON); + if (toRemove) str = str.substring(1, str.length - 1); + return json.decode(str); + } catch (e) { return null; } + }, + getSelectedString: function() { + var sel = (new XPCNativeWrapper(window.content.window)).getSelection(); + return sel.toString(); + } +}; + +// vimperator plugin command register +var CommandRegister = { + register: function(cmdClass, siteinfo) { + cmdClass.siteinfo = siteinfo; + + commands.addUserCommand( + cmdClass.name, + cmdClass.description, + $U.bind(cmdClass, cmdClass.cmdAction), + { + completer: cmdClass.cmdCompleter || 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: cmdClass.cmdOptions, + argCount: cmdClass.argCount || undefined, + bang: cmdClass.bang || true, + count: cmdClass.count || false + }, + true // replace + ); + + }, + addUserMaps: function(prefix, mapdef) { + mapdef.forEach(function([key, command, special, args]) { + var cmd = prefix + (special ? '! ' : ' ') + command + ' '; + mappings.addUserMap( + [modes.NORMAL, modes.VISUAL], + [key], + "user defined mapping", + function() { + var str = $U.getSelectedString() || args || ''; + if (str.length) { + liberator.execute(cmd + str); + } else { + commandline.open(':', cmd, modes.EX); + } + }, + { + rhs: ':' + cmd, + norremap: true + } + ); + }); + } +}; + + +// like the Prototype JavaScript framework +var Request = function() { + this.initialize.apply(this, arguments); +}; +Request.EVENTS = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; +Request.requestCount = 0; +Request.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, asynchronous) { + if (!(this.observers[name] instanceof Array)) return false; + this.observers[name].forEach(function(event) { + if (asynchronous) { + setTimeout(event, 10, args); + } else { + event(args); + } + }); + return true; + }, + _complete: false, + _request: function(method) { + + Request.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); + + // Force Firefox to handle ready state 4 for synchronous requests + if (!this.options.asynchronous && this.transport.overrideMimeType) + this._onStateChange(); + }, + _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 = Request.EVENTS[readyState]; + var response = new Response(this); + + if (state == 'Complete') { + Request.requestCount--; + try { + this._complete = true; + this.fireEvent('on' + (this.isSuccess() ? 'Success' : 'Failure'), response, this.options.asynchronous); + } catch (e) { + if (!this.fireEvent('onException', e, this.options.asynchronous)) throw e; + } + } + }, + setRequestHeaders: function() { + var headers = { + 'Accept': 'text/javascript, application/javascript, text/html, application/xhtml+xml, application/xml, text/xml, */*;q=0.1' + }; + + if (this.method == 'POST') { + headers['Content-type'] = 'application/x-www-form-urlencoded' + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + if (this.transport.overrideMimeType) { + let year = parseInt((navigator.userAgent.match(/\bGecko\/(\d{4})/) || [0, 2005])[1], 10); + if (0 < year && year < 2005) + headers['Connection'] = 'close'; + } + } + + for (let key in this.headers) + if (this.headers.hasOwnProperty(key)) headers[key] = this.headers[key]; + + for (let 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: Request.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(?:[ \t\n\r][^>]*)?>|<\/html[ \t\r\n]*>[\S\s]*$/ig, '').replace(/[\r\n]+/g, ' '); + let 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') { + $U.log('retry parsing.'); + 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); + + if (nodesSnapshot.snapshotLength == 0) return parentNode; + parentNode = parentNode || document.createElementNS(null, 'div'); + for (let i = 0, l = nodesSnapshot.snapshotLength; i < l; parentNode.appendChild(nodesSnapshot.snapshotItem(i++))); + return parentNode; + } +}; + +// initial data access. +var DataAccess = { + 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 useWedata = typeof liberator.globalVariables.multi_requester_use_wedata == 'undefined' ? + true : $U.eval(liberator.globalVariables.multi_requester_use_wedata); + + if (useWedata) { + $U.log('use Wedata'); + this.getWedata(function(site) { + if (mergedSiteinfo[site.name]) return; + mergedSiteinfo[site.name] = {}; + $U.extend(mergedSiteinfo[site.name], site); + }); + } + + if (liberator.globalVariables.multi_requester_siteinfo) { + liberator.globalVariables.multi_requester_siteinfo.forEach(function(site) { + if (!mergedSiteinfo[site.name]) mergedSiteinfo[site.name] = {}; + $U.extend(mergedSiteinfo[site.name], site); + }); + } + + SITEINFO.forEach(function(site) { + if (!mergedSiteinfo[site.name]) mergedSiteinfo[site.name] = {}; + $U.extend(mergedSiteinfo[site.name], site); + }); + + return $U.A(mergedSiteinfo); + }, + getWedata: function(func) { + var req = new Request( + 'http://wedata.net/databases/Multi%20Requester/items.json' + ); + req.addEventListener('onSuccess', function(res) { + var text = res.responseText; + if (!text) return; + var json = $U.evalJson(text); + if (!json) return; + + json.forEach(function(item) func(item.data)); + CommandRegister.register(MultiRequester, $U.A(mergedSiteinfo)); + + }); + req.get(); + } +}; + +// main controller. +var MultiRequester = { + name: DataAccess.getCommand(), + description: 'request, and display to the buffer', + cmdOptions: [ + [['-s'], liberator.OPTION_NOARG] + ], + cmdAction: function(args, special, count) { + + var parsedArgs = this.parseArgs(args); + if (!parsedArgs || !parsedArgs.siteinfo || !parsedArgs.str) { return; } // do nothing + + var siteinfo = parsedArgs.siteinfo; + var url = siteinfo.url; + // see: http://fifnel.com/2008/11/14/1980/ + var srcEncode = siteinfo.srcEncode || 'UTF-8'; + var urlEncode = siteinfo.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, parsedArgs.str)); + $U.log(url); + + if (special) { + liberator.open(url, liberator.NEW_TAB); + } else { + let req = new Request(url, null, { + encoding: srcEncode, + siteinfo: siteinfo, + 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(); + + $U.echo('Loading ' + parsedArgs.name + ' ...', commandline.FORCE_SINGLELINE); + } + }, + // return {name: '', siteinfo: {}, str: ''} or null + parseArgs: function(args) { + + if (!args) return null; + + var isOptS = args.hasOwnProperty('-s'); + if ((isOptS && args.arguments.length < 1) || (!isOptS && args.arguments.length < 2)) return null; + + var siteName = args.arguments.shift(); + var str = (isOptS ? $U.getSelectedString() : args.arguments.join()).replace(/[\n\r]+/g, ''); + var siteinfo = this.getSite(siteName); + + return {name: siteName, siteinfo: siteinfo, str: str}; + }, + getSite: function(name) { + if (!name) this.siteinfo[0]; + var ret = null; + this.siteinfo.forEach(function(s) { + if (s.name == name) ret = s; + }); + return ret; + }, + onSuccess: function(res) { + + var url, escapedUrl, xpath, doc, html; + + try { + + if (!res.isSuccess || res.responseText == '') throw 'response is fail or null'; + + url = res.request.url; + escapedUrl = util.escapeHTML(url); + xpath = res.request.options.siteinfo.resultXpath; + doc = res.getHTMLDocument(xpath); + if (!doc) throw 'XPath result is undefined or null.: XPath -> ' + xpath; + + html = '<div style="white-space:normal;"><base href="' + escapedUrl + '"/>' + + '<a href="' + escapedUrl + '" class="hl-Title" target="_self">' + escapedUrl + '</a>' + + (new XMLSerializer()).serializeToString(doc).replace(/<[^>]+>/g, function(all) all.toLowerCase()) + + '</div>'; + $U.echo(new XMLList(html)); + + } catch (e) { + $U.echoerr('error!!: ' + e); + } + }, + onFailure: function(res) { + $U.echoerr('request failure!!: ' + res.statusText); + }, + onException: function(e) { + $U.echoerr('exception!!: ' + e); + } +}; + +CommandRegister.register(MultiRequester, DataAccess.getSiteInfo()); +if (liberator.globalVariables.multi_requester_mappings) { + CommandRegister.addUserMaps(MultiRequester.name[0], liberator.globalVariables.multi_requester_mappings); +} + +return MultiRequester; + +})(); +// vim: set fdm=marker sw=4 ts=4 sts=0 et: |