xmldatasource.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview
  16. * Implementations of DataNode for wrapping XML data.
  17. *
  18. */
  19. goog.provide('goog.ds.XmlDataSource');
  20. goog.provide('goog.ds.XmlHttpDataSource');
  21. goog.require('goog.Uri');
  22. goog.require('goog.dom.NodeType');
  23. goog.require('goog.dom.xml');
  24. goog.require('goog.ds.BasicNodeList');
  25. goog.require('goog.ds.DataManager');
  26. goog.require('goog.ds.DataNode');
  27. goog.require('goog.ds.LoadState');
  28. goog.require('goog.ds.logger');
  29. goog.require('goog.log');
  30. goog.require('goog.net.XhrIo');
  31. goog.require('goog.string');
  32. /**
  33. * Data source whose backing is an xml node
  34. *
  35. * @param {Node} node The XML node. Can be null.
  36. * @param {goog.ds.XmlDataSource} parent Parent of XML element. Can be null.
  37. * @param {string=} opt_name The name of this node relative to the parent node.
  38. *
  39. * @extends {goog.ds.DataNode}
  40. * @constructor
  41. */
  42. // TODO(arv): Use interfaces when available.
  43. goog.ds.XmlDataSource = function(node, parent, opt_name) {
  44. this.parent_ = parent;
  45. this.dataName_ = opt_name || (node ? node.nodeName : '');
  46. this.setNode_(node);
  47. };
  48. /**
  49. * Constant to select XML attributes for getChildNodes
  50. * @type {string}
  51. * @private
  52. */
  53. goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_ = '@*';
  54. /**
  55. * Set the current root nodeof the data source.
  56. * Can be an attribute node, text node, or element node
  57. * @param {Node} node The node. Can be null.
  58. *
  59. * @private
  60. */
  61. goog.ds.XmlDataSource.prototype.setNode_ = function(node) {
  62. this.node_ = node;
  63. if (node != null) {
  64. switch (node.nodeType) {
  65. case goog.dom.NodeType.ATTRIBUTE:
  66. case goog.dom.NodeType.TEXT:
  67. this.value_ = node.nodeValue;
  68. break;
  69. case goog.dom.NodeType.ELEMENT:
  70. if (node.childNodes.length == 1 &&
  71. node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
  72. this.value_ = node.firstChild.nodeValue;
  73. }
  74. }
  75. }
  76. };
  77. /**
  78. * Creates the DataNodeList with the child nodes for this element.
  79. * Allows for only building list as needed.
  80. *
  81. * @private
  82. */
  83. goog.ds.XmlDataSource.prototype.createChildNodes_ = function() {
  84. if (this.childNodeList_) {
  85. return;
  86. }
  87. var childNodeList = new goog.ds.BasicNodeList();
  88. if (this.node_ != null) {
  89. var childNodes = this.node_.childNodes;
  90. for (var i = 0, childNode; childNode = childNodes[i]; i++) {
  91. if (childNode.nodeType != goog.dom.NodeType.TEXT ||
  92. !goog.ds.XmlDataSource.isEmptyTextNodeValue_(childNode.nodeValue)) {
  93. var newNode =
  94. new goog.ds.XmlDataSource(childNode, this, childNode.nodeName);
  95. childNodeList.add(newNode);
  96. }
  97. }
  98. }
  99. this.childNodeList_ = childNodeList;
  100. };
  101. /**
  102. * Creates the DataNodeList with the attributes for the element
  103. * Allows for only building list as needed.
  104. *
  105. * @private
  106. */
  107. goog.ds.XmlDataSource.prototype.createAttributes_ = function() {
  108. if (this.attributes_) {
  109. return;
  110. }
  111. var attributes = new goog.ds.BasicNodeList();
  112. if (this.node_ != null && this.node_.attributes != null) {
  113. var atts = this.node_.attributes;
  114. for (var i = 0, att; att = atts[i]; i++) {
  115. var newNode = new goog.ds.XmlDataSource(att, this, att.nodeName);
  116. attributes.add(newNode);
  117. }
  118. }
  119. this.attributes_ = attributes;
  120. };
  121. /**
  122. * Get the value of the node
  123. * @return {Object} The value of the node, or null if no value.
  124. * @override
  125. */
  126. goog.ds.XmlDataSource.prototype.get = function() {
  127. this.createChildNodes_();
  128. return this.value_;
  129. };
  130. /**
  131. * Set the value of the node
  132. * @param {*} value The new value of the node.
  133. * @override
  134. */
  135. goog.ds.XmlDataSource.prototype.set = function(value) {
  136. throw Error('Can\'t set on XmlDataSource yet');
  137. };
  138. /** @override */
  139. goog.ds.XmlDataSource.prototype.getChildNodes = function(opt_selector) {
  140. if (opt_selector &&
  141. opt_selector == goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_) {
  142. this.createAttributes_();
  143. return this.attributes_;
  144. } else if (
  145. opt_selector == null ||
  146. opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
  147. this.createChildNodes_();
  148. return this.childNodeList_;
  149. } else {
  150. throw Error('Unsupported selector');
  151. }
  152. };
  153. /**
  154. * Gets a named child node of the current node
  155. * @param {string} name The node name.
  156. * @return {goog.ds.DataNode} The child node, or null if
  157. * no node of this name exists.
  158. * @override
  159. */
  160. goog.ds.XmlDataSource.prototype.getChildNode = function(name) {
  161. if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
  162. var att = this.node_.getAttributeNode(name.substring(1));
  163. return att ? new goog.ds.XmlDataSource(att, this) : null;
  164. } else {
  165. return /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
  166. }
  167. };
  168. /**
  169. * Gets the value of a child node
  170. * @param {string} name The node name.
  171. * @return {*} The value of the node, or null if no value or the child node
  172. * doesn't exist.
  173. * @override
  174. */
  175. goog.ds.XmlDataSource.prototype.getChildNodeValue = function(name) {
  176. if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
  177. var node = this.node_.getAttributeNode(name.substring(1));
  178. return node ? node.nodeValue : null;
  179. } else {
  180. var node = this.getChildNode(name);
  181. return node ? node.get() : null;
  182. }
  183. };
  184. /**
  185. * Get the name of the node relative to the parent node
  186. * @return {string} The name of the node.
  187. * @override
  188. */
  189. goog.ds.XmlDataSource.prototype.getDataName = function() {
  190. return this.dataName_;
  191. };
  192. /**
  193. * Setthe name of the node relative to the parent node
  194. * @param {string} name The name of the node.
  195. * @override
  196. */
  197. goog.ds.XmlDataSource.prototype.setDataName = function(name) {
  198. this.dataName_ = name;
  199. };
  200. /**
  201. * Gets the a qualified data path to this node
  202. * @return {string} The data path.
  203. * @override
  204. */
  205. goog.ds.XmlDataSource.prototype.getDataPath = function() {
  206. var parentPath = '';
  207. if (this.parent_) {
  208. parentPath = this.parent_.getDataPath() +
  209. (this.dataName_.indexOf(goog.ds.STR_ARRAY_START) != -1 ?
  210. '' :
  211. goog.ds.STR_PATH_SEPARATOR);
  212. }
  213. return parentPath + this.dataName_;
  214. };
  215. /**
  216. * Load or reload the backing data for this node
  217. * @override
  218. */
  219. goog.ds.XmlDataSource.prototype.load = function() {
  220. // Nothing to do
  221. };
  222. /**
  223. * Gets the state of the backing data for this node
  224. * @return {goog.ds.LoadState} The state.
  225. * @override
  226. */
  227. goog.ds.XmlDataSource.prototype.getLoadState = function() {
  228. return this.node_ ? goog.ds.LoadState.LOADED : goog.ds.LoadState.NOT_LOADED;
  229. };
  230. /**
  231. * Check whether a node is an empty text node. Nodes consisting of only white
  232. * space (#x20, #xD, #xA, #x9) can generally be collapsed to a zero length
  233. * text string.
  234. * @param {string} str String to match.
  235. * @return {boolean} True if string equates to empty text node.
  236. * @private
  237. */
  238. goog.ds.XmlDataSource.isEmptyTextNodeValue_ = function(str) {
  239. return /^[\r\n\t ]*$/.test(str);
  240. };
  241. /**
  242. * Creates an XML document with one empty node.
  243. * Useful for places where you need a node that
  244. * can be queried against.
  245. *
  246. * @return {Document} Document with one empty node.
  247. * @private
  248. */
  249. goog.ds.XmlDataSource.createChildlessDocument_ = function() {
  250. return goog.dom.xml.createDocument('nothing');
  251. };
  252. /**
  253. * Data source whose backing is an XMLHttpRequest,
  254. *
  255. * A URI of an empty string will mean that no request is made
  256. * and the data source will be a single, empty node.
  257. *
  258. * @param {(string|goog.Uri)} uri URL of the XMLHttpRequest.
  259. * @param {string} name Name of the datasource.
  260. *
  261. * implements goog.ds.XmlHttpDataSource.
  262. * @constructor
  263. * @extends {goog.ds.XmlDataSource}
  264. * @final
  265. */
  266. goog.ds.XmlHttpDataSource = function(uri, name) {
  267. goog.ds.XmlDataSource.call(this, null, null, name);
  268. if (uri) {
  269. this.uri_ = new goog.Uri(uri);
  270. } else {
  271. this.uri_ = null;
  272. }
  273. };
  274. goog.inherits(goog.ds.XmlHttpDataSource, goog.ds.XmlDataSource);
  275. /**
  276. * Default load state is NOT_LOADED
  277. * @private
  278. */
  279. goog.ds.XmlHttpDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
  280. /**
  281. * Load or reload the backing data for this node.
  282. * Fires the XMLHttpRequest
  283. * @override
  284. */
  285. goog.ds.XmlHttpDataSource.prototype.load = function() {
  286. if (this.uri_) {
  287. goog.log.info(
  288. goog.ds.logger, 'Sending XML request for DataSource ' +
  289. this.getDataName() + ' to ' + this.uri_);
  290. this.loadState_ = goog.ds.LoadState.LOADING;
  291. goog.net.XhrIo.send(this.uri_, goog.bind(this.complete_, this));
  292. } else {
  293. this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
  294. this.loadState_ = goog.ds.LoadState.NOT_LOADED;
  295. }
  296. };
  297. /**
  298. * Gets the state of the backing data for this node
  299. * @return {goog.ds.LoadState} The state.
  300. * @override
  301. */
  302. goog.ds.XmlHttpDataSource.prototype.getLoadState = function() {
  303. return this.loadState_;
  304. };
  305. /**
  306. * Handles the completion of an XhrIo request. Dispatches to success or load
  307. * based on the result.
  308. * @param {!goog.events.Event} e The XhrIo event object.
  309. * @private
  310. */
  311. goog.ds.XmlHttpDataSource.prototype.complete_ = function(e) {
  312. var xhr = /** @type {goog.net.XhrIo} */ (e.target);
  313. if (xhr && xhr.isSuccess()) {
  314. this.success_(xhr);
  315. } else {
  316. this.failure_();
  317. }
  318. };
  319. /**
  320. * Success result. Checks whether valid XML was returned
  321. * and sets the XML and loadstate.
  322. *
  323. * @param {!goog.net.XhrIo} xhr The successful XhrIo object.
  324. * @private
  325. */
  326. goog.ds.XmlHttpDataSource.prototype.success_ = function(xhr) {
  327. goog.log.info(
  328. goog.ds.logger, 'Got data for DataSource ' + this.getDataName());
  329. var xml = xhr.getResponseXml();
  330. // Fix for case where IE returns valid XML as text but
  331. // doesn't parse by default
  332. if (xml && !xml.hasChildNodes() && goog.isObject(xhr.getResponseText())) {
  333. xml = goog.dom.xml.loadXml(xhr.getResponseText());
  334. }
  335. // Failure result
  336. if (!xml || !xml.hasChildNodes()) {
  337. this.loadState_ = goog.ds.LoadState.FAILED;
  338. this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
  339. } else {
  340. this.loadState_ = goog.ds.LoadState.LOADED;
  341. this.node_ = xml.documentElement;
  342. }
  343. if (this.getDataName()) {
  344. goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
  345. }
  346. };
  347. /**
  348. * Failure result
  349. *
  350. * @private
  351. */
  352. goog.ds.XmlHttpDataSource.prototype.failure_ = function() {
  353. goog.log.info(
  354. goog.ds.logger,
  355. 'Data retrieve failed for DataSource ' + this.getDataName());
  356. this.loadState_ = goog.ds.LoadState.FAILED;
  357. this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
  358. if (this.getDataName()) {
  359. goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
  360. }
  361. };