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