selenium-core-runner 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/Gemfile +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/selenium_core_runner/suites_controller.rb +19 -0
  6. data/app/views/selenium_core_runner/suites/index.html.erb +177 -0
  7. data/app/views/selenium_core_runner/suites/show.html.erb +0 -0
  8. data/config/routes.rb +6 -0
  9. data/lib/selenium-core-runner/engine.rb +19 -0
  10. data/lib/selenium-core-runner.rb +3 -0
  11. data/public/selenium-core-runner/Blank.html +7 -0
  12. data/public/selenium-core-runner/InjectedRemoteRunner.html +8 -0
  13. data/public/selenium-core-runner/RemoteRunner.html +101 -0
  14. data/public/selenium-core-runner/SeleniumLog.html +109 -0
  15. data/public/selenium-core-runner/TestPrompt.html +145 -0
  16. data/public/selenium-core-runner/TestRunner-splash.html +55 -0
  17. data/public/selenium-core-runner/TestRunner.hta +177 -0
  18. data/public/selenium-core-runner/TestRunner.html +177 -0
  19. data/public/selenium-core-runner/icons/all.png +0 -0
  20. data/public/selenium-core-runner/icons/continue.png +0 -0
  21. data/public/selenium-core-runner/icons/continue_disabled.png +0 -0
  22. data/public/selenium-core-runner/icons/pause.png +0 -0
  23. data/public/selenium-core-runner/icons/pause_disabled.png +0 -0
  24. data/public/selenium-core-runner/icons/selected.png +0 -0
  25. data/public/selenium-core-runner/icons/step.png +0 -0
  26. data/public/selenium-core-runner/icons/step_disabled.png +0 -0
  27. data/public/selenium-core-runner/iedoc-core.xml +1789 -0
  28. data/public/selenium-core-runner/iedoc.xml +1830 -0
  29. data/public/selenium-core-runner/lib/cssQuery/cssQuery-p.js +6 -0
  30. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level2.js +142 -0
  31. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level3.js +150 -0
  32. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-standard.js +53 -0
  33. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery.js +356 -0
  34. data/public/selenium-core-runner/lib/prototype.js +2006 -0
  35. data/public/selenium-core-runner/lib/scriptaculous/builder.js +101 -0
  36. data/public/selenium-core-runner/lib/scriptaculous/controls.js +815 -0
  37. data/public/selenium-core-runner/lib/scriptaculous/dragdrop.js +915 -0
  38. data/public/selenium-core-runner/lib/scriptaculous/effects.js +958 -0
  39. data/public/selenium-core-runner/lib/scriptaculous/scriptaculous.js +47 -0
  40. data/public/selenium-core-runner/lib/scriptaculous/slider.js +283 -0
  41. data/public/selenium-core-runner/lib/scriptaculous/unittest.js +383 -0
  42. data/public/selenium-core-runner/lib/snapsie.js +91 -0
  43. data/public/selenium-core-runner/scripts/find_matching_child.js +69 -0
  44. data/public/selenium-core-runner/scripts/htmlutils.js +1623 -0
  45. data/public/selenium-core-runner/scripts/injection.html +72 -0
  46. data/public/selenium-core-runner/scripts/selenium-api.js +3240 -0
  47. data/public/selenium-core-runner/scripts/selenium-browserbot.js +2333 -0
  48. data/public/selenium-core-runner/scripts/selenium-browserdetect.js +153 -0
  49. data/public/selenium-core-runner/scripts/selenium-commandhandlers.js +379 -0
  50. data/public/selenium-core-runner/scripts/selenium-executionloop.js +175 -0
  51. data/public/selenium-core-runner/scripts/selenium-logging.js +148 -0
  52. data/public/selenium-core-runner/scripts/selenium-remoterunner.js +695 -0
  53. data/public/selenium-core-runner/scripts/selenium-testrunner.js +1362 -0
  54. data/public/selenium-core-runner/scripts/selenium-version.js +5 -0
  55. data/public/selenium-core-runner/scripts/ui-doc.html +803 -0
  56. data/public/selenium-core-runner/scripts/ui-element.js +1627 -0
  57. data/public/selenium-core-runner/scripts/ui-map-sample.js +979 -0
  58. data/public/selenium-core-runner/scripts/user-extensions.js +3 -0
  59. data/public/selenium-core-runner/scripts/user-extensions.js.sample +75 -0
  60. data/public/selenium-core-runner/scripts/xmlextras.js +153 -0
  61. data/public/selenium-core-runner/selenium-logo.png +0 -0
  62. data/public/selenium-core-runner/selenium-test.css +43 -0
  63. data/public/selenium-core-runner/selenium.css +316 -0
  64. data/public/selenium-core-runner/xpath/dom.js +566 -0
  65. data/public/selenium-core-runner/xpath/javascript-xpath-0.1.11.js +2816 -0
  66. data/public/selenium-core-runner/xpath/util.js +549 -0
  67. data/public/selenium-core-runner/xpath/xmltoken.js +149 -0
  68. data/public/selenium-core-runner/xpath/xpath.js +2481 -0
  69. metadata +121 -0
