AtomReadStore.js 15 KB

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