ayril 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+