root/trunk/website/soundings/includes/xpath.js @ 38

Revision 5, 69.4 kB (checked in by DanWilson, 17 years ago)

Initial Commit Of ModelGlue? Website (upgrade to blogcfc 511)

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