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