Debian: prepare debian/changelog for uploading a new snapshot
[conkeror.git] / modules / opensearch.js
blob0c6865d117f252696ff1501f6b6ea3c21371b307
1 /**
2 * (C) Copyright 2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2010,2012 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
6 * COPYING file.
7 **/
9 // Supported OpenSearch parameters
10 // http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax
12 require("webjump.js");
15 define_variable("opensearch_load_paths",
16 [file_locator_service.get("ProfD", Ci.nsIFile),
17 file_locator_service.get("CurProcD", Ci.nsIFile)]
18 .map(function (x) x.append("search-engines") || x),
19 "Paths to search for opensearch description files. Default list "+
20 "includes the subdirectory called 'search-engines' in your profile "+
21 "directory and the Conkeror installation directory.");
23 const opensearch_response_type_json = "application/x-suggestions+json";
24 const opensearch_response_type_xml = "application/x-suggestions+xml";
27 function opensearch_parse_error (msg) {
28 var e = new Error(msg);
29 e.__proto__ = opensearch_parse_error.prototype;
30 return e;
32 opensearch_parse_error.prototype.__proto__ = Error.prototype;
35 function opensearch_xml_completions (completer, data) {
36 completions.call(this, completer, data);
38 opensearch_xml_completions.prototype = {
39 constructor: opensearch_xml_completions,
40 __proto__: completions.prototype,
41 toString: function () "#<opensearch_xml_completions>",
42 get_string: function (i) this.data[i][0],
43 get_description: function (i) {
44 if (this.data[i][1])
45 return this.data[i][1] + " results";
46 return "";
51 function opensearch_json_completions (completer, data, descriptions) {
52 completions.call(this, completer, data);
53 this.descriptions = descriptions;
55 opensearch_json_completions.prototype = {
56 constructor: opensearch_json_completions,
57 __proto__: completions.prototype,
58 toString: function () "#<opensearch_json_completions>",
59 descriptions: null,
60 get_string: function (i) String(this.data[i]),
61 get_description: function (i) {
62 if (this.descriptions)
63 return String(this.descriptions[i]);
64 return null;
69 function opensearch_xml_completer (eng) {
70 this.eng = eng;
72 opensearch_xml_completer.prototype = {
73 constructor: opensearch_xml_completer,
74 __proto__: completer.prototype,
75 toString: function () "#<opensearch_xml_completer>",
76 eng: null,
77 complete: function (input, pos) {
78 let str = input.substring(0, pos);
79 try {
80 let lspec = this.eng.get_query_load_spec(str, opensearch_response_type_xml);
81 let result = yield send_http_request(lspec);
82 let doc = result.responseXML;
83 var narrowed = [];
84 if (doc) {
85 let elems = doc.getElementsByTagName("CompleteSuggestion");
86 for (let i = 0; i < elems.length; ++i) {
87 let node = elems[i];
88 let name = node.firstChild.getAttribute("data");
89 let desc = node.lastChild.getAttribute("int");
90 if (name)
91 narrowed.push([name,desc]);
93 delete this.doc;
94 delete this.elem;
95 delete this.result;
96 delete this.lspec;
97 yield co_return(new opensearch_xml_completions(this, narrowed));
99 } catch (e) {
100 yield co_return(null);
106 function opensearch_json_completer (eng) {
107 this.eng = eng;
109 opensearch_json_completer.prototype = {
110 constructor: opensearch_json_completer,
111 __proto__: completer.prototype,
112 toString: function () "#<opensearch_json_completer>",
113 eng: null,
114 complete: function (input, pos) {
115 let str = input.substring(0,pos);
116 try {
117 let lspec = this.eng.get_query_load_spec(str, opensearch_response_type_json);
118 let result = yield send_http_request(lspec);
119 let data = JSON.parse(result.responseText);
120 delete this.result;
121 delete this.lspec;
122 if (!(array_p(data) &&
123 data.length >= 2 &&
124 typeof data[0] == "string" &&
125 data[0] == str &&
126 array_p(data[1])))
127 yield co_return(null);
128 if (data[2] && array_p(data[2]) &&
129 data[2].length == data[1].length)
131 var descriptions = data[2];
133 yield co_return(new opensearch_json_completions(this, data[1], descriptions));
134 } catch (e) {
135 yield co_return(null);
141 function opensearch_description () {
142 this.urls = {};
144 opensearch_description.prototype = {
145 constructor: opensearch_description,
146 name: null,
147 description: null,
148 urls: null,
149 query_charset: "UTF-8",
151 supports_response_type: function (type) {
152 return (type in this.urls);
156 * Returns null if the result mime_type isn't supported. The string
157 * search_terms will be escaped by this function.
159 get_query_load_spec: function (search_terms, type) {
160 if (type == null)
161 type = "text/html";
162 var url = this.urls[type];
163 if (!url)
164 return null;
165 search_terms = encodeURIComponent(search_terms);
166 var eng = this;
168 function substitute (value) {
169 // Insert the OpenSearch parameters we're confident about
170 value = value.replace(/\{searchTerms\??\}/g, search_terms);
171 value = value.replace(/\{inputEncoding\??\}/g, eng.query_charset);
172 value = value.replace(/\{language\??\}/g, get_locale() || "*");
173 value = value.replace(/\{outputEncoding\??\}/g, "UTF-8");
175 // Remove any optional parameters
176 value = value.replace(/\{(?:\w+:)?\w+\?\}/g, "");
178 // Insert any remaining required params with our default values
179 value = value.replace(/\{count\??\}/g, "20"); // 20 results
180 value = value.replace(/\{startIndex\??\}/g, "1"); // start at 1st result
181 value = value.replace(/\{startPage\??\}/g, "1"); // 1st page
183 return value;
186 var url_string = substitute(url.template);
188 var data = url.params.map(function (p) (p.name + "=" + substitute(p.value))).join("&");
190 if (url.method == "GET") {
191 if (data.length > 0) {
192 if (url_string.indexOf("?") == -1)
193 url_string += "?";
194 else
195 url_string += "&";
196 url_string += data;
198 return load_spec({uri: url_string});
199 } else {
200 return load_spec({uri: url_string, raw_post_data: data,
201 request_mime_type: "application/x-www-form-urlencoded"});
206 * Guess the url of a home page to correspond with the search engine.
207 * Take the text/html url for the search engine, trim off the path and
208 * any "search." prefix on the domain.
209 * This works for all the provided search engines.
211 get_homepage: function () {
212 var url = this.urls["text/html"];
213 if (!url)
214 return null;
215 url = url_path_trim(url.template);
216 url = url.replace("//search.", "//");
217 return url;
220 get completer () {
221 if (this.supports_response_type(opensearch_response_type_xml))
222 return new opensearch_xml_completer(this);
223 if (this.supports_response_type(opensearch_response_type_json))
224 return new opensearch_json_completer(this);
225 return null;
230 function opensearch_url (type, method, template) {
231 if (!method || !type || !template)
232 throw opensearch_parse_error("Missing method, type, or template for search engine URL");
233 method = method.toUpperCase();
234 type = type.toUpperCase();
235 if (method != "GET" && method != "POST")
236 throw opensearch_parse_error("Invalid method");
237 var template_uri = make_uri(template);
238 switch (template_uri.scheme) {
239 case "http":
240 case "https":
241 break;
242 default:
243 throw opensearch_parse_error("URL template has invalid scheme.");
244 break;
246 this.type = type;
247 this.method = method;
248 this.template = template;
249 this.params = [];
251 opensearch_url.prototype = {
252 constructor: opensearch_url,
253 add_param: function (name, value) {
254 this.params.push({name: name, value: value});
259 function opensearch_parse (node) {
260 var eng = new opensearch_description();
261 for each (let child in node.childNodes) {
262 switch (child.localName) {
263 case "ShortName":
264 eng.name = child.textContent;
265 break;
266 case "Description":
267 eng.description = child.textContent;
268 break;
269 case "Url":
270 try {
271 let type = child.getAttribute("type");
272 let method = child.getAttribute("method") || "GET";
273 let template = child.getAttribute("template");
274 let engine_url = new opensearch_url(type, method, template);
275 for each (let p in child.childNodes) {
276 if (p.localName == "Param") {
277 let name = p.getAttribute("name");
278 let value = p.getAttribute("value");
279 if (name && value)
280 engine_url.add_param(name, value);
283 eng.urls[type] = engine_url;
284 } catch (e) {
285 // Skip this element if parsing fails
287 break;
288 case "InputEncoding":
289 eng.query_charset = child.textContent.toUpperCase();
290 break;
293 return eng;
297 function opensearch_read_file (file) {
298 var file_istream = Cc["@mozilla.org/network/file-input-stream;1"]
299 .createInstance(Ci.nsIFileInputStream);
300 file_istream.init(file, MODE_RDONLY, parseInt("0644", 8), false);
301 var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"]
302 .createInstance(Ci.nsIDOMParser);
303 var doc = dom_parser.parseFromStream(file_istream, "UTF-8",
304 file.fileSize, "text/xml");
305 return opensearch_parse(doc.documentElement);
309 define_keywords("$alternative");
310 function define_opensearch_webjump (name, spec) {
311 keywords(arguments);
312 let alternative = arguments.$alternative;
314 var path = null;
315 if (spec instanceof Ci.nsIFile)
316 path = spec;
317 else {
318 for (let i = 0, n = opensearch_load_paths.length; i < n; ++i) {
319 path = make_file(opensearch_load_paths[i]).clone();
320 path.append(spec);
321 if (path.exists())
322 break;
325 if (! path || ! path.exists())
326 throw new Error("Opensearch file not found.");
328 var eng = opensearch_read_file(path);
330 if (alternative == null)
331 alternative = eng.get_homepage();
333 define_webjump(name,
334 function (arg) {
335 return eng.get_query_load_spec(arg);
337 $alternative = alternative,
338 $doc = eng.description,
339 $completer = eng.completer);
342 define_opensearch_webjump("google", "google.xml");
343 define_opensearch_webjump("bugzilla", "mozilla-bugzilla.xml");
344 define_opensearch_webjump("wikipedia", "wikipedia.xml");
345 define_opensearch_webjump("wiktionary", "wiktionary.xml");
346 define_opensearch_webjump("answers", "answers.xml");
347 define_opensearch_webjump("yahoo", "yahoo.xml");
348 define_opensearch_webjump("creativecommons", "creativecommons.xml");
349 define_opensearch_webjump("ebay", "eBay.xml");
350 define_opensearch_webjump("duckduckgo", "duckduckgo.xml");
352 provide("opensearch");