package/package.json000644 0000001367 13253057213011575 0ustar00000000 000000 { "name": "nwmatcher", "version": "1.4.4", "description": "A CSS3-compliant JavaScript selector engine.", "homepage": "http://javascript.nwbox.com/NWMatcher/", "main": "./src/nwmatcher", "keywords": [ "css", "matcher", "selector", "ender" ], "license": "MIT", "author": { "name": "Diego Perini", "email": "diego.perini@gmail.com", "web": "http://www.iport.it/" }, "maintainers": [ { "name": "Diego Perini", "email": "diego.perini@gmail.com", "web": "http://www.iport.it/" } ], "bugs": { "url": "http://github.com/dperini/nwmatcher/issues" }, "repository": { "type": "git", "url": "git://github.com/dperini/nwmatcher.git" }, "ender": "./lib/ender/bridge.js" } package/LICENSE000644 0000002073 13247334345010316 0ustar00000000 000000 Copyright (c) 2007-2018 Diego Perini (http://www.iport.it) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/README.md000644 0000007445 13167471757010612 0ustar00000000 000000 # [NWMatcher](http://dperini.github.io/nwmatcher/) A fast CSS selector engine and matcher. ## Installation To include NWMatcher in a standard web page: ```html ``` To use it with Node.js: ``` $ npm install nwmatcher ``` NWMatcher currently supports browsers (as a global, `NW.Dom`) and headless environments (as a CommonJS module). ## Supported Selectors Here is a list of all the CSS2/CSS3 [Supported selectors](https://github.com/dperini/nwmatcher/wiki/CSS-supported-selectors). ## Features and Compliance You can read more about NWMatcher [features and compliance](https://github.com/dperini/nwmatcher/wiki/Features-and-compliance) on the wiki. ## API ### DOM Selection #### `first( selector, context )` Returns a reference to the first element matching `selector`, starting at `context`. #### `match( element, selector, context )` Returns `true` if `element` matches `selector`, starting at `context`; returns `false` otherwise. #### `select( selector, context, callback )` Returns an array of all the elements matching `selector`, starting at `context`. If `callback` is provided, it is invoked for each matching element. ### DOM Helpers #### `byId( id, from )` Returns a reference to the first element with ID `id`, optionally filtered to descendants of the element `from`. #### `byTag( tag, from )` Returns an array of elements having the specified tag name `tag`, optionally filtered to descendants of the element `from`. #### `byClass( class, from )` Returns an array of elements having the specified class name `class`, optionally filtered to descendants of the element `from`. #### `byName( name, from )` Returns an array of elements having the specified value `name` for their name attribute, optionally filtered to descendants of the element `from`. #### `getAttribute( element, attribute )` Return the value read from the attribute of `element` with name `attribute`, as a string. #### `hasAttribute( element, attribute )` Returns true `element` has an attribute with name `attribute` set; returns `false` otherwise. ### Engine Configuration #### `configure( options )` The following is the list of currently available configuration options, their default values and descriptions, they are boolean flags that can be set to `true` or `false`: * `CACHING`: false - false to disable caching of result sets, true to enable * `ESCAPECHR`: true - true to allow CSS escaped identifiers, false to disallow * `NON_ASCII`: true - true to allow identifiers containing non-ASCII (utf-8) chars * `SELECTOR3`: true - switch syntax RE, true to use Level 3, false to use Level 2 * `UNICODE16`: true - true to allow identifiers containing Unicode (utf-16) chars * `SHORTCUTS`: false - false to disable mangled selector strings like "+div" or "ul>" * `SIMPLENOT`: true - true to disallow complex selectors nested in ':not()' classes * `SVG_LCASE`: false - false to disable matching lowercase tag names of SVG elements * `UNIQUE_ID`: true - true to disallow multiple elements with the same id (strict) * `USE_HTML5`: true - true to use HTML5 specs for ":checked" and similar UI states * `USE_QSAPI`: true - true to use browsers native Query Selector API if available * `VERBOSITY`: true - true to throw exceptions, false to skip throwing exceptions * `LOGERRORS`: true - true to print console errors or warnings, false to mute them Example: ```js NW.Dom.configure( { USE_QSAPI: false, VERBOSITY: false } ); ``` #### `registerOperator( symbol, resolver )` Registers a new symbol and its matching resolver in the operators table. Example: ```js NW.Dom.registerOperator( '!=', 'n!="%m"' ); ``` #### `registerSelector( name, rexp, func )` Registers a new selector, with the matching regular expression and the appropriate resolver function, in the selectors table. package/src/modules/nwmatcher-cache.js000644 0000012631 13247334406015136 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * Caching/memoization module for NWMatcher * * Added capabilities: * * - Mutation Events are feature tested and used safely * - handle caching different document types HTML/XML/SVG * - store result sets for different selectors / contexts * - simultaneously control mutation on multiple documents * */ (function(global) { // export the public API for CommonJS implementations, // for headless JS engines or for standard web browsers var Dom = // as CommonJS/NodeJS module typeof exports == 'object' ? exports : // create or extend NW namespace ((global.NW || (global.NW = { })) && (global.NW.Dom || (global.NW.Dom = { }))), Contexts = { }, Results = { }, isEnabled = false, isExpired = true, isPaused = false, context = global.document, root = context.documentElement, // timing pauses now = 0, // last time cache initialization was called lastCalled = 0, // minimum time allowed between calls to the cache initialization minCacheRest = 15, //ms mutationTest = function(type, callback) { var isSupported = false, root = document.documentElement, div = document.createElement('div'), handler = function() { isSupported = true; }; root.insertBefore(div, root.firstChild); div.addEventListener(type, handler, true); if (callback && callback.call) callback(div); div.removeEventListener(type, handler, true); root.removeChild(div); return isSupported; }, // check for Mutation Events, DOMAttrModified should be // enough to ensure DOMNodeInserted/DOMNodeRemoved exist HACKED_MUTATION_EVENTS = false, NATIVE_MUTATION_EVENTS = root.addEventListener ? mutationTest('DOMAttrModified', function(e) { e.setAttribute('id', 'nw'); }) : false, loadResults = function(selector, from, doc, root) { if (isEnabled && !isPaused) { if (!isExpired) { if (Results[selector] && Contexts[selector] === from) { return Results[selector]; } } else { // pause caching while we are getting // hammered by dom mutations (jdalton) now = (new Date).getTime(); if ((now - lastCalled) < minCacheRest) { isPaused = isExpired = true; setTimeout(function() { isPaused = false; }, minCacheRest); } else setCache(true, doc); lastCalled = now; } } return undefined; }, saveResults = function(selector, from, doc, data) { Contexts[selector] = from; Results[selector] = data; return; }, /*-------------------------------- CACHING ---------------------------------*/ // invoked by mutation events to expire cached parts mutationWrapper = function(event) { var d = event.target.ownerDocument || event.target; stopMutation(d); expireCache(d); }, // append mutation events startMutation = function(d) { if (!d.isCaching && d.addEventListener) { // FireFox/Opera/Safari/KHTML have support for Mutation Events d.addEventListener('DOMAttrModified', mutationWrapper, true); d.addEventListener('DOMNodeInserted', mutationWrapper, true); d.addEventListener('DOMNodeRemoved', mutationWrapper, true); d.isCaching = true; } }, // remove mutation events stopMutation = function(d) { if (d.isCaching && d.removeEventListener) { d.removeEventListener('DOMAttrModified', mutationWrapper, true); d.removeEventListener('DOMNodeInserted', mutationWrapper, true); d.removeEventListener('DOMNodeRemoved', mutationWrapper, true); d.isCaching = false; } }, // enable/disable context caching system // @d optional document context (iframe, xml document) // script loading context will be used as default context setCache = function(enable, d) { if (!!enable) { isExpired = false; startMutation(d); } else { isExpired = true; stopMutation(d); } isEnabled = !!enable; }, // expire complete cache // can be invoked by Mutation Events or // programmatically by other code/scripts // document context is mandatory no checks expireCache = function(d) { isExpired = true; Contexts = { }; Results = { }; }; if (!NATIVE_MUTATION_EVENTS && root.addEventListener && Element && Element.prototype) { if (mutationTest('DOMNodeInserted', function(e) { e.appendChild(document.createElement('div')); }) && mutationTest('DOMNodeRemoved', function(e) { e.removeChild(e.appendChild(document.createElement('div'))); })) { HACKED_MUTATION_EVENTS = true; Element.prototype._setAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function(name, val) { this._setAttribute(name, val); mutationWrapper({ target: this, type: 'DOMAttrModified', attrName: name, attrValue: val }); }; } } isEnabled = NATIVE_MUTATION_EVENTS || HACKED_MUTATION_EVENTS; /*------------------------------- PUBLIC API -------------------------------*/ // save results into cache Dom.saveResults = saveResults; // load results from cache Dom.loadResults = loadResults; // expire DOM tree cache Dom.expireCache = expireCache; // enable/disable cache Dom.setCache = setCache; })(this); package/src/modules/nwmatcher-jquery.js000644 0000007520 13247334431015411 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * this is just a small example to show * how an extension for NWMatcher could be * adapted to handle special jQuery selectors * * Child Selectors * :even, :odd, :eq, :lt, :gt, :first, :last, :nth * * Pseudo Selectors * :has, :button, :header, :input, :checkbox, :radio, :file, :image * :password, :reset, :submit, :text, :hidden, :visible, :parent * */ // for structural pseudo-classes extensions NW.Dom.registerSelector( 'jquery:child', /^\:((?:(nth|eq|lt|gt)\(([^()]*)\))|(?:even|odd|first|last))(.*)/i, (function(global) { return function(match, source, selector) { var status = true, ACCEPT_NODE = NW.Dom.ACCEPT_NODE; switch (match[1].toLowerCase()) { case 'odd': source = source.replace(ACCEPT_NODE, 'if((x=x^1)==0){' + ACCEPT_NODE + '}'); break; case 'even': source = source.replace(ACCEPT_NODE, 'if((x=x^1)==1){' + ACCEPT_NODE + '}'); break; case 'first': source = 'n=h.getElementsByTagName(e.nodeName);if(n.length&&n[0]===e){' + source + '}'; break; case 'last': source = 'n=h.getElementsByTagName(e.nodeName);if(n.length&&n[n.length-1]===e){' + source + '}'; break; default: switch (match[2].toLowerCase()) { case 'nth': source = 'n=h.getElementsByTagName(e.nodeName);if(n.length&&n[' + match[3] + ']===e){' + source + '}'; break; case 'eq': source = source.replace(ACCEPT_NODE, 'if(x++==' + match[3] + '){' + ACCEPT_NODE + '}'); break; case 'lt': source = source.replace(ACCEPT_NODE, 'if(x++<' + match[3] + '){' + ACCEPT_NODE + '}'); break; case 'gt': source = source.replace(ACCEPT_NODE, 'if(x++>' + match[3] + '){' + ACCEPT_NODE + '}'); break; default: status = false; break; } break; } // compiler will add this to "source" return { 'source': source, 'status': status }; }; })(this)); // for element pseudo-classes extensions NW.Dom.registerSelector( 'jquery:pseudo', /^\:(has|checkbox|file|image|password|radio|reset|submit|text|button|input|header|hidden|visible|parent)(?:\(\s*(["']*)?([^'"()]*)\2\s*\))?(.*)/i, (function(global) { return function(match, source) { var status = true, ACCEPT_NODE = NW.Dom.ACCEPT_NODE; switch(match[1].toLowerCase()) { case 'has': source = source.replace(ACCEPT_NODE, 'if(e.getElementsByTagName("' + match[3].replace(/^\s|\s$/g, '') + '")[0]){' + ACCEPT_NODE + '}'); break; case 'checkbox': case 'file': case 'image': case 'password': case 'radio': case 'reset': case 'submit': case 'text': // :checkbox, :file, :image, :password, :radio, :reset, :submit, :text source = 'if(/^' + match[1] + '$/i.test(e.type)){' + source + '}'; break; case 'button': case 'input': source = 'if(e.type||/button/i.test(e.nodeName)){' + source + '}'; break; case 'header': source = 'if(/h[1-6]/i.test(e.nodeName)){' + source + '}'; break; case 'hidden': source = 'if(!e.offsetWidth&&!e.offsetHeight){' + source + '}'; break; case 'visible': source = 'if(e.offsetWidth||e.offsetHeight){' + source + '}'; break; case 'parent': source += 'if(e.firstChild){' + source + '}'; break; default: status = false; break; } // compiler will add this to "source" return { 'source': source, 'status': status }; }; })(this)); package/src/modules/nwmatcher-pseudos.js000644 0000021667 13247334450015565 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * CSS3 pseudo-classes extension for NWMatcher * * Added capabilities: * * - structural pseudo-classes * * :root, :empty, * :nth-child(), nth-of-type(), * :nth-last-child(), nth-last-of-type(), * :first-child, :last-child, :only-child * :first-of-type, :last-of-type, :only-of-type * * - negation, language, target and UI element pseudo-classes * * :not(), :target, :lang(), :target * :link, :visited, :active, :focus, :hover, * :checked, :disabled, :enabled, :selected */ (function(global) { var LINK_NODES = { 'a': 1, 'A': 1, 'area': 1, 'AREA': 1, 'link': 1, 'LINK': 1 }, root = document.documentElement, contains = 'compareDocumentPosition' in root ? function(container, element) { return (container.compareDocumentPosition(element) & 16) == 16; } : 'contains' in root ? function(container, element) { return element.nodeType == 1 && container.contains(element); } : function(container, element) { while ((element = element.parentNode) && element.nodeType == 1) { if (element === container) return true; } return false; }, isLink = function(element) { return element.getAttribute('href') && LINK_NODES[element.nodeName]; }, isEmpty = function(node) { node = node.firstChild; while (node) { if (node.nodeType == 3 || node.nodeName > '@') return false; node = node.nextSibling; } return true; }, nthElement = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling'; while ((element = element[succ])) { if (element.nodeName > '@') ++count; } return count; }, nthOfType = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling', type = element.nodeName; while ((element = element[succ])) { if (element.nodeName == type) ++count; } return count; }; NW.Dom.Snapshot['contains'] = contains; NW.Dom.Snapshot['isLink'] = isLink; NW.Dom.Snapshot['isEmpty'] = isEmpty; NW.Dom.Snapshot['nthOfType'] = nthOfType; NW.Dom.Snapshot['nthElement'] = nthElement; })(this); NW.Dom.registerSelector( 'nwmatcher: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, (function(global) { return function(match, source) { var a, n, b, status = true, test, type; switch (match[1]) { case 'root': if (match[3]) source = 'if(e===h||s.contains(h,e)){' + source + '}'; else source = 'if(e===h){' + source + '}'; break; case 'empty': source = 'if(s.isEmpty(e)){' + source + '}'; break; default: if (match[1] && match[2]) { if (match[2] == 'n') { source = 'if(e!==h){' + source + '}'; break; } else if (match[2] == 'even') { a = 2; b = 0; } else if (match[2] == 'odd') { a = 2; b = 1; } else { b = ((n = match[2].match(/(-?\d+)$/)) ? parseInt(n[1], 10) : 0); a = ((n = match[2].match(/(-?\d*)n/i)) ? parseInt(n[1], 10) : 0); if (n && n[1] == '-') a = -1; } test = a > 1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n>=' + b + '&&(n-(' + b + '))%' + a + '==0' : a < -1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n<=' + b + '&&(n-(' + b + '))%' + a + '==0' : a === 0 ? 'n==' + b : a == -1 ? 'n<=' + b : 'n>=' + b; source = 'if(e!==h){' + 'n=s[' + (/-of-type/i.test(match[1]) ? '"nthOfType"' : '"nthElement"') + ']' + '(e,' + (/last/i.test(match[1]) ? 'true' : 'false') + ');' + 'if(' + test + '){' + source + '}' + '}'; } else if (match[1]) { a = /first/i.test(match[1]) ? 'previous' : 'next'; n = /only/i.test(match[1]) ? 'previous' : 'next'; b = /first|last/i.test(match[1]); type = /-of-type/i.test(match[1]) ? '&&n.nodeName!==e.nodeName' : '&&n.nodeName<"@"'; source = 'if(e!==h){' + ( 'n=e;while((n=n.' + a + 'Sibling)' + type + ');if(!n){' + (b ? source : 'n=e;while((n=n.' + n + 'Sibling)' + type + ');if(!n){' + source + '}') + '}' ) + '}'; } else { status = false; } break; } return { 'source': source, 'status': status }; }; })(this)); NW.Dom.registerSelector( 'nwmatcher: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, (function(global) { var doc = global.document, Config = NW.Dom.Config, Tokens = NW.Dom.Tokens, reTrimSpace = RegExp('^\\s+|\\s+$', 'g'), reSimpleNot = RegExp('^((?!:not)' + '(' + Tokens.prefixes + '|' + Tokens.identifier + '|\\([^()]*\\))+|\\[' + Tokens.attributes + '\\])$'); return function(match, source) { var expr, status = true, test; switch (match[1].match(/^\w+/)[0]) { case 'not': expr = match[3].replace(reTrimSpace, ''); if (Config.SIMPLENOT && !reSimpleNot.test(expr)) { NW.Dom.emit('Negation pseudo-class only accepts simple selectors "' + selector + '"'); } else { if ('compatMode' in doc) { source = 'if(!' + NW.Dom.compile(expr, '', false) + '(e,s,d,h,g)){' + source + '}'; } else { source = 'if(!s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}'; } } break; case 'checked': source = 'if((typeof e.form!=="undefined"&&(/^(?:radio|checkbox)$/i).test(e.type)&&e.checked)' + (Config.USE_HTML5 ? '||(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' : '') + '){' + source + '}'; break; case 'disabled': source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===true){' + source + '}'; break; case 'enabled': source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===false){' + source + '}'; break; case 'lang': test = ''; if (match[2]) test = match[2].substr(0, 2) + '-'; source = 'do{(n=e.lang||"").toLowerCase();' + 'if((n==""&&h.lang=="' + match[2].toLowerCase() + '")||' + '(n&&(n=="' + match[2].toLowerCase() + '"||n.substr(0,3)=="' + test.toLowerCase() + '")))' + '{' + source + 'break;}}while((e=e.parentNode)&&e!==g);'; break; case 'target': source = 'if(e.id==d.location.hash.slice(1)){' + source + '}'; break; case 'link': source = 'if(s.isLink(e)&&!e.visited){' + source + '}'; break; case 'visited': source = 'if(s.isLink(e)&&e.visited){' + source + '}'; break; case 'active': source = 'if(e===d.activeElement){' + source + '}'; break; case 'hover': source = 'if(e===d.hoverElement){' + source + '}'; break; case 'focus': source = 'hasFocus' in doc ? 'if(e===d.activeElement&&d.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number")){' + source + '}' : 'if(e===d.activeElement&&(e.type||e.href)){' + source + '}'; break; case 'selected': source = 'if(/^option$/i.test(e.nodeName)&&(e.selected||e.checked)){' + source + '}'; break; default: status = false; break; } return { 'source': source, 'status': status }; }; })(this)); NW.Dom.registerSelector( 'nwmatcher:epseudos', /^((?:[:]{1,2}(?:after|before|first-letter|first-line))|(?:[:]{2,2}(?:selection|backdrop|placeholder)))?(.*)/i, (function(global) { return function(match, source) { var status = true; switch (match[1].match(/(\w+)$/)[0]) { case 'after': case 'before': case 'first-letter': case 'first-line': case 'selection': case 'backdrop': case 'placeholder': source = 'if(!(/1|11/).test(e.nodeType)){' + source + '}'; break; default: status = false; break; } return { 'source': source, 'status': status }; }; })(this)); package/src/modules/nwmatcher-shortcuts.js000644 0000001742 13141104045016115 0ustar00000000 000000 NW.Dom.shortcuts = (function() { // match missing R/L context var nextID = 0, reLeftContext = /^[\x20\t\n\r\f]*[>+~]/, reRightContext = /[>+~][\x20\t\n\r\f]*$/; return function(selector, from, alt) { // add left context if missing if (reLeftContext.test(selector)) { if (from.nodeType == 9) { selector = '* ' + selector; } else if (/html|body/i.test(from.nodeName)) { selector = from.nodeName + ' ' + selector; } else if (alt) { selector = NW.Dom.shortcuts(selector, alt); } else if (from.nodeType == 1 && from.id) { selector = '#' + from.id + ' ' + selector; } else { ++nextID; selector = '#' + (from.id = 'NW' + nextID) + ' ' + selector; //NW.Dom.emit('Unable to resolve a context for the shortcut selector "' + selector + '"'); } } // add right context if missing if (reRightContext.test(selector)) { selector += ' *'; } return selector; }; })(); package/src/modules/nwmatcher-traversal.js000644 0000005322 13141104045016060 0ustar00000000 000000 /* * Element Traversal methods from Juriy Zaytsev (kangax) * used to emulate Prototype up/down/previous/next methods */ (function(D){ // TODO: all of this needs tests var match = D.match, select = D.select, root = document.documentElement, // Use the Element Traversal API if available. nextElement = 'nextElementSibling', previousElement = 'previousElementSibling', parentElement = 'parentElement'; // Fall back to the DOM Level 1 API. if (!(nextElement in root)) nextElement = 'nextSibling'; if (!(previousElement in root)) previousElement = 'previousSibling'; if (!(parentElement in root)) parentElement = 'parentNode'; function walkElements(property, element, expr) { var i = 0, isIndex = typeof expr == 'number'; if (typeof expr == 'undefined') { isIndex = true; expr = 0; } while ((element = element[property])) { if (element.nodeType != 1) continue; if (isIndex) { ++i; if (i == expr) return element; } else if (match(element, expr)) { return element; } } return null; } /** * @method up * @param {HTMLElement} element element to walk from * @param {String | Number} expr CSS expression or an index * @return {HTMLElement | null} */ function up(element, expr) { return walkElements(parentElement, element, expr); } /** * @method next * @param {HTMLElement} element element to walk from * @param {String | Number} expr CSS expression or an index * @return {HTMLElement | null} */ function next(element, expr) { return walkElements(nextElement, element, expr); } /** * @method previous * @param {HTMLElement} element element to walk from * @param {String | Number} expr CSS expression or an index * @return {HTMLElement | null} */ function previous(element, expr) { return walkElements(previousElement, element, expr); } /** * @method down * @param {HTMLElement} element element to walk from * @param {String | Number} expr CSS expression or an index * @return {HTMLElement | null} */ function down(element, expr) { var isIndex = typeof expr == 'number', descendants, index, descendant; if (expr === null) { element = element.firstChild; while (element && element.nodeType != 1) element = element[nextElement]; return element; } if (!isIndex && match(element, expr) || isIndex && expr === 0) return element; descendants = select('*', element); if (isIndex) return descendants[expr] || null; index = 0; while ((descendant = descendants[index]) && !match(descendant, expr)) { ++index; } return descendant || null; } D.up = up; D.down = down; D.next = next; D.previous = previous; })(NW.Dom); package/src/modules/nwmatcher-webforms.js000644 0000007162 13247334514015722 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * this is just a small example to show * how an extension for NWMatcher could be * adapted to handle WebForms/HTML5 selectors * * Pseudo Selectors * :default, :indeterminate, :optional, :required, * :valid, :invalid, :in-range, :out-of-range, * :read-only, :read-write * :has, :matches (not yet in a defined specification) * */ // for UI pseudo-classes extensions (WebForms/HTML5) NW.Dom.registerSelector( 'html5:pseudos', /^\:(default|indeterminate|optional|required|valid|invalid|in-range|out-of-range|read-only|read-write)(.*)/, (function(global) { return function(match, source) { var status = true, HTML5PseudoClasses = { 'default': 4, 'indeterminate': 4, 'invalid': 4, 'valid': 4, 'optional': 4, 'required': 4, 'read-write': 4, 'read-only': 4 }; switch (match[1]) { // HTML5 UI element states (form controls) case 'default': // only radio buttons, check boxes and option elements source = 'if(((typeof e.form!=="undefined"&&(/radio|checkbox/i).test(e.type))||/option/i.test(e.nodeName))&&(e.defaultChecked||e.defaultSelected)){' + source + '}'; break; case 'indeterminate': // only radio buttons, check boxes and option elements source = 'if(typeof e.form!=="undefined"&&(/radio|checkbox/i).test(e.type)&&s.select("[checked]",e.form).length===0){' + source + '}'; break; case 'optional': // only fields for which "required" applies source = 'if(typeof e.form!=="undefined"&&typeof e.required!="undefined"&&!e.required){' + source + '}'; break; case 'required': // only fields for which "required" applies source = 'if(typeof e.form!=="undefined"&&typeof e.required!="undefined"&&e.required){' + source + '}'; break; case 'read-write': // only fields for which "readOnly" applies source = 'if(typeof e.form!=="undefined"&&typeof e.readOnly!="undefined"&&!e.readOnly){' + source + '}'; break; case 'read-only': // only fields for which "readOnly" applies source = 'if(typeof e.form!=="undefined"&&typeof e.readOnly!="undefined"&&e.readOnly){' + source + '}'; break; case 'invalid': // only fields for which validity applies source = 'if(typeof e.form!=="undefined"&&typeof e.validity=="object"&&!e.validity.valid){' + source + '}'; break; case 'valid': // only fields for which validity applies source = 'if(typeof e.form!=="undefined"&&typeof e.validity=="object"&&e.validity.valid){' + source + '}'; break; case 'in-range': // only fields for which validity applies source = 'if(typeof e.form!=="undefined"&&' + '(s.getAttribute(e,"min")||s.getAttribute(e,"max"))&&' + 'typeof e.validity=="object"&&!e.validity.typeMismatch&&' + '!e.validity.rangeUnderflow&&!e.validity.rangeOverflow){' + source + '}'; break; case 'out-of-range': // only fields for which validity applies source = 'if(typeof e.form!=="undefined"&&' + '(s.getAttribute(e,"min")||s.getAttribute(e,"max"))&&' + 'typeof e.validity=="object"&&(e.validity.rangeUnderflow||e.validity.rangeOverflow)){' + source + '}'; break; default: status = false; break; } // compiler will add this to "source" return { 'source': source, 'status': status }; }; })(this)); package/src/nwmatcher-base.js000644 0000060627 13247334107013343 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * nwmatcher-base.js - A fast CSS selector engine and matcher * * Author: Diego Perini * Version: 1.4.4 * Created: 20070722 * Release: 20180305 * * License: * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE * Download: * http://javascript.nwbox.com/NWMatcher/nwmatcher.js */ (function(global, factory) { if (typeof module == 'object' && typeof exports == 'object') { module.exports = factory; } else if (typeof define === 'function' && define["amd"]) { define(factory); } else { global.NW || (global.NW = { }); global.NW.Dom = factory(global); } })(this, function(global) { var version = 'nwmatcher-1.4.4', doc = global.document, root = doc.documentElement, isSingleMatch, isSingleSelect, lastSlice, lastContext, lastPosition, lastMatcher, lastSelector, lastPartsMatch, lastPartsSelect, prefixes = '(?:[#.:]|::)?', operators = '([~*^$|!]?={1})', whitespace = '[\\x20\\t\\n\\r\\f]', combinators = '\\x20|[>+~](?=[^>+~])', pseudoparms = '(?:[-+]?\\d*n)?[-+]?\\d*', skip_groups = '\\[.*\\]|\\(.*\\)|\\{.*\\}', any_esc_chr = '\\\\.', alphalodash = '[_a-zA-Z]', non_asc_chr = '[^\\x00-\\x9f]', escaped_chr = '\\\\[^\\n\\r\\f0-9a-fA-F]', unicode_chr = '\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|' + whitespace + ')?', quotedvalue = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' + "|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'", reSplitGroup = /([^,\\()[\]]+|\[[^[\]]*\]|\[.*\]|\([^()]+\)|\(.*\)|\{[^{}]+\}|\{.*\}|\\.)+/g, reTrimSpaces = RegExp('[\\n\\r\\f]|^' + whitespace + '+|' + whitespace + '+$', 'g'), reEscapedChars = /\\([0-9a-fA-F]{1,6}[\x20\t\n\r\f]?|.)|([\x22\x27])/g, standardValidator, extendedValidator, reValidator, attrcheck, attributes, attrmatcher, pseudoclass, reOptimizeSelector, reSimpleNot, reSplitToken, Optimize, identifier, extensions = '.+', Patterns = { children: RegExp('^' + whitespace + '*\\>' + whitespace + '*(.*)'), adjacent: RegExp('^' + whitespace + '*\\+' + whitespace + '*(.*)'), relative: RegExp('^' + whitespace + '*\\~' + whitespace + '*(.*)'), ancestor: RegExp('^' + whitespace + '+(.*)'), universal: RegExp('^\\*(.*)') }, Tokens = { prefixes: prefixes, identifier: identifier, attributes: attributes }, QUIRKS_MODE, XML_DOCUMENT, GEBTN = 'getElementsByTagName' in doc, GEBCN = 'getElementsByClassName' in doc, IE_LT_9 = typeof doc.addEventListener != 'function', LINK_NODES = { a: 1, A: 1, area: 1, AREA: 1, link: 1, LINK: 1 }, ATTR_BOOLEAN = { checked: 1, disabled: 1, ismap: 1, multiple: 1, readonly: 1, selected: 1 }, ATTR_DEFAULT = { value: 'defaultValue', checked: 'defaultChecked', selected: 'defaultSelected' }, ATTR_URIDATA = { action: 2, cite: 2, codebase: 2, data: 2, href: 2, longdesc: 2, lowsrc: 2, src: 2, usemap: 2 }, HTML_TABLE = { 'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1, 'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1, 'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1, 'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1, 'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1 }, NATIVE_TRAVERSAL_API = 'nextElementSibling' in root && 'previousElementSibling' in root, Selectors = { }, Operators = { '=': "n=='%m'", '^=': "n.indexOf('%m')==0", '*=': "n.indexOf('%m')>-1", '|=': "(n+'-').indexOf('%m-')==0", '~=': "(' '+n+' ').indexOf(' %m ')>-1", '$=': "n.substr(n.length-'%m'.length)=='%m'" }, concatCall = function(data, elements, callback) { var i = -1, element; while ((element = elements[++i])) { if (false === callback(data[data.length] = element)) { break; } } return data; }, switchContext = function(from, force) { var oldDoc = doc; lastContext = from; doc = from.ownerDocument || from; if (force || oldDoc !== doc) { root = doc.documentElement; XML_DOCUMENT = doc.createElement('DiV').nodeName == 'DiV'; QUIRKS_MODE = !XML_DOCUMENT && typeof doc.compatMode == 'string' ? doc.compatMode.indexOf('CSS') < 0 : (function() { var style = doc.createElement('div').style; return style && (style.width = 1) && style.width == '1px'; })(); Config.CACHING && Dom.setCache(true, doc); } }, codePointToUTF16 = function(codePoint) { if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\\ufffd'; } if (codePoint < 0x10000) { var lowHex = '000' + codePoint.toString(16); return '\\u' + lowHex.substr(lowHex.length - 4); } return '\\u' + (((codePoint - 0x10000) >> 0x0a) + 0xd800).toString(16) + '\\u' + (((codePoint - 0x10000) % 0x400) + 0xdc00).toString(16); }, stringFromCodePoint = function(codePoint) { if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\ufffd'; } if (codePoint < 0x10000) { return String.fromCharCode(codePoint); } return String.fromCodePoint ? String.fromCodePoint(codePoint) : String.fromCharCode( ((codePoint - 0x10000) >> 0x0a) + 0xd800, ((codePoint - 0x10000) % 0x400) + 0xdc00); }, convertEscapes = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { return p2 ? '\\' + p2 : (/^[0-9a-fA-F]/).test(p1) ? codePointToUTF16(parseInt(p1, 16)) : (/^[\\\x22\x27]/).test(p1) ? substring : p1; } ); }, unescapeIdentifier = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { return p2 ? p2 : (/^[0-9a-fA-F]/).test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : (/^[\\\x22\x27]/).test(p1) ? substring : p1; } ); }, byIdRaw = function(id, elements) { var i = -1, element; while ((element = elements[++i])) { if (element.getAttribute('id') == id) { break; } } return element || null; }, _byId = !IE_LT_9 ? function(id, from) { id = (/\\/).test(id) ? unescapeIdentifier(id) : id; return from.getElementById && from.getElementById(id) || byIdRaw(id, from.getElementsByTagName('*')); } : function(id, from) { var element = null; id = (/\\/).test(id) ? unescapeIdentifier(id) : id; if (XML_DOCUMENT || from.nodeType != 9) { return byIdRaw(id, from.getElementsByTagName('*')); } if ((element = from.getElementById(id)) && element.name == id && from.getElementsByName) { return byIdRaw(id, from.getElementsByName(id)); } return element; }, byId = function(id, from) { from || (from = doc); if (lastContext !== from) { switchContext(from); } return _byId(id, from); }, byTagRaw = function(tag, from) { var any = tag == '*', element = from, elements = [ ], next = element.firstChild; any || (tag = tag.toUpperCase()); while ((element = next)) { if (element.tagName > '@' && (any || element.tagName.toUpperCase() == tag)) { elements[elements.length] = element; } if ((next = element.firstChild || element.nextSibling)) continue; while (!next && (element = element.parentNode) && element !== from) { next = element.nextSibling; } } return elements; }, getAttribute = !IE_LT_9 ? function(node, attribute) { return node.getAttribute(attribute); } : function(node, attribute) { attribute = attribute.toLowerCase(); if (typeof node[attribute] == 'object') { return node.attributes[attribute] && node.attributes[attribute].value; } return ( attribute == 'type' ? node.getAttribute(attribute) : ATTR_URIDATA[attribute] ? node.getAttribute(attribute, 2) : ATTR_BOOLEAN[attribute] ? node.getAttribute(attribute) ? attribute : 'false' : (node = node.getAttributeNode(attribute)) && node.value); }, hasAttribute = !IE_LT_9 && root.hasAttribute ? function(node, attribute) { return node.hasAttribute(attribute); } : function(node, attribute) { var obj = node.getAttributeNode(attribute = attribute.toLowerCase()); return ATTR_DEFAULT[attribute] && attribute != 'value' ? node[ATTR_DEFAULT[attribute]] : obj && obj.specified; }, configure = function(option) { if (typeof option == 'string') { return !!Config[option]; } if (typeof option != 'object') { return Config; } for (var i in option) { Config[i] = !!option[i]; if (i == 'SIMPLENOT') { matchContexts = { }; matchResolvers = { }; selectContexts = { }; selectResolvers = { }; } } setIdentifierSyntax(); reValidator = RegExp(Config.SIMPLENOT ? standardValidator : extendedValidator); return true; }, emit = function(message) { if (Config.VERBOSITY) { throw Error(message); } if (Config.LOGERRORS && console && console.log) { console.log(message); } }, Config = { CACHING: false, ESCAPECHR: true, NON_ASCII: true, SELECTOR3: true, UNICODE16: true, SHORTCUTS: false, SIMPLENOT: true, SVG_LCASE: false, UNIQUE_ID: true, USE_HTML5: true, VERBOSITY: true, LOGERRORS: true }, initialize = function(doc) { setIdentifierSyntax(); switchContext(doc, true); }, setIdentifierSyntax = function() { var syntax = '', start = Config['SELECTOR3'] ? '-{2}|' : ''; Config['NON_ASCII'] && (syntax += '|' + non_asc_chr); Config['UNICODE16'] && (syntax += '|' + unicode_chr); Config['ESCAPECHR'] && (syntax += '|' + escaped_chr); syntax += (Config['UNICODE16'] || Config['ESCAPECHR']) ? '' : '|' + any_esc_chr; identifier = '-?(?:' + start + alphalodash + syntax + ')(?:-|[0-9]|' + alphalodash + syntax + ')*'; attrcheck = '(' + quotedvalue + '|' + identifier + ')'; attributes = whitespace + '*(' + identifier + '(?::' + identifier + ')?)' + whitespace + '*(?:' + operators + whitespace + '*' + attrcheck + ')?' + whitespace + '*' + '(i)?' + whitespace + '*'; attrmatcher = attributes.replace(attrcheck, '([\\x22\\x27]*)((?:\\\\?.)*?)\\3'); pseudoclass = '((?:' + pseudoparms + '|' + quotedvalue + '|' + prefixes + identifier + '|' + '\\[' + attributes + '\\]|' + '\\(.+\\)|' + whitespace + '*|' + ',)+)'; standardValidator = '(?=[\\x20\\t\\n\\r\\f]*[^>+~(){}<>])' + '(' + '\\*' + '|(?:' + prefixes + identifier + ')' + '|' + combinators + '|\\[' + attributes + '\\]' + '|\\(' + pseudoclass + '\\)' + '|\\{' + extensions + '\\}' + '|(?:,|' + whitespace + '*)' + ')+'; reSimpleNot = RegExp('^(' + '(?!:not)' + '(' + prefixes + identifier + '|\\([^()]*\\))+' + '|\\[' + attributes + '\\]' + ')$'); reSplitToken = RegExp('(' + prefixes + identifier + '|' + '\\[' + attributes + '\\]|' + '\\(' + pseudoclass + '\\)|' + '\\\\.|[^\\x20\\t\\n\\r\\f>+~])+', 'g'); reOptimizeSelector = RegExp(identifier + '|^$'); Optimize = { ID: RegExp('^\\*?#(' + identifier + ')|' + skip_groups), TAG: RegExp('^(' + identifier + ')|' + skip_groups), CLASS: RegExp('^\\.(' + identifier + '$)|' + skip_groups) }; Patterns.id = RegExp('^#(' + identifier + ')(.*)'); Patterns.tagName = RegExp('^(' + identifier + ')(.*)'); Patterns.className = RegExp('^\\.(' + identifier + ')(.*)'); Patterns.attribute = RegExp('^\\[' + attrmatcher + '\\](.*)'); Tokens.identifier = identifier; Tokens.attributes = attributes; extendedValidator = standardValidator.replace(pseudoclass, '.*'); reValidator = RegExp(standardValidator); }, ACCEPT_NODE = 'r[r.length]=c[k];if(f&&false===f(c[k]))break main;else continue main;', REJECT_NODE = IE_LT_9 ? 'if(e.nodeName<"A")continue;' : '', TO_UPPER_CASE = IE_LT_9 ? '.toUpperCase()' : '', compile = function(selector, source, mode) { var parts = typeof selector == 'string' ? selector.match(reSplitGroup) : selector; typeof source == 'string' || (source = ''); if (parts.length == 1) { source += compileSelector(parts[0], mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } else { var i = -1, seen = { }, token; while ((token = parts[++i])) { token = token.replace(reTrimSpaces, ''); if (!seen[token] && (seen[token] = true)) { source += compileSelector(token, mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } } } if (mode) { return Function('c,s,d,h,g,f', 'var N,n,x=0,k=-1,e,r=[];main:while((e=c[++k])){' + source + '}return r;'); } else { return Function('e,s,d,h,g,f', 'var N,n,x=0,k=e;' + source + 'return false;'); } }, compileSelector = function(selector, source, mode) { var k = 0, expr, match, result, status, test, type; while (selector) { k++; if ((match = selector.match(Patterns.universal))) { expr = ''; } else if ((match = selector.match(Patterns.id))) { match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; source = 'if(' + (XML_DOCUMENT ? 's.getAttribute(e,"id")' : '(e.submit?s.getAttribute(e,"id"):e.id)') + '=="' + match[1] + '"' + '){' + source + '}'; } else if ((match = selector.match(Patterns.tagName))) { test = Config.SVG_LCASE ? '||e.nodeName=="' + match[1].toLowerCase() + '"' : ''; source = 'if(e.nodeName' + (XML_DOCUMENT ? '=="' + match[1] + '"' : TO_UPPER_CASE + '=="' + match[1].toUpperCase() + '"' + test) + '){' + source + '}'; } else if ((match = selector.match(Patterns.className))) { match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; match[1] = QUIRKS_MODE ? match[1].toLowerCase() : match[1]; source = 'if((n=' + (XML_DOCUMENT ? 'e.getAttribute("class")' : 'e.className') + ')&&n.length&&(" "+' + (QUIRKS_MODE ? 'n.toLowerCase()' : 'n') + '.replace(/' + whitespace + '+/g," ")+" ").indexOf(" ' + match[1] + ' ")>-1' + '){' + source + '}'; } else if ((match = selector.match(Patterns.attribute))) { expr = match[1].split(':'); expr = expr.length == 2 ? expr[1] : expr[0] + ''; if (match[2] && !Operators[match[2]]) { emit('Unsupported operator in attribute selectors "' + selector + '"'); return ''; } test = 'false'; if (match[2] && match[4] && (test = Operators[match[2]])) { match[4] = (/\\/).test(match[4]) ? convertEscapes(match[4]) : match[4]; type = match[5] == 'i' || HTML_TABLE[expr.toLowerCase()]; test = test.replace(/\%m/g, type ? match[4].toLowerCase() : match[4]); } else if (match[2] == '!=' || match[2] == '=') { test = 'n' + match[2] + '=""'; } source = 'if(n=s.hasAttribute(e,"' + match[1] + '")){' + (match[2] ? 'n=s.getAttribute(e,"' + match[1] + '")' : '') + (type && match[2] ? '.toLowerCase();' : ';') + 'if(' + (match[2] ? test : 'n') + '){' + source + '}}'; } else if ((match = selector.match(Patterns.adjacent))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;if((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}e=N' + k + ';'; } else if ((match = selector.match(Patterns.relative))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;while((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}e=N' + k + ';'; } else if ((match = selector.match(Patterns.children))) { source = 'var N' + k + '=e;if((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } else if ((match = selector.match(Patterns.ancestor))) { source = 'var N' + k + '=e;while((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } else { expr = false; status = false; for (expr in Selectors) { if ((match = selector.match(Selectors[expr].Expression)) && match[1]) { result = Selectors[expr].Callback(match, source); if ('match' in result) { match = result.match; } source = result.source; status = result.status; if (status) { break; } } } if (!status) { emit('Unknown pseudo-class selector "' + selector + '"'); return ''; } if (!expr) { emit('Unknown token in selector "' + selector + '"'); return ''; } } if (!match) { emit('Invalid syntax in selector "' + selector + '"'); return ''; } selector = match && match[match.length - 1]; } return source; }, match = function(element, selector, from, callback) { var parts; if (!(element && element.nodeType == 1)) { emit('Invalid element argument'); return false; } else if (typeof selector != 'string') { emit('Invalid selector argument'); return false; } else if (lastContext !== from) { switchContext(from || (from = element.ownerDocument)); } selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); Config.SHORTCUTS && (selector = Dom.shortcuts(selector, element, from)); if (lastMatcher != selector) { if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleMatch = (parts = selector.match(reSplitGroup)).length < 2; lastMatcher = selector; lastPartsMatch = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return false; } } else parts = lastPartsMatch; if (!matchResolvers[selector] || matchContexts[selector] !== from) { matchResolvers[selector] = compile(isSingleMatch ? [selector] : parts, '', false); matchContexts[selector] = from; } return matchResolvers[selector](element, Snapshot, doc, root, from, callback); }, first = function(selector, from) { return select(selector, from, function() { return false; })[0] || null; }, select = function(selector, from, callback) { var i, changed, element, elements, parts, token, original = selector; if (arguments.length === 0) { emit('Not enough arguments'); return [ ]; } else if (typeof selector != 'string') { return [ ]; } else if (from && !(/1|9|11/).test(from.nodeType)) { emit('Invalid or illegal context element'); return [ ]; } else if (lastContext !== from) { switchContext(from || (from = doc)); } if (Config.CACHING && (elements = Dom.loadResults(original, from, doc, root))) { return callback ? concatCall([ ], elements, callback) : elements; } selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); Config.SHORTCUTS && (selector = Dom.shortcuts(selector, from)); if ((changed = lastSelector != selector)) { if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleSelect = (parts = selector.match(reSplitGroup)).length < 2; lastSelector = selector; lastPartsSelect = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return [ ]; } } else parts = lastPartsSelect; if (from.nodeType == 11) { elements = byTagRaw('*', from); } else if (isSingleSelect) { if (changed) { parts = selector.match(reSplitToken); token = parts[parts.length - 1]; lastSlice = token.split(':not'); lastSlice = lastSlice[lastSlice.length - 1]; lastPosition = selector.length - token.length; } if (Config.UNIQUE_ID && lastSlice && (parts = lastSlice.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, from))) { if (match(element, selector)) { callback && callback(element); elements = [element]; } else elements = [ ]; } } else if (Config.UNIQUE_ID && (parts = selector.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, doc))) { if ('#' + token == selector) { callback && callback(element); elements = [element]; } else if (/[>+~]/.test(selector)) { from = element.parentNode; } else { from = element; } } else elements = [ ]; } if (elements) { Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; } if (!XML_DOCUMENT && GEBTN && lastSlice && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) { if ((elements = from.getElementsByTagName(token)).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*'); } else if (!XML_DOCUMENT && GEBCN && lastSlice && (parts = lastSlice.match(Optimize.CLASS)) && (token = parts[1])) { if ((elements = from.getElementsByClassName(unescapeIdentifier(token))).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1)) ? '' : '*'); } } if (!elements) { if (IE_LT_9) { elements = /^(?:applet|object)$/i.test(from.nodeName) ? from.children : byTagRaw('*', from); } else { elements = from.getElementsByTagName('*'); } } if (!selectResolvers[selector] || selectContexts[selector] !== from) { selectResolvers[selector] = compile(isSingleSelect ? [selector] : parts, REJECT_NODE, true); selectContexts[selector] = from; } elements = selectResolvers[selector](elements, Snapshot, doc, root, from, callback); Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; }, FN = function(x) { return x; }, matchContexts = { }, matchResolvers = { }, selectContexts = { }, selectResolvers = { }, Snapshot = { byId: _byId, match: match, select: select, getAttribute: getAttribute, hasAttribute: hasAttribute }, Dom = { ACCEPT_NODE: ACCEPT_NODE, byId: byId, match: match, first: first, select: select, compile: compile, configure: configure, setCache: FN, shortcuts: FN, loadResults: FN, saveResults: FN, emit: emit, Config: Config, Snapshot: Snapshot, Operators: Operators, Selectors: Selectors, Tokens: Tokens, Version: version, registerOperator: function(symbol, resolver) { Operators[symbol] || (Operators[symbol] = resolver); }, registerSelector: function(name, rexp, func) { Selectors[name] || (Selectors[name] = { Expression: rexp, Callback: func }); } }; initialize(doc); return Dom; }); package/src/nwmatcher-noqsa.js000644 0000100240 13250016226013525 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * nwmatcher-noqsa.js - A fast CSS selector engine and matcher * * Author: Diego Perini * Version: 1.4.4 * Created: 20070722 * Release: 20180305 * * License: * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE * Download: * http://javascript.nwbox.com/NWMatcher/nwmatcher.js */ (function(global, factory) { if (typeof module == 'object' && typeof exports == 'object') { module.exports = factory; } else if (typeof define === 'function' && define["amd"]) { define(factory); } else { global.NW || (global.NW = { }); global.NW.Dom = factory(global); } })(this, function(global) { var version = 'nwmatcher-1.4.4', doc = global.document, root = doc.documentElement, isSingleMatch, isSingleSelect, lastSlice, lastContext, lastPosition, lastMatcher, lastSelector, lastPartsMatch, lastPartsSelect, prefixes = '(?:[#.:]|::)?', operators = '([~*^$|!]?={1})', whitespace = '[\\x20\\t\\n\\r\\f]', combinators = '\\x20|[>+~](?=[^>+~])', pseudoparms = '(?:[-+]?\\d*n)?[-+]?\\d*', skip_groups = '\\[.*\\]|\\(.*\\)|\\{.*\\}', any_esc_chr = '\\\\.', alphalodash = '[_a-zA-Z]', non_asc_chr = '[^\\x00-\\x9f]', escaped_chr = '\\\\[^\\n\\r\\f0-9a-fA-F]', unicode_chr = '\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|' + whitespace + ')?', quotedvalue = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' + "|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'", reSplitGroup = /([^,\\()[\]]+|\[[^[\]]*\]|\[.*\]|\([^()]+\)|\(.*\)|\{[^{}]+\}|\{.*\}|\\.)+/g, reTrimSpaces = RegExp('[\\n\\r\\f]|^' + whitespace + '+|' + whitespace + '+$', 'g'), reEscapedChars = /\\([0-9a-fA-F]{1,6}[\x20\t\n\r\f]?|.)|([\x22\x27])/g, standardValidator, extendedValidator, reValidator, attrcheck, attributes, attrmatcher, pseudoclass, reOptimizeSelector, reSimpleNot, reSplitToken, Optimize, identifier, extensions = '.+', Patterns = { 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, dpseudos: /^\:(link|visited|target|active|focus|hover|checked|disabled|enabled|selected|lang\(([-\w]{2,})\)|(?:matches|not)\(\s?(:nth(?:-last)?(?:-child|-of-type)\(\s?(?:even|odd|(?:[-+]{0,1}\d*n\s?)?[-+]{0,1}\s?\d*)\s?\)|[^()]*)\s?\))?(.*)/i, epseudos: /^((?:[:]{1,2}(?:after|before|first-letter|first-line))|(?:[:]{2,2}(?:selection|backdrop|placeholder)))?(.*)/i, children: RegExp('^' + whitespace + '?\\>' + whitespace + '?(.*)'), adjacent: RegExp('^' + whitespace + '?\\+' + whitespace + '?(.*)'), relative: RegExp('^' + whitespace + '?\\~' + whitespace + '?(.*)'), ancestor: RegExp('^' + whitespace + '+(.*)'), universal: RegExp('^\\*(.*)') }, Tokens = { prefixes: prefixes, identifier: identifier, attributes: attributes }, QUIRKS_MODE, XML_DOCUMENT, GEBTN = 'getElementsByTagName' in doc, GEBCN = 'getElementsByClassName' in doc, IE_LT_9 = typeof doc.addEventListener != 'function', LINK_NODES = { a: 1, A: 1, area: 1, AREA: 1, link: 1, LINK: 1 }, ATTR_BOOLEAN = { checked: 1, disabled: 1, ismap: 1, multiple: 1, readonly: 1, selected: 1 }, ATTR_DEFAULT = { value: 'defaultValue', checked: 'defaultChecked', selected: 'defaultSelected' }, ATTR_URIDATA = { action: 2, cite: 2, codebase: 2, data: 2, href: 2, longdesc: 2, lowsrc: 2, src: 2, usemap: 2 }, HTML_TABLE = { 'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1, 'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1, 'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1, 'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1, 'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1 }, NATIVE_TRAVERSAL_API = 'nextElementSibling' in root && 'previousElementSibling' in root, Selectors = { }, Operators = { '=': "n=='%m'", '^=': "n.indexOf('%m')==0", '*=': "n.indexOf('%m')>-1", '|=': "(n+'-').indexOf('%m-')==0", '~=': "(' '+n+' ').indexOf(' %m ')>-1", '$=': "n.substr(n.length-'%m'.length)=='%m'" }, concatCall = function(data, elements, callback) { var i = -1, element; while ((element = elements[++i])) { if (false === callback(data[data.length] = element)) { break; } } return data; }, switchContext = function(from, force) { var oldDoc = doc; lastContext = from; doc = from.ownerDocument || from; if (force || oldDoc !== doc) { root = doc.documentElement; XML_DOCUMENT = doc.createElement('DiV').nodeName == 'DiV'; QUIRKS_MODE = !XML_DOCUMENT && typeof doc.compatMode == 'string' ? doc.compatMode.indexOf('CSS') < 0 : (function() { var style = doc.createElement('div').style; return style && (style.width = 1) && style.width == '1px'; })(); Config.CACHING && Dom.setCache(true, doc); } }, codePointToUTF16 = function(codePoint) { if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\\ufffd'; } if (codePoint < 0x10000) { var lowHex = '000' + codePoint.toString(16); return '\\u' + lowHex.substr(lowHex.length - 4); } return '\\u' + (((codePoint - 0x10000) >> 0x0a) + 0xd800).toString(16) + '\\u' + (((codePoint - 0x10000) % 0x400) + 0xdc00).toString(16); }, stringFromCodePoint = function(codePoint) { if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\ufffd'; } if (codePoint < 0x10000) { return String.fromCharCode(codePoint); } return String.fromCodePoint ? String.fromCodePoint(codePoint) : String.fromCharCode( ((codePoint - 0x10000) >> 0x0a) + 0xd800, ((codePoint - 0x10000) % 0x400) + 0xdc00); }, convertEscapes = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { return p2 ? '\\' + p2 : (/^[0-9a-fA-F]/).test(p1) ? codePointToUTF16(parseInt(p1, 16)) : (/^[\\\x22\x27]/).test(p1) ? substring : p1; } ); }, unescapeIdentifier = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { return p2 ? p2 : (/^[0-9a-fA-F]/).test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : (/^[\\\x22\x27]/).test(p1) ? substring : p1; } ); }, byIdRaw = function(id, elements) { var i = -1, element; while ((element = elements[++i])) { if (element.getAttribute('id') == id) { break; } } return element || null; }, _byId = !IE_LT_9 ? function(id, from) { id = (/\\/).test(id) ? unescapeIdentifier(id) : id; return from.getElementById && from.getElementById(id) || byIdRaw(id, from.getElementsByTagName('*')); } : function(id, from) { var element = null; id = (/\\/).test(id) ? unescapeIdentifier(id) : id; if (XML_DOCUMENT || from.nodeType != 9) { return byIdRaw(id, from.getElementsByTagName('*')); } if ((element = from.getElementById(id)) && element.name == id && from.getElementsByName) { return byIdRaw(id, from.getElementsByName(id)); } return element; }, byId = function(id, from) { from || (from = doc); if (lastContext !== from) { switchContext(from); } return _byId(id, from); }, byTagRaw = function(tag, from) { var any = tag == '*', element = from, elements = [ ], next = element.firstChild; any || (tag = tag.toUpperCase()); while ((element = next)) { if (element.tagName > '@' && (any || element.tagName.toUpperCase() == tag)) { elements[elements.length] = element; } if ((next = element.firstChild || element.nextSibling)) continue; while (!next && (element = element.parentNode) && element !== from) { next = element.nextSibling; } } return elements; }, contains = 'compareDocumentPosition' in root ? function(container, element) { return (container.compareDocumentPosition(element) & 16) == 16; } : 'contains' in root ? function(container, element) { return container !== element && container.contains(element); } : function(container, element) { while ((element = element.parentNode)) { if (element === container) return true; } return false; }, getAttribute = !IE_LT_9 ? function(node, attribute) { return node.getAttribute(attribute); } : function(node, attribute) { attribute = attribute.toLowerCase(); if (typeof node[attribute] == 'object') { return node.attributes[attribute] && node.attributes[attribute].value; } return ( attribute == 'type' ? node.getAttribute(attribute) : ATTR_URIDATA[attribute] ? node.getAttribute(attribute, 2) : ATTR_BOOLEAN[attribute] ? node.getAttribute(attribute) ? attribute : 'false' : (node = node.getAttributeNode(attribute)) && node.value); }, hasAttribute = !IE_LT_9 && root.hasAttribute ? function(node, attribute) { return node.hasAttribute(attribute); } : function(node, attribute) { var obj = node.getAttributeNode(attribute = attribute.toLowerCase()); return ATTR_DEFAULT[attribute] && attribute != 'value' ? node[ATTR_DEFAULT[attribute]] : obj && obj.specified; }, isEmpty = function(node) { node = node.firstChild; while (node) { if (node.nodeType == 3 || node.nodeName > '@') return false; node = node.nextSibling; } return true; }, isLink = function(element) { return hasAttribute(element, 'href') && LINK_NODES[element.nodeName]; }, nthElement = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling'; while ((element = element[succ])) { if (element.nodeName > '@') ++count; } return count; }, nthOfType = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling', type = element.nodeName; while ((element = element[succ])) { if (element.nodeName == type) ++count; } return count; }, configure = function(option) { if (typeof option == 'string') { return !!Config[option]; } if (typeof option != 'object') { return Config; } for (var i in option) { Config[i] = !!option[i]; if (i == 'SIMPLENOT') { matchContexts = { }; matchResolvers = { }; selectContexts = { }; selectResolvers = { }; } } setIdentifierSyntax(); reValidator = RegExp(Config.SIMPLENOT ? standardValidator : extendedValidator); return true; }, emit = function(message) { if (Config.VERBOSITY) { throw Error(message); } if (Config.LOGERRORS && console && console.log) { console.log(message); } }, Config = { CACHING: false, ESCAPECHR: true, NON_ASCII: true, SELECTOR3: true, UNICODE16: true, SHORTCUTS: false, SIMPLENOT: true, SVG_LCASE: false, UNIQUE_ID: true, USE_HTML5: true, VERBOSITY: true, LOGERRORS: true }, initialize = function(doc) { setIdentifierSyntax(); switchContext(doc, true); }, setIdentifierSyntax = function() { var syntax = '', start = Config['SELECTOR3'] ? '-{2}|' : ''; Config['NON_ASCII'] && (syntax += '|' + non_asc_chr); Config['UNICODE16'] && (syntax += '|' + unicode_chr); Config['ESCAPECHR'] && (syntax += '|' + escaped_chr); syntax += (Config['UNICODE16'] || Config['ESCAPECHR']) ? '' : '|' + any_esc_chr; identifier = '-?(?:' + start + alphalodash + syntax + ')(?:-|[0-9]|' + alphalodash + syntax + ')*'; attrcheck = '(' + quotedvalue + '|' + identifier + ')'; attributes = whitespace + '*(' + identifier + '(?::' + identifier + ')?)' + whitespace + '*(?:' + operators + whitespace + '*' + attrcheck + ')?' + whitespace + '*' + '(i)?' + whitespace + '*'; attrmatcher = attributes.replace(attrcheck, '([\\x22\\x27]*)((?:\\\\?.)*?)\\3'); pseudoclass = '((?:' + pseudoparms + '|' + quotedvalue + '|' + prefixes + identifier + '|' + '\\[' + attributes + '\\]|' + '\\(.+\\)|' + whitespace + '*|' + ',)+)'; standardValidator = '(?=[\\x20\\t\\n\\r\\f]*[^>+~(){}<>])' + '(' + '\\*' + '|(?:' + prefixes + identifier + ')' + '|' + combinators + '|\\[' + attributes + '\\]' + '|\\(' + pseudoclass + '\\)' + '|\\{' + extensions + '\\}' + '|(?:,|' + whitespace + '*)' + ')+'; reSimpleNot = RegExp('^(' + '(?!:not)' + '(' + prefixes + identifier + '|\\([^()]*\\))+' + '|\\[' + attributes + '\\]' + ')$'); reSplitToken = RegExp('(' + prefixes + identifier + '|' + '\\[' + attributes + '\\]|' + '\\(' + pseudoclass + '\\)|' + '\\\\.|[^\\x20\\t\\n\\r\\f>+~])+', 'g'); reOptimizeSelector = RegExp(identifier + '|^$'); Optimize = { ID: RegExp('^\\*?#(' + identifier + ')|' + skip_groups), TAG: RegExp('^(' + identifier + ')|' + skip_groups), CLASS: RegExp('^\\.(' + identifier + '$)|' + skip_groups) }; Patterns.id = RegExp('^#(' + identifier + ')(.*)'); Patterns.tagName = RegExp('^(' + identifier + ')(.*)'); Patterns.className = RegExp('^\\.(' + identifier + ')(.*)'); Patterns.attribute = RegExp('^\\[' + attrmatcher + '\\](.*)'); Tokens.identifier = identifier; Tokens.attributes = attributes; extendedValidator = standardValidator.replace(pseudoclass, '.*'); reValidator = RegExp(standardValidator); }, ACCEPT_NODE = 'r[r.length]=c[k];if(f&&false===f(c[k]))break main;else continue main;', REJECT_NODE = IE_LT_9 ? 'if(e.nodeName<"A")continue;' : '', TO_UPPER_CASE = IE_LT_9 ? '.toUpperCase()' : '', compile = function(selector, source, mode) { var parts = typeof selector == 'string' ? selector.match(reSplitGroup) : selector; typeof source == 'string' || (source = ''); if (parts.length == 1) { source += compileSelector(parts[0], mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } else { var i = -1, seen = { }, token; while ((token = parts[++i])) { token = token.replace(reTrimSpaces, ''); if (!seen[token] && (seen[token] = true)) { source += compileSelector(token, mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } } } if (mode) { return Function('c,s,d,h,g,f', 'var N,n,x=0,k=-1,e,r=[];main:while((e=c[++k])){' + source + '}return r;'); } else { return Function('e,s,d,h,g,f', 'var N,n,x=0,k=e;' + source + 'return false;'); } }, compileSelector = function(selector, source, mode) { var a, b, n, k = 0, expr, match, result, status, test, type; while (selector) { k++; if ((match = selector.match(Patterns.universal))) { expr = ''; } else if ((match = selector.match(Patterns.id))) { match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; source = 'if(' + (XML_DOCUMENT ? 's.getAttribute(e,"id")' : '(e.submit?s.getAttribute(e,"id"):e.id)') + '=="' + match[1] + '"' + '){' + source + '}'; } else if ((match = selector.match(Patterns.tagName))) { test = Config.SVG_LCASE ? '||e.nodeName=="' + match[1].toLowerCase() + '"' : ''; source = 'if(e.nodeName' + (XML_DOCUMENT ? '=="' + match[1] + '"' : TO_UPPER_CASE + '=="' + match[1].toUpperCase() + '"' + test) + '){' + source + '}'; } else if ((match = selector.match(Patterns.className))) { match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; match[1] = QUIRKS_MODE ? match[1].toLowerCase() : match[1]; source = 'if((n=' + (XML_DOCUMENT ? 'e.getAttribute("class")' : 'e.className') + ')&&n.length&&(" "+' + (QUIRKS_MODE ? 'n.toLowerCase()' : 'n') + '.replace(/' + whitespace + '+/g," ")+" ").indexOf(" ' + match[1] + ' ")>-1' + '){' + source + '}'; } else if ((match = selector.match(Patterns.attribute))) { expr = match[1].split(':'); expr = expr.length == 2 ? expr[1] : expr[0] + ''; if (match[2] && !Operators[match[2]]) { emit('Unsupported operator in attribute selectors "' + selector + '"'); return ''; } test = 'false'; if (match[2] && match[4] && (test = Operators[match[2]])) { match[4] = (/\\/).test(match[4]) ? convertEscapes(match[4]) : match[4]; type = match[5] == 'i' || HTML_TABLE[expr.toLowerCase()]; test = test.replace(/\%m/g, type ? match[4].toLowerCase() : match[4]); } else if (match[2] == '!=' || match[2] == '=') { test = 'n' + match[2] + '=""'; } source = 'if(n=s.hasAttribute(e,"' + match[1] + '")){' + (match[2] ? 'n=s.getAttribute(e,"' + match[1] + '")' : '') + (type && match[2] ? '.toLowerCase();' : ';') + 'if(' + (match[2] ? test : 'n') + '){' + source + '}}'; } else if ((match = selector.match(Patterns.adjacent))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;if((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}e=N' + k + ';'; } else if ((match = selector.match(Patterns.relative))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;while((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}e=N' + k + ';'; } else if ((match = selector.match(Patterns.children))) { source = 'var N' + k + '=e;if((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } else if ((match = selector.match(Patterns.ancestor))) { source = 'var N' + k + '=e;while((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } else if ((match = selector.match(Patterns.spseudos)) && match[1]) { switch (match[1]) { case 'root': if (match[3]) { source = 'if(e===h||s.contains(h,e)){' + source + '}'; } else { source = 'if(e===h){' + source + '}'; } break; case 'empty': source = 'if(s.isEmpty(e)){' + source + '}'; break; default: if (match[1] && match[2]) { if (match[2] == 'n') { source = 'if(e!==h){' + source + '}'; break; } else if (match[2] == 'even') { a = 2; b = 0; } else if (match[2] == 'odd') { a = 2; b = 1; } else { b = ((n = match[2].match(/(-?\d+)$/)) ? parseInt(n[1], 10) : 0); a = ((n = match[2].match(/(-?\d*)n/i)) ? parseInt(n[1], 10) : 0); if (n && n[1] == '-') a = -1; } test = a > 1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n>=' + b + '&&(n-(' + b + '))%' + a + '==0' : a < -1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n<=' + b + '&&(n-(' + b + '))%' + a + '==0' : a === 0 ? 'n==' + b : a == -1 ? 'n<=' + b : 'n>=' + b; source = 'if(e!==h){' + 'n=s[' + (/-of-type/i.test(match[1]) ? '"nthOfType"' : '"nthElement"') + ']' + '(e,' + (/last/i.test(match[1]) ? 'true' : 'false') + ');' + 'if(' + test + '){' + source + '}' + '}'; } else { a = /first/i.test(match[1]) ? 'previous' : 'next'; n = /only/i.test(match[1]) ? 'previous' : 'next'; b = /first|last/i.test(match[1]); type = /-of-type/i.test(match[1]) ? '&&n.nodeName!=e.nodeName' : '&&n.nodeName<"@"'; source = 'if(e!==h){' + ( 'n=e;while((n=n.' + a + 'Sibling)' + type + ');if(!n){' + (b ? source : 'n=e;while((n=n.' + n + 'Sibling)' + type + ');if(!n){' + source + '}') + '}' ) + '}'; } break; } } else if ((match = selector.match(Patterns.dpseudos)) && match[1]) { switch (match[1].match(/^\w+/)[0]) { case 'matches': expr = match[3].replace(reTrimSpaces, ''); source = 'if(s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}'; break; case 'not': expr = match[3].replace(reTrimSpaces, ''); if (Config.SIMPLENOT && !reSimpleNot.test(expr)) { emit('Negation pseudo-class only accepts simple selectors "' + selector + '"'); return ''; } else { if ('compatMode' in doc) { source = 'if(!' + compile(expr, '', false) + '(e,s,d,h,g)){' + source + '}'; } else { source = 'if(!s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}'; } } break; case 'checked': source = 'if((typeof e.form!=="undefined"&&(/^(?:radio|checkbox)$/i).test(e.type)&&e.checked)' + (Config.USE_HTML5 ? '||(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' : '') + '){' + source + '}'; break; case 'disabled': source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===true){' + source + '}'; break; case 'enabled': source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===false){' + source + '}'; break; case 'lang': test = ''; if (match[2]) test = match[2].substr(0, 2) + '-'; source = 'do{(n=e.lang||"").toLowerCase();' + 'if((n==""&&h.lang=="' + match[2].toLowerCase() + '")||' + '(n&&(n=="' + match[2].toLowerCase() + '"||n.substr(0,3)=="' + test.toLowerCase() + '")))' + '{' + source + 'break;}}while((e=e.parentNode)&&e!==g);'; break; case 'target': source = 'if(e.id==d.location.hash.slice(1)){' + source + '}'; break; case 'link': source = 'if(s.isLink(e)&&!e.visited){' + source + '}'; break; case 'visited': source = 'if(s.isLink(e)&&e.visited){' + source + '}'; break; case 'active': source = 'if(e===d.activeElement){' + source + '}'; break; case 'hover': source = 'if(e===d.hoverElement){' + source + '}'; break; case 'focus': source = 'hasFocus' in doc ? 'if(e===d.activeElement&&d.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number")){' + source + '}' : 'if(e===d.activeElement&&(e.type||e.href)){' + source + '}'; break; case 'selected': source = 'if(/^option$/i.test(e.nodeName)&&(e.selected||e.checked)){' + source + '}'; break; default: break; } } else if ((match = selector.match(Patterns.epseudos)) && match[1]) { source = 'if(!(/1|11/).test(e.nodeType)){' + source + '}'; } else { expr = false; status = false; for (expr in Selectors) { if ((match = selector.match(Selectors[expr].Expression)) && match[1]) { result = Selectors[expr].Callback(match, source); if ('match' in result) { match = result.match; } source = result.source; status = result.status; if (status) { break; } } } if (!status) { emit('Unknown pseudo-class selector "' + selector + '"'); return ''; } if (!expr) { emit('Unknown token in selector "' + selector + '"'); return ''; } } if (!match) { emit('Invalid syntax in selector "' + selector + '"'); return ''; } selector = match && match[match.length - 1]; } return source; }, match = function(element, selector, from, callback) { var parts; if (!(element && element.nodeType == 1)) { emit('Invalid element argument'); return false; } else if (typeof selector != 'string') { emit('Invalid selector argument'); return false; } else if (lastContext !== from) { switchContext(from || (from = element.ownerDocument)); } selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); Config.SHORTCUTS && (selector = Dom.shortcuts(selector, element, from)); if (lastMatcher != selector) { if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleMatch = (parts = selector.match(reSplitGroup)).length < 2; lastMatcher = selector; lastPartsMatch = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return false; } } else parts = lastPartsMatch; if (!matchResolvers[selector] || matchContexts[selector] !== from) { matchResolvers[selector] = compile(isSingleMatch ? [selector] : parts, '', false); matchContexts[selector] = from; } return matchResolvers[selector](element, Snapshot, doc, root, from, callback); }, first = function(selector, from) { return select(selector, from, function() { return false; })[0] || null; }, select = function(selector, from, callback) { var i, changed, element, elements, parts, token, original = selector; if (arguments.length === 0) { emit('Not enough arguments'); return [ ]; } else if (typeof selector != 'string') { return [ ]; } else if (from && !(/1|9|11/).test(from.nodeType)) { emit('Invalid or illegal context element'); return [ ]; } else if (lastContext !== from) { switchContext(from || (from = doc)); } if (Config.CACHING && (elements = Dom.loadResults(original, from, doc, root))) { return callback ? concatCall([ ], elements, callback) : elements; } selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); Config.SHORTCUTS && (selector = Dom.shortcuts(selector, from)); if ((changed = lastSelector != selector)) { if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleSelect = (parts = selector.match(reSplitGroup)).length < 2; lastSelector = selector; lastPartsSelect = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return [ ]; } } else parts = lastPartsSelect; if (from.nodeType == 11) { elements = byTagRaw('*', from); } else if (isSingleSelect) { if (changed) { parts = selector.match(reSplitToken); token = parts[parts.length - 1]; lastSlice = token.split(':not'); lastSlice = lastSlice[lastSlice.length - 1]; lastPosition = selector.length - token.length; } if (Config.UNIQUE_ID && lastSlice && (parts = lastSlice.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, from))) { if (match(element, selector)) { callback && callback(element); elements = [element]; } else elements = [ ]; } } else if (Config.UNIQUE_ID && (parts = selector.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, doc))) { if ('#' + token == selector) { callback && callback(element); elements = [element]; } else if (/[>+~]/.test(selector)) { from = element.parentNode; } else { from = element; } } else elements = [ ]; } if (elements) { Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; } if (!XML_DOCUMENT && GEBTN && lastSlice && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) { if ((elements = from.getElementsByTagName(token)).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*'); } else if (!XML_DOCUMENT && GEBCN && lastSlice && (parts = lastSlice.match(Optimize.CLASS)) && (token = parts[1])) { if ((elements = from.getElementsByClassName(unescapeIdentifier(token))).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1)) ? '' : '*'); } } if (!elements) { if (IE_LT_9) { elements = /^(?:applet|object)$/i.test(from.nodeName) ? from.children : byTagRaw('*', from); } else { elements = from.getElementsByTagName('*'); } } if (!selectResolvers[selector] || selectContexts[selector] !== from) { selectResolvers[selector] = compile(isSingleSelect ? [selector] : parts, REJECT_NODE, true); selectContexts[selector] = from; } elements = selectResolvers[selector](elements, Snapshot, doc, root, from, callback); Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; }, FN = function(x) { return x; }, matchContexts = { }, matchResolvers = { }, selectContexts = { }, selectResolvers = { }, Snapshot = { byId: _byId, match: match, select: select, isLink: isLink, isEmpty: isEmpty, contains: contains, nthOfType: nthOfType, nthElement: nthElement, getAttribute: getAttribute, hasAttribute: hasAttribute }, Dom = { ACCEPT_NODE: ACCEPT_NODE, byId: byId, match: match, first: first, select: select, compile: compile, contains: contains, configure: configure, getAttribute: getAttribute, hasAttribute: hasAttribute, setCache: FN, shortcuts: FN, loadResults: FN, saveResults: FN, emit: emit, Config: Config, Snapshot: Snapshot, Operators: Operators, Selectors: Selectors, Tokens: Tokens, Version: version, registerOperator: function(symbol, resolver) { Operators[symbol] || (Operators[symbol] = resolver); }, registerSelector: function(name, rexp, func) { Selectors[name] || (Selectors[name] = { Expression: rexp, Callback: func }); } }; initialize(doc); return Dom; }); package/src/nwmatcher.js000644 0000171362 13247334221012427 0ustar00000000 000000 /* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * nwmatcher.js - A fast CSS selector engine and matcher * * Author: Diego Perini * Version: 1.4.4 * Created: 20070722 * Release: 20180305 * * License: * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE * Download: * http://javascript.nwbox.com/NWMatcher/nwmatcher.js */ (function(global, factory) { if (typeof module == 'object' && typeof exports == 'object') { module.exports = factory; } else if (typeof define === 'function' && define["amd"]) { define(factory); } else { global.NW || (global.NW = { }); global.NW.Dom = factory(global); } })(this, function(global) { var version = 'nwmatcher-1.4.4', // processing context & root element doc = global.document, root = doc.documentElement, // save utility methods references slice = [ ].slice, // persist previous parsed data isSingleMatch, isSingleSelect, lastSlice, lastContext, lastPosition, lastMatcher, lastSelector, lastPartsMatch, lastPartsSelect, // accepted prefix identifiers // (id, class & pseudo-class) prefixes = '(?:[#.:]|::)?', // accepted attribute operators operators = '([~*^$|!]?={1})', // accepted whitespace characters whitespace = '[\\x20\\t\\n\\r\\f]', // 4 combinators F E, F>E, F+E, F~E combinators = '\\x20|[>+~](?=[^>+~])', // an+b format params for pseudo-classes pseudoparms = '(?:[-+]?\\d*n)?[-+]?\\d*', // skip [ ], ( ), { } brackets groups skip_groups = '\\[.*\\]|\\(.*\\)|\\{.*\\}', // any escaped char any_esc_chr = '\\\\.', // alpha chars & low dash alphalodash = '[_a-zA-Z]', // non-ascii chars (utf-8) non_asc_chr = '[^\\x00-\\x9f]', // escape sequences in strings escaped_chr = '\\\\[^\\n\\r\\f0-9a-fA-F]', // Unicode chars including trailing whitespace unicode_chr = '\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|' + whitespace + ')?', // CSS quoted string values quotedvalue = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' + "|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'", // regular expression used to skip single/nested brackets groups (round, square, curly) // used to split comma groups excluding commas inside quotes '' "" or brackets () [] {} reSplitGroup = /([^,\\()[\]]+|\[[^[\]]*\]|\[.*\]|\([^()]+\)|\(.*\)|\{[^{}]+\}|\{.*\}|\\.)+/g, // regular expression to trim extra leading/trailing whitespace in selector strings // whitespace is any combination of these 5 character [\x20\t\n\r\f] // http://www.w3.org/TR/css3-selectors/#selector-syntax reTrimSpaces = RegExp('[\\n\\r\\f]|^' + whitespace + '+|' + whitespace + '+$', 'g'), // regular expression used in convertEscapes and unescapeIdentifier reEscapedChars = /\\([0-9a-fA-F]{1,6}[\x20\t\n\r\f]?|.)|([\x22\x27])/g, // for in excess whitespace removal reWhiteSpace = /[\x20\t\n\r\f]+/g, standardValidator, extendedValidator, reValidator, attrcheck, attributes, attrmatcher, pseudoclass, reOptimizeSelector, reSimpleNot, reSplitToken, Optimize, reClass, reSimpleSelector, // http://www.w3.org/TR/css3-syntax/#characters // unicode/ISO 10646 characters \xA0 and higher // NOTE: Safari 2.0.x crashes with escaped (\\) // Unicode ranges in regular expressions so we // use a negated character range class instead // now assigned at runtime from config options identifier, // placeholder for extensions extensions = '.+', // precompiled Regular Expressions Patterns = { // structural pseudo-classes and child selectors 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, // uistates + dynamic + negation pseudo-classes dpseudos: /^\:(link|visited|target|active|focus|hover|checked|disabled|enabled|selected|lang\(([-\w]{2,})\)|(?:matches|not)\(\s?(:nth(?:-last)?(?:-child|-of-type)\(\s?(?:even|odd|(?:[-+]{0,1}\d*n\s?)?[-+]{0,1}\s?\d*)\s?\)|[^()]*)\s?\))?(.*)/i, // pseudo-elements selectors epseudos: /^((?:[:]{1,2}(?:after|before|first-letter|first-line))|(?:[:]{2,2}(?:selection|backdrop|placeholder)))?(.*)/i, // E > F children: RegExp('^' + whitespace + '?\\>' + whitespace + '?(.*)'), // E + F adjacent: RegExp('^' + whitespace + '?\\+' + whitespace + '?(.*)'), // E ~ F relative: RegExp('^' + whitespace + '?\\~' + whitespace + '?(.*)'), // E F ancestor: RegExp('^' + whitespace + '+(.*)'), // all universal: RegExp('^\\*(.*)') }, Tokens = { prefixes: prefixes, identifier: identifier, attributes: attributes }, /*----------------------------- FEATURE TESTING ----------------------------*/ // detect native methods isNative = (function() { var re = / \w+\(/, isnative = String(({ }).toString).replace(re, ' ('); return function(method) { return method && typeof method != 'string' && isnative == String(method).replace(re, ' ('); }; })(), // NATIVE_XXXXX true if method exist and is callable // detect if DOM methods are native in browsers NATIVE_FOCUS = isNative(doc.hasFocus), NATIVE_QSAPI = isNative(doc.querySelector), NATIVE_GEBID = isNative(doc.getElementById), NATIVE_GEBTN = isNative(root.getElementsByTagName), NATIVE_GEBCN = isNative(root.getElementsByClassName), // detect native getAttribute/hasAttribute methods, // frameworks extend these to elements, but it seems // this does not work for XML namespaced attributes, // used to check both getAttribute/hasAttribute in IE NATIVE_GET_ATTRIBUTE = isNative(root.getAttribute), NATIVE_HAS_ATTRIBUTE = isNative(root.hasAttribute), // check if slice() can convert nodelist to array // see http://yura.thinkweb2.com/cft/ NATIVE_SLICE_PROTO = (function() { var isBuggy = false; try { isBuggy = !!slice.call(doc.childNodes, 0)[0]; } catch(e) { } return isBuggy; })(), // supports the new traversal API NATIVE_TRAVERSAL_API = 'nextElementSibling' in root && 'previousElementSibling' in root, // BUGGY_XXXXX true if method is feature tested and has known bugs // detect buggy gEBID BUGGY_GEBID = NATIVE_GEBID ? (function() { var isBuggy = true, x = 'x' + String(+new Date), a = doc.createElementNS ? 'a' : ''; (a = doc.createElement(a)).name = x; root.insertBefore(a, root.firstChild); isBuggy = !!doc.getElementById(x); root.removeChild(a); return isBuggy; })() : true, // detect IE gEBTN comment nodes bug BUGGY_GEBTN = NATIVE_GEBTN ? (function() { var div = doc.createElement('div'); div.appendChild(doc.createComment('')); return !!div.getElementsByTagName('*')[0]; })() : true, // detect Opera gEBCN second class and/or UTF8 bugs as well as Safari 3.2 // caching class name results and not detecting when changed, // tests are based on the jQuery selector test suite BUGGY_GEBCN = NATIVE_GEBCN ? (function() { var isBuggy, div = doc.createElement('div'), test = '\u53f0\u5317'; // Opera tests div.appendChild(doc.createElement('span')). setAttribute('class', test + 'abc ' + test); div.appendChild(doc.createElement('span')). setAttribute('class', 'x'); isBuggy = !div.getElementsByClassName(test)[0]; // Safari test div.lastChild.className = test; return isBuggy || div.getElementsByClassName(test).length != 2; })() : true, // detect IE bug with dynamic attributes BUGGY_GET_ATTRIBUTE = NATIVE_GET_ATTRIBUTE ? (function() { var input = doc.createElement('input'); input.setAttribute('value', 5); return input.defaultValue != 5; })() : true, // detect IE bug with non-standard boolean attributes BUGGY_HAS_ATTRIBUTE = NATIVE_HAS_ATTRIBUTE ? (function() { var option = doc.createElement('option'); option.setAttribute('selected', 'selected'); return !option.hasAttribute('selected'); })() : true, // detect Safari bug with selected option elements BUGGY_SELECTED = (function() { var select = doc.createElement('select'); select.appendChild(doc.createElement('option')); return !select.firstChild.selected; })(), // initialized with the loading context // and reset for each different context BUGGY_QUIRKS_GEBCN, BUGGY_QUIRKS_QSAPI, QUIRKS_MODE, XML_DOCUMENT, // detect Opera browser OPERA = typeof global.opera != 'undefined' && (/opera/i).test(({ }).toString.call(global.opera)), // skip simple selector optimizations for Opera >= 11 OPERA_QSAPI = OPERA && parseFloat(global.opera.version()) >= 11, // check Selector API implementations RE_BUGGY_QSAPI = NATIVE_QSAPI ? (function() { var pattern = [ ], context, element, expect = function(selector, element, n) { var result = false; context.appendChild(element); try { result = context.querySelectorAll(selector).length == n; } catch(e) { } while (context.firstChild) { context.removeChild(context.firstChild); } return result; }; // certain bugs can only be detected in standard documents // to avoid writing a live loading document create a fake one if (doc.implementation && doc.implementation.createDocument) { // use a shadow document body as context context = doc.implementation.createDocument('', '', null). appendChild(doc.createElement('html')). appendChild(doc.createElement('head')).parentNode. appendChild(doc.createElement('body')); } else { // use an unattached div node as context context = doc.createElement('div'); } // fix for Safari 8.x and other engines that // fail querying filtered sibling combinators element = doc.createElement('div'); element.innerHTML = '


'; expect('p#a+*', element, 0) && pattern.push('\\w+#\\w+.*[+~]'); // ^= $= *= operators bugs with empty values (Opera 10 / IE8) element = doc.createElement('p'); element.setAttribute('class', ''); expect('[class^=""]', element, 1) && pattern.push('[*^$]=[\\x20\\t\\n\\r\\f]*(?:""|' + "'')"); // :checked bug with option elements (Firefox 3.6.x) // it wrongly includes 'selected' options elements // HTML5 rules says selected options also match element = doc.createElement('option'); element.setAttribute('selected', 'selected'); expect(':checked', element, 0) && pattern.push(':checked'); // :enabled :disabled bugs with hidden fields (Firefox 3.5) // http://www.w3.org/TR/html5/links.html#selector-enabled // http://www.w3.org/TR/css3-selectors/#enableddisabled // not supported by IE8 Query Selector element = doc.createElement('input'); element.setAttribute('type', 'hidden'); expect(':enabled', element, 0) && pattern.push(':enabled', ':disabled'); // :link bugs with hyperlinks matching (Firefox/Safari) element = doc.createElement('link'); element.setAttribute('href', 'x'); expect(':link', element, 1) || pattern.push(':link'); // avoid attribute selectors for IE QSA if (BUGGY_HAS_ATTRIBUTE) { // IE fails in reading: // - original values for input/textarea // - original boolean values for controls pattern.push('\\[[\\x20\\t\\n\\r\\f]*(?:checked|disabled|ismap|multiple|readonly|selected|value)'); } return pattern.length ? RegExp(pattern.join('|')) : { 'test': function() { return false; } }; })() : true, /*----------------------------- LOOKUP OBJECTS -----------------------------*/ IE_LT_9 = typeof doc.addEventListener != 'function', LINK_NODES = { 'a': 1, 'A': 1, 'area': 1, 'AREA': 1, 'link': 1, 'LINK': 1 }, // boolean attributes should return attribute name instead of true/false ATTR_BOOLEAN = { 'checked': 1, 'disabled': 1, 'ismap': 1, 'multiple': 1, 'readonly': 1, 'selected': 1 }, // dynamic attributes that needs to be checked against original HTML value ATTR_DEFAULT = { 'value': 'defaultValue', 'checked': 'defaultChecked', 'selected': 'defaultSelected' }, // attributes referencing URI data values need special treatment in IE ATTR_URIDATA = { 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 }, // HTML 5 draft specifications // http://www.whatwg.org/specs/web-apps/current-work/#selectors HTML_TABLE = { // NOTE: class name attribute selectors must always be treated using a // case-sensitive match, this has changed from previous specifications 'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1, 'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1, 'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1, 'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1, 'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1 }, /*-------------------------- REGULAR EXPRESSIONS ---------------------------*/ // placeholder to add functionalities Selectors = { // as a simple example this will check // for chars not in standard ascii table // // 'mySpecialSelector': { // 'Expression': /\u0080-\uffff/, // 'Callback': mySelectorCallback // } // // 'mySelectorCallback' will be invoked // only after passing all other standard // checks and only if none of them worked }, // attribute operators Operators = { '=': "n=='%m'", '^=': "n.indexOf('%m')==0", '*=': "n.indexOf('%m')>-1", '|=': "(n+'-').indexOf('%m-')==0", '~=': "(' '+n+' ').indexOf(' %m ')>-1", '$=': "n.substr(n.length-'%m'.length)=='%m'" }, /*------------------------------ UTIL METHODS ------------------------------*/ // concat elements to data concatList = function(data, elements) { var i = -1, element; if (!data.length && Array.slice) return Array.slice(elements); while ((element = elements[++i])) data[data.length] = element; return data; }, // concat elements to data and callback concatCall = function(data, elements, callback) { var i = -1, element; while ((element = elements[++i])) { if (false === callback(data[data.length] = element)) { break; } } return data; }, // change context specific variables switchContext = function(from, force) { var div, oldDoc = doc; // save passed context lastContext = from; // set new context document doc = from.ownerDocument || from; if (force || oldDoc !== doc) { // set document root root = doc.documentElement; // set host environment flags XML_DOCUMENT = doc.createElement('DiV').nodeName == 'DiV'; // In quirks mode css class names are case insensitive. // In standards mode they are case sensitive. See docs: // https://developer.mozilla.org/en/Mozilla_Quirks_Mode_Behavior // http://www.whatwg.org/specs/web-apps/current-work/#selectors QUIRKS_MODE = !XML_DOCUMENT && typeof doc.compatMode == 'string' ? doc.compatMode.indexOf('CSS') < 0 : (function() { var style = doc.createElement('div').style; return style && (style.width = 1) && style.width == '1px'; })(); div = doc.createElement('div'); div.appendChild(doc.createElement('p')).setAttribute('class', 'xXx'); div.appendChild(doc.createElement('p')).setAttribute('class', 'xxx'); // GEBCN buggy in quirks mode, match count is: // Firefox 3.0+ [xxx = 1, xXx = 1] // Opera 10.63+ [xxx = 0, xXx = 2] BUGGY_QUIRKS_GEBCN = !XML_DOCUMENT && NATIVE_GEBCN && QUIRKS_MODE && (div.getElementsByClassName('xxx').length != 2 || div.getElementsByClassName('xXx').length != 2); // QSAPI buggy in quirks mode, match count is: // At least Chrome 4+, Firefox 3.5+, Opera 10.x+, Safari 4+ [xxx = 1, xXx = 2] // Safari 3.2 QSA doesn't work with mixedcase in quirksmode [xxx = 1, xXx = 0] // https://bugs.webkit.org/show_bug.cgi?id=19047 // must test the attribute selector '[class~=xxx]' // before '.xXx' or the bug may not present itself BUGGY_QUIRKS_QSAPI = !XML_DOCUMENT && NATIVE_QSAPI && QUIRKS_MODE && (div.querySelectorAll('[class~=xxx]').length != 2 || div.querySelectorAll('.xXx').length != 2); Config.CACHING && Dom.setCache(true, doc); } }, // convert single codepoint to UTF-16 encoding codePointToUTF16 = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\\ufffd'; } // javascript strings are UTF-16 encoded if (codePoint < 0x10000) { var lowHex = '000' + codePoint.toString(16); return '\\u' + lowHex.substr(lowHex.length - 4); } // supplementary high + low surrogates return '\\u' + (((codePoint - 0x10000) >> 0x0a) + 0xd800).toString(16) + '\\u' + (((codePoint - 0x10000) % 0x400) + 0xdc00).toString(16); }, // convert single codepoint to string stringFromCodePoint = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\ufffd'; } if (codePoint < 0x10000) { return String.fromCharCode(codePoint); } return String.fromCodePoint ? String.fromCodePoint(codePoint) : String.fromCharCode( ((codePoint - 0x10000) >> 0x0a) + 0xd800, ((codePoint - 0x10000) % 0x400) + 0xdc00); }, // convert escape sequence in a CSS string or identifier // to javascript string with javascript escape sequences convertEscapes = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { // unescaped " or ' return p2 ? '\\' + p2 : // javascript strings are UTF-16 encoded (/^[0-9a-fA-F]/).test(p1) ? codePointToUTF16(parseInt(p1, 16)) : // \' \" (/^[\\\x22\x27]/).test(p1) ? substring : // \g \h \. \# etc p1; } ); }, // convert escape sequence in a CSS string or identifier // to javascript string with characters representations unescapeIdentifier = function(str) { return str.replace(reEscapedChars, function(substring, p1, p2) { // unescaped " or ' return p2 ? p2 : // javascript strings are UTF-16 encoded (/^[0-9a-fA-F]/).test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : // \' \" (/^[\\\x22\x27]/).test(p1) ? substring : // \g \h \. \# etc p1; } ); }, /*------------------------------ DOM METHODS -------------------------------*/ // element by id (raw) // @return reference or null byIdRaw = function(id, elements) { var i = -1, element; while ((element = elements[++i])) { if (element.getAttribute('id') == id) { break; } } return element || null; }, // element by id // @return reference or null _byId = !BUGGY_GEBID ? function(id, from) { id = (/\\/).test(id) ? unescapeIdentifier(id) : id; return from.getElementById && from.getElementById(id) || byIdRaw(id, from.getElementsByTagName('*')); } : function(id, from) { var element = null; id = (/\\/).test(id) ? unescapeIdentifier(id) : id; if (XML_DOCUMENT || from.nodeType != 9) { return byIdRaw(id, from.getElementsByTagName('*')); } if ((element = from.getElementById(id)) && element.name == id && from.getElementsByName) { return byIdRaw(id, from.getElementsByName(id)); } return element; }, // publicly exposed byId // @return reference or null byId = function(id, from) { from || (from = doc); if (lastContext !== from) { switchContext(from); } return _byId(id, from); }, // elements by tag (raw) // @return array byTagRaw = function(tag, from) { var any = tag == '*', element = from, elements = [ ], next = element.firstChild; any || (tag = tag.toUpperCase()); while ((element = next)) { if (element.tagName > '@' && (any || element.tagName.toUpperCase() == tag)) { elements[elements.length] = element; } if ((next = element.firstChild || element.nextSibling)) continue; while (!next && (element = element.parentNode) && element !== from) { next = element.nextSibling; } } return elements; }, // elements by tag // @return array _byTag = !BUGGY_GEBTN && NATIVE_SLICE_PROTO ? function(tag, from) { return XML_DOCUMENT || from.nodeType == 11 ? byTagRaw(tag, from) : slice.call(from.getElementsByTagName(tag), 0); } : function(tag, from) { var i = -1, j = i, data = [ ], element, elements = XML_DOCUMENT || from.nodeType == 11 ? byTagRaw(tag, from) : from.getElementsByTagName(tag); if (tag == '*') { while ((element = elements[++i])) { if (element.nodeName > '@') { data[++j] = element; } } } else { while ((element = elements[++i])) { data[i] = element; } } return data; }, // publicly exposed byTag // @return array byTag = function(tag, from) { from || (from = doc); if (lastContext !== from) { switchContext(from); } return _byTag(tag, from); }, // publicly exposed byName // @return array byName = function(name, from) { return select('[name="' + name.replace(/\\([^\\]{1})/g, '$1') + '"]', from); }, // elements by class (raw) // @return array byClassRaw = function(name, from) { var i = -1, j = i, data = [ ], element, elements = _byTag('*', from), n; name = ' ' + (QUIRKS_MODE ? name.toLowerCase() : name) + ' '; while ((element = elements[++i])) { n = XML_DOCUMENT ? element.getAttribute('class') : element.className; if (n && n.length && (' ' + (QUIRKS_MODE ? n.toLowerCase() : n). replace(reWhiteSpace, ' ') + ' ').indexOf(name) > -1) { data[++j] = element; } } return data; }, // elements by class // @return array _byClass = function(name, from) { name = QUIRKS_MODE ? name.toLowerCase() : name; name = (/\\/).test(name) ? unescapeIdentifier(name) : name; return (BUGGY_GEBCN || BUGGY_QUIRKS_GEBCN || XML_DOCUMENT || !from.getElementsByClassName) ? byClassRaw(name, from) : slice.call(from.getElementsByClassName(name)); }, // publicly exposed byClass // @return array byClass = function(name, from) { from || (from = doc); if (lastContext !== from) { switchContext(from); } return _byClass(name, from); }, // check element is descendant of container // @return boolean contains = 'compareDocumentPosition' in root ? function(container, element) { return (container.compareDocumentPosition(element) & 16) == 16; } : 'contains' in root ? function(container, element) { return container !== element && container.contains(element); } : function(container, element) { while ((element = element.parentNode)) { if (element === container) return true; } return false; }, // attribute value // @return string getAttribute = !BUGGY_GET_ATTRIBUTE && !IE_LT_9 ? function(node, attribute) { return node.getAttribute(attribute); } : function(node, attribute) { attribute = attribute.toLowerCase(); if (typeof node[attribute] == 'object') { return node.attributes[attribute] && node.attributes[attribute].value; } return ( // 'type' can only be read by using native getAttribute attribute == 'type' ? node.getAttribute(attribute) : // specific URI data attributes (parameter 2 to fix IE bug) ATTR_URIDATA[attribute] ? node.getAttribute(attribute, 2) : // boolean attributes should return name instead of true/false ATTR_BOOLEAN[attribute] ? node.getAttribute(attribute) ? attribute : 'false' : (node = node.getAttributeNode(attribute)) && node.value); }, // attribute presence // @return boolean hasAttribute = !BUGGY_HAS_ATTRIBUTE && !IE_LT_9 ? function(node, attribute) { return XML_DOCUMENT ? !!node.getAttribute(attribute) : node.hasAttribute(attribute); } : function(node, attribute) { // read the node attribute object var obj = node.getAttributeNode(attribute = attribute.toLowerCase()); return ATTR_DEFAULT[attribute] && attribute != 'value' ? node[ATTR_DEFAULT[attribute]] : obj && obj.specified; }, // check node emptyness // @return boolean isEmpty = function(node) { node = node.firstChild; while (node) { if (node.nodeType == 3 || node.nodeName > '@') return false; node = node.nextSibling; } return true; }, // check if element matches the :link pseudo // @return boolean isLink = function(element) { return hasAttribute(element,'href') && LINK_NODES[element.nodeName]; }, // child position by nodeType // @return number nthElement = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling'; while ((element = element[succ])) { if (element.nodeName > '@') ++count; } return count; }, // child position by nodeName // @return number nthOfType = function(element, last) { var count = 1, succ = last ? 'nextSibling' : 'previousSibling', type = element.nodeName; while ((element = element[succ])) { if (element.nodeName == type) ++count; } return count; }, /*------------------------------- DEBUGGING --------------------------------*/ // get/set (string/object) working modes configure = function(option) { if (typeof option == 'string') { return !!Config[option]; } if (typeof option != 'object') { return Config; } for (var i in option) { Config[i] = !!option[i]; if (i == 'SIMPLENOT') { matchContexts = { }; matchResolvers = { }; selectContexts = { }; selectResolvers = { }; if (!Config[i]) { Config['USE_QSAPI'] = false; } } else if (i == 'USE_QSAPI') { Config[i] = !!option[i] && NATIVE_QSAPI; } } setIdentifierSyntax(); reValidator = RegExp(Config.SIMPLENOT ? standardValidator : extendedValidator); return true; }, // control user notifications emit = function(message) { if (Config.VERBOSITY) { throw Error(message); } if (Config.LOGERRORS && console && console.log) { console.log(message); } }, Config = { // true to enable caching of result sets, false to disable CACHING: false, // true to allow CSS escaped identifiers, false to disallow ESCAPECHR: true, // true to allow identifiers containing non-ASCII (utf-8) chars NON_ASCII: true, // switch syntax RE, true to use Level 3, false to use Level 2 SELECTOR3: true, // true to allow identifiers containing Unicode (utf-16) chars UNICODE16: true, // by default do not add missing left/right context // to mangled selector strings like "+div" or "ul>" // callable Dom.shortcuts method has to be available SHORTCUTS: false, // true to disable complex selectors nested in // ':not()' pseudo-classes as for specifications SIMPLENOT: true, // true to match lowercase tag names of SVG elements in HTML SVG_LCASE: false, // strict QSA match all non-unique IDs (false) // speed & libs compat match unique ID (true) UNIQUE_ID: true, // true to follow HTML5 specs handling of ":checked" // pseudo-class and similar UI states (indeterminate) USE_HTML5: true, // true to use browsers native Query Selector API if available USE_QSAPI: NATIVE_QSAPI, // true to throw exceptions, false to skip throwing exceptions VERBOSITY: true, // true to print console errors or warnings, false to mute them LOGERRORS: true }, /*---------------------------- COMPILER METHODS ----------------------------*/ // init REs and context initialize = function(doc) { setIdentifierSyntax(); switchContext(doc, true); }, // set/reset default identifier syntax // based on user configuration options // rebuild the validator and other REs setIdentifierSyntax = function() { var syntax = '', start = Config['SELECTOR3'] ? '-{2}|' : ''; Config['NON_ASCII'] && (syntax += '|' + non_asc_chr); Config['UNICODE16'] && (syntax += '|' + unicode_chr); Config['ESCAPECHR'] && (syntax += '|' + escaped_chr); syntax += (Config['UNICODE16'] || Config['ESCAPECHR']) ? '' : '|' + any_esc_chr; identifier = '-?(?:' + start + alphalodash + syntax + ')(?:-|[0-9]|' + alphalodash + syntax + ')*'; // build attribute string attrcheck = '(' + quotedvalue + '|' + identifier + ')'; attributes = whitespace + '*(' + identifier + '(?::' + identifier + ')?)' + whitespace + '*(?:' + operators + whitespace + '*' + attrcheck + ')?' + whitespace + '*' + '(i)?' + whitespace + '*'; attrmatcher = attributes.replace(attrcheck, '([\\x22\\x27]*)((?:\\\\?.)*?)\\3'); // build pseudoclass string pseudoclass = '((?:' + // an+b parameters or quoted string pseudoparms + '|' + quotedvalue + '|' + // id, class, pseudo-class selector prefixes + identifier + '|' + // nested HTML attribute selector '\\[' + attributes + '\\]|' + // nested pseudo-class selector '\\(.+\\)|' + whitespace + '*|' + // nested pseudos/separators ',)+)'; // CSS3: syntax scanner and // one pass validation only // using regular expression standardValidator = // discard start '(?=[\\x20\\t\\n\\r\\f]*[^>+~(){}<>])' + // open match group '(' + //universal selector '\\*' + // id/class/tag/pseudo-class identifier '|(?:' + prefixes + identifier + ')' + // combinator selector '|' + combinators + // HTML attribute selector '|\\[' + attributes + '\\]' + // pseudo-classes parameters '|\\(' + pseudoclass + '\\)' + // dom properties selector (extension) '|\\{' + extensions + '\\}' + // selector group separator (comma) '|(?:,|' + whitespace + '*)' + // close match group ')+'; // only allow simple selectors nested in ':not()' pseudo-classes reSimpleNot = RegExp('^(' + '(?!:not)' + '(' + prefixes + identifier + '|\\([^()]*\\))+' + '|\\[' + attributes + '\\]' + ')$'); // split last, right most, selector group token reSplitToken = RegExp('(' + prefixes + identifier + '|' + '\\[' + attributes + '\\]|' + '\\(' + pseudoclass + '\\)|' + '\\\\.|[^\\x20\\t\\n\\r\\f>+~])+', 'g'); reOptimizeSelector = RegExp(identifier + '|^$'); reSimpleSelector = RegExp( BUGGY_GEBTN && BUGGY_GEBCN || OPERA ? '^#?' + identifier + '$' : BUGGY_GEBTN ? '^[.#]?' + identifier + '$' : BUGGY_GEBCN ? '^(?:\\*|#' + identifier + ')$' : '^(?:\\*|[.#]?' + identifier + ')$'); // matches class selectors reClass = RegExp('(?:\\[[\\x20\\t\\n\\r\\f]*class\\b|\\.' + identifier + ')'); Optimize = { ID: RegExp('^\\*?#(' + identifier + ')|' + skip_groups), TAG: RegExp('^(' + identifier + ')|' + skip_groups), CLASS: RegExp('^\\.(' + identifier + '$)|' + skip_groups) }; Patterns.id = RegExp('^#(' + identifier + ')(.*)'); Patterns.tagName = RegExp('^(' + identifier + ')(.*)'); Patterns.className = RegExp('^\\.(' + identifier + ')(.*)'); Patterns.attribute = RegExp('^\\[' + attrmatcher + '\\](.*)'); Tokens.identifier = identifier; Tokens.attributes = attributes; // validator for complex selectors in ':not()' pseudo-classes extendedValidator = standardValidator.replace(pseudoclass, '.*'); // validator for standard selectors as default reValidator = RegExp(standardValidator); }, // code string reused to build compiled functions ACCEPT_NODE = 'r[r.length]=c[k];if(f&&false===f(c[k]))break main;else continue main;', // compile a comma separated group of selector // @mode boolean true for select, false for match // return a compiled function compile = function(selector, source, mode) { var parts = typeof selector == 'string' ? selector.match(reSplitGroup) : selector; // ensures that source is a string typeof source == 'string' || (source = ''); if (parts.length == 1) { source += compileSelector(parts[0], mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } else { // for each selector in the group var i = -1, seen = { }, token; while ((token = parts[++i])) { token = token.replace(reTrimSpaces, ''); // avoid repeating the same token // in comma separated group (p, p) if (!seen[token] && (seen[token] = true)) { source += compileSelector(token, mode ? ACCEPT_NODE : 'f&&f(k);return true;', mode); } } } if (mode) { // for select method return Function('c,s,d,h,g,f', 'var N,n,x=0,k=-1,e,r=[];main:while((e=c[++k])){' + source + '}return r;'); } else { // for match method return Function('e,s,d,h,g,f', 'var N,n,x=0,k=e;' + source + 'return false;'); } }, // compile a CSS3 string selector into ad-hoc javascript matching function // @return string (to be compiled) compileSelector = function(selector, source, mode) { var a, b, n, k = 0, expr, match, result, status, test, type; while (selector) { k++; // *** Universal selector // * match all (empty block, do not remove) if ((match = selector.match(Patterns.universal))) { // do nothing, handled in the compiler where // BUGGY_GEBTN return comment nodes (ex: IE) expr = ''; } // *** ID selector // #Foo Id case sensitive else if ((match = selector.match(Patterns.id))) { // document can contain conflicting elements (id/name) // prototype selector unit need this method to recover bad HTML forms match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; source = 'if(' + (XML_DOCUMENT ? 's.getAttribute(e,"id")' : '(e.submit?s.getAttribute(e,"id"):e.id)') + '=="' + match[1] + '"' + '){' + source + '}'; } // *** Type selector // Foo Tag (case insensitive) else if ((match = selector.match(Patterns.tagName))) { // both tagName and nodeName properties may be upper/lower case // depending on their creation NAMESPACE in createElementNS() test = Config.SVG_LCASE ? '||e.nodeName=="' + match[1].toLowerCase() + '"' : ''; source = 'if(e.nodeName' + (XML_DOCUMENT ? '=="' + match[1] + '"' : '.toUpperCase()' + '=="' + match[1].toUpperCase() + '"' + test) + '){' + source + '}'; } // *** Class selector // .Foo Class (case sensitive) else if ((match = selector.match(Patterns.className))) { // W3C CSS3 specs: element whose "class" attribute has been assigned a // list of whitespace-separated values, see section 6.4 Class selectors // and notes at the bottom; explicitly non-normative in this specification. match[1] = (/\\/).test(match[1]) ? convertEscapes(match[1]) : match[1]; match[1] = QUIRKS_MODE ? match[1].toLowerCase() : match[1]; source = 'if((n=' + (XML_DOCUMENT ? 's.getAttribute(e,"class")' : 'e.className') + ')&&n.length&&(" "+' + (QUIRKS_MODE ? 'n.toLowerCase()' : 'n') + '.replace(/' + whitespace + '+/g," ")+" ").indexOf(" ' + match[1] + ' ")>-1' + '){' + source + '}'; } // *** Attribute selector // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= // case sensitivity is treated differently depending on the document type (see map) else if ((match = selector.match(Patterns.attribute))) { // xml namespaced attribute ? expr = match[1].split(':'); expr = expr.length == 2 ? expr[1] : expr[0] + ''; if (match[2] && !Operators[match[2]]) { emit('Unsupported operator in attribute selectors "' + selector + '"'); return ''; } test = 'false'; // replace Operators parameter if needed if (match[2] && match[4] && (test = Operators[match[2]])) { match[4] = (/\\/).test(match[4]) ? convertEscapes(match[4]) : match[4]; // case treatment depends on document type type = match[5] == 'i' || HTML_TABLE[expr.toLowerCase()]; test = test.replace(/\%m/g, type ? match[4].toLowerCase() : match[4]); } else if (match[2] == '!=' || match[2] == '=') { test = 'n' + match[2] + '=""'; } source = 'if(n=s.hasAttribute(e,"' + match[1] + '")){' + (match[2] ? 'n=s.getAttribute(e,"' + match[1] + '")' : '') + (type && match[2] ? '.toLowerCase();' : ';') + 'if(' + (match[2] ? test : 'n') + '){' + source + '}}'; } // *** Adjacent sibling combinator // E + F (F adiacent sibling of E) else if ((match = selector.match(Patterns.adjacent))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;if((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}e=N' + k + ';'; } // *** General sibling combinator // E ~ F (F relative sibling of E) else if ((match = selector.match(Patterns.relative))) { source = NATIVE_TRAVERSAL_API ? 'var N' + k + '=e;while((e=e.previousElementSibling)){' + source + '}e=N' + k + ';' : 'var N' + k + '=e;while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}e=N' + k + ';'; } // *** Child combinator // E > F (F children of E) else if ((match = selector.match(Patterns.children))) { source = 'var N' + k + '=e;if((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } // *** Descendant combinator // E F (E ancestor of F) else if ((match = selector.match(Patterns.ancestor))) { source = 'var N' + k + '=e;while((e=e.parentNode)&&e.nodeType==1){' + source + '}e=N' + k + ';'; } // *** Structural pseudo-classes // :root, :empty, // :first-child, :last-child, :only-child, // :first-of-type, :last-of-type, :only-of-type, // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() else if ((match = selector.match(Patterns.spseudos)) && match[1]) { switch (match[1]) { case 'root': // element root of the document if (match[3]) { source = 'if(e===h||s.contains(h,e)){' + source + '}'; } else { source = 'if(e===h){' + source + '}'; } break; case 'empty': // element that has no children source = 'if(s.isEmpty(e)){' + source + '}'; break; default: if (match[1] && match[2]) { if (match[2] == 'n') { source = 'if(e!==h){' + source + '}'; break; } else if (match[2] == 'even') { a = 2; b = 0; } else if (match[2] == 'odd') { a = 2; b = 1; } else { // assumes correct "an+b" format, "b" before "a" to keep "n" values b = ((n = match[2].match(/(-?\d+)$/)) ? parseInt(n[1], 10) : 0); a = ((n = match[2].match(/(-?\d*)n/i)) ? parseInt(n[1], 10) : 0); if (n && n[1] == '-') a = -1; } // build test expression out of structural pseudo (an+b) parameters // see here: http://www.w3.org/TR/css3-selectors/#nth-child-pseudo test = a > 1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n>=' + b + '&&(n-(' + b + '))%' + a + '==0' : a < -1 ? (/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' : 'n<=' + b + '&&(n-(' + b + '))%' + a + '==0' : a === 0 ? 'n==' + b : a == -1 ? 'n<=' + b : 'n>=' + b; // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) source = 'if(e!==h){' + 'n=s[' + (/-of-type/i.test(match[1]) ? '"nthOfType"' : '"nthElement"') + ']' + '(e,' + (/last/i.test(match[1]) ? 'true' : 'false') + ');' + 'if(' + test + '){' + source + '}' + '}'; } else { // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) a = /first/i.test(match[1]) ? 'previous' : 'next'; n = /only/i.test(match[1]) ? 'previous' : 'next'; b = /first|last/i.test(match[1]); type = /-of-type/i.test(match[1]) ? '&&n.nodeName!=e.nodeName' : '&&n.nodeName<"@"'; source = 'if(e!==h){' + ( 'n=e;while((n=n.' + a + 'Sibling)' + type + ');if(!n){' + (b ? source : 'n=e;while((n=n.' + n + 'Sibling)' + type + ');if(!n){' + source + '}') + '}' ) + '}'; } break; } } // *** negation, user action and target pseudo-classes // *** UI element states and dynamic pseudo-classes // CSS4 :matches // CSS3 :not, :checked, :enabled, :disabled, :target // CSS3 :active, :hover, :focus // CSS3 :link, :visited else if ((match = selector.match(Patterns.dpseudos)) && match[1]) { switch (match[1].match(/^\w+/)[0]) { // CSS4 matches pseudo-class case 'matches': expr = match[3].replace(reTrimSpaces, ''); source = 'if(s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}'; break; // CSS3 negation pseudo-class case 'not': // compile nested selectors, DO NOT pass the callback parameter // SIMPLENOT allow disabling complex selectors nested // in ':not()' pseudo-classes, breaks some test units expr = match[3].replace(reTrimSpaces, ''); if (Config.SIMPLENOT && !reSimpleNot.test(expr)) { // see above, log error but continue execution emit('Negation pseudo-class only accepts simple selectors "' + selector + '"'); return ''; } else { if ('compatMode' in doc) { source = 'if(!' + compile(expr, '', false) + '(e,s,d,h,g)){' + source + '}'; } else { source = 'if(!s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}'; } } break; // CSS3 UI element states case 'checked': // for radio buttons checkboxes (HTML4) and options (HTML5) source = 'if((typeof e.form!=="undefined"&&(/^(?:radio|checkbox)$/i).test(e.type)&&e.checked)' + (Config.USE_HTML5 ? '||(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' : '') + '){' + source + '}'; break; case 'disabled': // does not consider hidden input fields source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===true){' + source + '}'; break; case 'enabled': // does not consider hidden input fields source = 'if(((typeof e.form!=="undefined"' + (Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') + ')||s.isLink(e))&&e.disabled===false){' + source + '}'; break; // CSS3 lang pseudo-class case 'lang': test = ''; if (match[2]) test = match[2].substr(0, 2) + '-'; source = 'do{(n=e.lang||"").toLowerCase();' + 'if((n==""&&h.lang=="' + match[2].toLowerCase() + '")||' + '(n&&(n=="' + match[2].toLowerCase() + '"||n.substr(0,3)=="' + test.toLowerCase() + '")))' + '{' + source + 'break;}}while((e=e.parentNode)&&e!==g);'; break; // CSS3 target pseudo-class case 'target': source = 'if(e.id==d.location.hash.slice(1)){' + source + '}'; break; // CSS3 dynamic pseudo-classes case 'link': source = 'if(s.isLink(e)&&!e.visited){' + source + '}'; break; case 'visited': source = 'if(s.isLink(e)&&e.visited){' + source + '}'; break; // CSS3 user action pseudo-classes IE & FF3 have native support // these capabilities may be emulated by some event managers case 'active': if (XML_DOCUMENT) break; source = 'if(e===d.activeElement){' + source + '}'; break; case 'hover': if (XML_DOCUMENT) break; source = 'if(e===d.hoverElement){' + source + '}'; break; case 'focus': if (XML_DOCUMENT) break; source = NATIVE_FOCUS ? 'if(e===d.activeElement&&d.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number")){' + source + '}' : 'if(e===d.activeElement&&(e.type||e.href)){' + source + '}'; break; // CSS2 selected pseudo-classes, not part of current CSS3 drafts // the 'selected' property is only available for option elements case 'selected': // fix Safari selectedIndex property bug expr = BUGGY_SELECTED ? '||(n=e.parentNode)&&n.options[n.selectedIndex]===e' : ''; source = 'if(/^option$/i.test(e.nodeName)&&(e.selected||e.checked' + expr + ')){' + source + '}'; break; default: break; } } else if ((match = selector.match(Patterns.epseudos)) && match[1]) { source = 'if(!(/1|11/).test(e.nodeType)){' + source + '}'; } else { // this is where external extensions are // invoked if expressions match selectors expr = false; status = false; for (expr in Selectors) { if ((match = selector.match(Selectors[expr].Expression)) && match[1]) { result = Selectors[expr].Callback(match, source); if ('match' in result) { match = result.match; } source = result.source; status = result.status; if (status) { break; } } } // if an extension fails to parse the selector // it must return a false boolean in "status" if (!status) { // log error but continue execution, don't throw real exceptions // because blocking following processes maybe is not a good idea emit('Unknown pseudo-class selector "' + selector + '"'); return ''; } if (!expr) { // see above, log error but continue execution emit('Unknown token in selector "' + selector + '"'); return ''; } } // error if no matches found by the pattern scan if (!match) { emit('Invalid syntax in selector "' + selector + '"'); return ''; } // ensure "match" is not null or empty since // we do not throw real DOMExceptions above selector = match && match[match.length - 1]; } return source; }, /*----------------------------- QUERY METHODS ------------------------------*/ // match element with selector // @return boolean match = function(element, selector, from, callback) { var parts; if (!(element && element.nodeType == 1)) { emit('Invalid element argument'); return false; } else if (typeof selector != 'string') { emit('Invalid selector argument'); return false; } else if (from && from.nodeType == 1 && !contains(from, element)) { return false; } else if (lastContext !== from) { // reset context data when it changes // and ensure context is set to a default switchContext(from || (from = element.ownerDocument)); } // normalize the selector string, remove [\n\r\f] // whitespace, replace codepoints 0 with '\ufffd' // trim non-relevant leading/trailing whitespaces selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); Config.SHORTCUTS && (selector = Dom.shortcuts(selector, element, from)); if (lastMatcher != selector) { // process valid selector strings if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleMatch = (parts = selector.match(reSplitGroup)).length < 2; // save passed selector lastMatcher = selector; lastPartsMatch = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return false; } } else parts = lastPartsMatch; // compile matcher resolvers if necessary if (!matchResolvers[selector] || matchContexts[selector] !== from) { matchResolvers[selector] = compile(isSingleMatch ? [selector] : parts, '', false); matchContexts[selector] = from; } return matchResolvers[selector](element, Snapshot, doc, root, from, callback); }, // select only the first element // matching selector (document ordered) first = function(selector, from) { return select(selector, from, function() { return false; })[0] || null; }, // select elements matching selector // using new Query Selector API // or cross-browser client API // @return array select = function(selector, from, callback) { var i, changed, element, elements, parts, token, original = selector; if (arguments.length === 0) { emit('Not enough arguments'); return [ ]; } else if (typeof selector != 'string') { return [ ]; } else if (from && !(/1|9|11/).test(from.nodeType)) { emit('Invalid or illegal context element'); return [ ]; } else if (lastContext !== from) { // reset context data when it changes // and ensure context is set to a default switchContext(from || (from = doc)); } if (Config.CACHING && (elements = Dom.loadResults(original, from, doc, root))) { return callback ? concatCall([ ], elements, callback) : elements; } // normalize the selector string, remove [\n\r\f] // whitespace, replace codepoints 0 with '\ufffd' // trim non-relevant leading/trailing whitespaces selector = selector. replace(reTrimSpaces, ''). replace(/\x00|\\$/g, '\ufffd'); if (!OPERA_QSAPI && reSimpleSelector.test(selector)) { switch (selector.charAt(0)) { case '#': if (Config.UNIQUE_ID) { elements = (element = _byId(selector.slice(1), from)) ? [ element ] : [ ]; } break; case '.': elements = _byClass(selector.slice(1), from); break; default: elements = _byTag(selector, from); break; } } else if (!XML_DOCUMENT && Config.USE_QSAPI && !(BUGGY_QUIRKS_QSAPI && reClass.test(selector)) && !RE_BUGGY_QSAPI.test(selector)) { try { elements = from.querySelectorAll(selector); } catch(e) { } } if (elements) { elements = callback ? concatCall([ ], elements, callback) : NATIVE_SLICE_PROTO ? slice.call(elements) : concatList([ ], elements); Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; } Config.SHORTCUTS && (selector = Dom.shortcuts(selector, from)); if ((changed = lastSelector != selector)) { // process valid selector strings if ((parts = selector.match(reValidator)) && parts[0] == selector) { isSingleSelect = (parts = selector.match(reSplitGroup)).length < 2; // save passed selector lastSelector = selector; lastPartsSelect = parts; } else { emit('The string "' + selector + '", is not a valid CSS selector'); return [ ]; } } else parts = lastPartsSelect; // commas separators are treated sequentially to maintain order if (from.nodeType == 11) { elements = byTagRaw('*', from); } else if (!XML_DOCUMENT && isSingleSelect) { if (changed) { // get right most selector token parts = selector.match(reSplitToken); token = parts[parts.length - 1]; // only last slice before :not rules lastSlice = token.split(':not'); lastSlice = lastSlice[lastSlice.length - 1]; // position where token was found lastPosition = selector.length - token.length; } // ID optimization RTL, to reduce number of elements to visit if (Config.UNIQUE_ID && lastSlice && (parts = lastSlice.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, from))) { if (match(element, selector)) { callback && callback(element); elements = [element]; } else elements = [ ]; } } // ID optimization LTR, to reduce selection context searches else if (Config.UNIQUE_ID && (parts = selector.match(Optimize.ID)) && (token = parts[1])) { if ((element = _byId(token, doc))) { if ('#' + token == selector) { callback && callback(element); elements = [element]; } else if (/[>+~]/.test(selector)) { from = element.parentNode; } else { from = element; } } else elements = [ ]; } if (elements) { Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; } if (!NATIVE_GEBCN && lastSlice && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) { if ((elements = _byTag(token, from)).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*'); } else if (lastSlice && (parts = lastSlice.match(Optimize.CLASS)) && (token = parts[1])) { if ((elements = _byClass(token, from)).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1)) ? '' : '*'); } else if ((parts = selector.match(Optimize.CLASS)) && (token = parts[1])) { if ((elements = _byClass(token, from)).length === 0) { return [ ]; } for (i = 0, els = [ ]; elements.length > i; ++i) { els = concatList(els, elements[i].getElementsByTagName('*')); } elements = els; selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace('.' + token, reOptimizeSelector.test(selector.charAt(selector.indexOf(token) - 1)) ? '' : '*'); } else if (NATIVE_GEBCN && lastSlice && (parts = lastSlice.match(Optimize.TAG)) && (token = parts[1])) { if ((elements = _byTag(token, from)).length === 0) { return [ ]; } selector = selector.slice(0, lastPosition) + selector.slice(lastPosition).replace(token, '*'); } } if (!elements) { if (IE_LT_9) { elements = /^(?:applet|object)$/i.test(from.nodeName) ? from.children : byTagRaw('*', from); } else { elements = from.getElementsByTagName('*'); } } // end of prefiltering pass // compile selector resolver if necessary if (!selectResolvers[selector] || selectContexts[selector] !== from) { selectResolvers[selector] = compile(isSingleSelect ? [selector] : parts, '', true); selectContexts[selector] = from; } elements = selectResolvers[selector](elements, Snapshot, doc, root, from, callback); Config.CACHING && Dom.saveResults(original, from, doc, elements); return elements; }, /*-------------------------------- STORAGE ---------------------------------*/ // empty function handler FN = function(x) { return x; }, // compiled match functions returning booleans matchContexts = { }, matchResolvers = { }, // compiled select functions returning collections selectContexts = { }, selectResolvers = { }, // used to pass methods to compiled functions Snapshot = { // element indexing methods nthElement: nthElement, nthOfType: nthOfType, // element inspection methods getAttribute: getAttribute, hasAttribute: hasAttribute, // element selection methods byClass: _byClass, byName: byName, byTag: _byTag, byId: _byId, // helper/check methods contains: contains, isEmpty: isEmpty, isLink: isLink, // selection/matching select: select, match: match }, /*------------------------------- PUBLIC API -------------------------------*/ // code referenced by extensions Dom = { ACCEPT_NODE: ACCEPT_NODE, // retrieve element by id attr byId: byId, // retrieve elements by tag name byTag: byTag, // retrieve elements by name attr byName: byName, // retrieve elements by class name byClass: byClass, // read the value of the attribute // as was in the original HTML code getAttribute: getAttribute, // check for the attribute presence // as was in the original HTML code hasAttribute: hasAttribute, // element match selector, return boolean true/false match: match, // first element match only, return element or null first: first, // elements matching selector, starting from element select: select, // compile selector into ad-hoc javascript resolver compile: compile, // check that two elements are ancestor/descendant contains: contains, // handle selector engine configuration settings configure: configure, // initialize caching for each document setCache: FN, // load previously collected result set loadResults: FN, // save previously collected result set saveResults: FN, // handle missing context in selector strings shortcuts: FN, // log resolvers errors/warnings emit: emit, // options enabing specific engine functionality Config: Config, // pass methods references to compiled resolvers Snapshot: Snapshot, // operators descriptor // for attribute operators extensions Operators: Operators, // selectors descriptor // for pseudo-class selectors extensions Selectors: Selectors, // export validators REs Tokens: Tokens, // export version string Version: version, // add or overwrite user defined operators registerOperator: function(symbol, resolver) { Operators[symbol] || (Operators[symbol] = resolver); }, // add selector patterns for user defined callbacks registerSelector: function(name, rexp, func) { Selectors[name] || (Selectors[name] = { Expression: rexp, Callback: func }); } }; /*---------------------------------- INIT ----------------------------------*/ // init context specific variables initialize(doc); return Dom; });