if (!dojo._hasResource["dojox.data.OpmlStore"]) { // _hasResource checks added // by build. Do not use // _hasResource directly in // your code. dojo._hasResource["dojox.data.OpmlStore"] = true; dojo.provide("dojox.data.OpmlStore"); dojo.require("dojo.data.util.filter"); dojo.require("dojo.data.util.simpleFetch"); dojo.declare("dojox.data.OpmlStore", null, { /* * summary: The OpmlStore implements the dojo.data.api.Read API. */ /* * examples: var opmlStore = new * dojo.data.OpmlStore({url:"geography.xml"}); var opmlStore = new * dojo.data.OpmlStore({url:"http://example.com/geography.xml"}); */ constructor : function(/* Object */keywordParameters) { // summary: constructor // keywordParameters: {url: String, label: String} Where label is // optional and configures what should be used as the return from // getLabel() this._xmlData = null; this._arrayOfTopLevelItems = []; this._arrayOfAllItems = []; this._metadataNodes = null; this._loadFinished = false; this.url = keywordParameters.url; this._opmlData = keywordParameters.data; // XML DOM Document if (keywordParameters.label) { this.label = keywordParameters.label; } this._loadInProgress = false; // Got to track the initial load to // prevent duelling loads of the // dataset. this._queuedFetches = []; this._identityMap = {}; this._identCount = 0; this._idProp = "_I"; }, label : "text", url : "", _assertIsItem : function(/* item */item) { if (!this.isItem(item)) { throw new Error("dojo.data.OpmlStore: a function was passed an item argument that was not an item"); } }, _assertIsAttribute : function(/* item || 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 (!dojo.isString(attribute)) { throw new Error("dojox.data.OpmlStore: a function was passed an attribute argument that was not an attribute object nor an attribute name string"); } }, _removeChildNodesThatAreNotElementNodes : function(/* node */node, /* boolean */ recursive) { var childNodes = node.childNodes; if (childNodes.length === 0) { return; } var nodesToRemove = []; var i, childNode; for (i = 0; i < childNodes.length; ++i) { childNode = childNodes[i]; if (childNode.nodeType != 1) { nodesToRemove.push(childNode); } } for (i = 0; i < nodesToRemove.length; ++i) { childNode = nodesToRemove[i]; node.removeChild(childNode); } if (recursive) { for (i = 0; i < childNodes.length; ++i) { childNode = childNodes[i]; this._removeChildNodesThatAreNotElementNodes(childNode, recursive); } } }, _processRawXmlTree : function(/* xmlDoc */rawXmlTree) { this._loadFinished = true; this._xmlData = rawXmlTree; var headNodes = rawXmlTree.getElementsByTagName('head'); var headNode = headNodes[0]; if (headNode) { this._removeChildNodesThatAreNotElementNodes(headNode); this._metadataNodes = headNode.childNodes; } var bodyNodes = rawXmlTree.getElementsByTagName('body'); var bodyNode = bodyNodes[0]; if (bodyNode) { this._removeChildNodesThatAreNotElementNodes(bodyNode, true); var bodyChildNodes = bodyNodes[0].childNodes; for (var i = 0; i < bodyChildNodes.length; ++i) { var node = bodyChildNodes[i]; if (node.tagName == 'outline') { this._identityMap[this._identCount] = node; this._identCount++; this._arrayOfTopLevelItems.push(node); this._arrayOfAllItems.push(node); this._checkChildNodes(node); } } } }, _checkChildNodes : function(node /* Node */) { // summary: // Internal function to recurse over all child nodes from the store // and add them // As non-toplevel items // description: // Internal function to recurse over all child nodes from the store // and add them // As non-toplevel items // // node: // The child node to walk. if (node.firstChild) { for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; if (child.tagName == 'outline') { this._identityMap[this._identCount] = child; this._identCount++; this._arrayOfAllItems.push(child); this._checkChildNodes(child); } } } }, _getItemsArray : function(/* object? */queryOptions) { // summary: // Internal function to determine which list of items to search // over. // queryOptions: The query options parameter, if any. if (queryOptions && queryOptions.deep) { return this._arrayOfAllItems; } return this._arrayOfTopLevelItems; }, /*********************************************************************** * dojo.data.api.Read API **********************************************************************/ getValue : function( /* item */item, /* attribute || attribute-name-string */attribute, /* value? */defaultValue) { // summary: // See dojo.data.api.Read.getValue() this._assertIsItem(item); this._assertIsAttribute(attribute); if (attribute == 'children') { return (item.firstChild || defaultValue); // Object } else { var value = item.getAttribute(attribute); return (value !== undefined) ? value : defaultValue; // Object } }, getValues : function(/* item */item, /* attribute || attribute-name-string */attribute) { // summary: // See dojo.data.api.Read.getValues() this._assertIsItem(item); this._assertIsAttribute(attribute); var array = []; if (attribute == 'children') { for (var i = 0; i < item.childNodes.length; ++i) { array.push(item.childNodes[i]); } } else if (item.getAttribute(attribute) !== null) { array.push(item.getAttribute(attribute)); } return array; // Array }, getAttributes : function(/* item */item) { // summary: // See dojo.data.api.Read.getAttributes() this._assertIsItem(item); var attributes = []; var xmlNode = item; var xmlAttributes = xmlNode.attributes; for (var i = 0; i < xmlAttributes.length; ++i) { var xmlAttribute = xmlAttributes.item(i); attributes.push(xmlAttribute.nodeName); } if (xmlNode.childNodes.length > 0) { attributes.push('children'); } return attributes; // Array }, hasAttribute : function( /* item */item, /* attribute || attribute-name-string */attribute) { // summary: // See dojo.data.api.Read.hasAttribute() return (this.getValues(item, attribute).length > 0); // Boolean }, containsValue : function(/* item */item, /* attribute || attribute-name-string */attribute, /* anything */value) { // summary: // See dojo.data.api.Read.containsValue() var regexp = undefined; if (typeof value === "string") { regexp = dojo.data.util.filter.patternToRegExp(value, false); } return this._containsValue(item, attribute, value, regexp); // boolean. }, _containsValue : function( /* item */item, /* attribute || attribute-name-string */attribute, /* anything */value, /* RegExp? */regexp) { // summary: // Internal function for looking at the values contained by the // item. // description: // Internal function for looking at the values contained by the // item. This // function allows for denoting if the comparison should be case // sensitive for // strings or not (for handling filtering cases where string case // should not matter) // // item: // The data item to examine for attribute values. // attribute: // The attribute to inspect. // value: // The value to match. // regexp: // Optional regular expression generated off value if value was of // string type to handle wildcarding. // If present and attribute values are string, then it can be used // for comparison instead of 'value' var values = this.getValues(item, attribute); for (var i = 0; i < values.length; ++i) { var possibleValue = values[i]; if (typeof possibleValue === "string" && regexp) { return (possibleValue.match(regexp) !== null); } else { // Non-string matching. if (value === possibleValue) { return true; // Boolean } } } return false; // Boolean }, isItem : function(/* anything */something) { // summary: // See dojo.data.api.Read.isItem() // description: // Four things are verified to ensure that "something" is an item: // something can not be null, the nodeType must be an XML Element, // the tagName must be "outline", and the node must be a member of // XML document for this datastore. return (something && something.nodeType == 1 && something.tagName == 'outline' && something.ownerDocument === this._xmlData); // Boolean }, isItemLoaded : function(/* anything */something) { // summary: // See dojo.data.api.Read.isItemLoaded() // OpmlStore loads every item, so if it's an item, then it's loaded. return this.isItem(something); // Boolean }, loadItem : function(/* item */item) { // summary: // See dojo.data.api.Read.loadItem() // description: // The OpmlStore always loads all items, so if it's an item, then // it's loaded. // From the dojo.data.api.Read.loadItem docs: // If a call to isItemLoaded() returns true before loadItem() is // even called, // then loadItem() need not do any work at all and will not even // invoke the callback handlers. }, getLabel : function(/* item */item) { // summary: // See dojo.data.api.Read.getLabel() if (this.isItem(item)) { return this.getValue(item, this.label); // String } return undefined; // undefined }, getLabelAttributes : function(/* item */item) { // summary: // See dojo.data.api.Read.getLabelAttributes() return [this.label]; // array }, // The dojo.data.api.Read.fetch() function is implemented as // a mixin from dojo.data.util.simpleFetch. // That mixin requires us to define _fetchItems(). _fetchItems : function( /* Object */keywordArgs, /* Function */findCallback, /* Function */errorCallback) { // summary: // See dojo.data.util.simpleFetch.fetch() var self = this; var filter = function(requestArgs, arrayOfItems) { var items = null; if (requestArgs.query) { items = []; var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; // See if there are any string values that can be regexp // parsed first to avoid multiple regexp gens on the // same value for each item examined. Much more efficient. var regexpList = {}; for (var key in requestArgs.query) { var value = requestArgs.query[key]; if (typeof value === "string") { regexpList[key] = dojo.data.util.filter .patternToRegExp(value, ignoreCase); } } for (var i = 0; i < arrayOfItems.length; ++i) { var match = true; var candidateItem = arrayOfItems[i]; for (var key in requestArgs.query) { var value = requestArgs.query[key]; if (!self._containsValue(candidateItem, key, value, regexpList[key])) { match = false; } } if (match) { items.push(candidateItem); } } } else { // We want a copy to pass back in case the parent wishes to // sort the array. We shouldn't allow resort // of the internal list so that multiple callers can get // lists and sort without affecting each other. if (arrayOfItems.length > 0) { items = arrayOfItems.slice(0, arrayOfItems.length); } } findCallback(items, requestArgs); }; if (this._loadFinished) { filter(keywordArgs, this ._getItemsArray(keywordArgs.queryOptions)); } else { // If fetches come in before the loading has finished, but while // a load is in progress, we have to defer the fetching to be // invoked in the callback. if (this._loadInProgress) { this._queuedFetches.push({ args : keywordArgs, filter : filter }); } else { if (this.url !== "") { this._loadInProgress = true; var getArgs = { url : self.url, handleAs : "xml" }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(function(data) { self._processRawXmlTree(data); filter( keywordArgs, self ._getItemsArray(keywordArgs.queryOptions)); self._handleQueuedFetches(); }); getHandler.addErrback(function(error) { throw error; }); } else if (this._opmlData) { this._processRawXmlTree(this._opmlData); this._opmlData = null; filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); } else { throw new Error("dojox.data.OpmlStore: No OPML source data was provided as either URL or XML data input."); } } } }, getFeatures : function() { // summary: See dojo.data.api.Read.getFeatures() var features = { 'dojo.data.api.Read' : true, 'dojo.data.api.Identity' : true }; return features; // Object }, /*********************************************************************** * dojo.data.api.Identity API **********************************************************************/ getIdentity : function(/* item */item) { // summary: // See dojo.data.api.Identity.getIdentity() if (this.isItem(item)) { // No ther way to do this other than O(n) without // complete rework of how the tree stores nodes. for (var i in this._identityMap) { if (this._identityMap[i] === item) { return i; } } } return null; // null }, fetchItemByIdentity : function(/* Object */keywordArgs) { // summary: // See dojo.data.api.Identity.fetchItemByIdentity() // Hasn't loaded yet, we have to trigger the load. if (!this._loadFinished) { var self = this; if (this.url !== "") { // If fetches come in before the loading has finished, but // while // a load is in progress, we have to defer the fetching to // be // invoked in the callback. if (this._loadInProgress) { this._queuedFetches.push({ args : keywordArgs }); } else { this._loadInProgress = true; var getArgs = { url : self.url, handleAs : "xml" }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(function(data) { var scope = keywordArgs.scope ? keywordArgs.scope : dojo.global; try { self._processRawXmlTree(data); var item = self._identityMap[keywordArgs.identity]; if (!self.isItem(item)) { item = null; } if (keywordArgs.onItem) { keywordArgs.onItem.call(scope, item); } self._handleQueuedFetches(); } catch (error) { if (keywordArgs.onError) { keywordArgs.onError.call(scope, error); } } }); getHandler.addErrback(function(error) { this._loadInProgress = false; if (keywordArgs.onError) { var scope = keywordArgs.scope ? keywordArgs.scope : dojo.global; keywordArgs.onError.call(scope, error); } }); } } else if (this._opmlData) { this._processRawXmlTree(this._opmlData); this._opmlData = null; var item = this._identityMap[keywordArgs.identity]; if (!self.isItem(item)) { item = null; } if (keywordArgs.onItem) { var scope = keywordArgs.scope ? keywordArgs.scope : dojo.global; keywordArgs.onItem.call(scope, item); } } } else { // Already loaded. We can just look it up and call back. var item = this._identityMap[keywordArgs.identity]; if (!this.isItem(item)) { item = null; } if (keywordArgs.onItem) { var scope = keywordArgs.scope ? keywordArgs.scope : dojo.global; keywordArgs.onItem.call(scope, item); } } }, getIdentityAttributes : function(/* item */item) { // summary: // See dojo.data.api.Identity.getIdentifierAttributes() // Identity isn't a public attribute in the item, it's the node // count. // So, return null. return null; }, _handleQueuedFetches : function() { // summary: // Internal function to execute delayed request in the store. // Execute any deferred fetches now. if (this._queuedFetches.length > 0) { for (var i = 0; i < this._queuedFetches.length; i++) { var fData = this._queuedFetches[i]; var delayedQuery = fData.args; var delayedFilter = fData.filter; if (delayedFilter) { delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); } else { this.fetchItemByIdentity(delayedQuery); } } this._queuedFetches = []; } }, close : function( /* dojo.data.api.Request || keywordArgs || null */request) { // summary: // See dojo.data.api.Read.close() } }); // Mix in the simple fetch implementation to this class. dojo.extend(dojox.data.OpmlStore, dojo.data.util.simpleFetch); }