| // Copyright 2006 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /** |
| * @fileoverview |
| * Implementations of DataNode for wrapping XML data. |
| * |
| */ |
| |
| goog.provide('goog.ds.XmlDataSource'); |
| goog.provide('goog.ds.XmlHttpDataSource'); |
| |
| goog.require('goog.Uri'); |
| goog.require('goog.dom.NodeType'); |
| goog.require('goog.dom.xml'); |
| goog.require('goog.ds.BasicNodeList'); |
| goog.require('goog.ds.DataManager'); |
| goog.require('goog.ds.DataNode'); |
| goog.require('goog.ds.LoadState'); |
| goog.require('goog.ds.logger'); |
| goog.require('goog.net.XhrIo'); |
| goog.require('goog.string'); |
| |
| |
| |
| /** |
| * Data source whose backing is an xml node |
| * |
| * @param {Node} node The XML node. Can be null. |
| * @param {goog.ds.XmlDataSource} parent Parent of XML element. Can be null. |
| * @param {string=} opt_name The name of this node relative to the parent node. |
| * |
| * @extends {goog.ds.DataNode} |
| * @constructor |
| */ |
| // TODO(arv): Use interfaces when available. |
| goog.ds.XmlDataSource = function(node, parent, opt_name) { |
| this.parent_ = parent; |
| this.dataName_ = opt_name || (node ? node.nodeName : ''); |
| this.setNode_(node); |
| }; |
| |
| |
| /** |
| * Constant to select XML attributes for getChildNodes |
| * @type {string} |
| * @private |
| */ |
| goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_ = '@*'; |
| |
| |
| /** |
| * Set the current root nodeof the data source. |
| * Can be an attribute node, text node, or element node |
| * @param {Node} node The node. Can be null. |
| * |
| * @private |
| */ |
| goog.ds.XmlDataSource.prototype.setNode_ = function(node) { |
| this.node_ = node; |
| if (node != null) { |
| switch (node.nodeType) { |
| case goog.dom.NodeType.ATTRIBUTE: |
| case goog.dom.NodeType.TEXT: |
| this.value_ = node.nodeValue; |
| break; |
| case goog.dom.NodeType.ELEMENT: |
| if (node.childNodes.length == 1 && |
| node.firstChild.nodeType == goog.dom.NodeType.TEXT) { |
| this.value_ = node.firstChild.nodeValue; |
| } |
| } |
| } |
| }; |
| |
| |
| /** |
| * Creates the DataNodeList with the child nodes for this element. |
| * Allows for only building list as needed. |
| * |
| * @private |
| */ |
| goog.ds.XmlDataSource.prototype.createChildNodes_ = function() { |
| if (this.childNodeList_) { |
| return; |
| } |
| var childNodeList = new goog.ds.BasicNodeList(); |
| if (this.node_ != null) { |
| var childNodes = this.node_.childNodes; |
| for (var i = 0, childNode; childNode = childNodes[i]; i++) { |
| if (childNode.nodeType != goog.dom.NodeType.TEXT || |
| !goog.ds.XmlDataSource.isEmptyTextNodeValue_(childNode.nodeValue)) { |
| var newNode = new goog.ds.XmlDataSource(childNode, |
| this, childNode.nodeName); |
| childNodeList.add(newNode); |
| } |
| } |
| } |
| this.childNodeList_ = childNodeList; |
| }; |
| |
| |
| /** |
| * Creates the DataNodeList with the attributes for the element |
| * Allows for only building list as needed. |
| * |
| * @private |
| */ |
| goog.ds.XmlDataSource.prototype.createAttributes_ = function() { |
| if (this.attributes_) { |
| return; |
| } |
| var attributes = new goog.ds.BasicNodeList(); |
| if (this.node_ != null && this.node_.attributes != null) { |
| var atts = this.node_.attributes; |
| for (var i = 0, att; att = atts[i]; i++) { |
| var newNode = new goog.ds.XmlDataSource(att, this, att.nodeName); |
| attributes.add(newNode); |
| } |
| } |
| this.attributes_ = attributes; |
| }; |
| |
| |
| /** |
| * Get the value of the node |
| * @return {Object} The value of the node, or null if no value. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.get = function() { |
| this.createChildNodes_(); |
| return this.value_; |
| }; |
| |
| |
| /** |
| * Set the value of the node |
| * @param {*} value The new value of the node. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.set = function(value) { |
| throw Error('Can\'t set on XmlDataSource yet'); |
| }; |
| |
| |
| /** @override */ |
| goog.ds.XmlDataSource.prototype.getChildNodes = function(opt_selector) { |
| if (opt_selector && opt_selector == |
| goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_) { |
| this.createAttributes_(); |
| return this.attributes_; |
| } else if (opt_selector == null || |
| opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) { |
| this.createChildNodes_(); |
| return this.childNodeList_; |
| } else { |
| throw Error('Unsupported selector'); |
| } |
| |
| }; |
| |
| |
| /** |
| * Gets a named child node of the current node |
| * @param {string} name The node name. |
| * @return {goog.ds.DataNode} The child node, or null if |
| * no node of this name exists. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.getChildNode = function(name) { |
| if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) { |
| var att = this.node_.getAttributeNode(name.substring(1)); |
| return att ? new goog.ds.XmlDataSource(att, this) : null; |
| } else { |
| return /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name)); |
| } |
| }; |
| |
| |
| /** |
| * Gets the value of a child node |
| * @param {string} name The node name. |
| * @return {*} The value of the node, or null if no value or the child node |
| * doesn't exist. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.getChildNodeValue = function(name) { |
| if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) { |
| var node = this.node_.getAttributeNode(name.substring(1)); |
| return node ? node.nodeValue : null; |
| } else { |
| var node = this.getChildNode(name); |
| return node ? node.get() : null; |
| } |
| }; |
| |
| |
| /** |
| * Get the name of the node relative to the parent node |
| * @return {string} The name of the node. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.getDataName = function() { |
| return this.dataName_; |
| }; |
| |
| |
| /** |
| * Setthe name of the node relative to the parent node |
| * @param {string} name The name of the node. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.setDataName = function(name) { |
| this.dataName_ = name; |
| }; |
| |
| |
| /** |
| * Gets the a qualified data path to this node |
| * @return {string} The data path. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.getDataPath = function() { |
| var parentPath = ''; |
| if (this.parent_) { |
| parentPath = this.parent_.getDataPath() + |
| (this.dataName_.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' : |
| goog.ds.STR_PATH_SEPARATOR); |
| } |
| |
| return parentPath + this.dataName_; |
| }; |
| |
| |
| /** |
| * Load or reload the backing data for this node |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.load = function() { |
| // Nothing to do |
| }; |
| |
| |
| /** |
| * Gets the state of the backing data for this node |
| * @return {goog.ds.LoadState} The state. |
| * @override |
| */ |
| goog.ds.XmlDataSource.prototype.getLoadState = function() { |
| return this.node_ ? goog.ds.LoadState.LOADED : goog.ds.LoadState.NOT_LOADED; |
| }; |
| |
| |
| /** |
| * Check whether a node is an empty text node. Nodes consisting of only white |
| * space (#x20, #xD, #xA, #x9) can generally be collapsed to a zero length |
| * text string. |
| * @param {string} str String to match. |
| * @return {boolean} True if string equates to empty text node. |
| * @private |
| */ |
| goog.ds.XmlDataSource.isEmptyTextNodeValue_ = function(str) { |
| return /^[\r\n\t ]*$/.test(str); |
| }; |
| |
| |
| /** |
| * Creates an XML document with one empty node. |
| * Useful for places where you need a node that |
| * can be queried against. |
| * |
| * @return {Document} Document with one empty node. |
| * @private |
| */ |
| goog.ds.XmlDataSource.createChildlessDocument_ = function() { |
| return goog.dom.xml.createDocument('nothing'); |
| }; |
| |
| |
| |
| /** |
| * Data source whose backing is an XMLHttpRequest, |
| * |
| * A URI of an empty string will mean that no request is made |
| * and the data source will be a single, empty node. |
| * |
| * @param {(string|goog.Uri)} uri URL of the XMLHttpRequest. |
| * @param {string} name Name of the datasource. |
| * |
| * implements goog.ds.XmlHttpDataSource. |
| * @constructor |
| * @extends {goog.ds.XmlDataSource} |
| * @final |
| */ |
| goog.ds.XmlHttpDataSource = function(uri, name) { |
| goog.ds.XmlDataSource.call(this, null, null, name); |
| if (uri) { |
| this.uri_ = new goog.Uri(uri); |
| } else { |
| this.uri_ = null; |
| } |
| }; |
| goog.inherits(goog.ds.XmlHttpDataSource, goog.ds.XmlDataSource); |
| |
| |
| /** |
| * Default load state is NOT_LOADED |
| * @private |
| */ |
| goog.ds.XmlHttpDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED; |
| |
| |
| /** |
| * Load or reload the backing data for this node. |
| * Fires the XMLHttpRequest |
| * @override |
| */ |
| goog.ds.XmlHttpDataSource.prototype.load = function() { |
| if (this.uri_) { |
| goog.log.info(goog.ds.logger, 'Sending XML request for DataSource ' + |
| this.getDataName() + ' to ' + this.uri_); |
| this.loadState_ = goog.ds.LoadState.LOADING; |
| |
| goog.net.XhrIo.send(this.uri_, goog.bind(this.complete_, this)); |
| } else { |
| this.node_ = goog.ds.XmlDataSource.createChildlessDocument_(); |
| this.loadState_ = goog.ds.LoadState.NOT_LOADED; |
| } |
| }; |
| |
| |
| /** |
| * Gets the state of the backing data for this node |
| * @return {goog.ds.LoadState} The state. |
| * @override |
| */ |
| goog.ds.XmlHttpDataSource.prototype.getLoadState = function() { |
| return this.loadState_; |
| }; |
| |
| |
| /** |
| * Handles the completion of an XhrIo request. Dispatches to success or load |
| * based on the result. |
| * @param {!goog.events.Event} e The XhrIo event object. |
| * @private |
| */ |
| goog.ds.XmlHttpDataSource.prototype.complete_ = function(e) { |
| var xhr = /** @type {goog.net.XhrIo} */ (e.target); |
| if (xhr && xhr.isSuccess()) { |
| this.success_(xhr); |
| } else { |
| this.failure_(); |
| } |
| }; |
| |
| |
| /** |
| * Success result. Checks whether valid XML was returned |
| * and sets the XML and loadstate. |
| * |
| * @param {!goog.net.XhrIo} xhr The successful XhrIo object. |
| * @private |
| */ |
| goog.ds.XmlHttpDataSource.prototype.success_ = function(xhr) { |
| goog.log.info(goog.ds.logger, |
| 'Got data for DataSource ' + this.getDataName()); |
| var xml = xhr.getResponseXml(); |
| |
| // Fix for case where IE returns valid XML as text but |
| // doesn't parse by default |
| if (xml && !xml.hasChildNodes() && |
| goog.isObject(xhr.getResponseText())) { |
| xml = goog.dom.xml.loadXml(xhr.getResponseText()); |
| } |
| // Failure result |
| if (!xml || !xml.hasChildNodes()) { |
| this.loadState_ = goog.ds.LoadState.FAILED; |
| this.node_ = goog.ds.XmlDataSource.createChildlessDocument_(); |
| } else { |
| this.loadState_ = goog.ds.LoadState.LOADED; |
| this.node_ = xml.documentElement; |
| } |
| |
| if (this.getDataName()) { |
| goog.ds.DataManager.getInstance().fireDataChange(this.getDataName()); |
| } |
| }; |
| |
| |
| /** |
| * Failure result |
| * |
| * @private |
| */ |
| goog.ds.XmlHttpDataSource.prototype.failure_ = function() { |
| goog.log.info(goog.ds.logger, 'Data retrieve failed for DataSource ' + |
| this.getDataName()); |
| |
| this.loadState_ = goog.ds.LoadState.FAILED; |
| this.node_ = goog.ds.XmlDataSource.createChildlessDocument_(); |
| |
| if (this.getDataName()) { |
| goog.ds.DataManager.getInstance().fireDataChange(this.getDataName()); |
| } |
| }; |