PageRenderTime 104ms CodeModel.GetById 10ms app.highlight 79ms RepoModel.GetById 1ms app.codeStats 1ms

/testing/selenium-core/xpath/xpath.js

http://datanucleus-appengine.googlecode.com/
JavaScript | 2450 lines | 1776 code | 354 blank | 320 comment | 441 complexity | 380ca59ea83ca2115ebf3101c89cd4b5 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// Copyright 2005 Google Inc.
   2// All Rights Reserved
   3//
   4// An XPath parser and evaluator written in JavaScript. The
   5// implementation is complete except for functions handling
   6// namespaces.
   7//
   8// Reference: [XPATH] XPath Specification
   9// <http://www.w3.org/TR/1999/REC-xpath-19991116>.
  10//
  11//
  12// The API of the parser has several parts:
  13//
  14// 1. The parser function xpathParse() that takes a string and returns
  15// an expession object.
  16//
  17// 2. The expression object that has an evaluate() method to evaluate the
  18// XPath expression it represents. (It is actually a hierarchy of
  19// objects that resembles the parse tree, but an application will call
  20// evaluate() only on the top node of this hierarchy.)
  21//
  22// 3. The context object that is passed as an argument to the evaluate()
  23// method, which represents the DOM context in which the expression is
  24// evaluated.
  25//
  26// 4. The value object that is returned from evaluate() and represents
  27// values of the different types that are defined by XPath (number,
  28// string, boolean, and node-set), and allows to convert between them.
  29//
  30// These parts are near the top of the file, the functions and data
  31// that are used internally follow after them.
  32//
  33//
  34// Author: Steffen Meschkat <mesch@google.com>
  35
  36
  37// The entry point for the parser.
  38//
  39// @param expr a string that contains an XPath expression.
  40// @return an expression object that can be evaluated with an
  41// expression context.
  42
  43function xpathParse(expr) {
  44  xpathLog('parse ' + expr);
  45  xpathParseInit();
  46
  47  var cached = xpathCacheLookup(expr);
  48  if (cached) {
  49    xpathLog(' ... cached');
  50    return cached;
  51  }
  52
  53  // Optimize for a few common cases: simple attribute node tests
  54  // (@id), simple element node tests (page), variable references
  55  // ($address), numbers (4), multi-step path expressions where each
  56  // step is a plain element node test
  57  // (page/overlay/locations/location).
  58
  59  if (expr.match(/^(\$|@)?\w+$/i)) {
  60    var ret = makeSimpleExpr(expr);
  61    xpathParseCache[expr] = ret;
  62    xpathLog(' ... simple');
  63    return ret;
  64  }
  65
  66  if (expr.match(/^\w+(\/\w+)*$/i)) {
  67    var ret = makeSimpleExpr2(expr);
  68    xpathParseCache[expr] = ret;
  69    xpathLog(' ... simple 2');
  70    return ret;
  71  }
  72
  73  var cachekey = expr; // expr is modified during parse
  74
  75  var stack = [];
  76  var ahead = null;
  77  var previous = null;
  78  var done = false;
  79
  80  var parse_count = 0;
  81  var lexer_count = 0;
  82  var reduce_count = 0;
  83
  84  while (!done) {
  85    parse_count++;
  86    expr = expr.replace(/^\s*/, '');
  87    previous = ahead;
  88    ahead = null;
  89
  90    var rule = null;
  91    var match = '';
  92    for (var i = 0; i < xpathTokenRules.length; ++i) {
  93      var result = xpathTokenRules[i].re.exec(expr);
  94      lexer_count++;
  95      if (result && result.length > 0 && result[0].length > match.length) {
  96        rule = xpathTokenRules[i];
  97        match = result[0];
  98        break;
  99      }
 100    }
 101
 102    // Special case: allow operator keywords to be element and
 103    // variable names.
 104
 105    // NOTE(mesch): The parser resolves conflicts by looking ahead,
 106    // and this is the only case where we look back to
 107    // disambiguate. So this is indeed something different, and
 108    // looking back is usually done in the lexer (via states in the
 109    // general case, called "start conditions" in flex(1)). Also,the
 110    // conflict resolution in the parser is not as robust as it could
 111    // be, so I'd like to keep as much off the parser as possible (all
 112    // these precedence values should be computed from the grammar
 113    // rules and possibly associativity declarations, as in bison(1),
 114    // and not explicitly set.
 115
 116    if (rule &&
 117        (rule == TOK_DIV ||
 118         rule == TOK_MOD ||
 119         rule == TOK_AND ||
 120         rule == TOK_OR) &&
 121        (!previous ||
 122         previous.tag == TOK_AT ||
 123         previous.tag == TOK_DSLASH ||
 124         previous.tag == TOK_SLASH ||
 125         previous.tag == TOK_AXIS ||
 126         previous.tag == TOK_DOLLAR)) {
 127      rule = TOK_QNAME;
 128    }
 129
 130    if (rule) {
 131      expr = expr.substr(match.length);
 132      xpathLog('token: ' + match + ' -- ' + rule.label);
 133      ahead = {
 134        tag: rule,
 135        match: match,
 136        prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compiler
 137        expr: makeTokenExpr(match)
 138      };
 139
 140    } else {
 141      xpathLog('DONE');
 142      done = true;
 143    }
 144
 145    while (xpathReduce(stack, ahead)) {
 146      reduce_count++;
 147      xpathLog('stack: ' + stackToString(stack));
 148    }
 149  }
 150
 151  xpathLog('stack: ' + stackToString(stack));
 152
 153  // DGF any valid XPath should "reduce" to a single Expr token
 154  if (stack.length != 1) {
 155    throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
 156  }
 157
 158  var result = stack[0].expr;
 159  xpathParseCache[cachekey] = result;
 160
 161  xpathLog('XPath parse: ' + parse_count + ' / ' +
 162           lexer_count + ' / ' + reduce_count);
 163
 164  return result;
 165}
 166
 167var xpathParseCache = {};
 168
 169function xpathCacheLookup(expr) {
 170  return xpathParseCache[expr];
 171}
 172
 173/*DGF xpathReduce is where the magic happens in this parser.
 174Skim down to the bottom of this file to find the table of 
 175grammatical rules and precedence numbers, "The productions of the grammar".
 176
 177The idea here
 178is that we want to take a stack of tokens and apply
 179grammatical rules to them, "reducing" them to higher-level
 180tokens.  Ultimately, any valid XPath should reduce to exactly one
 181"Expr" token.
 182
 183Reduce too early or too late and you'll have two tokens that can't reduce
 184to single Expr.  For example, you may hastily reduce a qname that
 185should name a function, incorrectly treating it as a tag name.
 186Or you may reduce too late, accidentally reducing the last part of the
 187XPath into a top-level "Expr" that won't reduce with earlier parts of
 188the XPath.
 189
 190A "cand" is a grammatical rule candidate, with a given precedence
 191number.  "ahead" is the upcoming token, which also has a precedence
 192number.  If the token has a higher precedence number than
 193the rule candidate, we'll "shift" the token onto the token stack,
 194instead of immediately applying the rule candidate.
 195
 196Some tokens have left associativity, in which case we shift when they
 197have LOWER precedence than the candidate.
 198*/
 199function xpathReduce(stack, ahead) {
 200  var cand = null;
 201
 202  if (stack.length > 0) {
 203    var top = stack[stack.length-1];
 204    var ruleset = xpathRules[top.tag.key];
 205
 206    if (ruleset) {
 207      for (var i = 0; i < ruleset.length; ++i) {
 208        var rule = ruleset[i];
 209        var match = xpathMatchStack(stack, rule[1]);
 210        if (match.length) {
 211          cand = {
 212            tag: rule[0],
 213            rule: rule,
 214            match: match
 215          };
 216          cand.prec = xpathGrammarPrecedence(cand);
 217          break;
 218        }
 219      }
 220    }
 221  }
 222
 223  var ret;
 224  if (cand && (!ahead || cand.prec > ahead.prec ||
 225               (ahead.tag.left && cand.prec >= ahead.prec))) {
 226    for (var i = 0; i < cand.match.matchlength; ++i) {
 227      stack.pop();
 228    }
 229
 230    xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
 231             ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
 232                          (ahead.tag.left ? ' left' : '')
 233                          : ' none '));
 234
 235    var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
 236    xpathLog('going to apply ' + cand.rule[3].toString());
 237    cand.expr = cand.rule[3].apply(null, matchexpr);
 238
 239    stack.push(cand);
 240    ret = true;
 241
 242  } else {
 243    if (ahead) {
 244      xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
 245               (ahead.tag.left ? ' left' : '') +
 246               ' over ' + (cand ? cand.tag.label + ' ' +
 247                           cand.prec : ' none'));
 248      stack.push(ahead);
 249    }
 250    ret = false;
 251  }
 252  return ret;
 253}
 254
 255function xpathMatchStack(stack, pattern) {
 256
 257  // NOTE(mesch): The stack matches for variable cardinality are
 258  // greedy but don't do backtracking. This would be an issue only
 259  // with rules of the form A* A, i.e. with an element with variable
 260  // cardinality followed by the same element. Since that doesn't
 261  // occur in the grammar at hand, all matches on the stack are
 262  // unambiguous.
 263
 264  var S = stack.length;
 265  var P = pattern.length;
 266  var p, s;
 267  var match = [];
 268  match.matchlength = 0;
 269  var ds = 0;
 270  for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
 271    ds = 0;
 272    var qmatch = [];
 273    if (pattern[p] == Q_MM) {
 274      p -= 1;
 275      match.push(qmatch);
 276      while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
 277        qmatch.push(stack[s - ds]);
 278        ds += 1;
 279        match.matchlength += 1;
 280      }
 281
 282    } else if (pattern[p] == Q_01) {
 283      p -= 1;
 284      match.push(qmatch);
 285      while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
 286        qmatch.push(stack[s - ds]);
 287        ds += 1;
 288        match.matchlength += 1;
 289      }
 290
 291    } else if (pattern[p] == Q_1M) {
 292      p -= 1;
 293      match.push(qmatch);
 294      if (stack[s].tag == pattern[p]) {
 295        while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
 296          qmatch.push(stack[s - ds]);
 297          ds += 1;
 298          match.matchlength += 1;
 299        }
 300      } else {
 301        return [];
 302      }
 303
 304    } else if (stack[s].tag == pattern[p]) {
 305      match.push(stack[s]);
 306      ds += 1;
 307      match.matchlength += 1;
 308
 309    } else {
 310      return [];
 311    }
 312
 313    reverseInplace(qmatch);
 314    qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
 315  }
 316
 317  reverseInplace(match);
 318
 319  if (p == -1) {
 320    return match;
 321
 322  } else {
 323    return [];
 324  }
 325}
 326
 327function xpathTokenPrecedence(tag) {
 328  return tag.prec || 2;
 329}
 330
 331function xpathGrammarPrecedence(frame) {
 332  var ret = 0;
 333
 334  if (frame.rule) { /* normal reduce */
 335    if (frame.rule.length >= 3 && frame.rule[2] >= 0) {
 336      ret = frame.rule[2];
 337
 338    } else {
 339      for (var i = 0; i < frame.rule[1].length; ++i) {
 340        var p = xpathTokenPrecedence(frame.rule[1][i]);
 341        ret = Math.max(ret, p);
 342      }
 343    }
 344  } else if (frame.tag) { /* TOKEN match */
 345    ret = xpathTokenPrecedence(frame.tag);
 346
 347  } else if (frame.length) { /* Q_ match */
 348    for (var j = 0; j < frame.length; ++j) {
 349      var p = xpathGrammarPrecedence(frame[j]);
 350      ret = Math.max(ret, p);
 351    }
 352  }
 353
 354  return ret;
 355}
 356
 357function stackToString(stack) {
 358  var ret = '';
 359  for (var i = 0; i < stack.length; ++i) {
 360    if (ret) {
 361      ret += '\n';
 362    }
 363    ret += stack[i].tag.label;
 364  }
 365  return ret;
 366}
 367
 368
 369// XPath expression evaluation context. An XPath context consists of a
 370// DOM node, a list of DOM nodes that contains this node, a number
 371// that represents the position of the single node in the list, and a
 372// current set of variable bindings. (See XPath spec.)
 373//
 374// The interface of the expression context:
 375//
 376//   Constructor -- gets the node, its position, the node set it
 377//   belongs to, and a parent context as arguments. The parent context
 378//   is used to implement scoping rules for variables: if a variable
 379//   is not found in the current context, it is looked for in the
 380//   parent context, recursively. Except for node, all arguments have
 381//   default values: default position is 0, default node set is the
 382//   set that contains only the node, and the default parent is null.
 383//
 384//     Notice that position starts at 0 at the outside interface;
 385//     inside XPath expressions this shows up as position()=1.
 386//
 387//   clone() -- creates a new context with the current context as
 388//   parent. If passed as argument to clone(), the new context has a
 389//   different node, position, or node set. What is not passed is
 390//   inherited from the cloned context.
 391//
 392//   setVariable(name, expr) -- binds given XPath expression to the
 393//   name.
 394//
 395//   getVariable(name) -- what the name says.
 396//
 397//   setNode(position) -- sets the context to the node at the given
 398//   position. Needed to implement scoping rules for variables in
 399//   XPath. (A variable is visible to all subsequent siblings, not
 400//   only to its children.)
 401//
 402//   set/isCaseInsensitive -- specifies whether node name tests should
 403//   be case sensitive.  If you're executing xpaths against a regular
 404//   HTML DOM, you probably don't want case-sensitivity, because
 405//   browsers tend to disagree about whether elements & attributes
 406//   should be upper/lower case.  If you're running xpaths in an
 407//   XSLT instance, you probably DO want case sensitivity, as per the
 408//   XSL spec.
 409
 410function ExprContext(node, opt_position, opt_nodelist, opt_parent,
 411  opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
 412  opt_returnOnFirstMatch)
 413{
 414  this.node = node;
 415  this.position = opt_position || 0;
 416  this.nodelist = opt_nodelist || [ node ];
 417  this.variables = {};
 418  this.parent = opt_parent || null;
 419  this.caseInsensitive = opt_caseInsensitive || false;
 420  this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
 421  this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
 422  if (opt_parent) {
 423    this.root = opt_parent.root;
 424  } else if (this.node.nodeType == DOM_DOCUMENT_NODE) {
 425    // NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
 426    // document is null. Our root, however is the document that we are
 427    // processing, so the initial context is created from its document
 428    // node, which case we must handle here explcitly.
 429    this.root = node;
 430  } else {
 431    this.root = node.ownerDocument;
 432  }
 433}
 434
 435ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
 436  return new ExprContext(
 437      opt_node || this.node,
 438      typeof opt_position != 'undefined' ? opt_position : this.position,
 439      opt_nodelist || this.nodelist, this, this.caseInsensitive,
 440      this.ignoreAttributesWithoutValue, this.returnOnFirstMatch);
 441};
 442
 443ExprContext.prototype.setVariable = function(name, value) {
 444  if (value instanceof StringValue || value instanceof BooleanValue || 
 445    value instanceof NumberValue || value instanceof NodeSetValue) {
 446    this.variables[name] = value;
 447    return;
 448  }
 449  if ('true' === value) {
 450    this.variables[name] = new BooleanValue(true);
 451  } else if ('false' === value) {
 452    this.variables[name] = new BooleanValue(false);
 453  } else if (TOK_NUMBER.re.test(value)) {
 454    this.variables[name] = new NumberValue(value);
 455  } else {
 456    // DGF What if it's null?
 457    this.variables[name] = new StringValue(value);
 458  }
 459};
 460
 461ExprContext.prototype.getVariable = function(name) {
 462  if (typeof this.variables[name] != 'undefined') {
 463    return this.variables[name];
 464
 465  } else if (this.parent) {
 466    return this.parent.getVariable(name);
 467
 468  } else {
 469    return null;
 470  }
 471};
 472
 473ExprContext.prototype.setNode = function(position) {
 474  this.node = this.nodelist[position];
 475  this.position = position;
 476};
 477
 478ExprContext.prototype.contextSize = function() {
 479  return this.nodelist.length;
 480};
 481
 482ExprContext.prototype.isCaseInsensitive = function() {
 483  return this.caseInsensitive;
 484};
 485
 486ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
 487  return this.caseInsensitive = caseInsensitive;
 488};
 489
 490ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
 491  return this.ignoreAttributesWithoutValue;
 492};
 493
 494ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
 495  return this.ignoreAttributesWithoutValue = ignore;
 496};
 497
 498ExprContext.prototype.isReturnOnFirstMatch = function() {
 499  return this.returnOnFirstMatch;
 500};
 501
 502ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
 503  return this.returnOnFirstMatch = returnOnFirstMatch;
 504};
 505
 506// XPath expression values. They are what XPath expressions evaluate
 507// to. Strangely, the different value types are not specified in the
 508// XPath syntax, but only in the semantics, so they don't show up as
 509// nonterminals in the grammar. Yet, some expressions are required to
 510// evaluate to particular types, and not every type can be coerced
 511// into every other type. Although the types of XPath values are
 512// similar to the types present in JavaScript, the type coercion rules
 513// are a bit peculiar, so we explicitly model XPath types instead of
 514// mapping them onto JavaScript types. (See XPath spec.)
 515//
 516// The four types are:
 517//
 518//   StringValue
 519//
 520//   NumberValue
 521//
 522//   BooleanValue
 523//
 524//   NodeSetValue
 525//
 526// The common interface of the value classes consists of methods that
 527// implement the XPath type coercion rules:
 528//
 529//   stringValue() -- returns the value as a JavaScript String,
 530//
 531//   numberValue() -- returns the value as a JavaScript Number,
 532//
 533//   booleanValue() -- returns the value as a JavaScript Boolean,
 534//
 535//   nodeSetValue() -- returns the value as a JavaScript Array of DOM
 536//   Node objects.
 537//
 538
 539function StringValue(value) {
 540  this.value = value;
 541  this.type = 'string';
 542}
 543
 544StringValue.prototype.stringValue = function() {
 545  return this.value;
 546}
 547
 548StringValue.prototype.booleanValue = function() {
 549  return this.value.length > 0;
 550}
 551
 552StringValue.prototype.numberValue = function() {
 553  return this.value - 0;
 554}
 555
 556StringValue.prototype.nodeSetValue = function() {
 557  throw this;
 558}
 559
 560function BooleanValue(value) {
 561  this.value = value;
 562  this.type = 'boolean';
 563}
 564
 565BooleanValue.prototype.stringValue = function() {
 566  return '' + this.value;
 567}
 568
 569BooleanValue.prototype.booleanValue = function() {
 570  return this.value;
 571}
 572
 573BooleanValue.prototype.numberValue = function() {
 574  return this.value ? 1 : 0;
 575}
 576
 577BooleanValue.prototype.nodeSetValue = function() {
 578  throw this;
 579}
 580
 581function NumberValue(value) {
 582  this.value = value;
 583  this.type = 'number';
 584}
 585
 586NumberValue.prototype.stringValue = function() {
 587  return '' + this.value;
 588}
 589
 590NumberValue.prototype.booleanValue = function() {
 591  return !!this.value;
 592}
 593
 594NumberValue.prototype.numberValue = function() {
 595  return this.value - 0;
 596}
 597
 598NumberValue.prototype.nodeSetValue = function() {
 599  throw this;
 600}
 601
 602function NodeSetValue(value) {
 603  this.value = value;
 604  this.type = 'node-set';
 605}
 606
 607NodeSetValue.prototype.stringValue = function() {
 608  if (this.value.length == 0) {
 609    return '';
 610  } else {
 611    return xmlValue(this.value[0]);
 612  }
 613}
 614
 615NodeSetValue.prototype.booleanValue = function() {
 616  return this.value.length > 0;
 617}
 618
 619NodeSetValue.prototype.numberValue = function() {
 620  return this.stringValue() - 0;
 621}
 622
 623NodeSetValue.prototype.nodeSetValue = function() {
 624  return this.value;
 625};
 626
 627// XPath expressions. They are used as nodes in the parse tree and
 628// possess an evaluate() method to compute an XPath value given an XPath
 629// context. Expressions are returned from the parser. Teh set of
 630// expression classes closely mirrors the set of non terminal symbols
 631// in the grammar. Every non trivial nonterminal symbol has a
 632// corresponding expression class.
 633//
 634// The common expression interface consists of the following methods:
 635//
 636// evaluate(context) -- evaluates the expression, returns a value.
 637//
 638// toString() -- returns the XPath text representation of the
 639// expression (defined in xsltdebug.js).
 640//
 641// parseTree(indent) -- returns a parse tree representation of the
 642// expression (defined in xsltdebug.js).
 643
 644function TokenExpr(m) {
 645  this.value = m;
 646}
 647
 648TokenExpr.prototype.evaluate = function() {
 649  return new StringValue(this.value);
 650};
 651
 652function LocationExpr() {
 653  this.absolute = false;
 654  this.steps = [];
 655}
 656
 657LocationExpr.prototype.appendStep = function(s) {
 658  var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
 659  if (combinedStep) {
 660    this.steps[this.steps.length-1] = combinedStep;
 661  } else {
 662    this.steps.push(s);
 663  }
 664}
 665
 666LocationExpr.prototype.prependStep = function(s) {
 667  var combinedStep = this._combineSteps(s, this.steps[0]);
 668  if (combinedStep) {
 669    this.steps[0] = combinedStep;
 670  } else {
 671    this.steps.unshift(s);
 672  }
 673};
 674
 675// DGF try to combine two steps into one step (perf enhancement)
 676LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
 677  if (!prevStep) return null;
 678  if (!nextStep) return null;
 679  var hasPredicates = (prevStep.predicates && prevStep.predicates.length > 0);
 680  if (prevStep.nodetest instanceof NodeTestAny && !hasPredicates) {
 681    // maybe suitable to be combined
 682    if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
 683      if (nextStep.axis == xpathAxis.CHILD) {
 684        // HBC - commenting out, because this is not a valid reduction
 685        //nextStep.axis = xpathAxis.DESCENDANT;
 686        //return nextStep;
 687      } else if (nextStep.axis == xpathAxis.SELF) {
 688        nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
 689        return nextStep;
 690      }
 691    } else if (prevStep.axis == xpathAxis.DESCENDANT) {
 692      if (nextStep.axis == xpathAxis.SELF) {
 693        nextStep.axis = xpathAxis.DESCENDANT;
 694        return nextStep;
 695      }
 696    }
 697  }
 698  return null;
 699}
 700
 701LocationExpr.prototype.evaluate = function(ctx) {
 702  var start;
 703  if (this.absolute) {
 704    start = ctx.root;
 705
 706  } else {
 707    start = ctx.node;
 708  }
 709
 710  var nodes = [];
 711  xPathStep(nodes, this.steps, 0, start, ctx);
 712  return new NodeSetValue(nodes);
 713};
 714
 715function xPathStep(nodes, steps, step, input, ctx) {
 716  var s = steps[step];
 717  var ctx2 = ctx.clone(input);
 718  
 719  if (ctx.returnOnFirstMatch && !s.hasPositionalPredicate) {
 720    var nodelist = s.evaluate(ctx2).nodeSetValue();
 721    // the predicates were not processed in the last evaluate(), so that we can
 722    // process them here with the returnOnFirstMatch optimization. We do a
 723    // depth-first grab at any nodes that pass the predicate tests. There is no
 724    // way to optimize when predicates contain positional selectors, including
 725    // indexes or uses of the last() or position() functions, because they
 726    // typically require the entire nodelist for context. Process without
 727    // optimization if we encounter such selectors.
 728    var nLength = nodelist.length;
 729    var pLength = s.predicate.length;
 730    nodelistLoop:
 731    for (var i = 0; i < nLength; ++i) {
 732      var n = nodelist[i];
 733      for (var j = 0; j < pLength; ++j) {
 734        if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
 735          continue nodelistLoop;
 736        }
 737      }
 738      // n survived the predicate tests!
 739      if (step == steps.length - 1) {
 740        nodes.push(n);
 741      }
 742      else {
 743        xPathStep(nodes, steps, step + 1, n, ctx);
 744      }
 745      if (nodes.length > 0) {
 746        break;
 747      }
 748    }
 749  }
 750  else {
 751    // set returnOnFirstMatch to false for the cloned ExprContext, because
 752    // behavior in StepExpr.prototype.evaluate is driven off its value. Note
 753    // that the original context may still have true for this value.
 754    ctx2.returnOnFirstMatch = false;
 755    var nodelist = s.evaluate(ctx2).nodeSetValue();
 756    for (var i = 0; i < nodelist.length; ++i) {
 757      if (step == steps.length - 1) {
 758        nodes.push(nodelist[i]);
 759      } else {
 760        xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
 761      }
 762    }
 763  }
 764}
 765
 766function StepExpr(axis, nodetest, opt_predicate) {
 767  this.axis = axis;
 768  this.nodetest = nodetest;
 769  this.predicate = opt_predicate || [];
 770  this.hasPositionalPredicate = false;
 771  for (var i = 0; i < this.predicate.length; ++i) {
 772    if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
 773      this.hasPositionalPredicate = true;
 774      break;
 775    }
 776  }
 777}
 778
 779StepExpr.prototype.appendPredicate = function(p) {
 780  this.predicate.push(p);
 781  if (!this.hasPositionalPredicate) {
 782    this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
 783  }
 784}
 785
 786StepExpr.prototype.evaluate = function(ctx) {
 787  var input = ctx.node;
 788  var nodelist = [];
 789  var skipNodeTest = false;
 790  
 791  if (this.nodetest instanceof NodeTestAny) {
 792    skipNodeTest = true;
 793  }
 794
 795  // NOTE(mesch): When this was a switch() statement, it didn't work
 796  // in Safari/2.0. Not sure why though; it resulted in the JavaScript
 797  // console output "undefined" (without any line number or so).
 798
 799  if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {
 800    nodelist.push(input);
 801    for (var n = input.parentNode; n; n = n.parentNode) {
 802      nodelist.push(n);
 803    }
 804
 805  } else if (this.axis == xpathAxis.ANCESTOR) {
 806    for (var n = input.parentNode; n; n = n.parentNode) {
 807      nodelist.push(n);
 808    }
 809
 810  } else if (this.axis == xpathAxis.ATTRIBUTE) {
 811    if (this.nodetest.name != undefined) {
 812      // single-attribute step
 813      if (input.attributes) {
 814        if (input.attributes instanceof Array) {
 815          // probably evaluating on document created by xmlParse()
 816          copyArray(nodelist, input.attributes);
 817        }
 818        else {
 819          if (this.nodetest.name == 'style') {
 820            var value = input.getAttribute('style');
 821            if (value && typeof(value) != 'string') {
 822              // this is the case where indexing into the attributes array
 823              // doesn't give us the attribute node in IE - we create our own
 824              // node instead
 825              nodelist.push(XNode.create(DOM_ATTRIBUTE_NODE, 'style',
 826                value.cssText, document));
 827            }
 828            else {
 829              nodelist.push(input.attributes[this.nodetest.name]);
 830            }
 831          }
 832          else {
 833            nodelist.push(input.attributes[this.nodetest.name]);
 834          }
 835        }
 836      }
 837    }
 838    else {
 839      // all-attributes step
 840      if (ctx.ignoreAttributesWithoutValue) {
 841        copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
 842      }
 843      else {
 844        copyArray(nodelist, input.attributes);
 845      }
 846    }
 847    
 848  } else if (this.axis == xpathAxis.CHILD) {
 849    copyArray(nodelist, input.childNodes);
 850
 851  } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
 852    if (this.nodetest.evaluate(ctx).booleanValue()) {
 853      nodelist.push(input);
 854    }
 855    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
 856    xpathCollectDescendants(nodelist, input, tagName);
 857    if (tagName) skipNodeTest = true;
 858
 859  } else if (this.axis == xpathAxis.DESCENDANT) {
 860    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
 861    xpathCollectDescendants(nodelist, input, tagName);
 862    if (tagName) skipNodeTest = true;
 863
 864  } else if (this.axis == xpathAxis.FOLLOWING) {
 865    for (var n = input; n; n = n.parentNode) {
 866      for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
 867        nodelist.push(nn);
 868        xpathCollectDescendants(nodelist, nn);
 869      }
 870    }
 871
 872  } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
 873    for (var n = input.nextSibling; n; n = n.nextSibling) {
 874      nodelist.push(n);
 875    }
 876
 877  } else if (this.axis == xpathAxis.NAMESPACE) {
 878    alert('not implemented: axis namespace');
 879
 880  } else if (this.axis == xpathAxis.PARENT) {
 881    if (input.parentNode) {
 882      nodelist.push(input.parentNode);
 883    }
 884
 885  } else if (this.axis == xpathAxis.PRECEDING) {
 886    for (var n = input; n; n = n.parentNode) {
 887      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
 888        nodelist.push(nn);
 889        xpathCollectDescendantsReverse(nodelist, nn);
 890      }
 891    }
 892
 893  } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
 894    for (var n = input.previousSibling; n; n = n.previousSibling) {
 895      nodelist.push(n);
 896    }
 897
 898  } else if (this.axis == xpathAxis.SELF) {
 899    nodelist.push(input);
 900
 901  } else {
 902    throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
 903  }
 904
 905  if (!skipNodeTest) {
 906    // process node test
 907    var nodelist0 = nodelist;
 908    nodelist = [];
 909    for (var i = 0; i < nodelist0.length; ++i) {
 910      var n = nodelist0[i];
 911      if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
 912        nodelist.push(n);
 913      }
 914    }
 915  }
 916
 917  // process predicates
 918  if (!ctx.returnOnFirstMatch) {
 919    for (var i = 0; i < this.predicate.length; ++i) {
 920      var nodelist0 = nodelist;
 921      nodelist = [];
 922      for (var ii = 0; ii < nodelist0.length; ++ii) {
 923        var n = nodelist0[ii];
 924        if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
 925          nodelist.push(n);
 926        }
 927      }
 928    }
 929  }
 930
 931  return new NodeSetValue(nodelist);
 932};
 933
 934function NodeTestAny() {
 935  this.value = new BooleanValue(true);
 936}
 937
 938NodeTestAny.prototype.evaluate = function(ctx) {
 939  return this.value;
 940};
 941
 942function NodeTestElementOrAttribute() {}
 943
 944NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
 945  return new BooleanValue(
 946      ctx.node.nodeType == DOM_ELEMENT_NODE ||
 947      ctx.node.nodeType == DOM_ATTRIBUTE_NODE);
 948}
 949
 950function NodeTestText() {}
 951
 952NodeTestText.prototype.evaluate = function(ctx) {
 953  return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
 954}
 955
 956function NodeTestComment() {}
 957
 958NodeTestComment.prototype.evaluate = function(ctx) {
 959  return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
 960}
 961
 962function NodeTestPI(target) {
 963  this.target = target;
 964}
 965
 966NodeTestPI.prototype.evaluate = function(ctx) {
 967  return new
 968  BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&
 969               (!this.target || ctx.node.nodeName == this.target));
 970}
 971
 972function NodeTestNC(nsprefix) {
 973  this.regex = new RegExp("^" + nsprefix + ":");
 974  this.nsprefix = nsprefix;
 975}
 976
 977NodeTestNC.prototype.evaluate = function(ctx) {
 978  var n = ctx.node;
 979  return new BooleanValue(this.regex.match(n.nodeName));
 980}
 981
 982function NodeTestName(name) {
 983  this.name = name;
 984  this.re = new RegExp('^' + name + '$', "i");
 985}
 986
 987NodeTestName.prototype.evaluate = function(ctx) {
 988  var n = ctx.node;
 989  if (ctx.caseInsensitive) {
 990    if (n.nodeName.length != this.name.length) return new BooleanValue(false);
 991    return new BooleanValue(this.re.test(n.nodeName));
 992  } else {
 993    return new BooleanValue(n.nodeName == this.name);
 994  }
 995}
 996
 997function PredicateExpr(expr) {
 998  this.expr = expr;
 999}
