| /* | 
|     pseudo selectors | 
|   | 
|     --- | 
|   | 
|     they are available in two forms: | 
|     * filters called when the selector | 
|       is compiled and return a function | 
|       that needs to return next() | 
|     * pseudos get called on execution | 
|       they need to return a boolean | 
| */ | 
|   | 
| var getNCheck = require("nth-check"); | 
| var BaseFuncs = require("boolbase"); | 
| var attributes = require("./attributes.js"); | 
| var trueFunc = BaseFuncs.trueFunc; | 
| var falseFunc = BaseFuncs.falseFunc; | 
|   | 
| var checkAttrib = attributes.rules.equals; | 
|   | 
| function getAttribFunc(name, value) { | 
|     var data = { name: name, value: value }; | 
|     return function attribFunc(next, rule, options) { | 
|         return checkAttrib(next, data, options); | 
|     }; | 
| } | 
|   | 
| function getChildFunc(next, adapter) { | 
|     return function(elem) { | 
|         return !!adapter.getParent(elem) && next(elem); | 
|     }; | 
| } | 
|   | 
| var filters = { | 
|     contains: function(next, text, options) { | 
|         var adapter = options.adapter; | 
|   | 
|         return function contains(elem) { | 
|             return next(elem) && adapter.getText(elem).indexOf(text) >= 0; | 
|         }; | 
|     }, | 
|     icontains: function(next, text, options) { | 
|         var itext = text.toLowerCase(); | 
|         var adapter = options.adapter; | 
|   | 
|         return function icontains(elem) { | 
|             return ( | 
|                 next(elem) && | 
|                 adapter | 
|                     .getText(elem) | 
|                     .toLowerCase() | 
|                     .indexOf(itext) >= 0 | 
|             ); | 
|         }; | 
|     }, | 
|   | 
|     //location specific methods | 
|     "nth-child": function(next, rule, options) { | 
|         var func = getNCheck(rule); | 
|         var adapter = options.adapter; | 
|   | 
|         if (func === falseFunc) return func; | 
|         if (func === trueFunc) return getChildFunc(next, adapter); | 
|   | 
|         return function nthChild(elem) { | 
|             var siblings = adapter.getSiblings(elem); | 
|   | 
|             for (var i = 0, pos = 0; i < siblings.length; i++) { | 
|                 if (adapter.isTag(siblings[i])) { | 
|                     if (siblings[i] === elem) break; | 
|                     else pos++; | 
|                 } | 
|             } | 
|   | 
|             return func(pos) && next(elem); | 
|         }; | 
|     }, | 
|     "nth-last-child": function(next, rule, options) { | 
|         var func = getNCheck(rule); | 
|         var adapter = options.adapter; | 
|   | 
|         if (func === falseFunc) return func; | 
|         if (func === trueFunc) return getChildFunc(next, adapter); | 
|   | 
|         return function nthLastChild(elem) { | 
|             var siblings = adapter.getSiblings(elem); | 
|   | 
|             for (var pos = 0, i = siblings.length - 1; i >= 0; i--) { | 
|                 if (adapter.isTag(siblings[i])) { | 
|                     if (siblings[i] === elem) break; | 
|                     else pos++; | 
|                 } | 
|             } | 
|   | 
|             return func(pos) && next(elem); | 
|         }; | 
|     }, | 
|     "nth-of-type": function(next, rule, options) { | 
|         var func = getNCheck(rule); | 
|         var adapter = options.adapter; | 
|   | 
|         if (func === falseFunc) return func; | 
|         if (func === trueFunc) return getChildFunc(next, adapter); | 
|   | 
|         return function nthOfType(elem) { | 
|             var siblings = adapter.getSiblings(elem); | 
|   | 
|             for (var pos = 0, i = 0; i < siblings.length; i++) { | 
|                 if (adapter.isTag(siblings[i])) { | 
|                     if (siblings[i] === elem) break; | 
|                     if (adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; | 
|                 } | 
|             } | 
|   | 
|             return func(pos) && next(elem); | 
|         }; | 
|     }, | 
|     "nth-last-of-type": function(next, rule, options) { | 
|         var func = getNCheck(rule); | 
|         var adapter = options.adapter; | 
|   | 
|         if (func === falseFunc) return func; | 
|         if (func === trueFunc) return getChildFunc(next, adapter); | 
|   | 
|         return function nthLastOfType(elem) { | 
|             var siblings = adapter.getSiblings(elem); | 
|   | 
|             for (var pos = 0, i = siblings.length - 1; i >= 0; i--) { | 
|                 if (adapter.isTag(siblings[i])) { | 
|                     if (siblings[i] === elem) break; | 
|                     if (adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; | 
|                 } | 
|             } | 
|   | 
|             return func(pos) && next(elem); | 
|         }; | 
|     }, | 
|   | 
|     //TODO determine the actual root element | 
|     root: function(next, rule, options) { | 
|         var adapter = options.adapter; | 
|   | 
|         return function(elem) { | 
|             return !adapter.getParent(elem) && next(elem); | 
|         }; | 
|     }, | 
|   | 
|     scope: function(next, rule, options, context) { | 
|         var adapter = options.adapter; | 
|   | 
|         if (!context || context.length === 0) { | 
|             //equivalent to :root | 
|             return filters.root(next, rule, options); | 
|         } | 
|   | 
|         function equals(a, b) { | 
|             if (typeof adapter.equals === "function") return adapter.equals(a, b); | 
|   | 
|             return a === b; | 
|         } | 
|   | 
|         if (context.length === 1) { | 
|             //NOTE: can't be unpacked, as :has uses this for side-effects | 
|             return function(elem) { | 
|                 return equals(context[0], elem) && next(elem); | 
|             }; | 
|         } | 
|   | 
|         return function(elem) { | 
|             return context.indexOf(elem) >= 0 && next(elem); | 
|         }; | 
|     }, | 
|   | 
|     //jQuery extensions (others follow as pseudos) | 
|     checkbox: getAttribFunc("type", "checkbox"), | 
|     file: getAttribFunc("type", "file"), | 
|     password: getAttribFunc("type", "password"), | 
|     radio: getAttribFunc("type", "radio"), | 
|     reset: getAttribFunc("type", "reset"), | 
|     image: getAttribFunc("type", "image"), | 
|     submit: getAttribFunc("type", "submit"), | 
|   | 
|     //dynamic state pseudos. These depend on optional Adapter methods. | 
|     hover: function(next, rule, options) { | 
|         var adapter = options.adapter; | 
|   | 
|         if (typeof adapter.isHovered === 'function') { | 
|             return function hover(elem) { | 
|                 return next(elem) && adapter.isHovered(elem); | 
|             }; | 
|         } | 
|   | 
|         return falseFunc; | 
|     }, | 
|     visited: function(next, rule, options) { | 
|         var adapter = options.adapter; | 
|   | 
|         if (typeof adapter.isVisited === 'function') { | 
|             return function visited(elem) { | 
|                 return next(elem) && adapter.isVisited(elem); | 
|             }; | 
|         } | 
|   | 
|         return falseFunc; | 
|     }, | 
|     active: function(next, rule, options) { | 
|         var adapter = options.adapter; | 
|   | 
|         if (typeof adapter.isActive === 'function') { | 
|             return function active(elem) { | 
|                 return next(elem) && adapter.isActive(elem); | 
|             }; | 
|         } | 
|   | 
|         return falseFunc; | 
|     } | 
| }; | 
|   | 
| //helper methods | 
| function getFirstElement(elems, adapter) { | 
|     for (var i = 0; elems && i < elems.length; i++) { | 
|         if (adapter.isTag(elems[i])) return elems[i]; | 
|     } | 
| } | 
|   | 
| //while filters are precompiled, pseudos get called when they are needed | 
| var pseudos = { | 
|     empty: function(elem, adapter) { | 
|         return !adapter.getChildren(elem).some(function(elem) { | 
|             return adapter.isTag(elem) || elem.type === "text"; | 
|         }); | 
|     }, | 
|   | 
|     "first-child": function(elem, adapter) { | 
|         return getFirstElement(adapter.getSiblings(elem), adapter) === elem; | 
|     }, | 
|     "last-child": function(elem, adapter) { | 
|         var siblings = adapter.getSiblings(elem); | 
|   | 
|         for (var i = siblings.length - 1; i >= 0; i--) { | 
|             if (siblings[i] === elem) return true; | 
|             if (adapter.isTag(siblings[i])) break; | 
|         } | 
|   | 
|         return false; | 
|     }, | 
|     "first-of-type": function(elem, adapter) { | 
|         var siblings = adapter.getSiblings(elem); | 
|   | 
|         for (var i = 0; i < siblings.length; i++) { | 
|             if (adapter.isTag(siblings[i])) { | 
|                 if (siblings[i] === elem) return true; | 
|                 if (adapter.getName(siblings[i]) === adapter.getName(elem)) break; | 
|             } | 
|         } | 
|   | 
|         return false; | 
|     }, | 
|     "last-of-type": function(elem, adapter) { | 
|         var siblings = adapter.getSiblings(elem); | 
|   | 
|         for (var i = siblings.length - 1; i >= 0; i--) { | 
|             if (adapter.isTag(siblings[i])) { | 
|                 if (siblings[i] === elem) return true; | 
|                 if (adapter.getName(siblings[i]) === adapter.getName(elem)) break; | 
|             } | 
|         } | 
|   | 
|         return false; | 
|     }, | 
|     "only-of-type": function(elem, adapter) { | 
|         var siblings = adapter.getSiblings(elem); | 
|   | 
|         for (var i = 0, j = siblings.length; i < j; i++) { | 
|             if (adapter.isTag(siblings[i])) { | 
|                 if (siblings[i] === elem) continue; | 
|                 if (adapter.getName(siblings[i]) === adapter.getName(elem)) { | 
|                     return false; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         return true; | 
|     }, | 
|     "only-child": function(elem, adapter) { | 
|         var siblings = adapter.getSiblings(elem); | 
|   | 
|         for (var i = 0; i < siblings.length; i++) { | 
|             if (adapter.isTag(siblings[i]) && siblings[i] !== elem) return false; | 
|         } | 
|   | 
|         return true; | 
|     }, | 
|   | 
|     //:matches(a, area, link)[href] | 
|     link: function(elem, adapter) { | 
|         return adapter.hasAttrib(elem, "href"); | 
|     }, | 
|     //TODO: :any-link once the name is finalized (as an alias of :link) | 
|   | 
|     //forms | 
|     //to consider: :target | 
|   | 
|     //:matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type) | 
|     selected: function(elem, adapter) { | 
|         if (adapter.hasAttrib(elem, "selected")) return true; | 
|         else if (adapter.getName(elem) !== "option") return false; | 
|   | 
|         //the first <option> in a <select> is also selected | 
|         var parent = adapter.getParent(elem); | 
|   | 
|         if (!parent || adapter.getName(parent) !== "select" || adapter.hasAttrib(parent, "multiple")) { | 
|             return false; | 
|         } | 
|   | 
|         var siblings = adapter.getChildren(parent); | 
|         var sawElem = false; | 
|   | 
|         for (var i = 0; i < siblings.length; i++) { | 
|             if (adapter.isTag(siblings[i])) { | 
|                 if (siblings[i] === elem) { | 
|                     sawElem = true; | 
|                 } else if (!sawElem) { | 
|                     return false; | 
|                 } else if (adapter.hasAttrib(siblings[i], "selected")) { | 
|                     return false; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         return sawElem; | 
|     }, | 
|     //https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements | 
|     //:matches( | 
|     //  :matches(button, input, select, textarea, menuitem, optgroup, option)[disabled], | 
|     //  optgroup[disabled] > option), | 
|     // fieldset[disabled] * //TODO not child of first <legend> | 
|     //) | 
|     disabled: function(elem, adapter) { | 
|         return adapter.hasAttrib(elem, "disabled"); | 
|     }, | 
|     enabled: function(elem, adapter) { | 
|         return !adapter.hasAttrib(elem, "disabled"); | 
|     }, | 
|     //:matches(:matches(:radio, :checkbox)[checked], :selected) (TODO menuitem) | 
|     checked: function(elem, adapter) { | 
|         return adapter.hasAttrib(elem, "checked") || pseudos.selected(elem, adapter); | 
|     }, | 
|     //:matches(input, select, textarea)[required] | 
|     required: function(elem, adapter) { | 
|         return adapter.hasAttrib(elem, "required"); | 
|     }, | 
|     //:matches(input, select, textarea):not([required]) | 
|     optional: function(elem, adapter) { | 
|         return !adapter.hasAttrib(elem, "required"); | 
|     }, | 
|   | 
|     //jQuery extensions | 
|   | 
|     //:not(:empty) | 
|     parent: function(elem, adapter) { | 
|         return !pseudos.empty(elem, adapter); | 
|     }, | 
|     //:matches(h1, h2, h3, h4, h5, h6) | 
|     header: namePseudo(["h1", "h2", "h3", "h4", "h5", "h6"]), | 
|   | 
|     //:matches(button, input[type=button]) | 
|     button: function(elem, adapter) { | 
|         var name = adapter.getName(elem); | 
|         return ( | 
|             name === "button" || (name === "input" && adapter.getAttributeValue(elem, "type") === "button") | 
|         ); | 
|     }, | 
|     //:matches(input, textarea, select, button) | 
|     input: namePseudo(["input", "textarea", "select", "button"]), | 
|     //input:matches(:not([type!='']), [type='text' i]) | 
|     text: function(elem, adapter) { | 
|         var attr; | 
|         return ( | 
|             adapter.getName(elem) === "input" && | 
|             (!(attr = adapter.getAttributeValue(elem, "type")) || attr.toLowerCase() === "text") | 
|         ); | 
|     } | 
| }; | 
|   | 
| function namePseudo(names) { | 
|     if (typeof Set !== "undefined") { | 
|         // eslint-disable-next-line no-undef | 
|         var nameSet = new Set(names); | 
|   | 
|         return function(elem, adapter) { | 
|             return nameSet.has(adapter.getName(elem)); | 
|         }; | 
|     } | 
|   | 
|     return function(elem, adapter) { | 
|         return names.indexOf(adapter.getName(elem)) >= 0; | 
|     }; | 
| } | 
|   | 
| function verifyArgs(func, name, subselect) { | 
|     if (subselect === null) { | 
|         if (func.length > 2 && name !== "scope") { | 
|             throw new Error("pseudo-selector :" + name + " requires an argument"); | 
|         } | 
|     } else { | 
|         if (func.length === 2) { | 
|             throw new Error("pseudo-selector :" + name + " doesn't have any arguments"); | 
|         } | 
|     } | 
| } | 
|   | 
| //FIXME this feels hacky | 
| var re_CSS3 = /^(?:(?:nth|last|first|only)-(?:child|of-type)|root|empty|(?:en|dis)abled|checked|not)$/; | 
|   | 
| module.exports = { | 
|     compile: function(next, data, options, context) { | 
|         var name = data.name; | 
|         var subselect = data.data; | 
|         var adapter = options.adapter; | 
|   | 
|         if (options && options.strict && !re_CSS3.test(name)) { | 
|             throw new Error(":" + name + " isn't part of CSS3"); | 
|         } | 
|   | 
|         if (typeof filters[name] === "function") { | 
|             return filters[name](next, subselect, options, context); | 
|         } else if (typeof pseudos[name] === "function") { | 
|             var func = pseudos[name]; | 
|   | 
|             verifyArgs(func, name, subselect); | 
|   | 
|             if (func === falseFunc) { | 
|                 return func; | 
|             } | 
|   | 
|             if (next === trueFunc) { | 
|                 return function pseudoRoot(elem) { | 
|                     return func(elem, adapter, subselect); | 
|                 }; | 
|             } | 
|   | 
|             return function pseudoArgs(elem) { | 
|                 return func(elem, adapter, subselect) && next(elem); | 
|             }; | 
|         } else { | 
|             throw new Error("unmatched pseudo-class :" + name); | 
|         } | 
|     }, | 
|     filters: filters, | 
|     pseudos: pseudos | 
| }; |