Project

General

Profile

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