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