AtomReadStore.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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.AtomReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.data.AtomReadStore"] = true;
  8. dojo.provide("dojox.data.AtomReadStore");
  9. dojo.require("dojo.data.util.filter");
  10. dojo.require("dojo.data.util.simpleFetch");
  11. dojo.require("dojo.date.stamp");
  12. dojo.experimental("dojox.data.AtomReadStore");
  13. dojo.declare("dojox.data.AtomReadStore", null, {
  14. // summary:
  15. // A read only data store for Atom XML based services or documents
  16. // description:
  17. // A data store for Atom XML based services or documents. This store is still under development
  18. // and doesn't support wildcard filtering yet. Attribute filtering is limited to category or id.
  19. constructor: function(/* object */ args){
  20. // summary:
  21. // Constructor for the AtomRead store.
  22. // args:
  23. // An anonymous object to initialize properties. It expects the following values:
  24. // url: The url to a service or an XML document that represents the store
  25. // unescapeHTML: A boolean to specify whether or not to unescape HTML text
  26. // sendQuery: A boolean indicate to add a query string to the service URL
  27. if(args){
  28. this.url = args.url;
  29. this.rewriteUrl = args.rewriteUrl;
  30. this.label = args.label || this.label;
  31. this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery);
  32. this.unescapeHTML = args.unescapeHTML;
  33. if("urlPreventCache" in args){
  34. this.urlPreventCache = args.urlPreventCache?true:false;
  35. }
  36. }
  37. if(!this.url){
  38. throw new Error("AtomReadStore: a URL must be specified when creating the data store");
  39. }
  40. },
  41. //Values that may be set by the parser.
  42. //Ergo, have to be instantiated to something
  43. //So the parser knows how to set them.
  44. url: "",
  45. label: "title",
  46. sendQuery: false,
  47. unescapeHTML: false,
  48. //Configurable preventCache option for the URL.
  49. urlPreventCache: false,
  50. /* dojo.data.api.Read */
  51. getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){
  52. // summary:
  53. // Return an attribute value
  54. // description:
  55. // 'item' must be an instance of an object created by the AtomReadStore instance.
  56. // Accepted attributes are id, subtitle, title, summary, content, author, updated,
  57. // published, category, link and alternate
  58. // item:
  59. // An item returned by a call to the 'fetch' method.
  60. // attribute:
  61. // A attribute of the Atom Entry
  62. // defaultValue:
  63. // A default value
  64. // returns:
  65. // An attribute value found, otherwise 'defaultValue'
  66. this._assertIsItem(item);
  67. this._assertIsAttribute(attribute);
  68. this._initItem(item);
  69. attribute = attribute.toLowerCase();
  70. //If the attribute has previously been retrieved, then return it
  71. if(!item._attribs[attribute] && !item._parsed){
  72. this._parseItem(item);
  73. item._parsed = true;
  74. }
  75. var retVal = item._attribs[attribute];
  76. if(!retVal && attribute == "summary"){
  77. var content = this.getValue(item, "content");
  78. var regexp = new RegExp("/(<([^>]+)>)/g", "i");
  79. var text = content.text.replace(regexp,"");
  80. retVal = {
  81. text: text.substring(0, Math.min(400, text.length)),
  82. type: "text"
  83. };
  84. item._attribs[attribute] = retVal;
  85. }
  86. if(retVal && this.unescapeHTML){
  87. if((attribute == "content" || attribute == "summary" || attribute == "subtitle") && !item["_"+attribute+"Escaped"]){
  88. retVal.text = this._unescapeHTML(retVal.text);
  89. item["_"+attribute+"Escaped"] = true;
  90. }
  91. }
  92. return retVal ? dojo.isArray(retVal) ? retVal[0]: retVal : defaultValue;
  93. },
  94. getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
  95. // summary:
  96. // Return an attribute value
  97. // description:
  98. // 'item' must be an instance of an object created by the AtomReadStore instance.
  99. // Accepted attributes are id, subtitle, title, summary, content, author, updated,
  100. // published, category, link and alternate
  101. // item:
  102. // An item returned by a call to the 'fetch' method.
  103. // attribute:
  104. // A attribute of the Atom Entry
  105. // returns:
  106. // An array of values for the attribute value found, otherwise 'defaultValue'
  107. this._assertIsItem(item);
  108. this._assertIsAttribute(attribute);
  109. this._initItem(item);
  110. attribute = attribute.toLowerCase();
  111. //If the attribute has previously been retrieved, then return it
  112. if(!item._attribs[attribute]){
  113. this._parseItem(item);
  114. }
  115. var retVal = item._attribs[attribute];
  116. return retVal ? ((retVal.length !== undefined && typeof(retVal) !== "string") ? retVal : [retVal]) : undefined;
  117. },
  118. getAttributes: function(/* item */ item){
  119. // summary:
  120. // Return an array of attribute names
  121. // description:
  122. // 'item' must be have been created by the AtomReadStore instance.
  123. // tag names of child elements and XML attribute names of attributes
  124. // specified to the element are returned along with special attribute
  125. // names applicable to the element including "tagName", "childNodes"
  126. // if the element has child elements, "text()" if the element has
  127. // child text nodes, and attribute names in '_attributeMap' that match
  128. // the tag name of the element.
  129. // item:
  130. // An XML element
  131. // returns:
  132. // An array of attributes found
  133. this._assertIsItem(item);
  134. if(!item._attribs){
  135. this._initItem(item);
  136. this._parseItem(item);
  137. }
  138. var attrNames = [];
  139. for(var x in item._attribs){
  140. attrNames.push(x);
  141. }
  142. return attrNames; //array
  143. },
  144. hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
  145. // summary:
  146. // Check whether an element has the attribute
  147. // item:
  148. // 'item' must be created by the AtomReadStore instance.
  149. // attribute:
  150. // An attribute of an Atom Entry item.
  151. // returns:
  152. // True if the element has the attribute, otherwise false
  153. return (this.getValue(item, attribute) !== undefined); //boolean
  154. },
  155. containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){
  156. // summary:
  157. // Check whether the attribute values contain the value
  158. // item:
  159. // 'item' must be an instance of a dojox.data.XmlItem from the store instance.
  160. // attribute:
  161. // A tag name of a child element, An XML attribute name or one of
  162. // special names
  163. // returns:
  164. // True if the attribute values contain the value, otherwise false
  165. var values = this.getValues(item, attribute);
  166. for(var i = 0; i < values.length; i++){
  167. if((typeof value === "string")){
  168. if(values[i].toString && values[i].toString() === value){
  169. return true;
  170. }
  171. }else if(values[i] === value){
  172. return true; //boolean
  173. }
  174. }
  175. return false;//boolean
  176. },
  177. isItem: function(/* anything */ something){
  178. // summary:
  179. // Check whether the object is an item (XML element)
  180. // item:
  181. // An object to check
  182. // returns:
  183. // True if the object is an XML element, otherwise false
  184. if(something && something.element && something.store && something.store === this){
  185. return true; //boolean
  186. }
  187. return false; //boolran
  188. },
  189. isItemLoaded: function(/* anything */ something){
  190. // summary:
  191. // Check whether the object is an item (XML element) and loaded
  192. // item:
  193. // An object to check
  194. // returns:
  195. // True if the object is an XML element, otherwise false
  196. return this.isItem(something); //boolean
  197. },
  198. loadItem: function(/* object */ keywordArgs){
  199. // summary:
  200. // Load an item (XML element)
  201. // keywordArgs:
  202. // object containing the args for loadItem. See dojo.data.api.Read.loadItem()
  203. },
  204. getFeatures: function(){
  205. // summary:
  206. // Return supported data APIs
  207. // returns:
  208. // "dojo.data.api.Read" and "dojo.data.api.Write"
  209. var features = {
  210. "dojo.data.api.Read": true
  211. };
  212. return features; //array
  213. },
  214. getLabel: function(/* item */ item){
  215. // summary:
  216. // See dojo.data.api.Read.getLabel()
  217. if((this.label !== "") && this.isItem(item)){
  218. var label = this.getValue(item,this.label);
  219. if(label && label.text){
  220. return label.text;
  221. }else if(label){
  222. return label.toString();
  223. }else{
  224. return undefined;
  225. }
  226. }
  227. return undefined; //undefined
  228. },
  229. getLabelAttributes: function(/* item */ item){
  230. // summary:
  231. // See dojo.data.api.Read.getLabelAttributes()
  232. if(this.label !== ""){
  233. return [this.label]; //array
  234. }
  235. return null; //null
  236. },
  237. getFeedValue: function(attribute, defaultValue){
  238. // summary:
  239. // Non-API method for retrieving values regarding the Atom feed,
  240. // rather than the Atom entries.
  241. var values = this.getFeedValues(attribute, defaultValue);
  242. if(dojo.isArray(values)){
  243. return values[0];
  244. }
  245. return values;
  246. },
  247. getFeedValues: function(attribute, defaultValue){
  248. // summary:
  249. // Non-API method for retrieving values regarding the Atom feed,
  250. // rather than the Atom entries.
  251. if(!this.doc){
  252. return defaultValue;
  253. }
  254. if(!this._feedMetaData){
  255. this._feedMetaData = {
  256. element: this.doc.getElementsByTagName("feed")[0],
  257. store: this,
  258. _attribs: {}
  259. };
  260. this._parseItem(this._feedMetaData);
  261. }
  262. return this._feedMetaData._attribs[attribute] || defaultValue;
  263. },
  264. _initItem: function(item){
  265. // summary:
  266. // Initializes an item before it can be parsed.
  267. if(!item._attribs){
  268. item._attribs = {};
  269. }
  270. },
  271. _fetchItems: function(request, fetchHandler, errorHandler){
  272. // summary:
  273. // Retrieves the items from the Atom XML document.
  274. var url = this._getFetchUrl(request);
  275. if(!url){
  276. errorHandler(new Error("No URL specified."));
  277. return;
  278. }
  279. var localRequest = (!this.sendQuery ? request : null); // use request for _getItems()
  280. var _this = this;
  281. var docHandler = function(data){
  282. _this.doc = data;
  283. var items = _this._getItems(data, localRequest);
  284. var query = request.query;
  285. if(query){
  286. if(query.id){
  287. items = dojo.filter(items, function(item){
  288. return (_this.getValue(item, "id") == query.id);
  289. });
  290. }else if(query.category){
  291. items = dojo.filter(items, function(entry){
  292. var cats = _this.getValues(entry, "category");
  293. if(!cats){
  294. return false;
  295. }
  296. return dojo.some(cats, "return item.term=='"+query.category+"'");
  297. });
  298. }
  299. }
  300. if(items && items.length > 0){
  301. fetchHandler(items, request);
  302. }else{
  303. fetchHandler([], request);
  304. }
  305. };
  306. if(this.doc){
  307. docHandler(this.doc);
  308. }else{
  309. var getArgs = {
  310. url: url,
  311. handleAs: "xml",
  312. preventCache: this.urlPreventCache
  313. };
  314. var getHandler = dojo.xhrGet(getArgs);
  315. getHandler.addCallback(docHandler);
  316. getHandler.addErrback(function(data){
  317. errorHandler(data, request);
  318. });
  319. }
  320. },
  321. _getFetchUrl: function(request){
  322. if(!this.sendQuery){
  323. return this.url;
  324. }
  325. var query = request.query;
  326. if(!query){
  327. return this.url;
  328. }
  329. if(dojo.isString(query)){
  330. return this.url + query;
  331. }
  332. var queryString = "";
  333. for(var name in query){
  334. var value = query[name];
  335. if(value){
  336. if(queryString){
  337. queryString += "&";
  338. }
  339. queryString += (name + "=" + value);
  340. }
  341. }
  342. if(!queryString){
  343. return this.url;
  344. }
  345. //Check to see if the URL already has query params or not.
  346. var fullUrl = this.url;
  347. if(fullUrl.indexOf("?") < 0){
  348. fullUrl += "?";
  349. }else{
  350. fullUrl += "&";
  351. }
  352. return fullUrl + queryString;
  353. },
  354. _getItems: function(document, request){
  355. // summary:
  356. // Parses the document in a first pass
  357. if(this._items){
  358. return this._items;
  359. }
  360. var items = [];
  361. var nodes = [];
  362. if(document.childNodes.length < 1){
  363. this._items = items;
  364. console.log("dojox.data.AtomReadStore: Received an invalid Atom document. Check the content type header");
  365. return items;
  366. }
  367. var feedNodes = dojo.filter(document.childNodes, "return item.tagName && item.tagName.toLowerCase() == 'feed'");
  368. var query = request.query;
  369. if(!feedNodes || feedNodes.length != 1){
  370. console.log("dojox.data.AtomReadStore: Received an invalid Atom document, number of feed tags = " + (feedNodes? feedNodes.length : 0));
  371. return items;
  372. }
  373. nodes = dojo.filter(feedNodes[0].childNodes, "return item.tagName && item.tagName.toLowerCase() == 'entry'");
  374. if(request.onBegin){
  375. request.onBegin(nodes.length, this.sendQuery ? request : {});
  376. }
  377. for(var i = 0; i < nodes.length; i++){
  378. var node = nodes[i];
  379. if(node.nodeType != 1 /*ELEMENT_NODE*/){
  380. continue;
  381. }
  382. items.push(this._getItem(node));
  383. }
  384. this._items = items;
  385. return items;
  386. },
  387. close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
  388. // summary:
  389. // See dojo.data.api.Read.close()
  390. },
  391. /* internal API */
  392. _getItem: function(element){
  393. return {
  394. element: element,
  395. store: this
  396. };
  397. },
  398. _parseItem: function(item){
  399. var attribs = item._attribs;
  400. var _this = this;
  401. var text, type;
  402. function getNodeText(node){
  403. var txt = node.textContent || node.innerHTML || node.innerXML;
  404. if(!txt && node.childNodes[0]){
  405. var child = node.childNodes[0];
  406. if(child && (child.nodeType == 3 || child.nodeType == 4)){
  407. txt = node.childNodes[0].nodeValue;
  408. }
  409. }
  410. return txt;
  411. }
  412. function parseTextAndType(node){
  413. return {text: getNodeText(node),type: node.getAttribute("type")};
  414. }
  415. dojo.forEach(item.element.childNodes, function(node){
  416. var tagName = node.tagName ? node.tagName.toLowerCase() : "";
  417. switch(tagName){
  418. case "title":
  419. attribs[tagName] = {
  420. text: getNodeText(node),
  421. type: node.getAttribute("type")
  422. }; break;
  423. case "subtitle":
  424. case "summary":
  425. case "content":
  426. attribs[tagName] = parseTextAndType(node);
  427. break;
  428. case "author":
  429. var nameNode ,uriNode;
  430. dojo.forEach(node.childNodes, function(child){
  431. if(!child.tagName){
  432. return;
  433. }
  434. switch(child.tagName.toLowerCase()){
  435. case "name":
  436. nameNode = child;
  437. break;
  438. case "uri":
  439. uriNode = child;
  440. break;
  441. }
  442. });
  443. var author = {};
  444. if(nameNode && nameNode.length == 1){
  445. author.name = getNodeText(nameNode[0]);
  446. }
  447. if(uriNode && uriNode.length == 1){
  448. author.uri = getNodeText(uriNode[0]);
  449. }
  450. attribs[tagName] = author;
  451. break;
  452. case "id":
  453. attribs[tagName] = getNodeText(node);
  454. break;
  455. case "updated":
  456. attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node) );
  457. break;
  458. case "published":
  459. attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node));
  460. break;
  461. case "category":
  462. if(!attribs[tagName]){
  463. attribs[tagName] = [];
  464. }
  465. attribs[tagName].push({scheme:node.getAttribute("scheme"), term: node.getAttribute("term")});
  466. break;
  467. case "link":
  468. if(!attribs[tagName]){
  469. attribs[tagName] = [];
  470. }
  471. var link = {
  472. rel: node.getAttribute("rel"),
  473. href: node.getAttribute("href"),
  474. type: node.getAttribute("type")};
  475. attribs[tagName].push(link);
  476. if(link.rel == "alternate"){
  477. attribs["alternate"] = link;
  478. }
  479. break;
  480. default:
  481. break;
  482. }
  483. });
  484. },
  485. _unescapeHTML : function(text){
  486. //Replace HTML character codes with their unencoded equivalents, e.g. &#8217; with '
  487. text = text.replace(/&#8217;/m , "'").replace(/&#8243;/m , "\"").replace(/&#60;/m,">").replace(/&#62;/m,"<").replace(/&#38;/m,"&");
  488. return text;
  489. },
  490. _assertIsItem: function(/* item */ item){
  491. // summary:
  492. // This function tests whether the item passed in is indeed an item in the store.
  493. // item:
  494. // The item to test for being contained by the store.
  495. if(!this.isItem(item)){
  496. throw new Error("dojox.data.AtomReadStore: Invalid item argument.");
  497. }
  498. },
  499. _assertIsAttribute: function(/* attribute-name-string */ attribute){
  500. // summary:
  501. // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
  502. // attribute:
  503. // The attribute to test for being contained by the store.
  504. if(typeof attribute !== "string"){
  505. throw new Error("dojox.data.AtomReadStore: Invalid attribute argument.");
  506. }
  507. }
  508. });
  509. dojo.extend(dojox.data.AtomReadStore,dojo.data.util.simpleFetch);
  510. }