envjs19 0.3.8.20101029121421

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