OpenSearchStore.js 10 KB

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