@@ -0,0 +1,2481 @@
1
+ // Copyright 2005 Google Inc.
2
+ // All Rights Reserved
3
+ //
4
+ // An XPath parser and evaluator written in JavaScript. The
5
+ // implementation is complete except for functions handling
6
+ // namespaces.
7
+ //
8
+ // Reference: [XPATH] XPath Specification
9
+ // <http://www.w3.org/TR/1999/REC-xpath-19991116>.
10
+ //
11
+ //
12
+ // The API of the parser has several parts:
13
+ //
14
+ // 1. The parser function xpathParse() that takes a string and returns
15
+ // an expession object.
16
+ //
17
+ // 2. The expression object that has an evaluate() method to evaluate the
18
+ // XPath expression it represents. (It is actually a hierarchy of
19
+ // objects that resembles the parse tree, but an application will call
20
+ // evaluate() only on the top node of this hierarchy.)
21
+ //
22
+ // 3. The context object that is passed as an argument to the evaluate()
23
+ // method, which represents the DOM context in which the expression is
24
+ // evaluated.
25
+ //
26
+ // 4. The value object that is returned from evaluate() and represents
27
+ // values of the different types that are defined by XPath (number,
28
+ // string, boolean, and node-set), and allows to convert between them.
29
+ //
30
+ // These parts are near the top of the file, the functions and data
31
+ // that are used internally follow after them.
32
+ //
33
+ //
34
+ // Author: Steffen Meschkat <mesch@google.com>
35
+
36
+
37
+ // The entry point for the parser.
38
+ //
39
+ // @param expr a string that contains an XPath expression.
40
+ // @return an expression object that can be evaluated with an
41
+ // expression context.
42
+
43
+ function xpathParse(expr) {
44
+ xpathLog('parse ' + expr);
45
+ xpathParseInit();
46
+
47
+ var cached = xpathCacheLookup(expr);
48
+ if (cached) {
49
+ xpathLog(' ... cached');
50
+ return cached;
51
+ }
52
+
53
+ // Optimize for a few common cases: simple attribute node tests
54
+ // (@id), simple element node tests (page), variable references
55
+ // ($address), numbers (4), multi-step path expressions where each
56
+ // step is a plain element node test
57
+ // (page/overlay/locations/location).
58
+
59
+ if (expr.match(/^(\$|@)?\w+$/i)) {
60
+ var ret = makeSimpleExpr(expr);
61
+ xpathParseCache[expr] = ret;
62
+ xpathLog(' ... simple');
63
+ return ret;
64
+ }
65
+
66
+ if (expr.match(/^\w+(\/\w+)*$/i)) {
67
+ var ret = makeSimpleExpr2(expr);
68
+ xpathParseCache[expr] = ret;
69
+ xpathLog(' ... simple 2');
70
+ return ret;
71
+ }
72
+
73
+ var cachekey = expr; // expr is modified during parse
74
+
75
+ var stack = [];
76
+ var ahead = null;
77
+ var previous = null;
78
+ var done = false;
79
+
80
+ var parse_count = 0;
81
+ var lexer_count = 0;
82
+ var reduce_count = 0;
83
+
84
+ while (!done) {
85
+ parse_count++;
86
+ expr = expr.replace(/^\s*/, '');
87
+ previous = ahead;
88
+ ahead = null;
89
+
90
+ var rule = null;
91
+ var match = '';
92
+ for (var i = 0; i < xpathTokenRules.length; ++i) {
93
+ var result = xpathTokenRules[i].re.exec(expr);
94
+ lexer_count++;
95
+ if (result && result.length > 0 && result[0].length > match.length) {
96
+ rule = xpathTokenRules[i];
97
+ match = result[0];
98
+ break;
99
+ }
100
+ }
101
+
102
+ // Special case: allow operator keywords to be element and
103
+ // variable names.
104
+
105
+ // NOTE(mesch): The parser resolves conflicts by looking ahead,
106
+ // and this is the only case where we look back to
107
+ // disambiguate. So this is indeed something different, and
108
+ // looking back is usually done in the lexer (via states in the
109
+ // general case, called "start conditions" in flex(1)). Also,the
110
+ // conflict resolution in the parser is not as robust as it could
111
+ // be, so I'd like to keep as much off the parser as possible (all
112
+ // these precedence values should be computed from the grammar
113
+ // rules and possibly associativity declarations, as in bison(1),
114
+ // and not explicitly set.
115
+
116
+ if (rule &&
117
+ (rule == TOK_DIV ||
118
+ rule == TOK_MOD ||
119
+ rule == TOK_AND ||
120
+ rule == TOK_OR) &&
121
+ (!previous ||
122
+ previous.tag == TOK_AT ||
123
+ previous.tag == TOK_DSLASH ||
124
+ previous.tag == TOK_SLASH ||
125
+ previous.tag == TOK_AXIS ||
126
+ previous.tag == TOK_DOLLAR)) {
127
+ rule = TOK_QNAME;
128
+ }
129
+
130
+ if (rule) {
131
+ expr = expr.substr(match.length);
132
+ xpathLog('token: ' + match + ' -- ' + rule.label);
133
+ ahead = {
134
+ tag: rule,
135
+ match: match,
136
+ prec: rule.prec ? rule.prec : 0, // || 0 is removed by the compiler
137
+ expr: makeTokenExpr(match)
138
+ };
139
+
140
+ } else {
141
+ xpathLog('DONE');
142
+ done = true;
143
+ }
144
+
145
+ while (xpathReduce(stack, ahead)) {
146
+ reduce_count++;
147
+ xpathLog('stack: ' + stackToString(stack));
148
+ }
149
+ }
150
+
151
+ xpathLog('stack: ' + stackToString(stack));
152
+
153
+ // DGF any valid XPath should "reduce" to a single Expr token
154
+ if (stack.length != 1) {
155
+ throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
156
+ }
157
+
158
+ var result = stack[0].expr;
159
+ xpathParseCache[cachekey] = result;
160
+
161
+ xpathLog('XPath parse: ' + parse_count + ' / ' +
162
+ lexer_count + ' / ' + reduce_count);
163
+
164
+ return result;
165
+ }
166
+
167
+ var xpathParseCache = {};
168
+
169
+ function xpathCacheLookup(expr) {
170
+ return xpathParseCache[expr];
171
+ }
172
+
173
+ /*DGF xpathReduce is where the magic happens in this parser.
174
+ Skim down to the bottom of this file to find the table of
175
+ grammatical rules and precedence numbers, "The productions of the grammar".
176
+
177
+ The idea here
178
+ is that we want to take a stack of tokens and apply
179
+ grammatical rules to them, "reducing" them to higher-level
180
+ tokens. Ultimately, any valid XPath should reduce to exactly one
181
+ "Expr" token.
182
+
183
+ Reduce too early or too late and you'll have two tokens that can't reduce
184
+ to single Expr. For example, you may hastily reduce a qname that
185
+ should name a function, incorrectly treating it as a tag name.
186
+ Or you may reduce too late, accidentally reducing the last part of the
187
+ XPath into a top-level "Expr" that won't reduce with earlier parts of
188
+ the XPath.
189
+
190
+ A "cand" is a grammatical rule candidate, with a given precedence
191
+ number. "ahead" is the upcoming token, which also has a precedence
192
+ number. If the token has a higher precedence number than
193
+ the rule candidate, we'll "shift" the token onto the token stack,
194
+ instead of immediately applying the rule candidate.
195
+
196
+ Some tokens have left associativity, in which case we shift when they
197
+ have LOWER precedence than the candidate.
198
+ */
199
+ function xpathReduce(stack, ahead) {
200
+ var cand = null;
201
+
202
+ if (stack.length > 0) {
203
+ var top = stack[stack.length-1];
204
+ var ruleset = xpathRules[top.tag.key];
205
+
206
+ if (ruleset) {
207
+ for (var i = 0; i < ruleset.length; ++i) {
208
+ var rule = ruleset[i];
209
+ var match = xpathMatchStack(stack, rule[1]);
210
+ if (match.length) {
211
+ cand = {
212
+ tag: rule[0],
213
+ rule: rule,
214
+ match: match
215
+ };
216
+ cand.prec = xpathGrammarPrecedence(cand);
217
+ break;
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ var ret;
224
+ if (cand && (!ahead || cand.prec > ahead.prec ||
225
+ (ahead.tag.left && cand.prec >= ahead.prec))) {
226
+ for (var i = 0; i < cand.match.matchlength; ++i) {
227
+ stack.pop();
228
+ }
229
+
230
+ xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
231
+ ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
232
+ (ahead.tag.left ? ' left' : '')
233
+ : ' none '));
234
+
235
+ var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
236
+ xpathLog('going to apply ' + cand.rule[3].toString());
237
+ cand.expr = cand.rule[3].apply(null, matchexpr);
238
+
239
+ stack.push(cand);
240
+ ret = true;
241
+
242
+ } else {
243
+ if (ahead) {
244
+ xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
245
+ (ahead.tag.left ? ' left' : '') +
246
+ ' over ' + (cand ? cand.tag.label + ' ' +
247
+ cand.prec : ' none'));
248
+ stack.push(ahead);
249
+ }
250
+ ret = false;
251
+ }
252
+ return ret;
253
+ }
254
+
255
+ function xpathMatchStack(stack, pattern) {
256
+
257
+ // NOTE(mesch): The stack matches for variable cardinality are
258
+ // greedy but don't do backtracking. This would be an issue only
259
+ // with rules of the form A* A, i.e. with an element with variable
260
+ // cardinality followed by the same element. Since that doesn't
261
+ // occur in the grammar at hand, all matches on the stack are
262
+ // unambiguous.
263
+
264
+ var S = stack.length;
265
+ var P = pattern.length;
266
+ var p, s;
267
+ var match = [];
268
+ match.matchlength = 0;
269
+ var ds = 0;
270
+ for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
271
+ ds = 0;
272
+ var qmatch = [];
273
+ if (pattern[p] == Q_MM) {
274
+ p -= 1;
275
+ match.push(qmatch);
276
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
277
+ qmatch.push(stack[s - ds]);
278
+ ds += 1;
279
+ match.matchlength += 1;
280
+ }
281
+
282
+ } else if (pattern[p] == Q_01) {
283
+ p -= 1;
284
+ match.push(qmatch);
285
+ while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
286
+ qmatch.push(stack[s - ds]);
287
+ ds += 1;
288
+ match.matchlength += 1;
289
+ }
290
+
291
+ } else if (pattern[p] == Q_1M) {
292
+ p -= 1;
293
+ match.push(qmatch);
294
+ if (stack[s].tag == pattern[p]) {
295
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
296
+ qmatch.push(stack[s - ds]);
297
+ ds += 1;
298
+ match.matchlength += 1;
299
+ }
300
+ } else {
301
+ return [];
302
+ }
303
+
304
+ } else if (stack[s].tag == pattern[p]) {
305
+ match.push(stack[s]);
306
+ ds += 1;
307
+ match.matchlength += 1;
308
+
309
+ } else {
310
+ return [];
311
+ }
312
+
313
+ reverseInplace(qmatch);
314
+ qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
315
+ }
316
+
317
+ reverseInplace(match);
318
+
319
+ if (p == -1) {
320
+ return match;
321
+
322
+ } else {
323
+ return [];
324
+ }
325
+ }
326
+
327
+ function xpathTokenPrecedence(tag) {
328
+ return tag.prec || 2;
329
+ }
330
+
331
+ function xpathGrammarPrecedence(frame) {
332
+ var ret = 0;
333
+
334
+ if (frame.rule) { /* normal reduce */
335
+ if (frame.rule.length >= 3 && frame.rule[2] >= 0) {
336
+ ret = frame.rule[2];
337
+
338
+ } else {
339
+ for (var i = 0; i < frame.rule[1].length; ++i) {
340
+ var p = xpathTokenPrecedence(frame.rule[1][i]);
341
+ ret = Math.max(ret, p);
342
+ }
343
+ }
344
+ } else if (frame.tag) { /* TOKEN match */
345
+ ret = xpathTokenPrecedence(frame.tag);
346
+
347
+ } else if (frame.length) { /* Q_ match */
348
+ for (var j = 0; j < frame.length; ++j) {
349
+ var p = xpathGrammarPrecedence(frame[j]);
350
+ ret = Math.max(ret, p);
351
+ }
352
+ }
353
+
354
+ return ret;
355
+ }
356
+
357
+ function stackToString(stack) {
358
+ var ret = '';
359
+ for (var i = 0; i < stack.length; ++i) {
360
+ if (ret) {
361
+ ret += '\n';
362
+ }
363
+ ret += stack[i].tag.label;
364
+ }
365
+ return ret;
366
+ }
367
+
368
+
369
+ // XPath expression evaluation context. An XPath context consists of a
370
+ // DOM node, a list of DOM nodes that contains this node, a number
371
+ // that represents the position of the single node in the list, and a
372
+ // current set of variable bindings. (See XPath spec.)
373
+ //
374
+ // The interface of the expression context:
375
+ //
376
+ // Constructor -- gets the node, its position, the node set it
377
+ // belongs to, and a parent context as arguments. The parent context
378
+ // is used to implement scoping rules for variables: if a variable
379
+ // is not found in the current context, it is looked for in the
380
+ // parent context, recursively. Except for node, all arguments have
381
+ // default values: default position is 0, default node set is the
382
+ // set that contains only the node, and the default parent is null.
383
+ //
384
+ // Notice that position starts at 0 at the outside interface;
385
+ // inside XPath expressions this shows up as position()=1.
386
+ //
387
+ // clone() -- creates a new context with the current context as
388
+ // parent. If passed as argument to clone(), the new context has a
389
+ // different node, position, or node set. What is not passed is
390
+ // inherited from the cloned context.
391
+ //
392
+ // setVariable(name, expr) -- binds given XPath expression to the
393
+ // name.
394
+ //
395
+ // getVariable(name) -- what the name says.
396
+ //
397
+ // setNode(position) -- sets the context to the node at the given
398
+ // position. Needed to implement scoping rules for variables in
399
+ // XPath. (A variable is visible to all subsequent siblings, not
400
+ // only to its children.)
401
+ //
402
+ // set/isCaseInsensitive -- specifies whether node name tests should
403
+ // be case sensitive. If you're executing xpaths against a regular
404
+ // HTML DOM, you probably don't want case-sensitivity, because
405
+ // browsers tend to disagree about whether elements & attributes
406
+ // should be upper/lower case. If you're running xpaths in an
407
+ // XSLT instance, you probably DO want case sensitivity, as per the
408
+ // XSL spec.
409
+ //
410
+ // set/isReturnOnFirstMatch -- whether XPath evaluation should quit as soon
411
+ // as a result is found. This is an optimization that might make sense if you
412
+ // only care about the first result.
413
+ //
414
+ // set/isIgnoreNonElementNodesForNTA -- whether to ignore non-element nodes
415
+ // when evaluating the "node()" any node test. While technically this is
416
+ // contrary to the XPath spec, practically it can enhance performance
417
+ // significantly, and makes sense if you a) use "node()" when you mean "*",
418
+ // and b) use "//" when you mean "/descendant::*/".
419
+
420
+ function ExprContext(node, opt_position, opt_nodelist, opt_parent,
421
+ opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
422
+ opt_returnOnFirstMatch, opt_ignoreNonElementNodesForNTA)
423
+ {
424
+ this.node = node;
425
+ this.position = opt_position || 0;
426
+ this.nodelist = opt_nodelist || [ node ];
427
+ this.variables = {};
428
+ this.parent = opt_parent || null;
429
+ this.caseInsensitive = opt_caseInsensitive || false;
430
+ this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
431
+ this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
432
+ this.ignoreNonElementNodesForNTA = opt_ignoreNonElementNodesForNTA || false;
433
+ if (opt_parent) {
434
+ this.root = opt_parent.root;
435
+ } else if (this.node.nodeType == DOM_DOCUMENT_NODE) {
436
+ // NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
437
+ // document is null. Our root, however is the document that we are
438
+ // processing, so the initial context is created from its document
439
+ // node, which case we must handle here explcitly.
440
+ this.root = node;
441
+ } else {
442
+ this.root = node.ownerDocument;
443
+ }
444
+ }
445
+
446
+ ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
447
+ return new ExprContext(
448
+ opt_node || this.node,
449
+ typeof opt_position != 'undefined' ? opt_position : this.position,
450
+ opt_nodelist || this.nodelist, this, this.caseInsensitive,
451
+ this.ignoreAttributesWithoutValue, this.returnOnFirstMatch,
452
+ this.ignoreNonElementNodesForNTA);
453
+ };
454
+
455
+ ExprContext.prototype.setVariable = function(name, value) {
456
+ if (value instanceof StringValue || value instanceof BooleanValue ||
457
+ value instanceof NumberValue || value instanceof NodeSetValue) {
458
+ this.variables[name] = value;
459
+ return;
460
+ }
461
+ if ('true' === value) {
462
+ this.variables[name] = new BooleanValue(true);
463
+ } else if ('false' === value) {
464
+ this.variables[name] = new BooleanValue(false);
465
+ } else if (TOK_NUMBER.re.test(value)) {
466
+ this.variables[name] = new NumberValue(value);
467
+ } else {
468
+ // DGF What if it's null?
469
+ this.variables[name] = new StringValue(value);
470
+ }
471
+ };
472
+
473
+ ExprContext.prototype.getVariable = function(name) {
474
+ if (typeof this.variables[name] != 'undefined') {
475
+ return this.variables[name];
476
+
477
+ } else if (this.parent) {
478
+ return this.parent.getVariable(name);
479
+
480
+ } else {
481
+ return null;
482
+ }
483
+ };
484
+
485
+ ExprContext.prototype.setNode = function(position) {
486
+ this.node = this.nodelist[position];
487
+ this.position = position;
488
+ };
489
+
490
+ ExprContext.prototype.contextSize = function() {
491
+ return this.nodelist.length;
492
+ };
493
+
494
+ ExprContext.prototype.isCaseInsensitive = function() {
495
+ return this.caseInsensitive;
496
+ };
497
+
498
+ ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
499
+ return this.caseInsensitive = caseInsensitive;
500
+ };
501
+
502
+ ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
503
+ return this.ignoreAttributesWithoutValue;
504
+ };
505
+
506
+ ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
507
+ return this.ignoreAttributesWithoutValue = ignore;
508
+ };
509
+
510
+ ExprContext.prototype.isReturnOnFirstMatch = function() {
511
+ return this.returnOnFirstMatch;
512
+ };
513
+
514
+ ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
515
+ return this.returnOnFirstMatch = returnOnFirstMatch;
516
+ };
517
+
518
+ ExprContext.prototype.isIgnoreNonElementNodesForNTA = function() {
519
+ return this.ignoreNonElementNodesForNTA;
520
+ };
521
+
522
+ ExprContext.prototype.setIgnoreNonElementNodesForNTA = function(ignoreNonElementNodesForNTA) {
523
+ return this.ignoreNonElementNodesForNTA = ignoreNonElementNodesForNTA;
524
+ };
525
+
526
+ // XPath expression values. They are what XPath expressions evaluate
527
+ // to. Strangely, the different value types are not specified in the
528
+ // XPath syntax, but only in the semantics, so they don't show up as
529
+ // nonterminals in the grammar. Yet, some expressions are required to
530
+ // evaluate to particular types, and not every type can be coerced
531
+ // into every other type. Although the types of XPath values are
532
+ // similar to the types present in JavaScript, the type coercion rules
533
+ // are a bit peculiar, so we explicitly model XPath types instead of
534
+ // mapping them onto JavaScript types. (See XPath spec.)
535
+ //
536
+ // The four types are:
537
+ //
538
+ // StringValue
539
+ //
540
+ // NumberValue
541
+ //
542
+ // BooleanValue
543
+ //
544
+ // NodeSetValue
545
+ //
546
+ // The common interface of the value classes consists of methods that
547
+ // implement the XPath type coercion rules:
548
+ //
549
+ // stringValue() -- returns the value as a JavaScript String,
550
+ //
551
+ // numberValue() -- returns the value as a JavaScript Number,
552
+ //
553
+ // booleanValue() -- returns the value as a JavaScript Boolean,
554
+ //
555
+ // nodeSetValue() -- returns the value as a JavaScript Array of DOM
556
+ // Node objects.
557
+ //
558
+
559
+ function StringValue(value) {
560
+ this.value = value;
561
+ this.type = 'string';
562
+ }
563
+
564
+ StringValue.prototype.stringValue = function() {
565
+ return this.value;
566
+ }
567
+
568
+ StringValue.prototype.booleanValue = function() {
569
+ return this.value.length > 0;
570
+ }
571
+
572
+ StringValue.prototype.numberValue = function() {
573
+ return this.value - 0;
574
+ }
575
+
576
+ StringValue.prototype.nodeSetValue = function() {
577
+ throw this;
578
+ }
579
+
580
+ function BooleanValue(value) {
581
+ this.value = value;
582
+ this.type = 'boolean';
583
+ }
584
+
585
+ BooleanValue.prototype.stringValue = function() {
586
+ return '' + this.value;
587
+ }
588
+
589
+ BooleanValue.prototype.booleanValue = function() {
590
+ return this.value;
591
+ }
592
+
593
+ BooleanValue.prototype.numberValue = function() {
594
+ return this.value ? 1 : 0;
595
+ }
596
+
597
+ BooleanValue.prototype.nodeSetValue = function() {
598
+ throw this;
599
+ }
600
+
601
+ function NumberValue(value) {
602
+ this.value = value;
603
+ this.type = 'number';
604
+ }
605
+
606
+ NumberValue.prototype.stringValue = function() {
607
+ return '' + this.value;
608
+ }
609
+
610
+ NumberValue.prototype.booleanValue = function() {
611
+ return !!this.value;
612
+ }
613
+
614
+ NumberValue.prototype.numberValue = function() {
615
+ return this.value - 0;
616
+ }
617
+
618
+ NumberValue.prototype.nodeSetValue = function() {
619
+ throw this;
620
+ }
621
+
622
+ function NodeSetValue(value) {
623
+ this.value = value;
624
+ this.type = 'node-set';
625
+ }
626
+
627
+ NodeSetValue.prototype.stringValue = function() {
628
+ if (this.value.length == 0) {
629
+ return '';
630
+ } else {
631
+ return xmlValue(this.value[0]);
632
+ }
633
+ }
634
+
635
+ NodeSetValue.prototype.booleanValue = function() {
636
+ return this.value.length > 0;
637
+ }
638
+
639
+ NodeSetValue.prototype.numberValue = function() {
640
+ return this.stringValue() - 0;
641
+ }
642
+
643
+ NodeSetValue.prototype.nodeSetValue = function() {
644
+ return this.value;
645
+ };
646
+
647
+ // XPath expressions. They are used as nodes in the parse tree and
648
+ // possess an evaluate() method to compute an XPath value given an XPath
649
+ // context. Expressions are returned from the parser. Teh set of
650
+ // expression classes closely mirrors the set of non terminal symbols
651
+ // in the grammar. Every non trivial nonterminal symbol has a
652
+ // corresponding expression class.
653
+ //
654
+ // The common expression interface consists of the following methods:
655
+ //
656
+ // evaluate(context) -- evaluates the expression, returns a value.
657
+ //
658
+ // toString() -- returns the XPath text representation of the
659
+ // expression (defined in xsltdebug.js).
660
+ //
661
+ // parseTree(indent) -- returns a parse tree representation of the
662
+ // expression (defined in xsltdebug.js).
663
+
664
+ function TokenExpr(m) {
665
+ this.value = m;
666
+ }
667
+
668
+ TokenExpr.prototype.evaluate = function() {
669
+ return new StringValue(this.value);
670
+ };
671
+
672
+ function LocationExpr() {
673
+ this.absolute = false;
674
+ this.steps = [];
675
+ }
676
+
677
+ LocationExpr.prototype.appendStep = function(s) {
678
+ var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
679
+ if (combinedStep) {
680
+ this.steps[this.steps.length-1] = combinedStep;
681
+ } else {
682
+ this.steps.push(s);
683
+ }
684
+ }
685
+
686
+ LocationExpr.prototype.prependStep = function(s) {
687
+ var combinedStep = this._combineSteps(s, this.steps[0]);
688
+ if (combinedStep) {
689
+ this.steps[0] = combinedStep;
690
+ } else {
691
+ this.steps.unshift(s);
692
+ }
693
+ };
694
+
695
+ // DGF try to combine two steps into one step (perf enhancement)
696
+ LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
697
+ if (!prevStep) return null;
698
+ if (!nextStep) return null;
699
+ var hasPredicates = (prevStep.predicates && prevStep.predicates.length > 0);
700
+ if (prevStep.nodetest instanceof NodeTestAny && !hasPredicates) {
701
+ // maybe suitable to be combined
702
+ if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
703
+ if (nextStep.axis == xpathAxis.CHILD) {
704
+ // HBC - commenting out, because this is not a valid reduction
705
+ //nextStep.axis = xpathAxis.DESCENDANT;
706
+ //return nextStep;
707
+ } else if (nextStep.axis == xpathAxis.SELF) {
708
+ nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
709
+ return nextStep;
710
+ }
711
+ } else if (prevStep.axis == xpathAxis.DESCENDANT) {
712
+ if (nextStep.axis == xpathAxis.SELF) {
713
+ nextStep.axis = xpathAxis.DESCENDANT;
714
+ return nextStep;
715
+ }
716
+ }
717
+ }
718
+ return null;
719
+ }
720
+
721
+ LocationExpr.prototype.evaluate = function(ctx) {
722
+ var start;
723
+ if (this.absolute) {
724
+ start = ctx.root;
725
+
726
+ } else {
727
+ start = ctx.node;
728
+ }
729
+
730
+ var nodes = [];
731
+ xPathStep(nodes, this.steps, 0, start, ctx);
732
+ return new NodeSetValue(nodes);
733
+ };
734
+
735
+ function xPathStep(nodes, steps, step, input, ctx) {
736
+ var s = steps[step];
737
+ var ctx2 = ctx.clone(input);
738
+
739
+ if (ctx.returnOnFirstMatch && !s.hasPositionalPredicate) {
740
+ var nodelist = s.evaluate(ctx2).nodeSetValue();
741
+ // the predicates were not processed in the last evaluate(), so that we can
742
+ // process them here with the returnOnFirstMatch optimization. We do a
743
+ // depth-first grab at any nodes that pass the predicate tests. There is no
744
+ // way to optimize when predicates contain positional selectors, including
745
+ // indexes or uses of the last() or position() functions, because they
746
+ // typically require the entire nodelist for context. Process without
747
+ // optimization if we encounter such selectors.
748
+ var nLength = nodelist.length;
749
+ var pLength = s.predicate.length;
750
+ nodelistLoop:
751
+ for (var i = 0; i < nLength; ++i) {
752
+ var n = nodelist[i];
753
+ for (var j = 0; j < pLength; ++j) {
754
+ if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
755
+ continue nodelistLoop;
756
+ }
757
+ }
758
+ // n survived the predicate tests!
759
+ if (step == steps.length - 1) {
760
+ nodes.push(n);
761
+ }
762
+ else {
763
+ xPathStep(nodes, steps, step + 1, n, ctx);
764
+ }
765
+ if (nodes.length > 0) {
766
+ break;
767
+ }
768
+ }
769
+ }
770
+ else {
771
+ // set returnOnFirstMatch to false for the cloned ExprContext, because
772
+ // behavior in StepExpr.prototype.evaluate is driven off its value. Note
773
+ // that the original context may still have true for this value.
774
+ ctx2.returnOnFirstMatch = false;
775
+ var nodelist = s.evaluate(ctx2).nodeSetValue();
776
+ for (var i = 0; i < nodelist.length; ++i) {
777
+ if (step == steps.length - 1) {
778
+ nodes.push(nodelist[i]);
779
+ } else {
780
+ xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
781
+ }
782
+ }
783
+ }
784
+ }
785
+
786
+ function StepExpr(axis, nodetest, opt_predicate) {
787
+ this.axis = axis;
788
+ this.nodetest = nodetest;
789
+ this.predicate = opt_predicate || [];
790
+ this.hasPositionalPredicate = false;
791
+ for (var i = 0; i < this.predicate.length; ++i) {
792
+ if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
793
+ this.hasPositionalPredicate = true;
794
+ break;
795
+ }
796
+ }
797
+ }
798
+
799
+ StepExpr.prototype.appendPredicate = function(p) {
800
+ this.predicate.push(p);
801
+ if (!this.hasPositionalPredicate) {
802
+ this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
803
+ }
804
+ }
805
+
806
+ StepExpr.prototype.evaluate = function(ctx) {
807
+ var input = ctx.node;
808
+ var nodelist = [];
809
+ var skipNodeTest = false;
810
+
811
+ if (this.nodetest instanceof NodeTestAny) {
812
+ skipNodeTest = true;
813
+ }
814
+
815
+ // NOTE(mesch): When this was a switch() statement, it didn't work
816
+ // in Safari/2.0. Not sure why though; it resulted in the JavaScript
817
+ // console output "undefined" (without any line number or so).
818
+
819
+ if (this.axis == xpathAxis.ANCESTOR_OR_SELF) {
820
+ nodelist.push(input);
821
+ for (var n = input.parentNode; n; n = n.parentNode) {
822
+ nodelist.push(n);
823
+ }
824
+
825
+ } else if (this.axis == xpathAxis.ANCESTOR) {
826
+ for (var n = input.parentNode; n; n = n.parentNode) {
827
+ nodelist.push(n);
828
+ }
829
+
830
+ } else if (this.axis == xpathAxis.ATTRIBUTE) {
831
+ if (this.nodetest.name != undefined) {
832
+ // single-attribute step
833
+ if (input.attributes) {
834
+ if (input.attributes instanceof Array) {
835
+ // probably evaluating on document created by xmlParse()
836
+ copyArray(nodelist, input.attributes);
837
+ }
838
+ else {
839
+ if (this.nodetest.name == 'style') {
840
+ var value = input.getAttribute('style');
841
+ if (value && typeof(value) != 'string') {
842
+ // this is the case where indexing into the attributes array
843
+ // doesn't give us the attribute node in IE - we create our own
844
+ // node instead
845
+ nodelist.push(XNode.create(DOM_ATTRIBUTE_NODE, 'style',
846
+ value.cssText, document));
847
+ }
848
+ else {
849
+ nodelist.push(input.attributes[this.nodetest.name]);
850
+ }
851
+ }
852
+ else {
853
+ nodelist.push(input.attributes[this.nodetest.name]);
854
+ }
855
+ }
856
+ }
857
+ }
858
+ else {
859
+ // all-attributes step
860
+ if (ctx.ignoreAttributesWithoutValue) {
861
+ copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
862
+ }
863
+ else {
864
+ copyArray(nodelist, input.attributes);
865
+ }
866
+ }
867
+
868
+ } else if (this.axis == xpathAxis.CHILD) {
869
+ copyArray(nodelist, input.childNodes);
870
+
871
+ } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
872
+ if (this.nodetest.evaluate(ctx).booleanValue()) {
873
+ nodelist.push(input);
874
+ }
875
+ var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
876
+ xpathCollectDescendants(nodelist, input, tagName);
877
+ if (tagName) skipNodeTest = true;
878
+
879
+ } else if (this.axis == xpathAxis.DESCENDANT) {
880
+ var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
881
+ xpathCollectDescendants(nodelist, input, tagName);
882
+ if (tagName) skipNodeTest = true;
883
+
884
+ } else if (this.axis == xpathAxis.FOLLOWING) {
885
+ for (var n = input; n; n = n.parentNode) {
886
+ for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
887
+ nodelist.push(nn);
888
+ xpathCollectDescendants(nodelist, nn);
889
+ }
890
+ }
891
+
892
+ } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
893
+ for (var n = input.nextSibling; n; n = n.nextSibling) {
894
+ nodelist.push(n);
895
+ }
896
+
897
+ } else if (this.axis == xpathAxis.NAMESPACE) {
898
+ alert('not implemented: axis namespace');
899
+
900
+ } else if (this.axis == xpathAxis.PARENT) {
901
+ if (input.parentNode) {
902
+ nodelist.push(input.parentNode);
903
+ }
904
+
905
+ } else if (this.axis == xpathAxis.PRECEDING) {
906
+ for (var n = input; n; n = n.parentNode) {
907
+ for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
908
+ nodelist.push(nn);
909
+ xpathCollectDescendantsReverse(nodelist, nn);
910
+ }
911
+ }
912
+
913
+ } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
914
+ for (var n = input.previousSibling; n; n = n.previousSibling) {
915
+ nodelist.push(n);
916
+ }
917
+
918
+ } else if (this.axis == xpathAxis.SELF) {
919
+ nodelist.push(input);
920
+
921
+ } else {
922
+ throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
923
+ }
924
+
925
+ if (!skipNodeTest) {
926
+ // process node test
927
+ var nodelist0 = nodelist;
928
+ nodelist = [];
929
+ for (var i = 0; i < nodelist0.length; ++i) {
930
+ var n = nodelist0[i];
931
+ if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
932
+ nodelist.push(n);
933
+ }
934
+ }
935
+ }
936
+
937
+ // process predicates
938
+ if (!ctx.returnOnFirstMatch) {
939
+ for (var i = 0; i < this.predicate.length; ++i) {
940
+ var nodelist0 = nodelist;
941
+ nodelist = [];
942
+ for (var ii = 0; ii < nodelist0.length; ++ii) {
943
+ var n = nodelist0[ii];
944
+ if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
945
+ nodelist.push(n);
946
+ }
947
+ }
948
+ }
949
+ }
950
+
951
+ return new NodeSetValue(nodelist);
952
+ };
953
+
954
+ function NodeTestAny() {
955
+ this.value = new BooleanValue(true);
956
+ }
957
+
958
+ NodeTestAny.prototype.evaluate = function(ctx) {
959
+ return this.value;
960
+ };
961
+
962
+ function NodeTestElementOrAttribute() {}
963
+
964
+ NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
965
+ return new BooleanValue(
966
+ ctx.node.nodeType == DOM_ELEMENT_NODE ||
967
+ ctx.node.nodeType == DOM_ATTRIBUTE_NODE);
968
+ }
969
+
970
+ function NodeTestText() {}
971
+
972
+ NodeTestText.prototype.evaluate = function(ctx) {
973
+ return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
974
+ }
975
+
976
+ function NodeTestComment() {}
977
+
978
+ NodeTestComment.prototype.evaluate = function(ctx) {
979
+ return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
980
+ }
981
+
982
+ function NodeTestPI(target) {
983
+ this.target = target;
984
+ }
985
+
986
+ NodeTestPI.prototype.evaluate = function(ctx) {
987
+ return new
988
+ BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&
989
+ (!this.target || ctx.node.nodeName == this.target));
990
+ }
991
+
992
+ function NodeTestNC(nsprefix) {
993
+ this.regex = new RegExp("^" + nsprefix + ":");
994
+ this.nsprefix = nsprefix;
995
+ }
996
+
997
+ NodeTestNC.prototype.evaluate = function(ctx) {
998
+ var n = ctx.node;
999
+ return new BooleanValue(this.regex.match(n.nodeName));
1000
+ }
1001
+
1002
+ function NodeTestName(name) {
1003
+ this.name = name;
1004
+ this.re = new RegExp('^' + name + '$', "i");
1005
+ }
1006
+
1007
+ NodeTestName.prototype.evaluate = function(ctx) {
1008
+ var n = ctx.node;
1009
+ if (ctx.caseInsensitive) {
1010
+ if (n.nodeName.length != this.name.length) return new BooleanValue(false);
1011
+ return new BooleanValue(this.re.test(n.nodeName));
1012
+ } else {
1013
+ return new BooleanValue(n.nodeName == this.name);
1014
+ }
1015
+ }
1016
+
1017
+ function PredicateExpr(expr) {
1018
+ this.expr = expr;
1019
+ }
1020
+
1021
+ PredicateExpr.prototype.evaluate = function(ctx) {
1022
+ var v = this.expr.evaluate(ctx);
1023
+ if (v.type == 'number') {
1024
+ // NOTE(mesch): Internally, position is represented starting with
1025
+ // 0, however in XPath position starts with 1. See functions
1026
+ // position() and last().
1027
+ return new BooleanValue(ctx.position == v.numberValue() - 1);
1028
+ } else {
1029
+ return new BooleanValue(v.booleanValue());
1030
+ }
1031
+ };
1032
+
1033
+ function FunctionCallExpr(name) {
1034
+ this.name = name;
1035
+ this.args = [];
1036
+ }
1037
+
1038
+ FunctionCallExpr.prototype.appendArg = function(arg) {
1039
+ this.args.push(arg);
1040
+ };
1041
+
1042
+ FunctionCallExpr.prototype.evaluate = function(ctx) {
1043
+ var fn = '' + this.name.value;
1044
+ var f = this.xpathfunctions[fn];
1045
+ if (f) {
1046
+ return f.call(this, ctx);
1047
+ } else {
1048
+ xpathLog('XPath NO SUCH FUNCTION ' + fn);
1049
+ return new BooleanValue(false);
1050
+ }
1051
+ };
1052
+
1053
+ FunctionCallExpr.prototype.xpathfunctions = {
1054
+ 'last': function(ctx) {
1055
+ assert(this.args.length == 0);
1056
+ // NOTE(mesch): XPath position starts at 1.
1057
+ return new NumberValue(ctx.contextSize());
1058
+ },
1059
+
1060
+ 'position': function(ctx) {
1061
+ assert(this.args.length == 0);
1062
+ // NOTE(mesch): XPath position starts at 1.
1063
+ return new NumberValue(ctx.position + 1);
1064
+ },
1065
+
1066
+ 'count': function(ctx) {
1067
+ assert(this.args.length == 1);
1068
+ var v = this.args[0].evaluate(ctx);
1069
+ return new NumberValue(v.nodeSetValue().length);
1070
+ },
1071
+
1072
+ 'id': function(ctx) {
1073
+ assert(this.args.length == 1);
1074
+ var e = this.args[0].evaluate(ctx);
1075
+ var ret = [];
1076
+ var ids;
1077
+ if (e.type == 'node-set') {
1078
+ ids = [];
1079
+ var en = e.nodeSetValue();
1080
+ for (var i = 0; i < en.length; ++i) {
1081
+ var v = xmlValue(en[i]).split(/\s+/);
1082
+ for (var ii = 0; ii < v.length; ++ii) {
1083
+ ids.push(v[ii]);
1084
+ }
1085
+ }
1086
+ } else {
1087
+ ids = e.stringValue().split(/\s+/);
1088
+ }
1089
+ var d = ctx.root;
1090
+ for (var i = 0; i < ids.length; ++i) {
1091
+ var n = d.getElementById(ids[i]);
1092
+ if (n) {
1093
+ ret.push(n);
1094
+ }
1095
+ }
1096
+ return new NodeSetValue(ret);
1097
+ },
1098
+
1099
+ 'local-name': function(ctx) {
1100
+ alert('not implmented yet: XPath function local-name()');
1101
+ },
1102
+
1103
+ 'namespace-uri': function(ctx) {
1104
+ alert('not implmented yet: XPath function namespace-uri()');
1105
+ },
1106
+
1107
+ 'name': function(ctx) {
1108
+ assert(this.args.length == 1 || this.args.length == 0);
1109
+ var n;
1110
+ if (this.args.length == 0) {
1111
+ n = [ ctx.node ];
1112
+ } else {
1113
+ n = this.args[0].evaluate(ctx).nodeSetValue();
1114
+ }
1115
+
1116
+ if (n.length == 0) {
1117
+ return new StringValue('');
1118
+ } else {
1119
+ return new StringValue(n[0].nodeName);
1120
+ }
1121
+ },
1122
+
1123
+ 'string': function(ctx) {
1124
+ assert(this.args.length == 1 || this.args.length == 0);
1125
+ if (this.args.length == 0) {
1126
+ return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
1127
+ } else {
1128
+ return new StringValue(this.args[0].evaluate(ctx).stringValue());
1129
+ }
1130
+ },
1131
+
1132
+ 'concat': function(ctx) {
1133
+ var ret = '';
1134
+ for (var i = 0; i < this.args.length; ++i) {
1135
+ ret += this.args[i].evaluate(ctx).stringValue();
1136
+ }
1137
+ return new StringValue(ret);
1138
+ },
1139
+
1140
+ 'starts-with': function(ctx) {
1141
+ assert(this.args.length == 2);
1142
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1143
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1144
+ return new BooleanValue(s0.indexOf(s1) == 0);
1145
+ },
1146
+
1147
+ 'ends-with': function(ctx) {
1148
+ assert(this.args.length == 2);
1149
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1150
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1151
+ var re = new RegExp(RegExp.escape(s1) + '$');
1152
+ return new BooleanValue(re.test(s0));
1153
+ },
1154
+
1155
+ 'contains': function(ctx) {
1156
+ assert(this.args.length == 2);
1157
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1158
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1159
+ return new BooleanValue(s0.indexOf(s1) != -1);
1160
+ },
1161
+
1162
+ 'substring-before': function(ctx) {
1163
+ assert(this.args.length == 2);
1164
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1165
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1166
+ var i = s0.indexOf(s1);
1167
+ var ret;
1168
+ if (i == -1) {
1169
+ ret = '';
1170
+ } else {
1171
+ ret = s0.substr(0,i);
1172
+ }
1173
+ return new StringValue(ret);
1174
+ },
1175
+
1176
+ 'substring-after': function(ctx) {
1177
+ assert(this.args.length == 2);
1178
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1179
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1180
+ var i = s0.indexOf(s1);
1181
+ var ret;
1182
+ if (i == -1) {
1183
+ ret = '';
1184
+ } else {
1185
+ ret = s0.substr(i + s1.length);
1186
+ }
1187
+ return new StringValue(ret);
1188
+ },
1189
+
1190
+ 'substring': function(ctx) {
1191
+ // NOTE: XPath defines the position of the first character in a
1192
+ // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
1193
+ assert(this.args.length == 2 || this.args.length == 3);
1194
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1195
+ var s1 = this.args[1].evaluate(ctx).numberValue();
1196
+ var ret;
1197
+ if (this.args.length == 2) {
1198
+ var i1 = Math.max(0, Math.round(s1) - 1);
1199
+ ret = s0.substr(i1);
1200
+
1201
+ } else {
1202
+ var s2 = this.args[2].evaluate(ctx).numberValue();
1203
+ var i0 = Math.round(s1) - 1;
1204
+ var i1 = Math.max(0, i0);
1205
+ var i2 = Math.round(s2) - Math.max(0, -i0);
1206
+ ret = s0.substr(i1, i2);
1207
+ }
1208
+ return new StringValue(ret);
1209
+ },
1210
+
1211
+ 'string-length': function(ctx) {
1212
+ var s;
1213
+ if (this.args.length > 0) {
1214
+ s = this.args[0].evaluate(ctx).stringValue();
1215
+ } else {
1216
+ s = new NodeSetValue([ ctx.node ]).stringValue();
1217
+ }
1218
+ return new NumberValue(s.length);
1219
+ },
1220
+
1221
+ 'normalize-space': function(ctx) {
1222
+ var s;
1223
+ if (this.args.length > 0) {
1224
+ s = this.args[0].evaluate(ctx).stringValue();
1225
+ } else {
1226
+ s = new NodeSetValue([ ctx.node ]).stringValue();
1227
+ }
1228
+ s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
1229
+ return new StringValue(s);
1230
+ },
1231
+
1232
+ 'translate': function(ctx) {
1233
+ assert(this.args.length == 3);
1234
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1235
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1236
+ var s2 = this.args[2].evaluate(ctx).stringValue();
1237
+
1238
+ for (var i = 0; i < s1.length; ++i) {
1239
+ s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
1240
+ }
1241
+ return new StringValue(s0);
1242
+ },
1243
+
1244
+ 'matches': function(ctx) {
1245
+ assert(this.args.length >= 2);
1246
+ var s0 = this.args[0].evaluate(ctx).stringValue();
1247
+ var s1 = this.args[1].evaluate(ctx).stringValue();
1248
+ if (this.args.length > 2) {
1249
+ var s2 = this.args[2].evaluate(ctx).stringValue();
1250
+ if (/[^mi]/.test(s2)) {
1251
+ throw 'Invalid regular expression syntax: ' + s2;
1252
+ }
1253
+ }
1254
+
1255
+ try {
1256
+ var re = new RegExp(s1, s2);
1257
+ }
1258
+ catch (e) {
1259
+ throw 'Invalid matches argument: ' + s1;
1260
+ }
1261
+ return new BooleanValue(re.test(s0));
1262
+ },
1263
+
1264
+ 'boolean': function(ctx) {
1265
+ assert(this.args.length == 1);
1266
+ return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
1267
+ },
1268
+
1269
+ 'not': function(ctx) {
1270
+ assert(this.args.length == 1);
1271
+ var ret = !this.args[0].evaluate(ctx).booleanValue();
1272
+ return new BooleanValue(ret);
1273
+ },
1274
+
1275
+ 'true': function(ctx) {
1276
+ assert(this.args.length == 0);
1277
+ return new BooleanValue(true);
1278
+ },
1279
+
1280
+ 'false': function(ctx) {
1281
+ assert(this.args.length == 0);
1282
+ return new BooleanValue(false);
1283
+ },
1284
+
1285
+ 'lang': function(ctx) {
1286
+ assert(this.args.length == 1);
1287
+ var lang = this.args[0].evaluate(ctx).stringValue();
1288
+ var xmllang;
1289
+ var n = ctx.node;
1290
+ while (n && n != n.parentNode /* just in case ... */) {
1291
+ xmllang = n.getAttribute('xml:lang');
1292
+ if (xmllang) {
1293
+ break;
1294
+ }
1295
+ n = n.parentNode;
1296
+ }
1297
+ if (!xmllang) {
1298
+ return new BooleanValue(false);
1299
+ } else {
1300
+ var re = new RegExp('^' + lang + '$', 'i');
1301
+ return new BooleanValue(xmllang.match(re) ||
1302
+ xmllang.replace(/_.*$/,'').match(re));
1303
+ }
1304
+ },
1305
+
1306
+ 'number': function(ctx) {
1307
+ assert(this.args.length == 1 || this.args.length == 0);
1308
+
1309
+ if (this.args.length == 1) {
1310
+ return new NumberValue(this.args[0].evaluate(ctx).numberValue());
1311
+ } else {
1312
+ return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
1313
+ }
1314
+ },
1315
+
1316
+ 'sum': function(ctx) {
1317
+ assert(this.args.length == 1);
1318
+ var n = this.args[0].evaluate(ctx).nodeSetValue();
1319
+ var sum = 0;
1320
+ for (var i = 0; i < n.length; ++i) {
1321
+ sum += xmlValue(n[i]) - 0;
1322
+ }
1323
+ return new NumberValue(sum);
1324
+ },
1325
+
1326
+ 'floor': function(ctx) {
1327
+ assert(this.args.length == 1);
1328
+ var num = this.args[0].evaluate(ctx).numberValue();
1329
+ return new NumberValue(Math.floor(num));
1330
+ },
1331
+
1332
+ 'ceiling': function(ctx) {
1333
+ assert(this.args.length == 1);
1334
+ var num = this.args[0].evaluate(ctx).numberValue();
1335
+ return new NumberValue(Math.ceil(num));
1336
+ },
1337
+
1338
+ 'round': function(ctx) {
1339
+ assert(this.args.length == 1);
1340
+ var num = this.args[0].evaluate(ctx).numberValue();
1341
+ return new NumberValue(Math.round(num));
1342
+ },
1343
+
1344
+ // TODO(mesch): The following functions are custom. There is a
1345
+ // standard that defines how to add functions, which should be
1346
+ // applied here.
1347
+
1348
+ 'ext-join': function(ctx) {
1349
+ assert(this.args.length == 2);
1350
+ var nodes = this.args[0].evaluate(ctx).nodeSetValue();
1351
+ var delim = this.args[1].evaluate(ctx).stringValue();
1352
+ var ret = '';
1353
+ for (var i = 0; i < nodes.length; ++i) {
1354
+ if (ret) {
1355
+ ret += delim;
1356
+ }
1357
+ ret += xmlValue(nodes[i]);
1358
+ }
1359
+ return new StringValue(ret);
1360
+ },
1361
+
1362
+ // ext-if() evaluates and returns its second argument, if the
1363
+ // boolean value of its first argument is true, otherwise it
1364
+ // evaluates and returns its third argument.
1365
+
1366
+ 'ext-if': function(ctx) {
1367
+ assert(this.args.length == 3);
1368
+ if (this.args[0].evaluate(ctx).booleanValue()) {
1369
+ return this.args[1].evaluate(ctx);
1370
+ } else {
1371
+ return this.args[2].evaluate(ctx);
1372
+ }
1373
+ },
1374
+
1375
+ // ext-cardinal() evaluates its single argument as a number, and
1376
+ // returns the current node that many times. It can be used in the
1377
+ // select attribute to iterate over an integer range.
1378
+
1379
+ 'ext-cardinal': function(ctx) {
1380
+ assert(this.args.length >= 1);
1381
+ var c = this.args[0].evaluate(ctx).numberValue();
1382
+ var ret = [];
1383
+ for (var i = 0; i < c; ++i) {
1384
+ ret.push(ctx.node);
1385
+ }
1386
+ return new NodeSetValue(ret);
1387
+ }
1388
+ };
1389
+
1390
+ function UnionExpr(expr1, expr2) {
1391
+ this.expr1 = expr1;
1392
+ this.expr2 = expr2;
1393
+ }
1394
+
1395
+ UnionExpr.prototype.evaluate = function(ctx) {
1396
+ var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
1397
+ var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
1398
+ var I1 = nodes1.length;
1399
+ for (var i2 = 0; i2 < nodes2.length; ++i2) {
1400
+ var n = nodes2[i2];
1401
+ var inBoth = false;
1402
+ for (var i1 = 0; i1 < I1; ++i1) {
1403
+ if (nodes1[i1] == n) {
1404
+ inBoth = true;
1405
+ i1 = I1; // break inner loop
1406
+ }
1407
+ }
1408
+ if (!inBoth) {
1409
+ nodes1.push(n);
1410
+ }
1411
+ }
1412
+ return new NodeSetValue(nodes1);
1413
+ };
1414
+
1415
+ function PathExpr(filter, rel) {
1416
+ this.filter = filter;
1417
+ this.rel = rel;
1418
+ }
1419
+
1420
+ PathExpr.prototype.evaluate = function(ctx) {
1421
+ // the filter expression should be evaluated in its entirety with no
1422
+ // optimization, as we can't backtrack to it after having moved on to
1423
+ // evaluating the relative location path
1424
+ var flag = ctx.returnOnFirstMatch;
1425
+ ctx.setReturnOnFirstMatch(false);
1426
+ var nodes = this.filter.evaluate(ctx).nodeSetValue();
1427
+ ctx.setReturnOnFirstMatch(flag);
1428
+
1429
+ var nodes1 = [];
1430
+ if (ctx.returnOnFirstMatch) {
1431
+ for (var i = 0; i < nodes.length; ++i) {
1432
+ nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1433
+ if (nodes1.length > 0) {
1434
+ break;
1435
+ }
1436
+ }
1437
+ return new NodeSetValue(nodes1);
1438
+ }
1439
+ else {
1440
+ for (var i = 0; i < nodes.length; ++i) {
1441
+ var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1442
+ for (var ii = 0; ii < nodes0.length; ++ii) {
1443
+ nodes1.push(nodes0[ii]);
1444
+ }
1445
+ }
1446
+ return new NodeSetValue(nodes1);
1447
+ }
1448
+ };
1449
+
1450
+ function FilterExpr(expr, predicate) {
1451
+ this.expr = expr;
1452
+ this.predicate = predicate;
1453
+ }
1454
+
1455
+ FilterExpr.prototype.evaluate = function(ctx) {
1456
+ var flag = ctx.returnOnFirstMatch;
1457
+ ctx.setReturnOnFirstMatch(false);
1458
+ var nodes = this.expr.evaluate(ctx).nodeSetValue();
1459
+ ctx.setReturnOnFirstMatch(flag);
1460
+
1461
+ for (var i = 0; i < this.predicate.length; ++i) {
1462
+ var nodes0 = nodes;
1463
+ nodes = [];
1464
+ for (var j = 0; j < nodes0.length; ++j) {
1465
+ var n = nodes0[j];
1466
+ if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
1467
+ nodes.push(n);
1468
+ }
1469
+ }
1470
+ }
1471
+
1472
+ return new NodeSetValue(nodes);
1473
+ }
1474
+
1475
+ function UnaryMinusExpr(expr) {
1476
+ this.expr = expr;
1477
+ }
1478
+
1479
+ UnaryMinusExpr.prototype.evaluate = function(ctx) {
1480
+ return new NumberValue(-this.expr.evaluate(ctx).numberValue());
1481
+ };
1482
+
1483
+ function BinaryExpr(expr1, op, expr2) {
1484
+ this.expr1 = expr1;
1485
+ this.expr2 = expr2;
1486
+ this.op = op;
1487
+ }
1488
+
1489
+ BinaryExpr.prototype.evaluate = function(ctx) {
1490
+ var ret;
1491
+ switch (this.op.value) {
1492
+ case 'or':
1493
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
1494
+ this.expr2.evaluate(ctx).booleanValue());
1495
+ break;
1496
+
1497
+ case 'and':
1498
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&
1499
+ this.expr2.evaluate(ctx).booleanValue());
1500
+ break;
1501
+
1502
+ case '+':
1503
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
1504
+ this.expr2.evaluate(ctx).numberValue());
1505
+ break;
1506
+
1507
+ case '-':
1508
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
1509
+ this.expr2.evaluate(ctx).numberValue());
1510
+ break;
1511
+
1512
+ case '*':
1513
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
1514
+ this.expr2.evaluate(ctx).numberValue());
1515
+ break;
1516
+
1517
+ case 'mod':
1518
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
1519
+ this.expr2.evaluate(ctx).numberValue());
1520
+ break;
1521
+
1522
+ case 'div':
1523
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
1524
+ this.expr2.evaluate(ctx).numberValue());
1525
+ break;
1526
+
1527
+ case '=':
1528
+ ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
1529
+ break;
1530
+
1531
+ case '!=':
1532
+ ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
1533
+ break;
1534
+
1535
+ case '<':
1536
+ ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });
1537
+ break;
1538
+
1539
+ case '<=':
1540
+ ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });
1541
+ break;
1542
+
1543
+ case '>':
1544
+ ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });
1545
+ break;
1546
+
1547
+ case '>=':
1548
+ ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });
1549
+ break;
1550
+
1551
+ default:
1552
+ alert('BinaryExpr.evaluate: ' + this.op.value);
1553
+ }
1554
+ return ret;
1555
+ };
1556
+
1557
+ BinaryExpr.prototype.compare = function(ctx, cmp) {
1558
+ var v1 = this.expr1.evaluate(ctx);
1559
+ var v2 = this.expr2.evaluate(ctx);
1560
+
1561
+ var ret;
1562
+ if (v1.type == 'node-set' && v2.type == 'node-set') {
1563
+ var n1 = v1.nodeSetValue();
1564
+ var n2 = v2.nodeSetValue();
1565
+ ret = false;
1566
+ for (var i1 = 0; i1 < n1.length; ++i1) {
1567
+ for (var i2 = 0; i2 < n2.length; ++i2) {
1568
+ if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
1569
+ ret = true;
1570
+ // Break outer loop. Labels confuse the jscompiler and we
1571
+ // don't use them.
1572
+ i2 = n2.length;
1573
+ i1 = n1.length;
1574
+ }
1575
+ }
1576
+ }
1577
+
1578
+ } else if (v1.type == 'node-set' || v2.type == 'node-set') {
1579
+
1580
+ if (v1.type == 'number') {
1581
+ var s = v1.numberValue();
1582
+ var n = v2.nodeSetValue();
1583
+
1584
+ ret = false;
1585
+ for (var i = 0; i < n.length; ++i) {
1586
+ var nn = xmlValue(n[i]) - 0;
1587
+ if (cmp(s, nn)) {
1588
+ ret = true;
1589
+ break;
1590
+ }
1591
+ }
1592
+
1593
+ } else if (v2.type == 'number') {
1594
+ var n = v1.nodeSetValue();
1595
+ var s = v2.numberValue();
1596
+
1597
+ ret = false;
1598
+ for (var i = 0; i < n.length; ++i) {
1599
+ var nn = xmlValue(n[i]) - 0;
1600
+ if (cmp(nn, s)) {
1601
+ ret = true;
1602
+ break;
1603
+ }
1604
+ }
1605
+
1606
+ } else if (v1.type == 'string') {
1607
+ var s = v1.stringValue();
1608
+ var n = v2.nodeSetValue();
1609
+
1610
+ ret = false;
1611
+ for (var i = 0; i < n.length; ++i) {
1612
+ var nn = xmlValue(n[i]);
1613
+ if (cmp(s, nn)) {
1614
+ ret = true;
1615
+ break;
1616
+ }
1617
+ }
1618
+
1619
+ } else if (v2.type == 'string') {
1620
+ var n = v1.nodeSetValue();
1621
+ var s = v2.stringValue();
1622
+
1623
+ ret = false;
1624
+ for (var i = 0; i < n.length; ++i) {
1625
+ var nn = xmlValue(n[i]);
1626
+ if (cmp(nn, s)) {
1627
+ ret = true;
1628
+ break;
1629
+ }
1630
+ }
1631
+
1632
+ } else {
1633
+ ret = cmp(v1.booleanValue(), v2.booleanValue());
1634
+ }
1635
+
1636
+ } else if (v1.type == 'boolean' || v2.type == 'boolean') {
1637
+ ret = cmp(v1.booleanValue(), v2.booleanValue());
1638
+
1639
+ } else if (v1.type == 'number' || v2.type == 'number') {
1640
+ ret = cmp(v1.numberValue(), v2.numberValue());
1641
+
1642
+ } else {
1643
+ ret = cmp(v1.stringValue(), v2.stringValue());
1644
+ }
1645
+
1646
+ return new BooleanValue(ret);
1647
+ }
1648
+
1649
+ function LiteralExpr(value) {
1650
+ this.value = value;
1651
+ }
1652
+
1653
+ LiteralExpr.prototype.evaluate = function(ctx) {
1654
+ return new StringValue(this.value);
1655
+ };
1656
+
1657
+ function NumberExpr(value) {
1658
+ this.value = value;
1659
+ }
1660
+
1661
+ NumberExpr.prototype.evaluate = function(ctx) {
1662
+ return new NumberValue(this.value);
1663
+ };
1664
+
1665
+ function VariableExpr(name) {
1666
+ this.name = name;
1667
+ }
1668
+
1669
+ VariableExpr.prototype.evaluate = function(ctx) {
1670
+ return ctx.getVariable(this.name);
1671
+ }
1672
+
1673
+ // Factory functions for semantic values (i.e. Expressions) of the
1674
+ // productions in the grammar. When a production is matched to reduce
1675
+ // the current parse state stack, the function is called with the
1676
+ // semantic values of the matched elements as arguments, and returns
1677
+ // another semantic value. The semantic value is a node of the parse
1678
+ // tree, an expression object with an evaluate() method that evaluates the
1679
+ // expression in an actual context. These factory functions are used
1680
+ // in the specification of the grammar rules, below.
1681
+
1682
+ function makeTokenExpr(m) {
1683
+ return new TokenExpr(m);
1684
+ }
1685
+
1686
+ function passExpr(e) {
1687
+ return e;
1688
+ }
1689
+
1690
+ function makeLocationExpr1(slash, rel) {
1691
+ rel.absolute = true;
1692
+ return rel;
1693
+ }
1694
+
1695
+ function makeLocationExpr2(dslash, rel) {
1696
+ rel.absolute = true;
1697
+ rel.prependStep(makeAbbrevStep(dslash.value));
1698
+ return rel;
1699
+ }
1700
+
1701
+ function makeLocationExpr3(slash) {
1702
+ var ret = new LocationExpr();
1703
+ ret.appendStep(makeAbbrevStep('.'));
1704
+ ret.absolute = true;
1705
+ return ret;
1706
+ }
1707
+
1708
+ function makeLocationExpr4(dslash) {
1709
+ var ret = new LocationExpr();
1710
+ ret.absolute = true;
1711
+ ret.appendStep(makeAbbrevStep(dslash.value));
1712
+ return ret;
1713
+ }
1714
+
1715
+ function makeLocationExpr5(step) {
1716
+ var ret = new LocationExpr();
1717
+ ret.appendStep(step);
1718
+ return ret;
1719
+ }
1720
+
1721
+ function makeLocationExpr6(rel, slash, step) {
1722
+ rel.appendStep(step);
1723
+ return rel;
1724
+ }
1725
+
1726
+ function makeLocationExpr7(rel, dslash, step) {
1727
+ rel.appendStep(makeAbbrevStep(dslash.value));
1728
+ rel.appendStep(step);
1729
+ return rel;
1730
+ }
1731
+
1732
+ function makeStepExpr1(dot) {
1733
+ return makeAbbrevStep(dot.value);
1734
+ }
1735
+
1736
+ function makeStepExpr2(ddot) {
1737
+ return makeAbbrevStep(ddot.value);
1738
+ }
1739
+
1740
+ function makeStepExpr3(axisname, axis, nodetest) {
1741
+ return new StepExpr(axisname.value, nodetest);
1742
+ }
1743
+
1744
+ function makeStepExpr4(at, nodetest) {
1745
+ return new StepExpr('attribute', nodetest);
1746
+ }
1747
+
1748
+ function makeStepExpr5(nodetest) {
1749
+ return new StepExpr('child', nodetest);
1750
+ }
1751
+
1752
+ function makeStepExpr6(step, predicate) {
1753
+ step.appendPredicate(predicate);
1754
+ return step;
1755
+ }
1756
+
1757
+ function makeAbbrevStep(abbrev) {
1758
+ switch (abbrev) {
1759
+ case '//':
1760
+ return new StepExpr('descendant-or-self', new NodeTestAny);
1761
+
1762
+ case '.':
1763
+ return new StepExpr('self', new NodeTestAny);
1764
+
1765
+ case '..':
1766
+ return new StepExpr('parent', new NodeTestAny);
1767
+ }
1768
+ }
1769
+
1770
+ function makeNodeTestExpr1(asterisk) {
1771
+ return new NodeTestElementOrAttribute;
1772
+ }
1773
+
1774
+ function makeNodeTestExpr2(ncname, colon, asterisk) {
1775
+ return new NodeTestNC(ncname.value);
1776
+ }
1777
+
1778
+ function makeNodeTestExpr3(qname) {
1779
+ return new NodeTestName(qname.value);
1780
+ }
1781
+
1782
+ function makeNodeTestExpr4(typeo, parenc) {
1783
+ var type = typeo.value.replace(/\s*\($/, '');
1784
+ switch(type) {
1785
+ case 'node':
1786
+ return new NodeTestAny;
1787
+
1788
+ case 'text':
1789
+ return new NodeTestText;
1790
+
1791
+ case 'comment':
1792
+ return new NodeTestComment;
1793
+
1794
+ case 'processing-instruction':
1795
+ return new NodeTestPI('');
1796
+ }
1797
+ }
1798
+
1799
+ function makeNodeTestExpr5(typeo, target, parenc) {
1800
+ var type = typeo.replace(/\s*\($/, '');
1801
+ if (type != 'processing-instruction') {
1802
+ throw type;
1803
+ }
1804
+ return new NodeTestPI(target.value);
1805
+ }
1806
+
1807
+ function makePredicateExpr(pareno, expr, parenc) {
1808
+ return new PredicateExpr(expr);
1809
+ }
1810
+
1811
+ function makePrimaryExpr(pareno, expr, parenc) {
1812
+ return expr;
1813
+ }
1814
+
1815
+ function makeFunctionCallExpr1(name, pareno, parenc) {
1816
+ return new FunctionCallExpr(name);
1817
+ }
1818
+
1819
+ function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
1820
+ var ret = new FunctionCallExpr(name);
1821
+ ret.appendArg(arg1);
1822
+ for (var i = 0; i < args.length; ++i) {
1823
+ ret.appendArg(args[i]);
1824
+ }
1825
+ return ret;
1826
+ }
1827
+
1828
+ function makeArgumentExpr(comma, expr) {
1829
+ return expr;
1830
+ }
1831
+
1832
+ function makeUnionExpr(expr1, pipe, expr2) {
1833
+ return new UnionExpr(expr1, expr2);
1834
+ }
1835
+
1836
+ function makePathExpr1(filter, slash, rel) {
1837
+ return new PathExpr(filter, rel);
1838
+ }
1839
+
1840
+ function makePathExpr2(filter, dslash, rel) {
1841
+ rel.prependStep(makeAbbrevStep(dslash.value));
1842
+ return new PathExpr(filter, rel);
1843
+ }
1844
+
1845
+ function makeFilterExpr(expr, predicates) {
1846
+ if (predicates.length > 0) {
1847
+ return new FilterExpr(expr, predicates);
1848
+ } else {
1849
+ return expr;
1850
+ }
1851
+ }
1852
+
1853
+ function makeUnaryMinusExpr(minus, expr) {
1854
+ return new UnaryMinusExpr(expr);
1855
+ }
1856
+
1857
+ function makeBinaryExpr(expr1, op, expr2) {
1858
+ return new BinaryExpr(expr1, op, expr2);
1859
+ }
1860
+
1861
+ function makeLiteralExpr(token) {
1862
+ // remove quotes from the parsed value:
1863
+ var value = token.value.substring(1, token.value.length - 1);
1864
+ return new LiteralExpr(value);
1865
+ }
1866
+
1867
+ function makeNumberExpr(token) {
1868
+ return new NumberExpr(token.value);
1869
+ }
1870
+
1871
+ function makeVariableReference(dollar, name) {
1872
+ return new VariableExpr(name.value);
1873
+ }
1874
+
1875
+ // Used before parsing for optimization of common simple cases. See
1876
+ // the begin of xpathParse() for which they are.
1877
+ function makeSimpleExpr(expr) {
1878
+ if (expr.charAt(0) == '$') {
1879
+ return new VariableExpr(expr.substr(1));
1880
+ } else if (expr.charAt(0) == '@') {
1881
+ var a = new NodeTestName(expr.substr(1));
1882
+ var b = new StepExpr('attribute', a);
1883
+ var c = new LocationExpr();
1884
+ c.appendStep(b);
1885
+ return c;
1886
+ } else if (expr.match(/^[0-9]+$/)) {
1887
+ return new NumberExpr(expr);
1888
+ } else {
1889
+ var a = new NodeTestName(expr);
1890
+ var b = new StepExpr('child', a);
1891
+ var c = new LocationExpr();
1892
+ c.appendStep(b);
1893
+ return c;
1894
+ }
1895
+ }
1896
+
1897
+ function makeSimpleExpr2(expr) {
1898
+ var steps = stringSplit(expr, '/');
1899
+ var c = new LocationExpr();
1900
+ for (var i = 0; i < steps.length; ++i) {
1901
+ var a = new NodeTestName(steps[i]);
1902
+ var b = new StepExpr('child', a);
1903
+ c.appendStep(b);
1904
+ }
1905
+ return c;
1906
+ }
1907
+
1908
+ // The axes of XPath expressions.
1909
+
1910
+ var xpathAxis = {
1911
+ ANCESTOR_OR_SELF: 'ancestor-or-self',
1912
+ ANCESTOR: 'ancestor',
1913
+ ATTRIBUTE: 'attribute',
1914
+ CHILD: 'child',
1915
+ DESCENDANT_OR_SELF: 'descendant-or-self',
1916
+ DESCENDANT: 'descendant',
1917
+ FOLLOWING_SIBLING: 'following-sibling',
1918
+ FOLLOWING: 'following',
1919
+ NAMESPACE: 'namespace',
1920
+ PARENT: 'parent',
1921
+ PRECEDING_SIBLING: 'preceding-sibling',
1922
+ PRECEDING: 'preceding',
1923
+ SELF: 'self'
1924
+ };
1925
+
1926
+ var xpathAxesRe = [
1927
+ xpathAxis.ANCESTOR_OR_SELF,
1928
+ xpathAxis.ANCESTOR,
1929
+ xpathAxis.ATTRIBUTE,
1930
+ xpathAxis.CHILD,
1931
+ xpathAxis.DESCENDANT_OR_SELF,
1932
+ xpathAxis.DESCENDANT,
1933
+ xpathAxis.FOLLOWING_SIBLING,
1934
+ xpathAxis.FOLLOWING,
1935
+ xpathAxis.NAMESPACE,
1936
+ xpathAxis.PARENT,
1937
+ xpathAxis.PRECEDING_SIBLING,
1938
+ xpathAxis.PRECEDING,
1939
+ xpathAxis.SELF
1940
+ ].join('|');
1941
+
1942
+
1943
+ // The tokens of the language. The label property is just used for
1944
+ // generating debug output. The prec property is the precedence used
1945
+ // for shift/reduce resolution. Default precedence is 0 as a lookahead
1946
+ // token and 2 on the stack. TODO(mesch): this is certainly not
1947
+ // necessary and too complicated. Simplify this!
1948
+
1949
+ // NOTE: tabular formatting is the big exception, but here it should
1950
+ // be OK.
1951
+
1952
+ var TOK_PIPE = { label: "|", prec: 17, re: new RegExp("^\\|") };
1953
+ var TOK_DSLASH = { label: "//", prec: 19, re: new RegExp("^//") };
1954
+ var TOK_SLASH = { label: "/", prec: 30, re: new RegExp("^/") };
1955
+ var TOK_AXIS = { label: "::", prec: 20, re: new RegExp("^::") };
1956
+ var TOK_COLON = { label: ":", prec: 1000, re: new RegExp("^:") };
1957
+ var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') };
1958
+ var TOK_PARENO = { label: "(", prec: 34, re: new RegExp("^\\(") };
1959
+ var TOK_PARENC = { label: ")", re: new RegExp("^\\)") };
1960
+ var TOK_DDOT = { label: "..", prec: 34, re: new RegExp("^\\.\\.") };
1961
+ var TOK_DOT = { label: ".", prec: 34, re: new RegExp("^\\.") };
1962
+ var TOK_AT = { label: "@", prec: 34, re: new RegExp("^@") };
1963
+
1964
+ var TOK_COMMA = { label: ",", re: new RegExp("^,") };
1965
+
1966
+ var TOK_OR = { label: "or", prec: 10, re: new RegExp("^or\\b") };
1967
+ var TOK_AND = { label: "and", prec: 11, re: new RegExp("^and\\b") };
1968
+ var TOK_EQ = { label: "=", prec: 12, re: new RegExp("^=") };
1969
+ var TOK_NEQ = { label: "!=", prec: 12, re: new RegExp("^!=") };
1970
+ var TOK_GE = { label: ">=", prec: 13, re: new RegExp("^>=") };
1971
+ var TOK_GT = { label: ">", prec: 13, re: new RegExp("^>") };
1972
+ var TOK_LE = { label: "<=", prec: 13, re: new RegExp("^<=") };
1973
+ var TOK_LT = { label: "<", prec: 13, re: new RegExp("^<") };
1974
+ var TOK_PLUS = { label: "+", prec: 14, re: new RegExp("^\\+"), left: true };
1975
+ var TOK_MINUS = { label: "-", prec: 14, re: new RegExp("^\\-"), left: true };
1976
+ var TOK_DIV = { label: "div", prec: 15, re: new RegExp("^div\\b"), left: true };
1977
+ var TOK_MOD = { label: "mod", prec: 15, re: new RegExp("^mod\\b"), left: true };
1978
+
1979
+ var TOK_BRACKO = { label: "[", prec: 32, re: new RegExp("^\\[") };
1980
+ var TOK_BRACKC = { label: "]", re: new RegExp("^\\]") };
1981
+ var TOK_DOLLAR = { label: "$", re: new RegExp("^\\$") };
1982
+
1983
+ var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^' + XML_NC_NAME) };
1984
+
1985
+ var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };
1986
+ var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };
1987
+ var TOK_LITERALQQ = {
1988
+ label: "[litqq]",
1989
+ prec: 20,
1990
+ re: new RegExp('^"[^\\"]*"')
1991
+ };
1992
+
1993
+ var TOK_NUMBER = {
1994
+ label: "[number]",
1995
+ prec: 35,
1996
+ re: new RegExp('^\\d+(\\.\\d*)?') };
1997
+
1998
+ var TOK_QNAME = {
1999
+ label: "[qname]",
2000
+ re: new RegExp('^(' + XML_NC_NAME + ':)?' + XML_NC_NAME)
2001
+ };
2002
+
2003
+ var TOK_NODEO = {
2004
+ label: "[nodetest-start]",
2005
+ re: new RegExp('^(processing-instruction|comment|text|node)\\(')
2006
+ };
2007
+
2008
+ // The table of the tokens of our grammar, used by the lexer: first
2009
+ // column the tag, second column a regexp to recognize it in the
2010
+ // input, third column the precedence of the token, fourth column a
2011
+ // factory function for the semantic value of the token.
2012
+ //
2013
+ // NOTE: order of this list is important, because the first match
2014
+ // counts. Cf. DDOT and DOT, and AXIS and COLON.
2015
+
2016
+ var xpathTokenRules = [
2017
+ TOK_DSLASH,
2018
+ TOK_SLASH,
2019
+ TOK_DDOT,
2020
+ TOK_DOT,
2021
+ TOK_AXIS,
2022
+ TOK_COLON,
2023
+ TOK_AXISNAME,
2024
+ TOK_NODEO,
2025
+ TOK_PARENO,
2026
+ TOK_PARENC,
2027
+ TOK_BRACKO,
2028
+ TOK_BRACKC,
2029
+ TOK_AT,
2030
+ TOK_COMMA,
2031
+ TOK_OR,
2032
+ TOK_AND,
2033
+ TOK_NEQ,
2034
+ TOK_EQ,
2035
+ TOK_GE,
2036
+ TOK_GT,
2037
+ TOK_LE,
2038
+ TOK_LT,
2039
+ TOK_PLUS,
2040
+ TOK_MINUS,
2041
+ TOK_ASTERISK,
2042
+ TOK_PIPE,
2043
+ TOK_MOD,
2044
+ TOK_DIV,
2045
+ TOK_LITERALQ,
2046
+ TOK_LITERALQQ,
2047
+ TOK_NUMBER,
2048
+ TOK_QNAME,
2049
+ TOK_NCNAME,
2050
+ TOK_DOLLAR
2051
+ ];
2052
+
2053
+ // All the nonterminals of the grammar. The nonterminal objects are
2054
+ // identified by object identity; the labels are used in the debug
2055
+ // output only.
2056
+ var XPathLocationPath = { label: "LocationPath" };
2057
+ var XPathRelativeLocationPath = { label: "RelativeLocationPath" };
2058
+ var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" };
2059
+ var XPathStep = { label: "Step" };
2060
+ var XPathNodeTest = { label: "NodeTest" };
2061
+ var XPathPredicate = { label: "Predicate" };
2062
+ var XPathLiteral = { label: "Literal" };
2063
+ var XPathExpr = { label: "Expr" };
2064
+ var XPathPrimaryExpr = { label: "PrimaryExpr" };
2065
+ var XPathVariableReference = { label: "Variablereference" };
2066
+ var XPathNumber = { label: "Number" };
2067
+ var XPathFunctionCall = { label: "FunctionCall" };
2068
+ var XPathArgumentRemainder = { label: "ArgumentRemainder" };
2069
+ var XPathPathExpr = { label: "PathExpr" };
2070
+ var XPathUnionExpr = { label: "UnionExpr" };
2071
+ var XPathFilterExpr = { label: "FilterExpr" };
2072
+ var XPathDigits = { label: "Digits" };
2073
+
2074
+ var xpathNonTerminals = [
2075
+ XPathLocationPath,
2076
+ XPathRelativeLocationPath,
2077
+ XPathAbsoluteLocationPath,
2078
+ XPathStep,
2079
+ XPathNodeTest,
2080
+ XPathPredicate,
2081
+ XPathLiteral,
2082
+ XPathExpr,
2083
+ XPathPrimaryExpr,
2084
+ XPathVariableReference,
2085
+ XPathNumber,
2086
+ XPathFunctionCall,
2087
+ XPathArgumentRemainder,
2088
+ XPathPathExpr,
2089
+ XPathUnionExpr,
2090
+ XPathFilterExpr,
2091
+ XPathDigits
2092
+ ];
2093
+
2094
+ // Quantifiers that are used in the productions of the grammar.
2095
+ var Q_01 = { label: "?" };
2096
+ var Q_MM = { label: "*" };
2097
+ var Q_1M = { label: "+" };
2098
+
2099
+ // Tag for left associativity (right assoc is implied by undefined).
2100
+ var ASSOC_LEFT = true;
2101
+
2102
+ // The productions of the grammar. Columns of the table:
2103
+ //
2104
+ // - target nonterminal,
2105
+ // - pattern,
2106
+ // - precedence,
2107
+ // - semantic value factory
2108
+ //
2109
+ // The semantic value factory is a function that receives parse tree
2110
+ // nodes from the stack frames of the matched symbols as arguments and
2111
+ // returns an a node of the parse tree. The node is stored in the top
2112
+ // stack frame along with the target object of the rule. The node in
2113
+ // the parse tree is an expression object that has an evaluate() method
2114
+ // and thus evaluates XPath expressions.
2115
+ //
2116
+ // The precedence is used to decide between reducing and shifting by
2117
+ // comparing the precendence of the rule that is candidate for
2118
+ // reducing with the precedence of the look ahead token. Precedence of
2119
+ // -1 means that the precedence of the tokens in the pattern is used
2120
+ // instead. TODO: It shouldn't be necessary to explicitly assign
2121
+ // precedences to rules.
2122
+
2123
+ // DGF As it stands, these precedences are purely empirical; we're
2124
+ // not sure they can be made to be consistent at all.
2125
+
2126
+ var xpathGrammarRules =
2127
+ [
2128
+ [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
2129
+ passExpr ],
2130
+ [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,
2131
+ passExpr ],
2132
+
2133
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18,
2134
+ makeLocationExpr1 ],
2135
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,
2136
+ makeLocationExpr2 ],
2137
+
2138
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,
2139
+ makeLocationExpr3 ],
2140
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,
2141
+ makeLocationExpr4 ],
2142
+
2143
+ [ XPathRelativeLocationPath, [ XPathStep ], 31,
2144
+ makeLocationExpr5 ],
2145
+ [ XPathRelativeLocationPath,
2146
+ [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,
2147
+ makeLocationExpr6 ],
2148
+ [ XPathRelativeLocationPath,
2149
+ [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,
2150
+ makeLocationExpr7 ],
2151
+
2152
+ [ XPathStep, [ TOK_DOT ], 33,
2153
+ makeStepExpr1 ],
2154
+ [ XPathStep, [ TOK_DDOT ], 33,
2155
+ makeStepExpr2 ],
2156
+ [ XPathStep,
2157
+ [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,
2158
+ makeStepExpr3 ],
2159
+ [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,
2160
+ makeStepExpr4 ],
2161
+ [ XPathStep, [ XPathNodeTest ], 33,
2162
+ makeStepExpr5 ],
2163
+ [ XPathStep, [ XPathStep, XPathPredicate ], 33,
2164
+ makeStepExpr6 ],
2165
+
2166
+ [ XPathNodeTest, [ TOK_ASTERISK ], 33,
2167
+ makeNodeTestExpr1 ],
2168
+ [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,
2169
+ makeNodeTestExpr2 ],
2170
+ [ XPathNodeTest, [ TOK_QNAME ], 33,
2171
+ makeNodeTestExpr3 ],
2172
+ [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,
2173
+ makeNodeTestExpr4 ],
2174
+ [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,
2175
+ makeNodeTestExpr5 ],
2176
+
2177
+ [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,
2178
+ makePredicateExpr ],
2179
+
2180
+ [ XPathPrimaryExpr, [ XPathVariableReference ], 33,
2181
+ passExpr ],
2182
+ [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,
2183
+ makePrimaryExpr ],
2184
+ [ XPathPrimaryExpr, [ XPathLiteral ], 30,
2185
+ passExpr ],
2186
+ [ XPathPrimaryExpr, [ XPathNumber ], 30,
2187
+ passExpr ],
2188
+ [ XPathPrimaryExpr, [ XPathFunctionCall ], 31,
2189
+ passExpr ],
2190
+
2191
+ [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
2192
+ makeFunctionCallExpr1 ],
2193
+ [ XPathFunctionCall,
2194
+ [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,
2195
+ TOK_PARENC ], -1,
2196
+ makeFunctionCallExpr2 ],
2197
+ [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,
2198
+ makeArgumentExpr ],
2199
+
2200
+ [ XPathUnionExpr, [ XPathPathExpr ], 20,
2201
+ passExpr ],
2202
+ [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,
2203
+ makeUnionExpr ],
2204
+
2205
+ [ XPathPathExpr, [ XPathLocationPath ], 20,
2206
+ passExpr ],
2207
+ [ XPathPathExpr, [ XPathFilterExpr ], 19,
2208
+ passExpr ],
2209
+ [ XPathPathExpr,
2210
+ [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 19,
2211
+ makePathExpr1 ],
2212
+ [ XPathPathExpr,
2213
+ [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 19,
2214
+ makePathExpr2 ],
2215
+
2216
+ [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 31,
2217
+ makeFilterExpr ],
2218
+
2219
+ [ XPathExpr, [ XPathPrimaryExpr ], 16,
2220
+ passExpr ],
2221
+ [ XPathExpr, [ XPathUnionExpr ], 16,
2222
+ passExpr ],
2223
+
2224
+ [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,
2225
+ makeUnaryMinusExpr ],
2226
+
2227
+ [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,
2228
+ makeBinaryExpr ],
2229
+ [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,
2230
+ makeBinaryExpr ],
2231
+
2232
+ [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,
2233
+ makeBinaryExpr ],
2234
+ [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,
2235
+ makeBinaryExpr ],
2236
+
2237
+ [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,
2238
+ makeBinaryExpr ],
2239
+ [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,
2240
+ makeBinaryExpr ],
2241
+ [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,
2242
+ makeBinaryExpr ],
2243
+ [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,
2244
+ makeBinaryExpr ],
2245
+
2246
+ [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,
2247
+ makeBinaryExpr, ASSOC_LEFT ],
2248
+ [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,
2249
+ makeBinaryExpr, ASSOC_LEFT ],
2250
+
2251
+ [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,
2252
+ makeBinaryExpr, ASSOC_LEFT ],
2253
+ [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,
2254
+ makeBinaryExpr, ASSOC_LEFT ],
2255
+ [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,
2256
+ makeBinaryExpr, ASSOC_LEFT ],
2257
+
2258
+ [ XPathLiteral, [ TOK_LITERALQ ], -1,
2259
+ makeLiteralExpr ],
2260
+ [ XPathLiteral, [ TOK_LITERALQQ ], -1,
2261
+ makeLiteralExpr ],
2262
+
2263
+ [ XPathNumber, [ TOK_NUMBER ], -1,
2264
+ makeNumberExpr ],
2265
+
2266
+ [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,
2267
+ makeVariableReference ]
2268
+ ];
2269
+
2270
+ // That function computes some optimizations of the above data
2271
+ // structures and will be called right here. It merely takes the
2272
+ // counter variables out of the global scope.
2273
+
2274
+ var xpathRules = [];
2275
+
2276
+ function xpathParseInit() {
2277
+ if (xpathRules.length) {
2278
+ return;
2279
+ }
2280
+
2281
+ // Some simple optimizations for the xpath expression parser: sort
2282
+ // grammar rules descending by length, so that the longest match is
2283
+ // first found.
2284
+
2285
+ xpathGrammarRules.sort(function(a,b) {
2286
+ var la = a[1].length;
2287
+ var lb = b[1].length;
2288
+ if (la < lb) {
2289
+ return 1;
2290
+ } else if (la > lb) {
2291
+ return -1;
2292
+ } else {
2293
+ return 0;
2294
+ }
2295
+ });
2296
+
2297
+ var k = 1;
2298
+ for (var i = 0; i < xpathNonTerminals.length; ++i) {
2299
+ xpathNonTerminals[i].key = k++;
2300
+ }
2301
+
2302
+ for (i = 0; i < xpathTokenRules.length; ++i) {
2303
+ xpathTokenRules[i].key = k++;
2304
+ }
2305
+
2306
+ xpathLog('XPath parse INIT: ' + k + ' rules');
2307
+
2308
+ // Another slight optimization: sort the rules into bins according
2309
+ // to the last element (observing quantifiers), so we can restrict
2310
+ // the match against the stack to the subest of rules that match the
2311
+ // top of the stack.
2312
+ //
2313
+ // TODO(mesch): What we actually want is to compute states as in
2314
+ // bison, so that we don't have to do any explicit and iterated
2315
+ // match against the stack.
2316
+
2317
+ function push_(array, position, element) {
2318
+ if (!array[position]) {
2319
+ array[position] = [];
2320
+ }
2321
+ array[position].push(element);
2322
+ }
2323
+
2324
+ for (i = 0; i < xpathGrammarRules.length; ++i) {
2325
+ var rule = xpathGrammarRules[i];
2326
+ var pattern = rule[1];
2327
+
2328
+ for (var j = pattern.length - 1; j >= 0; --j) {
2329
+ if (pattern[j] == Q_1M) {
2330
+ push_(xpathRules, pattern[j-1].key, rule);
2331
+ break;
2332
+
2333
+ } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
2334
+ push_(xpathRules, pattern[j-1].key, rule);
2335
+ --j;
2336
+
2337
+ } else {
2338
+ push_(xpathRules, pattern[j].key, rule);
2339
+ break;
2340
+ }
2341
+ }
2342
+ }
2343
+
2344
+ xpathLog('XPath parse INIT: ' + xpathRules.length + ' rule bins');
2345
+
2346
+ var sum = 0;
2347
+ mapExec(xpathRules, function(i) {
2348
+ if (i) {
2349
+ sum += i.length;
2350
+ }
2351
+ });
2352
+
2353
+ xpathLog('XPath parse INIT: ' + (sum / xpathRules.length) +
2354
+ ' average bin size');
2355
+ }
2356
+
2357
+ // Local utility functions that are used by the lexer or parser.
2358
+
2359
+ function xpathCollectDescendants(nodelist, node, opt_tagName) {
2360
+ if (opt_tagName && node.getElementsByTagName) {
2361
+ copyArray(nodelist, node.getElementsByTagName(opt_tagName));
2362
+ return;
2363
+ }
2364
+ for (var n = node.firstChild; n; n = n.nextSibling) {
2365
+ nodelist.push(n);
2366
+ xpathCollectDescendants(nodelist, n);
2367
+ }
2368
+ }
2369
+
2370
+ /**
2371
+ * DGF - extract a tag name suitable for getElementsByTagName
2372
+ *
2373
+ * @param nodetest the node test
2374
+ * @param ignoreNonElementNodesForNTA if true, the node list returned when
2375
+ * evaluating "node()" will not contain
2376
+ * non-element nodes. This can boost
2377
+ * performance. This is false by default.
2378
+ */
2379
+ function xpathExtractTagNameFromNodeTest(nodetest, ignoreNonElementNodesForNTA) {
2380
+ if (nodetest instanceof NodeTestName) {
2381
+ return nodetest.name;
2382
+ }
2383
+ if ((ignoreNonElementNodesForNTA && nodetest instanceof NodeTestAny) ||
2384
+ nodetest instanceof NodeTestElementOrAttribute) {
2385
+ return "*";
2386
+ }
2387
+ }
2388
+
2389
+ function xpathCollectDescendantsReverse(nodelist, node) {
2390
+ for (var n = node.lastChild; n; n = n.previousSibling) {
2391
+ nodelist.push(n);
2392
+ xpathCollectDescendantsReverse(nodelist, n);
2393
+ }
2394
+ }
2395
+
2396
+
2397
+ // The entry point for the library: match an expression against a DOM
2398
+ // node. Returns an XPath value.
2399
+ function xpathDomEval(expr, node) {
2400
+ var expr1 = xpathParse(expr);
2401
+ var ret = expr1.evaluate(new ExprContext(node));
2402
+ return ret;
2403
+ }
2404
+
2405
+ // Utility function to sort a list of nodes. Used by xsltSort() and
2406
+ // nxslSelect().
2407
+ function xpathSort(input, sort) {
2408
+ if (sort.length == 0) {
2409
+ return;
2410
+ }
2411
+
2412
+ var sortlist = [];
2413
+
2414
+ for (var i = 0; i < input.contextSize(); ++i) {
2415
+ var node = input.nodelist[i];
2416
+ var sortitem = { node: node, key: [] };
2417
+ var context = input.clone(node, 0, [ node ]);
2418
+
2419
+ for (var j = 0; j < sort.length; ++j) {
2420
+ var s = sort[j];
2421
+ var value = s.expr.evaluate(context);
2422
+
2423
+ var evalue;
2424
+ if (s.type == 'text') {
2425
+ evalue = value.stringValue();
2426
+ } else if (s.type == 'number') {
2427
+ evalue = value.numberValue();
2428
+ }
2429
+ sortitem.key.push({ value: evalue, order: s.order });
2430
+ }
2431
+
2432
+ // Make the sort stable by adding a lowest priority sort by
2433
+ // id. This is very convenient and furthermore required by the
2434
+ // spec ([XSLT] - Section 10 Sorting).
2435
+ sortitem.key.push({ value: i, order: 'ascending' });
2436
+
2437
+ sortlist.push(sortitem);
2438
+ }
2439
+
2440
+ sortlist.sort(xpathSortByKey);
2441
+
2442
+ var nodes = [];
2443
+ for (var i = 0; i < sortlist.length; ++i) {
2444
+ nodes.push(sortlist[i].node);
2445
+ }
2446
+ input.nodelist = nodes;
2447
+ input.setNode(0);
2448
+ }
2449
+
2450
+
2451
+ // Sorts by all order criteria defined. According to the JavaScript
2452
+ // spec ([ECMA] Section 11.8.5), the compare operators compare strings
2453
+ // as strings and numbers as numbers.
2454
+ //
2455
+ // NOTE: In browsers which do not follow the spec, this breaks only in
2456
+ // the case that numbers should be sorted as strings, which is very
2457
+ // uncommon.
2458
+ function xpathSortByKey(v1, v2) {
2459
+ // NOTE: Sort key vectors of different length never occur in
2460
+ // xsltSort.
2461
+
2462
+ for (var i = 0; i < v1.key.length; ++i) {
2463
+ var o = v1.key[i].order == 'descending' ? -1 : 1;
2464
+ if (v1.key[i].value > v2.key[i].value) {
2465
+ return +1 * o;
2466
+ } else if (v1.key[i].value < v2.key[i].value) {
2467
+ return -1 * o;
2468
+ }
2469
+ }
2470
+
2471
+ return 0;
2472
+ }
2473
+
2474
+
2475
+ // Parses and then evaluates the given XPath expression in the given
2476
+ // input context. Notice that parsed xpath expressions are cached.
2477
+ function xpathEval(select, context) {
2478
+ var expr = xpathParse(select);
2479
+ var ret = expr.evaluate(context);
2480
+ return ret;
2481
+ }