@@ -81,29 +81,24 @@ export type TraversalType =
8181
8282const reName = / ^ [ ^ \\ # ] ? (?: \\ (?: [ \d a - f ] { 1 , 6 } \s ? | .) | [ \w \- \u00b0 - \uFFFF ] ) + / ;
8383const reEscape = / \\ ( [ \d a - f ] { 1 , 6 } \s ? | ( \s ) | .) / gi;
84- // Modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87
85- const reAttr =
86- / ^ \s * (?: ( \* | [ - \w ] * ) \| ) ? ( (?: \\ .| [ \w \u00b0 - \uFFFF - ] ) + ) \s * (?: ( \S ? ) = \s * (?: ( [ ' " ] ) ( (?: [ ^ \\ ] | \\ [ ^ ] ) * ?) \4| ( # ? (?: \\ .| [ \w \u00b0 - \uFFFF - ] ) * ) | ) | ) \s * ( [ i I s S ] ) ? \s * \] / ;
87-
88- const actionTypes : { [ key : string ] : AttributeAction } = {
89- undefined : "exists" ,
90- "" : "equals" ,
91- "~" : "element" ,
92- "^" : "start" ,
93- $ : "end" ,
94- "*" : "any" ,
95- "!" : "not" ,
96- "|" : "hyphen" ,
97- } ;
9884
99- const Traversals : { [ key : string ] : TraversalType } = {
85+ const actionTypes = new Map < string , AttributeAction > ( [
86+ [ "~" , "element" ] ,
87+ [ "^" , "start" ] ,
88+ [ "$" , "end" ] ,
89+ [ "*" , "any" ] ,
90+ [ "!" , "not" ] ,
91+ [ "|" , "hyphen" ] ,
92+ ] ) ;
93+
94+ const Traversals : Record < string , TraversalType > = {
10095 ">" : "child" ,
10196 "<" : "parent" ,
10297 "~" : "sibling" ,
10398 "+" : "adjacent" ,
10499} ;
105100
106- const attribSelectors : { [ key : string ] : [ string , AttributeAction ] } = {
101+ const attribSelectors : Record < string , [ string , AttributeAction ] > = {
107102 "#" : [ "id" , "equals" ] ,
108103 "." : [ "class" , "element" ] ,
109104} ;
@@ -302,10 +297,7 @@ function parseSelector(
302297 tokens = [ ] ;
303298 sawWS = false ;
304299 stripWhitespace ( 1 ) ;
305- } else if (
306- firstChar === "/" &&
307- selector . charAt ( selectorIndex + 1 ) === "*"
308- ) {
300+ } else if ( selector . startsWith ( "/*" , selectorIndex ) ) {
309301 const endIndex = selector . indexOf ( "*/" , selectorIndex + 2 ) ;
310302
311303 if ( endIndex < 0 ) {
@@ -332,51 +324,134 @@ function parseSelector(
332324 ignoreCase : options . xmlMode ? null : false ,
333325 } ) ;
334326 } else if ( firstChar === "[" ) {
335- const attributeMatch = selector
336- . slice ( selectorIndex + 1 )
337- . match ( reAttr ) ;
338-
339- if ( ! attributeMatch ) {
340- throw new Error (
341- `Malformed attribute selector: ${ selector . slice (
342- selectorIndex
343- ) } `
344- ) ;
327+ stripWhitespace ( 1 ) ;
328+
329+ // Determine attribute name and namespace
330+
331+ let name ;
332+ let namespace : string | null = null ;
333+
334+ if ( selector . charAt ( selectorIndex ) === "|" ) {
335+ namespace = "" ;
336+ selectorIndex += 1 ;
337+ }
338+
339+ if ( selector . startsWith ( "*|" , selectorIndex ) ) {
340+ namespace = "*" ;
341+ selectorIndex += 2 ;
345342 }
346343
347- const [
348- completeSelector ,
349- namespace = null ,
350- baseName ,
351- actionType ,
352- ,
353- quotedValue = "" ,
354- value = quotedValue ,
355- forceIgnore ,
356- ] = attributeMatch ;
344+ name = getName ( 0 ) ;
357345
358- selectorIndex += completeSelector . length + 1 ;
359- let name = unescapeCSS ( baseName ) ;
346+ if (
347+ namespace === null &&
348+ selector . charAt ( selectorIndex ) === "|" &&
349+ selector . charAt ( selectorIndex + 1 ) !== "="
350+ ) {
351+ namespace = name ;
352+ name = getName ( 1 ) ;
353+ }
360354
361355 if ( options . lowerCaseAttributeNames ?? ! options . xmlMode ) {
362356 name = name . toLowerCase ( ) ;
363357 }
364358
365- const ignoreCase =
359+ stripWhitespace ( 0 ) ;
360+
361+ // Determine comparison operation
362+
363+ let action : AttributeAction = "exists" ;
364+ const possibleAction = actionTypes . get (
365+ selector . charAt ( selectorIndex )
366+ ) ;
367+
368+ if ( possibleAction ) {
369+ action = possibleAction ;
370+
371+ if ( selector . charAt ( selectorIndex + 1 ) !== "=" ) {
372+ throw new Error ( "Expected `=`" ) ;
373+ }
374+
375+ stripWhitespace ( 2 ) ;
376+ } else if ( selector . charAt ( selectorIndex ) === "=" ) {
377+ action = "equals" ;
378+ stripWhitespace ( 1 ) ;
379+ }
380+
381+ // Determine value
382+
383+ let value = "" ;
384+ let ignoreCase : boolean | null = null ;
385+
386+ if ( action !== "exists" ) {
387+ if ( quotes . has ( selector . charAt ( selectorIndex ) ) ) {
388+ const quote = selector . charAt ( selectorIndex ) ;
389+ let sectionEnd = selectorIndex + 1 ;
390+ while (
391+ sectionEnd < selector . length &&
392+ ( selector . charAt ( sectionEnd ) !== quote ||
393+ isEscaped ( sectionEnd ) )
394+ ) {
395+ sectionEnd += 1 ;
396+ }
397+
398+ if ( selector . charAt ( sectionEnd ) !== quote ) {
399+ throw new Error ( "Attribute value didn't end" ) ;
400+ }
401+
402+ value = unescapeCSS (
403+ selector . slice ( selectorIndex + 1 , sectionEnd )
404+ ) ;
405+ selectorIndex = sectionEnd + 1 ;
406+ } else {
407+ const valueStart = selectorIndex ;
408+
409+ while (
410+ selectorIndex < selector . length &&
411+ ( ( ! isWhitespace ( selector . charAt ( selectorIndex ) ) &&
412+ selector . charAt ( selectorIndex ) !== "]" ) ||
413+ isEscaped ( selectorIndex ) )
414+ ) {
415+ selectorIndex += 1 ;
416+ }
417+
418+ value = unescapeCSS (
419+ selector . slice ( valueStart , selectorIndex )
420+ ) ;
421+ }
422+
423+ stripWhitespace ( 0 ) ;
424+
425+ // See if we have a force ignore flag
426+
427+ const forceIgnore = selector . charAt ( selectorIndex ) ;
366428 // If the forceIgnore flag is set (either `i` or `s`), use that value
367- forceIgnore
368- ? forceIgnore . toLowerCase ( ) === "i"
369- : // If `xmlMode` is set, there are no rules; return `null`.
370- options . xmlMode
371- ? null
372- : // Otherwise, use the `caseInsensitiveAttributes` list.
373- caseInsensitiveAttributes . has ( name ) ;
429+ if ( forceIgnore === "s" || forceIgnore === "S" ) {
430+ ignoreCase = false ;
431+ stripWhitespace ( 1 ) ;
432+ } else if ( forceIgnore === "i" || forceIgnore === "I" ) {
433+ ignoreCase = true ;
434+ stripWhitespace ( 1 ) ;
435+ }
436+ }
437+
438+ // If `xmlMode` is set, there are no rules; otherwise, use the `caseInsensitiveAttributes` list.
439+ if ( ! options . xmlMode ) {
440+ // TODO: Skip this for `exists`, as there is no value to compare to.
441+ ignoreCase ??= caseInsensitiveAttributes . has ( name ) ;
442+ }
443+
444+ if ( selector . charAt ( selectorIndex ) !== "]" ) {
445+ throw new Error ( "Attribute selector didn't terminate" ) ;
446+ }
447+
448+ selectorIndex += 1 ;
374449
375450 const attributeSelector : AttributeSelector = {
376451 type : "attribute" ,
377452 name,
378- action : actionTypes [ actionType ] ,
379- value : unescapeCSS ( value ) ,
453+ action,
454+ value,
380455 namespace,
381456 ignoreCase,
382457 } ;
0 commit comments