UNPKG

snizzle

Version:

A pure-JavaScript fast, CSS selector engine program to be easily select DOM-Elements

1,606 lines (1,417 loc) 52 kB
/** * Snizzle is a advance feature-rich CSS Selector Engine v1.8.0 * https://github.com/jqrony/snizzle * * @releases +10 releases * @version 1.8.0 * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * @license MIT * https://github.com/jqrony/snizzle/blob/main/LICENSE * * @author Shahzada Modassir <codingmodassir@gmail.com> * Date: 28 November 10:03 GMT+0530 (India Standard Time) */ export default (function(window) { /** * Inject [use strict] Mode * ------------------------ * Throw ReferenceError when pass undeclare variables */ "use strict"; var i, support, unique, Expr, getText, isXML, tokenize, select, contains, copy, flat, _snizzle, access, doAdjust, compile, // Instance-specific data expando = "Snizzle" + 1 * Date.now(), preferredDoc = window.document, // The current version of Snizzle version = "1.8.0", // Instance array-obj methods arr = [], concat = arr.concat, push = arr.push, _flat = arr.flat, isFlat = !!_flat, slice = arr.slice, indexOf = arr.indexOf, hasOwn = ({}).hasOwnProperty, // Instance iterator and toStringTag iterator = Symbol.iterator, stringTag = Symbol.toStringTag, // Local document vars setDocument, document, docElem, pseudoHooks, documentIsHTML, // Define jsconsole Fix minifier bugs jsconsole = window.console || console, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }, // Used for QSA Elements // Selecting all elements using context.querySelectorAll() // Returns NodeList[] selectAll = function(selector, context) { return (context || document).querySelectorAll(selector); }, // Used for Snizzle support // use checks() // checks a global window with document for Snizzle support // throw new Error checks = function() { if (!preferredDoc) { throw new Error("Snizzle requires window with document"); } }, // Regular expressions sources // HTML Singleton TAGS with no closing TAG nctags = "img|input|meta|area|keygen|base|link|br|hr|source|col|param|track|wbr|embed|"+ "command", // Input type value used for :pseudos types = "radio|email|number|checkbox|file|tel|password|image|search|color|range|url", // Collapse noeditable tags to Singleton tags // Define none-editable HTML TAG noeditable = nctags + "|script|style|audio|video|head|button|textarea|select|details|"+ "optgroup", notypes = "button|datetime\\-local|checkbox|color|file|radio|image|reset|submit|date|"+ "month|range|time|hidden|week", // Self booleans attributes booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|"+ "ismap|loop|multiple|open|readonly|required|scoped|muted", themes = "theme-color|apple-mobile-web-app-status-bar-style|msapplication-TileColor|" + "msapplication-navbutton-color", // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] // or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", // Pseudos selectors: https://www.w3.org/TR/selectors/#pseudo-classes pseudos = ":(" + identifier + ")(?:\\((" + // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // 1. TAG (capture 1), 2. :nth-child (capture 2), 3. Slash (capture 3) xpOrdDesc = "((-?\\w)+(\\["+ whitespace + "*(\\d+)" + whitespace + "*\\])*(\\/|))+", xpAxises = "(?:ancestor|descendant)(\\-(or\\-self))?|((?:following|preceding))(\\-(sibling))?"+ "\\:\\:", // Regular expressions // https://www.w3.org/TR/CSS2/text.html#egbidiwscollapse // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp(whitespace + "+", "g"), rcombinators = new RegExp("^" + whitespace+ "*([>+^~<]|" +whitespace+ ")" + whitespace + "*"), rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), ridentifier = new RegExp("^" + identifier + "$"), rthemes = new RegExp("^(?:" + themes + ")$", "i"), rnative = /^[^{]+\{\s*\[native \w/, rinputs = /^(?:input|select|textarea|button)/i, rjson = /^\bapplication\/json\b$/i, rmodule = /^\bmodule\b$/i, rvalid = /^(?:input|select|form|textarea)$/i, rxpnth = /\[(\d+)]/g, rhtml = /HTML$/i, rheader = /^h[1-6]$/i, rslash = /\//g, rtypes = /^|\|(?:(date|time\\-local)|time|month|week)+/g, rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"), rnctags = new RegExp("^(?:" + nctags + ")$", "i"), rnotype = new RegExp("^(?:" + notypes + ")$"), // TAG testing for Audio or Video Element rplayable = /^(?:audio|video)$/i, // nodeType testing Element or Document or DocumentFragment rprimaryNodeType = /^(?:1|9|11)$/, rdashAlpha = /-([a-z])/g, rXpOrdDesc = new RegExp("^" + xpOrdDesc), rXpAxises = new RegExp("^" + xpAxises), // Input type testing Element radio or checkbox rcheckable = /^(?:radio|checkbox)$/i, // Used for :readonly pseudo rReadableType = new RegExp(notypes.replace(rtypes, "")), // nodeType testing TextNode or CDATASection rsecondryNodeType = /^(?:3|4)$/, rtrimComma = new RegExp(whitespace + "*," + whitespace + "*$"), // None animation => (none 0s ease 0s 1 normal none running) rnoneanimation = /^(none)\s*(0s)\s*(ease)\s*(0s).*(running)/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, // None editable TAGS rnoeditable = new RegExp("^(?:" + noeditable + ")$", "i"), // Left triming //* or / or // And right triming only / rxptrim = new RegExp("^[\\/]{1,2}\\**|\/$" + whitespace + "*", "g"), // exprMatcher = { "ID": new RegExp("^#(" + identifier + ")"), "CLASS": new RegExp("^\\.(" + identifier + ")"), "TAG": new RegExp("^(" + identifier + "|[*])"), "ATTR": new RegExp("^" + attributes), "PSEUDO": new RegExp("^" + pseudos), "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i"), "XPATH": /^([\/]{1,2}[^\/].*?(?:\/\w|\]|\],|[^, ]+,|))(\s|$)/ }; // Checks Snizzle support or block code executation silently try {checks()} catch(e) {jsconsole.error(e.message); return} /** * Internal specialFunction * ------------------------ * specialFunction Mark a function for special use by Snizzle */ function specialFunction(fn) { fn[expando]=true; return fn; } /** * Create trueCond single use method * --------------------------------- * Check true condition with String Boolean 'true' parse true * @returns {Boolean} true/false */ function trueCond(editable) { return editable==="true" ? true : false; } flat = function(array) { return isFlat ? _flat.call(array) : concat.apply([], array); }; /** * Create Snizzle external public API * ---------------------------------- */ function Snizzle(selector, context, results, seed) { var match, nodeList, matched, elem, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if (typeof selector!== "string" || !selector || (nodeType!==1 && nodeType!==9 && nodeType!==11)) { return document; } // Try to shortcut find operations (as opposed) in HTML documents if (!seed) { setDocument(context); context = context || document; (match=documentIsHTML && (nodeType!==11 && rquickExpr.exec(selector))); // QSA Support // Take advantage of querySelectorAll if support QSA method. if (support.qsa && match) { nodeList = selectAll(match[0], context); push.apply(results, match[1] ? [(nodeList[0] || selectAll(match[0], newContext)[0])||[]] : nodeList); return flat(results); } // Others Support // If the QSA not support, try using a "get*By*" DOM method if (match && (matched=match[1])) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID // *Document context elem=nodeType===9 ? (context.getElementById(matched)) : // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID // *Element context newContext && newContext.getElementById(matched); elem && results.push(elem); return results; } else if (match) { // Type/TAG Selector // Support: Chrome, IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of TAG elem=match[2] ? context.getElementsByTagName(selector) : // CLASS Selector // Support: Chrome, IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of CLASS match[3] && context.getElementsByClassName(match[3]); push.apply(results, elem); return results; } } // All others complex selectors return select(selector.replace(rtrim, "$1"), context, results, seed); } // Expose support vars for convenience support = Snizzle.support = {}; // Expose version vars for convenience Snizzle.version = version; // Add toStringTag and iterator method if (typeof Symbol==="function") { Snizzle[stringTag] = Snizzle.name; Snizzle[iterator] = arr[iterator]; } /** * Internal assert used for support * -------------------------------- * Support testing using an element * @param {Function} fn Passed the created element and returns a boolean result */ function assert(fn) { var elem = document.createElement("fieldset"); try { return !!fn(elem); } catch(e) { return false; } finally { elem.parentNode && elem.parentNode.removeChild(elem); elem = null; } } /** * @param {Array|Object} results An copyable array object * @returns newly-cloned Array */ copy = Snizzle.copy = function(results) { var clone = [], i = 0, len = results.length; for(; i < len; i++) clone.push(results[i]); return flat(clone); }; /** * Sets document-related variables once based on the current document * @returns {Object} Returns the current document */ setDocument = Snizzle.setDocument = function(node) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if (doc===document||doc.nodeType!==9||!doc.documentElement) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML(document); if (preferredDoc!==doc && (subWindow=document.defaultView) && subWindow.top!==subWindow) { // Support: IE 11, Edge subWindow.addEventListener && subWindow.addEventListener("unload", unloadHandler, false), // Support: IE 9 - 10 only subWindow.attachEvent && subWindow.attachEvent("onunload", unloadHandler); } /* * Create supports of ATTR or CLASS or TAG or ID or SCOPE * ------------------------------------------------------ */ support.getElementsByClassName=rnative.test(document.getElementsByClassName); support.qsa=rnative.test(document.querySelectorAll); support.dataset=assert(function(el) { el.dataset["snizzle"] = expando; return !!(el.dataset["snizzle"] || el.dataset); }); support.scope=assert(function(el) { docElem.appendChild(el).appendChild(document.createElement("div")); return init(el, "querySelectorAll", ":scope fieldset div").length; }); support.attributes=assert(function(el) { el.className = "s"; return !el.getAttribute("className"); }); support.children=assert(function(el) { el.appendChild(document.createElement("div")); return el.children && !!el.children.length; }); support.getElementsByTagName=assert(function(el) { el.appendChild(document.createComment("")); return !el.getElementsByTagName("*").length; }); support.xPathExpression = document.evaluate && rnative.test(document.evaluate); support.getById=assert(function(el) { docElem.appendChild(el).id = expando; return !document.getElementsByName||!document.getElementsByName(expando).length; }); /* * Assign filter in ID method * ------------------------------------------------------------------ */ Expr.filter["ID"] = specialFunction(function(id) { return access(function(elem) { return (elem.id || attrVal(elem, "id", "getAttributeNode")) === id; }); }); /* * Assign find in CLASS method * ------------------------------------------------------------------ */ Expr.find["CLASS"] = function(className, context) { if (documentIsHTML) { return init(context, "getElementsByClassName", className) || selectAll(className, context); } }; /* * Assign find in CHILDREN method * ------------------------------------------------------------------ */ Expr.find["CHILDREN"] = !support.children ? function(context) { return slice.call(context.children); } : function(context) { var results = context.childNodes; return access(function(elem) {return elem.nodeType===1})(results); }; /* * Assign find in TAG method * ------------------------------------------------------------------ */ Expr.find["TAG"] = support.getElementsByTagName ? function(tag, context) { return init(context, "getElementsByTagName", tag) || selectAll(tag, context); } : function(tag, context) { var tmp = [], // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes results = context.getElementsByTagName(tag); // HANDLE: If tag is equal to "*" if (tag==="*") { return access(function(elem) { elem.nodeType===1 && tmp.push(elem); })(results), tmp; } return results; }; /* * Assign find in ELEMENTS method * ------------------------------------------------------------------ */ Expr.find["ELEMENTS"] = function(elem) { return slice.call(Expr.find["TAG"]("*", elem)); }; /* * Assign find in ID method * ------------------------------------------------------------------ */ Expr.find["ID"] = function(id, context) { var elems, i, elem = documentIsHTML && init(context, "getElementById", id); if (elem) { // Verify the id attribute if (attrVal(elem, "id", "getAttributeNode")===id) { return [elem]; } // Fall back on getElementsByName elems = elem.getElementsByName(id); i = 0; while((elem = elems[i++])) { // Verify the id attribute if (attrVal(elem, "id", "getAttributeNode")===id) { return [elem]; } } return []; } }; Expr.find["ID"] = support.getById ? function(id, context) { var elem = documentIsHTML && init(context, "getElementById", id); return elem ? [elem] : []; } : Expr.find["ID"]; /* Contains -------------------------------------------------------------------*/ hasCompare = rnative.test(document.compaireDocumentPosition); /* * Create contains method * ---------------------- * Element contains another Purposefully self-exclusive * As in, an element does not contain itself */ contains = hasCompare || rnative.test(docElem.contains) ? function(context, elem) { var html = context.nodeType === 9 ? context.documentElement : context, pnode = elem && elem.parentNode, compare = context.compaireDocumentPosition; return html===pnode||!!(pnode && pnode.nodeType===1 && (init(context, "contains", elem) || compare(elem)) ); } : function(context, elem) { if (elem) { while((elem=elem.parentNode)) { if (elem===context) { return true; } } } return false; }; return document; }; /** * Create Internal Private init method * ----------------------------------- * Check constructor or property and Initialize it with arg */ function init(owner, constructor, arg) { return owner[constructor] && owner[constructor](arg); } /** * Create expr matches public API * ------------------------------ * @param {String} expr An String CSS Selectors * @param {Element} elements HTML list elements * @returns matched expr selectors HTML Elements with array */ Snizzle.matches = function(expr, elements) { return Snizzle(expr, null, null, elements); }; /** * SET document if vars needed * --------------------------- * IE/Edge sometimes throw a "Permission denied" error when * strict-comparing two documents; shallow comparisons work. * eslint-disable-next-line eqeqeq */ Snizzle.contains=function(context, elem) { (context.ownerDocument||context)!==document&&setDocument(context); return contains(context, elem); }; Snizzle.attr = function(elem, name) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when // strict-comparing two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ((elem.ownerDocument||elem)!==document) { setDocument(elem); } name = (name || "").toLowerCase(); var fn = Expr.attrHandle[name], // Don't get fooled by Object.prototype properties val = fn && hasOwn.call(Expr.attrHandle, name) ? fn(elem) : undefined; return val!==undefined ? val : support.attributes||!documentIsHTML ? elem.getAttribute(name) : (val=elem.getAttributeNode(elem)) && val.specified ? val.value : null; }; Snizzle.error = function(message) { throw new SyntaxError("Unrecognized expression: " + message); }; /** * Create unique public API * ------------------------ * Remove the all duplicates value of array-object * @returns A unique Array */ unique = Snizzle.unique = function(results) { var i = 0, cloneArray, len = results.length; results = results || []; cloneArray = copy(results); // getting all content length results.length = 0; results.splice(0, len); for(; i < len; i++) { if (slice.call(cloneArray).indexOf(cloneArray[i])===i) { results.push(cloneArray[i]); } } return results; }; /** * One time assignments uniqueSort * ------------------------------- * Document sorting and removing duplicates and * @param {ArrayLike} results An ArrayLike array * @returns A sorted unique Array */ Snizzle.uniqueSort = function(results) { return unique(results).slice(0).sort(); }; // Utility function for retrieving the text value of an array of DOM nodes getText = Snizzle.getText = function(elem) { var text = "", nodeType = elem.nodeType; // Handle none-element object if (!nodeType) { // If no nodeType, this is expected to be an array access(function(elem) {text+=getText(elem)})(elem); } else if (rprimaryNodeType.test(nodeType)) { // Handle Element or Document or DocumentFragment // Use textContent for elements if (typeof elem.textContent==="string") { return elem.textContent; } else { // Otherwise Traverse its children for(elem=elem.firstChild; elem; elem=elem.nextSibling) { text += getText(elem); } } } else if (rsecondryNodeType.test(nodeType)) { // Handle TextNode or CDATASection return elem.nodeValue; } // Do not include comment or processing instruction nodes return text; }; /** * Create Internal attrVal Private method * -------------------------------------- * Returns the custom AttributeNode value or Attribute value * attrVal can be check exists or none-exists attr * @returns Output: Due String or Boolean */ function attrVal(elem, attr, getProp) { var value = init(elem, getProp || "getAttribute", attr); return value && value.nodeType === 2 ? value.nodeValue : value; } // Used by camelCase as callback to replace() function fcamelCase(_all, letter) { return letter.toUpperCase(); } // Convert dashed to camelCase; Used by aria :pseudo // Support: IE <=9 - 11, Edge 12 - 15 // Microsoft forgot to hump their vendor prefix (#9572) function camelCase(string) { return (string||"").replace(rdashAlpha, fcamelCase); } /** * Create Internal/External isXML method * ------------------------------------- * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True if elem is non-HTML XML node */ isXML = Snizzle.isXML = function(elem) { var namespace = elem && elem.namespaceURI, docElem = elem && (elem.ownerDocument||elem).documentElement; return !rhtml.test(namespace||docElem && docElem.nodeName||"HTML"); }; /** * Create Internal/External access method * ------------------------------------ * Bind with outerMap function * Execute a callback for every element in the matched set. */ access = Snizzle.access = function(isMap, fn) { return function(obj) { var i=0, len=obj.length, value, ret=[]; if (typeof isMap==="function") { fn = fn || isMap; isMap = undefined; } for(; i < len; i++) { value = fn(obj[i], i, obj, length, []); value && (isMap ? ret.push(value) : ret.push(obj[i])); } return unique(flat(ret)); }; }; /** * Create addCombinators Internal Method * ------------------------------------- * Handler Combinators [>+^~<] with compaire element. */ function addCombinators(src) { var tmp = [], dir = src.dir, type = src.type, _with = src.with, once = !!src.once; return access(true, function(elem) { if (once || _with) { return once ? elem[dir] : Expr[_with][type](elem); } else { while((elem = elem[dir]) && elem.nodeType===1) { tmp.push(elem); } return tmp; } }); } /** * Create multi External :pseudos Handler Hooks * -------------------------------------------- * pseudoHooks to be used Boolean or Inline properties for pseudo */ pseudoHooks = Snizzle.pseudoHooks = { /** * Create External positionalPseudo Handler * ---------------------------------------- * Returns a function to use in pseudos for :eq, :lt, :gt etc. */ positionalPseudo: function(fn) { return function(results) { var j, matches = [], matchesIndex = fn([], results.length, results), i = matchesIndex.length; while(i--) { if (results[(j=matchesIndex[i])]) { matches[i] = results[j]; } } return matches; }; }, /** * Create External inputOrButtonPseudo Handler * ------------------------------------------- * Returns a function to use in pseudos for input or button :type/:button */ inputOrButtonPseudo: function(type, isButton) { var tag = isButton ? "button" : "input"; return access(function(elem) { return (nodeName(elem)===tag||!!isButton && nodeName(elem)==="input")&&elem.type===type; }); }, /** * Create External hiddenPseudo Handler * ------------------------------------ * Returns a function to use in pseudos for :visible/:hidden */ hiddenPseudo: function(hidden) { return access(function(elem) { var visibility = getStyle(elem, "visibility"), display = getStyle(elem, "display"); return (visibility==="hidden"||display==="none"||elem.hidden)===hidden; }); }, /** * Create External disabledPseudo Handler * -------------------------------------- * Returns a function to use in Snizzle pseudos for :enabled/:disabled * @param {Boolean} disabled true for :disabled; false for :enabled */ disabledPseudo: function(disabled) { // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return access(function(elem) { // Only certain elements can match :enabled or :disabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled if (("form" in elem) && // * listed form-associated elements in a disabled fieldset // * option elements in a disabled optgroup // All such elements have a "form" property. elem.parentNode && elem.disabled===false) { // Option elements defer to a parent optgroup if present if ("label" in elem) { return ("label" in elem.parentNode) ? elem.parentNode.disabled===disabled :elem.disabled===disabled; } // Support: IE 6 - 11 // Use the isDisabled shortcut property to // check for disabled fieldset ancestors return elem.disabled === disabled || elem.disabled !== !disabled; } return (("label" in elem)||("form" in elem) && elem.disabled===disabled)||false; }); }, /** * Create External scriptPseudo Handler * ------------------------------------ * Returns a function to use in pseudos for :module/:json :intscript/:extscript */ scriptPseudo: function(external) { return access(function(elem) { if (typeof external!=="boolean") { return external.test(elem.type||attrVal(elem, "type")); } else { return nodeName(elem)==="script" && elem.hasAttribute("src")===external; } }); }, /** * Create External formPseudo Handler * ---------------------------------- * Returns a function to use in pseudos for :get/:post */ formPseudo: function(method) { return access(function(elem) { return nodeName(elem)==="form"&&((elem.method||attrVal(elem, "method"))===method); }); }, /** * Create External rangePseudo Handler * ----------------------------------- * Returns a function to use in pseudos for :in-range or :out-of-range */ rangePseudo: function(isOutRange) { return access(function(elem) { var isNum, min = elem.min, max = elem.max, value = nodeName(elem)==="input" && elem.type==="number" && +elem.value; isNum = function(data) { return data !== "" && typeof data !== "boolean" && !isNaN(data); }; return isNum(value) && (((isNum(min) ? +min <= value : true) && (isNum(max) ? +max >= value : true))===!isOutRange); }); } }; /** * Create getStyle Internal Method * ------------------------------- * Returns elem of computed or inline style using prop */ function getStyle(elem, prop) { var value = elem.style[prop] || window.getComputedStyle(elem)[prop]; return parseInt(value) || value; } /** * Create Expr and selectors source * -------------------------------- * Assign multi objects methods and Handlers Like :pseudo, preFilter, * filter, relative, combinators and createPseudo, find etc. Methods. */ Expr = Snizzle.selectors = { // createPseudo to create arg based markable :pseudo createPseudo: specialFunction, combinators: {}, attrHandle: {}, find: {}, __external: {}, match: exprMatcher, // Can be adjusted cacheLen by the user cacheLen: 80, extendPseudo: extend, relative: { "$": {dir: "previousElementSibling", once: true}, " ": {type: "ELEMENTS", with: "find"}, ">": {type: "CHILDREN", with: "find"}, "~": {dir: "nextElementSibling"}, "^": {dir: "parentNode"}, "<": {dir: "parentNode", once: true}, "?": {dir: "previousElementSibling"}, "+": {dir: "nextElementSibling", once: true}, }, preFilter: { "XPATH": function(match) { // Trim or slice rComma like expression(,) to expression match[1] = (match[1]).replace(rtrimComma, ""); // Move the given value to match[3] and trim whitespaces match[0] = (match[1] || match[0].trim()).toLowerCase(); return match.slice(0, 2); }, "CLASS": function(match) { return match.slice(0, 2); }, "ATTR": function(match) { // Move the given value to match[3] whether quoted or unquoted match[3] = (match[3] || match[4] || match[5] || ""); return match.slice(0, 4); }, "TAG": function(match) { return match.slice(0, 2); }, "ID": function(match) { return match.slice(0, 2); }, "CHILD": function(match) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); match[6] = match[0].toLowerCase(); return match; }, "PSEUDO": function(match) { // Accept quoted ['|"] arguments as-is if (match[3]) { match[2] = match[4] || match[5] || ""; } // Return only captures needed by the pseudo filter method (type and argument) return match.slice(0, 3); } }, filter: { // Support: Chrome, FireFox, Safari 3.1, Edge, IE // https://developer.mozilla.org/en-US/docs/Web/XPath "XPATH": specialFunction(function(xPathExpression) { return access(function(elem) { var results, i, tmp=[], snapType =XPathResult.ORDERED_NODE_SNAPSHOT_TYPE; results = document.evaluate(xPathExpression, elem, null, snapType, null); i = results.snapshotLength; // Iterate through the matched elements while(i--) { // Let's push with each matched element tmp.push(results.snapshotItem(i)); } return indexOf.call(tmp, elem) > -1; }); }), "TAG": specialFunction(function(tagName) { tagName = (tagName + "").toLowerCase(); return access(function(elem) { return tagName==="*" ? true : nodeName(elem) === tagName; }); }), "CLASS": specialFunction(function(className) { return access(function(elem) { var pattern; return (pattern=new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + ")|$")) && pattern.test( elem.className || attrVal(elem, "class")||"" ); }); }), "ATTR": specialFunction(function(name, operator, check) { return access(function(elem) { var result = Snizzle.attr(elem, name) || attrVal(elem, name); if (result==null) { return operator==="!="; } if (!operator) { return !!result; } // toString result result += ""; /* eslint-disable max-len */ return operator==="=" ? result===check : operator==="^=" ? check && result.indexOf(check)===0 : operator==="*=" ? check && result.indexOf(check) >-1 : operator==="!=" ? result!==check : operator==="$=" ? check && result.slice(-check.length)===check : operator==="~=" ? (" " + result.replace(rwhitespace, " ") + " ").indexOf(check) >-1 : operator==="|=" ? result===check||result.slice(0, check.length+1)===check + "-" : false; /* eslint-enable max-len */ }); }), "CHILD": specialFunction(function(type, what, _arg, first, last, expr) { /* matches from exprMatcher["CHILD"] * Deal the CSS3 pseudo 01 :nth-last-of-type(n) 02 :nth-of-type(n) 03 :first-of-type 04 :first-child 05 :last-child 06 :last-of-type 07 :nth-child(n) 08 :only-child 09 :only-of-type 10 :nth-last-child(n) */ // For QSA Support // Take advantage of querySelectorAll if support QSA method. // TODO: Need to improve and enhance CHILD Handler selector return support.qsa ? access(function(elem) { return indexOf.call(slice.call(selectAll(expr)), elem) > -1; }) : // Otherwise always run // TODO: Need to improve and enhance CHILD Handler selector access(function(elem) { }); }), "PSEUDO": specialFunction(function(pseudo, arguemnt) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var fn = combine(pseudo) || Snizzle.error("Unsupport pseudo: Compilation failed '" + pseudo + "' not supported!"); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Snizzle does return fn[expando] ? fn(arguemnt) : fn; }) }, _pseudos: { "required": access(function(elem) { return nodeName(elem)==="input" && (!!elem.required || attrVal(elem, "required")); }), "link": access(function(elem) { return nodeName(elem)==="a" && attrVal(elem, "href") != null; }), "out-of-range": pseudoHooks.rangePseudo(true), "in-range": pseudoHooks.rangePseudo(false), "active": access(function(elem) { return elem.activeElement; }), "indeterminate": access(function(elem) { return nodeName(elem)==="input" && rcheckable.test(elem.type) && !!elem.indeterminate; }), "dir": specialFunction(function(expression) { return access(function(elem) { var dir = elem.dir || attrVal(elem, "dir"); return dir && dir === expression; }); }), "model": access(function(elem) { return nodeName(elem)==="dialog" && elem.open===true; }), "exitscreen": access(function(elem) { return !(elem.fullscreen || elem.fullscreenElement); }), "fullscreen": access(function(elem) { return !!(elem.fullscreen || elem.fullscreenElement); }), "muted": access(function(elem) { return rplayable.test(nodeName(elem)) && !!elem.muted; }), "autoplay": access(function(elem) { return rplayable.test(nodeName(elem)) && !!elem.autoplay; }), "paused": access(function(elem) { return rplayable.test(nodeName(elem)) && !!elem.paused; }), "playing": access(function(elem) { return rplayable.test(nodeName(elem)) && !elem.paused; }), "default": function(elem) { return Expr.pseudos.checked(elem); }, "optional": access(function(elem) { return nodeName(elem)==="input" && (!elem.required || attrVal(elem, "required")==null); }), "valid": access(function(elem) { return rvalid.test(nodeName(elem)) && elem.checkValidity && elem.checkValidity(); }), "invalid": access(function(elem) { return rvalid.test(nodeName(elem)) && elem.checkValidity && !elem.checkValidity(); }), "readonly": access(function(elem) { return nodeName(elem)==="input" && !rReadableType.test(elem.type) && !!elem.readOnly; }) }, pseudos: { "theme": access(function(elem) { return nodeName(elem)==="meta" && rthemes.test(elem.name||attrVal(elem, "name")); }), "not": specialFunction(function(selector) { var target = Snizzle.matches(selector); return access(function(elem) { return indexOf.call(target, elem) === -1; }) }), "has": specialFunction(function(selector) { var target = Snizzle.matches(selector); return access(function(elem) { return indexOf.call(target, elem) > -1; }); }), "filter": specialFunction(function(selector) { var target = Snizzle.matches(selector); return access(function(elem) { return indexOf.call(target, elem) > -1; }); }), "contains": specialFunction(function(text) { return access(function(elem) { return (elem.textContent||getText(elem)).indexOf(text) > -1; }); }), "icontains": specialFunction(function(text) { return access(function(elem) { return ( elem.textContent || elem.innerText || getText(elem) || "" ).toLowerCase().indexOf((text + "").toLowerCase()) > -1; }); }), "rcontains": specialFunction(function(regex) { return access(function(elem) { regex = new RegExp(regex); return regex.test(elem.textContent||getText(elem)); }); }), "ircontains": specialFunction(function(regex) { return access(function(elem) { regex = new RegExp(regex, "i"); return regex.test(elem.textContent||getText(elem)); }); }), // Miscellaneous "target": access(function(elem) { var hash = window.location && window.location.hash; return hash && hash.slice(1) === elem.id; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": specialFunction(function(lang) { // lang value must be a valid identifier if (!ridentifier.test(lang || "")) { Snizzle.error("Unsupported lang: " + lang); } // change case lang toLowerCase lang = (lang||"").toLowerCase(); return access(function(elem) { do { var elemLang; if ((elemLang=documentIsHTML ? elem.lang : attrVal(elem, "xml:lang")||attrVal(elem, "lang"))) { // change case elemLang toLowerCase elemLang = elemLang.toLowerCase(); return elemLang===lang || elemLang.indexOf(lang + "-")===0; } } while((elem = elem.parentNode) && elem.nodeType === 1); return false; }); }), /* Boolean and Inline properties */ "intscript": pseudoHooks.scriptPseudo(false), "disabled": pseudoHooks.disabledPseudo(true), "post": pseudoHooks.formPseudo("POST"), "visible": pseudoHooks.hiddenPseudo(false), "json": pseudoHooks.scriptPseudo(rjson), "get": pseudoHooks.formPseudo("GET"), "hidden": pseudoHooks.hiddenPseudo(true), "enabled": pseudoHooks.disabledPseudo(false), "extscript": pseudoHooks.scriptPseudo(true), "module": pseudoHooks.scriptPseudo(rmodule), /* end */ "inline": access(function(elem) { return rnctags.test(nodeName(elem)); }), "context": function(context) { return flat([context]); }, "root": access(function(elem) { return elem === docElem; }), "focus": access(function(elem) { return elem===elem.activeElement && (!document.hasFocus||document.hasFocus()) && !!(elem.type||elem.href||~elem.tabIndex); }), "checked": access(function(elem) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = Snizzle.nodeName(elem); return (nodeName==="input" && elem.checked) || (nodeName==="option" && !!elem.selected); }), "offset": access(function(elem) { return getStyle(elem, "position")!=="static"||elem===docElem; }), "selected": access(function(elem) { // Accessing this property makes selected-by-default // options in Safari work properly // eslint-disable-next-line no-unused-expressions elem.parentNode && elem.parentNode.selectedIndex; return elem.selected === true; }), "editable": access(function(elem) { return !rnoeditable.test(nodeName(elem)) && trueCond( (elem.contentEditable||attrVal(elem, "contenteditable")) ); }), "writable": access(function(elem) { var writable = !(elem.readOnly||elem.disabled); return writable && (nodeName(elem)==="textarea" || nodeName(elem)==="input"&&!rnotype.test(elem.type)); }), "viewport": access(function(elem) { return nodeName(elem)==="meta" && ((elem.name || attrVal(elem, "name"))==="viewport"); }), "parent": function(seed) { return access(function(elem) { return indexOf.call(Expr.pseudos.empty(seed), elem)===-1; })(seed); }, // Contents "empty": access(function(elem) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text:3; cdata:4; Clazzer ref:5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for(elem=elem.firstChild; elem; elem=elem.nextSibling) { if (elem.nodeType < 6) { return false; } } return true; }), // Element/input types and Headers "header": access(function(elem) { return rheader.test(elem.nodeName || nodeName(elem)); }), "input": access(function(elem) { return rinputs.test(elem.nodeName || nodeName(elem)); }), "button": access(function(elem) { var nodeName = Snizzle.nodeName(elem); return (nodeName==="button")||(nodeName==="input" && elem.type==="button"); }), "text": access(function(elem) { var attr; return nodeName(elem)==="input" && elem.type==="text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with type==="text" ((attr=attrVal(elem, "type")) != null || attr.toLowerCase() === "text"); }), "name": specialFunction(function(name) { return access(function(elem) { return (elem.name||attrVal(elem, "name", "getAttributeNode")) === name; }); }), "animated": access(function(elem) { return nodeName(elem)==="marquee" || !rnoneanimation.test(getStyle(elem, "animation")); }), // Position-in-collection pseudos "eq": specialFunction(function(i) { return pseudoHooks.positionalPseudo(function(_, length) { return [i < 0 ? i + length : i]; }); }), "first": pseudoHooks.positionalPseudo(function() { return [ 0 ]; }), "last": pseudoHooks.positionalPseudo(function(_, length) { return [ length - 1 ]; }), "center": pseudoHooks.positionalPseudo(function(_, length) { var remainder = length % 2, i = length >= 3 && Math.ceil(length / 2) - 1; return [i, !remainder && i !== false && i + 1]; }), "skip": specialFunction(function(i) { return access(function(_elem, j) { return !(j % (i + 1)); }); }), "odd": access(function(_elem, i) { return i % 2; }), "even": access(function(_elem, i) { return (i + 1) % 2; }), "lt": specialFunction(function(i) { return pseudoHooks.positionalPseudo(function(matchesIndex, length) { i = i < 0 ? ~~i + length : i > length ? length : i; for(; --i >= 0;) matchesIndex.push(i); return matchesIndex.reverse(); }); }), "gt": specialFunction(function(i) { return pseudoHooks.positionalPseudo(function(matchesIndex, length) { i = i < 0 ? ~~i + length : i > length ? length : i; for(; ++i < length;) matchesIndex.push(i); return matchesIndex; }); }), "role": specialFunction(function(name) { return access(function(elem) { return (elem.role || attrVal(elem, "role")) === name; }); }), "aria": specialFunction(function(name) { name = "aria-" + name.toLowerCase(); return access(function(elem) { return elem[camelCase(name)] != null||attrVal(elem, name) != null; }); }), "data": specialFunction(function(name) { return access(function(elem) { return hasOwn.call(elem.dataset, name) || attrVal(elem, "data-" + name) != null; }); }) } }; Expr.pseudos["match"] = Expr.pseudos["rcontains"]; Expr.pseudos["ctx"] = Expr.pseudos["context"]; Expr.pseudos["where"] = Expr.pseudos["is"]; Expr.pseudos["nth"] = Expr.pseudos["eq"]; Expr.pseudos["scope"] = Expr.pseudos["root"]; Expr.pseudos["is"] = Expr.pseudos["filter"]; Expr.pseudos["imatch"] = Expr.pseudos["ircontains"]; // Add button/input type pseudos for(i in {submit:true, reset:true, menu:true}) { Expr.pseudos[i]=pseudoHooks.inputOrButtonPseudo(i, true); } // Add Expr.relative Combinators for(i in Expr.relative) { Expr.combinators[i]=addCombinators(Expr.relative[i]); } /** * Create Input type :PSEUDO * ------------------------- * A multi input types :pseudo Like :file, :password, :url */ access(function(type) { Expr.pseudos[type]=pseudoHooks.inputOrButtonPseudo(type); })(types.match(/\w+/g)); /** * Create tokenize selectors parser Method * --------------------------------------- * Tokenize multi chainable or comma seprated selectors */ tokenize=Snizzle.tokenize=function(selector) { var soFar, matched, match, groups, tokens, type, preFilters, rXPath; rXPath = exprMatcher.XPATH; soFar = selector.trim(); groups = []; preFilters = Expr.preFilter; while(soFar) { // Adjust multi comma selectors tokens // Comma and first run if (!matched || (match = rcomma.exec(soFar))) { match && (soFar=soFar.slice(match[0].length)||soFar); groups.push((tokens = [])); } matched = false; // Tokenize Combinators tokens [>~+<] if ((match = rcombinators.exec(soFar))) { matched = match.shift(); tokens.push({ value: matched, type: match[0].replace(rtrim, " ") }); soFar = soFar.slice(matched.length); } // Tokenize :PSEUDO or TAG or ID or CLASS or ATTR for(type in Expr.filter) { if ((match = exprMatcher[type].exec(soFar)) && (match = preFilters[type](match))) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match, xpath: rXPath.test(matched) }); soFar = soFar.slice(matched.length); } } // If matched false, Stop/Break while loop if (!matched) {break}; } // throw an error or return tokens return soFar ? Snizzle.error(soFar) : groups.slice(0); }; /** * Create compile External/Internal with public API * ------------------------------------------------ * Select only single XPathExpression selenium selectors elements * @param {String} expression Single XPath selenium expression * @param {Element|ArrayLike} context An element or elements list * @returns {Array} matched seed results */ compile = Snizzle.compile = function(expression, context) { var selector = doAdjust(expression); context = select(selector, context); return context; }; /** * Create Internal XPathCache Method * --------------------------------- * Cached self XPath expression or throwing the XPath RangeError * @returns Self cached expression string */ function XPathCache(expression) { var match = tokenize(expression.trim()), token = match.shift(), {value} = token[0]; value = value.replace(rxptrim, ""); if (token.length > 1 || match.length) { throw new RangeError("Allow only single XPath Expression!"); } // Rreturns cached expression return value; } /** * Create doAdjust XPath Adjuster Method * ------------------------------------- * Adjust XPath Expression to CSS3 or Javascript selector * TODO: Need to improve and enhance XPath Adjuster */ doAdjust = Snizzle.doAdjust = function(expression) { var soFar, match, matched, tokens; soFar = XPathCache(expression); tokens = ""; while(soFar) { // Descendant and Order Expression /html/div[2] if ((match = rXpOrdDesc.exec(soFar))) { matched = match.shift(); soFar = soFar.slice(matched.length); matched = matched.replace(rslash, " > ").replace(rxpnth, ":nth-child($1)"); tokens += matched; } matched = false; // Simple Childrens "*" Handling if ((match = /^\*/.exec(soFar))) { matched = match.shift(); tokens += matched; soFar = soFar.slice(matched.length); } // :: separates an axis name from a node test in an XPath expression if ((match = rXpAxises.exec(soFar))) { matched = match.shift(); console.log(matched); soFar = soFar.slice(matched.length); } if (!matched) {break} } return soFar.length ? Snizzle.error(soFar) : tokens; }; /** * Create Internal combine Method * ------------------------------ * @param {String} pseudos * Returns matched pseudos handler, Force pseudo to be an pseudo * @returns pseudo Function */ function combine(pseudos) { return Expr.pseudos[pseudos] || Expr._pseudos[pseudos] || Expr.__external[pseudos] || Expr.setFilters[pseudos] || Expr.attrHandle[pseudos]; } /** * Create select Internal or public API * ------------------------------------ * select the chainable and multi complex selector to be easily * TODO: Can be improve and enhance select method Handler */ select = Snizzle.select = function(selector, context, results) { var j, tokens, token, type, seed, fn, compiled = typeof selector === "function" && selector, match = tokenize((selector = compiled.selector || selector)), i = match.length; // Force results to be an empry Array results = results || []; while(i--) { tokens = match[i]; seed = !context.nodeType&&typeof context==="object" ? context : slice.call(Expr.find["TAG"]("*", context || document)); j = 0; // Switch seed for single combinator if (tokens.length === 1 && rcombinators.test(tokens[0].type)) { seed = [context || preferredDoc]; } while((token = tokens[j++])) { type = token.type; // Compile xPathExpression convert XPath to PSEUDO selector if (!!token.xpath && !support.xPathExpression) { seed = compile(token.value, seed); // Handle: All combinators [>+~<] selectors } else if ((fn = Expr.combinators[type])) { see