| // 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 |
| * Expression evaluation utilities. Expression format is very similar to XPath. |
| * |
| * Expression details: |
| * - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B'). |
| * getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on |
| * call |
| * - If expression ends with '/name()', will get the name() of the node |
| * referenced by the preceding path. |
| * - If expression ends with '/count()', will get the count() of the nodes that |
| * match the expression referenced by the preceding path. |
| * - If expression ends with '?', the value is OK to evaluate to null. This is |
| * not enforced by the expression evaluation functions, instead it is |
| * provided as a flag for client code which may ignore depending on usage |
| * - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX) |
| * |
| */ |
| |
| |
| goog.provide('goog.ds.Expr'); |
| |
| goog.require('goog.ds.BasicNodeList'); |
| goog.require('goog.ds.EmptyNodeList'); |
| goog.require('goog.string'); |
| |
| |
| |
| /** |
| * Create a new expression. An expression uses a string expression language, and |
| * from this string and a passed in DataNode can evaluate to a value, DataNode, |
| * or a DataNodeList. |
| * |
| * @param {string=} opt_expr The string expression. |
| * @constructor |
| * @final |
| */ |
| goog.ds.Expr = function(opt_expr) { |
| if (opt_expr) { |
| this.setSource_(opt_expr); |
| } |
| }; |
| |
| |
| /** |
| * Set the source expression text & parse |
| * |
| * @param {string} expr The string expression source. |
| * @param {Array=} opt_parts Array of the parts of an expression. |
| * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression, |
| * passed in as a hint for processing. |
| * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression |
| * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for |
| * processing. |
| * @private |
| */ |
| goog.ds.Expr.prototype.setSource_ = function(expr, opt_parts, |
| opt_childExpr, opt_prevExpr) { |
| this.src_ = expr; |
| |
| if (!opt_childExpr && !opt_prevExpr) { |
| // Check whether it can be empty |
| if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) { |
| this.canBeEmpty_ = true; |
| expr = expr.substring(0, expr.length - 1); |
| } |
| |
| // Check whether this is an node function |
| if (goog.string.endsWith(expr, '()')) { |
| if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) || |
| goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) || |
| goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) { |
| var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR); |
| if (lastPos != -1) { |
| this.exprFn_ = expr.substring(lastPos + 1); |
| expr = expr.substring(0, lastPos); |
| } else { |
| this.exprFn_ = expr; |
| expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR; |
| } |
| if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) { |
| this.isCount_ = true; |
| } |
| } |
| } |
| } |
| |
| // Split into component parts |
| this.parts_ = opt_parts || expr.split('/'); |
| this.size_ = this.parts_.length; |
| this.last_ = this.parts_[this.size_ - 1]; |
| this.root_ = this.parts_[0]; |
| |
| if (this.size_ == 1) { |
| this.rootExpr_ = this; |
| this.isAbsolute_ = goog.string.startsWith(expr, '$'); |
| } else { |
| this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null, |
| this, null); |
| this.isAbsolute_ = this.rootExpr_.isAbsolute_; |
| this.root_ = this.rootExpr_.root_; |
| } |
| |
| if (this.size_ == 1 && !this.isAbsolute_) { |
| // Check whether expression maps to current node, for convenience |
| this.isCurrent_ = (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR || |
| expr == goog.ds.Expr.String_.EMPTY_EXPR); |
| |
| // Whether this expression is just an attribute (i.e. '@foo') |
| this.isJustAttribute_ = |
| goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START); |
| |
| // Check whether this is a common node expression |
| this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR; |
| this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR; |
| this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR; |
| } |
| }; |
| |
| |
| /** |
| * Get the source data path for the expression |
| * @return {string} The path. |
| */ |
| goog.ds.Expr.prototype.getSource = function() { |
| return this.src_; |
| }; |
| |
| |
| /** |
| * Gets the last part of the expression. |
| * @return {?string} Last part of the expression. |
| */ |
| goog.ds.Expr.prototype.getLast = function() { |
| return this.last_; |
| }; |
| |
| |
| /** |
| * Gets the parent expression of this expression, or null if this is top level |
| * @return {goog.ds.Expr} The parent. |
| */ |
| goog.ds.Expr.prototype.getParent = function() { |
| if (!this.parentExprSet_) { |
| if (this.size_ > 1) { |
| this.parentExpr_ = goog.ds.Expr.createInternal_(null, |
| this.parts_.slice(0, this.parts_.length - 1), this, null); |
| } |
| this.parentExprSet_ = true; |
| } |
| return this.parentExpr_; |
| }; |
| |
| |
| /** |
| * Gets the parent expression of this expression, or null if this is top level |
| * @return {goog.ds.Expr} The parent. |
| */ |
| goog.ds.Expr.prototype.getNext = function() { |
| if (!this.nextExprSet_) { |
| if (this.size_ > 1) { |
| this.nextExpr_ = goog.ds.Expr.createInternal_(null, this.parts_.slice(1), |
| null, this); |
| } |
| this.nextExprSet_ = true; |
| } |
| return this.nextExpr_; |
| }; |
| |
| |
| /** |
| * Evaluate an expression on a data node, and return a value |
| * Recursively walks through child nodes to evaluate |
| * TODO(user) Support other expression functions |
| * |
| * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. |
| * If not provided, evaluates against DataManager global root. |
| * @return {*} Value of the node, or null if doesn't exist. |
| */ |
| goog.ds.Expr.prototype.getValue = function(opt_ds) { |
| if (opt_ds == null) { |
| opt_ds = goog.ds.DataManager.getInstance(); |
| } else if (this.isAbsolute_) { |
| opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() : |
| goog.ds.DataManager.getInstance(); |
| } |
| |
| if (this.isCount_) { |
| var nodes = this.getNodes(opt_ds); |
| return nodes.getCount(); |
| } |
| |
| if (this.size_ == 1) { |
| return opt_ds.getChildNodeValue(this.root_); |
| } else if (this.size_ == 0) { |
| return opt_ds.get(); |
| } |
| |
| var nextDs = opt_ds.getChildNode(this.root_); |
| |
| if (nextDs == null) { |
| return null; |
| } else { |
| return this.getNext().getValue(nextDs); |
| } |
| }; |
| |
| |
| /** |
| * Evaluate an expression on a data node, and return matching nodes |
| * Recursively walks through child nodes to evaluate |
| * |
| * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. |
| * If not provided, evaluates against data root. |
| * @param {boolean=} opt_canCreate If true, will try to create new nodes. |
| * @return {goog.ds.DataNodeList} Matching nodes. |
| */ |
| goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) { |
| return /** @type {goog.ds.DataNodeList} */(this.getNodes_(opt_ds, |
| false, opt_canCreate)); |
| }; |
| |
| |
| /** |
| * Evaluate an expression on a data node, and return the first matching node |
| * Recursively walks through child nodes to evaluate |
| * |
| * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. |
| * If not provided, evaluates against DataManager global root. |
| * @param {boolean=} opt_canCreate If true, will try to create new nodes. |
| * @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist. |
| */ |
| goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) { |
| return /** @type {goog.ds.DataNode} */(this.getNodes_(opt_ds, |
| true, opt_canCreate)); |
| }; |
| |
| |
| /** |
| * Evaluate an expression on a data node, and return the first matching node |
| * Recursively walks through child nodes to evaluate |
| * |
| * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. |
| * If not provided, evaluates against DataManager global root. |
| * @param {boolean=} opt_selectOne Whether to return single matching DataNode |
| * or matching nodes in DataNodeList. |
| * @param {boolean=} opt_canCreate If true, will try to create new nodes. |
| * @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes, |
| * depending on value of opt_selectOne. |
| * @private |
| */ |
| goog.ds.Expr.prototype.getNodes_ = function(opt_ds, opt_selectOne, |
| opt_canCreate) { |
| if (opt_ds == null) { |
| opt_ds = goog.ds.DataManager.getInstance(); |
| } else if (this.isAbsolute_) { |
| opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() : |
| goog.ds.DataManager.getInstance(); |
| } |
| |
| if (this.size_ == 0 && opt_selectOne) { |
| return opt_ds; |
| } else if (this.size_ == 0 && !opt_selectOne) { |
| return new goog.ds.BasicNodeList([opt_ds]); |
| } else if (this.size_ == 1) { |
| if (opt_selectOne) { |
| return opt_ds.getChildNode(this.root_, opt_canCreate); |
| } |
| else { |
| var possibleListChild = opt_ds.getChildNode(this.root_); |
| if (possibleListChild && possibleListChild.isList()) { |
| return possibleListChild.getChildNodes(); |
| } else { |
| return opt_ds.getChildNodes(this.root_); |
| } |
| } |
| } else { |
| var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate); |
| if (nextDs == null && opt_selectOne) { |
| return null; |
| } else if (nextDs == null && !opt_selectOne) { |
| return new goog.ds.EmptyNodeList(); |
| } |
| return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate); |
| } |
| }; |
| |
| |
| /** |
| * Whether the expression can be null. |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.canBeEmpty_ = false; |
| |
| |
| /** |
| * The parsed paths in the expression |
| * |
| * @type {Array<string>} |
| * @private |
| */ |
| goog.ds.Expr.prototype.parts_ = []; |
| |
| |
| /** |
| * Number of paths in the expression |
| * |
| * @type {?number} |
| * @private |
| */ |
| goog.ds.Expr.prototype.size_ = null; |
| |
| |
| /** |
| * The root node path in the expression |
| * |
| * @type {string} |
| * @private |
| */ |
| goog.ds.Expr.prototype.root_; |
| |
| |
| /** |
| * The last path in the expression |
| * |
| * @type {?string} |
| * @private |
| */ |
| goog.ds.Expr.prototype.last_ = null; |
| |
| |
| /** |
| * Whether the expression evaluates to current node |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.isCurrent_ = false; |
| |
| |
| /** |
| * Whether the expression is just an attribute |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.isJustAttribute_ = false; |
| |
| |
| /** |
| * Does this expression select all DOM-style child nodes (element and text) |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.isAllChildNodes_ = false; |
| |
| |
| /** |
| * Does this expression select all DOM-style attribute nodes (starts with '@') |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.isAllAttributes_ = false; |
| |
| |
| /** |
| * Does this expression select all DOM-style element child nodes |
| * |
| * @type {boolean} |
| * @private |
| */ |
| goog.ds.Expr.prototype.isAllElements_ = false; |
| |
| |
| /** |
| * The function used by this expression |
| * |
| * @type {?string} |
| * @private |
| */ |
| goog.ds.Expr.prototype.exprFn_ = null; |
| |
| |
| /** |
| * Cached value for the parent expression. |
| * @type {goog.ds.Expr?} |
| * @private |
| */ |
| goog.ds.Expr.prototype.parentExpr_ = null; |
| |
| |
| /** |
| * Cached value for the next expression. |
| * @type {goog.ds.Expr?} |
| * @private |
| */ |
| goog.ds.Expr.prototype.nextExpr_ = null; |
| |
| |
| /** |
| * Create an expression from a string, can use cached values |
| * |
| * @param {string} expr The expression string. |
| * @return {goog.ds.Expr} The expression object. |
| */ |
| goog.ds.Expr.create = function(expr) { |
| var result = goog.ds.Expr.cache_[expr]; |
| |
| if (result == null) { |
| result = new goog.ds.Expr(expr); |
| goog.ds.Expr.cache_[expr] = result; |
| } |
| return result; |
| }; |
| |
| |
| /** |
| * Create an expression from a string, can use cached values |
| * Uses hints from related expressions to help in creation |
| * |
| * @param {?string=} opt_expr The string expression source. |
| * @param {Array=} opt_parts Array of the parts of an expression. |
| * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression, |
| * passed in as a hint for processing. |
| * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression |
| * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for |
| * processing. |
| * @return {goog.ds.Expr} The expression object. |
| * @private |
| */ |
| goog.ds.Expr.createInternal_ = function(opt_expr, opt_parts, opt_childExpr, |
| opt_prevExpr) { |
| var expr = opt_expr || opt_parts.join('/'); |
| var result = goog.ds.Expr.cache_[expr]; |
| |
| if (result == null) { |
| result = new goog.ds.Expr(); |
| result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr); |
| goog.ds.Expr.cache_[expr] = result; |
| } |
| return result; |
| }; |
| |
| |
| /** |
| * Cache of pre-parsed expressions |
| * @private |
| */ |
| goog.ds.Expr.cache_ = {}; |
| |
| |
| /** |
| * Commonly used strings in expressions. |
| * @enum {string} |
| * @private |
| */ |
| goog.ds.Expr.String_ = { |
| SEPARATOR: '/', |
| CURRENT_NODE_EXPR: '.', |
| EMPTY_EXPR: '', |
| ATTRIBUTE_START: '@', |
| ALL_CHILD_NODES_EXPR: '*|text()', |
| ALL_ATTRIBUTES_EXPR: '@*', |
| ALL_ELEMENTS_EXPR: '*', |
| NAME_EXPR: 'name()', |
| COUNT_EXPR: 'count()', |
| POSITION_EXPR: 'position()', |
| INDEX_START: '[', |
| INDEX_END: ']', |
| CAN_BE_EMPTY: '?' |
| }; |
| |
| |
| /** |
| * Standard expressions |
| */ |
| |
| |
| /** |
| * The current node |
| */ |
| goog.ds.Expr.CURRENT = goog.ds.Expr.create( |
| goog.ds.Expr.String_.CURRENT_NODE_EXPR); |
| |
| |
| /** |
| * For DOM interop - all DOM child nodes (text + element). |
| * Text nodes have dataName #text |
| */ |
| goog.ds.Expr.ALL_CHILD_NODES = |
| goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR); |
| |
| |
| /** |
| * For DOM interop - all DOM element child nodes |
| */ |
| goog.ds.Expr.ALL_ELEMENTS = |
| goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR); |
| |
| |
| /** |
| * For DOM interop - all DOM attribute nodes |
| * Attribute nodes have dataName starting with "@" |
| */ |
| goog.ds.Expr.ALL_ATTRIBUTES = |
| goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR); |
| |
| |
| /** |
| * Get the dataName of a node |
| */ |
| goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR); |
| |
| |
| /** |
| * Get the count of nodes matching an expression |
| */ |
| goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR); |
| |
| |
| /** |
| * Get the position of the "current" node in the current node list |
| * This will only apply for datasources that support the concept of a current |
| * node (none exist yet). This is similar to XPath position() and concept of |
| * current node |
| */ |
| goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR); |