Path: blob/trunk/third_party/closure/goog/datasource/expr.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview16* 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 on21* call22* - If expression ends with '/name()', will get the name() of the node23* referenced by the preceding path.24* - If expression ends with '/count()', will get the count() of the nodes that25* match the expression referenced by the preceding path.26* - If expression ends with '?', the value is OK to evaluate to null. This is27* not enforced by the expression evaluation functions, instead it is28* provided as a flag for client code which may ignore depending on usage29* - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX)30*31*/323334goog.provide('goog.ds.Expr');3536goog.require('goog.ds.BasicNodeList');37goog.require('goog.ds.EmptyNodeList');38goog.require('goog.string');39404142/**43* Create a new expression. An expression uses a string expression language, and44* from this string and a passed in DataNode can evaluate to a value, DataNode,45* or a DataNodeList.46*47* @param {string=} opt_expr The string expression.48* @constructor49* @final50*/51goog.ds.Expr = function(opt_expr) {52if (opt_expr) {53this.setSource_(opt_expr);54}55};565758/**59* Set the source expression text & parse60*61* @param {string} expr The string expression source.62* @param {Array=} opt_parts Array of the parts of an expression.63* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,64* passed in as a hint for processing.65* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression66* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for67* processing.68* @private69*/70goog.ds.Expr.prototype.setSource_ = function(71expr, opt_parts, opt_childExpr, opt_prevExpr) {72this.src_ = expr;7374if (!opt_childExpr && !opt_prevExpr) {75// Check whether it can be empty76if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) {77this.canBeEmpty_ = true;78expr = expr.substring(0, expr.length - 1);79}8081// Check whether this is an node function82if (goog.string.endsWith(expr, '()')) {83if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) ||84goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) ||85goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) {86var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR);87if (lastPos != -1) {88this.exprFn_ = expr.substring(lastPos + 1);89expr = expr.substring(0, lastPos);90} else {91this.exprFn_ = expr;92expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR;93}94if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) {95this.isCount_ = true;96}97}98}99}100101// Split into component parts102this.parts_ = opt_parts || expr.split('/');103this.size_ = this.parts_.length;104this.last_ = this.parts_[this.size_ - 1];105this.root_ = this.parts_[0];106107if (this.size_ == 1) {108this.rootExpr_ = this;109this.isAbsolute_ = goog.string.startsWith(expr, '$');110} else {111this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null, this, null);112this.isAbsolute_ = this.rootExpr_.isAbsolute_;113this.root_ = this.rootExpr_.root_;114}115116if (this.size_ == 1 && !this.isAbsolute_) {117// Check whether expression maps to current node, for convenience118this.isCurrent_ =119(expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR ||120expr == goog.ds.Expr.String_.EMPTY_EXPR);121122// Whether this expression is just an attribute (i.e. '@foo')123this.isJustAttribute_ =124goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START);125126// Check whether this is a common node expression127this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR;128this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR;129this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR;130}131};132133134/**135* Get the source data path for the expression136* @return {string} The path.137*/138goog.ds.Expr.prototype.getSource = function() {139return this.src_;140};141142143/**144* Gets the last part of the expression.145* @return {?string} Last part of the expression.146*/147goog.ds.Expr.prototype.getLast = function() {148return this.last_;149};150151152/**153* Gets the parent expression of this expression, or null if this is top level154* @return {goog.ds.Expr} The parent.155*/156goog.ds.Expr.prototype.getParent = function() {157if (!this.parentExprSet_) {158if (this.size_ > 1) {159this.parentExpr_ = goog.ds.Expr.createInternal_(160null, this.parts_.slice(0, this.parts_.length - 1), this, null);161}162this.parentExprSet_ = true;163}164return this.parentExpr_;165};166167168/**169* Gets the parent expression of this expression, or null if this is top level170* @return {goog.ds.Expr} The parent.171*/172goog.ds.Expr.prototype.getNext = function() {173if (!this.nextExprSet_) {174if (this.size_ > 1) {175this.nextExpr_ =176goog.ds.Expr.createInternal_(null, this.parts_.slice(1), null, this);177}178this.nextExprSet_ = true;179}180return this.nextExpr_;181};182183184/**185* Evaluate an expression on a data node, and return a value186* Recursively walks through child nodes to evaluate187* TODO(user) Support other expression functions188*189* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.190* If not provided, evaluates against DataManager global root.191* @return {*} Value of the node, or null if doesn't exist.192* @suppress {missingRequire} Cannot depend on goog.ds.DataManager because193* it creates a circular dependency.194*/195goog.ds.Expr.prototype.getValue = function(opt_ds) {196if (opt_ds == null) {197opt_ds = goog.ds.DataManager.getInstance();198} else if (this.isAbsolute_) {199opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :200goog.ds.DataManager.getInstance();201}202203if (this.isCount_) {204var nodes = this.getNodes(opt_ds);205return nodes.getCount();206}207208if (this.size_ == 1) {209return opt_ds.getChildNodeValue(this.root_);210} else if (this.size_ == 0) {211return opt_ds.get();212}213214var nextDs = opt_ds.getChildNode(this.root_);215216if (nextDs == null) {217return null;218} else {219return this.getNext().getValue(nextDs);220}221};222223224/**225* Evaluate an expression on a data node, and return matching nodes226* Recursively walks through child nodes to evaluate227*228* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.229* If not provided, evaluates against data root.230* @param {boolean=} opt_canCreate If true, will try to create new nodes.231* @return {goog.ds.DataNodeList} Matching nodes.232*/233goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) {234return /** @type {goog.ds.DataNodeList} */ (235this.getNodes_(opt_ds, false, opt_canCreate));236};237238239/**240* Evaluate an expression on a data node, and return the first matching node241* Recursively walks through child nodes to evaluate242*243* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.244* If not provided, evaluates against DataManager global root.245* @param {boolean=} opt_canCreate If true, will try to create new nodes.246* @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist.247*/248goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) {249return /** @type {goog.ds.DataNode} */ (250this.getNodes_(opt_ds, true, opt_canCreate));251};252253254/**255* Evaluate an expression on a data node, and return the first matching node256* Recursively walks through child nodes to evaluate257*258* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.259* If not provided, evaluates against DataManager global root.260* @param {boolean=} opt_selectOne Whether to return single matching DataNode261* or matching nodes in DataNodeList.262* @param {boolean=} opt_canCreate If true, will try to create new nodes.263* @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes,264* depending on value of opt_selectOne.265* @private266* @suppress {missingRequire} Cannot depend on goog.ds.DataManager because267* it creates a circular dependency.268*/269goog.ds.Expr.prototype.getNodes_ = function(270opt_ds, opt_selectOne, opt_canCreate) {271if (opt_ds == null) {272opt_ds = goog.ds.DataManager.getInstance();273} else if (this.isAbsolute_) {274opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :275goog.ds.DataManager.getInstance();276}277278if (this.size_ == 0 && opt_selectOne) {279return opt_ds;280} else if (this.size_ == 0 && !opt_selectOne) {281return new goog.ds.BasicNodeList([opt_ds]);282} else if (this.size_ == 1) {283if (opt_selectOne) {284return opt_ds.getChildNode(this.root_, opt_canCreate);285} else {286var possibleListChild = opt_ds.getChildNode(this.root_);287if (possibleListChild && possibleListChild.isList()) {288return possibleListChild.getChildNodes();289} else {290return opt_ds.getChildNodes(this.root_);291}292}293} else {294var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate);295if (nextDs == null && opt_selectOne) {296return null;297} else if (nextDs == null && !opt_selectOne) {298return new goog.ds.EmptyNodeList();299}300return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate);301}302};303304305/**306* Whether the expression can be null.307*308* @type {boolean}309* @private310*/311goog.ds.Expr.prototype.canBeEmpty_ = false;312313314/**315* The parsed paths in the expression316*317* @type {Array<string>}318* @private319*/320goog.ds.Expr.prototype.parts_ = [];321322323/**324* Number of paths in the expression325*326* @type {?number}327* @private328*/329goog.ds.Expr.prototype.size_ = null;330331332/**333* The root node path in the expression334*335* @type {string}336* @private337*/338goog.ds.Expr.prototype.root_;339340341/**342* The last path in the expression343*344* @type {?string}345* @private346*/347goog.ds.Expr.prototype.last_ = null;348349350/**351* Whether the expression evaluates to current node352*353* @type {boolean}354* @private355*/356goog.ds.Expr.prototype.isCurrent_ = false;357358359/**360* Whether the expression is just an attribute361*362* @type {boolean}363* @private364*/365goog.ds.Expr.prototype.isJustAttribute_ = false;366367368/**369* Does this expression select all DOM-style child nodes (element and text)370*371* @type {boolean}372* @private373*/374goog.ds.Expr.prototype.isAllChildNodes_ = false;375376377/**378* Does this expression select all DOM-style attribute nodes (starts with '@')379*380* @type {boolean}381* @private382*/383goog.ds.Expr.prototype.isAllAttributes_ = false;384385386/**387* Does this expression select all DOM-style element child nodes388*389* @type {boolean}390* @private391*/392goog.ds.Expr.prototype.isAllElements_ = false;393394395/**396* The function used by this expression397*398* @type {?string}399* @private400*/401goog.ds.Expr.prototype.exprFn_ = null;402403404/**405* Cached value for the parent expression.406* @type {goog.ds.Expr?}407* @private408*/409goog.ds.Expr.prototype.parentExpr_ = null;410411412/**413* Cached value for the next expression.414* @type {goog.ds.Expr?}415* @private416*/417goog.ds.Expr.prototype.nextExpr_ = null;418419420/**421* Create an expression from a string, can use cached values422*423* @param {string} expr The expression string.424* @return {goog.ds.Expr} The expression object.425*/426goog.ds.Expr.create = function(expr) {427var result = goog.ds.Expr.cache_[expr];428429if (result == null) {430result = new goog.ds.Expr(expr);431goog.ds.Expr.cache_[expr] = result;432}433return result;434};435436437/**438* Create an expression from a string, can use cached values439* Uses hints from related expressions to help in creation440*441* @param {?string=} opt_expr The string expression source.442* @param {Array=} opt_parts Array of the parts of an expression.443* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,444* passed in as a hint for processing.445* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression446* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for447* processing.448* @return {goog.ds.Expr} The expression object.449* @private450*/451goog.ds.Expr.createInternal_ = function(452opt_expr, opt_parts, opt_childExpr, opt_prevExpr) {453var expr = opt_expr || opt_parts.join('/');454var result = goog.ds.Expr.cache_[expr];455456if (result == null) {457result = new goog.ds.Expr();458result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr);459goog.ds.Expr.cache_[expr] = result;460}461return result;462};463464465/**466* Cache of pre-parsed expressions467* @private468*/469goog.ds.Expr.cache_ = {};470471472/**473* Commonly used strings in expressions.474* @enum {string}475* @private476*/477goog.ds.Expr.String_ = {478SEPARATOR: '/',479CURRENT_NODE_EXPR: '.',480EMPTY_EXPR: '',481ATTRIBUTE_START: '@',482ALL_CHILD_NODES_EXPR: '*|text()',483ALL_ATTRIBUTES_EXPR: '@*',484ALL_ELEMENTS_EXPR: '*',485NAME_EXPR: 'name()',486COUNT_EXPR: 'count()',487POSITION_EXPR: 'position()',488INDEX_START: '[',489INDEX_END: ']',490CAN_BE_EMPTY: '?'491};492493494/**495* Standard expressions496*/497498499/**500* The current node501*/502goog.ds.Expr.CURRENT =503goog.ds.Expr.create(goog.ds.Expr.String_.CURRENT_NODE_EXPR);504505506/**507* For DOM interop - all DOM child nodes (text + element).508* Text nodes have dataName #text509*/510goog.ds.Expr.ALL_CHILD_NODES =511goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR);512513514/**515* For DOM interop - all DOM element child nodes516*/517goog.ds.Expr.ALL_ELEMENTS =518goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR);519520521/**522* For DOM interop - all DOM attribute nodes523* Attribute nodes have dataName starting with "@"524*/525goog.ds.Expr.ALL_ATTRIBUTES =526goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR);527528529/**530* Get the dataName of a node531*/532goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR);533534535/**536* Get the count of nodes matching an expression537*/538goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR);539540541/**542* Get the position of the "current" node in the current node list543* This will only apply for datasources that support the concept of a current544* node (none exist yet). This is similar to XPath position() and concept of545* current node546*/547goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);548549550