Excluding Positionals: :not(:positional)
Relevant source files
The following files were used as context for generating this wiki page:
The :not(:positional) selector is a specialized feature of the cheerio-select engine that allows for the exclusion of elements based on their position within a set. Unlike standard CSS pseudo-classes, this implementation supports jQuery-style positional selectors such as :first, :last, :eq, :gt, :lt, :even, and :odd within a negation context.
This functionality is part of the library's role as a thin wrapper around css-select, extending it specifically to handle the procedural nature of positional filtering that is not native to standard CSS selection. By wrapping these positionals in a :not() pseudo-class, developers can perform complex set subtractions based on index and order.
Sources: README.md:6-15, src/index.ts:1-15
Architectural Overview
The exclusion logic is integrated into the primary filtering pipeline. When the engine encounters a :not pseudo-class, it identifies whether the argument contains positional selectors. If so, it leverages a specific execution path that calculates the matches of the inner selector and then subtracts them from the current element set.
Positional Types Supporting Negation
| Selector | Description |
|---|---|
:first | Excludes the first element in the current set. |
:last | Excludes the last element in the current set. |
:eq(index) | Excludes the element at the specified zero-based index. |
:gt(index) | Excludes elements with an index higher than the specified value. |
:lt(index) | Excludes elements with an index lower than the specified value. |
:even | Excludes even-indexed elements (0, 2, 4...). |
:odd | Excludes odd-indexed elements (1, 3, 5...). |
Sources: README.md:8-15, src/index.ts:104-128
Implementation Logic
The logic for :not(:positional) is handled within the filterByPosition function. When a not filter is encountered, the system performs a recursive call to filterParsed using the inner selector data. The resulting set of elements is then compared against the original set to perform the exclusion.
Data Flow for Negation
The following diagram illustrates how the filterByPosition function handles the not case by creating a Set of elements to be excluded and filtering the original array.
Sources: src/index.ts:125-130
Logic Snippet: filterByPosition
case "not": {
const filtered = new Set(
filterParsed(data as Selector[][], elements, options),
);
return elements.filter((element) => !filtered.has(element));
}
Sources: src/index.ts:125-130
Grouping and Parsing
Before positionals are processed, the selector is parsed by css-what and then grouped by the groupSelectors helper. This helper separates "plain" CSS selectors from "filtered" selectors (those containing positionals). The engine then iterates through the filtered selectors, applying the positional logic sequentially.
Sources: src/index.ts:68-76, src/helpers.spec.ts:7-24
Testing and Edge Cases
The implementation is verified through extensive test suites that cover nested negations and sequential positional pseudos. Key test scenarios include:
- Sequential Pseudos:
:has(:contains(mark)):has(code)ensures that positionals can be chained without breaking the tokenization or exclusion logic. - Recursive Negation:
option:not(:not(:selected))validates that the engine can handle multiple levels of:notwhen applied to elements. - Compound Positional Negation:
p:not(:first, :last)allows for the exclusion of multiple positional constraints simultaneously.
Sources: test/sizzle.spec.ts:850-865, test/sizzle.spec.ts:980-990, src/index.spec.ts:70-80
Summary
The :not(:positional) feature provides a powerful mechanism for set manipulation by bridging the gap between standard CSS negation and jQuery's procedural positional selectors. By utilizing a Set-based subtraction method within the filterByPosition function, cheerio-select ensures efficient and accurate exclusion of elements based on their relative order and index.
Sources: src/index.ts:125-130, README.md:1-15