add selenium tests
authorioguix <ioguix>
Tue, 16 Oct 2007 20:53:44 +0000 (20:53 +0000)
committerioguix <ioguix>
Tue, 16 Oct 2007 20:53:44 +0000 (20:53 +0000)
selenium/xpath/dom.js [new file with mode: 0644]
selenium/xpath/misc.js [new file with mode: 0644]
selenium/xpath/xpath.js [new file with mode: 0644]

diff --git a/selenium/xpath/dom.js b/selenium/xpath/dom.js
new file mode 100644 (file)
index 0000000..5e49748
--- /dev/null
@@ -0,0 +1,428 @@
+// Copyright 2005 Google Inc.\r
+// All Rights Reserved\r
+//\r
+// An XML parse and a minimal DOM implementation that just supportes\r
+// the subset of the W3C DOM that is used in the XSLT implementation.\r
+//\r
+// References: \r
+//\r
+// [DOM] W3C DOM Level 3 Core Specification\r
+//       <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/>.\r
+//\r
+// \r
+// Author: Steffen Meschkat <mesch@google.com>\r
+\r
+// NOTE: The split() method in IE omits empty result strings. This is\r
+// utterly annoying. So we don't use it here.\r
+\r
+// Resolve entities in XML text fragments. According to the DOM\r
+// specification, the DOM is supposed to resolve entity references at\r
+// the API level. I.e. no entity references are passed through the\r
+// API. See "Entities and the DOM core", p.12, DOM 2 Core\r
+// Spec. However, different browsers actually pass very different\r
+// values at the API.\r
+//\r
+function xmlResolveEntities(s) {\r
+\r
+  var parts = stringSplit(s, '&');\r
+\r
+  var ret = parts[0];\r
+  for (var i = 1; i < parts.length; ++i) {\r
+    var rp = stringSplit(parts[i], ';');\r
+    if (rp.length == 1) {\r
+      // no entity reference: just a & but no ;\r
+      ret += parts[i];\r
+      continue;\r
+    }\r
+    \r
+    var ch;\r
+    switch (rp[0]) {\r
+      case 'lt': \r
+        ch = '<';\r
+        break;\r
+      case 'gt': \r
+        ch = '>';\r
+        break;\r
+      case 'amp': \r
+        ch = '&';\r
+        break;\r
+      case 'quot': \r
+        ch = '"';\r
+        break;\r
+      case 'apos': \r
+        ch = '\'';\r
+        break;\r
+      case 'nbsp': \r
+        ch = String.fromCharCode(160);\r
+        break;\r
+      default:\r
+        // Cool trick: let the DOM do the entity decoding. We assign\r
+        // the entity text through non-W3C DOM properties and read it\r
+        // through the W3C DOM. W3C DOM access is specified to resolve\r
+        // entities. \r
+        var span = window.document.createElement('span');\r
+        span.innerHTML = '&' + rp[0] + '; ';\r
+        ch = span.childNodes[0].nodeValue.charAt(0);\r
+    }\r
+    ret += ch + rp[1];\r
+  }\r
+\r
+  return ret;\r
+}\r
+\r
+\r
+// Parses the given XML string with our custom, JavaScript XML parser. Written\r
+// by Steffen Meschkat (mesch@google.com).\r
+function xmlParse(xml) {\r
+  Timer.start('xmlparse');\r
+  var regex_empty = /\/$/;\r
+\r
+  // See also <http://www.w3.org/TR/REC-xml/#sec-common-syn> for\r
+  // allowed chars in a tag and attribute name. TODO(mesch): the\r
+  // following is still not completely correct.\r
+\r
+  var regex_tagname = /^([\w:-]*)/;\r
+  var regex_attribute = /([\w:-]+)\s?=\s?('([^\']*)'|"([^\"]*)")/g;\r
+\r
+  var xmldoc = new XDocument();\r
+  var root = xmldoc;\r
+\r
+  // For the record: in Safari, we would create native DOM nodes, but\r
+  // in Opera that is not possible, because the DOM only allows HTML\r
+  // element nodes to be created, so we have to do our own DOM nodes.\r
+\r
+  // xmldoc = document.implementation.createDocument('','',null);\r
+  // root = xmldoc; // .createDocumentFragment();\r
+  // NOTE(mesch): using the DocumentFragment instead of the Document\r
+  // crashes my Safari 1.2.4 (v125.12).\r
+  var stack = [];\r
+\r
+  var parent = root;\r
+  stack.push(parent);\r
+\r
+  var x = stringSplit(xml, '<');\r
+  for (var i = 1; i < x.length; ++i) {\r
+    var xx = stringSplit(x[i], '>');\r
+    var tag = xx[0];\r
+    var text = xmlResolveEntities(xx[1] || '');\r
+\r
+    if (tag.charAt(0) == '/') {\r
+      stack.pop();\r
+      parent = stack[stack.length-1];\r
+\r
+    } else if (tag.charAt(0) == '?') {\r
+      // Ignore XML declaration and processing instructions\r
+    } else if (tag.charAt(0) == '!') {\r
+      // Ignore notation and comments\r
+    } else {\r
+      var empty = tag.match(regex_empty);\r
+      var tagname = regex_tagname.exec(tag)[1];\r
+      var node = xmldoc.createElement(tagname);\r
+\r
+      var att;\r
+      while (att = regex_attribute.exec(tag)) {\r
+        var val = xmlResolveEntities(att[3] || att[4] || '');\r
+        node.setAttribute(att[1], val);\r
+      }\r
+      \r
+      if (empty) {\r
+        parent.appendChild(node);\r
+      } else {\r
+        parent.appendChild(node);\r
+        parent = node;\r
+        stack.push(node);\r
+      }\r
+    }\r
+\r
+    if (text && parent != root) {\r
+      parent.appendChild(xmldoc.createTextNode(text));\r
+    }\r
+  }\r
+\r
+  Timer.end('xmlparse');\r
+  return root;\r
+}\r
+\r
+\r
+// Our W3C DOM Node implementation. Note we call it XNode because we\r
+// can't define the identifier Node. We do this mostly for Opera,\r
+// where we can't reuse the HTML DOM for parsing our own XML, and for\r
+// Safari, where it is too expensive to have the template processor\r
+// operate on native DOM nodes.\r
+function XNode(type, name, value, owner) {\r
+  this.attributes = [];\r
+  this.childNodes = [];\r
+\r
+  XNode.init.call(this, type, name, value, owner);\r
+}\r
+\r
+// Don't call as method, use apply() or call().\r
+XNode.init = function(type, name, value, owner) {\r
+  this.nodeType = type - 0;\r
+  this.nodeName = '' + name;\r
+  this.nodeValue = '' + value;\r
+  this.ownerDocument = owner;\r
+\r
+  this.firstChild = null;\r
+  this.lastChild = null;\r
+  this.nextSibling = null;\r
+  this.previousSibling = null;\r
+  this.parentNode = null;\r
+}\r
+\r
+XNode.unused_ = [];\r
+\r
+XNode.recycle = function(node) {\r
+  if (!node) {\r
+    return;\r
+  }\r
+\r
+  if (node.constructor == XDocument) {\r
+    XNode.recycle(node.documentElement);\r
+    return;\r
+  }\r
+\r
+  if (node.constructor != this) {\r
+    return;\r
+  }\r
+\r
+  XNode.unused_.push(node);\r
+  for (var a = 0; a < node.attributes.length; ++a) {\r
+    XNode.recycle(node.attributes[a]);\r
+  }\r
+  for (var c = 0; c < node.childNodes.length; ++c) {\r
+    XNode.recycle(node.childNodes[c]);\r
+  }\r
+  node.attributes.length = 0;\r
+  node.childNodes.length = 0;\r
+  XNode.init.call(node, 0, '', '', null);\r
+}\r
+\r
+XNode.create = function(type, name, value, owner) {\r
+  if (XNode.unused_.length > 0) {\r
+    var node = XNode.unused_.pop();\r
+    XNode.init.call(node, type, name, value, owner);\r
+    return node;\r
+  } else {\r
+    return new XNode(type, name, value, owner);\r
+  }\r
+}\r
+\r
+XNode.prototype.appendChild = function(node) {\r
+  // firstChild\r
+  if (this.childNodes.length == 0) {\r
+    this.firstChild = node;\r
+  }\r
+\r
+  // previousSibling\r
+  node.previousSibling = this.lastChild;\r
+\r
+  // nextSibling\r
+  node.nextSibling = null;\r
+  if (this.lastChild) {\r
+    this.lastChild.nextSibling = node;\r
+  }\r
+\r
+  // parentNode\r
+  node.parentNode = this;\r
+\r
+  // lastChild\r
+  this.lastChild = node;\r
+\r
+  // childNodes\r
+  this.childNodes.push(node);\r
+}\r
+\r
+\r
+XNode.prototype.replaceChild = function(newNode, oldNode) {\r
+  if (oldNode == newNode) {\r
+    return;\r
+  }\r
+\r
+  for (var i = 0; i < this.childNodes.length; ++i) {\r
+    if (this.childNodes[i] == oldNode) {\r
+      this.childNodes[i] = newNode;\r
+      \r
+      var p = oldNode.parentNode;\r
+      oldNode.parentNode = null;\r
+      newNode.parentNode = p;\r
+      \r
+      p = oldNode.previousSibling;\r
+      oldNode.previousSibling = null;\r
+      newNode.previousSibling = p;\r
+      if (newNode.previousSibling) {\r
+        newNode.previousSibling.nextSibling = newNode;\r
+      }\r
+      \r
+      p = oldNode.nextSibling;\r
+      oldNode.nextSibling = null;\r
+      newNode.nextSibling = p;\r
+      if (newNode.nextSibling) {\r
+        newNode.nextSibling.previousSibling = newNode;\r
+      }\r
+\r
+      if (this.firstChild == oldNode) {\r
+        this.firstChild = newNode;\r
+      }\r
+\r
+      if (this.lastChild == oldNode) {\r
+        this.lastChild = newNode;\r
+      }\r
+\r
+      break;\r
+    }\r
+  }\r
+}\r
+\r
+XNode.prototype.insertBefore = function(newNode, oldNode) {\r
+  if (oldNode == newNode) {\r
+    return;\r
+  }\r
+\r
+  if (oldNode.parentNode != this) {\r
+    return;\r
+  }\r
+\r
+  if (newNode.parentNode) {\r
+    newNode.parentNode.removeChild(newNode);\r
+  }\r
+\r
+  var newChildren = [];\r
+  for (var i = 0; i < this.childNodes.length; ++i) {\r
+    var c = this.childNodes[i];\r
+    if (c == oldNode) {\r
+      newChildren.push(newNode);\r
+\r
+      newNode.parentNode = this;\r
+\r
+      newNode.previousSibling = oldNode.previousSibling;\r
+      oldNode.previousSibling = newNode;\r
+      if (newNode.previousSibling) {\r
+        newNode.previousSibling.nextSibling = newNode;\r
+      }\r
+      \r
+      newNode.nextSibling = oldNode;\r
+\r
+      if (this.firstChild == oldNode) {\r
+        this.firstChild = newNode;\r
+      }\r
+    }\r
+    newChildren.push(c);\r
+  }\r
+  this.childNodes = newChildren;\r
+}\r
+\r
+XNode.prototype.removeChild = function(node) {\r
+  var newChildren = [];\r
+  for (var i = 0; i < this.childNodes.length; ++i) {\r
+    var c = this.childNodes[i];\r
+    if (c != node) {\r
+      newChildren.push(c);\r
+    } else {\r
+      if (c.previousSibling) {\r
+        c.previousSibling.nextSibling = c.nextSibling;\r
+      }\r
+      if (c.nextSibling) {\r
+        c.nextSibling.previousSibling = c.previousSibling;\r
+      }\r
+      if (this.firstChild == c) {\r
+        this.firstChild = c.nextSibling;\r
+      }\r
+      if (this.lastChild == c) {\r
+        this.lastChild = c.previousSibling;\r
+      }\r
+    }\r
+  }\r
+  this.childNodes = newChildren;\r
+}\r
+\r
+\r
+XNode.prototype.hasAttributes = function() {\r
+  return this.attributes.length > 0;\r
+}\r
+\r
+\r
+XNode.prototype.setAttribute = function(name, value) {\r
+  for (var i = 0; i < this.attributes.length; ++i) {\r
+    if (this.attributes[i].nodeName == name) {\r
+      this.attributes[i].nodeValue = '' + value;\r
+      return;\r
+    }\r
+  }\r
+  this.attributes.push(new XNode(DOM_ATTRIBUTE_NODE, name, value));\r
+}\r
+\r
+\r
+XNode.prototype.getAttribute = function(name) {\r
+  for (var i = 0; i < this.attributes.length; ++i) {\r
+    if (this.attributes[i].nodeName == name) {\r
+      return this.attributes[i].nodeValue;\r
+    }\r
+  }\r
+  return null;\r
+}\r
+\r
+XNode.prototype.removeAttribute = function(name) {\r
+  var a = [];\r
+  for (var i = 0; i < this.attributes.length; ++i) {\r
+    if (this.attributes[i].nodeName != name) {\r
+      a.push(this.attributes[i]);\r
+    }\r
+  }\r
+  this.attributes = a;\r
+}\r
+\r
+\r
+function XDocument() {\r
+  XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, this);\r
+  this.documentElement = null;\r
+}\r
+\r
+XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document');\r
+\r
+XDocument.prototype.clear = function() {\r
+  XNode.recycle(this.documentElement);\r
+  this.documentElement = null;\r
+}\r
+\r
+XDocument.prototype.appendChild = function(node) {\r
+  XNode.prototype.appendChild.call(this, node);\r
+  this.documentElement = this.childNodes[0];\r
+}\r
+\r
+XDocument.prototype.createElement = function(name) {\r
+  return XNode.create(DOM_ELEMENT_NODE, name, null, this);\r
+}\r
+\r
+XDocument.prototype.createDocumentFragment = function() {\r
+  return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment',\r
+                    null, this);\r
+}\r
+\r
+XDocument.prototype.createTextNode = function(value) {\r
+  return XNode.create(DOM_TEXT_NODE, '#text', value, this);\r
+}\r
+\r
+XDocument.prototype.createAttribute = function(name) {\r
+  return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);\r
+}\r
+\r
+XDocument.prototype.createComment = function(data) {\r
+  return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);\r
+}\r
+\r
+XNode.prototype.getElementsByTagName = function(name, list) {\r
+  if (!list) {\r
+    list = [];\r
+  }\r
+\r
+  if (this.nodeName == name) {\r
+    list.push(this);\r
+  }\r
+\r
+  for (var i = 0; i < this.childNodes.length; ++i) {\r
+    this.childNodes[i].getElementsByTagName(name, list);\r
+  }\r
+\r
+  return list;\r
+}\r
diff --git a/selenium/xpath/misc.js b/selenium/xpath/misc.js
new file mode 100644 (file)
index 0000000..9017542
--- /dev/null
@@ -0,0 +1,255 @@
+// Copyright 2005 Google Inc.\r
+// All Rights Reserved\r
+//\r
+// Miscellania that support the ajaxslt implementation.\r
+//\r
+// Author: Steffen Meschkat <mesch@google.com>\r
+//\r
+\r
+function el(i) {\r
+  return document.getElementById(i);\r
+}\r
+\r
+function px(x) {\r
+  return x + 'px';\r
+}\r
+\r
+// Split a string s at all occurrences of character c. This is like\r
+// the split() method of the string object, but IE omits empty\r
+// strings, which violates the invariant (s.split(x).join(x) == s).\r
+function stringSplit(s, c) {\r
+  var a = s.indexOf(c);\r
+  if (a == -1) {\r
+    return [ s ];\r
+  }\r
+  \r
+  var parts = [];\r
+  parts.push(s.substr(0,a));\r
+  while (a != -1) {\r
+    var a1 = s.indexOf(c, a + 1);\r
+    if (a1 != -1) {\r
+      parts.push(s.substr(a + 1, a1 - a - 1));\r
+    } else {\r
+      parts.push(s.substr(a + 1));\r
+    } \r
+    a = a1;\r
+  }\r
+\r
+  return parts;\r
+}\r
+\r
+// Returns the text value if a node; for nodes without children this\r
+// is the nodeValue, for nodes with children this is the concatenation\r
+// of the value of all children.\r
+function xmlValue(node) {\r
+  if (!node) {\r
+    return '';\r
+  }\r
+\r
+  var ret = '';\r
+  if (node.nodeType == DOM_TEXT_NODE ||\r
+      node.nodeType == DOM_CDATA_SECTION_NODE ||\r
+      node.nodeType == DOM_ATTRIBUTE_NODE) {\r
+    ret += node.nodeValue;\r
+\r
+  } else if (node.nodeType == DOM_ELEMENT_NODE ||\r
+             node.nodeType == DOM_DOCUMENT_NODE ||\r
+             node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {\r
+    for (var i = 0; i < node.childNodes.length; ++i) {\r
+      ret += arguments.callee(node.childNodes[i]);\r
+    }\r
+  }\r
+  return ret;\r
+}\r
+\r
+// Returns the representation of a node as XML text.\r
+function xmlText(node) {\r
+  var ret = '';\r
+  if (node.nodeType == DOM_TEXT_NODE) {\r
+    ret += xmlEscapeText(node.nodeValue);\r
+    \r
+  } else if (node.nodeType == DOM_ELEMENT_NODE) {\r
+    ret += '<' + node.nodeName;\r
+    for (var i = 0; i < node.attributes.length; ++i) {\r
+      var a = node.attributes[i];\r
+      if (a && a.nodeName && a.nodeValue) {\r
+        ret += ' ' + a.nodeName;\r
+        ret += '="' + xmlEscapeAttr(a.nodeValue) + '"';\r
+      }\r
+    }\r
+\r
+    if (node.childNodes.length == 0) {\r
+      ret += '/>';\r
+\r
+    } else {\r
+      ret += '>';\r
+      for (var i = 0; i < node.childNodes.length; ++i) {\r
+        ret += arguments.callee(node.childNodes[i]);\r
+      }\r
+      ret += '</' + node.nodeName + '>';\r
+    }\r
+    \r
+  } else if (node.nodeType == DOM_DOCUMENT_NODE || \r
+             node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {\r
+    for (var i = 0; i < node.childNodes.length; ++i) {\r
+      ret += arguments.callee(node.childNodes[i]);\r
+    }\r
+  }\r
+  \r
+  return ret;\r
+}\r
+\r
+// Applies the given function to each element of the array.\r
+function mapExec(array, func) {\r
+  for (var i = 0; i < array.length; ++i) {\r
+    func(array[i]);\r
+  }\r
+}\r
+\r
+// Returns an array that contains the return value of the given\r
+// function applied to every element of the input array.\r
+function mapExpr(array, func) {\r
+  var ret = [];\r
+  for (var i = 0; i < array.length; ++i) {\r
+    ret.push(func(array[i]));\r
+  }\r
+  return ret;\r
+};\r
+\r
+// Reverses the given array in place.\r
+function reverseInplace(array) {\r
+  for (var i = 0; i < array.length / 2; ++i) {\r
+    var h = array[i];\r
+    var ii = array.length - i - 1;\r
+    array[i] = array[ii];\r
+    array[ii] = h;\r
+  }\r
+}\r
+\r
+// Shallow-copies an array.\r
+function copyArray(dst, src) { \r
+  for (var i = 0; i < src.length; ++i) {\r
+    dst.push(src[i]);\r
+  }\r
+}\r
+\r
+function assert(b) {\r
+  if (!b) {\r
+    throw 'assertion failed';\r
+  }\r
+}\r
+\r
+// Based on\r
+// <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247>\r
+var DOM_ELEMENT_NODE = 1;\r
+var DOM_ATTRIBUTE_NODE = 2;\r
+var DOM_TEXT_NODE = 3;\r
+var DOM_CDATA_SECTION_NODE = 4;\r
+var DOM_ENTITY_REFERENCE_NODE = 5;\r
+var DOM_ENTITY_NODE = 6;\r
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;\r
+var DOM_COMMENT_NODE = 8;\r
+var DOM_DOCUMENT_NODE = 9;\r
+var DOM_DOCUMENT_TYPE_NODE = 10;\r
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;\r
+var DOM_NOTATION_NODE = 12;\r
+\r
+\r
+var xpathdebug = false; // trace xpath parsing\r
+var xsltdebug = false; // trace xslt processing\r
+\r
+\r
+// Escape XML special markup chracters: tag delimiter < > and entity\r
+// reference start delimiter &. The escaped string can be used in XML\r
+// text portions (i.e. between tags).\r
+function xmlEscapeText(s) {\r
+  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\r
+}\r
+\r
+// Escape XML special markup characters: tag delimiter < > entity\r
+// reference start delimiter & and quotes ". The escaped string can be\r
+// used in double quoted XML attribute value portions (i.e. in\r
+// attributes within start tags).\r
+function xmlEscapeAttr(s) {\r
+  return xmlEscapeText(s).replace(/\"/g, '&quot;');\r
+}\r
+\r
+// Escape markup in XML text, but don't touch entity references. The\r
+// escaped string can be used as XML text (i.e. between tags).\r
+function xmlEscapeTags(s) {\r
+  return s.replace(/</g, '&lt;').replace(/>/g, '&gt;');\r
+}\r
+\r
+// An implementation of the debug log. \r
+\r
+var logging__ = false;\r
+\r
+function Log() {};\r
+\r
+Log.lines = [];\r
+\r
+Log.write = function(s) {\r
+  if (logging__) {\r
+    this.lines.push(xmlEscapeText(s));\r
+    this.show();\r
+  }\r
+};\r
+\r
+// Writes the given XML with every tag on a new line.\r
+Log.writeXML = function(xml) {\r
+  if (logging__) {\r
+    var s0 = xml.replace(/</g, '\n<');\r
+    var s1 = xmlEscapeText(s0);\r
+    var s2 = s1.replace(/\s*\n(\s|\n)*/g, '<br/>');\r
+    this.lines.push(s2);\r
+    this.show();\r
+  }\r
+}\r
+\r
+// Writes without any escaping\r
+Log.writeRaw = function(s) {\r
+  if (logging__) {\r
+    this.lines.push(s);\r
+    this.show();\r
+  }\r
+}\r
+\r
+Log.clear = function() {\r
+  if (logging__) {\r
+    var l = this.div();\r
+    l.innerHTML = '';\r
+    this.lines = [];\r
+  }\r
+}\r
+\r
+Log.show = function() {\r
+  var l = this.div();\r
+  l.innerHTML += this.lines.join('<br/>') + '<br/>';\r
+  this.lines = [];\r
+  l.scrollTop = l.scrollHeight;\r
+}\r
+\r
+Log.div = function() {\r
+  var l = document.getElementById('log');\r
+  if (!l) {\r
+    l = document.createElement('div');\r
+    l.id = 'log';\r
+    l.style.position = 'absolute';\r
+    l.style.right = '5px';\r
+    l.style.top = '5px';\r
+    l.style.width = '250px';\r
+    l.style.height = '150px';\r
+    l.style.overflow = 'auto';\r
+    l.style.backgroundColor = '#f0f0f0';\r
+    l.style.border = '1px solid gray';\r
+    l.style.fontSize = '10px';\r
+    l.style.padding = '5px';\r
+    document.body.appendChild(l);\r
+  }\r
+  return l;\r
+}\r
+\r
+\r
+function Timer() {}\r
+Timer.start = function() {}\r
+Timer.end = function() {}\r
diff --git a/selenium/xpath/xpath.js b/selenium/xpath/xpath.js
new file mode 100644 (file)
index 0000000..ce78458
--- /dev/null
@@ -0,0 +1,2182 @@
+// Copyright 2005 Google Inc.\r
+// All Rights Reserved\r
+//\r
+// An XPath parser and evaluator written in JavaScript. The\r
+// implementation is complete except for functions handling\r
+// namespaces.\r
+//\r
+// Reference: [XPATH] XPath Specification\r
+// <http://www.w3.org/TR/1999/REC-xpath-19991116>.\r
+//\r
+//\r
+// The API of the parser has several parts:\r
+//\r
+// 1. The parser function xpathParse() that takes a string and returns\r
+// an expession object.\r
+//\r
+// 2. The expression object that has an evaluate() method to evaluate the\r
+// XPath expression it represents. (It is actually a hierarchy of\r
+// objects that resembles the parse tree, but an application will call\r
+// evaluate() only on the top node of this hierarchy.)\r
+//\r
+// 3. The context object that is passed as an argument to the evaluate()\r
+// method, which represents the DOM context in which the expression is\r
+// evaluated.\r
+//\r
+// 4. The value object that is returned from evaluate() and represents\r
+// values of the different types that are defined by XPath (number,\r
+// string, boolean, and node-set), and allows to convert between them.\r
+//\r
+// These parts are near the top of the file, the functions and data\r
+// that are used internally follow after them.\r
+//\r
+//\r
+// TODO(mesch): add jsdoc comments. Use more coherent naming.\r
+//\r
+//\r
+// Author: Steffen Meschkat <mesch@google.com>\r
+\r
+\r
+// The entry point for the parser.\r
+//\r
+// @param expr a string that contains an XPath expression.\r
+// @return an expression object that can be evaluated with an\r
+// expression context.\r
+\r
+function xpathParse(expr) {\r
+  if (xpathdebug) {\r
+    Log.write('XPath parse ' + expr);\r
+  }\r
+  xpathParseInit();\r
+\r
+  var cached = xpathCacheLookup(expr);\r
+  if (cached) {\r
+    if (xpathdebug) {\r
+      Log.write(' ... cached');\r
+    }\r
+    return cached;\r
+  }\r
+\r
+  // Optimize for a few common cases: simple attribute node tests\r
+  // (@id), simple element node tests (page), variable references\r
+  // ($address), numbers (4), multi-step path expressions where each\r
+  // step is a plain element node test\r
+  // (page/overlay/locations/location).\r
+  \r
+  if (expr.match(/^(\$|@)?\w+$/i)) {\r
+    var ret = makeSimpleExpr(expr);\r
+    xpathParseCache[expr] = ret;\r
+    if (xpathdebug) {\r
+      Log.write(' ... simple');\r
+    }\r
+    return ret;\r
+  }\r
+\r
+  if (expr.match(/^\w+(\/\w+)*$/i)) {\r
+    var ret = makeSimpleExpr2(expr);\r
+    xpathParseCache[expr] = ret;\r
+    if (xpathdebug) {\r
+      Log.write(' ... simple 2');\r
+    }\r
+    return ret;\r
+  }\r
+\r
+  var cachekey = expr; // expr is modified during parse\r
+  if (xpathdebug) {\r
+    Timer.start('XPath parse', cachekey);\r
+  }\r
+\r
+  var stack = [];\r
+  var ahead = null;\r
+  var previous = null;\r
+  var done = false;\r
+\r
+  var parse_count = 0;\r
+  var lexer_count = 0;\r
+  var reduce_count = 0;\r
+  \r
+  while (!done) {\r
+    parse_count++;\r
+    expr = expr.replace(/^\s*/, '');\r
+    previous = ahead;\r
+    ahead = null;\r
+\r
+    var rule = null;\r
+    var match = '';\r
+    for (var i = 0; i < xpathTokenRules.length; ++i) {\r
+      var result = xpathTokenRules[i].re.exec(expr);\r
+      lexer_count++;\r
+      if (result && result.length > 0 && result[0].length > match.length) {\r
+        rule = xpathTokenRules[i];\r
+        match = result[0];\r
+        break;\r
+      }\r
+    }\r
+\r
+    // Special case: allow operator keywords to be element and\r
+    // variable names.\r
+\r
+    // NOTE(mesch): The parser resolves conflicts by looking ahead,\r
+    // and this is the only case where we look back to\r
+    // disambiguate. So this is indeed something different, and\r
+    // looking back is usually done in the lexer (via states in the\r
+    // general case, called "start conditions" in flex(1)). Also,the\r
+    // conflict resolution in the parser is not as robust as it could\r
+    // be, so I'd like to keep as much off the parser as possible (all\r
+    // these precedence values should be computed from the grammar\r
+    // rules and possibly associativity declarations, as in bison(1),\r
+    // and not explicitly set.\r
+\r
+    if (rule &&\r
+        (rule == TOK_DIV || \r
+         rule == TOK_MOD ||\r
+         rule == TOK_AND || \r
+         rule == TOK_OR) &&\r
+        (!previous || \r
+         previous.tag == TOK_AT || \r
+         previous.tag == TOK_DSLASH || \r
+         previous.tag == TOK_SLASH ||\r
+         previous.tag == TOK_AXIS || \r
+         previous.tag == TOK_DOLLAR)) {\r
+      rule = TOK_QNAME;\r
+    }\r
+\r
+    if (rule) {\r
+      expr = expr.substr(match.length);\r
+      if (xpathdebug) {\r
+        Log.write('token: ' + match + ' -- ' + rule.label);\r
+      }\r
+      ahead = {\r
+        tag: rule,\r
+        match: match,\r
+        prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compiler\r
+        expr: makeTokenExpr(match)\r
+      };\r
+\r
+    } else {\r
+      if (xpathdebug) {\r
+        Log.write('DONE');\r
+      }\r
+      done = true;\r
+    }\r
+\r
+    while (xpathReduce(stack, ahead)) {\r
+      reduce_count++;\r
+      if (xpathdebug) {\r
+        Log.write('stack: ' + stackToString(stack));\r
+      }\r
+    }\r
+  }\r
+\r
+  if (xpathdebug) {\r
+    Log.write(stackToString(stack));\r
+  }\r
+\r
+  if (stack.length != 1) {\r
+    throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);\r
+  }\r
+\r
+  var result = stack[0].expr;\r
+  xpathParseCache[cachekey] = result;\r
+\r
+  if (xpathdebug) {\r
+    Timer.end('XPath parse', cachekey);\r
+  }\r
+\r
+  if (xpathdebug) {\r
+    Log.write('XPath parse: ' + parse_count + ' / ' + \r
+              lexer_count + ' / ' + reduce_count);\r
+  }\r
+\r
+  return result;\r
+}\r
+\r
+var xpathParseCache = {};\r
+\r
+function xpathCacheLookup(expr) {\r
+  return xpathParseCache[expr];\r
+}\r
+\r
+function xpathReduce(stack, ahead) {\r
+  var cand = null;\r
+\r
+  if (stack.length > 0) {\r
+    var top = stack[stack.length-1];\r
+    var ruleset = xpathRules[top.tag.key];\r
+\r
+    if (ruleset) {\r
+      for (var i = 0; i < ruleset.length; ++i) {\r
+        var rule = ruleset[i];\r
+        var match = xpathMatchStack(stack, rule[1]);\r
+        if (match.length) {\r
+          cand = {\r
+            tag: rule[0],\r
+            rule: rule,\r
+            match: match\r
+          };\r
+          cand.prec = xpathGrammarPrecedence(cand);\r
+          break;\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  var ret;\r
+  if (cand && (!ahead || cand.prec > ahead.prec || \r
+               (ahead.tag.left && cand.prec >= ahead.prec))) {\r
+    for (var i = 0; i < cand.match.matchlength; ++i) {\r
+      stack.pop();\r
+    }\r
+\r
+    if (xpathdebug) {\r
+      Log.write('reduce ' + cand.tag.label + ' ' + cand.prec +\r
+                ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec + \r
+                             (ahead.tag.left ? ' left' : '')\r
+                             : ' none '));\r
+    }\r
+\r
+    var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });\r
+    cand.expr = cand.rule[3].apply(null, matchexpr);\r
+\r
+    stack.push(cand);\r
+    ret = true;\r
+\r
+  } else {\r
+    if (ahead) {\r
+      if (xpathdebug) {\r
+        Log.write('shift ' + ahead.tag.label + ' ' + ahead.prec + \r
+                  (ahead.tag.left ? ' left' : '') +\r
+                  ' over ' + (cand ? cand.tag.label + ' ' + \r
+                              cand.prec : ' none'));\r
+      }\r
+      stack.push(ahead);\r
+    }\r
+    ret = false;\r
+  }\r
+  return ret;\r
+}\r
+\r
+function xpathMatchStack(stack, pattern) {\r
+\r
+  // NOTE(mesch): The stack matches for variable cardinality are\r
+  // greedy but don't do backtracking. This would be an issue only\r
+  // with rules of the form A* A, i.e. with an element with variable\r
+  // cardinality followed by the same element. Since that doesn't\r
+  // occur in the grammar at hand, all matches on the stack are\r
+  // unambiguous.\r
+\r
+  var S = stack.length;\r
+  var P = pattern.length;\r
+  var p, s;\r
+  var match = [];\r
+  match.matchlength = 0;\r
+  var ds = 0;\r
+  for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {\r
+    ds = 0;\r
+    var qmatch = [];\r
+    if (pattern[p] == Q_MM) {\r
+      p -= 1;\r
+      match.push(qmatch);\r
+      while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {\r
+        qmatch.push(stack[s - ds]);\r
+        ds += 1;\r
+        match.matchlength += 1;\r
+      }\r
+\r
+    } else if (pattern[p] == Q_01) {\r
+      p -= 1;\r
+      match.push(qmatch);\r
+      while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {\r
+        qmatch.push(stack[s - ds]);\r
+        ds += 1;\r
+        match.matchlength += 1;\r
+      }\r
+\r
+    } else if (pattern[p] == Q_1M) {\r
+      p -= 1;\r
+      match.push(qmatch);\r
+      if (stack[s].tag == pattern[p]) {\r
+        while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {\r
+          qmatch.push(stack[s - ds]);\r
+          ds += 1;\r
+          match.matchlength += 1;\r
+        }\r
+      } else {\r
+        return [];\r
+      }\r
+\r
+    } else if (stack[s].tag == pattern[p]) {\r
+      match.push(stack[s]);\r
+      ds += 1;\r
+      match.matchlength += 1;\r
+\r
+    } else {\r
+      return [];\r
+    }\r
+\r
+    reverseInplace(qmatch);\r
+    qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });\r
+  }\r
+\r
+  reverseInplace(match);\r
+\r
+  if (p == -1) {\r
+    return match;\r
+\r
+  } else {\r
+    return [];\r
+  }\r
+}\r
+\r
+function xpathTokenPrecedence(tag) {\r
+  return tag.prec || 2;\r
+}\r
+\r
+function xpathGrammarPrecedence(frame) {\r
+  var ret = 0;\r
+\r
+  if (frame.rule) { /* normal reduce */\r
+    if (frame.rule.length >= 3 && frame.rule[2] >= 0) {\r
+      ret = frame.rule[2];\r
+\r
+    } else {\r
+      for (var i = 0; i < frame.rule[1].length; ++i) {\r
+        var p = xpathTokenPrecedence(frame.rule[1][i]);\r
+        ret = Math.max(ret, p);\r
+      }\r
+    }\r
+  } else if (frame.tag) { /* TOKEN match */\r
+    ret = xpathTokenPrecedence(frame.tag);\r
+\r
+  } else if (frame.length) { /* Q_ match */\r
+    for (var j = 0; j < frame.length; ++j) {\r
+      var p = xpathGrammarPrecedence(frame[j]);\r
+      ret = Math.max(ret, p);\r
+    }\r
+  }\r
+\r
+  return ret;\r
+}\r
+\r
+function stackToString(stack) {\r
+  var ret = '';\r
+  for (var i = 0; i < stack.length; ++i) {\r
+    if (ret) {\r
+      ret += '\n';\r
+    }\r
+    ret += stack[i].tag.label;\r
+  }\r
+  return ret;\r
+}\r
+\r
+\r
+// XPath expression evaluation context. An XPath context consists of a\r
+// DOM node, a list of DOM nodes that contains this node, a number\r
+// that represents the position of the single node in the list, and a\r
+// current set of variable bindings. (See XPath spec.)\r
+//\r
+// The interface of the expression context:\r
+//\r
+//   Constructor -- gets the node, its position, the node set it\r
+//   belongs to, and a parent context as arguments. The parent context\r
+//   is used to implement scoping rules for variables: if a variable\r
+//   is not found in the current context, it is looked for in the\r
+//   parent context, recursively. Except for node, all arguments have\r
+//   default values: default position is 0, default node set is the\r
+//   set that contains only the node, and the default parent is null.\r
+//\r
+//     Notice that position starts at 0 at the outside interface;\r
+//     inside XPath expressions this shows up as position()=1.\r
+//\r
+//   clone() -- creates a new context with the current context as\r
+//   parent. If passed as argument to clone(), the new context has a\r
+//   different node, position, or node set. What is not passed is\r
+//   inherited from the cloned context.\r
+//\r
+//   setVariable(name, expr) -- binds given XPath expression to the\r
+//   name.\r
+//\r
+//   getVariable(name) -- what the name says.\r
+//\r
+//   setNode(node, position) -- sets the context to the new node and\r
+//   its corresponding position. Needed to implement scoping rules for\r
+//   variables in XPath. (A variable is visible to all subsequent\r
+//   siblings, not only to its children.)\r
+\r
+function ExprContext(node, position, nodelist, parent) {\r
+  this.node = node;\r
+  this.position = position || 0;\r
+  this.nodelist = nodelist || [ node ];\r
+  this.variables = {};\r
+  this.parent = parent || null;\r
+  this.root = parent ? parent.root : node.ownerDocument;\r
+}\r
+\r
+ExprContext.prototype.clone = function(node, position, nodelist) {\r
+  return new\r
+  ExprContext(node || this.node,\r
+              typeof position != 'undefined' ? position : this.position,\r
+              nodelist || this.nodelist, this);\r
+};\r
+\r
+ExprContext.prototype.setVariable = function(name, value) {\r
+  this.variables[name] = value;\r
+};\r
+\r
+ExprContext.prototype.getVariable = function(name) {\r
+  if (typeof this.variables[name] != 'undefined') {\r
+    return this.variables[name];\r
+\r
+  } else if (this.parent) {\r
+    return this.parent.getVariable(name);\r
+\r
+  } else {\r
+    return null;\r
+  }\r
+}\r
+\r
+ExprContext.prototype.setNode = function(node, position) {\r
+  this.node = node;\r
+  this.position = position;\r
+}\r
+\r
+\r
+// XPath expression values. They are what XPath expressions evaluate\r
+// to. Strangely, the different value types are not specified in the\r
+// XPath syntax, but only in the semantics, so they don't show up as\r
+// nonterminals in the grammar. Yet, some expressions are required to\r
+// evaluate to particular types, and not every type can be coerced\r
+// into every other type. Although the types of XPath values are\r
+// similar to the types present in JavaScript, the type coercion rules\r
+// are a bit peculiar, so we explicitly model XPath types instead of\r
+// mapping them onto JavaScript types. (See XPath spec.)\r
+//\r
+// The four types are:\r
+//\r
+//   StringValue\r
+//\r
+//   NumberValue\r
+//\r
+//   BooleanValue\r
+//\r
+//   NodeSetValue\r
+//\r
+// The common interface of the value classes consists of methods that\r
+// implement the XPath type coercion rules:\r
+//\r
+//   stringValue() -- returns the value as a JavaScript String,\r
+//\r
+//   numberValue() -- returns the value as a JavaScript Number,\r
+//\r
+//   booleanValue() -- returns the value as a JavaScript Boolean,\r
+//\r
+//   nodeSetValue() -- returns the value as a JavaScript Array of DOM\r
+//   Node objects.\r
+//\r
+\r
+function StringValue(value) {\r
+  this.value = value;\r
+  this.type = 'string';\r
+}\r
+\r
+StringValue.prototype.stringValue = function() {\r
+  return this.value;\r
+}\r
+\r
+StringValue.prototype.booleanValue = function() {\r
+  return this.value.length > 0;\r
+}\r
+\r
+StringValue.prototype.numberValue = function() {\r
+  return this.value - 0;\r
+}\r
+\r
+StringValue.prototype.nodeSetValue = function() {\r
+  throw this + ' ' + Error().stack;\r
+}\r
+\r
+function BooleanValue(value) {\r
+  this.value = value;\r
+  this.type = 'boolean';\r
+}\r
+\r
+BooleanValue.prototype.stringValue = function() {\r
+  return '' + this.value;\r
+}\r
+\r
+BooleanValue.prototype.booleanValue = function() {\r
+  return this.value;\r
+}\r
+\r
+BooleanValue.prototype.numberValue = function() {\r
+  return this.value ? 1 : 0;\r
+}\r
+\r
+BooleanValue.prototype.nodeSetValue = function() {\r
+  throw this + ' ' + Error().stack;\r
+}\r
+\r
+function NumberValue(value) {\r
+  this.value = value;\r
+  this.type = 'number';\r
+}\r
+\r
+NumberValue.prototype.stringValue = function() {\r
+  return '' + this.value;\r
+}\r
+\r
+NumberValue.prototype.booleanValue = function() {\r
+  return !!this.value;\r
+}\r
+\r
+NumberValue.prototype.numberValue = function() {\r
+  return this.value - 0;\r
+}\r
+\r
+NumberValue.prototype.nodeSetValue = function() {\r
+  throw this + ' ' + Error().stack;\r
+}\r
+\r
+function NodeSetValue(value) {\r
+  this.value = value;\r
+  this.type = 'node-set';\r
+}\r
+\r
+NodeSetValue.prototype.stringValue = function() {\r
+  if (this.value.length == 0) {\r
+    return '';\r
+  } else {\r
+    return xmlValue(this.value[0]);\r
+  }\r
+}\r
+\r
+NodeSetValue.prototype.booleanValue = function() {\r
+  return this.value.length > 0;\r
+}\r
+\r
+NodeSetValue.prototype.numberValue = function() {\r
+  return this.stringValue() - 0;\r
+}\r
+\r
+NodeSetValue.prototype.nodeSetValue = function() {\r
+  return this.value;\r
+};\r
+\r
+// XPath expressions. They are used as nodes in the parse tree and\r
+// possess an evaluate() method to compute an XPath value given an XPath\r
+// context. Expressions are returned from the parser. Teh set of\r
+// expression classes closely mirrors the set of non terminal symbols\r
+// in the grammar. Every non trivial nonterminal symbol has a\r
+// corresponding expression class.\r
+//\r
+// The common expression interface consists of the following methods:\r
+//\r
+// evaluate(context) -- evaluates the expression, returns a value.\r
+//\r
+// toString() -- returns the XPath text representation of the\r
+// expression (defined in xsltdebug.js).\r
+//\r
+// parseTree(indent) -- returns a parse tree representation of the\r
+// expression (defined in xsltdebug.js).\r
+\r
+function TokenExpr(m) {\r
+  this.value = m;\r
+}\r
+\r
+TokenExpr.prototype.evaluate = function() {\r
+  return new StringValue(this.value);\r
+};\r
+\r
+function LocationExpr() {\r
+  this.absolute = false;\r
+  this.steps = [];\r
+}\r
+\r
+LocationExpr.prototype.appendStep = function(s) {\r
+  this.steps.push(s);\r
+}\r
+\r
+LocationExpr.prototype.prependStep = function(s) {\r
+  var steps0 = this.steps;\r
+  this.steps = [ s ];\r
+  for (var i = 0; i < steps0.length; ++i) {\r
+    this.steps.push(steps0[i]);\r
+  }\r
+};\r
+\r
+LocationExpr.prototype.evaluate = function(ctx) {\r
+  var start;\r
+  if (this.absolute) {\r
+    start = ctx.root;\r
+\r
+  } else {\r
+    start = ctx.node;\r
+  }\r
+\r
+  var nodes = [];\r
+  xPathStep(nodes, this.steps, 0, start, ctx);\r
+  return new NodeSetValue(nodes);\r
+};\r
+\r
+function xPathStep(nodes, steps, step, input, ctx) {\r
+  var s = steps[step];\r
+  var ctx2 = ctx.clone(input);\r
+  var nodelist = s.evaluate(ctx2).nodeSetValue();\r
+\r
+  for (var i = 0; i < nodelist.length; ++i) {\r
+    if (step == steps.length - 1) {\r
+      nodes.push(nodelist[i]);\r
+    } else {\r
+      xPathStep(nodes, steps, step + 1, nodelist[i], ctx);\r
+    }\r
+  }\r
+}\r
+\r
+function StepExpr(axis, nodetest, predicate) {\r
+  this.axis = axis;\r
+  this.nodetest = nodetest;\r
+  this.predicate = predicate || [];\r
+}\r
+\r
+StepExpr.prototype.appendPredicate = function(p) {\r
+  this.predicate.push(p);\r
+}\r
+\r
+StepExpr.prototype.evaluate = function(ctx) {\r
+  var input = ctx.node;\r
+  var nodelist = [];\r
+\r
+  // NOTE(mesch): When this was a switch() statement, it didn't work\r
+  // in Safari/2.0. Not sure why though; it resulted in the JavaScript\r
+  // console output "undefined" (without any line number or so).\r
+\r
+  if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {\r
+    nodelist.push(input);\r
+    for (var n = input.parentNode; n; n = input.parentNode) {\r
+      nodelist.push(n);\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.ANCESTOR) {\r
+    for (var n = input.parentNode; n; n = input.parentNode) {\r
+      nodelist.push(n);\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.ATTRIBUTE) {\r
+    copyArray(nodelist, input.attributes);\r
+\r
+  } else if (this.axis == xpathAxis.CHILD) {\r
+    copyArray(nodelist, input.childNodes);\r
+\r
+  } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {\r
+    nodelist.push(input);\r
+    xpathCollectDescendants(nodelist, input);\r
+\r
+  } else if (this.axis == xpathAxis.DESCENDANT) {\r
+    xpathCollectDescendants(nodelist, input);\r
+\r
+  } else if (this.axis == xpathAxis.FOLLOWING) {\r
+    for (var n = input.parentNode; n; n = n.parentNode) {\r
+      for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {\r
+        nodelist.push(nn);\r
+        xpathCollectDescendants(nodelist, nn);\r
+      }\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {\r
+    for (var n = input.nextSibling; n; n = input.nextSibling) {\r
+      nodelist.push(n);\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.NAMESPACE) {\r
+    alert('not implemented: axis namespace');\r
+\r
+  } else if (this.axis == xpathAxis.PARENT) {\r
+    if (input.parentNode) {\r
+      nodelist.push(input.parentNode);\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.PRECEDING) {\r
+    for (var n = input.parentNode; n; n = n.parentNode) {\r
+      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {\r
+        nodelist.push(nn);\r
+        xpathCollectDescendantsReverse(nodelist, nn);\r
+      }\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {\r
+    for (var n = input.previousSibling; n; n = input.previousSibling) {\r
+      nodelist.push(n);\r
+    }\r
+\r
+  } else if (this.axis == xpathAxis.SELF) {\r
+    nodelist.push(input);\r
+\r
+  } else {\r
+    throw 'ERROR -- NO SUCH AXIS: ' + this.axis;\r
+  }\r
+\r
+  // process node test\r
+  var nodelist0 = nodelist;\r
+  nodelist = [];\r
+  for (var i = 0; i < nodelist0.length; ++i) {\r
+    var n = nodelist0[i];\r
+    if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {\r
+      nodelist.push(n);\r
+    }\r
+  }\r
+\r
+  // process predicates\r
+  for (var i = 0; i < this.predicate.length; ++i) {\r
+    var nodelist0 = nodelist;\r
+    nodelist = [];\r
+    for (var ii = 0; ii < nodelist0.length; ++ii) {\r
+      var n = nodelist0[ii];\r
+      if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {\r
+        nodelist.push(n);\r
+      }\r
+    }\r
+  }\r
+\r
+  return new NodeSetValue(nodelist);\r
+};\r
+\r
+function NodeTestAny() {\r
+  this.value = new BooleanValue(true);\r
+}\r
+\r
+NodeTestAny.prototype.evaluate = function(ctx) {\r
+  return this.value;\r
+};\r
+\r
+function NodeTestElement() {}\r
+\r
+NodeTestElement.prototype.evaluate = function(ctx) {\r
+  return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE);\r
+}\r
+\r
+function NodeTestText() {}\r
+\r
+NodeTestText.prototype.evaluate = function(ctx) {\r
+  return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);\r
+}\r
+\r
+function NodeTestComment() {}\r
+\r
+NodeTestComment.prototype.evaluate = function(ctx) {\r
+  return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);\r
+}\r
+\r
+function NodeTestPI(target) {\r
+  this.target = target;\r
+}\r
+\r
+NodeTestPI.prototype.evaluate = function(ctx) {\r
+  return new\r
+  BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&\r
+               (!this.target || ctx.node.nodeName == this.target));\r
+}\r
+\r
+function NodeTestNC(nsprefix) {\r
+  this.regex = new RegExp("^" + nsprefix + ":");\r
+  this.nsprefix = nsprefix;\r
+}\r
+\r
+NodeTestNC.prototype.evaluate = function(ctx) {\r
+  var n = ctx.node;\r
+  return new BooleanValue(this.regex.match(n.nodeName));\r
+}\r
+\r
+function NodeTestName(name) {\r
+  this.name = name;\r
+}\r
+\r
+NodeTestName.prototype.evaluate = function(ctx) {\r
+  var n = ctx.node;\r
+  // NOTE (Patrick Lightbody): this change allows node selection to be case-insensitive\r
+  return new BooleanValue(n.nodeName.toUpperCase() == this.name.toUpperCase());\r
+}\r
+\r
+function PredicateExpr(expr) {\r
+  this.expr = expr;\r
+}\r
+\r
+PredicateExpr.prototype.evaluate = function(ctx) {\r
+  var v = this.expr.evaluate(ctx);\r
+  if (v.type == 'number') {\r
+    // NOTE(mesch): Internally, position is represented starting with\r
+    // 0, however in XPath position starts with 1. See functions\r
+    // position() and last().\r
+    return new BooleanValue(ctx.position == v.numberValue() - 1);\r
+  } else {\r
+    return new BooleanValue(v.booleanValue());\r
+  }\r
+};\r
+\r
+function FunctionCallExpr(name) {\r
+  this.name = name;\r
+  this.args = [];\r
+}\r
+\r
+FunctionCallExpr.prototype.appendArg = function(arg) {\r
+  this.args.push(arg);\r
+};\r
+\r
+FunctionCallExpr.prototype.evaluate = function(ctx) {\r
+  var fn = '' + this.name.value;\r
+  var f = this.xpathfunctions[fn];\r
+  if (f) {\r
+    return f.call(this, ctx);\r
+  } else {\r
+    Log.write('XPath NO SUCH FUNCTION ' + fn);\r
+    return new BooleanValue(false);\r
+  }\r
+};\r
+\r
+FunctionCallExpr.prototype.xpathfunctions = {\r
+  'last': function(ctx) {\r
+    assert(this.args.length == 0);\r
+    // NOTE(mesch): XPath position starts at 1.\r
+    return new NumberValue(ctx.nodelist.length);\r
+  },\r
+\r
+  'position': function(ctx) {\r
+    assert(this.args.length == 0);\r
+    // NOTE(mesch): XPath position starts at 1.\r
+    return new NumberValue(ctx.position + 1);\r
+  },\r
+\r
+  'count': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var v = this.args[0].evaluate(ctx);\r
+    return new NumberValue(v.nodeSetValue().length);\r
+  },\r
+\r
+  'id': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var e = this.args.evaluate(ctx);\r
+    var ret = [];\r
+    var ids;\r
+    if (e.type == 'node-set') {\r
+      ids = [];\r
+      for (var i = 0; i < e.length; ++i) {\r
+        var v = xmlValue(e[i]).split(/\s+/);\r
+        for (var ii = 0; ii < v.length; ++ii) {\r
+          ids.push(v[ii]);\r
+        }\r
+      }\r
+    } else {\r
+      ids = e.split(/\s+/);\r
+    }\r
+    var d = ctx.node.ownerDocument;\r
+    for (var i = 0; i < ids.length; ++i) {\r
+      var n = d.getElementById(ids[i]);\r
+      if (n) {\r
+        ret.push(n);\r
+      }\r
+    }\r
+    return new NodeSetValue(ret);\r
+  },\r
+\r
+  'local-name': function(ctx) {\r
+    alert('not implmented yet: XPath function local-name()');\r
+  },\r
+\r
+  'namespace-uri': function(ctx) {\r
+    alert('not implmented yet: XPath function namespace-uri()');\r
+  },\r
+\r
+  'name': function(ctx) {\r
+    assert(this.args.length == 1 || this.args.length == 0);\r
+    var n;\r
+    if (this.args.length == 0) {\r
+      n = [ ctx.node ];\r
+    } else {\r
+      n = this.args[0].evaluate(ctx).nodeSetValue();\r
+    }\r
+\r
+    if (n.length == 0) {\r
+      return new StringValue('');\r
+    } else {\r
+      return new StringValue(n[0].nodeName);\r
+    }\r
+  },\r
+\r
+  'string':  function(ctx) {\r
+    assert(this.args.length == 1 || this.args.length == 0);\r
+    if (this.args.length == 0) {\r
+      return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());\r
+    } else {\r
+      return new StringValue(this.args[0].evaluate(ctx).stringValue());\r
+    }\r
+  },\r
+\r
+  'concat': function(ctx) {\r
+    var ret = '';\r
+    for (var i = 0; i < this.args.length; ++i) {\r
+      ret += this.args[i].evaluate(ctx).stringValue();\r
+    }\r
+    return new StringValue(ret);\r
+  },\r
+\r
+  'starts-with': function(ctx) {\r
+    assert(this.args.length == 2);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).stringValue();\r
+    return new BooleanValue(s0.indexOf(s1) == 0);\r
+  },\r
+\r
+  'contains': function(ctx) {\r
+    assert(this.args.length == 2);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).stringValue();\r
+    return new BooleanValue(s0.indexOf(s1) != -1);\r
+  },\r
+\r
+  'substring-before': function(ctx) {\r
+    assert(this.args.length == 2);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).stringValue();\r
+    var i = s0.indexOf(s1);\r
+    var ret;\r
+    if (i == -1) {\r
+      ret = '';\r
+    } else {\r
+      ret = s0.substr(0,i);\r
+    }\r
+    return new StringValue(ret);\r
+  },\r
+\r
+  'substring-after': function(ctx) {\r
+    assert(this.args.length == 2);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).stringValue();\r
+    var i = s0.indexOf(s1);\r
+    var ret;\r
+    if (i == -1) {\r
+      ret = '';\r
+    } else {\r
+      ret = s0.substr(i + s1.length);\r
+    }\r
+    return new StringValue(ret);\r
+  },\r
+\r
+  'substring': function(ctx) {\r
+    // NOTE: XPath defines the position of the first character in a\r
+    // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).\r
+    assert(this.args.length == 2 || this.args.length == 3);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).numberValue();\r
+    var ret;\r
+    if (this.args.length == 2) {\r
+      var i1 = Math.max(0, Math.round(s1) - 1);\r
+      ret = s0.substr(i1);\r
+\r
+    } else {\r
+      var s2 = this.args[2].evaluate(ctx).numberValue();\r
+      var i0 = Math.round(s1) - 1;\r
+      var i1 = Math.max(0, i0);\r
+      var i2 = Math.round(s2) - Math.max(0, -i0);\r
+      ret = s0.substr(i1, i2);\r
+    }\r
+    return new StringValue(ret);\r
+  },\r
+\r
+  'string-length': function(ctx) {\r
+    var s;\r
+    if (this.args.length > 0) {\r
+      s = this.args[0].evaluate(ctx).stringValue();\r
+    } else {\r
+      s = new NodeSetValue([ ctx.node ]).stringValue();\r
+    }\r
+    return new NumberValue(s.length);\r
+  },\r
+\r
+  'normalize-space': function(ctx) {\r
+    var s;\r
+    if (this.args.length > 0) {\r
+      s = this.args[0].evaluate(ctx).stringValue();\r
+    } else {\r
+      s = new NodeSetValue([ ctx.node ]).stringValue();\r
+    }\r
+    s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');\r
+    return new StringValue(s);\r
+  },\r
+\r
+  'translate': function(ctx) {\r
+    assert(this.args.length == 3);\r
+    var s0 = this.args[0].evaluate(ctx).stringValue();\r
+    var s1 = this.args[1].evaluate(ctx).stringValue();\r
+    var s2 = this.args[2].evaluate(ctx).stringValue();\r
+\r
+    for (var i = 0; i < s1.length; ++i) {\r
+      s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));\r
+    }\r
+    return new StringValue(s0);\r
+  },\r
+\r
+  'boolean': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());\r
+  },\r
+\r
+  'not': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var ret = !this.args[0].evaluate(ctx).booleanValue();\r
+    return new BooleanValue(ret);\r
+  },\r
+\r
+  'true': function(ctx) {\r
+    assert(this.args.length == 0);\r
+    return new BooleanValue(true);\r
+  },\r
+\r
+  'false': function(ctx) {\r
+    assert(this.args.length == 0);\r
+    return new BooleanValue(false);\r
+  },\r
+\r
+  'lang': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var lang = this.args[0].evaluate(ctx).stringValue();\r
+    var xmllang;\r
+    var n = ctx.node;\r
+    while (n && n != n.parentNode /* just in case ... */) {\r
+      xmllang = n.getAttribute('xml:lang');\r
+      if (xmllang) {\r
+        break;\r
+      }\r
+      n = n.parentNode;\r
+    }\r
+    if (!xmllang) {\r
+      return new BooleanValue(false);\r
+    } else {\r
+      var re = new RegExp('^' + lang + '$', 'i');\r
+      return new BooleanValue(xmllang.match(re) ||\r
+                              xmllang.replace(/_.*$/,'').match(re));\r
+    }\r
+  },\r
+\r
+  'number': function(ctx) {\r
+    assert(this.args.length == 1 || this.args.length == 0);\r
+\r
+    if (this.args.length == 1) {\r
+      return new NumberValue(this.args[0].evaluate(ctx).numberValue());\r
+    } else {\r
+      return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());\r
+    }\r
+  },\r
+\r
+  'sum': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var n = this.args[0].evaluate(ctx).nodeSetValue();\r
+    var sum = 0;\r
+    for (var i = 0; i < n.length; ++i) {\r
+      sum += xmlValue(n[i]) - 0;\r
+    }\r
+    return new NumberValue(sum);\r
+  },\r
+\r
+  'floor': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var num = this.args[0].evaluate(ctx).numberValue();\r
+    return new NumberValue(Math.floor(num));\r
+  },\r
+\r
+  'ceiling': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var num = this.args[0].evaluate(ctx).numberValue();\r
+    return new NumberValue(Math.ceil(num));\r
+  },\r
+\r
+  'round': function(ctx) {\r
+    assert(this.args.length == 1);\r
+    var num = this.args[0].evaluate(ctx).numberValue();\r
+    return new NumberValue(Math.round(num));\r
+  },\r
+\r
+  // TODO(mesch): The following functions are custom. There is a\r
+  // standard that defines how to add functions, which should be\r
+  // applied here.\r
+\r
+  'ext-join': function(ctx) {\r
+    assert(this.args.length == 2);\r
+    var nodes = this.args[0].evaluate(ctx).nodeSetValue();\r
+    var delim = this.args[1].evaluate(ctx).stringValue();\r
+    var ret = '';\r
+    for (var i = 0; i < nodes.length; ++i) {\r
+      if (ret) {\r
+        ret += delim;\r
+      }\r
+      ret += xmlValue(nodes[i]);\r
+    }\r
+    return new StringValue(ret);\r
+  },\r
+\r
+  // ext-if() evaluates and returns its second argument, if the\r
+  // boolean value of its first argument is true, otherwise it\r
+  // evaluates and returns its third argument.\r
+\r
+  'ext-if': function(ctx) {\r
+    assert(this.args.length == 3);\r
+    if (this.args[0].evaluate(ctx).booleanValue()) {\r
+      return this.args[1].evaluate(ctx);\r
+    } else {\r
+      return this.args[2].evaluate(ctx);\r
+    }\r
+  },\r
+\r
+  'ext-sprintf': function(ctx) {\r
+    assert(this.args.length >= 1);\r
+    var args = [];\r
+    for (var i = 0; i < this.args.length; ++i) {\r
+      args.push(this.args[i].evaluate(ctx).stringValue());\r
+    }\r
+    return new StringValue(sprintf.apply(null, args));\r
+  },\r
+\r
+  // ext-cardinal() evaluates its single argument as a number, and\r
+  // returns the current node that many times. It can be used in the\r
+  // select attribute to iterate over an integer range.\r
+  \r
+  'ext-cardinal': function(ctx) {\r
+    assert(this.args.length >= 1);\r
+    var c = this.args[0].evaluate(ctx).numberValue();\r
+    var ret = [];\r
+    for (var i = 0; i < c; ++i) {\r
+      ret.push(ctx.node);\r
+    }\r
+    return new NodeSetValue(ret);\r
+  }\r
+};\r
+\r
+function UnionExpr(expr1, expr2) {\r
+  this.expr1 = expr1;\r
+  this.expr2 = expr2;\r
+}\r
+\r
+UnionExpr.prototype.evaluate = function(ctx) {\r
+  var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();\r
+  var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();\r
+  var I1 = nodes1.length;\r
+  for (var i2 = 0; i2 < nodes2.length; ++i2) {\r
+    for (var i1 = 0; i1 < I1; ++i1) {\r
+      if (nodes1[i1] == nodes2[i2]) {\r
+        // break inner loop and continue outer loop, labels confuse\r
+        // the js compiler, so we don't use them here.\r
+        i1 = I1;\r
+      }\r
+    }\r
+    nodes1.push(nodes2[i2]);\r
+  }\r
+  return new NodeSetValue(nodes2);\r
+};\r
+\r
+function PathExpr(filter, rel) {\r
+  this.filter = filter;\r
+  this.rel = rel;\r
+}\r
+\r
+PathExpr.prototype.evaluate = function(ctx) {\r
+  var nodes = this.filter.evaluate(ctx).nodeSetValue();\r
+  var nodes1 = [];\r
+  for (var i = 0; i < nodes.length; ++i) {\r
+    var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();\r
+    for (var ii = 0; ii < nodes0.length; ++ii) {\r
+      nodes1.push(nodes0[ii]);\r
+    }\r
+  }\r
+  return new NodeSetValue(nodes1);\r
+};\r
+\r
+function FilterExpr(expr, predicate) {\r
+  this.expr = expr;\r
+  this.predicate = predicate;\r
+}\r
+\r
+FilterExpr.prototype.evaluate = function(ctx) {\r
+  var nodes = this.expr.evaluate(ctx).nodeSetValue();\r
+  for (var i = 0; i < this.predicate.length; ++i) {\r
+    var nodes0 = nodes;\r
+    nodes = [];\r
+    for (var j = 0; j < nodes0.length; ++j) {\r
+      var n = nodes0[j];\r
+      if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {\r
+        nodes.push(n);\r
+      }\r
+    }\r
+  }\r
+\r
+  return new NodeSetValue(nodes);\r
+}\r
+\r
+function UnaryMinusExpr(expr) {\r
+  this.expr = expr;\r
+}\r
+\r
+UnaryMinusExpr.prototype.evaluate = function(ctx) {\r
+  return new NumberValue(-this.expr.evaluate(ctx).numberValue());\r
+};\r
+\r
+function BinaryExpr(expr1, op, expr2) {\r
+  this.expr1 = expr1;\r
+  this.expr2 = expr2;\r
+  this.op = op;\r
+}\r
+\r
+BinaryExpr.prototype.evaluate = function(ctx) {\r
+  var ret;\r
+  switch (this.op.value) {\r
+    case 'or':\r
+      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||\r
+                             this.expr2.evaluate(ctx).booleanValue());\r
+      break;\r
+\r
+    case 'and':\r
+      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&\r
+                             this.expr2.evaluate(ctx).booleanValue());\r
+      break;\r
+\r
+    case '+':\r
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +\r
+                            this.expr2.evaluate(ctx).numberValue());\r
+      break;\r
+\r
+    case '-':\r
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -\r
+                            this.expr2.evaluate(ctx).numberValue());\r
+      break;\r
+\r
+    case '*':\r
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *\r
+                            this.expr2.evaluate(ctx).numberValue());\r
+      break;\r
+\r
+    case 'mod':\r
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %\r
+                            this.expr2.evaluate(ctx).numberValue());\r
+      break;\r
+\r
+    case 'div':\r
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /\r
+                            this.expr2.evaluate(ctx).numberValue());\r
+      break;\r
+\r
+    case '=':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });\r
+      break;\r
+\r
+    case '!=':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });\r
+      break;\r
+\r
+    case '<':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });\r
+      break;\r
+\r
+    case '<=':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });\r
+      break;\r
+\r
+    case '>':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });\r
+      break;\r
+\r
+    case '>=':\r
+      ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });\r
+      break;\r
+\r
+    default:\r
+      alert('BinaryExpr.evaluate: ' + this.op.value);\r
+  }\r
+  return ret;\r
+};\r
+\r
+BinaryExpr.prototype.compare = function(ctx, cmp) {\r
+  var v1 = this.expr1.evaluate(ctx);\r
+  var v2 = this.expr2.evaluate(ctx);\r
+\r
+  var ret;\r
+  if (v1.type == 'node-set' && v2.type == 'node-set') {\r
+    var n1 = v1.nodeSetValue();\r
+    var n2 = v2.nodeSetValue();\r
+    ret = false;\r
+    for (var i1 = 0; i1 < n1.length; ++i1) {\r
+      for (var i2 = 0; i2 < n2.length; ++i2) {\r
+        if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {\r
+          ret = true;\r
+          // Break outer loop. Labels confuse the jscompiler and we\r
+          // don't use them.\r
+          i2 = n2.length;\r
+          i1 = n1.length;\r
+        }\r
+      }\r
+    }\r
+\r
+  } else if (v1.type == 'node-set' || v2.type == 'node-set') {\r
+\r
+    if (v1.type == 'number') {\r
+      var s = v1.numberValue();\r
+      var n = v2.nodeSetValue();\r
+\r
+      ret = false;\r
+      for (var i = 0;  i < n.length; ++i) {\r
+        var nn = xmlValue(n[i]) - 0;\r
+        if (cmp(s, nn)) {\r
+          ret = true;\r
+          break;\r
+        }\r
+      }\r
+\r
+    } else if (v2.type == 'number') {\r
+      var n = v1.nodeSetValue();\r
+      var s = v2.numberValue();\r
+\r
+      ret = false;\r
+      for (var i = 0;  i < n.length; ++i) {\r
+        var nn = xmlValue(n[i]) - 0;\r
+        if (cmp(nn, s)) {\r
+          ret = true;\r
+          break;\r
+        }\r
+      }\r
+\r
+    } else if (v1.type == 'string') {\r
+      var s = v1.stringValue();\r
+      var n = v2.nodeSetValue();\r
+\r
+      ret = false;\r
+      for (var i = 0;  i < n.length; ++i) {\r
+        var nn = xmlValue(n[i]);\r
+        if (cmp(s, nn)) {\r
+          ret = true;\r
+          break;\r
+        }\r
+      }\r
+\r
+    } else if (v2.type == 'string') {\r
+      var n = v1.nodeSetValue();\r
+      var s = v2.stringValue();\r
+\r
+      ret = false;\r
+      for (var i = 0;  i < n.length; ++i) {\r
+        var nn = xmlValue(n[i]);\r
+        if (cmp(nn, s)) {\r
+          ret = true;\r
+          break;\r
+        }\r
+      }\r
+\r
+    } else {\r
+      ret = cmp(v1.booleanValue(), v2.booleanValue());\r
+    }\r
+\r
+  } else if (v1.type == 'boolean' || v2.type == 'boolean') {\r
+    ret = cmp(v1.booleanValue(), v2.booleanValue());\r
+\r
+  } else if (v1.type == 'number' || v2.type == 'number') {\r
+    ret = cmp(v1.numberValue(), v2.numberValue());\r
+\r
+  } else {\r
+    ret = cmp(v1.stringValue(), v2.stringValue());\r
+  }\r
+\r
+  return new BooleanValue(ret);\r
+}\r
+\r
+function LiteralExpr(value) {\r
+  this.value = value;\r
+}\r
+\r
+LiteralExpr.prototype.evaluate = function(ctx) {\r
+  return new StringValue(this.value);\r
+};\r
+\r
+function NumberExpr(value) {\r
+  this.value = value;\r
+}\r
+\r
+NumberExpr.prototype.evaluate = function(ctx) {\r
+  return new NumberValue(this.value);\r
+};\r
+\r
+function VariableExpr(name) {\r
+  this.name = name;\r
+}\r
+\r
+VariableExpr.prototype.evaluate = function(ctx) {\r
+  return ctx.getVariable(this.name);\r
+}\r
+\r
+// Factory functions for semantic values (i.e. Expressions) of the\r
+// productions in the grammar. When a production is matched to reduce\r
+// the current parse state stack, the function is called with the\r
+// semantic values of the matched elements as arguments, and returns\r
+// another semantic value. The semantic value is a node of the parse\r
+// tree, an expression object with an evaluate() method that evaluates the\r
+// expression in an actual context. These factory functions are used\r
+// in the specification of the grammar rules, below.\r
+\r
+function makeTokenExpr(m) {\r
+  return new TokenExpr(m);\r
+}\r
+\r
+function passExpr(e) {\r
+  return e;\r
+}\r
+\r
+function makeLocationExpr1(slash, rel) {\r
+  rel.absolute = true;\r
+  return rel;\r
+}\r
+\r
+function makeLocationExpr2(dslash, rel) {\r
+  rel.absolute = true;\r
+  rel.prependStep(makeAbbrevStep(dslash.value));\r
+  return rel;\r
+}\r
+\r
+function makeLocationExpr3(slash) {\r
+  var ret = new LocationExpr();\r
+  ret.appendStep(makeAbbrevStep('.'));\r
+  ret.absolute = true;\r
+  return ret;\r
+}\r
+\r
+function makeLocationExpr4(dslash) {\r
+  var ret = new LocationExpr();\r
+  ret.absolute = true;\r
+  ret.appendStep(makeAbbrevStep(dslash.value));\r
+  return ret;\r
+}\r
+\r
+function makeLocationExpr5(step) {\r
+  var ret = new LocationExpr();\r
+  ret.appendStep(step);\r
+  return ret;\r
+}\r
+\r
+function makeLocationExpr6(rel, slash, step) {\r
+  rel.appendStep(step);\r
+  return rel;\r
+}\r
+\r
+function makeLocationExpr7(rel, dslash, step) {\r
+  rel.appendStep(makeAbbrevStep(dslash.value));\r
+  return rel;\r
+}\r
+\r
+function makeStepExpr1(dot) {\r
+  return makeAbbrevStep(dot.value);\r
+}\r
+\r
+function makeStepExpr2(ddot) {\r
+  return makeAbbrevStep(ddot.value);\r
+}\r
+\r
+function makeStepExpr3(axisname, axis, nodetest) {\r
+  return new StepExpr(axisname.value, nodetest);\r
+}\r
+\r
+function makeStepExpr4(at, nodetest) {\r
+  return new StepExpr('attribute', nodetest);\r
+}\r
+\r
+function makeStepExpr5(nodetest) {\r
+  return new StepExpr('child', nodetest);\r
+}\r
+\r
+function makeStepExpr6(step, predicate) {\r
+  step.appendPredicate(predicate);\r
+  return step;\r
+}\r
+\r
+function makeAbbrevStep(abbrev) {\r
+  switch (abbrev) {\r
+  case '//':\r
+    return new StepExpr('descendant-or-self', new NodeTestAny);\r
+\r
+  case '.':\r
+    return new StepExpr('self', new NodeTestAny);\r
+\r
+  case '..':\r
+    return new StepExpr('parent', new NodeTestAny);\r
+  }\r
+}\r
+\r
+function makeNodeTestExpr1(asterisk) {\r
+  return new NodeTestElement;\r
+}\r
+\r
+function makeNodeTestExpr2(ncname, colon, asterisk) {\r
+  return new NodeTestNC(ncname.value);\r
+}\r
+\r
+function makeNodeTestExpr3(qname) {\r
+  return new NodeTestName(qname.value);\r
+}\r
+\r
+function makeNodeTestExpr4(typeo, parenc) {\r
+  var type = typeo.value.replace(/\s*\($/, '');\r
+  switch(type) {\r
+  case 'node':\r
+    return new NodeTestAny;\r
+\r
+  case 'text':\r
+    return new NodeTestText;\r
+\r
+  case 'comment':\r
+    return new NodeTestComment;\r
+\r
+  case 'processing-instruction':\r
+    return new NodeTestPI;\r
+  }\r
+}\r
+\r
+function makeNodeTestExpr5(typeo, target, parenc) {\r
+  var type = typeo.replace(/\s*\($/, '');\r
+  if (type != 'processing-instruction') {\r
+    throw type + ' ' + Error().stack;\r
+  }\r
+  return new NodeTestPI(target.value);\r
+}\r
+\r
+function makePredicateExpr(pareno, expr, parenc) {\r
+  return new PredicateExpr(expr);\r
+}\r
+\r
+function makePrimaryExpr(pareno, expr, parenc) {\r
+  return expr;\r
+}\r
+\r
+function makeFunctionCallExpr1(name, pareno, parenc) {\r
+  return new FunctionCallExpr(name);\r
+}\r
+\r
+function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {\r
+  var ret = new FunctionCallExpr(name);\r
+  ret.appendArg(arg1);\r
+  for (var i = 0; i < args.length; ++i) {\r
+    ret.appendArg(args[i]);\r
+  }\r
+  return ret;\r
+}\r
+\r
+function makeArgumentExpr(comma, expr) {\r
+  return expr;\r
+}\r
+\r
+function makeUnionExpr(expr1, pipe, expr2) {\r
+  return new UnionExpr(expr1, expr2);\r
+}\r
+\r
+function makePathExpr1(filter, slash, rel) {\r
+  return new PathExpr(filter, rel);\r
+}\r
+\r
+function makePathExpr2(filter, dslash, rel) {\r
+  rel.prependStep(makeAbbrevStep(dslash.value));\r
+  return new PathExpr(filter, rel);\r
+}\r
+\r
+function makeFilterExpr(expr, predicates) {\r
+  if (predicates.length > 0) {\r
+    return new FilterExpr(expr, predicates);\r
+  } else {\r
+    return expr;\r
+  }\r
+}\r
+\r
+function makeUnaryMinusExpr(minus, expr) {\r
+  return new UnaryMinusExpr(expr);\r
+}\r
+\r
+function makeBinaryExpr(expr1, op, expr2) {\r
+  return new BinaryExpr(expr1, op, expr2);\r
+}\r
+\r
+function makeLiteralExpr(token) {\r
+  // remove quotes from the parsed value:\r
+  var value = token.value.substring(1, token.value.length - 1);\r
+  return new LiteralExpr(value);\r
+}\r
+\r
+function makeNumberExpr(token) {\r
+  return new NumberExpr(token.value);\r
+}\r
+\r
+function makeVariableReference(dollar, name) {\r
+  return new VariableExpr(name.value);\r
+}\r
+\r
+// Used before parsing for optimization of common simple cases. See\r
+// the begin of xpathParse() for which they are.\r
+function makeSimpleExpr(expr) {\r
+  if (expr.charAt(0) == '$') {\r
+    return new VariableExpr(expr.substr(1));\r
+  } else if (expr.charAt(0) == '@') {\r
+    var a = new NodeTestName(expr.substr(1));\r
+    var b = new StepExpr('attribute', a);\r
+    var c = new LocationExpr();\r
+    c.appendStep(b);\r
+    return c;\r
+  } else if (expr.match(/^[0-9]+$/)) {\r
+    return new NumberExpr(expr);\r
+  } else {\r
+    var a = new NodeTestName(expr);\r
+    var b = new StepExpr('child', a);\r
+    var c = new LocationExpr();\r
+    c.appendStep(b);\r
+    return c;\r
+  }\r
+}\r
+\r
+function makeSimpleExpr2(expr) {\r
+  var steps = expr.split('/');\r
+  var c = new LocationExpr();\r
+  for (var i in steps) {\r
+    var a = new NodeTestName(steps[i]);\r
+    var b = new StepExpr('child', a);\r
+    c.appendStep(b);\r
+  }\r
+  return c;\r
+}\r
+\r
+// The axes of XPath expressions.\r
+\r
+var xpathAxis = {\r
+  ANCESTOR_OR_SELF: 'ancestor-or-self',\r
+  ANCESTOR: 'ancestor',\r
+  ATTRIBUTE: 'attribute',\r
+  CHILD: 'child',\r
+  DESCENDANT_OR_SELF: 'descendant-or-self',\r
+  DESCENDANT: 'descendant',\r
+  FOLLOWING_SIBLING: 'following-sibling',\r
+  FOLLOWING: 'following',\r
+  NAMESPACE: 'namespace',\r
+  PARENT: 'parent',\r
+  PRECEDING_SIBLING: 'preceding-sibling',\r
+  PRECEDING: 'preceding',\r
+  SELF: 'self'\r
+};\r
+\r
+var xpathAxesRe = [\r
+    xpathAxis.ANCESTOR_OR_SELF,\r
+    xpathAxis.ANCESTOR,\r
+    xpathAxis.ATTRIBUTE,\r
+    xpathAxis.CHILD,\r
+    xpathAxis.DESCENDANT_OR_SELF,\r
+    xpathAxis.DESCENDANT,\r
+    xpathAxis.FOLLOWING_SIBLING,\r
+    xpathAxis.FOLLOWING,\r
+    xpathAxis.NAMESPACE,\r
+    xpathAxis.PARENT,\r
+    xpathAxis.PRECEDING_SIBLING,\r
+    xpathAxis.PRECEDING,\r
+    xpathAxis.SELF\r
+].join('|');\r
+\r
+\r
+// The tokens of the language. The label property is just used for\r
+// generating debug output. The prec property is the precedence used\r
+// for shift/reduce resolution. Default precedence is 0 as a lookahead\r
+// token and 2 on the stack. TODO(mesch): this is certainly not\r
+// necessary and too complicated. Simplify this!\r
+\r
+// NOTE: tabular formatting is the big exception, but here it should\r
+// be OK.\r
+\r
+var TOK_PIPE =   { label: "|",   prec:   17, re: new RegExp("^\\|") };\r
+var TOK_DSLASH = { label: "//",  prec:   19, re: new RegExp("^//")  };\r
+var TOK_SLASH =  { label: "/",   prec:   30, re: new RegExp("^/")   };\r
+var TOK_AXIS =   { label: "::",  prec:   20, re: new RegExp("^::")  };\r
+var TOK_COLON =  { label: ":",   prec: 1000, re: new RegExp("^:")  };\r
+var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') };\r
+var TOK_PARENO = { label: "(",   prec:   34, re: new RegExp("^\\(") };\r
+var TOK_PARENC = { label: ")",               re: new RegExp("^\\)") };\r
+var TOK_DDOT =   { label: "..",  prec:   34, re: new RegExp("^\\.\\.") };\r
+var TOK_DOT =    { label: ".",   prec:   34, re: new RegExp("^\\.") };\r
+var TOK_AT =     { label: "@",   prec:   34, re: new RegExp("^@")   };\r
+\r
+var TOK_COMMA =  { label: ",",               re: new RegExp("^,") };\r
+\r
+var TOK_OR =     { label: "or",  prec:   10, re: new RegExp("^or\\b") };\r
+var TOK_AND =    { label: "and", prec:   11, re: new RegExp("^and\\b") };\r
+var TOK_EQ =     { label: "=",   prec:   12, re: new RegExp("^=")   };\r
+var TOK_NEQ =    { label: "!=",  prec:   12, re: new RegExp("^!=")  };\r
+var TOK_GE =     { label: ">=",  prec:   13, re: new RegExp("^>=")  };\r
+var TOK_GT =     { label: ">",   prec:   13, re: new RegExp("^>")   };\r
+var TOK_LE =     { label: "<=",  prec:   13, re: new RegExp("^<=")  };\r
+var TOK_LT =     { label: "<",   prec:   13, re: new RegExp("^<")   };\r
+var TOK_PLUS =   { label: "+",   prec:   14, re: new RegExp("^\\+"), left: true };\r
+var TOK_MINUS =  { label: "-",   prec:   14, re: new RegExp("^\\-"), left: true };\r
+var TOK_DIV =    { label: "div", prec:   15, re: new RegExp("^div\\b"), left: true };\r
+var TOK_MOD =    { label: "mod", prec:   15, re: new RegExp("^mod\\b"), left: true };\r
+\r
+var TOK_BRACKO = { label: "[",   prec:   32, re: new RegExp("^\\[") };\r
+var TOK_BRACKC = { label: "]",               re: new RegExp("^\\]") };\r
+var TOK_DOLLAR = { label: "$",               re: new RegExp("^\\$") };\r
+\r
+var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^[a-z][-\\w]*','i') };\r
+\r
+var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };\r
+var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };\r
+var TOK_LITERALQQ = {\r
+  label: "[litqq]",\r
+  prec: 20,\r
+  re: new RegExp('^"[^\\"]*"')\r
+};\r
+\r
+var TOK_NUMBER  = {\r
+  label: "[number]",\r
+  prec: 35,\r
+  re: new RegExp('^\\d+(\\.\\d*)?') };\r
+\r
+var TOK_QNAME = {\r
+  label: "[qname]",\r
+  re: new RegExp('^([a-z][-\\w]*:)?[a-z][-\\w]*','i')\r
+};\r
+\r
+var TOK_NODEO = {\r
+  label: "[nodetest-start]",\r
+  re: new RegExp('^(processing-instruction|comment|text|node)\\(')\r
+};\r
+\r
+// The table of the tokens of our grammar, used by the lexer: first\r
+// column the tag, second column a regexp to recognize it in the\r
+// input, third column the precedence of the token, fourth column a\r
+// factory function for the semantic value of the token.\r
+//\r
+// NOTE: order of this list is important, because the first match\r
+// counts. Cf. DDOT and DOT, and AXIS and COLON.\r
+\r
+var xpathTokenRules = [\r
+    TOK_DSLASH,\r
+    TOK_SLASH,\r
+    TOK_DDOT,\r
+    TOK_DOT,\r
+    TOK_AXIS,\r
+    TOK_COLON,\r
+    TOK_AXISNAME,\r
+    TOK_NODEO,\r
+    TOK_PARENO,\r
+    TOK_PARENC,\r
+    TOK_BRACKO,\r
+    TOK_BRACKC,\r
+    TOK_AT,\r
+    TOK_COMMA,\r
+    TOK_OR,\r
+    TOK_AND,\r
+    TOK_NEQ,\r
+    TOK_EQ,\r
+    TOK_GE,\r
+    TOK_GT,\r
+    TOK_LE,\r
+    TOK_LT,\r
+    TOK_PLUS,\r
+    TOK_MINUS,\r
+    TOK_ASTERISK,\r
+    TOK_PIPE,\r
+    TOK_MOD,\r
+    TOK_DIV,\r
+    TOK_LITERALQ,\r
+    TOK_LITERALQQ,\r
+    TOK_NUMBER,\r
+    TOK_QNAME,\r
+    TOK_NCNAME,\r
+    TOK_DOLLAR\r
+];\r
+\r
+// All the nonterminals of the grammar. The nonterminal objects are\r
+// identified by object identity; the labels are used in the debug\r
+// output only.\r
+var XPathLocationPath = { label: "LocationPath" };\r
+var XPathRelativeLocationPath = { label: "RelativeLocationPath" };\r
+var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" };\r
+var XPathStep = { label: "Step" };\r
+var XPathNodeTest = { label: "NodeTest" };\r
+var XPathPredicate = { label: "Predicate" };\r
+var XPathLiteral = { label: "Literal" };\r
+var XPathExpr = { label: "Expr" };\r
+var XPathPrimaryExpr = { label: "PrimaryExpr" };\r
+var XPathVariableReference = { label: "Variablereference" };\r
+var XPathNumber = { label: "Number" };\r
+var XPathFunctionCall = { label: "FunctionCall" };\r
+var XPathArgumentRemainder = { label: "ArgumentRemainder" };\r
+var XPathPathExpr = { label: "PathExpr" };\r
+var XPathUnionExpr = { label: "UnionExpr" };\r
+var XPathFilterExpr = { label: "FilterExpr" };\r
+var XPathDigits = { label: "Digits" };\r
+\r
+var xpathNonTerminals = [\r
+    XPathLocationPath,\r
+    XPathRelativeLocationPath,\r
+    XPathAbsoluteLocationPath,\r
+    XPathStep,\r
+    XPathNodeTest,\r
+    XPathPredicate,\r
+    XPathLiteral,\r
+    XPathExpr,\r
+    XPathPrimaryExpr,\r
+    XPathVariableReference,\r
+    XPathNumber,\r
+    XPathFunctionCall,\r
+    XPathArgumentRemainder,\r
+    XPathPathExpr,\r
+    XPathUnionExpr,\r
+    XPathFilterExpr,\r
+    XPathDigits\r
+];\r
+\r
+// Quantifiers that are used in the productions of the grammar.\r
+var Q_01 = { label: "?" };\r
+var Q_MM = { label: "*" };\r
+var Q_1M = { label: "+" };\r
+\r
+// Tag for left associativity (right assoc is implied by undefined).\r
+var ASSOC_LEFT = true;\r
+\r
+// The productions of the grammar. Columns of the table:\r
+//\r
+// - target nonterminal,\r
+// - pattern,\r
+// - precedence,\r
+// - semantic value factory\r
+//\r
+// The semantic value factory is a function that receives parse tree\r
+// nodes from the stack frames of the matched symbols as arguments and\r
+// returns an a node of the parse tree. The node is stored in the top\r
+// stack frame along with the target object of the rule. The node in\r
+// the parse tree is an expression object that has an evaluate() method\r
+// and thus evaluates XPath expressions.\r
+//\r
+// The precedence is used to decide between reducing and shifting by\r
+// comparing the precendence of the rule that is candidate for\r
+// reducing with the precedence of the look ahead token. Precedence of\r
+// -1 means that the precedence of the tokens in the pattern is used\r
+// instead. TODO: It shouldn't be necessary to explicitly assign\r
+// precedences to rules.\r
+\r
+var xpathGrammarRules =\r
+  [\r
+   [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,\r
+     passExpr ],\r
+   [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,\r
+     passExpr ],\r
+\r
+   [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18, \r
+     makeLocationExpr1 ],\r
+   [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,\r
+     makeLocationExpr2 ],\r
+\r
+   [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,\r
+     makeLocationExpr3 ],\r
+   [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,\r
+     makeLocationExpr4 ],\r
+\r
+   [ XPathRelativeLocationPath, [ XPathStep ], 31,\r
+     makeLocationExpr5 ],\r
+   [ XPathRelativeLocationPath,\r
+     [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,\r
+     makeLocationExpr6 ],\r
+   [ XPathRelativeLocationPath,\r
+     [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,\r
+     makeLocationExpr7 ],\r
+\r
+   [ XPathStep, [ TOK_DOT ], 33,\r
+     makeStepExpr1 ],\r
+   [ XPathStep, [ TOK_DDOT ], 33,\r
+     makeStepExpr2 ],\r
+   [ XPathStep,\r
+     [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,\r
+     makeStepExpr3 ],\r
+   [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,\r
+     makeStepExpr4 ],\r
+   [ XPathStep, [ XPathNodeTest ], 33,\r
+     makeStepExpr5 ],\r
+   [ XPathStep, [ XPathStep, XPathPredicate ], 33,\r
+     makeStepExpr6 ],\r
+\r
+   [ XPathNodeTest, [ TOK_ASTERISK ], 33,\r
+     makeNodeTestExpr1 ],\r
+   [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,\r
+     makeNodeTestExpr2 ],\r
+   [ XPathNodeTest, [ TOK_QNAME ], 33,\r
+     makeNodeTestExpr3 ],\r
+   [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,\r
+     makeNodeTestExpr4 ],\r
+   [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,\r
+     makeNodeTestExpr5 ],\r
+\r
+   [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,\r
+     makePredicateExpr ],\r
+\r
+   [ XPathPrimaryExpr, [ XPathVariableReference ], 33,\r
+     passExpr ],\r
+   [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,\r
+     makePrimaryExpr ],\r
+   [ XPathPrimaryExpr, [ XPathLiteral ], 30,\r
+     passExpr ],\r
+   [ XPathPrimaryExpr, [ XPathNumber ], 30,\r
+     passExpr ],\r
+   [ XPathPrimaryExpr, [ XPathFunctionCall ], 30,\r
+     passExpr ],\r
+\r
+   [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,\r
+     makeFunctionCallExpr1 ],\r
+   [ XPathFunctionCall,\r
+     [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,\r
+       TOK_PARENC ], -1,\r
+     makeFunctionCallExpr2 ],\r
+   [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,\r
+     makeArgumentExpr ],\r
+\r
+   [ XPathUnionExpr, [ XPathPathExpr ], 20,\r
+     passExpr ],\r
+   [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,\r
+     makeUnionExpr ],\r
+\r
+   [ XPathPathExpr, [ XPathLocationPath ], 20, \r
+     passExpr ], \r
+   [ XPathPathExpr, [ XPathFilterExpr ], 19, \r
+     passExpr ], \r
+   [ XPathPathExpr, \r
+     [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 20,\r
+     makePathExpr1 ],\r
+   [ XPathPathExpr,\r
+     [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 20,\r
+     makePathExpr2 ],\r
+\r
+   [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 20,\r
+     makeFilterExpr ], \r
+\r
+   [ XPathExpr, [ XPathPrimaryExpr ], 16,\r
+     passExpr ],\r
+   [ XPathExpr, [ XPathUnionExpr ], 16,\r
+     passExpr ],\r
+\r
+   [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,\r
+     makeUnaryMinusExpr ],\r
+\r
+   [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+   [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+\r
+   [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+   [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+\r
+   [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+   [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+   [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+   [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,\r
+     makeBinaryExpr ],\r
+\r
+   [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,\r
+     makeBinaryExpr, ASSOC_LEFT ],\r
+   [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,\r
+     makeBinaryExpr, ASSOC_LEFT ],\r
+\r
+   [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,\r
+     makeBinaryExpr, ASSOC_LEFT ],\r
+   [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,\r
+     makeBinaryExpr, ASSOC_LEFT ],\r
+   [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,\r
+     makeBinaryExpr, ASSOC_LEFT ],\r
+\r
+   [ XPathLiteral, [ TOK_LITERALQ ], -1,\r
+     makeLiteralExpr ],\r
+   [ XPathLiteral, [ TOK_LITERALQQ ], -1,\r
+     makeLiteralExpr ],\r
+\r
+   [ XPathNumber, [ TOK_NUMBER ], -1,\r
+     makeNumberExpr ],\r
+\r
+   [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,\r
+     makeVariableReference ]\r
+   ];\r
+\r
+// That function computes some optimizations of the above data\r
+// structures and will be called right here. It merely takes the\r
+// counter variables out of the global scope.\r
+\r
+var xpathRules = [];\r
+\r
+function xpathParseInit() {\r
+  if (xpathRules.length) {\r
+    return;\r
+  }\r
+\r
+  // Some simple optimizations for the xpath expression parser: sort\r
+  // grammar rules descending by length, so that the longest match is\r
+  // first found.\r
+\r
+  xpathGrammarRules.sort(function(a,b) {\r
+    var la = a[1].length;\r
+    var lb = b[1].length;\r
+    if (la < lb) {\r
+      return 1;\r
+    } else if (la > lb) {\r
+      return -1;\r
+    } else {\r
+      return 0;\r
+    }\r
+  });\r
+\r
+  var k = 1;\r
+  for (var i = 0; i < xpathNonTerminals.length; ++i) {\r
+    xpathNonTerminals[i].key = k++;\r
+  }\r
+\r
+  for (i = 0; i < xpathTokenRules.length; ++i) {\r
+    xpathTokenRules[i].key = k++;\r
+  }\r
+\r
+  Log.write('XPath parse INIT: ' + k + ' rules');\r
+\r
+  // Another slight optimization: sort the rules into bins according\r
+  // to the last element (observing quantifiers), so we can restrict\r
+  // the match against the stack to the subest of rules that match the\r
+  // top of the stack.\r
+  //\r
+  // TODO(mesch): What we actually want is to compute states as in\r
+  // bison, so that we don't have to do any explicit and iterated\r
+  // match against the stack.\r
+\r
+  function push_(array, position, element) {\r
+    if (!array[position]) {\r
+      array[position] = [];\r
+    }\r
+    array[position].push(element);\r
+  }\r
+\r
+  for (i = 0; i < xpathGrammarRules.length; ++i) {\r
+    var rule = xpathGrammarRules[i];\r
+    var pattern = rule[1];\r
+\r
+    for (var j = pattern.length - 1; j >= 0; --j) {\r
+      if (pattern[j] == Q_1M) {\r
+        push_(xpathRules, pattern[j-1].key, rule);\r
+        break;\r
+        \r
+      } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {\r
+        push_(xpathRules, pattern[j-1].key, rule);\r
+        --j;\r
+\r
+      } else {\r
+        push_(xpathRules, pattern[j].key, rule);\r
+        break;\r
+      }\r
+    }\r
+  }\r
+\r
+  Log.write('XPath parse INIT: ' + xpathRules.length + ' rule bins');\r
+  \r
+  var sum = 0;\r
+  mapExec(xpathRules, function(i) {\r
+    if (i) {\r
+      sum += i.length;\r
+    }\r
+  });\r
+  \r
+  Log.write('XPath parse INIT: ' + (sum / xpathRules.length) + ' average bin size');\r
+}\r
+\r
+// Local utility functions that are used by the lexer or parser.\r
+\r
+function xpathCollectDescendants(nodelist, node) {\r
+  for (var n = node.firstChild; n; n = n.nextSibling) {\r
+    nodelist.push(n);\r
+    arguments.callee(nodelist, n);\r
+  }\r
+}\r
+\r
+function xpathCollectDescendantsReverse(nodelist, node) {\r
+  for (var n = node.lastChild; n; n = n.previousSibling) {\r
+    nodelist.push(n);\r
+    arguments.callee(nodelist, n);\r
+  }\r
+}\r
+\r
+\r
+// The entry point for the library: match an expression against a DOM\r
+// node. Returns an XPath value.\r
+function xpathDomEval(expr, node) {\r
+  var expr1 = xpathParse(expr);\r
+  var ret = expr1.evaluate(new ExprContext(node));\r
+  return ret;\r
+}\r
+\r
+// Utility function to sort a list of nodes. Used by xsltSort() and\r
+// nxslSelect().\r
+function xpathSort(input, sort) {\r
+  if (sort.length == 0) {\r
+    return;\r
+  }\r
+\r
+  var sortlist = [];\r
+\r
+  for (var i = 0; i < input.nodelist.length; ++i) {\r
+    var node = input.nodelist[i];\r
+    var sortitem = { node: node, key: [] };\r
+    var context = input.clone(node, 0, [ node ]);\r
+    \r
+    for (var j = 0; j < sort.length; ++j) {\r
+      var s = sort[j];\r
+      var value = s.expr.evaluate(context);\r
+\r
+      var evalue;\r
+      if (s.type == 'text') {\r
+        evalue = value.stringValue();\r
+      } else if (s.type == 'number') {\r
+        evalue = value.numberValue();\r
+      }\r
+      sortitem.key.push({ value: evalue, order: s.order });\r
+    }\r
+\r
+    // Make the sort stable by adding a lowest priority sort by\r
+    // id. This is very convenient and furthermore required by the\r
+    // spec ([XSLT] - Section 10 Sorting).\r
+    sortitem.key.push({ value: i, order: 'ascending' });\r
+\r
+    sortlist.push(sortitem);\r
+  }\r
+\r
+  sortlist.sort(xpathSortByKey);\r
+\r
+  var nodes = [];\r
+  for (var i = 0; i < sortlist.length; ++i) {\r
+    nodes.push(sortlist[i].node);\r
+  }\r
+  input.nodelist = nodes;\r
+  input.setNode(nodes[0], 0);\r
+}\r
+\r
+\r
+// Sorts by all order criteria defined. According to the JavaScript\r
+// spec ([ECMA] Section 11.8.5), the compare operators compare strings\r
+// as strings and numbers as numbers.\r
+//\r
+// NOTE: In browsers which do not follow the spec, this breaks only in\r
+// the case that numbers should be sorted as strings, which is very\r
+// uncommon.\r
+\r
+function xpathSortByKey(v1, v2) {\r
+  // NOTE: Sort key vectors of different length never occur in\r
+  // xsltSort.\r
+\r
+  for (var i = 0; i < v1.key.length; ++i) {\r
+    var o = v1.key[i].order == 'descending' ? -1 : 1;\r
+    if (v1.key[i].value > v2.key[i].value) {\r
+      return +1 * o;\r
+    } else if (v1.key[i].value < v2.key[i].value) {\r
+      return -1 * o;\r
+    }\r
+  }\r
+\r
+  return 0;\r
+}\r