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
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;
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
) {
45 return this.data
[i
][1] + " results";
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>",
60 get_string: function (i
) String(this.data
[i
]),
61 get_description: function (i
) {
62 if (this.descriptions
)
63 return String(this.descriptions
[i
]);
69 function opensearch_xml_completer (eng
) {
72 opensearch_xml_completer
.prototype = {
73 constructor: opensearch_xml_completer
,
74 __proto__
: completer
.prototype,
75 toString: function () "#<opensearch_xml_completer>",
77 complete: function (input
, pos
) {
78 let str
= input
.substring(0, pos
);
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
;
85 let elems
= doc
.getElementsByTagName("CompleteSuggestion");
86 for (let i
= 0; i
< elems
.length
; ++i
) {
88 let name
= node
.firstChild
.getAttribute("data");
89 let desc
= node
.lastChild
.getAttribute("int");
91 narrowed
.push([name
,desc
]);
97 yield co_return(new opensearch_xml_completions(this, narrowed
));
100 yield co_return(null);
106 function opensearch_json_completer (eng
) {
109 opensearch_json_completer
.prototype = {
110 constructor: opensearch_json_completer
,
111 __proto__
: completer
.prototype,
112 toString: function () "#<opensearch_json_completer>",
114 complete: function (input
, pos
) {
115 let str
= input
.substring(0,pos
);
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
);
122 if (!(array_p(data
) &&
124 typeof data
[0] == "string" &&
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
));
135 yield co_return(null);
141 function opensearch_description () {
144 opensearch_description
.prototype = {
145 constructor: opensearch_description
,
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
) {
162 var url
= this.urls
[type
];
165 search_terms
= encodeURIComponent(search_terms
);
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
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)
198 return load_spec({uri
: url_string
});
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"];
215 url
= url_path_trim(url
.template
);
216 url
= url
.replace("//search.", "//");
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);
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
) {
243 throw opensearch_parse_error("URL template has invalid scheme.");
247 this.method
= method
;
248 this.template
= template
;
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
) {
264 eng
.name
= child
.textContent
;
267 eng
.description
= child
.textContent
;
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");
280 engine_url
.add_param(name
, value
);
283 eng
.urls
[type
] = engine_url
;
285 // Skip this element if parsing fails
288 case "InputEncoding":
289 eng
.query_charset
= child
.textContent
.toUpperCase();
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
) {
312 let alternative
= arguments
.$alternative
;
315 if (spec
instanceof Ci
.nsIFile
)
318 for (let i
= 0, n
= opensearch_load_paths
.length
; i
< n
; ++i
) {
319 path
= make_file(opensearch_load_paths
[i
]).clone();
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();
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");