browsercms 3.1.4 → 3.1.5

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