Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81141 views
1
/*
2
* Copyright (C) 2007-2015 Diego Perini
3
* All rights reserved.
4
*
5
* nwmatcher.js - A fast CSS selector engine and matcher
6
*
7
* Author: Diego Perini <diego.perini at gmail com>
8
* Version: 1.3.4
9
* Created: 20070722
10
* Release: 20150101
11
*
12
* License:
13
* http://javascript.nwbox.com/NWMatcher/MIT-LICENSE
14
* Download:
15
* http://javascript.nwbox.com/NWMatcher/nwmatcher.js
16
*/
17
18
(function(global, factory) {
19
20
if (typeof module == 'object' && typeof exports == 'object') {
21
// in a Node.js environment, the nwmatcher functions will operate on
22
// the passed "browserGlobal" and will be returned in an object
23
module.exports = function (browserGlobal) {
24
// passed global does not contain
25
// references to native objects
26
browserGlobal.console = console;
27
browserGlobal.parseInt = parseInt;
28
browserGlobal.Function = Function;
29
browserGlobal.Boolean = Boolean;
30
browserGlobal.Number = Number;
31
browserGlobal.RegExp = RegExp;
32
browserGlobal.String = String;
33
browserGlobal.Object = Object;
34
browserGlobal.Array = Array;
35
browserGlobal.Error = Error;
36
browserGlobal.Date = Date;
37
browserGlobal.Math = Math;
38
var exports = browserGlobal.Object();
39
factory(browserGlobal, exports);
40
return exports;
41
};
42
module.factory = factory;
43
} else {
44
// in a browser environment, the nwmatcher functions will operate on
45
// the "global" loading them and be attached to "global.NW.Dom"
46
factory(global,
47
(global.NW || (global.NW = global.Object())) &&
48
(global.NW.Dom || (global.NW.Dom = global.Object())));
49
global.NW.Dom.factory = factory;
50
}
51
52
})(this, function(global, exports) {
53
54
var version = 'nwmatcher-1.3.4',
55
56
Dom = exports,
57
58
// processing context & root element
59
doc = global.document,
60
root = doc.documentElement,
61
62
// save utility methods references
63
slice = global.Array.prototype.slice,
64
string = global.Object.prototype.toString,
65
66
// persist previous parsed data
67
isSingleMatch,
68
isSingleSelect,
69
70
lastSlice,
71
lastContext,
72
lastPosition,
73
74
lastMatcher,
75
lastSelector,
76
77
lastPartsMatch,
78
lastPartsSelect,
79
80
// accepted prefix identifiers
81
// (id, class & pseudo-class)
82
prefixes = '[#.:]?',
83
84
// accepted attribute operators
85
operators = '([~*^$|!]?={1})',
86
87
// accepted whitespace characters
88
whitespace = '[\\x20\\t\\n\\r\\f]*',
89
90
// 4 combinators F E, F>E, F+E, F~E
91
combinators = '[\\x20]|[>+~][^>+~]',
92
93
// an+b format params for pseudo-classes
94
pseudoparms = '(?:[-+]?\\d*n)?[-+]?\\d*',
95
96
// CSS quoted string values
97
quotedvalue = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' + "|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'",
98
99
// skip round brackets groups
100
skipround = '\\([^()]+\\)|\\(.*\\)',
101
// skip curly brackets groups
102
skipcurly = '\\{[^{}]+\\}|\\{.*\\}',
103
// skip square brackets groups
104
skipsquare = '\\[[^[\\]]*\\]|\\[.*\\]',
105
106
// skip [ ], ( ), { } brackets groups
107
skipgroup = '\\[.*\\]|\\(.*\\)|\\{.*\\}',
108
109
// http://www.w3.org/TR/css3-syntax/#characters
110
// unicode/ISO 10646 characters 161 and higher
111
// NOTE: Safari 2.0.x crashes with escaped (\\)
112
// Unicode ranges in regular expressions so we
113
// use a negated character range class instead
114
encoding = '(?:[-\\w]|[^\\x00-\\xa0]|\\\\.)',
115
116
// CSS identifier syntax
117
identifier = '(?:-?[_a-zA-Z]{1}[-\\w]*|[^\\x00-\\xa0]+|\\\\.+)+',
118
119
// build attribute string
120
attrcheck = '(' + quotedvalue + '|' + identifier + ')',
121
attributes = whitespace + '(' + encoding + '*:?' + encoding + '+)' +
122
whitespace + '(?:' + operators + whitespace + attrcheck + ')?' + whitespace,
123
attrmatcher = attributes.replace(attrcheck, '([\\x22\\x27]*)((?:\\\\?.)*?)\\3'),
124
125
// build pseudoclass string
126
pseudoclass = '((?:' +
127
// an+b parameters or quoted string
128
pseudoparms + '|' + quotedvalue + '|' +
129
// id, class, pseudo-class selector
130
prefixes + '|' + encoding + '+|' +
131
// nested HTML attribute selector
132
'\\[' + attributes + '\\]|' +
133
// nested pseudo-class selector
134
'\\(.+\\)|' + whitespace + '|' +
135
// nested pseudos/separators
136
',)+)',
137
138
// placeholder for extensions
139
extensions = '.+',
140
141
// CSS3: syntax scanner and
142
// one pass validation only
143
// using regular expression
144
standardValidator =
145
// discard start
146
'(?=[\\x20\\t\\n\\r\\f]*[^>+~(){}<>])' +
147
// open match group
148
'(' +
149
//universal selector
150
'\\*' +
151
// id/class/tag/pseudo-class identifier
152
'|(?:' + prefixes + identifier + ')' +
153
// combinator selector
154
'|' + combinators +
155
// HTML attribute selector
156
'|\\[' + attributes + '\\]' +
157
// pseudo-classes parameters
158
'|\\(' + pseudoclass + '\\)' +
159
// dom properties selector (extension)
160
'|\\{' + extensions + '\\}' +
161
// selector group separator (comma)
162
'|(?:,|' + whitespace + ')' +
163
// close match group
164
')+',
165
166
// validator for complex selectors in ':not()' pseudo-classes
167
extendedValidator = standardValidator.replace(pseudoclass, '.*'),
168
169
// validator for standard selectors as default
170
reValidator = new global.RegExp(standardValidator, 'g'),
171
172
// whitespace is any combination of these 5 character [\x20\t\n\r\f]
173
// http://www.w3.org/TR/css3-selectors/#selector-syntax
174
reTrimSpaces = new global.RegExp('^' +
175
whitespace + '|' + whitespace + '$', 'g'),
176
177
// only allow simple selectors nested in ':not()' pseudo-classes
178
reSimpleNot = new global.RegExp('^(' +
179
'(?!:not)' +
180
'(' + prefixes +
181
'|' + identifier +
182
'|\\([^()]*\\))+' +
183
'|\\[' + attributes + '\\]' +
184
')$'),
185
186
// split comma groups, exclude commas from
187
// quotes '' "" and from brackets () [] {}
188
reSplitGroup = new global.RegExp('(' +
189
'[^,\\\\()[\\]]+' +
190
'|' + skipsquare +
191
'|' + skipround +
192
'|' + skipcurly +
193
'|\\\\.' +
194
')+', 'g'),
195
196
// split last, right most, selector group token
197
reSplitToken = new global.RegExp('(' +
198
'\\[' + attributes + '\\]|' +
199
'\\(' + pseudoclass + '\\)|' +
200
'\\\\.|[^\\x20\\t\\r\\n\\f>+~])+', 'g'),
201
202
// for in excess whitespace removal
203
reWhiteSpace = /[\x20\t\n\r\f]+/g,
204
205
reOptimizeSelector = new global.RegExp(identifier + '|^$'),
206
207
/*----------------------------- FEATURE TESTING ----------------------------*/
208
209
// detect native methods
210
isNative = (function() {
211
var re = / \w+\(/,
212
isnative = String(Object.prototype.toString).replace(re, ' (');
213
return function(method) {
214
return method && typeof method != 'string' &&
215
isnative == String(method).replace(re, ' (');
216
};
217
})(),
218
219
// NATIVE_XXXXX true if method exist and is callable
220
// detect if DOM methods are native in browsers
221
NATIVE_FOCUS = isNative(doc.hasFocus),
222
NATIVE_QSAPI = isNative(doc.querySelector),
223
NATIVE_GEBID = isNative(doc.getElementById),
224
NATIVE_GEBTN = isNative(root.getElementsByTagName),
225
NATIVE_GEBCN = isNative(root.getElementsByClassName),
226
227
// detect native getAttribute/hasAttribute methods,
228
// frameworks extend these to elements, but it seems
229
// this does not work for XML namespaced attributes,
230
// used to check both getAttribute/hasAttribute in IE
231
NATIVE_GET_ATTRIBUTE = isNative(root.getAttribute),
232
NATIVE_HAS_ATTRIBUTE = isNative(root.hasAttribute),
233
234
// check if slice() can convert nodelist to array
235
// see http://yura.thinkweb2.com/cft/
236
NATIVE_SLICE_PROTO =
237
(function() {
238
var isBuggy = false;
239
try {
240
isBuggy = !!slice.call(doc.childNodes, 0)[0];
241
} catch(e) { }
242
return isBuggy;
243
})(),
244
245
// supports the new traversal API
246
NATIVE_TRAVERSAL_API =
247
'nextElementSibling' in root && 'previousElementSibling' in root,
248
249
// BUGGY_XXXXX true if method is feature tested and has known bugs
250
// detect buggy gEBID
251
BUGGY_GEBID = NATIVE_GEBID ?
252
(function() {
253
var isBuggy = true, x = 'x' + global.String(+new global.Date),
254
a = doc.createElementNS ? 'a' : '<a name="' + x + '">';
255
(a = doc.createElement(a)).name = x;
256
root.insertBefore(a, root.firstChild);
257
isBuggy = !!doc.getElementById(x);
258
root.removeChild(a);
259
return isBuggy;
260
})() :
261
true,
262
263
// detect IE gEBTN comment nodes bug
264
BUGGY_GEBTN = NATIVE_GEBTN ?
265
(function() {
266
var div = doc.createElement('div');
267
div.appendChild(doc.createComment(''));
268
return !!div.getElementsByTagName('*')[0];
269
})() :
270
true,
271
272
// detect Opera gEBCN second class and/or UTF8 bugs as well as Safari 3.2
273
// caching class name results and not detecting when changed,
274
// tests are based on the jQuery selector test suite
275
BUGGY_GEBCN = NATIVE_GEBCN ?
276
(function() {
277
var isBuggy, div = doc.createElement('div'), test = '\u53f0\u5317';
278
279
// Opera tests
280
div.appendChild(doc.createElement('span')).
281
setAttribute('class', test + 'abc ' + test);
282
div.appendChild(doc.createElement('span')).
283
setAttribute('class', 'x');
284
285
isBuggy = !div.getElementsByClassName(test)[0];
286
287
// Safari test
288
div.lastChild.className = test;
289
return isBuggy || div.getElementsByClassName(test).length != 2;
290
})() :
291
true,
292
293
// detect IE bug with dynamic attributes
294
BUGGY_GET_ATTRIBUTE = NATIVE_GET_ATTRIBUTE ?
295
(function() {
296
var input = doc.createElement('input');
297
input.setAttribute('value', 5);
298
return input.defaultValue != 5;
299
})() :
300
true,
301
302
// detect IE bug with non-standard boolean attributes
303
BUGGY_HAS_ATTRIBUTE = NATIVE_HAS_ATTRIBUTE ?
304
(function() {
305
var option = doc.createElement('option');
306
option.setAttribute('selected', 'selected');
307
return !option.hasAttribute('selected');
308
})() :
309
true,
310
311
// detect Safari bug with selected option elements
312
BUGGY_SELECTED =
313
(function() {
314
var select = doc.createElement('select');
315
select.appendChild(doc.createElement('option'));
316
return !select.firstChild.selected;
317
})(),
318
319
// initialized with the loading context
320
// and reset for each different context
321
BUGGY_QUIRKS_GEBCN,
322
BUGGY_QUIRKS_QSAPI,
323
324
QUIRKS_MODE,
325
XML_DOCUMENT,
326
327
// detect Opera browser
328
OPERA = /opera/i.test(string.call(global.opera)),
329
330
// skip simple selector optimizations for Opera >= 11
331
OPERA_QSAPI = OPERA && global.parseFloat(global.opera.version()) >= 11,
332
333
// check Selector API implementations
334
RE_BUGGY_QSAPI = NATIVE_QSAPI ?
335
(function() {
336
var pattern = new global.Array(), context, element,
337
338
expect = function(selector, element, n) {
339
var result = false;
340
context.appendChild(element);
341
try { result = context.querySelectorAll(selector).length == n; } catch(e) { }
342
while (context.firstChild) { context.removeChild(context.firstChild); }
343
return result;
344
};
345
346
// certain bugs can only be detected in standard documents
347
// to avoid writing a live loading document create a fake one
348
if (doc.implementation && doc.implementation.createDocument) {
349
// use a shadow document body as context
350
context = doc.implementation.createDocument('', '', null).
351
appendChild(doc.createElement('html')).
352
appendChild(doc.createElement('head')).parentNode.
353
appendChild(doc.createElement('body'));
354
} else {
355
// use an unattached div node as context
356
context = doc.createElement('div');
357
}
358
359
// fix for Safari 8.x and other engines that
360
// fail querying filtered sibling combinators
361
element = doc.createElement('div');
362
element.innerHTML = '<p id="a"></p><br>';
363
expect('p#a+*', element, 0) &&
364
pattern.push('\\w+#\\w+.*[+~]');
365
366
// ^= $= *= operators bugs with empty values (Opera 10 / IE8)
367
element = doc.createElement('p');
368
element.setAttribute('class', '');
369
expect('[class^=""]', element, 1) &&
370
pattern.push('[*^$]=[\\x20\\t\\n\\r\\f]*(?:""|' + "'')");
371
372
// :checked bug with option elements (Firefox 3.6.x)
373
// it wrongly includes 'selected' options elements
374
// HTML5 rules says selected options also match
375
element = doc.createElement('option');
376
element.setAttribute('selected', 'selected');
377
expect(':checked', element, 0) &&
378
pattern.push(':checked');
379
380
// :enabled :disabled bugs with hidden fields (Firefox 3.5)
381
// http://www.w3.org/TR/html5/links.html#selector-enabled
382
// http://www.w3.org/TR/css3-selectors/#enableddisabled
383
// not supported by IE8 Query Selector
384
element = doc.createElement('input');
385
element.setAttribute('type', 'hidden');
386
expect(':enabled', element, 0) &&
387
pattern.push(':enabled', ':disabled');
388
389
// :link bugs with hyperlinks matching (Firefox/Safari)
390
element = doc.createElement('link');
391
element.setAttribute('href', 'x');
392
expect(':link', element, 1) ||
393
pattern.push(':link');
394
395
// avoid attribute selectors for IE QSA
396
if (BUGGY_HAS_ATTRIBUTE) {
397
// IE fails in reading:
398
// - original values for input/textarea
399
// - original boolean values for controls
400
pattern.push('\\[[\\x20\\t\\n\\r\\f]*(?:checked|disabled|ismap|multiple|readonly|selected|value)');
401
}
402
403
return pattern.length ?
404
new global.RegExp(pattern.join('|')) :
405
{ 'test': function() { return false; } };
406
407
})() :
408
true,
409
410
// matches class selectors
411
RE_CLASS = new global.RegExp('(?:\\[[\\x20\\t\\n\\r\\f]*class\\b|\\.' + identifier + ')'),
412
413
// matches simple id, tag & class selectors
414
RE_SIMPLE_SELECTOR = new global.RegExp(
415
BUGGY_GEBTN && BUGGY_GEBCN || OPERA ?
416
'^#?-?[_a-zA-Z]{1}' + encoding + '*$' : BUGGY_GEBTN ?
417
'^[.#]?-?[_a-zA-Z]{1}' + encoding + '*$' : BUGGY_GEBCN ?
418
'^(?:\\*|#-?[_a-zA-Z]{1}' + encoding + '*)$' :
419
'^(?:\\*|[.#]?-?[_a-zA-Z]{1}' + encoding + '*)$'),
420
421
/*----------------------------- LOOKUP OBJECTS -----------------------------*/
422
423
LINK_NODES = new global.Object({ 'a': 1, 'A': 1, 'area': 1, 'AREA': 1, 'link': 1, 'LINK': 1 }),
424
425
// boolean attributes should return attribute name instead of true/false
426
ATTR_BOOLEAN = new global.Object({
427
'checked': 1, 'disabled': 1, 'ismap': 1,
428
'multiple': 1, 'readonly': 1, 'selected': 1
429
}),
430
431
// dynamic attributes that needs to be checked against original HTML value
432
ATTR_DEFAULT = new global.Object({
433
'value': 'defaultValue',
434
'checked': 'defaultChecked',
435
'selected': 'defaultSelected'
436
}),
437
438
// attributes referencing URI data values need special treatment in IE
439
ATTR_URIDATA = new global.Object({
440
'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2,
441
'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2
442
}),
443
444
// HTML 5 draft specifications
445
// http://www.whatwg.org/specs/web-apps/current-work/#selectors
446
HTML_TABLE = new global.Object({
447
// class attribute must be treated case-insensitive in HTML quirks mode
448
// initialized by default to Standard Mode (case-sensitive),
449
// set dynamically by the attribute resolver
450
'class': 0,
451
'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1,
452
'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1,
453
'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1,
454
'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1,
455
'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1,
456
'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1,
457
'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1,
458
'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1
459
}),
460
461
// the following attributes must be treated case-insensitive in XHTML mode
462
// Niels Leenheer http://rakaz.nl/item/css_selector_bugs_case_sensitivity
463
XHTML_TABLE = new global.Object({
464
'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1,
465
'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1,
466
'enctype': 1, 'face': 1, 'hreflang': 1, 'http-equiv': 1,
467
'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1,
468
'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1
469
}),
470
471
/*-------------------------- REGULAR EXPRESSIONS ---------------------------*/
472
473
// placeholder to add functionalities
474
Selectors = new global.Object({
475
// as a simple example this will check
476
// for chars not in standard ascii table
477
//
478
// 'mySpecialSelector': {
479
// 'Expression': /\u0080-\uffff/,
480
// 'Callback': mySelectorCallback
481
// }
482
//
483
// 'mySelectorCallback' will be invoked
484
// only after passing all other standard
485
// checks and only if none of them worked
486
}),
487
488
// attribute operators
489
Operators = new global.Object({
490
'=': "n=='%m'",
491
'^=': "n.indexOf('%m')==0",
492
'*=': "n.indexOf('%m')>-1",
493
'|=': "(n+'-').indexOf('%m-')==0",
494
'~=': "(' '+n+' ').indexOf(' %m ')>-1",
495
'$=': "n.substr(n.length-'%m'.length)=='%m'"
496
}),
497
498
// optimization expressions
499
Optimize = new global.Object({
500
ID: new global.RegExp('^\\*?#(' + encoding + '+)|' + skipgroup),
501
TAG: new global.RegExp('^(' + encoding + '+)|' + skipgroup),
502
CLASS: new global.RegExp('^\\*?\\.(' + encoding + '+$)|' + skipgroup)
503
}),
504
505
// precompiled Regular Expressions
506
Patterns = new global.Object({
507
// structural pseudo-classes and child selectors
508
spseudos: /^\:(root|empty|(?:first|last|only)(?:-child|-of-type)|nth(?:-last)?(?:-child|-of-type)\(\s*(even|odd|(?:[-+]{0,1}\d*n\s*)?[-+]{0,1}\s*\d*)\s*\))?(.*)/i,
509
// uistates + dynamic + negation pseudo-classes
510
dpseudos: /^\:(link|visited|target|active|focus|hover|checked|disabled|enabled|selected|lang\(([-\w]{2,})\)|not\(([^()]*|.*)\))?(.*)/i,
511
// element attribute matcher
512
attribute: new global.RegExp('^\\[' + attrmatcher + '\\](.*)'),
513
// E > F
514
children: /^[\x20\t\n\r\f]*\>[\x20\t\n\r\f]*(.*)/,
515
// E + F
516
adjacent: /^[\x20\t\n\r\f]*\+[\x20\t\n\r\f]*(.*)/,
517
// E ~ F
518
relative: /^[\x20\t\n\r\f]*\~[\x20\t\n\r\f]*(.*)/,
519
// E F
520
ancestor: /^[\x20\t\n\r\f]+(.*)/,
521
// all
522
universal: /^\*(.*)/,
523
// id
524
id: new global.RegExp('^#(' + encoding + '+)(.*)'),
525
// tag
526
tagName: new global.RegExp('^(' + encoding + '+)(.*)'),
527
// class
528
className: new global.RegExp('^\\.(' + encoding + '+)(.*)')
529
}),
530
531
/*------------------------------ UTIL METHODS ------------------------------*/
532
533
// concat elements to data
534
concatList =
535
function(data, elements) {
536
var i = -1, element;
537
if (!data.length && global.Array.slice)
538
return global.Array.slice(elements);
539
while ((element = elements[++i]))
540
data[data.length] = element;
541
return data;
542
},
543
544
// concat elements to data and callback
545
concatCall =
546
function(data, elements, callback) {
547
var i = -1, element;
548
while ((element = elements[++i])) {
549
if (false === callback(data[data.length] = element)) { break; }
550
}
551
return data;
552
},
553
554
// change context specific variables
555
switchContext =
556
function(from, force) {
557
var div, oldDoc = doc;
558
// save passed context
559
lastContext = from;
560
// set new context document
561
doc = from.ownerDocument || from;
562
if (force || oldDoc !== doc) {
563
// set document root
564
root = doc.documentElement;
565
// set host environment flags
566
XML_DOCUMENT = doc.createElement('DiV').nodeName == 'DiV';
567
568
// In quirks mode css class names are case insensitive.
569
// In standards mode they are case sensitive. See docs:
570
// https://developer.mozilla.org/en/Mozilla_Quirks_Mode_Behavior
571
// http://www.whatwg.org/specs/web-apps/current-work/#selectors
572
QUIRKS_MODE = !XML_DOCUMENT &&
573
typeof doc.compatMode == 'string' ?
574
doc.compatMode.indexOf('CSS') < 0 :
575
(function() {
576
var style = doc.createElement('div').style;
577
return style && (style.width = 1) && style.width == '1px';
578
})();
579
580
div = doc.createElement('div');
581
div.appendChild(doc.createElement('p')).setAttribute('class', 'xXx');
582
div.appendChild(doc.createElement('p')).setAttribute('class', 'xxx');
583
584
// GEBCN buggy in quirks mode, match count is:
585
// Firefox 3.0+ [xxx = 1, xXx = 1]
586
// Opera 10.63+ [xxx = 0, xXx = 2]
587
BUGGY_QUIRKS_GEBCN =
588
!XML_DOCUMENT && NATIVE_GEBCN && QUIRKS_MODE &&
589
(div.getElementsByClassName('xxx').length != 2 ||
590
div.getElementsByClassName('xXx').length != 2);
591
592
// QSAPI buggy in quirks mode, match count is:
593
// At least Chrome 4+, Firefox 3.5+, Opera 10.x+, Safari 4+ [xxx = 1, xXx = 2]
594
// Safari 3.2 QSA doesn't work with mixedcase in quirksmode [xxx = 1, xXx = 0]
595
// https://bugs.webkit.org/show_bug.cgi?id=19047
596
// must test the attribute selector '[class~=xxx]'
597
// before '.xXx' or the bug may not present itself
598
BUGGY_QUIRKS_QSAPI =
599
!XML_DOCUMENT && NATIVE_QSAPI && QUIRKS_MODE &&
600
(div.querySelectorAll('[class~=xxx]').length != 2 ||
601
div.querySelectorAll('.xXx').length != 2);
602
603
Config.CACHING && Dom.setCache(true, doc);
604
}
605
},
606
607
// convert a CSS string or identifier containing escape sequence to a
608
// javascript string with javascript escape sequences
609
convertEscapes =
610
function(str) {
611
return str.replace(/\\([0-9a-fA-F]{1,6}\x20?|.)|([\x22\x27])/g, function(substring, p1, p2) {
612
var codePoint, highHex, highSurrogate, lowHex, lowSurrogate;
613
614
if (p2) {
615
// unescaped " or '
616
return '\\' + p2;
617
}
618
619
if (/^[0-9a-fA-F]/.test(p1)) {
620
// \1f23
621
codePoint = parseInt(p1, 16);
622
623
if (codePoint < 0 || codePoint > 0x10ffff) {
624
// the replacement character
625
return '\\ufffd';
626
}
627
628
// javascript strings are in UTF-16
629
if (codePoint <= 0xffff) {
630
// Basic
631
lowHex = '000' + codePoint.toString(16);
632
return '\\u' + lowHex.substr(lowHex.length - 4);
633
}
634
635
// Supplementary
636
codePoint -= 0x10000;
637
highSurrogate = (codePoint >> 10) + 0xd800;
638
lowSurrogate = (codePoint % 0x400) + 0xdc00;
639
highHex = '000' + highSurrogate.toString(16);
640
lowHex = '000' + lowSurrogate.toString(16);
641
642
return '\\u' + highHex.substr(highHex.length - 4) +
643
'\\u' + lowHex.substr(lowHex.length - 4);
644
}
645
646
if (/^[\\\x22\x27]/.test(p1)) {
647
// \' \"
648
return substring;
649
}
650
651
// \g \h \. \# etc
652
return p1;
653
});
654
},
655
656
/*------------------------------ DOM METHODS -------------------------------*/
657
658
// element by id (raw)
659
// @return reference or null
660
byIdRaw =
661
function(id, elements) {
662
var i = -1, element = null;
663
while ((element = elements[++i])) {
664
if (element.getAttribute('id') == id) {
665
break;
666
}
667
}
668
return element;
669
},
670
671
// element by id
672
// @return reference or null
673
_byId = !BUGGY_GEBID ?
674
function(id, from) {
675
id = id.replace(/\\([^\\]{1})/g, '$1');
676
return from.getElementById && from.getElementById(id) ||
677
byIdRaw(id, from.getElementsByTagName('*'));
678
} :
679
function(id, from) {
680
var element = null;
681
id = id.replace(/\\([^\\]{1})/g, '$1');
682
if (XML_DOCUMENT || from.nodeType != 9) {
683
return byIdRaw(id, from.getElementsByTagName('*'));
684
}
685
if ((element = from.getElementById(id)) &&
686
element.name == id && from.getElementsByName) {
687
return byIdRaw(id, from.getElementsByName(id));
688
}
689
return element;
690
},
691
692
// publicly exposed byId
693
// @return reference or null
694
byId =
695
function(id, from) {
696
from || (from = doc);
697
if (lastContext !== from) { switchContext(from); }
698
return _byId(id, from);
699
},
700
701
// elements by tag (raw)
702
// @return array
703
byTagRaw =
704
function(tag, from) {
705
var any = tag == '*', element = from, elements = new global.Array(), next = element.firstChild;
706
any || (tag = tag.toUpperCase());
707
while ((element = next)) {
708
if (element.tagName > '@' && (any || element.tagName.toUpperCase() == tag)) {
709
elements[elements.length] = element;
710
}
711
if ((next = element.firstChild || element.nextSibling)) continue;
712
while (!next && (element = element.parentNode) && element !== from) {
713
next = element.nextSibling;
714
}
715
}
716
return elements;
717
},
718
719
// elements by tag
720
// @return array
721
_byTag = !BUGGY_GEBTN && NATIVE_SLICE_PROTO ?
722
function(tag, from) {
723
return XML_DOCUMENT || from.nodeType == 11 ? byTagRaw(tag, from) :
724
slice.call(from.getElementsByTagName(tag), 0);
725
} :
726
function(tag, from) {
727
var i = -1, j = i, data = new global.Array(),
728
element, elements = from.getElementsByTagName(tag);
729
if (tag == '*') {
730
while ((element = elements[++i])) {
731
if (element.nodeName > '@')
732
data[++j] = element;
733
}
734
} else {
735
while ((element = elements[++i])) {
736
data[i] = element;
737
}
738
}
739
return data;
740
},
741
742
// publicly exposed byTag
743
// @return array
744
byTag =
745
function(tag, from) {
746
from || (from = doc);
747
if (lastContext !== from) { switchContext(from); }
748
return _byTag(tag, from);
749
},
750
751
// publicly exposed byName
752
// @return array
753
byName =
754
function(name, from) {
755
return select('[name="' + name.replace(/\\([^\\]{1})/g, '$1') + '"]', from);
756
},
757
758
// elements by class (raw)
759
// @return array
760
byClassRaw =
761
function(name, from) {
762
var i = -1, j = i, data = new global.Array(), element, elements = _byTag('*', from), n;
763
name = ' ' + (QUIRKS_MODE ? name.toLowerCase() : name).replace(/\\([^\\]{1})/g, '$1') + ' ';
764
while ((element = elements[++i])) {
765
n = XML_DOCUMENT ? element.getAttribute('class') : element.className;
766
if (n && n.length && (' ' + (QUIRKS_MODE ? n.toLowerCase() : n).
767
replace(reWhiteSpace, ' ') + ' ').indexOf(name) > -1) {
768
data[++j] = element;
769
}
770
}
771
return data;
772
},
773
774
// elements by class
775
// @return array
776
_byClass =
777
function(name, from) {
778
return (BUGGY_GEBCN || BUGGY_QUIRKS_GEBCN || XML_DOCUMENT || !from.getElementsByClassName) ?
779
byClassRaw(name, from) : slice.call(from.getElementsByClassName(name.replace(/\\([^\\]{1})/g, '$1')), 0);
780
},
781
782
// publicly exposed byClass
783
// @return array
784
byClass =
785
function(name, from) {
786
from || (from = doc);
787
if (lastContext !== from) { switchContext(from); }
788
return _byClass(name, from);
789
},
790
791
// check element is descendant of container
792
// @return boolean
793
contains = 'compareDocumentPosition' in root ?
794
function(container, element) {
795
return (container.compareDocumentPosition(element) & 16) == 16;
796
} : 'contains' in root ?
797
function(container, element) {
798
return container !== element && container.contains(element);
799
} :
800
function(container, element) {
801
while ((element = element.parentNode)) {
802
if (element === container) return true;
803
}
804
return false;
805
},
806
807
// attribute value
808
// @return string
809
getAttribute = !BUGGY_GET_ATTRIBUTE ?
810
function(node, attribute) {
811
return node.getAttribute(attribute) || '';
812
} :
813
function(node, attribute) {
814
attribute = attribute.toLowerCase();
815
if (typeof node[attribute] == 'object') {
816
return node.attributes[attribute] &&
817
node.attributes[attribute].value || '';
818
}
819
return (
820
// 'type' can only be read by using native getAttribute
821
attribute == 'type' ? node.getAttribute(attribute) || '' :
822
// specific URI data attributes (parameter 2 to fix IE bug)
823
ATTR_URIDATA[attribute] ? node.getAttribute(attribute, 2) || '' :
824
// boolean attributes should return name instead of true/false
825
ATTR_BOOLEAN[attribute] ? node.getAttribute(attribute) ? attribute : 'false' :
826
((node = node.getAttributeNode(attribute)) && node.value) || '');
827
},
828
829
// attribute presence
830
// @return boolean
831
hasAttribute = !BUGGY_HAS_ATTRIBUTE ?
832
function(node, attribute) {
833
return XML_DOCUMENT ?
834
!!node.getAttribute(attribute) :
835
node.hasAttribute(attribute);
836
} :
837
function(node, attribute) {
838
attribute = attribute.toLowerCase();
839
if (ATTR_DEFAULT[attribute]) {
840
return !!node[ATTR_DEFAULT[attribute]];
841
}
842
// read the attribute node
843
node = node.getAttributeNode(attribute);
844
return !!(node && node.specified);
845
},
846
847
// check node emptyness
848
// @return boolean
849
isEmpty =
850
function(node) {
851
node = node.firstChild;
852
while (node) {
853
if (node.nodeType == 3 || node.nodeName > '@') return false;
854
node = node.nextSibling;
855
}
856
return true;
857
},
858
859
// check if element matches the :link pseudo
860
// @return boolean
861
isLink =
862
function(element) {
863
return hasAttribute(element,'href') && LINK_NODES[element.nodeName];
864
},
865
866
// child position by nodeType
867
// @return number
868
nthElement =
869
function(element, last) {
870
var count = 1, succ = last ? 'nextSibling' : 'previousSibling';
871
while ((element = element[succ])) {
872
if (element.nodeName > '@') ++count;
873
}
874
return count;
875
},
876
877
// child position by nodeName
878
// @return number
879
nthOfType =
880
function(element, last) {
881
var count = 1, succ = last ? 'nextSibling' : 'previousSibling', type = element.nodeName;
882
while ((element = element[succ])) {
883
if (element.nodeName == type) ++count;
884
}
885
return count;
886
},
887
888
/*------------------------------- DEBUGGING --------------------------------*/
889
890
// get/set (string/object) working modes
891
configure =
892
function(option) {
893
if (typeof option == 'string') { return Config[option] || Config; }
894
if (typeof option != 'object') { return false; }
895
for (var i in option) {
896
Config[i] = !!option[i];
897
if (i == 'SIMPLENOT') {
898
matchContexts = new global.Object();
899
matchResolvers = new global.Object();
900
selectContexts = new global.Object();
901
selectResolvers = new global.Object();
902
if (!Config[i]) { Config['USE_QSAPI'] = false; }
903
} else if (i == 'USE_QSAPI') {
904
Config[i] = !!option[i] && NATIVE_QSAPI;
905
}
906
}
907
reValidator = new global.RegExp(Config.SIMPLENOT ?
908
standardValidator : extendedValidator, 'g');
909
return true;
910
},
911
912
// control user notifications
913
emit =
914
function(message) {
915
if (Config.VERBOSITY) { throw new global.Error(message); }
916
if (global.console && global.console.log) {
917
global.console.log(message);
918
}
919
},
920
921
Config = new global.Object({
922
923
// used to enable/disable caching of result sets
924
CACHING: false,
925
926
// by default do not add missing left/right context
927
// to selector string shortcuts like "+div" or "ul>"
928
// callable Dom.shortcuts method has to be available
929
SHORTCUTS: false,
930
931
// by default disable complex selectors nested in
932
// ':not()' pseudo-classes, as for specifications
933
SIMPLENOT: true,
934
935
// strict QSA match all non-unique IDs (false)
936
// speed & libs compat match unique ID (true)
937
UNIQUE_ID: true,
938
939
// HTML5 handling for the ":checked" pseudo-class
940
USE_HTML5: true,
941
942
// controls enabling the Query Selector API branch
943
USE_QSAPI: NATIVE_QSAPI,
944
945
// controls the engine error/warning notifications
946
VERBOSITY: true
947
948
}),
949
950
/*---------------------------- COMPILER METHODS ----------------------------*/
951
952
// code string reused to build compiled functions
953
ACCEPT_NODE = 'r[r.length]=c[k];if(f&&false===f(c[k]))break main;else continue main;',
954
955
// compile a comma separated group of selector
956
// @mode boolean true for select, false for match
957
// return a compiled function
958
compile =
959
function(selector, source, mode) {
960
961
var parts = typeof selector == 'string' ? selector.match(reSplitGroup) : selector;
962
963
// ensures that source is a string
964
typeof source == 'string' || (source = '');
965
966
if (parts.length == 1) {
967
source += compileSelector(parts[0], mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode);
968
} else {
969
// for each selector in the group
970
var i = -1, seen = new global.Object(), token;
971
while ((token = parts[++i])) {
972
token = token.replace(reTrimSpaces, '');
973
// avoid repeating the same token
974
// in comma separated group (p, p)
975
if (!seen[token] && (seen[token] = true)) {
976
source += compileSelector(token, mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode);
977
}
978
}
979
}
980
981
if (mode) {
982
// for select method
983
return new global.Function('c,s,r,d,h,g,f,v',
984
'var N,n,x=0,k=-1,e;main:while((e=c[++k])){' + source + '}return r;');
985
} else {
986
// for match method
987
return new global.Function('e,s,r,d,h,g,f,v',
988
'var N,n,x=0,k=e;' + source + 'return false;');
989
}
990
},
991
992
// allows to cache already visited nodes
993
FILTER =
994
'var z=v[@]||(v[@]=[]),l=z.length-1;' +
995
'while(l>=0&&z[l]!==e)--l;' +
996
'if(l!==-1){break;}' +
997
'z[z.length]=e;',
998
999
// compile a CSS3 string selector into ad-hoc javascript matching function
1000
// @return string (to be compiled)
1001
compileSelector =
1002
function(selector, source, mode) {
1003
1004
var a, b, n, k = 0, expr, match, result, status, test, type;
1005
1006
while (selector) {
1007
1008
k++;
1009
1010
// *** Universal selector
1011
// * match all (empty block, do not remove)
1012
if ((match = selector.match(Patterns.universal))) {
1013
// do nothing, handled in the compiler where
1014
// BUGGY_GEBTN return comment nodes (ex: IE)
1015
expr = '';
1016
}
1017
1018
// *** ID selector
1019
// #Foo Id case sensitive
1020
else if ((match = selector.match(Patterns.id))) {
1021
// document can contain conflicting elements (id/name)
1022
// prototype selector unit need this method to recover bad HTML forms
1023
source = 'if(' + (XML_DOCUMENT ?
1024
's.getAttribute(e,"id")' :
1025
'(e.submit?s.getAttribute(e,"id"):e.id)') +
1026
'=="' + match[1] + '"' +
1027
'){' + source + '}';
1028
}
1029
1030
// *** Type selector
1031
// Foo Tag (case insensitive)
1032
else if ((match = selector.match(Patterns.tagName))) {
1033
// both tagName and nodeName properties may be upper/lower case
1034
// depending on their creation NAMESPACE in createElementNS()
1035
source = 'if(e.nodeName' + (XML_DOCUMENT ?
1036
'=="' + match[1] + '"' : '.toUpperCase()' +
1037
'=="' + match[1].toUpperCase() + '"') +
1038
'){' + source + '}';
1039
}
1040
1041
// *** Class selector
1042
// .Foo Class (case sensitive)
1043
else if ((match = selector.match(Patterns.className))) {
1044
// W3C CSS3 specs: element whose "class" attribute has been assigned a
1045
// list of whitespace-separated values, see section 6.4 Class selectors
1046
// and notes at the bottom; explicitly non-normative in this specification.
1047
source = 'if((n=' + (XML_DOCUMENT ?
1048
's.getAttribute(e,"class")' : 'e.className') +
1049
')&&n.length&&(" "+' + (QUIRKS_MODE ? 'n.toLowerCase()' : 'n') +
1050
'.replace(' + reWhiteSpace + '," ")+" ").indexOf(" ' +
1051
(QUIRKS_MODE ? match[1].toLowerCase() : match[1]) + ' ")>-1' +
1052
'){' + source + '}';
1053
}
1054
1055
// *** Attribute selector
1056
// [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $=
1057
// case sensitivity is treated differently depending on the document type (see map)
1058
else if ((match = selector.match(Patterns.attribute))) {
1059
1060
// xml namespaced attribute ?
1061
expr = match[1].split(':');
1062
expr = expr.length == 2 ? expr[1] : expr[0] + '';
1063
1064
if (match[2] && !Operators[match[2]]) {
1065
emit('Unsupported operator in attribute selectors "' + selector + '"');
1066
return '';
1067
}
1068
1069
test = 'false';
1070
1071
// replace Operators parameter if needed
1072
if (match[2] && match[4] && (test = Operators[match[2]])) {
1073
match[4] = convertEscapes(match[4]);
1074
// case treatment depends on document
1075
HTML_TABLE['class'] = QUIRKS_MODE ? 1 : 0;
1076
type = (XML_DOCUMENT ? XHTML_TABLE : HTML_TABLE)[expr.toLowerCase()];
1077
test = test.replace(/\%m/g, type ? match[4].toLowerCase() : match[4]);
1078
} else if (match[2] == '!=' || match[2] == '=') {
1079
test = 'n' + match[2] + '=""';
1080
}
1081
1082
// build expression for has/getAttribute
1083
expr = 'n=s.' + (match[2] ? 'get' : 'has') +
1084
'Attribute(e,"' + match[1] + '")' +
1085
(type && match[2] ? '.toLowerCase();' : ';');
1086
1087
source = expr + 'if(' + (match[2] ? test : 'n') + '){' + source + '}';
1088
}
1089
1090
// *** Adjacent sibling combinator
1091
// E + F (F adiacent sibling of E)
1092
else if ((match = selector.match(Patterns.adjacent))) {
1093
source = (mode ? '' : FILTER.replace(/@/g, k)) + source;
1094
source = NATIVE_TRAVERSAL_API ?
1095
'var N' + k + '=e;while(e&&(e=e.previousElementSibling)){' + source + 'break;}e=N' + k + ';' :
1096
'var N' + k + '=e;while(e&&(e=e.previousSibling)){if(e.nodeName>"@"){' + source + 'break;}}e=N' + k + ';';
1097
}
1098
1099
// *** General sibling combinator
1100
// E ~ F (F relative sibling of E)
1101
else if ((match = selector.match(Patterns.relative))) {
1102
source = (mode ? '' : FILTER.replace(/@/g, k)) + source;
1103
source = NATIVE_TRAVERSAL_API ?
1104
('var N' + k + '=e;e=e.parentNode.firstElementChild;' +
1105
'while(e&&e!==N' + k + '){' + source + 'e=e.nextElementSibling;}e=N' + k + ';') :
1106
('var N' + k + '=e;e=e.parentNode.firstChild;' +
1107
'while(e&&e!==N' + k + '){if(e.nodeName>"@"){' + source + '}e=e.nextSibling;}e=N' + k + ';');
1108
}
1109
1110
// *** Child combinator
1111
// E > F (F children of E)
1112
else if ((match = selector.match(Patterns.children))) {
1113
source = (mode ? '' : FILTER.replace(/@/g, k)) + source;
1114
source = 'var N' + k + '=e;while(e&&e!==h&&e!==g&&(e=e.parentNode)){' + source + 'break;}e=N' + k + ';';
1115
}
1116
1117
// *** Descendant combinator
1118
// E F (E ancestor of F)
1119
else if ((match = selector.match(Patterns.ancestor))) {
1120
source = (mode ? '' : FILTER.replace(/@/g, k)) + source;
1121
source = 'var N' + k + '=e;while(e&&e!==h&&e!==g&&(e=e.parentNode)){' + source + '}e=N' + k + ';';
1122
}
1123
1124
// *** Structural pseudo-classes
1125
// :root, :empty,
1126
// :first-child, :last-child, :only-child,
1127
// :first-of-type, :last-of-type, :only-of-type,
1128
// :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type()
1129
else if ((match = selector.match(Patterns.spseudos)) && match[1]) {
1130
1131
switch (match[1]) {
1132
case 'root':
1133
// element root of the document
1134
if (match[3]) {
1135
source = 'if(e===h||s.contains(h,e)){' + source + '}';
1136
} else {
1137
source = 'if(e===h){' + source + '}';
1138
}
1139
break;
1140
1141
case 'empty':
1142
// element that has no children
1143
source = 'if(s.isEmpty(e)){' + source + '}';
1144
break;
1145
1146
default:
1147
if (match[1] && match[2]) {
1148
if (match[2] == 'n') {
1149
source = 'if(e!==h){' + source + '}';
1150
break;
1151
} else if (match[2] == 'even') {
1152
a = 2;
1153
b = 0;
1154
} else if (match[2] == 'odd') {
1155
a = 2;
1156
b = 1;
1157
} else {
1158
// assumes correct "an+b" format, "b" before "a" to keep "n" values
1159
b = ((n = match[2].match(/(-?\d+)$/)) ? global.parseInt(n[1], 10) : 0);
1160
a = ((n = match[2].match(/(-?\d*)n/i)) ? global.parseInt(n[1], 10) : 0);
1161
if (n && n[1] == '-') a = -1;
1162
}
1163
1164
// build test expression out of structural pseudo (an+b) parameters
1165
// see here: http://www.w3.org/TR/css3-selectors/#nth-child-pseudo
1166
test = a > 1 ?
1167
(/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' :
1168
'n>=' + b + '&&(n-(' + b + '))%' + a + '==0' : a < -1 ?
1169
(/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' :
1170
'n<=' + b + '&&(n-(' + b + '))%' + a + '==0' : a === 0 ?
1171
'n==' + b : a == -1 ? 'n<=' + b : 'n>=' + b;
1172
1173
// 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type)
1174
source =
1175
'if(e!==h){' +
1176
'n=s[' + (/-of-type/i.test(match[1]) ? '"nthOfType"' : '"nthElement"') + ']' +
1177
'(e,' + (/last/i.test(match[1]) ? 'true' : 'false') + ');' +
1178
'if(' + test + '){' + source + '}' +
1179
'}';
1180
1181
} else {
1182
// 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type)
1183
a = /first/i.test(match[1]) ? 'previous' : 'next';
1184
n = /only/i.test(match[1]) ? 'previous' : 'next';
1185
b = /first|last/i.test(match[1]);
1186
1187
type = /-of-type/i.test(match[1]) ? '&&n.nodeName!=e.nodeName' : '&&n.nodeName<"@"';
1188
1189
source = 'if(e!==h){' +
1190
( 'n=e;while((n=n.' + a + 'Sibling)' + type + ');if(!n){' + (b ? source :
1191
'n=e;while((n=n.' + n + 'Sibling)' + type + ');if(!n){' + source + '}') + '}' ) + '}';
1192
}
1193
break;
1194
}
1195
1196
}
1197
1198
// *** negation, user action and target pseudo-classes
1199
// *** UI element states and dynamic pseudo-classes
1200
// CSS3 :not, :checked, :enabled, :disabled, :target
1201
// CSS3 :active, :hover, :focus
1202
// CSS3 :link, :visited
1203
else if ((match = selector.match(Patterns.dpseudos)) && match[1]) {
1204
1205
switch (match[1].match(/^\w+/)[0]) {
1206
// CSS3 negation pseudo-class
1207
case 'not':
1208
// compile nested selectors, DO NOT pass the callback parameter
1209
// SIMPLENOT allow disabling complex selectors nested
1210
// in ':not()' pseudo-classes, breaks some test units
1211
expr = match[3].replace(reTrimSpaces, '');
1212
1213
if (Config.SIMPLENOT && !reSimpleNot.test(expr)) {
1214
// see above, log error but continue execution
1215
emit('Negation pseudo-class only accepts simple selectors "' + selector + '"');
1216
return '';
1217
} else {
1218
if ('compatMode' in doc) {
1219
source = 'if(!' + compile(expr, '', false) + '(e,s,r,d,h,g)){' + source + '}';
1220
} else {
1221
source = 'if(!s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}';
1222
}
1223
}
1224
break;
1225
1226
// CSS3 UI element states
1227
case 'checked':
1228
// for radio buttons checkboxes (HTML4) and options (HTML5)
1229
source = 'if((typeof e.form!=="undefined"&&(/^(?:radio|checkbox)$/i).test(e.type)&&e.checked)' +
1230
(Config.USE_HTML5 ? '||(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' : '') +
1231
'){' + source + '}';
1232
break;
1233
case 'disabled':
1234
// does not consider hidden input fields
1235
source = 'if(((typeof e.form!=="undefined"' +
1236
(Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') +
1237
')||s.isLink(e))&&e.disabled===true){' + source + '}';
1238
break;
1239
case 'enabled':
1240
// does not consider hidden input fields
1241
source = 'if(((typeof e.form!=="undefined"' +
1242
(Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') +
1243
')||s.isLink(e))&&e.disabled===false){' + source + '}';
1244
break;
1245
1246
// CSS3 lang pseudo-class
1247
case 'lang':
1248
test = '';
1249
if (match[2]) test = match[2].substr(0, 2) + '-';
1250
source = 'do{(n=e.lang||"").toLowerCase();' +
1251
'if((n==""&&h.lang=="' + match[2].toLowerCase() + '")||' +
1252
'(n&&(n=="' + match[2].toLowerCase() +
1253
'"||n.substr(0,3)=="' + test.toLowerCase() + '")))' +
1254
'{' + source + 'break;}}while((e=e.parentNode)&&e!==g);';
1255
break;
1256
1257
// CSS3 target pseudo-class
1258
case 'target':
1259
source = 'if(e.id==d.location.hash.slice(1)){' + source + '}';
1260
break;
1261
1262
// CSS3 dynamic pseudo-classes
1263
case 'link':
1264
source = 'if(s.isLink(e)&&!e.visited){' + source + '}';
1265
break;
1266
case 'visited':
1267
source = 'if(s.isLink(e)&&e.visited){' + source + '}';
1268
break;
1269
1270
// CSS3 user action pseudo-classes IE & FF3 have native support
1271
// these capabilities may be emulated by some event managers
1272
case 'active':
1273
if (XML_DOCUMENT) break;
1274
source = 'if(e===d.activeElement){' + source + '}';
1275
break;
1276
case 'hover':
1277
if (XML_DOCUMENT) break;
1278
source = 'if(e===d.hoverElement){' + source + '}';
1279
break;
1280
case 'focus':
1281
if (XML_DOCUMENT) break;
1282
source = NATIVE_FOCUS ?
1283
'if(e===d.activeElement&&d.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number")){' + source + '}' :
1284
'if(e===d.activeElement&&(e.type||e.href)){' + source + '}';
1285
break;
1286
1287
// CSS2 selected pseudo-classes, not part of current CSS3 drafts
1288
// the 'selected' property is only available for option elements
1289
case 'selected':
1290
// fix Safari selectedIndex property bug
1291
expr = BUGGY_SELECTED ? '||(n=e.parentNode)&&n.options[n.selectedIndex]===e' : '';
1292
source = 'if(/^option$/i.test(e.nodeName)&&(e.selected||e.checked' + expr + ')){' + source + '}';
1293
break;
1294
1295
default:
1296
break;
1297
}
1298
1299
}
1300
1301
else {
1302
1303
// this is where external extensions are
1304
// invoked if expressions match selectors
1305
expr = false;
1306
status = false;
1307
for (expr in Selectors) {
1308
if ((match = selector.match(Selectors[expr].Expression)) && match[1]) {
1309
result = Selectors[expr].Callback(match, source);
1310
source = result.source;
1311
status = result.status;
1312
if (status) { break; }
1313
}
1314
}
1315
1316
// if an extension fails to parse the selector
1317
// it must return a false boolean in "status"
1318
if (!status) {
1319
// log error but continue execution, don't throw real exceptions
1320
// because blocking following processes maybe is not a good idea
1321
emit('Unknown pseudo-class selector "' + selector + '"');
1322
return '';
1323
}
1324
1325
if (!expr) {
1326
// see above, log error but continue execution
1327
emit('Unknown token in selector "' + selector + '"');
1328
return '';
1329
}
1330
1331
}
1332
1333
// error if no matches found by the pattern scan
1334
if (!match) {
1335
emit('Invalid syntax in selector "' + selector + '"');
1336
return '';
1337
}
1338
1339
// ensure "match" is not null or empty since
1340
// we do not throw real DOMExceptions above
1341
selector = match && match[match.length - 1];
1342
}
1343
1344
return source;
1345
},
1346
1347
/*----------------------------- QUERY METHODS ------------------------------*/
1348
1349
// match element with selector
1350
// @return boolean
1351
match =
1352
function(element, selector, from, callback) {
1353
1354
var parts;
1355
1356
if (!(element && element.nodeType == 1)) {
1357
emit('Invalid element argument');
1358
return false;
1359
} else if (typeof selector != 'string') {
1360
emit('Invalid selector argument');
1361
return false;
1362
} else if (from && from.nodeType == 1 && !contains(from, element)) {
1363
return false;
1364
} else if (lastContext !== from) {
1365
// reset context data when it changes
1366
// and ensure context is set to a default
1367
switchContext(from || (from = element.ownerDocument));
1368
}
1369
1370
selector = selector.replace(reTrimSpaces, '');
1371
1372
Config.SHORTCUTS && (selector = Dom.shortcuts(selector, element, from));
1373
1374
if (lastMatcher != selector) {
1375
// process valid selector strings
1376
if ((parts = selector.match(reValidator)) && parts[0] == selector) {
1377
isSingleMatch = (parts = selector.match(reSplitGroup)).length < 2;
1378
// save passed selector
1379
lastMatcher = selector;
1380
lastPartsMatch = parts;
1381
} else {
1382
emit('The string "' + selector + '", is not a valid CSS selector');
1383
return false;
1384
}
1385
} else parts = lastPartsMatch;
1386
1387
// compile matcher resolvers if necessary
1388
if (!matchResolvers[selector] || matchContexts[selector] !== from) {
1389
matchResolvers[selector] = compile(isSingleMatch ? [selector] : parts, '', false);
1390
matchContexts[selector] = from;
1391
}
1392
1393
return matchResolvers[selector](element, Snapshot, [ ], doc, root, from, callback, new global.Object());
1394
},
1395
1396
// select only the first element
1397
// matching selector (document ordered)
1398
first =
1399
function(selector, from) {
1400
return select(selector, from, function() { return false; })[0] || null;
1401
},
1402
1403
// select elements matching selector
1404
// using new Query Selector API
1405
// or cross-browser client API
1406
// @return array
1407
select =
1408
function(selector, from, callback) {
1409
1410
var i, changed, element, elements, parts, token, original = selector;
1411
1412
if (arguments.length === 0) {
1413
emit('Not enough arguments');
1414
return [ ];
1415
} else if (typeof selector != 'string') {
1416
return [ ];
1417
} else if (from && !(/1|9|11/).test(from.nodeType)) {
1418
emit('Invalid or illegal context element');
1419
return [ ];
1420
} else if (lastContext !== from) {
1421
// reset context data when it changes
1422
// and ensure context is set to a default
1423
switchContext(from || (from = doc));
1424
}
1425
1426
if (Config.CACHING && (elements = Dom.loadResults(original, from, doc, root))) {
1427
return callback ? concatCall([ ], elements, callback) : elements;
1428
}
1429
1430
if (!OPERA_QSAPI && RE_SIMPLE_SELECTOR.test(selector)) {
1431
switch (selector.charAt(0)) {
1432
case '#':
1433
if (Config.UNIQUE_ID) {
1434
elements = (element = _byId(selector.slice(1), from)) ? [ element ] : [ ];
1435
}
1436
break;
1437
case '.':
1438
elements = _byClass(selector.slice(1), from);
1439
break;
1440
default:
1441
elements = _byTag(selector, from);
1442
break;
1443
}
1444
}
1445
1446
else if (!XML_DOCUMENT && Config.USE_QSAPI &&
1447
!(BUGGY_QUIRKS_QSAPI && RE_CLASS.test(selector)) &&
1448
!RE_BUGGY_QSAPI.test(selector)) {
1449
try {
1450
elements = from.querySelectorAll(selector);
1451
} catch(e) { }
1452
}
1453
1454
if (elements) {
1455
elements = callback ? concatCall([ ], elements, callback) :
1456
NATIVE_SLICE_PROTO ? slice.call(elements) : concatList([ ], elements);
1457
Config.CACHING && Dom.saveResults(original, from, doc, elements);
1458
return elements;
1459
}
1460
1461
selector = selector.replace(reTrimSpaces, '');
1462
1463
Config.SHORTCUTS && (selector = Dom.shortcuts(selector, from));
1464
1465
if ((changed = lastSelector != selector)) {
1466
// process valid selector strings
1467
if ((parts = selector.match(reValidator)) && parts[0] == selector) {
1468
isSingleSelect = (parts = selector.match(reSplitGroup)).length < 2;
1469
// save passed selector
1470
lastSelector = selector;
1471
lastPartsSelect = parts;
1472
} else {
1473
emit('The string "' + selector + '", is not a valid CSS selector');
1474
return [ ];
1475
}
1476
} else parts = lastPartsSelect;
1477
1478
// commas separators are treated sequentially to maintain order
1479
if (from.nodeType == 11) {
1480
1481
elements = byTagRaw('*', from);
1482
1483
} else if (!XML_DOCUMENT && isSingleSelect) {
1484
1485
if (changed) {
1486
// get right most selector token
1487
parts = selector.match(reSplitToken);
1488
token = parts[parts.length - 1];
1489
1490
// only last slice before :not rules
1491
lastSlice = token.split(':not')[0];
1492
1493
// position where token was found
1494
lastPosition = selector.length - token.length;
1495
}
1496
1497
// ID optimization RTL, to reduce number of elements to visit
1498
if (Config.UNIQUE_ID && (parts = lastSlice.match(Optimize.ID)) && (token = parts[1])) {
1499
if ((element = _byId(token, from))) {
1500
if (match(element, selector)) {
1501
callback && callback(element);
1502
elements = new global.Array(element);
1503
} else elements = new global.Array();
1504
}
1505
}
1506
1507
// ID optimization LTR, to reduce selection context searches
1508
else if (Config.UNIQUE_ID && (parts = selector.match(Optimize.ID)) && (token = parts[1])) {
1509
if ((element = _byId(token, doc))) {
1510
if ('#' + token == selector) {
1511
callback && callback(element);
1512
elements = new global.Array(element);
1513
} else if (/[>+~]/.test(selector)) {
1514
from = element.parentNode;
1515
} else {
1516
from = element;
1517
}
1518
} else elements = new global.Array();
1519
}
1520
1521
if (elements) {
1522
Config.CACHING && Dom.saveResults(original, from, doc, elements);
1523
return elements;
1524
}
1525
1526
if (!NATIVE_GEBCN && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) {
1527
if ((elements = _byTag(token, from)).length === 0) { return [ ]; }
1528
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*');
1529
}
1530
1531
else if ((parts = lastSlice.match(Optimize.CLASS)) && (token = parts[1])) {
1532
if ((elements = _byClass(token, from)).length === 0) { return [ ]; }
1533
if (reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1))) {
1534
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, '');
1535
} else {
1536
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, '*');
1537
}
1538
}
1539
1540
else if ((parts = selector.match(Optimize.CLASS)) && (token = parts[1])) {
1541
if ((elements = _byClass(token, from)).length === 0) { return [ ]; }
1542
for (i = 0, els = new global.Array(); elements.length > i; ++i) {
1543
els = concatList(els, elements[i].getElementsByTagName('*'));
1544
}
1545
elements = els;
1546
if (reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1))) {
1547
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, '');
1548
} else {
1549
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, '*');
1550
}
1551
}
1552
1553
else if (NATIVE_GEBCN && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) {
1554
if ((elements = _byTag(token, from)).length === 0) { return [ ]; }
1555
selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*');
1556
}
1557
1558
}
1559
1560
if (!elements) {
1561
elements = /^(?:applet|object)$/i.test(from.nodeName) ? from.childNodes : _byTag('*', from);
1562
}
1563
// end of prefiltering pass
1564
1565
// compile selector resolver if necessary
1566
if (!selectResolvers[selector] || selectContexts[selector] !== from) {
1567
selectResolvers[selector] = compile(isSingleSelect ? [selector] : parts, '', true);
1568
selectContexts[selector] = from;
1569
}
1570
1571
elements = selectResolvers[selector](elements, Snapshot, [ ], doc, root, from, callback, new global.Object());
1572
1573
Config.CACHING && Dom.saveResults(original, from, doc, elements);
1574
1575
return elements;
1576
},
1577
1578
/*-------------------------------- STORAGE ---------------------------------*/
1579
1580
// empty function handler
1581
FN = function(x) { return x; },
1582
1583
// compiled match functions returning booleans
1584
matchContexts = new global.Object(),
1585
matchResolvers = new global.Object(),
1586
1587
// compiled select functions returning collections
1588
selectContexts = new global.Object(),
1589
selectResolvers = new global.Object(),
1590
1591
// used to pass methods to compiled functions
1592
Snapshot = new global.Object({
1593
1594
// element indexing methods
1595
nthElement: nthElement,
1596
nthOfType: nthOfType,
1597
1598
// element inspection methods
1599
getAttribute: getAttribute,
1600
hasAttribute: hasAttribute,
1601
1602
// element selection methods
1603
byClass: _byClass,
1604
byName: byName,
1605
byTag: _byTag,
1606
byId: _byId,
1607
1608
// helper/check methods
1609
contains: contains,
1610
isEmpty: isEmpty,
1611
isLink: isLink,
1612
1613
// selection/matching
1614
select: select,
1615
match: match
1616
}),
1617
1618
Tokens = new global.Object({
1619
prefixes: prefixes,
1620
encoding: encoding,
1621
operators: operators,
1622
whitespace: whitespace,
1623
identifier: identifier,
1624
attributes: attributes,
1625
combinators: combinators,
1626
pseudoclass: pseudoclass,
1627
pseudoparms: pseudoparms,
1628
quotedvalue: quotedvalue
1629
});
1630
1631
/*------------------------------- PUBLIC API -------------------------------*/
1632
1633
// code referenced by extensions
1634
Dom.ACCEPT_NODE = ACCEPT_NODE;
1635
1636
// retrieve element by id attr
1637
Dom.byId = byId;
1638
1639
// retrieve elements by tag name
1640
Dom.byTag = byTag;
1641
1642
// retrieve elements by name attr
1643
Dom.byName = byName;
1644
1645
// retrieve elements by class name
1646
Dom.byClass = byClass;
1647
1648
// read the value of the attribute
1649
// as was in the original HTML code
1650
Dom.getAttribute = getAttribute;
1651
1652
// check for the attribute presence
1653
// as was in the original HTML code
1654
Dom.hasAttribute = hasAttribute;
1655
1656
// element match selector, return boolean true/false
1657
Dom.match = match;
1658
1659
// first element match only, return element or null
1660
Dom.first = first;
1661
1662
// elements matching selector, starting from element
1663
Dom.select = select;
1664
1665
// compile selector into ad-hoc javascript resolver
1666
Dom.compile = compile;
1667
1668
// check that two elements are ancestor/descendant
1669
Dom.contains = contains;
1670
1671
// handle selector engine configuration settings
1672
Dom.configure = configure;
1673
1674
// initialize caching for each document
1675
Dom.setCache = FN;
1676
1677
// load previously collected result set
1678
Dom.loadResults = FN;
1679
1680
// save previously collected result set
1681
Dom.saveResults = FN;
1682
1683
// handle missing context in selector strings
1684
Dom.shortcuts = FN;
1685
1686
// log resolvers errors/warnings
1687
Dom.emit = emit;
1688
1689
// options enabing specific engine functionality
1690
Dom.Config = Config;
1691
1692
// pass methods references to compiled resolvers
1693
Dom.Snapshot = Snapshot;
1694
1695
// operators descriptor
1696
// for attribute operators extensions
1697
Dom.Operators = Operators;
1698
1699
// selectors descriptor
1700
// for pseudo-class selectors extensions
1701
Dom.Selectors = Selectors;
1702
1703
// export string patterns
1704
Dom.Tokens = Tokens;
1705
1706
// export version string
1707
Dom.Version = version;
1708
1709
// add or overwrite user defined operators
1710
Dom.registerOperator =
1711
function(symbol, resolver) {
1712
Operators[symbol] || (Operators[symbol] = resolver);
1713
};
1714
1715
// add selector patterns for user defined callbacks
1716
Dom.registerSelector =
1717
function(name, rexp, func) {
1718
Selectors[name] || (Selectors[name] = new global.Object({
1719
Expression: rexp,
1720
Callback: func
1721
}));
1722
};
1723
1724
/*---------------------------------- INIT ----------------------------------*/
1725
1726
// init context specific variables
1727
switchContext(doc, true);
1728
1729
});
1730
1731