securityos/node_modules/nwsapi/src/nwsapi.js

1794 lines
64 KiB
JavaScript

/*
* Copyright (C) 2007-2019 Diego Perini
* All rights reserved.
*
* nwsapi.js - Fast CSS Selectors API Engine
*
* Author: Diego Perini <diego.perini at gmail com>
* Version: 2.2.3
* Created: 20070722
* Release: 20230322
*
* 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.3',
doc = global.document,
root = doc.documentElement,
slice = Array.prototype.slice,
HSP = '[\\x20\\t]',
VSP = '[\\r\\n\\f]',
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('^' + WSP + '+|' + WSP + '+$|' + VSP, 'g'),
SplitGroup: RegExp('(\\([^)]*\\)|\\[[^[]*\\]|\\\\.|[^,])+', 'g'),
CommaGroup: RegExp('(\\s*,\\s*)' + NOT.square_enc + NOT.parens_enc, '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?([^\\[]*\\[[^\\]]*\\]|[^\\(]*\\([^\\)]*\\)|[^()]*)\\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-within|focus)\\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,
NONDIGITS: 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;
},
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, i, l, 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 <EOF-token> 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
(Config.NONDIGITS ? '(?=[^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, k = 0, 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) {
++k;
// 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' + '=="' + match[1] + '"' +
')){' + 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 = 'var N' + k + '=e;while(e&&(e=e.previousElementSibling)){' + source + '}e=N' + k + ';';
break;
// *** Adjacent sibling combinator
// E + F (F adiacent sibling of E)
case '+':
match = selector.match(Patterns.adjacent);
source = 'var N' + k + '=e;if(e&&(e=e.previousElementSibling)){' + source + '}e=N' + k + ';';
break;
// *** Descendant combinator
// E F (E ancestor of F)
case '\x09':
case '\x20':
match = selector.match(Patterns.ancestor);
source = 'var N' + k + '=e;while(e&&(e=e.parentElement)){' + source + '}e=N' + k + ';';
break;
// *** Child combinator
// E > F (F children of E)
case '>':
match = selector.match(Patterns.children);
source = 'var N' + k + '=e;if(e&&(e=e.parentElement)){' + source + '}e=N' + k + ';';
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, :focus-within
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-<foo-bar>
// 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;
});