OpenSearchStore.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.data.OpenSearchStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.data.OpenSearchStore"] = true;
  8. dojo.provide("dojox.data.OpenSearchStore");
  9. dojo.require("dojo.data.util.simpleFetch");
  10. dojo.require("dojox.xml.DomParser");
  11. dojo.require("dojox.xml.parser");
  12. dojo.experimental("dojox.data.OpenSearchStore");
  13. dojo.declare("dojox.data.OpenSearchStore", null, {
  14. constructor: function(/*Object*/args){
  15. // summary:
  16. // Initializer for the OpenSearchStore store.
  17. // description:
  18. // The OpenSearchStore is a Datastore interface to any search
  19. // engine that implements the open search specifications.
  20. if(args){
  21. this.label = args.label;
  22. this.url = args.url;
  23. this.itemPath = args.itemPath;
  24. if("urlPreventCache" in args){
  25. this.urlPreventCache = args.urlPreventCache?true:false;
  26. }
  27. }
  28. var def = dojo.xhrGet({
  29. url: this.url,
  30. handleAs: "xml",
  31. sync: true,
  32. preventCache: this.urlPreventCache
  33. });
  34. def.addCallback(this, "_processOsdd");
  35. def.addErrback(function(){
  36. throw new Error("Unable to load OpenSearch Description document from " . args.url);
  37. });
  38. },
  39. // URL to the open search description document
  40. url: "",
  41. itemPath: "",
  42. _storeRef: "_S",
  43. urlElement: null,
  44. iframeElement: null,
  45. //urlPreventCache: boolean
  46. //Flag denoting if xhrGet calls should use the preventCache option.
  47. urlPreventCache: true,
  48. ATOM_CONTENT_TYPE: 3,
  49. ATOM_CONTENT_TYPE_STRING: "atom",
  50. RSS_CONTENT_TYPE: 2,
  51. RSS_CONTENT_TYPE_STRING: "rss",
  52. XML_CONTENT_TYPE: 1,
  53. XML_CONTENT_TYPE_STRING: "xml",
  54. _assertIsItem: function(/* item */ item){
  55. // summary:
  56. // This function tests whether the item passed in is indeed an item in the store.
  57. // item:
  58. // The item to test for being contained by the store.
  59. if(!this.isItem(item)){
  60. throw new Error("dojox.data.OpenSearchStore: a function was passed an item argument that was not an item");
  61. }
  62. },
  63. _assertIsAttribute: function(/* attribute-name-string */ attribute){
  64. // summary:
  65. // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
  66. // attribute:
  67. // The attribute to test for being contained by the store.
  68. if(typeof attribute !== "string"){
  69. throw new Error("dojox.data.OpenSearchStore: a function was passed an attribute argument that was not an attribute name string");
  70. }
  71. },
  72. getFeatures: function(){
  73. // summary:
  74. // See dojo.data.api.Read.getFeatures()
  75. return {
  76. 'dojo.data.api.Read': true
  77. };
  78. },
  79. getValue: function(item, attribute, defaultValue){
  80. // summary:
  81. // See dojo.data.api.Read.getValue()
  82. var values = this.getValues(item, attribute);
  83. if(values){
  84. return values[0];
  85. }
  86. return defaultValue;
  87. },
  88. getAttributes: function(item){
  89. // summary:
  90. // See dojo.data.api.Read.getAttributes()
  91. return ["content"];
  92. },
  93. hasAttribute: function(item, attribute){
  94. // summary:
  95. // See dojo.data.api.Read.hasAttributes()
  96. if(this.getValue(item,attribute)){
  97. return true;
  98. }
  99. return false;
  100. },
  101. isItemLoaded: function(item){
  102. // summary:
  103. // See dojo.data.api.Read.isItemLoaded()
  104. return this.isItem(item);
  105. },
  106. loadItem: function(keywordArgs){
  107. // summary:
  108. // See dojo.data.api.Read.loadItem()
  109. },
  110. getLabel: function(item){
  111. // summary:
  112. // See dojo.data.api.Read.getLabel()
  113. return undefined;
  114. },
  115. getLabelAttributes: function(item){
  116. // summary:
  117. // See dojo.data.api.Read.getLabelAttributes()
  118. return null;
  119. },
  120. containsValue: function(item, attribute, value){
  121. // summary:
  122. // See dojo.data.api.Read.containsValue()
  123. var values = this.getValues(item,attribute);
  124. for(var i = 0; i < values.length; i++){
  125. if(values[i] === value){
  126. return true;
  127. }
  128. }
  129. return false;
  130. },
  131. getValues: function(item, attribute){
  132. // summary:
  133. // See dojo.data.api.Read.getValue()
  134. this._assertIsItem(item);
  135. this._assertIsAttribute(attribute);
  136. var value = this.processItem(item, attribute);
  137. if(value){
  138. return [value];
  139. }
  140. return undefined;
  141. },
  142. isItem: function(item){
  143. // summary:
  144. // See dojo.data.api.Read.isItem()
  145. if(item && item[this._storeRef] === this){
  146. return true;
  147. }
  148. return false;
  149. },
  150. close: function(request){
  151. // summary:
  152. // See dojo.data.api.Read.close()
  153. },
  154. process: function(data){
  155. // This should return an array of items. This would be the function to override if the
  156. // developer wanted to customize the processing/parsing of the entire batch of search
  157. // results.
  158. return this["_processOSD"+this.contentType](data);
  159. },
  160. processItem: function(item, attribute){
  161. // This returns the text that represents the item. If a developer wanted to customize
  162. // how an individual item is rendered/parsed, they'd override this function.
  163. return this["_processItem"+this.contentType](item.node, attribute);
  164. },
  165. _createSearchUrl: function(request){
  166. var template = this.urlElement.attributes.getNamedItem("template").nodeValue;
  167. var attrs = this.urlElement.attributes;
  168. var index = template.indexOf("{searchTerms}");
  169. template = template.substring(0, index) + request.query.searchTerms + template.substring(index+13);
  170. dojo.forEach([ {'name': 'count', 'test': request.count, 'def': '10'},
  171. {'name': 'startIndex', 'test': request.start, 'def': this.urlElement.attributes.getNamedItem("indexOffset")?this.urlElement.attributes.getNamedItem("indexOffset").nodeValue:0},
  172. {'name': 'startPage', 'test': request.startPage, 'def': this.urlElement.attributes.getNamedItem("pageOffset")?this.urlElement.attributes.getNamedItem("pageOffset").nodeValue:0},
  173. {'name': 'language', 'test': request.language, 'def': "*"},
  174. {'name': 'inputEncoding', 'test': request.inputEncoding, 'def': 'UTF-8'},
  175. {'name': 'outputEncoding', 'test': request.outputEncoding, 'def': 'UTF-8'}
  176. ], function(item){
  177. template = template.replace('{'+item.name+'}', item.test || item.def);
  178. template = template.replace('{'+item.name+'?}', item.test || item.def);
  179. });
  180. return template;
  181. },
  182. _fetchItems: function(request, fetchHandler, errorHandler){
  183. // summary:
  184. // Fetch OpenSearch items that match to a query
  185. // request:
  186. // A request object
  187. // fetchHandler:
  188. // A function to call for fetched items
  189. // errorHandler:
  190. // A function to call on error
  191. if(!request.query){
  192. request.query={};
  193. }
  194. //Build up the content using information from the request
  195. var self = this;
  196. var url = this._createSearchUrl(request);
  197. var getArgs = {
  198. url: url,
  199. preventCache: this.urlPreventCache
  200. };
  201. // Change to fetch the query results.
  202. var xhr = dojo.xhrGet(getArgs);
  203. xhr.addErrback(function(error){
  204. errorHandler(error, request);
  205. });
  206. xhr.addCallback(function(data){
  207. var items = [];
  208. if(data){
  209. //Process the items...
  210. items = self.process(data);
  211. for(var i=0; i < items.length; i++){
  212. items[i] = {node: items[i]};
  213. items[i][self._storeRef] = self;
  214. }
  215. }
  216. fetchHandler(items, request);
  217. });
  218. },
  219. _processOSDxml: function(data){
  220. var div = dojo.doc.createElement("div");
  221. div.innerHTML = data;
  222. return dojo.query(this.itemPath, div);
  223. },
  224. _processItemxml: function(item, attribute){
  225. if(attribute === "content"){
  226. return item.innerHTML;
  227. }
  228. return undefined;
  229. },
  230. _processOSDatom: function(data){
  231. return this._processOSDfeed(data, "entry");
  232. },
  233. _processItematom: function(item, attribute){
  234. return this._processItemfeed(item, attribute, "content");
  235. },
  236. _processOSDrss: function(data){
  237. return this._processOSDfeed(data, "item");
  238. },
  239. _processItemrss: function(item, attribute){
  240. return this._processItemfeed(item, attribute, "description");
  241. },
  242. _processOSDfeed: function(data, type){
  243. data = dojox.xml.parser.parse(data);
  244. var items = [];
  245. var nodeList = data.getElementsByTagName(type);
  246. for(var i=0; i<nodeList.length; i++){
  247. items.push(nodeList.item(i));
  248. }
  249. return items;
  250. },
  251. _processItemfeed: function(item, attribute, type){
  252. if(attribute === "content"){
  253. var content = item.getElementsByTagName(type).item(0);
  254. return this._getNodeXml(content, true);
  255. }
  256. return undefined;
  257. },
  258. _getNodeXml: function(node, skipFirst){
  259. var i;
  260. switch(node.nodeType){
  261. case 1:
  262. var xml = [];
  263. if(!skipFirst){
  264. xml.push("<"+node.tagName);
  265. var attr;
  266. for(i=0; i<node.attributes.length; i++){
  267. attr = node.attributes.item(i);
  268. xml.push(" "+attr.nodeName+"=\""+attr.nodeValue+"\"");
  269. }
  270. xml.push(">");
  271. }
  272. for(i=0; i<node.childNodes.length; i++){
  273. xml.push(this._getNodeXml(node.childNodes.item(i)));
  274. }
  275. if(!skipFirst){
  276. xml.push("</"+node.tagName+">\n");
  277. }
  278. return xml.join("");
  279. case 3:
  280. case 4:
  281. return node.nodeValue;
  282. }
  283. return undefined;
  284. },
  285. _processOsdd: function(doc){
  286. var urlnodes = doc.getElementsByTagName("Url");
  287. //TODO: Check all the urlnodes and determine what our best one is...
  288. var types = [];
  289. var contentType;
  290. var i;
  291. for(i=0; i<urlnodes.length; i++){
  292. contentType = urlnodes[i].attributes.getNamedItem("type").nodeValue;
  293. switch(contentType){
  294. case "application/rss+xml":
  295. types[i] = this.RSS_CONTENT_TYPE;
  296. break;
  297. case "application/atom+xml":
  298. types[i] = this.ATOM_CONTENT_TYPE;
  299. break;
  300. default:
  301. types[i] = this.XML_CONTENT_TYPE;
  302. break;
  303. }
  304. }
  305. var index = 0;
  306. var currentType = types[0];
  307. for(i=1; i<urlnodes.length; i++){
  308. if(types[i]>currentType){
  309. index = i;
  310. currentType = types[i];
  311. }
  312. }
  313. // We'll be using urlnodes[index] as it's the best option (ATOM > RSS > XML)
  314. var label = urlnodes[index].nodeName.toLowerCase();
  315. if(label == 'url'){
  316. var urlattrs = urlnodes[index].attributes;
  317. this.urlElement = urlnodes[index];
  318. switch(types[index]){
  319. case this.ATOM_CONTENT_TYPE:
  320. this.contentType = this.ATOM_CONTENT_TYPE_STRING;
  321. break;
  322. case this.RSS_CONTENT_TYPE:
  323. this.contentType = this.RSS_CONTENT_TYPE_STRING;
  324. break;
  325. case this.XML_CONTENT_TYPE:
  326. this.contentType = this.XML_CONTENT_TYPE_STRING;
  327. break;
  328. }
  329. }
  330. }
  331. });
  332. dojo.extend(dojox.data.OpenSearchStore,dojo.data.util.simpleFetch);
  333. }