Skip to main content
Module

x/deno_dom/src/dom/nwsapi.js

Browser DOM & HTML parser in Deno
Extremely Popular
Go to Latest
File
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801
// @ts-nocheck/* * 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.0 * Created: 20070722 * Release: 20191102 * * License: * http://javascript.nwbox.com/nwsapi/MIT-LICENSE * Download: * http://javascript.nwbox.com/nwsapi/nwsapi.js */
export default document => { const NW = Factory({ document, DOMException }, "null"); NW.configure({ IDS_DUPES: false, LOGERRORS: false, });
return NW;};
function Factory(global, Export) {
var version = 'nwsapi-2.2.0',
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: '(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: '(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-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_dbl: RegExp('^:(?:' + GROUPS.pseudo_dbl + ')(.*)', 'i'), pseudo_sng: RegExp('^:(?:' + GROUPS.pseudo_sng + ')(.*)', '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', '*': '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 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 { // DOCUMENT_FRAGMENT_NODE (11) if ((e = context.firstElementChild)) { tag = tag.toLowerCase(); if (!(e.nextElementSibling || tag == '*' || e.nodeName.toLowerCase() == tag)) { return slice.call(e[api]('*', tag)); } else { nodes = [ ]; do { if (tag == '*' || e.nodeName.toLowerCase() == 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.nodeName; 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.nodeName == 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').nodeName == '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 '(?=[^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 + '?' + '(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 nodeName 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, false);
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, not) {
// N is the negation pseudo-class flag // D is the default inverted negation flag var a, b, n, f, i, l, name, nested, NS, N = not ? '!' : '', D = not ? '' : '!', 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, ' ');
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.nodeName' + (Config.MIXEDCASE || hasMixedCaseTagNames(doc) ? '.toLowerCase()=="' + 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("' + name + '")') : !match[4] && ATTR_STD_OPS[match[2]] && match[2] != '~=' ? 'e.getAttribute("' + name + '")==""' : '(/' + test.p1 + match[4] + test.p2 + '/' + type + ').test(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.nodeName;' + 'n=e;while((n=n.nextElementSibling)&&n.nodeName!=o);if(!n){' + 'n=e;while((n=n.previousElementSibling)&&n.nodeName!=o);}if(' + D + 'n){' + source + '}'; break; case 'last-of-type': source = 'n=e;o=e.nodeName;while((n=n.nextElementSibling)&&n.nodeName!=o);if(' + D + 'n){' + source + '}'; break; case 'first-of-type': source = 'n=e;o=e.nodeName;while((n=n.previousElementSibling)&&n.nodeName!=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.nodeName;' + 'while((n=n.' + test + 'ElementSibling)&&n.nodeName!=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 // :matches( s1, [ s2, ... ]), :not( s1, [ s2, ... ]) else if ((match = selector.match(Patterns.logicalsel))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'matches': if (not === true || nested === true) { emit(':matches() pseudo-class cannot be nested'); } nested = true; expr = match[2].replace(REX.CommaGroup, ',').replace(REX.TrimSpaces, ''); // check nested compound selectors s1, s2 expr = match[2].match(REX.SplitGroup); for (i = 0, l = expr.length; l > i; ++i) { expr[i] = expr[i].replace(REX.TrimSpaces, ''); source = 'if(s.match("' + expr[i].replace(/\x22/g, '\\"') + '",e)){' + source + '}'; } break; case 'not': if (not === true || nested === true) { emit(':not() pseudo-class cannot be nested'); } expr = match[2].replace(REX.CommaGroup, ',').replace(REX.TrimSpaces, ''); // check nested compound selectors s1, s2 expr = match[2].match(REX.SplitGroup); for (i = 0, l = expr.length; l > i; ++i) { expr[i] = expr[i].replace(REX.TrimSpaces, ''); source = compileSelector(expr[i], source, false, callback, true); } 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 // :link, :visited, :target else if ((match = selector.match(Patterns.locationpc))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'link': source = 'if(' + N + '(/^a|area|link$/i.test(e.nodeName)&&e.hasAttribute("href"))){' + source + '}'; break; case 'visited': source = 'if(' + N + '(/^a|area|link$/i.test(e.nodeName)&&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.nodeName))&&"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.nodeName))&&"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.nodeName)&&(e.readOnly||e.disabled))||' + '("|password|text|".includes("|"+e.type+"|")&&e.readOnly)' + ')){' + source + '}'; break; case 'read-write': source = 'if(' + N + '(' + '((/^textarea$/i.test(e.nodeName)&&!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.nodeName))&&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.nodeName))&&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.nodeName)&&' + '("|radio|checkbox|".includes("|"+e.type+"|")&&e.checked)||' + '(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' + ')){' + source + '}'; break; case 'indeterminate': source = 'if(' + N + '(/^progress$/i.test(e.nodeName)&&!e.hasAttribute("value"))||' + '(/^input$/i.test(e.nodeName)&&("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.nodeName)&&e.required)' + '){' + source + '}'; break; case 'optional': source = 'if(' + N + '(/^input|select|textarea$/i.test(e.nodeName)&&!e.required)' + '){' + source + '}'; break; case 'invalid': source = 'if(' + N + '((' + '(/^form$/i.test(e.nodeName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&!e.checkValidity())||' + '(/^fieldset$/i.test(e.nodeName)&&s.first(":invalid",e))' + '){' + source + '}'; break; case 'valid': source = 'if(' + N + '((' + '(/^form$/i.test(e.nodeName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&e.checkValidity())||' + '(/^fieldset$/i.test(e.nodeName)&&s.first(":valid",e))' + '){' + source + '}'; break; case 'in-range': source = 'if(' + N + '(/^input$/i.test(e.nodeName))&&' + '(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.nodeName))&&' + '(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 else if ((match = selector.match(Patterns.pseudo_sng))) { source = 'if(' + D + '(e.nodeType==1)){' + source + '}'; }
// allow pseudo-elements starting with double colon (::) // ::after, ::before, ::marker, ::placeholder, ::inactive-selection, ::selection, ::-webkit-<foo-bar> else if ((match = selector.match(Patterns.pseudo_dbl))) { source = 'if(' + D + '(e.nodeType==1)){' + 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;
} // 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.nodeName.toLowerCase() + (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, nodes = [ ]; 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.nodeName)) { 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;
};