11import * as boolbase from "boolbase" ;
2+ import { parse } from "css-what" ;
23import getNCheck from "nth-check" ;
34import { cacheParentResults } from "../helpers/cache.js" ;
5+ import { copyOptions } from "../helpers/options.js" ;
46import { getElementParent } from "../helpers/querying.js" ;
5- import type { CompiledQuery , InternalOptions } from "../types.js" ;
7+ import type { CompiledQuery , CompileToken , InternalOptions } from "../types.js" ;
68
79/**
810 * RFC 4647 extended filtering with pre-split subtags.
@@ -29,150 +31,107 @@ function extendedFilter(tag: string[], range: string[]): boolean {
2931 return true ;
3032}
3133
32- type Filter = < Node , ElementNode extends Node > (
34+ /** @see {@link https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo } */
35+ const nthOfRegex = / ^ ( .+ ?) \s + o f \s + ( .+ ) $ / is;
36+
37+ /** A pre-compiled pseudo filter. */
38+ export type Filter = < Node , ElementNode extends Node > (
3339 next : CompiledQuery < ElementNode > ,
3440 text : string ,
3541 options : InternalOptions < Node , ElementNode > ,
3642 context ?: Node [ ] ,
43+ compileToken ?: CompileToken < Node , ElementNode > ,
3744) => CompiledQuery < ElementNode > ;
3845
39- /**
40- * Pre-compiled pseudo filters.
41- */
42- export const filters : Record < string , Filter > = {
43- contains ( next , text , options ) {
44- const { getText } = options . adapter ;
46+ function compileNth ( reverse : boolean , ofType : boolean ) : Filter {
47+ return function nth ( next , rule , options , context , compileToken ) {
48+ const { adapter, equals } = options ;
49+ const ofMatch = ofType ? null : rule . match ( nthOfRegex ) ;
50+ const nthCheck = getNCheck ( ofMatch ? ofMatch [ 1 ] . trim ( ) : rule ) ;
4551
46- return cacheParentResults ( next , options , ( element ) =>
47- getText ( element ) . includes ( text ) ,
48- ) ;
49- } ,
50- icontains ( next , text , options ) {
51- const itext = text . toLowerCase ( ) ;
52- const { getText } = options . adapter ;
52+ if ( nthCheck === boolbase . falseFunc ) return boolbase . falseFunc ;
5353
54- return cacheParentResults ( next , options , ( element ) =>
55- getText ( element ) . toLowerCase ( ) . includes ( itext ) ,
56- ) ;
57- } ,
54+ const ofSelector =
55+ ofMatch && compileToken
56+ ? compileToken (
57+ parse ( ofMatch [ 2 ] . trim ( ) ) ,
58+ copyOptions ( options ) ,
59+ context ,
60+ )
61+ : undefined ;
5862
59- // Location specific methods
60- "nth-child" ( next , rule , { adapter, equals } ) {
61- const nthCheck = getNCheck ( rule ) ;
63+ if ( ofSelector === boolbase . falseFunc ) return boolbase . falseFunc ;
6264
63- if ( nthCheck === boolbase . falseFunc ) {
64- return boolbase . falseFunc ;
65- }
66- if ( nthCheck === boolbase . trueFunc ) {
65+ if ( nthCheck === boolbase . trueFunc && ! ofSelector ) {
6766 return ( element ) =>
6867 getElementParent ( element , adapter ) !== null && next ( element ) ;
6968 }
7069
71- return function nthChild ( element ) {
72- const siblings = adapter . getSiblings ( element ) ;
73- let pos = 0 ;
74-
75- for ( const sibling of siblings ) {
76- if ( equals ( element , sibling ) ) {
77- break ;
78- }
79- if ( adapter . isTag ( sibling ) ) {
80- pos ++ ;
70+ type ElementNode = Parameters < typeof next > [ 0 ] ;
71+
72+ const shouldCount = ofSelector
73+ ? ( _element : ElementNode , sibling : ElementNode ) =>
74+ ofSelector ( sibling )
75+ : ofType
76+ ? ( element : ElementNode , sibling : ElementNode ) =>
77+ adapter . getName ( sibling ) === adapter . getName ( element )
78+ : boolbase . trueFunc ;
79+
80+ if ( reverse ) {
81+ return function nthLast ( element ) {
82+ if ( ofSelector && ! ofSelector ( element ) ) return false ;
83+ const siblings = adapter . getSiblings ( element ) ;
84+ let pos = 0 ;
85+ for ( let index = siblings . length - 1 ; index >= 0 ; index -- ) {
86+ const sibling = siblings [ index ] ;
87+ if ( equals ( element , sibling ) ) break ;
88+ if ( adapter . isTag ( sibling ) && shouldCount ( element , sibling ) )
89+ pos ++ ;
8190 }
82- }
83-
84- return nthCheck ( pos ) && next ( element ) ;
85- } ;
86- } ,
87- "nth-last-child" ( next , rule , { adapter, equals } ) {
88- const nthCheck = getNCheck ( rule ) ;
89-
90- if ( nthCheck === boolbase . falseFunc ) {
91- return boolbase . falseFunc ;
92- }
93- if ( nthCheck === boolbase . trueFunc ) {
94- return ( element ) =>
95- getElementParent ( element , adapter ) !== null && next ( element ) ;
91+ return nthCheck ( pos ) && next ( element ) ;
92+ } ;
9693 }
9794
98- return function nthLastChild ( element ) {
95+ return function nth ( element ) {
96+ if ( ofSelector && ! ofSelector ( element ) ) return false ;
9997 const siblings = adapter . getSiblings ( element ) ;
10098 let pos = 0 ;
101-
102- for ( let index = siblings . length - 1 ; index >= 0 ; index -- ) {
103- if ( equals ( element , siblings [ index ] ) ) {
104- break ;
105- }
106- if ( adapter . isTag ( siblings [ index ] ) ) {
99+ for ( const sibling of siblings ) {
100+ if ( equals ( element , sibling ) ) break ;
101+ if ( adapter . isTag ( sibling ) && shouldCount ( element , sibling ) )
107102 pos ++ ;
108- }
109103 }
110-
111104 return nthCheck ( pos ) && next ( element ) ;
112105 } ;
113- } ,
114- "nth-of-type" ( next , rule , { adapter, equals } ) {
115- const nthCheck = getNCheck ( rule ) ;
116-
117- if ( nthCheck === boolbase . falseFunc ) {
118- return boolbase . falseFunc ;
119- }
120- if ( nthCheck === boolbase . trueFunc ) {
121- return ( element ) =>
122- getElementParent ( element , adapter ) !== null && next ( element ) ;
123- }
124-
125- return function nthOfType ( element ) {
126- const siblings = adapter . getSiblings ( element ) ;
127- let pos = 0 ;
106+ } ;
107+ }
128108
129- for ( const currentSibling of siblings ) {
130- if ( equals ( element , currentSibling ) ) {
131- break ;
132- }
133- if (
134- adapter . isTag ( currentSibling ) &&
135- adapter . getName ( currentSibling ) === adapter . getName ( element )
136- ) {
137- pos ++ ;
138- }
139- }
109+ /**
110+ * Pre-compiled pseudo filters.
111+ */
112+ export const filters : Record < string , Filter > = {
113+ contains ( next , text , options ) {
114+ const { getText } = options . adapter ;
140115
141- return nthCheck ( pos ) && next ( element ) ;
142- } ;
116+ return cacheParentResults ( next , options , ( element ) =>
117+ getText ( element ) . includes ( text ) ,
118+ ) ;
143119 } ,
144- "nth-last-of-type" ( next , rule , { adapter, equals } ) {
145- const nthCheck = getNCheck ( rule ) ;
146-
147- if ( nthCheck === boolbase . falseFunc ) {
148- return boolbase . falseFunc ;
149- }
150- if ( nthCheck === boolbase . trueFunc ) {
151- return ( element ) =>
152- getElementParent ( element , adapter ) !== null && next ( element ) ;
153- }
154-
155- return function nthLastOfType ( element ) {
156- const siblings = adapter . getSiblings ( element ) ;
157- let pos = 0 ;
158-
159- for ( let index = siblings . length - 1 ; index >= 0 ; index -- ) {
160- const currentSibling = siblings [ index ] ;
161- if ( equals ( element , currentSibling ) ) {
162- break ;
163- }
164- if (
165- adapter . isTag ( currentSibling ) &&
166- adapter . getName ( currentSibling ) === adapter . getName ( element )
167- ) {
168- pos ++ ;
169- }
170- }
120+ icontains ( next , text , options ) {
121+ const itext = text . toLowerCase ( ) ;
122+ const { getText } = options . adapter ;
171123
172- return nthCheck ( pos ) && next ( element ) ;
173- } ;
124+ return cacheParentResults ( next , options , ( element ) =>
125+ getText ( element ) . toLowerCase ( ) . includes ( itext ) ,
126+ ) ;
174127 } ,
175128
129+ // Location specific methods
130+ "nth-child" : compileNth ( false , false ) ,
131+ "nth-last-child" : compileNth ( true , false ) ,
132+ "nth-of-type" : compileNth ( false , true ) ,
133+ "nth-last-of-type" : compileNth ( true , true ) ,
134+
176135 // TODO determine the actual root element
177136 root ( next , _rule , { adapter } ) {
178137 return ( element ) =>
0 commit comments