package/LICENSE000644 0000002074 3560116604 010270 0ustar00000000 000000 Copyright (c) 2007-2019 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/src/modules/nwsapi-jquery.js000644 0000010267 3560116604 014701 0ustar00000000 000000 /* * Copyright (C) 2007-2017 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, mode, callback) { var status = true, macro = mode ? NW.Dom.S_BODY : NW.Dom.M_BODY; macro = macro.replace('@', typeof callback == 'function' ? (mode ? NW.Dom.S_TEST : NW.Dom.M_TEST) : ''); switch (match[1].toLowerCase()) { case 'odd': source = source.replace(macro, 'if((n=n^1)==0){' + macro + '}'); break; case 'even': source = source.replace(macro, 'if((n=n^1)==1){' + macro + '}'); break; case 'first': source = 'n=s.root.getElementsByTagName(e.nodeName);if(n.length&&n[0]===e){' + source + '}'; break; case 'last': source = 'n=s.root.getElementsByTagName(e.nodeName);if(n.length&&n[n.length-1]===e){' + source + '}'; break; default: switch (match[2].toLowerCase()) { case 'nth': source = 'n=s.root.getElementsByTagName(e.nodeName);if(n.length&&n[' + match[3] + ']===e){' + source + '}'; break; case 'eq': source = source.replace(macro, 'if(x++==' + match[3] + '){' + macro + '}'); break; case 'lt': source = source.replace(macro, 'if(x++<' + match[3] + '){' + macro + '}'); break; case 'gt': source = source.replace(macro, 'if(x++>' + match[3] + '){' + macro + '}'); break; default: status = false; break; } break; } // compiler will add this to "source" return { 'source': source, 'status': status, 'modvar': 'x=0' }; }; })(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, mode, callback) { var status = true, macro = mode ? NW.Dom.S_BODY : NW.Dom.M_BODY; macro = macro.replace('@', typeof callback == 'function' ? (mode ? NW.Dom.S_TEST : NW.Dom.M_TEST) : ''); switch(match[1].toLowerCase()) { case 'has': source = source.replace(macro, 'if(e.getElementsByTagName("' + match[3].replace(/^\s|\s$/g, '') + '")[0]){' + macro + '}'); 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': source = 'if(/^button$/i.test(e.nodeName)){' + source + '}'; break; case 'input': source = 'if(/^(?:button|input|select|textarea)$/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/nwsapi-traversal.js000644 0000005322 3560116604 015361 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/nwsapi.js000644 0000201724 3560116604 011714 0ustar00000000 000000 /* * Copyright (C) 2007-2019 Diego Perini * All rights reserved. * * nwsapi.js - Fast CSS Selectors API Engine * * Author: Diego Perini * Version: 2.2.0 * Created: 20070722 * Release: 20220901 * * License: * http://javascript.nwbox.com/nwsapi/MIT-LICENSE * Download: * http://javascript.nwbox.com/nwsapi/nwsapi.js */ (function Export(global, factory) { 'use strict'; 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, Export); } })(this, function Factory(global, Export) { var version = 'nwsapi-2.2.2', doc = global.document, root = doc.documentElement, slice = Array.prototype.slice, WSP = '[\\x20\\t\\r\\n\\f]', CFG = { // extensions operators: '[~*^$|]=|=', combinators: '[\\x20\\t>+~](?=[^>+~])' }, NOT = { // not enclosed in double/single/parens/square double_enc: '(?=(?:[^"]*["][^"]*["])*[^"]*$)', single_enc: "(?=(?:[^']*['][^']*['])*[^']*$)", parens_enc: '(?![^\\x28]*\\x29)', square_enc: '(?![^\\x5b]*\\x5d)' }, REX = { // regular expressions HasEscapes: RegExp('\\\\'), HexNumbers: RegExp('^[0-9a-fA-F]'), EscOrQuote: RegExp('^\\\\|[\\x22\\x27]'), RegExpChar: RegExp('(?:(?!\\\\)[\\\\^$.*+?()[\\]{}|\\/])', 'g'), TrimSpaces: RegExp('[\\r\\n\\f]|^' + WSP + '+|' + WSP + '+$', 'g'), CommaGroup: RegExp('(\\s*,\\s*)' + NOT.square_enc + NOT.parens_enc, 'g'), SplitGroup: RegExp('((?:\\x28[^\\x29]*\\x29|\\[[^\\]]*\\]|\\\\.|[^,])+)', 'g'), FixEscapes: RegExp('\\\\([0-9a-fA-F]{1,6}' + WSP + '?|.)|([\\x22\\x27])', 'g'), CombineWSP: RegExp('[\\n\\r\\f\\x20]+' + NOT.single_enc + NOT.double_enc, 'g'), TabCharWSP: RegExp('(\\x20?\\t+\\x20?)' + NOT.single_enc + NOT.double_enc, 'g'), PseudosWSP: RegExp('\\s+([-+])\\s+' + NOT.square_enc, 'g') }, STD = { combinator: RegExp('\\s?([>+~])\\s?', 'g'), apimethods: RegExp('^(?:[a-z]+|\\*)\\|', 'i'), namespaces: RegExp('(\\*|[a-z]+)\\|[-a-z]+', 'i') }, GROUPS = { // pseudo-classes requiring parameters linguistic: '(dir|lang)\\x28\\s?([-\\w]{2,})\\s?(?:\\x29|$)', logicalsel: '(is|where|matches|not)\\x28\\s?([^()]*|[^\\x28]*\\x28[^\\x29]*\\x29)\\s?(?:\\x29|$)', treestruct: '(nth(?:-last)?(?:-child|-of-type))(?:\\x28\\s?(even|odd|(?:[-+]?\\d*)(?:n\\s?[-+]?\\s?\\d*)?)\\s?(?:\\x29|$))', // pseudo-classes not requiring parameters locationpc: '(any-link|link|visited|target)\\b', useraction: '(hover|active|focus|focus-within)\\b', structural: '(root|empty|(?:(?:first|last|only)(?:-child|-of-type)))\\b', inputstate: '(enabled|disabled|read-only|read-write|placeholder-shown|default)\\b', inputvalue: '(checked|indeterminate|required|optional|valid|invalid|in-range|out-of-range)\\b', // pseudo-classes for parsing only selectors pseudo_nop: '(autofill|-webkit-autofill)\\b', // pseudo-elements starting with single colon (:) pseudo_sng: '(after|before|first-letter|first-line)\\b', // pseudo-elements starting with double colon (::) pseudo_dbl: ':(after|before|first-letter|first-line|selection|placeholder|-webkit-[-a-zA-Z0-9]{2,})\\b' }, Patterns = { // pseudo-classes treestruct: RegExp('^:(?:' + GROUPS.treestruct + ')(.*)', 'i'), structural: RegExp('^:(?:' + GROUPS.structural + ')(.*)', 'i'), linguistic: RegExp('^:(?:' + GROUPS.linguistic + ')(.*)', 'i'), useraction: RegExp('^:(?:' + GROUPS.useraction + ')(.*)', 'i'), inputstate: RegExp('^:(?:' + GROUPS.inputstate + ')(.*)', 'i'), inputvalue: RegExp('^:(?:' + GROUPS.inputvalue + ')(.*)', 'i'), locationpc: RegExp('^:(?:' + GROUPS.locationpc + ')(.*)', 'i'), logicalsel: RegExp('^:(?:' + GROUPS.logicalsel + ')(.*)', 'i'), pseudo_nop: RegExp('^:(?:' + GROUPS.pseudo_nop + ')(.*)', 'i'), pseudo_sng: RegExp('^:(?:' + GROUPS.pseudo_sng + ')(.*)', 'i'), pseudo_dbl: RegExp('^:(?:' + GROUPS.pseudo_dbl + ')(.*)', 'i'), // combinator symbols children: RegExp('^' + WSP + '?\\>' + WSP + '?(.*)'), adjacent: RegExp('^' + WSP + '?\\+' + WSP + '?(.*)'), relative: RegExp('^' + WSP + '?\\~' + WSP + '?(.*)'), ancestor: RegExp('^' + WSP + '+(.*)'), // universal & namespace universal: RegExp('^\\*(.*)'), namespace: RegExp('^(\\w+|\\*)?\\|(.*)') }, // regexp to aproximate detection of RTL languages (Arabic) RTL = RegExp('^[\\u0591-\\u08ff\\ufb1d-\\ufdfd\\ufe70-\\ufefc ]+$'), // emulate firefox error strings qsNotArgs = 'Not enough arguments', qsInvalid = ' is not a valid selector', // detect structural pseudo-classes in selectors reNthElem = RegExp('(:nth(?:-last)?-child)', 'i'), reNthType = RegExp('(:nth(?:-last)?-of-type)', 'i'), // placeholder for global regexp reOptimizer, reValidator, // special handling configuration flags Config = { IDS_DUPES: true, MIXEDCASE: true, LOGERRORS: true, VERBOSITY: true }, NAMESPACE, QUIRKS_MODE, HTML_DOCUMENT, ATTR_STD_OPS = { '=': 1, '^=': 1, '$=': 1, '|=': 1, '*=': 1, '~=': 1 }, 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 }, Combinators = { }, Selectors = { }, Operators = { '=': { p1: '^', p2: '$', p3: 'true' }, '^=': { p1: '^', p2: '', p3: 'true' }, '$=': { p1: '', p2: '$', p3: 'true' }, '*=': { p1: '', p2: '', p3: 'true' }, '|=': { p1: '^', p2: '(-|$)', p3: 'true' }, '~=': { p1: '(^|\\s)', p2: '(\\s|$)', p3: 'true' } }, concatCall = function(nodes, callback) { var i = 0, l = nodes.length, list = Array(l); while (l > i) { if (false === callback(list[i] = nodes[i])) break; ++i; } return list; }, concatList = function(list, nodes) { var i = -1, l = nodes.length; while (l--) { list[list.length] = nodes[++i]; } return list; }, documentOrder = function(a, b) { if (!hasDupes && a === b) { hasDupes = true; return 0; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }, hasDupes = false, unique = function(nodes) { var i = 0, j = -1, l = nodes.length + 1, list = [ ]; while (--l) { if (nodes[i++] === nodes[i]) continue; list[++j] = nodes[i - 1]; } hasDupes = false; return list; }, // check context for mixed content hasMixedCaseTagNames = function(context) { var ns, api = 'getElementsByTagNameNS'; // current host context (ownerDocument) context = context.ownerDocument || context; // documentElement (root) element namespace or default html/xhtml namespace ns = context.documentElement.namespaceURI || 'http://www.w3.org/1999/xhtml'; // checking the number of non HTML nodes in the document return (context[api]('*', '*').length - context[api](ns, '*').length) > 0; }, switchContext = function(context, force) { var oldDoc = doc; doc = context.ownerDocument || context; if (force || oldDoc !== doc) { // force a new check for each document change // performed before the next select operation root = doc.documentElement; HTML_DOCUMENT = isHTML(doc); QUIRKS_MODE = HTML_DOCUMENT && doc.compatMode.indexOf('CSS') < 0; NAMESPACE = root && root.namespaceURI; Snapshot.doc = doc; Snapshot.root = root; } return (Snapshot.from = context); }, // 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 REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? '\\' + p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? codePointToUTF16(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, // convert escape sequence in a CSS string or identifier // to javascript string with characters representations unescapeIdentifier = function(str) { return REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, method = { '#': 'getElementById', '*': 'getElementsByTagName', '|': 'getElementsByTagNameNS', '.': 'getElementsByClassName' }, compat = { '#': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byId(n, c); }; }, '*': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byTag(n, c); }; }, '|': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byTag(n, c); }; }, '.': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byClass(n, c); }; } }, // find duplicate ids using iterative walk byIdRaw = function(id, context) { var node = context, nodes = [ ], next = node.firstElementChild; while ((node = next)) { node.id == id && (nodes[nodes.length] = node); if ((next = node.firstElementChild || node.nextElementSibling)) continue; while (!next && (node = node.parentElement) && node !== context) { next = node.nextElementSibling; } } return nodes; }, // context agnostic getElementById byId = function(id, context) { var e, nodes, api = method['#']; // duplicates id allowed if (Config.IDS_DUPES === false) { if (api in context) { return (e = context[api](id)) ? [ e ] : none; } } else { if ('all' in context) { if ((e = context.all[id])) { if (e.nodeType == 1) return e.getAttribute('id') != id ? [ ] : [ e ]; else if (id == 'length') return (e = context[api](id)) ? [ e ] : none; for (i = 0, l = e.length, nodes = [ ]; l > i; ++i) { if (e[i].id == id) nodes[nodes.length] = e[i]; } return nodes && nodes.length ? nodes : [ nodes ]; } else return none; } } return byIdRaw(id, context); }, // context agnostic getElementsByTagName byTag = function(tag, context) { var e, nodes, api = method['*']; // DOCUMENT_NODE (9) & ELEMENT_NODE (1) if (api in context) { return slice.call(context[api](tag)); } else { tag = tag.toLowerCase(); // DOCUMENT_FRAGMENT_NODE (11) if ((e = context.firstElementChild)) { if (!(e.nextElementSibling || tag == '*' || e.localName == tag)) { return slice.call(e[api](tag)); } else { nodes = [ ]; do { if (tag == '*' || e.localName == tag) nodes[nodes.length] = e; concatList(nodes, e[api](tag)); } while ((e = e.nextElementSibling)); } } else nodes = none; } return nodes; }, // context agnostic getElementsByClassName byClass = function(cls, context) { var e, nodes, api = method['.'], reCls; // DOCUMENT_NODE (9) & ELEMENT_NODE (1) if (api in context) { return slice.call(context[api](cls)); } else { // DOCUMENT_FRAGMENT_NODE (11) if ((e = context.firstElementChild)) { reCls = RegExp('(^|\\s)' + cls + '(\\s|$)', QUIRKS_MODE ? 'i' : ''); if (!(e.nextElementSibling || reCls.test(e.className))) { return slice.call(e[api](cls)); } else { nodes = [ ]; do { if (reCls.test(e.className)) nodes[nodes.length] = e; concatList(nodes, e[api](cls)); } while ((e = e.nextElementSibling)); } } else nodes = none; } return nodes; }, // namespace aware hasAttribute // helper for XML/XHTML documents hasAttributeNS = function(e, name) { var i, l, attr = e.getAttributeNames(); name = RegExp(':?' + name + '$', HTML_DOCUMENT ? 'i' : ''); for (i = 0, l = attr.length; l > i; ++i) { if (name.test(attr[i])) return true; } return false; }, // fast resolver for the :nth-child() and :nth-last-child() pseudo-classes nthElement = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l; if (parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0) { parents[i = l] = parent; l = 0; nodes[i] = Array(); e = parent && parent.firstElementChild || element; while (e) { nodes[i][l] = e; if (e === element) j = l; e = e.nextElementSibling; ++l; } set = i; idx = 0; len = l; if (l < 2) return l; } else { l = nodes[i].length; set = i; } } if (element !== nodes[i][j] && element !== nodes[i][j = 0]) { for (j = 0, e = nodes[i], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // fast resolver for the :nth-of-type() and :nth-last-of-type() pseudo-classes nthOfType = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l, name = element.localName; if (nodes[set] && nodes[set][name] && parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0 || !nodes[i][name]) { parents[i = l] = parent; nodes[i] || (nodes[i] = Object()); l = 0; nodes[i][name] = Array(); e = parent && parent.firstElementChild || element; while (e) { if (e === element) j = l; if (e.localName == name) { nodes[i][name][l] = e; ++l; } e = e.nextElementSibling; } set = i; idx = j; len = l; if (l < 2) return l; } else { l = nodes[i][name].length; set = i; } } if (element !== nodes[i][name][j] && element !== nodes[i][name][j = 0]) { for (j = 0, e = nodes[i][name], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // check if the document type is HTML isHTML = function(node) { var doc = node.ownerDocument || node; return doc.nodeType == 9 && // contentType not in IE <= 11 'contentType' in doc ? doc.contentType.indexOf('/html') > 0 : doc.createElement('DiV').localName == 'div'; }, // configure the engine to use special handling configure = function(option, clear) { if (typeof option == 'string') { return !!Config[option]; } if (typeof option != 'object') { return Config; } for (var i in option) { Config[i] = !!option[i]; } // clear lambda cache if (clear) { matchResolvers = { }; selectResolvers = { }; } setIdentifierSyntax(); return true; }, // centralized error and exceptions handling emit = function(message, proto) { var err; if (Config.VERBOSITY) { if (proto) { err = new proto(message); } else { err = new global.DOMException(message, 'SyntaxError'); } throw err; } if (Config.LOGERRORS && console && console.log) { console.log(message); } }, // execute the engine initialization code initialize = function(doc) { setIdentifierSyntax(); lastContext = switchContext(doc, true); }, // build validation regexps used by the engine setIdentifierSyntax = function() { // // NOTE: SPECIAL CASES IN CSS SYNTAX PARSING RULES // // The https://drafts.csswg.org/css-syntax/#typedef-eof-token // allow mangled|unclosed selector syntax at the end of selectors strings // // Literal equivalent hex representations of the characters: " ' ` ] ) // // \\x22 = " - double quotes \\x5b = [ - open square bracket // \\x27 = ' - single quote \\x5d = ] - closed square bracket // \\x60 = ` - back tick \\x28 = ( - open round parens // \\x5c = \ - back slash \\x29 = ) - closed round parens // // using hex format prevents false matches of opened/closed instances // pairs, coloring breakage and other editors highlightning problems. // var identifier = // doesn't start with a digit '(?=[^0-9])' + // can start with double dash '(?:-{2}' + // may include ascii chars '|[a-zA-Z0-9-_]' + // non-ascii chars '|[^\\x00-\\x9f]' + // escaped chars '|\\\\[^\\r\\n\\f0-9a-fA-F]' + // unicode chars '|\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|\\s)?' + // any escaped chars '|\\\\.' + ')+', pseudonames = '[-\\w]+', pseudoparms = '(?:[-+]?\\d*)(?:n\\s?[-+]?\\s?\\d*)', doublequote = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*(?:"|$)', singlequote = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*(?:'|$)", attrparser = identifier + '|' + doublequote + '|' + singlequote, attrvalues = '([\\x22\\x27]?)((?!\\3)*|(?:\\\\?.)*?)(?:\\3|$)', attributes = '\\[' + // attribute presence '(?:\\*\\|)?' + WSP + '?' + '(' + identifier + '(?::' + identifier + ')?)' + WSP + '?' + '(?:' + '(' + CFG.operators + ')' + WSP + '?' + '(?:' + attrparser + ')' + ')?' + // attribute case sensitivity '(?:' + WSP + '?\\b(i))?' + WSP + '?' + '(?:\\]|$)', attrmatcher = attributes.replace(attrparser, attrvalues), pseudoclass = '(?:\\x28' + WSP + '*' + '(?:' + pseudoparms + '?)?|' + // universal * & // namespace *|* '(?:\\*|\\|)|' + '(?:' + '(?::' + pseudonames + '(?:\\x28' + pseudoparms + '?(?:\\x29|$))?|' + ')|' + '(?:[.#]?' + identifier + ')|' + '(?:' + attributes + ')' + ')+|' + '(?:' + WSP + '?,' + WSP + '?)|' + '(?:' + WSP + '?)|' + '(?:\\x29|$))*', standardValidator = '(?=' + WSP + '?[^>+~(){}<>])' + '(?:' + // universal * & // namespace *|* '(?:\\*|\\|)|' + '(?:[.#]?' + identifier + ')+|' + '(?:' + attributes + ')+|' + '(?:::?' + pseudonames + pseudoclass + ')|' + '(?:' + WSP + '?' + CFG.combinators + WSP + '?)|' + '(?:' + WSP + '?,' + WSP + '?)|' + '(?:' + WSP + '?)' + ')+'; // the following global RE is used to return the // deepest localName in selector strings and then // use it to retrieve all possible matching nodes // that will be filtered by compiled resolvers reOptimizer = RegExp( '(?:([.:#*]?)' + '(' + identifier + ')' + '(?:' + ':[-\\w]+|' + '\\[[^\\]]+(?:\\]|$)|' + '\\x28[^\\x29]+(?:\\x29|$)' + ')*)$'); // global reValidator = RegExp(standardValidator, 'g'); Patterns.id = RegExp('^#(' + identifier + ')(.*)'); Patterns.tagName = RegExp('^(' + identifier + ')(.*)'); Patterns.className = RegExp('^\\.(' + identifier + ')(.*)'); Patterns.attribute = RegExp('^(?:' + attrmatcher + ')(.*)'); }, F_INIT = '"use strict";return function Resolver(c,f,x,r)', S_HEAD = 'var e,n,o,j=r.length-1,k=-1', M_HEAD = 'var e,n,o', S_LOOP = 'main:while((e=c[++k]))', N_LOOP = 'main:while((e=c.item(++k)))', M_LOOP = 'e=c;', S_BODY = 'r[++j]=c[k];', N_BODY = 'r[++j]=c.item(k);', M_BODY = '', S_TAIL = 'continue main;', M_TAIL = 'r=true;', S_TEST = 'if(f(c[k])){break main;}', N_TEST = 'if(f(c.item(k))){break main;}', M_TEST = 'f(c);', S_VARS = [ ], M_VARS = [ ], // compile groups or single selector strings into // executable functions for matching or selecting compile = function(selector, mode, callback) { var factory, token, head = '', loop = '', macro = '', source = '', vars = ''; // 'mode' can be boolean or null // true = select / false = match // null to use collection.item() switch (mode) { case true: if (selectLambdas[selector]) { return selectLambdas[selector]; } macro = S_BODY + (callback ? S_TEST : '') + S_TAIL; head = S_HEAD; loop = S_LOOP; break; case false: if (matchLambdas[selector]) { return matchLambdas[selector]; } macro = M_BODY + (callback ? M_TEST : '') + M_TAIL; head = M_HEAD; loop = M_LOOP; break; case null: if (selectLambdas[selector]) { return selectLambdas[selector]; } macro = N_BODY + (callback ? N_TEST : '') + S_TAIL; head = S_HEAD; loop = N_LOOP; break; default: break; } source = compileSelector(selector, macro, mode, callback); loop += mode || mode === null ? '{' + source + '}' : source; if (mode || mode === null && selector.includes(':nth')) { loop += reNthElem.test(selector) ? 's.nthElement(null, 2);' : ''; loop += reNthType.test(selector) ? 's.nthOfType(null, 2);' : ''; } if (S_VARS[0] || M_VARS[0]) { vars = ',' + (S_VARS.join(',') || M_VARS.join(',')); S_VARS.length = 0; M_VARS.length = 0; } factory = Function('s', F_INIT + '{' + head + vars + ';' + loop + 'return r;}')(Snapshot); return mode || mode === null ? (selectLambdas[selector] = factory) : (matchLambdas[selector] = factory); }, // build conditional code to check components of selector strings compileSelector = function(expression, source, mode, callback) { // N is the negation pseudo-class flag // D is the default inverted negation flag var a, b, n, f, i, l, name, NS, N = '', D = '!', compat, expr, match, result, status, symbol, test, type, selector = expression, selector_string, vars; // original 'select' or 'match' selector string before normalization selector_string = mode ? lastSelected : lastMatched; // isolate selector combinators/components and normalize whitespace selector = selector.replace(STD.combinator, '$1');//.replace(STD.whitespace, ' '); // javascript needs a label to break // out of the while loops processing selector_recursion_label: while (selector) { // get namespace prefix if present or get first char of selector symbol = STD.apimethods.test(selector) ? '|' : selector[0]; switch (symbol) { // universal resolver case '*': match = selector.match(Patterns.universal); if (N == '!') { source = 'if(' + N + 'true' + '){' + source + '}'; } break; // id resolver case '#': match = selector.match(Patterns.id); source = 'if(' + N + '(/^' + match[1] + '$/.test(e.getAttribute("id"))' + ')){' + source + '}'; break; // class name resolver case '.': match = selector.match(Patterns.className); compat = (QUIRKS_MODE ? 'i' : '') + '.test(e.getAttribute("class"))'; source = 'if(' + N + '(/(^|\\s)' + match[1] + '(\\s|$)/' + compat + ')){' + source + '}'; break; // tag name resolver case (/[_a-z]/i.test(symbol) ? symbol : undefined): match = selector.match(Patterns.tagName); source = 'if(' + N + '(e.localName' + (Config.MIXEDCASE || hasMixedCaseTagNames(doc) ? '=="' + match[1].toLowerCase() + '"' : '=="' + match[1].toUpperCase() + '"') + ')){' + source + '}'; break; // namespace resolver case '|': match = selector.match(Patterns.namespace); if (match[1] == '*') { source = 'if(' + N + 'true){' + source + '}'; } else if (!match[1]) { source = 'if(' + N + '(!e.namespaceURI)){' + source + '}'; } else if (typeof match[1] == 'string' && root.prefix == match[1]) { source = 'if(' + N + '(e.namespaceURI=="' + NAMESPACE + '")){' + source + '}'; } else { emit('\'' + selector_string + '\'' + qsInvalid); } break; // attributes resolver case '[': match = selector.match(Patterns.attribute); NS = match[0].match(STD.namespaces); name = match[1]; expr = name.split(':'); expr = expr.length == 2 ? expr[1] : expr[0]; if (match[2] && !(test = Operators[match[2]])) { emit('\'' + selector_string + '\'' + qsInvalid); return ''; } if (match[4] === '') { test = match[2] == '~=' ? { p1: '^\\s', p2: '+$', p3: 'true' } : match[2] in ATTR_STD_OPS && match[2] != '~=' ? { p1: '^', p2: '$', p3: 'true' } : test; } else if (match[2] == '~=' && match[4].includes(' ')) { // whitespace separated list but value contains space source = 'if(' + N + 'false){' + source + '}'; break; } else if (match[4]) { match[4] = convertEscapes(match[4]).replace(REX.RegExpChar, '\\$&'); } type = match[5] == 'i' || (HTML_DOCUMENT && HTML_TABLE[expr.toLowerCase()]) ? 'i' : ''; source = 'if(' + N + '(' + (!match[2] ? (NS ? 's.hasAttributeNS(e,"' + name + '")' : 'e.hasAttribute&&e.hasAttribute("' + name + '")') : !match[4] && ATTR_STD_OPS[match[2]] && match[2] != '~=' ? 'e.getAttribute&&e.getAttribute("' + name + '")==""' : '(/' + test.p1 + match[4] + test.p2 + '/' + type + ').test(e.getAttribute&&e.getAttribute("' + name + '"))==' + test.p3) + ')){' + source + '}'; break; // *** General sibling combinator // E ~ F (F relative sibling of E) case '~': match = selector.match(Patterns.relative); source = 'n=e;while((e=e.previousElementSibling)){' + source + '}e=n;'; break; // *** Adjacent sibling combinator // E + F (F adiacent sibling of E) case '+': match = selector.match(Patterns.adjacent); source = 'n=e;if((e=e.previousElementSibling)){' + source + '}e=n;'; break; // *** Descendant combinator // E F (E ancestor of F) case '\x09': case '\x20': match = selector.match(Patterns.ancestor); source = 'n=e;while((e=e.parentElement)){' + source + '}e=n;'; break; // *** Child combinator // E > F (F children of E) case '>': match = selector.match(Patterns.children); source = 'n=e;if((e=e.parentElement)){' + source + '}e=n;'; break; // *** user supplied combinators extensions case (symbol in Combinators ? symbol : undefined): // for other registered combinators extensions match[match.length - 1] = '*'; source = Combinators[symbol](match) + source; break; // *** tree-structural pseudo-classes // :root, :empty, :first-child, :last-child, :only-child, :first-of-type, :last-of-type, :only-of-type case ':': if ((match = selector.match(Patterns.structural))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'root': // there can only be one :root element, so exit the loop once found source = 'if(' + N + '(e===s.root)){' + source + (mode ? 'break main;' : '') + '}'; break; case 'empty': // matches elements that don't contain elements or text nodes source = 'n=e.firstChild;while(n&&!(/1|3/).test(n.nodeType)){n=n.nextSibling}if(' + D + 'n){' + source + '}'; break; // *** child-indexed pseudo-classes // :first-child, :last-child, :only-child case 'only-child': source = 'if(' + N + '(!e.nextElementSibling&&!e.previousElementSibling)){' + source + '}'; break; case 'last-child': source = 'if(' + N + '(!e.nextElementSibling)){' + source + '}'; break; case 'first-child': source = 'if(' + N + '(!e.previousElementSibling)){' + source + '}'; break; // *** typed child-indexed pseudo-classes // :only-of-type, :last-of-type, :first-of-type case 'only-of-type': source = 'o=e.localName;' + 'n=e;while((n=n.nextElementSibling)&&n.localName!=o);if(!n){' + 'n=e;while((n=n.previousElementSibling)&&n.localName!=o);}if(' + D + 'n){' + source + '}'; break; case 'last-of-type': source = 'n=e;o=e.localName;while((n=n.nextElementSibling)&&n.localName!=o);if(' + D + 'n){' + source + '}'; break; case 'first-of-type': source = 'n=e;o=e.localName;while((n=n.previousElementSibling)&&n.localName!=o);if(' + D + 'n){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** child-indexed & typed child-indexed pseudo-classes // :nth-child, :nth-of-type, :nth-last-child, :nth-last-of-type else if ((match = selector.match(Patterns.treestruct))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'nth-child': case 'nth-of-type': case 'nth-last-child': case 'nth-last-of-type': expr = /-of-type/i.test(match[1]); if (match[1] && match[2]) { type = /last/i.test(match[1]); if (match[2] == 'n') { source = 'if(' + N + 'true){' + source + '}'; break; } else if (match[2] == '1') { test = type ? 'next' : 'previous'; source = expr ? 'n=e;o=e.localName;' + 'while((n=n.' + test + 'ElementSibling)&&n.localName!=o);if(' + D + 'n){' + source + '}' : 'if(' + N + '!e.' + test + 'ElementSibling){' + source + '}'; break; } else if (match[2] == 'even' || match[2] == '2n0' || match[2] == '2n+0' || match[2] == '2n') { test = 'n%2==0'; } else if (match[2] == 'odd' || match[2] == '2n1' || match[2] == '2n+1') { test = 'n%2==1'; } else { f = /n/i.test(match[2]); n = match[2].split('n'); a = parseInt(n[0], 10) || 0; b = parseInt(n[1], 10) || 0; if (n[0] == '-') { a = -1; } if (n[0] == '+') { a = +1; } test = (b ? '(n' + (b > 0 ? '-' : '+') + Math.abs(b) + ')' : 'n') + '%' + a + '==0' ; test = a >= +1 ? (f ? 'n>' + (b - 1) + (Math.abs(a) != 1 ? '&&' + test : '') : 'n==' + a) : a <= -1 ? (f ? 'n<' + (b + 1) + (Math.abs(a) != 1 ? '&&' + test : '') : 'n==' + a) : a === 0 ? (n[0] ? 'n==' + b : 'n>' + (b - 1)) : 'false'; } expr = expr ? 'OfType' : 'Element'; type = type ? 'true' : 'false'; source = 'n=s.nth' + expr + '(e,' + type + ');if(' + N + '(' + test + ')){' + source + '}'; } else { emit('\'' + selector_string + '\'' + qsInvalid); } break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** logical combination pseudo-classes // :is( s1, [ s2, ... ]), :not( s1, [ s2, ... ]) else if ((match = selector.match(Patterns.logicalsel))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'is': case 'where': case 'matches': expr = match[2].replace(REX.CommaGroup, ',').replace(REX.TrimSpaces, ''); source = 'if(s.match("' + expr.replace(/\x22/g, '\\"') + '",e)){' + source + '}'; break; case 'not': expr = match[2].replace(REX.CommaGroup, ',').replace(REX.TrimSpaces, ''); source = 'if(!s.match("' + expr.replace(/\x22/g, '\\"') + '",e)){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** linguistic pseudo-classes // :dir( ltr / rtl ), :lang( en ) else if ((match = selector.match(Patterns.linguistic))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'dir': source = 'var p;if(' + N + '(' + '(/' + match[2] + '/i.test(e.dir))||(p=s.ancestor("[dir]", e))&&' + '(/' + match[2] + '/i.test(p.dir))||(e.dir==""||e.dir=="auto")&&' + '(' + (match[2] == 'ltr' ? '!':'')+ RTL +'.test(e.textContent)))' + '){' + source + '};'; break; case 'lang': expr = '(?:^|-)' + match[2] + '(?:-|$)'; source = 'var p;if(' + N + '(' + '(e.isConnected&&(e.lang==""&&(p=s.ancestor("[lang]",e)))&&' + '(p.lang=="' + match[2] + '")||/'+ expr +'/i.test(e.lang)))' + '){' + source + '};'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** location pseudo-classes // :any-link, :link, :visited, :target else if ((match = selector.match(Patterns.locationpc))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'any-link': source = 'if(' + N + '(/^a|area$/i.test(e.localName)&&e.hasAttribute("href")||e.visited)){' + source + '}'; break; case 'link': source = 'if(' + N + '(/^a|area$/i.test(e.localName)&&e.hasAttribute("href"))){' + source + '}'; break; case 'visited': source = 'if(' + N + '(/^a|area$/i.test(e.localName)&&e.hasAttribute("href")&&e.visited)){' + source + '}'; break; case 'target': source = 'if(' + N + '((s.doc.compareDocumentPosition(e)&16)&&s.doc.location.hash&&e.id==s.doc.location.hash.slice(1))){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** user actions pseudo-classes // :hover, :active, :focus else if ((match = selector.match(Patterns.useraction))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'hover': source = 'hasFocus' in doc && doc.hasFocus() ? 'if(' + N + '(e===s.doc.hoverElement)){' + source + '}' : 'if(' + D + 'true){' + source + '}'; break; case 'active': source = 'hasFocus' in doc && doc.hasFocus() ? 'if(' + N + '(e===s.doc.activeElement)){' + source + '}' : 'if(' + D + 'true){' + source + '}'; break; case 'focus': source = 'hasFocus' in doc ? 'if(' + N + '(e===s.doc.activeElement&&s.doc.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number"))){' + source + '}' : 'if(' + N + '(e===s.doc.activeElement&&(e.type||e.href))){' + source + '}'; break; case 'focus-within': source = 'hasFocus' in doc ? 'n=s.doc.activeElement;while(e){if(e===n||e.parentNode===n)break;}' + 'if(' + N + '(e===n&&s.doc.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number"))){' + source + '}' : source; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** user interface and form pseudo-classes // :enabled, :disabled, :read-only, :read-write, :placeholder-shown, :default else if ((match = selector.match(Patterns.inputstate))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'enabled': source = 'if(' + N + '(("form" in e||/^optgroup$/i.test(e.localName))&&"disabled" in e &&e.disabled===false' + ')){' + source + '}'; break; case 'disabled': // https://www.w3.org/TR/html5/forms.html#enabling-and-disabling-form-controls:-the-disabled-attribute source = 'if(' + N + '(("form" in e||/^optgroup$/i.test(e.localName))&&"disabled" in e&&' + '(e.disabled===true||(n=s.ancestor("fieldset",e))&&(n=s.first("legend",n))&&!n.contains(e))' + ')){' + source + '}'; break; case 'read-only': source = 'if(' + N + '(' + '(/^textarea$/i.test(e.localName)&&(e.readOnly||e.disabled))||' + '("|password|text|".includes("|"+e.type+"|")&&e.readOnly)' + ')){' + source + '}'; break; case 'read-write': source = 'if(' + N + '(' + '((/^textarea$/i.test(e.localName)&&!e.readOnly&&!e.disabled)||' + '("|password|text|".includes("|"+e.type+"|")&&!e.readOnly&&!e.disabled))||' + '(e.hasAttribute("contenteditable")||(s.doc.designMode=="on"))' + ')){' + source + '}'; break; case 'placeholder-shown': source = 'if(' + N + '(' + '(/^input|textarea$/i.test(e.localName))&&e.hasAttribute("placeholder")&&' + '("|textarea|password|number|search|email|text|tel|url|".includes("|"+e.type+"|"))&&' + '(!s.match(":focus",e))' + ')){' + source + '}'; break; case 'default': source = 'if(' + N + '("form" in e && e.form)){' + 'var x=0;n=[];' + 'if(e.type=="image")n=e.form.getElementsByTagName("input");' + 'if(e.type=="submit")n=e.form.elements;' + 'while(n[x]&&e!==n[x]){' + 'if(n[x].type=="image")break;' + 'if(n[x].type=="submit")break;' + 'x++;' + '}' + '}' + 'if(' + N + '(e.form&&(e===n[x]&&"|image|submit|".includes("|"+e.type+"|"))||' + '((/^option$/i.test(e.localName))&&e.defaultSelected)||' + '(("|radio|checkbox|".includes("|"+e.type+"|"))&&e.defaultChecked)' + ')){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** input pseudo-classes (for form validation) // :checked, :indeterminate, :valid, :invalid, :in-range, :out-of-range, :required, :optional else if ((match = selector.match(Patterns.inputvalue))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'checked': source = 'if(' + N + '(/^input$/i.test(e.localName)&&' + '("|radio|checkbox|".includes("|"+e.type+"|")&&e.checked)||' + '(/^option$/i.test(e.localName)&&(e.selected||e.checked))' + ')){' + source + '}'; break; case 'indeterminate': source = 'if(' + N + '(/^progress$/i.test(e.localName)&&!e.hasAttribute("value"))||' + '(/^input$/i.test(e.localName)&&("checkbox"==e.type&&e.indeterminate)||' + '("radio"==e.type&&e.name&&!s.first("input[name="+e.name+"]:checked",e.form))' + ')){' + source + '}'; break; case 'required': source = 'if(' + N + '(/^input|select|textarea$/i.test(e.localName)&&e.required)' + '){' + source + '}'; break; case 'optional': source = 'if(' + N + '(/^input|select|textarea$/i.test(e.localName)&&!e.required)' + '){' + source + '}'; break; case 'invalid': source = 'if(' + N + '((' + '(/^form$/i.test(e.localName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&!e.checkValidity())||' + '(/^fieldset$/i.test(e.localName)&&s.first(":invalid",e))' + '){' + source + '}'; break; case 'valid': source = 'if(' + N + '((' + '(/^form$/i.test(e.localName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&e.checkValidity())||' + '(/^fieldset$/i.test(e.localName)&&s.first(":valid",e))' + '){' + source + '}'; break; case 'in-range': source = 'if(' + N + '(/^input$/i.test(e.localName))&&' + '(e.willValidate&&!e.formNoValidate)&&' + '(!e.validity.rangeUnderflow&&!e.validity.rangeOverflow)&&' + '("|date|datetime-local|month|number|range|time|week|".includes("|"+e.type+"|"))&&' + '("range"==e.type||e.getAttribute("min")||e.getAttribute("max"))' + '){' + source + '}'; break; case 'out-of-range': source = 'if(' + N + '(/^input$/i.test(e.localName))&&' + '(e.willValidate&&!e.formNoValidate)&&' + '(e.validity.rangeUnderflow||e.validity.rangeOverflow)&&' + '("|date|datetime-local|month|number|range|time|week|".includes("|"+e.type+"|"))&&' + '("range"==e.type||e.getAttribute("min")||e.getAttribute("max"))' + '){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // allow pseudo-elements starting with single colon (:) // :after, :before, :first-letter, :first-line // assert: e.type is in double-colon format, like ::after else if ((match = selector.match(Patterns.pseudo_sng))) { source = 'if(e.element&&e.type.toLowerCase()=="' + ':' + match[0].toLowerCase() + '"){e=e.element;' + source + '}'; } // allow pseudo-elements starting with double colon (::) // ::after, ::before, ::marker, ::placeholder, ::inactive-selection, ::selection, ::-webkit- // assert: e.type is in double-colon format, like ::after else if ((match = selector.match(Patterns.pseudo_dbl))) { source = 'if(e.element&&e.type.toLowerCase()=="' + match[0].toLowerCase() + '"){e=e.element;' + source + '}'; } // placeholder for parsed only no-op selectors else if ((match = selector.match(Patterns.pseudo_nop))) { source = 'if(' + N + 'false' + '){' + source + '}'; } else { // reset expr = false; status = false; // process registered selector extensions for (expr in Selectors) { if ((match = selector.match(Selectors[expr].Expression))) { result = Selectors[expr].Callback(match, source, mode, callback); if ('match' in result) { match = result.match; } vars = result.modvar; if (mode) { // add extra select() vars vars && S_VARS.indexOf(vars) < 0 && (S_VARS[S_VARS.length] = vars); } else { // add extra match() vars vars && M_VARS.indexOf(vars) < 0 && (M_VARS[M_VARS.length] = vars); } // extension source code source = result.source; // extension status code status = result.status; // break on status error if (status) { break; } } } if (!status) { emit('unknown pseudo-class selector \'' + selector + '\''); return ''; } if (!expr) { emit('unknown token in selector \'' + selector + '\''); return ''; } } break; default: emit('\'' + selector_string + '\'' + qsInvalid); break selector_recursion_label; } // end of switch symbol if (!match) { emit('\'' + selector_string + '\'' + qsInvalid); return ''; } // pop last component selector = match.pop(); } // end of while selector return source; }, // replace ':scope' pseudo-class with element references makeref = function(selectors, element) { return selectors.replace(/:scope/ig, element.localName + (element.id ? '#' + element.id : '') + (element.className ? '.' + element.classList[0] : '')); }, // equivalent of w3c 'closest' method ancestor = function _closest(selectors, element, callback) { if ((/:scope/i).test(selectors)) { selectors = makeref(selectors, element); } while (element) { if (match(selectors, element, callback)) break; element = element.parentElement; } return element; }, match_assert = function(f, element, callback) { for (var i = 0, l = f.length, r = false; l > i; ++i) f[i](element, callback, null, false) && (r = true); return r; }, match_collect = function(selectors, callback) { for (var i = 0, l = selectors.length, f = [ ]; l > i; ++i) f[i] = compile(selectors[i], false, callback); return { factory: f }; }, // equivalent of w3c 'matches' method match = function _matches(selectors, element, callback) { var expressions, parsed; if (element && matchResolvers[selectors]) { return match_assert(matchResolvers[selectors].factory, element, callback); } lastMatched = selectors; // arguments validation if (arguments.length === 0) { emit(qsNotArgs, TypeError); return Config.VERBOSITY ? undefined : false; } else if (arguments[0] === '') { emit('\'\'' + qsInvalid); return Config.VERBOSITY ? undefined : false; } // input NULL or UNDEFINED if (typeof selectors != 'string') { selectors = '' + selectors; } if ((/:scope/i).test(selectors)) { selectors = makeref(selectors, element); } // normalize input string parsed = selectors. replace(/\x00|\\$/g, '\ufffd'). replace(REX.CombineWSP, '\x20'). replace(REX.PseudosWSP, '$1'). replace(REX.TabCharWSP, '\t'). replace(REX.CommaGroup, ','). replace(REX.TrimSpaces, ''); // parse, validate and split possible compound selectors if ((expressions = parsed.match(reValidator)) && expressions.join('') == parsed) { expressions = parsed.match(REX.SplitGroup); if (parsed[parsed.length - 1] == ',') { emit(qsInvalid); return Config.VERBOSITY ? undefined : false; } } else { emit('\'' + selectors + '\'' + qsInvalid); return Config.VERBOSITY ? undefined : false; } matchResolvers[selectors] = match_collect(expressions, callback); return match_assert(matchResolvers[selectors].factory, element, callback); }, // equivalent of w3c 'querySelector' method first = function _querySelector(selectors, context, callback) { if (arguments.length === 0) { emit(qsNotArgs, TypeError); } return select(selectors, context, typeof callback == 'function' ? function firstMatch(element) { callback(element); return false; } : function firstMatch() { return false; } )[0] || null; }, // equivalent of w3c 'querySelectorAll' method select = function _querySelectorAll(selectors, context, callback) { var expressions, nodes = [ ], parsed, resolver; context || (context = doc); if (selectors) { if ((resolver = selectResolvers[selectors])) { if (resolver.context === context && resolver.callback === callback) { var f = resolver.factory, h = resolver.htmlset, n = resolver.nodeset; if (n.length > 1) { for (var i = 0, l = n.length, list; l > i; ++i) { list = compat[n[i][0]](context, n[i].slice(1))(); if (f[i] !== null) { f[i](list, callback, context, nodes); } else { nodes = nodes.concat(list); } } if (l > 1 && nodes.length > 1) { nodes.sort(documentOrder); hasDupes && (nodes = unique(nodes)); } } else { if (f[0]) { nodes = f[0](h[0](), callback, context, nodes); } else { nodes = h[0](); } } return typeof callback == 'function' ? concatCall(nodes, callback) : nodes; } } } lastSelected = selectors; // arguments validation if (arguments.length === 0) { emit(qsNotArgs, TypeError); return Config.VERBOSITY ? undefined : none; } else if (arguments[0] === '') { emit('\'\'' + qsInvalid); return Config.VERBOSITY ? undefined : none; } else if (lastContext !== context) { lastContext = switchContext(context); } // input NULL or UNDEFINED if (typeof selectors != 'string') { selectors = '' + selectors; } if ((/:scope/i).test(selectors)) { selectors = makeref(selectors, context); } // normalize input string parsed = selectors. replace(/\x00|\\$/g, '\ufffd'). replace(REX.CombineWSP, '\x20'). replace(REX.PseudosWSP, '$1'). replace(REX.TabCharWSP, '\t'). replace(REX.CommaGroup, ','). replace(REX.TrimSpaces, ''); // parse, validate and split possible compound selectors if ((expressions = parsed.match(reValidator)) && expressions.join('') == parsed) { expressions = parsed.match(REX.SplitGroup); if (parsed[parsed.length - 1] == ',') { emit(qsInvalid); return Config.VERBOSITY ? undefined : false; } } else { emit('\'' + selectors + '\'' + qsInvalid); return Config.VERBOSITY ? undefined : false; } // save/reuse factory and closure collection selectResolvers[selectors] = collect(expressions, context, callback); nodes = selectResolvers[selectors].results; return typeof callback == 'function' ? concatCall(nodes, callback) : nodes; }, // optimize selectors avoiding duplicated checks optimize = function(selector, token) { var index = token.index, length = token[1].length + token[2].length; return selector.slice(0, index) + (' >+~'.indexOf(selector.charAt(index - 1)) > -1 ? (':['.indexOf(selector.charAt(index + length + 1)) > -1 ? '*' : '') : '') + selector.slice(index + length - (token[1] == '*' ? 1 : 0)); }, // prepare factory resolvers and closure collections collect = function(selectors, context, callback) { var i, l, seen = { }, token = ['', '*', '*'], optimized = selectors, factory = [ ], htmlset = [ ], nodeset = [ ], results = [ ], type; for (i = 0, l = selectors.length; l > i; ++i) { if (!seen[selectors[i]] && (seen[selectors[i]] = true)) { type = selectors[i].match(reOptimizer); if (type && type[1] != ':' && (token = type)) { token[1] || (token[1] = '*'); optimized[i] = optimize(optimized[i], token); } else { token = ['', '*', '*']; } } nodeset[i] = token[1] + token[2]; htmlset[i] = compat[token[1]](context, token[2]); factory[i] = compile(optimized[i], true, null); factory[i] ? factory[i](htmlset[i](), callback, context, results) : result.concat(htmlset[i]()); } if (l > 1) { results.sort(documentOrder); hasDupes && (results = unique(results)); } return { callback: callback, context: context, factory: factory, htmlset: htmlset, nodeset: nodeset, results: results }; }, // QSA placeholders to native references _closest, _matches, _querySelector, _querySelectorAll, // overrides QSA methods (only for browsers) install = function(all) { // save native QSA references _closest = Element.prototype.closest; _matches = Element.prototype.matches; _querySelector = Document.prototype.querySelector; _querySelectorAll = Document.prototype.querySelectorAll; Element.prototype.closest = function closest() { var ctor = Object.getPrototypeOf(this).__proto__.__proto__.constructor.name; if (!('nodeType' in this)) { emit('\'closest\' called on an object that does not implement interface ' + ctor + '.', TypeError); } return arguments.length < 1 ? ancestor.apply(this, [ ]) : arguments.length < 2 ? ancestor.apply(this, [ arguments[0], this ]) : ancestor.apply(this, [ arguments[0], this, typeof arguments[1] == 'function' ? arguments[1] : undefined ]); }; Element.prototype.matches = function matches() { var ctor = Object.getPrototypeOf(this).__proto__.__proto__.constructor.name; if (!('nodeType' in this)) { emit('\'matches\' called on an object that does not implement interface ' + ctor + '.', TypeError); } return arguments.length < 1 ? match.apply(this, [ ]) : arguments.length < 2 ? match.apply(this, [ arguments[0], this ]) : match.apply(this, [ arguments[0], this, typeof arguments[1] == 'function' ? arguments[1] : undefined ]); }; Element.prototype.querySelector = Document.prototype.querySelector = DocumentFragment.prototype.querySelector = function querySelector() { var ctor = Object.getPrototypeOf(this).__proto__.__proto__.constructor.name; if (!('nodeType' in this)) { emit('\'querySelector\' called on an object that does not implement interface ' + ctor + '.', TypeError); } return arguments.length < 1 ? first.apply(this, [ ]) : arguments.length < 2 ? first.apply(this, [ arguments[0], this ]) : first.apply(this, [ arguments[0], this, typeof arguments[1] == 'function' ? arguments[1] : undefined ]); }; Element.prototype.querySelectorAll = Document.prototype.querySelectorAll = DocumentFragment.prototype.querySelectorAll = function querySelectorAll() { var ctor = Object.getPrototypeOf(this).__proto__.__proto__.constructor.name; if (!('nodeType' in this)) { emit('\'querySelectorAll\' called on an object that does not implement interface ' + ctor + '.', TypeError); } return arguments.length < 1 ? select.apply(this, [ ]) : arguments.length < 2 ? select.apply(this, [ arguments[0], this ]) : select.apply(this, [ arguments[0], this, typeof arguments[1] == 'function' ? arguments[1] : undefined ]); }; if (all) { document.addEventListener('load', function(e) { var c, d, r, s, t = e.target; if (/iframe/i.test(t.localName)) { c = '(' + Export + ')(this, ' + Factory + ');'; d = t.contentDocument; s = d.createElement('script'); s.textContent = c + 'NW.Dom.install()'; r = d.documentElement; r.removeChild(r.insertBefore(s, r.firstChild)); } }, true); } }, // restore QSA methods (only for browsers) uninstall = function() { // reinstates QSA native references Element.prototype.closest = _closest; Element.prototype.matches = _matches; Element.prototype.querySelector = Document.prototype.querySelector = DocumentFragment.prototype.querySelector = _querySelector; Element.prototype.querySelectorAll = Document.prototype.querySelectorAll = DocumentFragment.prototype.querySelectorAll = _querySelectorAll; }, // empty set none = Array(), // context lastContext, // selector lastMatched, lastSelected, // cached lambdas matchLambdas = { }, selectLambdas = { }, // cached resolvers matchResolvers = { }, selectResolvers = { }, // passed to resolvers Snapshot = { doc: doc, from: doc, root: root, byTag: byTag, first: first, match: match, ancestor: ancestor, nthOfType: nthOfType, nthElement: nthElement, hasAttributeNS: hasAttributeNS }, // public exported methods/objects Dom = { // exported cache objects lastMatched: lastMatched, lastSelected: lastSelected, matchLambdas: matchLambdas, selectLambdas: selectLambdas, matchResolvers: matchResolvers, selectResolvers: selectResolvers, // exported compiler macros CFG: CFG, M_BODY: M_BODY, S_BODY: S_BODY, M_TEST: M_TEST, S_TEST: S_TEST, // exported engine methods byId: byId, byTag: byTag, byClass: byClass, match: match, first: first, select: select, closest: ancestor, compile: compile, configure: configure, emit: emit, Config: Config, Snapshot: Snapshot, Version: version, install: install, uninstall: uninstall, Operators: Operators, Selectors: Selectors, // register a new selector combinator symbol and its related function resolver registerCombinator: function(combinator, resolver) { var i = 0, l = combinator.length, symbol; for (; l > i; ++i) { if (combinator[i] != '=') { symbol = combinator[i]; break; } } if (CFG.combinators.indexOf(symbol) < 0) { CFG.combinators = CFG.combinators.replace('](', symbol + ']('); CFG.combinators = CFG.combinators.replace('])', symbol + '])'); Combinators[combinator] = resolver; setIdentifierSyntax(); } else { console.warn('Warning: the \'' + combinator + '\' combinator is already registered.'); } }, // register a new attribute operator symbol and its related function resolver registerOperator: function(operator, resolver) { var i = 0, l = operator.length, symbol; for (; l > i; ++i) { if (operator[i] != '=') { symbol = operator[i]; break; } } if (CFG.operators.indexOf(symbol) < 0 && !Operators[operator]) { CFG.operators = CFG.operators.replace(']=', symbol + ']='); Operators[operator] = resolver; setIdentifierSyntax(); } else { console.warn('Warning: the \'' + operator + '\' operator is already registered.'); } }, // register a new selector symbol and its related function resolver registerSelector: function(name, rexp, func) { Selectors[name] || (Selectors[name] = { Expression: rexp, Callback: func }); } }; initialize(doc); return Dom; }); package/package.json000644 0000001555 3560116604 011554 0ustar00000000 000000 { "name": "nwsapi", "version": "2.2.2", "description": "Fast CSS Selectors API Engine", "homepage": "http://javascript.nwbox.com/nwsapi/", "main": "./src/nwsapi", "keywords": [ "css", "css3", "css4", "matcher", "selector" ], "licenses": [ { "type": "MIT", "url": "http://javascript.nwbox.com/nwsapi/MIT-LICENSE" } ], "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/nwsapi/issues" }, "repository": { "type": "git", "url": "git://github.com/dperini/nwsapi.git" }, "scripts": { "lint": "eslint ./src/nwsapi.js" } } package/dist/lint.log000644 0000000000 3560116604 011662 0ustar00000000 000000 package/README.md000644 0000012500 3560116604 010535 0ustar00000000 000000 # [NWSAPI](http://dperini.github.io/nwsapi/) Fast CSS Selectors API Engine ![](https://img.shields.io/npm/v/nwsapi.svg?colorB=orange&style=flat) ![](https://img.shields.io/github/tag/dperini/nwsapi.svg?style=flat) ![](https://img.shields.io/npm/dw/nwsapi.svg?style=flat) ![](https://img.shields.io/github/issues/dperini/nwsapi.svg?style=flat) NWSAPI is the development progress of [NWMATCHER](https://github.com/dperini/nwmatcher) aiming at [Selectors Level 4](https://www.w3.org/TR/selectors-4/) conformance. It has been completely reworked to be easily extended and maintained. It is a right-to-left selector parser and compiler written in pure Javascript with no external dependencies. It was initially thought as a cross browser library to improve event delegation and web page scraping in various frameworks but it has become a popular replacement of the native CSS selection and matching functionality in newer browsers and headless environments. It uses [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) to parse CSS selector strings and [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming) to transforms these selector strings into Javascript function resolvers. This process is executed only once for each selector string allowing memoization of the function resolvers and achieving unmatched performances. ## Installation To include NWSAPI in a standard web page: ```html ``` To include NWSAPI in a standard web page and automatically replace the native QSA: ```html ``` To use NWSAPI with Node.js: ``` $ npm install nwsapi ``` NWSAPI 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/CSS4 [Supported selectors](https://github.com/dperini/nwsapi/wiki/CSS-supported-selectors). ## Features and Compliance You can read more about NWSAPI [features and compliance](https://github.com/dperini/nwsapi/wiki/Features-and-compliance) on the wiki. ## API ### DOM Selection #### `ancestor( selector, context, callback )` Returns a reference to the nearest ancestor element matching `selector`, starting at `context`. Returns `null` if no element is found. If `callback` is provided, it is invoked for the matched element. #### `first( selector, context, callback )` Returns a reference to the first element matching `selector`, starting at `context`. Returns `null` if no element matches. If `callback` is provided, it is invoked for the matched element. #### `match( selector, element, callback )` Returns `true` if `element` matches `selector`, starting at `context`; returns `false` otherwise. If `callback` is provided, it is invoked for the matched element. #### `select( selector, context, callback )` Returns an array of all the elements matching `selector`, starting at `context`; returns empty `Array` otherwise. 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`. ### 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`: * `IDS_DUPES`: true - true to allow using multiple elements having the same id, false to disallow * `LIVECACHE`: true - true for caching both results and resolvers, false for caching only resolvers * `MIXEDCASE`: true - true to match tag names case insensitive, false to match using case sensitive * `LOGERRORS`: true - true to print errors and warnings to the console, false to mute both of them ### Examples on extending the basic functionalities #### `configure( { : [ true | false ] } )` Disable logging errors/warnings to console, disallow duplicate ids. Example: ```js NW.Dom.configure( { LOGERRORS: false, IDS_DUPES: false } ); ``` NOTE: NW.Dom.configure() without parameters return the current configuration. #### `registerCombinator( symbol, resolver )` Registers a new symbol and its matching resolver in the combinators table. Example: ```js NW.Dom.registerCombinator( '^', 'e.parentElement' ); ``` #### `registerOperator( symbol, resolver )` Registers a new symbol and its matching resolver in the attribute operators table. Example: ```js NW.Dom.registerOperator( '!=', { p1: '^', p2: '$', p3: 'false' } ); ``` #### `registerSelector( name, rexp, func )` Registers a new selector, the matching RE and the resolver function, in the selectors table. Example: ```js NW.Dom.registerSelector('Controls', /^\:(control)(.*)/i, (function(global) { return function(match, source, mode, callback) { var status = true; source = 'if(/^(button|input|select|textarea)/i.test(e.nodeName)){' + source + '}'; return { 'source': source, 'status': status }; }; })(this)); ```