envjs 0.1.7 → 0.2.0

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