From ebd7c4d3b9a35e45c998d5360fac7ccfc8d8c2b0 Mon Sep 17 00:00:00 2001 From: suVene Date: Sun, 18 Jan 2009 13:35:06 +0000 Subject: インデント下げました. git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk@28619 d0d07461-0603-4401-acd4-de1884942a52 --- multi_requester.js | 705 +++++++++++++++++++++++++++-------------------------- 1 file changed, 354 insertions(+), 351 deletions(-) (limited to 'multi_requester.js') diff --git a/multi_requester.js b/multi_requester.js index 95ac2af..89588c9 100644 --- a/multi_requester.js +++ b/multi_requester.js @@ -1,22 +1,22 @@ /*** BEGIN LICENSE BLOCK {{{ - Copyright (c) 2008 suVene + Copyright (c) 2008 suVene - distributable under the terms of an MIT-style license. - http://www.opensource.jp/licenses/mit-license.html + distributable under the terms of an MIT-style license. + http://www.opensource.jp/licenses/mit-license.html }}} END LICENSE BLOCK ***/ // PLUGIN_INFO//{{{ var PLUGIN_INFO = - {NAME} - request, and the result is displayed to the buffer. - リクエストの結果をバッファに出力する。 - suVene - 0.4.11 - MIT - 2.0pre - 2.0pre - http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/multi_requester.js - {NAME} + request, and the result is displayed to the buffer. + リクエストの結果をバッファに出力する。 + suVene + 0.4.11 + MIT + 2.0pre + 2.0pre + http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/multi_requester.js + || command[!] subcommand [ANY_TEXT] ||< -- ! create new tab. -- ANY_TEXT your input text +- ! create new tab. +- ANY_TEXT your input text e.g.) >|| -:mr alc[,goo,any1,any2…] ANY_TEXT -> request by the input text, and display to the buffer. -:mr! goo[,any1,any2,…] {window.selection} -> request by the selected text, and display to the new tab. +:mr alc[,goo,any1,any2…] ANY_TEXT -> request by the input text, and display to the buffer. +:mr! goo[,any1,any2,…] {window.selection} -> request by the selected text, and display to the new tab. ||< == Custumize .vimperatorrc == -=== Command(default [mr]) === +=== Command(default [ mr ]) === >|| let g:multi_requester_command = "ANY1, ANY2, ……" or -liberator.globalVariables.multi_requester_command = [ANY1, ANY2, ……]; +liberator.globalVariables.multi_requester_command = [ ANY1, ANY2, …… ]; ||< === Default Sites (default undefined) === @@ -53,406 +53,409 @@ e.g.) >|| javascript <|| javascript <|| -let g:multi_requester_use_wedata = "false" // true by default +let g:multi_requester_use_wedata = "false" // true by default ||< - ]]> + ]]> ; //}}} (function() { if (!liberator.plugins.libly) { - liberator.log('multi_requester: needs _libly.js'); - return; + liberator.log("multi_requester: needs _libly.js"); + return; } // global variables {{{ -var DEFAULT_COMMAND = ['mr']; +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' - }, + { + 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 libly = liberator.plugins.libly; var $U = libly.$U; -var logger = $U.getLogger('multi_requester'); +var logger = $U.getLogger("multi_requester"); var mergedSiteinfo = {}; //}}} // 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(context, arg) { - context.title = ['Name', 'Descprition']; - var filters = context.filter.split(','); - var prefilters = filters.slice(0, filters.length - 1); - var prefilter = !prefilters.length ? '' : prefilters.join(',') + ','; - var subfilters = siteinfo.filter(function(s) prefilters.every(function(p) s.name != p)); - var allSuggestions = subfilters.map(function(s) [prefilter + s.name, s.description]); - context.completions = context.filter - ? allSuggestions.filter(function(s) s[0].indexOf(context.filter) == 0) - : allSuggestions; - }, - 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, bang, args]) { - var cmd = prefix + (bang ? '! ' : ' ') + command + ' '; - mappings.addUserMap( - [modes.NORMAL, modes.VISUAL], - [key], - 'user defined mapping', - function() { - if (args) { - liberator.execute(cmd + args); - } else { - let sel = $U.getSelectedString(); - if (sel.length) { - liberator.execute(cmd + sel); - } else { - commandline.open(':', cmd, modes.EX); - } - } - }, - { - rhs: ':' + cmd, - norremap: true - } - ); - }); - } + register: function(cmdClass, siteinfo) { + cmdClass.siteinfo = siteinfo; + + commands.addUserCommand( + cmdClass.name, + cmdClass.description, + $U.bind(cmdClass, cmdClass.cmdAction), + { + completer: cmdClass.cmdCompleter || function(context, arg) { + context.title = [ "Name", "Descprition" ]; + var filters = context.filter.split(","); + var prefilters = filters.slice(0, filters.length - 1); + var prefilter = !prefilters.length ? "" : prefilters.join(",") + ","; + var subfilters = siteinfo.filter(function(s) prefilters.every(function(p) s.name != p)); + var allSuggestions = subfilters.map(function(s) [prefilter + s.name, s.description]); + context.completions = context.filter + ? allSuggestions.filter(function(s) s[0].indexOf(context.filter) == 0) + : allSuggestions; + }, + 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, bang, args ]) { + var cmd = prefix + (bang ? "! " : " ") + command + " "; + mappings.addUserMap( + [ modes.NORMAL, modes.VISUAL ], + [ key ], + "user defined mapping", + function() { + if (args) { + liberator.execute(cmd + args); + } else { + let sel = $U.getSelectedString(); + if (sel.length) { + liberator.execute(cmd + sel); + } else { + commandline.open(":", cmd, modes.EX); + } + } + }, + { + rhs: ":" + cmd, + norremap: true + } + ); + }); + } }; //}}} // initial data access class {{{ 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 self = this; - var useWedata = typeof liberator.globalVariables.multi_requester_use_wedata == 'undefined' ? - true : $U.eval(liberator.globalVariables.multi_requester_use_wedata); - - 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); - if (site.map) { - CommandRegister.addUserMaps(MultiRequester.name[0], - [[site.map, site.name, site.bang, site.args]]); - } - }); + 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 self = this; + var useWedata = typeof liberator.globalVariables.multi_requester_use_wedata == "undefined" ? + true : $U.eval(liberator.globalVariables.multi_requester_use_wedata); + + 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); + if (site.map) { + CommandRegister.addUserMaps(MultiRequester.name[0], + [[ site.map, site.name, site.bang, site.args ]]); } + }); + } - SITEINFO.forEach(function(site) { - if (!mergedSiteinfo[site.name]) mergedSiteinfo[site.name] = {}; - $U.extend(mergedSiteinfo[site.name], site); - if (site.map) { - CommandRegister.addUserMaps(MultiRequester.name[0], - [[site.map, site.name, site.bang, site.args]]); - } - }); - - if (useWedata) { - logger.log('use wedata'); - var wedata = new libly.Wedata('Multi%20Requester'); - wedata.getItems(24 * 60 * 60 * 1000, - function(item) { - var site = item.data; - if (mergedSiteinfo[site.name]) return; - mergedSiteinfo[site.name] = {}; - $U.extend(mergedSiteinfo[site.name], site); - }, - function(isSuccess, data) { - if (!isSuccess) return; - CommandRegister.register(MultiRequester, $U.A(mergedSiteinfo)); - } - ); + SITEINFO.forEach(function(site) { + if (!mergedSiteinfo[site.name]) mergedSiteinfo[site.name] = {}; + $U.extend(mergedSiteinfo[site.name], site); + if (site.map) { + CommandRegister.addUserMaps(MultiRequester.name[0], + [[ site.map, site.name, site.bang, site.args ]]); + } + }); + + if (useWedata) { + logger.log("use wedata"); + var wedata = new libly.Wedata("Multi%20Requester"); + wedata.getItems(24 * 60 * 60 * 1000, + function(item) { + var site = item.data; + if (mergedSiteinfo[site.name]) return; + mergedSiteinfo[site.name] = {}; + $U.extend(mergedSiteinfo[site.name], site); + }, + function(isSuccess, data) { + if (!isSuccess) return; + CommandRegister.register(MultiRequester, $U.A(mergedSiteinfo)); } - - return $U.A(mergedSiteinfo); + ); } + + return $U.A(mergedSiteinfo); + } }; //}}} // main controller {{{ var MultiRequester = { - name: DataAccess.getCommand(), - description: 'request, and display to the buffer', - defaultSites: liberator.globalVariables.multi_requester_default_sites, - doProcess: false, - requestNames: '', - requestCount: 0, - echoHash: {}, - cmdAction: function(args) { //{{{ - - if (MultiRequester.doProcess) return; - - var bang = args.bang; - var count = args.count; - - var parsedArgs = this.parseArgs(args); - if (parsedArgs.count == 0) { return; } // do nothing - - MultiRequester.doProcess = true; - MultiRequester.requestNames = parsedArgs.names; - MultiRequester.requestCount = 0; - MultiRequester.echoHash = {}; - var siteinfo = parsedArgs.siteinfo; - for (let i = 0, len = parsedArgs.count; i < len; i++) { - - let info = siteinfo[i]; - let url = info.url; - // see: http://fifnel.com/2008/11/14/1980/ - let srcEncode = info.srcEncode || 'UTF-8'; - let urlEncode = info.urlEncode || srcEncode; - - let idxRepStr = url.indexOf('%s'); - if (idxRepStr > -1 && !parsedArgs.strs.length) continue; - - // via. lookupDictionary.js - let ttbu = Components.classes['@mozilla.org/intl/texttosuburi;1'] - .getService(Components.interfaces.nsITextToSubURI); - - let cnt = 0; - url = url.replace(/%s/g, function(m, i) ttbu.ConvertAndEscape(urlEncode, - (cnt < parsedArgs.strs.length ? parsedArgs.strs[cnt++] : parsedArgs.strs[cnt - 1]))); - logger.log(url + '[' + srcEncode + '][' + urlEncode + ']::' + info.xpath); - - if (bang) { - liberator.open(url, liberator.NEW_TAB); - } else { - let req = new libly.Request(url, null, { - encoding: srcEncode, - siteinfo: info, - args: { - args: args, - bang: bang, - 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(); - MultiRequester.requestCount++; - } - } - - if (MultiRequester.requestCount) { - logger.echo('Loading ' + parsedArgs.names + ' ...', commandline.FORCE_SINGLELINE); - } else { - MultiRequester.doProcess = false; - } - }, - // return {names: '', strs: [''], count: 0, siteinfo: [{}]} - parseArgs: function(args) { - - var self = this; - var ret = {}; - ret.names = ''; - ret.strs = []; - ret.count = 0; - var sel = $U.getSelectedString(); - - if (args.length < 1 && !sel.length) return ret; - - function parse(args, names) { - args = Array.concat(args); - ret.siteinfo = []; - ret.names = names || args.shift() || ''; - ret.strs = (args.length < 1 ? [sel.replace(/[\n\r]+/g, '')] : args); - - ret.names.split(',').forEach(function(name) { - var site = self.getSite(name); - if (site) { - ret.count++; - ret.siteinfo.push(site); - } - }); - } - - parse(args); - - if (!ret.siteinfo.length && this.defaultSites) - parse(args, this.defaultSites); - - return ret; - }, - getSite: function(name) { - if (!name) this.siteinfo[0]; - var ret = null; - this.siteinfo.forEach(function(s) { - if (s.name == name) ret = s; + name: DataAccess.getCommand(), + description: "request, and display to the buffer", + defaultSites: liberator.globalVariables.multi_requester_default_sites, + doProcess: false, + requestNames: "", + requestCount: 0, + echoHash: {}, + cmdAction: function(args) { //{{{ + + if (MultiRequester.doProcess) return; + + var bang = args.bang; + var count = args.count; + + var parsedArgs = this.parseArgs(args); + if (parsedArgs.count == 0) { return; } // do nothing + + MultiRequester.doProcess = true; + MultiRequester.requestNames = parsedArgs.names; + MultiRequester.requestCount = 0; + MultiRequester.echoHash = {}; + var siteinfo = parsedArgs.siteinfo; + for (let i = 0, len = parsedArgs.count; i < len; i++) { + + let info = siteinfo[i]; + let name = info.name; + + logger.log(name); + let url = info.url; + // see: http://fifnel.com/2008/11/14/1980/ + let srcEncode = info.srcEncode || "UTF-8"; + let urlEncode = info.urlEncode || srcEncode; + + let idxRepStr = url.indexOf("%s"); + if (idxRepStr > -1 && !parsedArgs.strs.length) continue; + + // via. lookupDictionary.js + let ttbu = Components.classes["@mozilla.org/intl/texttosuburi;1"] + .getService(Components.interfaces.nsITextToSubURI); + + let cnt = 0; + url = url.replace(/%s/g, function(m, i) ttbu.ConvertAndEscape(urlEncode, + (cnt < parsedArgs.strs.length ? parsedArgs.strs[cnt++] : parsedArgs.strs[cnt - 1]))); + logger.log(url + "[" + srcEncode + "][" + urlEncode + "]::" + info.xpath); + + if (bang) { + liberator.open(url, liberator.NEW_TAB); + } else { + let req = new libly.Request(url, null, { + encoding: srcEncode, + siteinfo: info, + args: { + args: args, + bang: bang, + count: count + } }); - return ret; - },//}}} - extractLink: function(res, extractLink) { //{{{ - - var el = res.getHTMLDocument(extractLink); - if (!el) throw 'extract link failed.: extractLink -> ' + extractLink; - var url = $U.pathToURL(el[0], res.req.url); - var req = new libly.Request(url, null, $U.extend(res.req.options, {extractLink: true})); - 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.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(); MultiRequester.requestCount++; - MultiRequester.doProcess = true; - - },//}}} - onSuccess: function(res) { //{{{ + } + } - if (!MultiRequester.doProcess) { - MultiRequester.requestCount = 0; - return; + if (MultiRequester.requestCount) { + logger.echo("Loading " + parsedArgs.names + " ...", commandline.FORCE_SINGLELINE); + } else { + MultiRequester.doProcess = false; + } + }, + // return {names: "", strs: [""], count: 0, siteinfo: [{}]} + parseArgs: function(args) { + + var self = this; + var ret = {}; + ret.names = ""; + ret.strs = []; + ret.count = 0; + var sel = $U.getSelectedString(); + + if (args.length < 1 && !sel.length) return ret; + + function parse(args, names) { + args = Array.concat(args); + ret.siteinfo = []; + ret.names = names || args.shift() || ""; + ret.strs = (args.length < 1 ? [ sel.replace(/[\n\r]+/g, "") ] : args); + + ret.names.split(",").forEach(function(name) { + var site = self.getSite(name); + if (site) { + ret.count++; + ret.siteinfo.push(site); } + }); + } - logger.log('success!!: ' + res.req.url); - MultiRequester.requestCount--; - if (MultiRequester.requestCount == 0) { - MultiRequester.doProcess = false; - } + parse(args); + + if (!ret.siteinfo.length && this.defaultSites) + parse(args, this.defaultSites); + + 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; + },//}}} + extractLink: function(res, extractLink) { //{{{ + + var el = res.getHTMLDocument(extractLink); + if (!el) throw "extract link failed.: extractLink -> " + extractLink; + var url = $U.pathToURL(el[0], res.req.url); + var req = new libly.Request(url, null, $U.extend(res.req.options, { extractLink: true })); + 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(); + MultiRequester.requestCount++; + MultiRequester.doProcess = true; + + },//}}} + onSuccess: function(res) { //{{{ + + if (!MultiRequester.doProcess) { + MultiRequester.requestCount = 0; + return; + } - var url, escapedUrl, xpath, doc, html, extractLink, ignoreTags; + logger.log("success!!: " + res.req.url); + MultiRequester.requestCount--; + if (MultiRequester.requestCount == 0) { + MultiRequester.doProcess = false; + } - try { + var url, escapedUrl, xpath, doc, html, extractLink, ignoreTags; + + try { + + if (!res.isSuccess() || res.responseText == "") throw "response is fail or null"; + + url = res.req.url; + escapedUrl = util.escapeHTML(url); + xpath = res.req.options.siteinfo.xpath; + extractLink = res.req.options.siteinfo.extractLink; + + if (extractLink && !res.req.options.extractLink) { + this.extractLink(res, extractLink); + return; + } + ignoreTags = [ "script" ].concat(libly.$U.A(res.req.options.siteinfo.ignoreTags)); + doc = document.createElementNS(null, "div"); + res.getHTMLDocument(xpath, null, ignoreTags, function(node, i) { + if (node.tagName.toLowerCase() != "html") + doc.appendChild(node); + }); + if (!doc || !doc.childNodes.length) throw "XPath result is undefined or null.: XPath -> " + xpath; + + $U.getNodesFromXPath("descendant-or-self::a | descendant-or-self::img", doc, function(node) { + var tagName = node.tagName.toLowerCase(); + if (tagName == "a") { + node.href = $U.pathToURL(node, url, res.doc); + } else if (tagName == "img") { + node.src = $U.pathToURL(node, url, res.doc); + } + }); - if (!res.isSuccess() || res.responseText == '') throw 'response is fail or null'; + html = '' + escapedUrl + '' + + $U.xmlSerialize(doc); - url = res.req.url; - escapedUrl = util.escapeHTML(url); - xpath = res.req.options.siteinfo.xpath; - extractLink = res.req.options.siteinfo.extractLink; + MultiRequester.echoHash[res.req.options.siteinfo.name] = html; - if (extractLink && !res.req.options.extractLink) { - this.extractLink(res, extractLink); - return; - } - ignoreTags = ['script'].concat(libly.$U.A(res.req.options.siteinfo.ignoreTags)); - doc = document.createElementNS(null, 'div'); - res.getHTMLDocument(xpath, null, ignoreTags, function(node, i) { - if (node.tagName.toLowerCase() != 'html') - doc.appendChild(node); - }); - if (!doc || !doc.childNodes.length) throw 'XPath result is undefined or null.: XPath -> ' + xpath; - - $U.getNodesFromXPath('descendant-or-self::a | descendant-or-self::img', doc, function(node) { - var tagName = node.tagName.toLowerCase(); - if (tagName == 'a') { - node.href = $U.pathToURL(node, url, res.doc); - } else if (tagName == 'img') { - node.src = $U.pathToURL(node, url, res.doc); - } - }); - - html = '' + escapedUrl + '' + - $U.xmlSerialize(doc); - - MultiRequester.echoHash[res.req.options.siteinfo.name] = html; - - } catch (e) { - logger.log('error!!: ' + e); - MultiRequester.echoHash[res.req.options.siteinfo.name] = - 'error!!: ' + e + ''; - } + } catch (e) { + logger.log("error!!: " + e); + MultiRequester.echoHash[res.req.options.siteinfo.name] = + 'error!!: ' + e + ''; + } - if (MultiRequester.requestCount == 0) { - let echoList = []; - MultiRequester.requestNames.split(',').forEach(function(name) { - echoList.push(MultiRequester.echoHash[name]); - }); - html = '
' + - echoList.join('') + - '
'; - try { logger.echo(new XMLList(html)); } catch (e) { logger.log(e); logger.echo(html); } - } + if (MultiRequester.requestCount == 0) { + let echoList = []; + MultiRequester.requestNames.split(",").forEach(function(name) { + echoList.push(MultiRequester.echoHash[name]); + }); + html = '
' + + echoList.join("") + + '
'; + try { logger.echo(new XMLList(html)); } catch (e) { logger.log(e); logger.echo(html); } + } - }, - onFailure: function(res) { - MultiRequester.doProcess = false; - logger.echoerr('request failure!!: ' + res.statusText); - }, - onException: function(e) { - MultiRequester.doProcess = false; - logger.echoerr('exception!!: ' + e); - }//}}} + }, + onFailure: function(res) { + MultiRequester.doProcess = false; + logger.echoerr("request failure!!: " + res.statusText); + }, + onException: function(e) { + MultiRequester.doProcess = false; + logger.echoerr("exception!!: " + e); + }//}}} }; //}}} // boot strap {{{ CommandRegister.register(MultiRequester, DataAccess.getSiteInfo()); if (liberator.globalVariables.multi_requester_mappings) { - CommandRegister.addUserMaps(MultiRequester.name[0], liberator.globalVariables.multi_requester_mappings); + CommandRegister.addUserMaps(MultiRequester.name[0], liberator.globalVariables.multi_requester_mappings); } //}}} -- cgit v1.2.3