expr.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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. * Expression evaluation utilities. Expression format is very similar to XPath.
  17. *
  18. * Expression details:
  19. * - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B').
  20. * getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on
  21. * call
  22. * - If expression ends with '/name()', will get the name() of the node
  23. * referenced by the preceding path.
  24. * - If expression ends with '/count()', will get the count() of the nodes that
  25. * match the expression referenced by the preceding path.
  26. * - If expression ends with '?', the value is OK to evaluate to null. This is
  27. * not enforced by the expression evaluation functions, instead it is
  28. * provided as a flag for client code which may ignore depending on usage
  29. * - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX)
  30. *
  31. */
  32. goog.provide('goog.ds.Expr');
  33. goog.require('goog.ds.BasicNodeList');
  34. goog.require('goog.ds.EmptyNodeList');
  35. goog.require('goog.string');
  36. /**
  37. * Create a new expression. An expression uses a string expression language, and
  38. * from this string and a passed in DataNode can evaluate to a value, DataNode,
  39. * or a DataNodeList.
  40. *
  41. * @param {string=} opt_expr The string expression.
  42. * @constructor
  43. * @final
  44. */
  45. goog.ds.Expr = function(opt_expr) {
  46. if (opt_expr) {
  47. this.setSource_(opt_expr);
  48. }
  49. };
  50. /**
  51. * Set the source expression text & parse
  52. *
  53. * @param {string} expr The string expression source.
  54. * @param {Array=} opt_parts Array of the parts of an expression.
  55. * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
  56. * passed in as a hint for processing.
  57. * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
  58. * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
  59. * processing.
  60. * @private
  61. */
  62. goog.ds.Expr.prototype.setSource_ = function(
  63. expr, opt_parts, opt_childExpr, opt_prevExpr) {
  64. this.src_ = expr;
  65. if (!opt_childExpr && !opt_prevExpr) {
  66. // Check whether it can be empty
  67. if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) {
  68. this.canBeEmpty_ = true;
  69. expr = expr.substring(0, expr.length - 1);
  70. }
  71. // Check whether this is an node function
  72. if (goog.string.endsWith(expr, '()')) {
  73. if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) ||
  74. goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) ||
  75. goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) {
  76. var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR);
  77. if (lastPos != -1) {
  78. this.exprFn_ = expr.substring(lastPos + 1);
  79. expr = expr.substring(0, lastPos);
  80. } else {
  81. this.exprFn_ = expr;
  82. expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR;
  83. }
  84. if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) {
  85. this.isCount_ = true;
  86. }
  87. }
  88. }
  89. }
  90. // Split into component parts
  91. this.parts_ = opt_parts || expr.split('/');
  92. this.size_ = this.parts_.length;
  93. this.last_ = this.parts_[this.size_ - 1];
  94. this.root_ = this.parts_[0];
  95. if (this.size_ == 1) {
  96. this.rootExpr_ = this;
  97. this.isAbsolute_ = goog.string.startsWith(expr, '$');
  98. } else {
  99. this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null, this, null);
  100. this.isAbsolute_ = this.rootExpr_.isAbsolute_;
  101. this.root_ = this.rootExpr_.root_;
  102. }
  103. if (this.size_ == 1 && !this.isAbsolute_) {
  104. // Check whether expression maps to current node, for convenience
  105. this.isCurrent_ =
  106. (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR ||
  107. expr == goog.ds.Expr.String_.EMPTY_EXPR);
  108. // Whether this expression is just an attribute (i.e. '@foo')
  109. this.isJustAttribute_ =
  110. goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START);
  111. // Check whether this is a common node expression
  112. this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR;
  113. this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR;
  114. this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR;
  115. }
  116. };
  117. /**
  118. * Get the source data path for the expression
  119. * @return {string} The path.
  120. */
  121. goog.ds.Expr.prototype.getSource = function() {
  122. return this.src_;
  123. };
  124. /**
  125. * Gets the last part of the expression.
  126. * @return {?string} Last part of the expression.
  127. */
  128. goog.ds.Expr.prototype.getLast = function() {
  129. return this.last_;
  130. };
  131. /**
  132. * Gets the parent expression of this expression, or null if this is top level
  133. * @return {goog.ds.Expr} The parent.
  134. */
  135. goog.ds.Expr.prototype.getParent = function() {
  136. if (!this.parentExprSet_) {
  137. if (this.size_ > 1) {
  138. this.parentExpr_ = goog.ds.Expr.createInternal_(
  139. null, this.parts_.slice(0, this.parts_.length - 1), this, null);
  140. }
  141. this.parentExprSet_ = true;
  142. }
  143. return this.parentExpr_;
  144. };
  145. /**
  146. * Gets the parent expression of this expression, or null if this is top level
  147. * @return {goog.ds.Expr} The parent.
  148. */
  149. goog.ds.Expr.prototype.getNext = function() {
  150. if (!this.nextExprSet_) {
  151. if (this.size_ > 1) {
  152. this.nextExpr_ =
  153. goog.ds.Expr.createInternal_(null, this.parts_.slice(1), null, this);
  154. }
  155. this.nextExprSet_ = true;
  156. }
  157. return this.nextExpr_;
  158. };
  159. /**
  160. * Evaluate an expression on a data node, and return a value
  161. * Recursively walks through child nodes to evaluate
  162. * TODO(user) Support other expression functions
  163. *
  164. * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
  165. * If not provided, evaluates against DataManager global root.
  166. * @return {*} Value of the node, or null if doesn't exist.
  167. * @suppress {missingRequire} Cannot depend on goog.ds.DataManager because
  168. * it creates a circular dependency.
  169. */
  170. goog.ds.Expr.prototype.getValue = function(opt_ds) {
  171. if (opt_ds == null) {
  172. opt_ds = goog.ds.DataManager.getInstance();
  173. } else if (this.isAbsolute_) {
  174. opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
  175. goog.ds.DataManager.getInstance();
  176. }
  177. if (this.isCount_) {
  178. var nodes = this.getNodes(opt_ds);
  179. return nodes.getCount();
  180. }
  181. if (this.size_ == 1) {
  182. return opt_ds.getChildNodeValue(this.root_);
  183. } else if (this.size_ == 0) {
  184. return opt_ds.get();
  185. }
  186. var nextDs = opt_ds.getChildNode(this.root_);
  187. if (nextDs == null) {
  188. return null;
  189. } else {
  190. return this.getNext().getValue(nextDs);
  191. }
  192. };
  193. /**
  194. * Evaluate an expression on a data node, and return matching nodes
  195. * Recursively walks through child nodes to evaluate
  196. *
  197. * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
  198. * If not provided, evaluates against data root.
  199. * @param {boolean=} opt_canCreate If true, will try to create new nodes.
  200. * @return {goog.ds.DataNodeList} Matching nodes.
  201. */
  202. goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) {
  203. return /** @type {goog.ds.DataNodeList} */ (
  204. this.getNodes_(opt_ds, false, opt_canCreate));
  205. };
  206. /**
  207. * Evaluate an expression on a data node, and return the first matching node
  208. * Recursively walks through child nodes to evaluate
  209. *
  210. * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
  211. * If not provided, evaluates against DataManager global root.
  212. * @param {boolean=} opt_canCreate If true, will try to create new nodes.
  213. * @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist.
  214. */
  215. goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) {
  216. return /** @type {goog.ds.DataNode} */ (
  217. this.getNodes_(opt_ds, true, opt_canCreate));
  218. };
  219. /**
  220. * Evaluate an expression on a data node, and return the first matching node
  221. * Recursively walks through child nodes to evaluate
  222. *
  223. * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
  224. * If not provided, evaluates against DataManager global root.
  225. * @param {boolean=} opt_selectOne Whether to return single matching DataNode
  226. * or matching nodes in DataNodeList.
  227. * @param {boolean=} opt_canCreate If true, will try to create new nodes.
  228. * @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes,
  229. * depending on value of opt_selectOne.
  230. * @private
  231. * @suppress {missingRequire} Cannot depend on goog.ds.DataManager because
  232. * it creates a circular dependency.
  233. */
  234. goog.ds.Expr.prototype.getNodes_ = function(
  235. opt_ds, opt_selectOne, opt_canCreate) {
  236. if (opt_ds == null) {
  237. opt_ds = goog.ds.DataManager.getInstance();
  238. } else if (this.isAbsolute_) {
  239. opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
  240. goog.ds.DataManager.getInstance();
  241. }
  242. if (this.size_ == 0 && opt_selectOne) {
  243. return opt_ds;
  244. } else if (this.size_ == 0 && !opt_selectOne) {
  245. return new goog.ds.BasicNodeList([opt_ds]);
  246. } else if (this.size_ == 1) {
  247. if (opt_selectOne) {
  248. return opt_ds.getChildNode(this.root_, opt_canCreate);
  249. } else {
  250. var possibleListChild = opt_ds.getChildNode(this.root_);
  251. if (possibleListChild && possibleListChild.isList()) {
  252. return possibleListChild.getChildNodes();
  253. } else {
  254. return opt_ds.getChildNodes(this.root_);
  255. }
  256. }
  257. } else {
  258. var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate);
  259. if (nextDs == null && opt_selectOne) {
  260. return null;
  261. } else if (nextDs == null && !opt_selectOne) {
  262. return new goog.ds.EmptyNodeList();
  263. }
  264. return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate);
  265. }
  266. };
  267. /**
  268. * Whether the expression can be null.
  269. *
  270. * @type {boolean}
  271. * @private
  272. */
  273. goog.ds.Expr.prototype.canBeEmpty_ = false;
  274. /**
  275. * The parsed paths in the expression
  276. *
  277. * @type {Array<string>}
  278. * @private
  279. */
  280. goog.ds.Expr.prototype.parts_ = [];
  281. /**
  282. * Number of paths in the expression
  283. *
  284. * @type {?number}
  285. * @private
  286. */
  287. goog.ds.Expr.prototype.size_ = null;
  288. /**
  289. * The root node path in the expression
  290. *
  291. * @type {string}
  292. * @private
  293. */
  294. goog.ds.Expr.prototype.root_;
  295. /**
  296. * The last path in the expression
  297. *
  298. * @type {?string}
  299. * @private
  300. */
  301. goog.ds.Expr.prototype.last_ = null;
  302. /**
  303. * Whether the expression evaluates to current node
  304. *
  305. * @type {boolean}
  306. * @private
  307. */
  308. goog.ds.Expr.prototype.isCurrent_ = false;
  309. /**
  310. * Whether the expression is just an attribute
  311. *
  312. * @type {boolean}
  313. * @private
  314. */
  315. goog.ds.Expr.prototype.isJustAttribute_ = false;
  316. /**
  317. * Does this expression select all DOM-style child nodes (element and text)
  318. *
  319. * @type {boolean}
  320. * @private
  321. */
  322. goog.ds.Expr.prototype.isAllChildNodes_ = false;
  323. /**
  324. * Does this expression select all DOM-style attribute nodes (starts with '@')
  325. *
  326. * @type {boolean}
  327. * @private
  328. */
  329. goog.ds.Expr.prototype.isAllAttributes_ = false;
  330. /**
  331. * Does this expression select all DOM-style element child nodes
  332. *
  333. * @type {boolean}
  334. * @private
  335. */
  336. goog.ds.Expr.prototype.isAllElements_ = false;
  337. /**
  338. * The function used by this expression
  339. *
  340. * @type {?string}
  341. * @private
  342. */
  343. goog.ds.Expr.prototype.exprFn_ = null;
  344. /**
  345. * Cached value for the parent expression.
  346. * @type {goog.ds.Expr?}
  347. * @private
  348. */
  349. goog.ds.Expr.prototype.parentExpr_ = null;
  350. /**
  351. * Cached value for the next expression.
  352. * @type {goog.ds.Expr?}
  353. * @private
  354. */
  355. goog.ds.Expr.prototype.nextExpr_ = null;
  356. /**
  357. * Create an expression from a string, can use cached values
  358. *
  359. * @param {string} expr The expression string.
  360. * @return {goog.ds.Expr} The expression object.
  361. */
  362. goog.ds.Expr.create = function(expr) {
  363. var result = goog.ds.Expr.cache_[expr];
  364. if (result == null) {
  365. result = new goog.ds.Expr(expr);
  366. goog.ds.Expr.cache_[expr] = result;
  367. }
  368. return result;
  369. };
  370. /**
  371. * Create an expression from a string, can use cached values
  372. * Uses hints from related expressions to help in creation
  373. *
  374. * @param {?string=} opt_expr The string expression source.
  375. * @param {Array=} opt_parts Array of the parts of an expression.
  376. * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
  377. * passed in as a hint for processing.
  378. * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
  379. * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
  380. * processing.
  381. * @return {goog.ds.Expr} The expression object.
  382. * @private
  383. */
  384. goog.ds.Expr.createInternal_ = function(
  385. opt_expr, opt_parts, opt_childExpr, opt_prevExpr) {
  386. var expr = opt_expr || opt_parts.join('/');
  387. var result = goog.ds.Expr.cache_[expr];
  388. if (result == null) {
  389. result = new goog.ds.Expr();
  390. result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr);
  391. goog.ds.Expr.cache_[expr] = result;
  392. }
  393. return result;
  394. };
  395. /**
  396. * Cache of pre-parsed expressions
  397. * @private
  398. */
  399. goog.ds.Expr.cache_ = {};
  400. /**
  401. * Commonly used strings in expressions.
  402. * @enum {string}
  403. * @private
  404. */
  405. goog.ds.Expr.String_ = {
  406. SEPARATOR: '/',
  407. CURRENT_NODE_EXPR: '.',
  408. EMPTY_EXPR: '',
  409. ATTRIBUTE_START: '@',
  410. ALL_CHILD_NODES_EXPR: '*|text()',
  411. ALL_ATTRIBUTES_EXPR: '@*',
  412. ALL_ELEMENTS_EXPR: '*',
  413. NAME_EXPR: 'name()',
  414. COUNT_EXPR: 'count()',
  415. POSITION_EXPR: 'position()',
  416. INDEX_START: '[',
  417. INDEX_END: ']',
  418. CAN_BE_EMPTY: '?'
  419. };
  420. /**
  421. * Standard expressions
  422. */
  423. /**
  424. * The current node
  425. */
  426. goog.ds.Expr.CURRENT =
  427. goog.ds.Expr.create(goog.ds.Expr.String_.CURRENT_NODE_EXPR);
  428. /**
  429. * For DOM interop - all DOM child nodes (text + element).
  430. * Text nodes have dataName #text
  431. */
  432. goog.ds.Expr.ALL_CHILD_NODES =
  433. goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR);
  434. /**
  435. * For DOM interop - all DOM element child nodes
  436. */
  437. goog.ds.Expr.ALL_ELEMENTS =
  438. goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR);
  439. /**
  440. * For DOM interop - all DOM attribute nodes
  441. * Attribute nodes have dataName starting with "@"
  442. */
  443. goog.ds.Expr.ALL_ATTRIBUTES =
  444. goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR);
  445. /**
  446. * Get the dataName of a node
  447. */
  448. goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR);
  449. /**
  450. * Get the count of nodes matching an expression
  451. */
  452. goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR);
  453. /**
  454. * Get the position of the "current" node in the current node list
  455. * This will only apply for datasources that support the concept of a current
  456. * node (none exist yet). This is similar to XPath position() and concept of
  457. * current node
  458. */
  459. goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);