dojo.provide("dojox.data.AtomReadStore"); dojo.require("dojo.data.util.simpleFetch"); dojo.require("dojo.data.util.filter"); dojo.require("dojo.date.stamp"); dojo.experimental("dojox.data.AtomReadStore"); dojo.declare("dojox.data.AtomReadStore", null, { // summary: // A read only data store for Atom XML based services or documents // description: // A data store for Atom XML based services or documents. This store is still under development // and doesn't support wildcard filtering yet. Attribute filtering is limited to category or id. constructor: function(/* object */ args) { // summary: // Constructor for the AtomRead store. // args: // An anonymous object to initialize properties. It expects the following values: // url: The url to a service or an XML document that represents the store // unescapeHTML: A boolean to specify whether or not to unescape HTML text // sendQuery: A boolean indicate to add a query string to the service URL if(args){ this.url = args.url; this.rewriteUrl = args.rewriteUrl; this.label = args.label || this.label; this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery); this.unescapeHTML = args.unescapeHTML; } if(!this.url){ throw new Error("AtomReadStore: a URL must be specified when creating the data store"); } }, //Values that may be set by the parser. //Ergo, have to be instantiated to something //So the parser knows how to set them. url: "", label: "title", sendQuery: false, unescapeHTML: false, /* dojo.data.api.Read */ getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){ // summary: // Return an attribute value // description: // 'item' must be an instance of an object created by the AtomReadStore instance. // Accepted attributes are id, subtitle, title, summary, content, author, updated, // published, category, link and alternate // item: // An item returned by a call to the 'fetch' method. // attribute: // A attribute of the Atom Entry // defaultValue: // A default value // returns: // An attribute value found, otherwise 'defaultValue' this._assertIsItem(item); this._assertIsAttribute(attribute); this._initItem(item); attribute = attribute.toLowerCase(); //If the attribute has previously been retrieved, then return it if(!item._attribs[attribute] && !item._parsed){ this._parseItem(item); item._parsed = true; } var retVal = item._attribs[attribute]; if(!retVal && attribute=="summary") { var content = this.getValue(item, "content"); var regexp = new RegExp("/(<([^>]+)>)/g", "i"); var text = content.text.replace(regexp,""); retVal = { text: text.substring(0, Math.min(400, text.length)), type: "text" }; item._attribs[attribute] = retVal; } if(retVal && this.unescapeHTML){ if ((attribute == "content" || attribute == "summary" || attribute == "subtitle") && !item["_"+attribute+"Escaped"]) { retVal.text = this._unescapeHTML(retVal.text); item["_"+attribute+"Escaped"] = true; } } return retVal ? dojo.isArray(retVal) ? retVal[0]: retVal : defaultValue; }, getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ // summary: // Return an attribute value // description: // 'item' must be an instance of an object created by the AtomReadStore instance. // Accepted attributes are id, subtitle, title, summary, content, author, updated, // published, category, link and alternate // item: // An item returned by a call to the 'fetch' method. // attribute: // A attribute of the Atom Entry // returns: // An array of values for the attribute value found, otherwise 'defaultValue' this._assertIsItem(item); this._assertIsAttribute(attribute); this._initItem(item); attribute = attribute.toLowerCase(); //If the attribute has previously been retrieved, then return it if(!item._attribs[attribute]){ this._parseItem(item); } var retVal = item._attribs[attribute]; return retVal ? ((retVal.length !== undefined && typeof(retVal) !== "string") ? retVal : [retVal]) : undefined; }, getAttributes: function(/* item */ item) { // summary: // Return an array of attribute names // description: // 'item' must be have been created by the AtomReadStore instance. // tag names of child elements and XML attribute names of attributes // specified to the element are returned along with special attribute // names applicable to the element including "tagName", "childNodes" // if the element has child elements, "text()" if the element has // child text nodes, and attribute names in '_attributeMap' that match // the tag name of the element. // item: // An XML element // returns: // An array of attributes found this._assertIsItem(item); if(!item._attribs){ this._initItem(item); this._parseItem(item); } var attrNames = []; for(var x in item._attribs){ attrNames.push(x); } return attrNames; //array }, hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){ // summary: // Check whether an element has the attribute // item: // 'item' must be created by the AtomReadStore instance. // attribute: // An attribute of an Atom Entry item. // returns: // True if the element has the attribute, otherwise false return (this.getValue(item, attribute) !== undefined); //boolean }, containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){ // summary: // Check whether the attribute values contain the value // item: // 'item' must be an instance of a dojox.data.XmlItem from the store instance. // attribute: // A tag name of a child element, An XML attribute name or one of // special names // returns: // True if the attribute values contain the value, otherwise false var values = this.getValues(item, attribute); for(var i = 0; i < values.length; i++){ if((typeof value === "string")){ if(values[i].toString && values[i].toString() === value){ return true; } }else if (values[i] === value){ return true; //boolean } } return false;//boolean }, isItem: function(/* anything */ something){ // summary: // Check whether the object is an item (XML element) // item: // An object to check // returns: // True if the object is an XML element, otherwise false if(something && something.element && something.store && something.store === this){ return true; //boolean } return false; //boolran }, isItemLoaded: function(/* anything */ something){ // summary: // Check whether the object is an item (XML element) and loaded // item: // An object to check // returns: // True if the object is an XML element, otherwise false return this.isItem(something); //boolean }, loadItem: function(/* object */ keywordArgs){ // summary: // Load an item (XML element) // keywordArgs: // object containing the args for loadItem. See dojo.data.api.Read.loadItem() }, getFeatures: function() { // summary: // Return supported data APIs // returns: // "dojo.data.api.Read" and "dojo.data.api.Write" var features = { "dojo.data.api.Read": true }; return features; //array }, getLabel: function(/* item */ item){ // summary: // See dojo.data.api.Read.getLabel() if((this.label !== "") && this.isItem(item)){ var label = this.getValue(item,this.label); if(label && label.text){ return label.text; }else if (label){ return label.toString(); }else{ return undefined; } } return undefined; //undefined }, getLabelAttributes: function(/* item */ item){ // summary: // See dojo.data.api.Read.getLabelAttributes() if(this.label !== ""){ return [this.label]; //array } return null; //null }, getFeedValue: function(attribute, defaultValue){ // summary: // Non-API method for retrieving values regarding the Atom feed, // rather than the Atom entries. var values = this.getFeedValues(attribute, defaultValue); if(dojo.isArray(values)){ return values[0]; } return values; }, getFeedValues: function(attribute, defaultValue){ // summary: // Non-API method for retrieving values regarding the Atom feed, // rather than the Atom entries. if(!this.doc){ return defaultValue; } if(!this._feedMetaData){ this._feedMetaData = { element: this.doc.getElementsByTagName("feed")[0], store: this, _attribs: {} }; this._parseItem(this._feedMetaData); } return this._feedMetaData._attribs[attribute] || defaultValue; }, _initItem: function(item){ // summary: // Initializes an item before it can be parsed. if(!item._attribs){ item._attribs = {}; } }, _fetchItems: function(request, fetchHandler, errorHandler) { // summary: // Retrieves the items from the Atom XML document. var url = this._getFetchUrl(request); if(!url){ errorHandler(new Error("No URL specified.")); return; } var localRequest = (!this.sendQuery ? request : null); // use request for _getItems() var _this = this; var docHandler = function(data){ _this.doc = data; var items = _this._getItems(data, localRequest); var query = request.query; if(query) { if(query.id) { items = dojo.filter(items, function(item){ return (_this.getValue(item, "id") == query.id); }); } else if(query.category){ items = dojo.filter(items, function(entry) { var cats = _this.getValues(entry, "category"); if(!cats){ return false; } return dojo.some(cats, "return item.term=='"+query.category+"'"); }); } } if (items && items.length > 0) { fetchHandler(items, request); } else { fetchHandler([], request); } }; if (this.doc) { docHandler(this.doc); }else{ var getArgs = { url: url, handleAs: "xml"//, // preventCache: true }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(docHandler); getHandler.addErrback(function(data){ errorHandler(data, request); }); } }, _getFetchUrl: function(request){ if(!this.sendQuery){ return this.url; } var query = request.query; if(!query){ return this.url; } if(dojo.isString(query)){ return this.url + query; } var queryString = ""; for(var name in query){ var value = query[name]; if(value){ if(queryString){ queryString += "&"; } queryString += (name + "=" + value); } } if(!queryString){ return this.url; } //Check to see if the URL already has query params or not. var fullUrl = this.url; if(fullUrl.indexOf("?") < 0){ fullUrl += "?"; }else{ fullUrl += "&"; } return fullUrl + queryString; }, _getItems: function(document, request) { // summary: // Parses the document in a first pass if(this._items){ return this._items; } var items = []; var nodes = []; if(document.childNodes.length < 1){ this._items = items; console.log("dojox.data.AtomReadStore: Received an invalid Atom document. Check the content type header"); return items; } var feedNodes = dojo.filter(document.childNodes, "return item.tagName && item.tagName.toLowerCase() == 'feed'"); var query = request.query; if(!feedNodes || feedNodes.length != 1){ console.log("dojox.data.AtomReadStore: Received an invalid Atom document, number of feed tags = " + (feedNodes? feedNodes.length : 0)); return items; } nodes = dojo.filter(feedNodes[0].childNodes, "return item.tagName && item.tagName.toLowerCase() == 'entry'"); if(request.onBegin){ request.onBegin(nodes.length, this.sendQuery ? request : {}); } for(var i = 0; i < nodes.length; i++){ var node = nodes[i]; if(node.nodeType != 1 /*ELEMENT_NODE*/){ continue; } items.push(this._getItem(node)); } this._items = items; return items; }, close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ // summary: // See dojo.data.api.Read.close() }, /* internal API */ _getItem: function(element){ return { element: element, store: this }; }, _parseItem: function(item) { var attribs = item._attribs; var _this = this; var text, type; function getNodeText(node){ var txt = node.textContent || node.innerHTML || node.innerXML; if(!txt && node.childNodes[0]){ var child = node.childNodes[0]; if (child && (child.nodeType == 3 || child.nodeType == 4)) { txt = node.childNodes[0].nodeValue; } } return txt; } function parseTextAndType(node) { return {text: getNodeText(node),type: node.getAttribute("type")}; } dojo.forEach(item.element.childNodes, function(node){ var tagName = node.tagName ? node.tagName.toLowerCase() : ""; switch(tagName){ case "title": attribs[tagName] = { text: getNodeText(node), type: node.getAttribute("type") }; break; case "subtitle": case "summary": case "content": attribs[tagName] = parseTextAndType(node); break; case "author": var nameNode ,uriNode; dojo.forEach(node.childNodes, function(child){ if(!child.tagName){ return; } switch(child.tagName.toLowerCase()){ case "name":nameNode = child;break; case "uri": uriNode = child; break; } }); var author = {}; if(nameNode && nameNode.length == 1){ author.name = getNodeText(nameNode[0]); } if(uriNode && uriNode.length == 1){ author.uri = getNodeText(uriNode[0]); } attribs[tagName] = author; break; case "id": attribs[tagName] = getNodeText(node); break; case "updated": attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node) );break; case "published": attribs[tagName] = dojo.date.stamp.fromISOString(getNodeText(node));break; case "category": if(!attribs[tagName]){ attribs[tagName] = []; } attribs[tagName].push({scheme:node.getAttribute("scheme"), term: node.getAttribute("term")}); break; case "link": if(!attribs[tagName]){ attribs[tagName] = []; } var link = { rel: node.getAttribute("rel"), href: node.getAttribute("href"), type: node.getAttribute("type")}; attribs[tagName].push(link); if(link.rel == "alternate") { attribs["alternate"] = link; } break; default: break; } }); }, _unescapeHTML : function(text) { //Replace HTML character codes with their unencoded equivalents, e.g. ’ with ' text = text.replace(/’/m , "'").replace(/″/m , "\"").replace(/</m,">").replace(/>/m,"<").replace(/&/m,"&"); return text; }, _assertIsItem: function(/* item */ item){ // summary: // This function tests whether the item passed in is indeed an item in the store. // item: // The item to test for being contained by the store. if(!this.isItem(item)){ throw new Error("dojox.data.AtomReadStore: Invalid item argument."); } }, _assertIsAttribute: function(/* attribute-name-string */ attribute){ // summary: // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. // attribute: // The attribute to test for being contained by the store. if(typeof attribute !== "string"){ throw new Error("dojox.data.AtomReadStore: Invalid attribute argument."); } } }); dojo.extend(dojox.data.AtomReadStore,dojo.data.util.simpleFetch);