css-select
Version:
a CSS selector compiler/engine
102 lines • 4.01 kB
JavaScript
/**
* CSS limits the characters considered as whitespace to space, tab & line
* feed. We add carriage returns as htmlparser2 doesn't normalize them to
* line feeds.
* @see {@link https://www.w3.org/TR/css-text-3/#white-space}
*/
const isDocumentWhiteSpace = /^[ \t\r\n]*$/;
// While filters are precompiled, pseudos get called when they are needed
/** Runtime pseudo selector implementations. */
export const pseudos = {
empty(element, { adapter }) {
const children = adapter.getChildren(element);
return (
// First, make sure the tag does not have any element children.
children.every((element) => !adapter.isTag(element)) &&
// Then, check that the text content is only whitespace.
children.every((element) =>
// FIXME: `getText` call is potentially expensive.
isDocumentWhiteSpace.test(adapter.getText(element))));
},
"first-child"(element, { adapter, equals }) {
if (adapter.prevElementSibling) {
return adapter.prevElementSibling(element) == null;
}
const firstChild = adapter
.getSiblings(element)
.find((sibling) => adapter.isTag(sibling));
return firstChild != null && equals(element, firstChild);
},
"last-child"(element, { adapter, equals }) {
const siblings = adapter.getSiblings(element);
for (let index = siblings.length - 1; index >= 0; index--) {
if (equals(element, siblings[index])) {
return true;
}
if (adapter.isTag(siblings[index])) {
break;
}
}
return false;
},
"first-of-type"(element, { adapter, equals }) {
const siblings = adapter.getSiblings(element);
const elementName = adapter.getName(element);
for (const currentSibling of siblings) {
if (equals(element, currentSibling)) {
return true;
}
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === elementName) {
break;
}
}
return false;
},
"last-of-type"(element, { adapter, equals }) {
const siblings = adapter.getSiblings(element);
const elementName = adapter.getName(element);
for (let index = siblings.length - 1; index >= 0; index--) {
const currentSibling = siblings[index];
if (equals(element, currentSibling)) {
return true;
}
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === elementName) {
break;
}
}
return false;
},
"only-of-type"(element, { adapter, equals }) {
const elementName = adapter.getName(element);
return adapter
.getSiblings(element)
.every((sibling) => equals(element, sibling) ||
!adapter.isTag(sibling) ||
adapter.getName(sibling) !== elementName);
},
"only-child"(element, { adapter, equals }) {
return adapter
.getSiblings(element)
.every((sibling) => equals(element, sibling) || !adapter.isTag(sibling));
},
};
/**
* Validate pseudo selector argument arity.
* @param pseudoClassCondition Pseudo-function implementation to wrap.
* @param name Name of the pseudo selector.
* @param subselect Subselector passed to the pseudo-function.
* @param argumentIndex Index of the argument parser to apply.
*/
export function verifyPseudoArguments(pseudoClassCondition, name, subselect, argumentIndex) {
if (subselect === null) {
if (pseudoClassCondition.length > argumentIndex) {
throw new Error(`Pseudo-class :${name} requires an argument`);
}
}
else if (pseudoClassCondition.length === argumentIndex) {
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
}
}
//# sourceMappingURL=pseudos.js.map