1000
1001PredicateExpr.prototype.evaluate = function(ctx) {
1002  var v = this.expr.evaluate(ctx);
1003  if (v.type == 'number') {
1004    // NOTE(mesch): Internally, position is represented starting with
1005    // 0, however in XPath position starts with 1. See functions
1006    // position() and last().
1007    return new BooleanValue(ctx.position == v.numberValue() - 1);
1008  } else {
1009    return new BooleanValue(v.booleanValue());
1010  }
1011};
1012
1013function FunctionCallExpr(name) {
1014  this.name = name;
1015  this.args = [];
1016}
1017
1018FunctionCallExpr.prototype.appendArg = function(arg) {
1019  this.args.push(arg);
1020};
1021
1022FunctionCallExpr.prototype.evaluate = function(ctx) {
1023  var fn = '' + this.name.value;
1024  var f = this.xpathfunctions[fn];
1025  if (f) {
1026    return f.call(this, ctx);
1027  } else {
1028    xpathLog('XPath NO SUCH FUNCTION ' + fn);
1029    return new BooleanValue(false);
1030  }
1031};
1032
1033FunctionCallExpr.prototype.xpathfunctions = {
1034  'last': function(ctx) {
1035    assert(this.args.length == 0);
1036    // NOTE(mesch): XPath position starts at 1.
1037    return new NumberValue(ctx.contextSize());
1038  },
1039
1040  'position': function(ctx) {
1041    assert(this.args.length == 0);
1042    // NOTE(mesch): XPath position starts at 1.
1043    return new NumberValue(ctx.position + 1);
1044  },
1045
1046  'count': function(ctx) {
1047    assert(this.args.length == 1);
1048    var v = this.args[0].evaluate(ctx);
1049    return new NumberValue(v.nodeSetValue().length);
1050  },
1051
1052  'id': function(ctx) {
1053    assert(this.args.length == 1);
1054    var e = this.args[0].evaluate(ctx);
1055    var ret = [];
1056    var ids;
1057    if (e.type == 'node-set') {
1058      ids = [];
1059      var en = e.nodeSetValue();
1060      for (var i = 0; i < en.length; ++i) {
1061        var v = xmlValue(en[i]).split(/\s+/);
1062        for (var ii = 0; ii < v.length; ++ii) {
1063          ids.push(v[ii]);
1064        }
1065      }
1066    } else {
1067      ids = e.stringValue().split(/\s+/);
1068    }
1069    var d = ctx.root;
1070    for (var i = 0; i < ids.length; ++i) {
1071      var n = d.getElementById(ids[i]);
1072      if (n) {
1073        ret.push(n);
1074      }
1075    }
1076    return new NodeSetValue(ret);
1077  },
1078
1079  'local-name': function(ctx) {
1080    alert('not implmented yet: XPath function local-name()');
1081  },
1082
1083  'namespace-uri': function(ctx) {
1084    alert('not implmented yet: XPath function namespace-uri()');
1085  },
1086
1087  'name': function(ctx) {
1088    assert(this.args.length == 1 || this.args.length == 0);
1089    var n;
1090    if (this.args.length == 0) {
1091      n = [ ctx.node ];
1092    } else {
1093      n = this.args[0].evaluate(ctx).nodeSetValue();
1094    }
1095
1096    if (n.length == 0) {
1097      return new StringValue('');
1098    } else {
1099      return new StringValue(n[0].nodeName);
1100    }
1101  },
1102
1103  'string':  function(ctx) {
1104    assert(this.args.length == 1 || this.args.length == 0);
1105    if (this.args.length == 0) {
1106      return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
1107    } else {
1108      return new StringValue(this.args[0].evaluate(ctx).stringValue());
1109    }
1110  },
1111
1112  'concat': function(ctx) {
1113    var ret = '';
1114    for (var i = 0; i < this.args.length; ++i) {
1115      ret += this.args[i].evaluate(ctx).stringValue();
1116    }
1117    return new StringValue(ret);
1118  },
1119
1120  'starts-with': function(ctx) {
1121    assert(this.args.length == 2);
1122    var s0 = this.args[0].evaluate(ctx).stringValue();
1123    var s1 = this.args[1].evaluate(ctx).stringValue();
1124    return new BooleanValue(s0.indexOf(s1) == 0);
1125  },
1126  
1127  'ends-with': function(ctx) {
1128    assert(this.args.length == 2);
1129    var s0 = this.args[0].evaluate(ctx).stringValue();
1130    var s1 = this.args[1].evaluate(ctx).stringValue();
1131    var re = new RegExp(RegExp.escape(s1) + '$');
1132    return new BooleanValue(re.test(s0));
1133  },
1134
1135  'contains': function(ctx) {
1136    assert(this.args.length == 2);
1137    var s0 = this.args[0].evaluate(ctx).stringValue();
1138    var s1 = this.args[1].evaluate(ctx).stringValue();
1139    return new BooleanValue(s0.indexOf(s1) != -1);
1140  },
1141
1142  'substring-before': function(ctx) {
1143    assert(this.args.length == 2);
1144    var s0 = this.args[0].evaluate(ctx).stringValue();
1145    var s1 = this.args[1].evaluate(ctx).stringValue();
1146    var i = s0.indexOf(s1);
1147    var ret;
1148    if (i == -1) {
1149      ret = '';
1150    } else {
1151      ret = s0.substr(0,i);
1152    }
1153    return new StringValue(ret);
1154  },
1155
1156  'substring-after': function(ctx) {
1157    assert(this.args.length == 2);
1158    var s0 = this.args[0].evaluate(ctx).stringValue();
1159    var s1 = this.args[1].evaluate(ctx).stringValue();
1160    var i = s0.indexOf(s1);
1161    var ret;
1162    if (i == -1) {
1163      ret = '';
1164    } else {
1165      ret = s0.substr(i + s1.length);
1166    }
1167    return new StringValue(ret);
1168  },
1169
1170  'substring': function(ctx) {
1171    // NOTE: XPath defines the position of the first character in a
1172    // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
1173    assert(this.args.length == 2 || this.args.length == 3);
1174    var s0 = this.args[0].evaluate(ctx).stringValue();
1175    var s1 = this.args[1].evaluate(ctx).numberValue();
1176    var ret;
1177    if (this.args.length == 2) {
1178      var i1 = Math.max(0, Math.round(s1) - 1);
1179      ret = s0.substr(i1);
1180
1181    } else {
1182      var s2 = this.args[2].evaluate(ctx).numberValue();
1183      var i0 = Math.round(s1) - 1;
1184      var i1 = Math.max(0, i0);
1185      var i2 = Math.round(s2) - Math.max(0, -i0);
1186      ret = s0.substr(i1, i2);
1187    }
1188    return new StringValue(ret);
1189  },
1190
1191  'string-length': function(ctx) {
1192    var s;
1193    if (this.args.length > 0) {
1194      s = this.args[0].evaluate(ctx).stringValue();
1195    } else {
1196      s = new NodeSetValue([ ctx.node ]).stringValue();
1197    }
1198    return new NumberValue(s.length);
1199  },
1200
1201  'normalize-space': function(ctx) {
1202    var s;
1203    if (this.args.length > 0) {
1204      s = this.args[0].evaluate(ctx).stringValue();
1205    } else {
1206      s = new NodeSetValue([ ctx.node ]).stringValue();
1207    }
1208    s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
1209    return new StringValue(s);
1210  },
1211
1212  'translate': function(ctx) {
1213    assert(this.args.length == 3);
1214    var s0 = this.args[0].evaluate(ctx).stringValue();
1215    var s1 = this.args[1].evaluate(ctx).stringValue();
1216    var s2 = this.args[2].evaluate(ctx).stringValue();
1217
1218    for (var i = 0; i < s1.length; ++i) {
1219      s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
1220    }
1221    return new StringValue(s0);
1222  },
1223  
1224  'matches': function(ctx) {
1225    assert(this.args.length >= 2);
1226    var s0 = this.args[0].evaluate(ctx).stringValue();
1227    var s1 = this.args[1].evaluate(ctx).stringValue();
1228    if (this.args.length > 2) {
1229      var s2 = this.args[2].evaluate(ctx).stringValue();
1230      if (/[^mi]/.test(s2)) {
1231        throw 'Invalid regular expression syntax: ' + s2;
1232      }
1233    }
1234    
1235    try {
1236      var re = new RegExp(s1, s2);
1237    }
1238    catch (e) {
1239      throw 'Invalid matches argument: ' + s1;
1240    }
1241    return new BooleanValue(re.test(s0));
1242  },
1243
1244  'boolean': function(ctx) {
1245    assert(this.args.length == 1);
1246    return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
1247  },
1248
1249  'not': function(ctx) {
1250    assert(this.args.length == 1);
1251    var ret = !this.args[0].evaluate(ctx).booleanValue();
1252    return new BooleanValue(ret);
1253  },
1254
1255  'true': function(ctx) {
1256    assert(this.args.length == 0);
1257    return new BooleanValue(true);
1258  },
1259
1260  'false': function(ctx) {
1261    assert(this.args.length == 0);
1262    return new BooleanValue(false);
1263  },
1264
1265  'lang': function(ctx) {
1266    assert(this.args.length == 1);
1267    var lang = this.args[0].evaluate(ctx).stringValue();
1268    var xmllang;
1269    var n = ctx.node;
1270    while (n && n != n.parentNode /* just in case ... */) {
1271      xmllang = n.getAttribute('xml:lang');
1272      if (xmllang) {
1273        break;
1274      }
1275      n = n.parentNode;
1276    }
1277    if (!xmllang) {
1278      return new BooleanValue(false);
1279    } else {
1280      var re = new RegExp('^' + lang + '$', 'i');
1281      return new BooleanValue(xmllang.match(re) ||
1282                              xmllang.replace(/_.*$/,'').match(re));
1283    }
1284  },
1285
1286  'number': function(ctx) {
1287    assert(this.args.length == 1 || this.args.length == 0);
1288
1289    if (this.args.length == 1) {
1290      return new NumberValue(this.args[0].evaluate(ctx).numberValue());
1291    } else {
1292      return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
1293    }
1294  },
1295
1296  'sum': function(ctx) {
1297    assert(this.args.length == 1);
1298    var n = this.args[0].evaluate(ctx).nodeSetValue();
1299    var sum = 0;
1300    for (var i = 0; i < n.length; ++i) {
1301      sum += xmlValue(n[i]) - 0;
1302    }
1303    return new NumberValue(sum);
1304  },
1305
1306  'floor': function(ctx) {
1307    assert(this.args.length == 1);
1308    var num = this.args[0].evaluate(ctx).numberValue();
1309    return new NumberValue(Math.floor(num));
1310  },
1311
1312  'ceiling': function(ctx) {
1313    assert(this.args.length == 1);
1314    var num = this.args[0].evaluate(ctx).numberValue();
1315    return new NumberValue(Math.ceil(num));
1316  },
1317
1318  'round': function(ctx) {
1319    assert(this.args.length == 1);
1320    var num = this.args[0].evaluate(ctx).numberValue();
1321    return new NumberValue(Math.round(num));
1322  },
1323
1324  // TODO(mesch): The following functions are custom. There is a
1325  // standard that defines how to add functions, which should be
1326  // applied here.
1327
1328  'ext-join': function(ctx) {
1329    assert(this.args.length == 2);
1330    var nodes = this.args[0].evaluate(ctx).nodeSetValue();
1331    var delim = this.args[1].evaluate(ctx).stringValue();
1332    var ret = '';
1333    for (var i = 0; i < nodes.length; ++i) {
1334      if (ret) {
1335        ret += delim;
1336      }
1337      ret += xmlValue(nodes[i]);
1338    }
1339    return new StringValue(ret);
1340  },
1341
1342  // ext-if() evaluates and returns its second argument, if the
1343  // boolean value of its first argument is true, otherwise it
1344  // evaluates and returns its third argument.
1345
1346  'ext-if': function(ctx) {
1347    assert(this.args.length == 3);
1348    if (this.args[0].evaluate(ctx).booleanValue()) {
1349      return this.args[1].evaluate(ctx);
1350    } else {
1351      return this.args[2].evaluate(ctx);
1352    }
1353  },
1354
1355  // ext-cardinal() evaluates its single argument as a number, and
1356  // returns the current node that many times. It can be used in the
1357  // select attribute to iterate over an integer range.
1358
1359  'ext-cardinal': function(ctx) {
1360    assert(this.args.length >= 1);
1361    var c = this.args[0].evaluate(ctx).numberValue();
1362    var ret = [];
1363    for (var i = 0; i < c; ++i) {
1364      ret.push(ctx.node);
1365    }
1366    return new NodeSetValue(ret);
1367  }
1368};
1369
1370function UnionExpr(expr1, expr2) {
1371  this.expr1 = expr1;
1372  this.expr2 = expr2;
1373}
1374
1375UnionExpr.prototype.evaluate = function(ctx) {
1376  var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
1377  var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
1378  var I1 = nodes1.length;
1379  for (var i2 = 0; i2 < nodes2.length; ++i2) {
1380    var n = nodes2[i2];
1381    var inBoth = false;
1382    for (var i1 = 0; i1 < I1; ++i1) {
1383      if (nodes1[i1] == n) {
1384        inBoth = true;
1385        i1 = I1; // break inner loop
1386      }
1387    }
1388    if (!inBoth) {
1389      nodes1.push(n);
1390    }
1391  }
1392  return new NodeSetValue(nodes1);
1393};
1394
1395function PathExpr(filter, rel) {
1396  this.filter = filter;
1397  this.rel = rel;
1398}
1399
1400PathExpr.prototype.evaluate = function(ctx) {
1401  // the filter expression should be evaluated in its entirety with no
1402  // optimization, as we can't backtrack to it after having moved on to
1403  // evaluating the relative location path
1404  var flag = ctx.returnOnFirstMatch;
1405  ctx.setReturnOnFirstMatch(false);
1406  var nodes = this.filter.evaluate(ctx).nodeSetValue();
1407  ctx.setReturnOnFirstMatch(flag);
1408  
1409  var nodes1 = [];
1410  if (ctx.returnOnFirstMatch) {
1411    for (var i = 0; i < nodes.length; ++i) {
1412      nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1413      if (nodes1.length > 0) {
1414        break;
1415      }
1416    }
1417    return new NodeSetValue(nodes1);
1418  }
1419  else {
1420    for (var i = 0; i < nodes.length; ++i) {
1421      var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1422      for (var ii = 0; ii < nodes0.length; ++ii) {
1423        nodes1.push(nodes0[ii]);
1424      }
1425    }
1426    return new NodeSetValue(nodes1);
1427  }
1428};
1429
1430function FilterExpr(expr, predicate) {
1431  this.expr = expr;
1432  this.predicate = predicate;
1433}
1434
1435FilterExpr.prototype.evaluate = function(ctx) {
1436  var nodes = this.expr.evaluate(ctx).nodeSetValue();
1437  for (var i = 0; i < this.predicate.length; ++i) {
1438    var nodes0 = nodes;
1439    nodes = [];
1440    for (var j = 0; j < nodes0.length; ++j) {
1441      var n = nodes0[j];
1442      if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
1443        nodes.push(n);
1444      }
1445    }
1446  }
1447
1448  return new NodeSetValue(nodes);
1449}
1450
1451function UnaryMinusExpr(expr) {
1452  this.expr = expr;
1453}
1454
1455UnaryMinusExpr.prototype.evaluate = function(ctx) {
1456  return new NumberValue(-this.expr.evaluate(ctx).numberValue());
1457};
1458
1459function BinaryExpr(expr1, op, expr2) {
1460  this.expr1 = expr1;
1461  this.expr2 = expr2;
1462  this.op = op;
1463}
1464
1465BinaryExpr.prototype.evaluate = function(ctx) {
1466  var ret;
1467  switch (this.op.value) {
1468    case 'or':
1469      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
1470                             this.expr2.evaluate(ctx).booleanValue());
1471      break;
1472
1473    case 'and':
1474      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&
1475                             this.expr2.evaluate(ctx).booleanValue());
1476      break;
1477
1478    case '+':
1479      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
1480                            this.expr2.evaluate(ctx).numberValue());
1481      break;
1482
1483    case '-':
1484      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
1485                            this.expr2.evaluate(ctx).numberValue());
1486      break;
1487
1488    case '*':
1489      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
1490                            this.expr2.evaluate(ctx).numberValue());
1491      break;
1492
1493    case 'mod':
1494      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
1495                            this.expr2.evaluate(ctx).numberValue());
1496      break;
1497
1498    case 'div':
1499      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
1500                            this.expr2.evaluate(ctx).numberValue());
1501      break;
1502
1503    case '=':
1504      ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
1505      break;
1506
1507    case '!=':
1508      ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
1509      break;
1510
1511    case '<':
1512      ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });
1513      break;
1514
1515    case '<=':
1516      ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });
1517      break;
1518
1519    case '>':
1520      ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });
1521      break;
1522
1523    case '>=':
1524      ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });
1525      break;
1526
1527    default:
1528      alert('BinaryExpr.evaluate: ' + this.op.value);
1529  }
1530  return ret;
1531};
1532
1533BinaryExpr.prototype.compare = function(ctx, cmp) {
1534  var v1 = this.expr1.evaluate(ctx);
1535  var v2 = this.expr2.evaluate(ctx);
1536
1537  var ret;
1538  if (v1.type == 'node-set' && v2.type == 'node-set') {
1539    var n1 = v1.nodeSetValue();
1540    var n2 = v2.nodeSetValue();
1541    ret = false;
1542    for (var i1 = 0; i1 < n1.length; ++i1) {
1543      for (var i2 = 0; i2 < n2.length; ++i2) {
1544        if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
1545          ret = true;
1546          // Break outer loop. Labels confuse the jscompiler and we
1547          // don't use them.
1548          i2 = n2.length;
1549          i1 = n1.length;
1550        }
1551      }
1552    }
1553
1554  } else if (v1.type == 'node-set' || v2.type == 'node-set') {
1555
1556    if (v1.type == 'number') {
1557      var s = v1.numberValue();
1558      var n = v2.nodeSetValue();
1559
1560      ret = false;
1561      for (var i = 0;  i < n.length; ++i) {
1562        var nn = xmlValue(n[i]) - 0;
1563        if (cmp(s, nn)) {
1564          ret = true;
1565          break;
1566        }
1567      }
1568
1569    } else if (v2.type == 'number') {
1570      var n = v1.nodeSetValue();
1571      var s = v2.numberValue();
1572
1573      ret = false;
1574      for (var i = 0;  i < n.length; ++i) {
1575        var nn = xmlValue(n[i]) - 0;
1576        if (cmp(nn, s)) {
1577          ret = true;
1578          break;
1579        }
1580      }
1581
1582    } else if (v1.type == 'string') {
1583      var s = v1.stringValue();
1584      var n = v2.nodeSetValue();
1585
1586      ret = false;
1587      for (var i = 0;  i < n.length; ++i) {
1588        var nn = xmlValue(n[i]);
1589        if (cmp(s, nn)) {
1590          ret = true;
1591          break;
1592        }
1593      }
1594
1595    } else if (v2.type == 'string') {
1596      var n = v1.nodeSetValue();
1597      var s = v2.stringValue();
1598
1599      ret = false;
1600      for (var i = 0;  i < n.length; ++i) {
1601        var nn = xmlValue(n[i]);
1602        if (cmp(nn, s)) {
1603          ret = true;
1604          break;
1605        }
1606      }
1607
1608    } else {
1609      ret = cmp(v1.booleanValue(), v2.booleanValue());
1610    }
1611
1612  } else if (v1.type == 'boolean' || v2.type == 'boolean') {
1613    ret = cmp(v1.booleanValue(), v2.booleanValue());
1614
1615  } else if (v1.type == 'number' || v2.type == 'number') {
1616    ret = cmp(v1.numberValue(), v2.numberValue());
1617
1618  } else {
1619    ret = cmp(v1.stringValue(), v2.stringValue());
1620  }
1621
1622  return new BooleanValue(ret);
1623}
1624
1625function LiteralExpr(value) {
1626  this.value = value;
1627}
1628
1629LiteralExpr.prototype.evaluate = function(ctx) {
1630  return new StringValue(this.value);
1631};
1632
1633function NumberExpr(value) {
1634  this.value = value;
1635}
1636
1637NumberExpr.prototype.evaluate = function(ctx) {
1638  return new NumberValue(this.value);
1639};
1640
1641function VariableExpr(name) {
1642  this.name = name;
1643}
1644
1645VariableExpr.prototype.evaluate = function(ctx) {
1646  return ctx.getVariable(this.name);
1647}
1648
1649// Factory functions for semantic values (i.e. Expressions) of the
1650// productions in the grammar. When a production is matched to reduce
1651// the current parse state stack, the function is called with the
1652// semantic values of the matched elements as arguments, and returns
1653// another semantic value. The semantic value is a node of the parse
1654// tree, an expression object with an evaluate() method that evaluates the
1655// expression in an actual context. These factory functions are used
1656// in the specification of the grammar rules, below.
1657
1658function makeTokenExpr(m) {
1659  return new TokenExpr(m);
1660}
1661
1662function passExpr(e) {
1663  return e;
1664}
1665
1666function makeLocationExpr1(slash, rel) {
1667  rel.absolute = true;
1668  return rel;
1669}
1670
1671function makeLocationExpr2(dslash, rel) {
1672  rel.absolute = true;
1673  rel.prependStep(makeAbbrevStep(dslash.value));
1674  return rel;
1675}
1676
1677function makeLocationExpr3(slash) {
1678  var ret = new LocationExpr();
1679  ret.appendStep(makeAbbrevStep('.'));
1680  ret.absolute = true;
1681  return ret;
1682}
1683
1684function makeLocationExpr4(dslash) {
1685  var ret = new LocationExpr();
1686  ret.absolute = true;
1687  ret.appendStep(makeAbbrevStep(dslash.value));
1688  return ret;
1689}
1690
1691function makeLocationExpr5(step) {
1692  var ret = new LocationExpr();
1693  ret.appendStep(step);
1694  return ret;
1695}
1696
1697function makeLocationExpr6(rel, slash, step) {
1698  rel.appendStep(step);
1699  return rel;
1700}
1701
1702function makeLocationExpr7(rel, dslash, step) {
1703  rel.appendStep(makeAbbrevStep(dslash.value));
1704  rel.appendStep(step);
1705  return rel;
1706}
1707
1708function makeStepExpr1(dot) {
1709  return makeAbbrevStep(dot.value);
1710}
1711
1712function makeStepExpr2(ddot) {
1713  return makeAbbrevStep(ddot.value);
1714}
1715
1716function makeStepExpr3(axisname, axis, nodetest) {
1717  return new StepExpr(axisname.value, nodetest);
1718}
1719
1720function makeStepExpr4(at, nodetest) {
1721  return new StepExpr('attribute', nodetest);
1722}
1723
1724function makeStepExpr5(nodetest) {
1725  return new StepExpr('child', nodetest);
1726}
1727
1728function makeStepExpr6(step, predicate) {
1729  step.appendPredicate(predicate);
1730  return step;
1731}
1732
1733function makeAbbrevStep(abbrev) {
1734  switch (abbrev) {
1735  case '//':
1736    return new StepExpr('descendant-or-self', new NodeTestAny);
1737
1738  case '.':
1739    return new StepExpr('self', new NodeTestAny);
1740
1741  case '..':
1742    return new StepExpr('parent', new NodeTestAny);
1743  }
1744}
1745
1746function makeNodeTestExpr1(asterisk) {
1747  return new NodeTestElementOrAttribute;
1748}
1749
1750function makeNodeTestExpr2(ncname, colon, asterisk) {
1751  return new NodeTestNC(ncname.value);
1752}
1753
1754function makeNodeTestExpr3(qname) {
1755  return new NodeTestName(qname.value);
1756}
1757
1758function makeNodeTestExpr4(typeo, parenc) {
1759  var type = typeo.value.replace(/\s*\($/, '');
1760  switch(type) {
1761  case 'node':
1762    return new NodeTestAny;
1763
1764  case 'text':
1765    return new NodeTestText;
1766
1767  case 'comment':
1768    return new NodeTestComment;
1769
1770  case 'processing-instruction':
1771    return new NodeTestPI('');
1772  }
1773}
1774
1775function makeNodeTestExpr5(typeo, target, parenc) {
1776  var type = typeo.replace(/\s*\($/, '');
1777  if (type != 'processing-instruction') {
1778    throw type;
1779  }
1780  return new NodeTestPI(target.value);
1781}
1782
1783function makePredicateExpr(pareno, expr, parenc) {
1784  return new PredicateExpr(expr);
1785}
1786
1787function makePrimaryExpr(pareno, expr, parenc) {
1788  return expr;
1789}
1790
1791function makeFunctionCallExpr1(name, pareno, parenc) {
1792  return new FunctionCallExpr(name);
1793}
1794
1795function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
1796  var ret = new FunctionCallExpr(name);
1797  ret.appendArg(arg1);
1798  for (var i = 0; i < args.length; ++i) {
1799    ret.appendArg(args[i]);
1800  }
1801  return ret;
1802}
1803
1804function makeArg

Large files files are truncated, but you can click here to view the full file