ayril 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/test/selector.js ADDED
@@ -0,0 +1,521 @@
1
+
2
+ Object.extend = function(destination, source) {
3
+ for (var property in source)
4
+ destination[property] = source[property];
5
+ return destination;
6
+ };
7
+
8
+ Object.keys = function(object) {
9
+ var results = [];
10
+ for (var property in object)
11
+ results.push(property);
12
+ return results;
13
+ };
14
+
15
+
16
+ function $A(iterable) {
17
+ if (!iterable) return [];
18
+ // Safari <2.0.4 crashes when accessing property of a node list with property accessor.
19
+ // It nevertheless works fine with `in` operator, which is why we use it here
20
+ if ('toArray' in iterable) return iterable.toArray();
21
+ var length = iterable.length || 0, results = new Array(length);
22
+ while (length--) results[length] = iterable[length];
23
+ return results;
24
+ }
25
+
26
+
27
+ var Class = (function() {
28
+ function create() {
29
+ var parent = null, properties = $A(arguments);
30
+ if (Object.isFunction(properties[0]))
31
+ parent = properties.shift();
32
+
33
+ function klass() {
34
+ this.initialize.apply(this, arguments);
35
+ }
36
+
37
+ Object.extend(klass, Class.Methods);
38
+ klass.superclass = parent;
39
+ klass.subclasses = [];
40
+
41
+ if (parent) {
42
+ var subclass = function() {};
43
+ subclass.prototype = parent.prototype;
44
+ klass.prototype = new subclass;
45
+ parent.subclasses.push(klass);
46
+ }
47
+
48
+ for (var i = 0; i < properties.length; i++)
49
+ klass.addMethods(properties[i]);
50
+
51
+ if (!klass.prototype.initialize)
52
+ klass.prototype.initialize = Prototype.emptyFunction;
53
+
54
+ klass.prototype.constructor = klass;
55
+ return klass;
56
+ }
57
+
58
+ function addMethods(source) {
59
+ var ancestor = this.superclass && this.superclass.prototype;
60
+ var properties = Object.keys(source);
61
+
62
+ if (!Object.keys({ toString: true }).length) {
63
+ if (source.toString != Object.prototype.toString)
64
+ properties.push("toString");
65
+ if (source.valueOf != Object.prototype.valueOf)
66
+ properties.push("valueOf");
67
+ }
68
+
69
+ for (var i = 0, length = properties.length; i < length; i++) {
70
+ var property = properties[i], value = source[property];
71
+ if (ancestor && Object.isFunction(value) &&
72
+ value.argumentNames().first() == "$super") {
73
+ var method = value;
74
+ value = (function(m) {
75
+ return function() { return ancestor[m].apply(this, arguments); };
76
+ })(property).wrap(method);
77
+
78
+ value.valueOf = method.valueOf.bind(method);
79
+ value.toString = method.toString.bind(method);
80
+ }
81
+ this.prototype[property] = value;
82
+ }
83
+
84
+ return this;
85
+ }
86
+
87
+ return {
88
+ create: create,
89
+ Methods: {
90
+ addMethods: addMethods
91
+ }
92
+ };
93
+ })();
94
+
95
+
96
+ Object.extend(Object, {
97
+ isFunction: function(object) {
98
+ return typeof object === "function";
99
+ },
100
+
101
+ isString: function(object) {
102
+ function getClass(object) {
103
+ return Object.prototype.toString.call(object)
104
+ .match(/^\[object\s(.*)\]$/)[1];
105
+ }
106
+
107
+ return getClass(object) === "String";
108
+ }
109
+ });
110
+
111
+
112
+ RegExp.escape = function(str) {
113
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
114
+ };
115
+
116
+
117
+ String.interpret = function(value) {
118
+ return value == null ? '' : String(value);
119
+ };
120
+
121
+ Object.extend(String.prototype, {
122
+ strip: function() {
123
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
124
+ },
125
+
126
+ startsWith: function(pattern) {
127
+ return this.indexOf(pattern) === 0;
128
+ },
129
+
130
+ gsub: function(pattern, replacement) {
131
+ function prepareReplacement(replacement) {
132
+ if (Object.isFunction(replacement)) return replacement;
133
+ var template = new Template(replacement);
134
+ return function(match) { return template.evaluate(match) };
135
+ }
136
+
137
+ var result = '', source = this, match;
138
+ replacement = prepareReplacement(replacement);
139
+
140
+ if (Object.isString(pattern))
141
+ pattern = RegExp.escape(pattern);
142
+
143
+ if (!(pattern.length || pattern.source)) {
144
+ replacement = replacement('');
145
+ return replacement + source.split('').join(replacement) + replacement;
146
+ }
147
+
148
+ while (source.length > 0) {
149
+ if (match = source.match(pattern)) {
150
+ result += source.slice(0, match.index);
151
+ result += String.interpret(replacement(match));
152
+ source = source.slice(match.index + match[0].length);
153
+ } else {
154
+ result += source, source = '';
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ });
160
+
161
+
162
+
163
+ var Template = Class.create({
164
+ initialize: function(template, pattern) {
165
+ this.template = template.toString();
166
+ this.pattern = pattern || Template.Pattern;
167
+ },
168
+
169
+ evaluate: function(object) {
170
+ if (Object.isFunction(object.toTemplateReplacements))
171
+ object = object.toTemplateReplacements();
172
+
173
+ return this.template.gsub(this.pattern, function(match) {
174
+ if (object == null) return '';
175
+
176
+ var before = match[1] || '';
177
+ if (before == '\\') return match[2];
178
+
179
+ var ctx = object, expr = match[3];
180
+ var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
181
+ match = pattern.exec(expr);
182
+ if (match == null) return before;
183
+
184
+ while (match != null) {
185
+ var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
186
+ ctx = ctx[comp];
187
+ if (null == ctx || '' == match[3]) break;
188
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
189
+ match = pattern.exec(expr);
190
+ }
191
+
192
+ return before + String.interpret(ctx);
193
+ });
194
+ }
195
+ });
196
+ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
197
+
198
+
199
+ var Selector = Class.create({
200
+ initialize: function(expression) {
201
+ this.expression = expression.strip();
202
+ this.compileXPathMatcher();
203
+ },
204
+
205
+
206
+ compileXPathMatcher: function() {
207
+ var e = this.expression, ps = Selector.patterns,
208
+ x = Selector.xpath, le, m, len = ps.length, name;
209
+
210
+ if (Selector._cache[e]) {
211
+ this.xpath = Selector._cache[e]; return;
212
+ }
213
+
214
+ this.matcher = ['.//*'];
215
+ while (e && le != e && (/\S/).test(e)) {
216
+ le = e;
217
+ for (var i = 0; i<len; i++) {
218
+ name = ps[i].name;
219
+ if (m = e.match(ps[i].re)) {
220
+ this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
221
+ new Template(x[name]).evaluate(m));
222
+ e = e.replace(m[0], '');
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ this.xpath = this.matcher.join('')
229
+ this.xpath = this.xpath.gsub(/\*?\[name\(\)='([a-zA-Z]+)'\]/, '#{1}');
230
+ Selector._cache[this.expression] = this.xpath;
231
+ },
232
+
233
+ /**
234
+ * Selector#findElements(root) -> [Element...]
235
+ * - root (Element || document): A "scope" to search within. All results will
236
+ * be descendants of this node.
237
+ *
238
+ * Searches the document for elements that match the instance's CSS
239
+ * selector.
240
+ **/
241
+ findElements: function(root) {
242
+ root = root || document;
243
+ return document._getElementsByXPath(this.xpath, root);
244
+ },
245
+
246
+ /**
247
+ * Selector#match(element) -> Boolean
248
+ *
249
+ * Tests whether a `element` matches the instance's CSS selector.
250
+ **/
251
+ match: function(element) {
252
+ this.tokens = [];
253
+
254
+ var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
255
+ var le, p, m, len = ps.length, name;
256
+
257
+ while (e && le !== e && (/\S/).test(e)) {
258
+ le = e;
259
+ for (var i = 0; i<len; i++) {
260
+ p = ps[i].re;
261
+ name = ps[i].name;
262
+ if (m = e.match(p)) {
263
+ // use the Selector.assertions methods unless the selector
264
+ // is too complex.
265
+ if (as[name]) {
266
+ this.tokens.push([name, Object.clone(m)]);
267
+ e = e.replace(m[0], '');
268
+ } else {
269
+ // reluctantly do a document-wide search
270
+ // and look for a match in the array
271
+ return this.findElements(document).include(element);
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ var match = true, name, matches;
278
+ for (var i = 0, token; token = this.tokens[i]; i++) {
279
+ name = token[0], matches = token[1];
280
+ if (!Selector.assertions[name](element, matches)) {
281
+ match = false; break;
282
+ }
283
+ }
284
+
285
+ return match;
286
+ },
287
+
288
+ toString: function() {
289
+ return this.expression;
290
+ },
291
+
292
+ inspect: function() {
293
+ return "#<Selector:" + this.expression.inspect() + ">";
294
+ }
295
+ });
296
+
297
+
298
+ Object.extend(Selector, {
299
+ _cache: { },
300
+
301
+ xpath: {
302
+ descendant: "//*",
303
+ child: "/*",
304
+ adjacent: "/following-sibling::*[1]",
305
+ laterSibling: '/following-sibling::*',
306
+ tagName: function(m) {
307
+ if (m[1] == '*') return '';
308
+ //return "[local-name()='" + m[1].toLowerCase() +
309
+ // "' or local-name()='" + m[1].toUpperCase() + "']";
310
+ return "[name()='" + m[1] + "']";
311
+ },
312
+ className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
313
+ id: "[@id='#{1}']",
314
+ attrPresence: function(m) {
315
+ m[1] = m[1].toLowerCase();
316
+ return new Template("[@#{1}]").evaluate(m);
317
+ },
318
+ attr: function(m) {
319
+ m[1] = m[1].toLowerCase();
320
+ m[3] = m[5] || m[6];
321
+ return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
322
+ },
323
+ pseudo: function(m) {
324
+ var h = Selector.xpath.pseudos[m[1]];
325
+ if (!h) return '';
326
+ if (Object.isFunction(h)) return h(m);
327
+ return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
328
+ },
329
+ operators: {
330
+ '=': "[@#{1}='#{3}']",
331
+ '!=': "[@#{1}!='#{3}']",
332
+ '^=': "[starts-with(@#{1}, '#{3}')]",
333
+ '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
334
+ '*=': "[contains(@#{1}, '#{3}')]",
335
+ '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
336
+ '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
337
+ },
338
+ pseudos: {
339
+ 'first-child': '[not(preceding-sibling::*)]',
340
+ 'last-child': '[not(following-sibling::*)]',
341
+ 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
342
+ 'empty': "[count(*) = 0 and (count(text()) = 0)]",
343
+ 'checked': "[@checked]",
344
+ 'disabled': "[(@disabled) and (@type!='hidden')]",
345
+ 'enabled': "[not(@disabled) and (@type!='hidden')]",
346
+ 'not': function(m) {
347
+ var e = m[6], p = Selector.patterns,
348
+ x = Selector.xpath, le, v, len = p.length, name;
349
+
350
+ var exclusion = [];
351
+ while (e && le != e && (/\S/).test(e)) {
352
+ le = e;
353
+ for (var i = 0; i<len; i++) {
354
+ name = p[i].name
355
+ if (m = e.match(p[i].re)) {
356
+ v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
357
+ exclusion.push("(" + v.substring(1, v.length - 1) + ")");
358
+ e = e.replace(m[0], '');
359
+ break;
360
+ }
361
+ }
362
+ }
363
+ return "[not(" + exclusion.join(" and ") + ")]";
364
+ },
365
+ 'nth-child': function(m) {
366
+ return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
367
+ },
368
+ 'nth-last-child': function(m) {
369
+ return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
370
+ },
371
+ 'nth-of-type': function(m) {
372
+ return Selector.xpath.pseudos.nth("position() ", m);
373
+ },
374
+ 'nth-last-of-type': function(m) {
375
+ return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
376
+ },
377
+ 'first-of-type': function(m) {
378
+ m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
379
+ },
380
+ 'last-of-type': function(m) {
381
+ m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
382
+ },
383
+ 'only-of-type': function(m) {
384
+ var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
385
+ },
386
+ nth: function(fragment, m) {
387
+ var mm, formula = m[6], predicate;
388
+ if (formula == 'even') formula = '2n+0';
389
+ if (formula == 'odd') formula = '2n+1';
390
+ if (mm = formula.match(/^(\d+)$/)) // digit only
391
+ return '[' + fragment + "= " + mm[1] + ']';
392
+ if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
393
+ if (mm[1] == "-") mm[1] = -1;
394
+ var a = mm[1] ? Number(mm[1]) : 1;
395
+ var b = mm[2] ? Number(mm[2]) : 0;
396
+ predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
397
+ "((#{fragment} - #{b}) div #{a} >= 0)]";
398
+ return new Template(predicate).evaluate({
399
+ fragment: fragment, a: a, b: b });
400
+ }
401
+ }
402
+ }
403
+ },
404
+
405
+ patterns: [
406
+ // combinators must be listed first
407
+ // (and descendant needs to be last combinator)
408
+ { name: 'laterSibling', re: /^\s*~\s*/ },
409
+ { name: 'child', re: /^\s*>\s*/ },
410
+ { name: 'adjacent', re: /^\s*\+\s*/ },
411
+ { name: 'descendant', re: /^\s/ },
412
+
413
+ // selectors follow
414
+ { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
415
+ { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ },
416
+ { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ },
417
+ { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
418
+ { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
419
+ { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
420
+ ],
421
+
422
+ // for Selector.match and Element#match
423
+ assertions: {
424
+ tagName: function(element, matches) {
425
+ return matches[1].toUpperCase() == element.tagName.toUpperCase();
426
+ },
427
+
428
+ className: function(element, matches) {
429
+ return Element.hasClassName(element, matches[1]);
430
+ },
431
+
432
+ id: function(element, matches) {
433
+ return element.id === matches[1];
434
+ },
435
+
436
+ attrPresence: function(element, matches) {
437
+ return Element.hasAttribute(element, matches[1]);
438
+ },
439
+
440
+ attr: function(element, matches) {
441
+ var nodeValue = Element.readAttribute(element, matches[1]);
442
+ return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
443
+ }
444
+ },
445
+ operators: {
446
+ '=': function(nv, v) { return nv == v; },
447
+ '!=': function(nv, v) { return nv != v; },
448
+ '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
449
+ '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
450
+ '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
451
+ '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
452
+ '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
453
+ '-').include('-' + (v || "").toUpperCase() + '-'); }
454
+ },
455
+
456
+ /**
457
+ * Selector.split(expression) -> [String...]
458
+ *
459
+ * Takes a string of CSS selectors separated by commas; returns an array
460
+ * of individual selectors.
461
+ *
462
+ * Safer than doing a naive `Array#split`, since selectors can have commas
463
+ * in other places.
464
+ **/
465
+ split: function(expression) {
466
+ var expressions = [];
467
+ expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
468
+ expressions.push(m[1].strip());
469
+ });
470
+ return expressions;
471
+ },
472
+
473
+ /**
474
+ * Selector.matchElements(elements, expression) -> [Element...]
475
+ *
476
+ * Filters the given collection of elements with `expression`.
477
+ *
478
+ * The only nodes returned will be those that match the given CSS selector.
479
+ **/
480
+ matchElements: function(elements, expression) {
481
+ var matches = $$(expression), h = Selector.handlers;
482
+ h.mark(matches);
483
+ for (var i = 0, results = [], element; element = elements[i]; i++)
484
+ if (element._countedByPrototype) results.push(element);
485
+ h.unmark(matches);
486
+ return results;
487
+ },
488
+
489
+ /**
490
+ * Selector.findElement(elements, expression[, index = 0]) -> Element
491
+ * Selector.findElement(elements[, index = 0]) -> Element
492
+ *
493
+ * Returns the `index`th element in the collection that matches
494
+ * `expression`.
495
+ *
496
+ * Returns the `index`th element overall if `expression` is not given.
497
+ **/
498
+ findElement: function(elements, expression, index) {
499
+ if (Object.isNumber(expression)) {
500
+ index = expression; expression = false;
501
+ }
502
+ return Selector.matchElements(elements, expression || '*')[index || 0];
503
+ },
504
+
505
+ /**
506
+ * Selector.findChildElements(element, expressions) -> [Element...]
507
+ *
508
+ * Searches beneath `element` for any elements that match the selector
509
+ * (or selectors) specified in `expressions`.
510
+ **/
511
+ findChildElements: function(element, expressions) {
512
+ expressions = Selector.split(expressions.join(','));
513
+ var results = [], h = Selector.handlers;
514
+ for (var i = 0, l = expressions.length, selector; i < l; i++) {
515
+ selector = new Selector(expressions[i].strip());
516
+ h.concat(results, selector.findElements(element));
517
+ }
518
+ return (l > 1) ? h.unique(results) : results;
519
+ }
520
+ });
521
+