Browse Source

fcl-css: fixed reading unary tile not as identifier

mattias 2 years ago
parent
commit
76e0ec6744
2 changed files with 544 additions and 95 deletions
  1. 118 5
      packages/fcl-css/src/fpcssparser.pp
  2. 426 90
      packages/fcl-css/src/fpcssresolver.pas

+ 118 - 5
packages/fcl-css/src/fpcssparser.pp

@@ -65,6 +65,9 @@ Type
     function ParseWQName: TCSSElement;
     function ParseWQName: TCSSElement;
     function ParseDeclaration(aIsAt : Boolean = false): TCSSDeclarationElement;
     function ParseDeclaration(aIsAt : Boolean = false): TCSSDeclarationElement;
     function ParseCall(aName: TCSSString): TCSSElement;
     function ParseCall(aName: TCSSString): TCSSElement;
+    procedure ParseSelectorCommaList(aCall: TCSSCallElement);
+    procedure ParseRelationalSelectorCommaList(aCall: TCSSCallElement);
+    procedure ParseNthChildParams(aCall: TCSSCallElement);
     function ParseUnary: TCSSElement;
     function ParseUnary: TCSSElement;
     function ParseUnit: TCSSUnits;
     function ParseUnit: TCSSUnits;
     function ParseIdentifier : TCSSIdentifierElement;
     function ParseIdentifier : TCSSIdentifierElement;
@@ -1064,7 +1067,7 @@ begin
     ctkPSEUDOFUNCTION,
     ctkPSEUDOFUNCTION,
     ctkFUNCTION : Result:=ParseCall('');
     ctkFUNCTION : Result:=ParseCall('');
     ctkSTAR,
     ctkSTAR,
-    ctkTILDE,
+    ctkTILDE: Result:=ParseInvalidToken;
     ctkIDENTIFIER: Result:=ParseIdentifier;
     ctkIDENTIFIER: Result:=ParseIdentifier;
     ctkCLASSNAME : Result:=ParseClassName;
     ctkCLASSNAME : Result:=ParseClassName;
   else
   else
@@ -1111,6 +1114,8 @@ var
   List: TCSSListElement;
   List: TCSSListElement;
 begin
 begin
   Result:=nil;
   Result:=nil;
+  if CurrentToken in [ctkLBRACE,ctkRBRACE,ctkRPARENTHESIS,ctkEOF] then
+    exit;
   El:=nil;
   El:=nil;
   Bin:=nil;
   Bin:=nil;
   List:=nil;
   List:=nil;
@@ -1127,7 +1132,7 @@ begin
       List:=nil;
       List:=nil;
       El:=ParseSub;
       El:=ParseSub;
       {$IFDEF VerbosecSSParser}
       {$IFDEF VerbosecSSParser}
-      writeln('TCSSParser.ParseSelector LIST NEXT ',CurrentToken,' ',CurrentTokenString);
+      writeln('TCSSParser.ParseSelector LIST NEXT ',CurrentToken,' ',CurrentTokenString,' El=',GetCSSObj(El));
       {$ENDIF}
       {$ENDIF}
       while CurrentToken in [ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION] do
       while CurrentToken in [ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION] do
         begin
         begin
@@ -1154,7 +1159,7 @@ begin
       {$ENDIF}
       {$ENDIF}
 
 
       case CurrentToken of
       case CurrentToken of
-      ctkLBRACE,ctkEOF,ctkSEMICOLON,ctkCOMMA:
+      ctkLBRACE,ctkRBRACE,ctkRBRACKET,ctkRPARENTHESIS,ctkEOF,ctkSEMICOLON,ctkCOMMA:
         break;
         break;
       ctkGT,ctkPLUS,ctkTILDE,ctkPIPE:
       ctkGT,ctkPLUS,ctkTILDE,ctkPIPE:
         begin
         begin
@@ -1175,7 +1180,7 @@ begin
         Bin.Operation:=boWhiteSpace;
         Bin.Operation:=boWhiteSpace;
         end;
         end;
       else
       else
-        Consume(ctkLBRACE);
+        break;
       end;
       end;
     until false;
     until false;
     ok:=true;
     ok:=true;
@@ -1365,7 +1370,17 @@ begin
       aName:=Copy(aName,1,L-1);
       aName:=Copy(aName,1,L-1);
     aCall.Name:=aName;
     aCall.Name:=aName;
     if CurrentToken=ctkPSEUDOFUNCTION then
     if CurrentToken=ctkPSEUDOFUNCTION then
-      Consume(ctkPSEUDOFUNCTION)
+      begin
+      Consume(ctkPSEUDOFUNCTION);
+      case aName of
+      ':not',':is',':where':
+        ParseSelectorCommaList(aCall);
+      ':has':
+        ParseRelationalSelectorCommaList(aCall);
+      ':nth-child',':nth-last-child',':nth-of-type',':nth-last-of-type':
+        ParseNthChildParams(aCall);
+      end;
+      end
     else
     else
       Consume(ctkFUNCTION);
       Consume(ctkFUNCTION);
     // Call argument list can be empty: mask()
     // Call argument list can be empty: mask()
@@ -1392,6 +1407,104 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TCSSParser.ParseSelectorCommaList(aCall: TCSSCallElement);
+var
+  El: TCSSElement;
+begin
+  while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
+    begin
+    El:=ParseSelector;
+    if EL=nil then exit;
+    aCall.AddArg(El);
+    SkipWhiteSpace;
+    if CurrentToken<>ctkCOMMA then
+      exit;
+    GetNextToken;
+  end;
+end;
+
+procedure TCSSParser.ParseRelationalSelectorCommaList(aCall: TCSSCallElement);
+var
+  El: TCSSElement;
+begin
+  while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
+    begin
+    if CurrentToken in [ctkGT,ctkPLUS,ctkTILDE,ctkPIPE] then
+      aCall.AddArg(ParseInvalidToken);
+    El:=ParseSelector;
+    if EL=nil then exit;
+    aCall.AddArg(El);
+    if CurrentToken<>ctkCOMMA then
+      exit;
+    GetNextToken;
+  end;
+end;
+
+procedure TCSSParser.ParseNthChildParams(aCall: TCSSCallElement);
+// Examples:
+// odd
+// even
+//  n
+//  +n
+// -2n
+// 2n+1
+//  even of :not(:hidden)
+// 2n+1 of [:not(display=none)]
+var
+  aUnary: TCSSUnaryElement;
+  IdentEl: TCSSIdentifierElement;
+begin
+  case CurrentToken of
+  ctkIDENTIFIER:
+    case lowercase(CurrentTokenString) of
+    'odd','even','n':
+      aCall.AddArg(ParseIdentifier);
+    '-n':
+      begin
+        aUnary:=TCSSUnaryElement(CreateElement(TCSSUnaryElement));
+        aCall.AddArg(aUnary);
+        aUnary.Operation:=uoMinus;
+        IdentEl:=TCSSIdentifierElement(CreateElement(TCSSIdentifierElement));
+        aUnary.Right:=IdentEl;
+        IdentEl.Value:='n';
+        GetNextToken;
+      end;
+    else
+      DoWarnExpectedButGot('An+B');
+      aCall.AddArg(ParseIdentifier);
+      exit;
+    end;
+  ctkINTEGER:
+    begin
+    aCall.AddArg(ParseInteger);
+    if (CurrentToken<>ctkIDENTIFIER) then
+      begin
+      DoWarnExpectedButGot('An+B');
+      exit;
+      end;
+    if (lowercase(CurrentTokenString)<>'n') then
+      begin
+      DoWarnExpectedButGot('An+B');
+      exit;
+      end;
+    aCall.AddArg(ParseIdentifier);
+    end;
+  else
+    DoWarnExpectedButGot('An+B');
+    exit;
+  end;
+
+  if CurrentToken in [ctkMINUS,ctkPLUS] then
+    aCall.AddArg(ParseUnary);
+  if CurrentToken=ctkINTEGER then
+    aCall.AddArg(ParseInteger);
+  if (CurrentToken=ctkIDENTIFIER) and SameText(CurrentTokenString,'of') then
+    begin
+    aCall.AddArg(ParseIdentifier);
+    aCall.AddArg(ParseSelector);
+    end;
+end;
+
 function TCSSParser.ParseString: TCSSElement;
 function TCSSParser.ParseString: TCSSElement;
 
 
 Var
 Var

+ 426 - 90
packages/fcl-css/src/fpcssresolver.pas

@@ -14,14 +14,15 @@
  **********************************************************************
  **********************************************************************
 
 
 ToDo:
 ToDo:
-- descendant combinator
-- and combinator
+- replace parser invalidtoken for relational operators ctkStar, Tile, Pipe
 - 'all' attribute
 - 'all' attribute
+- :has()
+- surpress duplicate warnings
+- cache list of nth-of-type
 - TCSSResolver.FindComputedAttribute  use binary search for >8 elements
 - TCSSResolver.FindComputedAttribute  use binary search for >8 elements
-- CSSSpecifityInline
 - namespaces
 - namespaces
 - layers
 - layers
-
+- @rules: @media, @font-face
 }
 }
 
 
 unit fpCSSResolver;
 unit fpCSSResolver;
@@ -32,7 +33,7 @@ unit fpCSSResolver;
 interface
 interface
 
 
 uses
 uses
-  Classes, SysUtils, Contnrs, StrUtils, fpCSSTree;
+  Classes, SysUtils, types, Contnrs, StrUtils, fpCSSTree;
 
 
 const
 const
   CSSSpecifityInvalid = -2;
   CSSSpecifityInvalid = -2;
@@ -51,14 +52,43 @@ const
   CSSAttributeID_All = 3; // id of attribute key 'all'
   CSSAttributeID_All = 3; // id of attribute key 'all'
   // pseudo attribute IDs
   // pseudo attribute IDs
   CSSPseudoID_Root = 1; // :root
   CSSPseudoID_Root = 1; // :root
-  CSSPseudoID_Empty = 2; // :empty
-  CSSPseudoID_FirstChild = 3; // :first-child
-  CSSPseudoID_LastChild = 4; // :last-child
-  CSSPseudoID_OnlyChild = 5; // :only-child
-  CSSPseudoID_FirstOfType = 6; // :first-of-type
-  CSSPseudoID_LastOfType = 7; // :last-of-type
-  CSSPseudoID_OnlyOfType = 8; // :only-of-type
-  CSSCallID_NthChild = 9; // :nth-child
+  CSSPseudoID_Empty = CSSPseudoID_Root+1; // :empty
+  CSSPseudoID_FirstChild = CSSPseudoID_Empty+1; // :first-child
+  CSSPseudoID_LastChild = CSSPseudoID_FirstChild+1; // :last-child
+  CSSPseudoID_OnlyChild = CSSPseudoID_LastChild+1; // :only-child
+  CSSPseudoID_FirstOfType = CSSPseudoID_OnlyChild+1; // :first-of-type
+  CSSPseudoID_LastOfType = CSSPseudoID_FirstOfType+1; // :last-of-type
+  CSSPseudoID_OnlyOfType = CSSPseudoID_LastOfType+1; // :only-of-type
+  CSSCallID_Not = CSSPseudoID_OnlyOfType+1; // :nth-child
+  CSSCallID_Is = CSSCallID_Not+1; // :nth-child
+  CSSCallID_Where = CSSCallID_Is+1; // :nth-child
+  CSSCallID_Has = CSSCallID_Where+1; // :nth-child
+  CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child
+  CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-child
+  CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-child
+  CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-child
+  CSSIDLast = CSSCallID_NthLastOfType;
+
+const
+  CSSPseudoNames: array[0..CSSIDLast] of string = (
+    '?',
+    ':root',
+    ':empty',
+    ':first-child',
+    ':last-child',
+    ':only-child',
+    ':first-of-type',
+    ':last-of-type',
+    ':only-of-type',
+    ':not()',
+    ':is()',
+    ':where()',
+    ':has()',
+    ':nth-child(n)',
+    ':nth-last-child(n)',
+    ':nth-of-type(n)',
+    ':nth-last-of-type(n)'
+    );
 
 
 type
 type
   TCSSMsgID = int64;
   TCSSMsgID = int64;
@@ -168,9 +198,15 @@ type
     destructor Destroy; override;
     destructor Destroy; override;
   end;
   end;
 
 
+  { TCSSCallNthChildParams }
+
   TCSSCallNthChildParams = class
   TCSSCallNthChildParams = class
     Modulo: integer;
     Modulo: integer;
     Start: integer;
     Start: integer;
+    HasOf: boolean;
+    OfSelector: TCSSElement;
+    ChildIDs: TIntegerDynArray;
+    destructor Destroy; override;
   end;
   end;
 
 
   TCSSResolverOption = (
   TCSSResolverOption = (
@@ -223,22 +259,29 @@ type
     procedure ComputeRule(aRule: TCSSRuleElement); virtual;
     procedure ComputeRule(aRule: TCSSRuleElement); virtual;
     procedure ComputeInline(El: TCSSElement); virtual;
     procedure ComputeInline(El: TCSSElement); virtual;
     procedure ComputeInlineRule(aRule: TCSSRuleElement); virtual;
     procedure ComputeInlineRule(aRule: TCSSRuleElement); virtual;
-    function SelectorMatches(aSelector: TCSSElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorIdentifierMatches(Identifier: TCSSIdentifierElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorHashIdentifierMatches(Identifier: TCSSHashIdentifierElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorPseudoClassMatches(aPseudoClass: TCSSPseudoClassElement; var TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorListMatches(aList: TCSSListElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorArrayMatches(anArray: TCSSArrayElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
+    function SelectorMatches(aSelector: TCSSElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorIdentifierMatches(Identifier: TCSSIdentifierElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorHashIdentifierMatches(Identifier: TCSSHashIdentifierElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorPseudoClassMatches(aPseudoClass: TCSSPseudoClassElement; var TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorListMatches(aList: TCSSListElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorArrayMatches(anArray: TCSSArrayElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
     function SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function SelectorCallMatches(aCall: TCSSCallElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
-    function Call_NthChild(aCall: TCSSCallElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
+    function SelectorCallMatches(aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Not(aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Is(aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Where(aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_NthChild(CallID: TCSSNumericalID; aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    procedure CollectSiblingsOf(CallID: TCSSNumericalID; TestNode: TCSSNode;
+      Params: TCSSCallNthChildParams); virtual;
+    function GetSiblingOfIndex(SiblingIDs: TIntegerDynArray; Index: integer): integer; virtual;
     function ComputeValue(El: TCSSElement): TCSSString; virtual;
     function ComputeValue(El: TCSSElement): TCSSString; virtual;
     function SameValueText(const A, B: TCSSString): boolean; virtual;
     function SameValueText(const A, B: TCSSString): boolean; virtual;
     function SameValueText(A: PChar; ALen: integer; B: PChar; BLen: integer): boolean; virtual;
     function SameValueText(A: PChar; ALen: integer; B: PChar; BLen: integer): boolean; virtual;
     function PosSubString(const SearchStr, Str: TCSSString): integer; virtual;
     function PosSubString(const SearchStr, Str: TCSSString): integer; virtual;
     function PosWord(const SearchWord, Words: TCSSString): integer; virtual;
     function PosWord(const SearchWord, Words: TCSSString): integer; virtual;
+    function GetSiblingCount(aNode: TCSSNode): integer; virtual;
     procedure MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity); virtual;
     procedure MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity); virtual;
     function ResolveIdentifier(El: TCSSIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
     function ResolveIdentifier(El: TCSSIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
     function ResolveCall(El: TCSSCallElement): TCSSNumericalID; virtual;
     function ResolveCall(El: TCSSCallElement): TCSSNumericalID; virtual;
@@ -269,6 +312,13 @@ type
 
 
 implementation
 implementation
 
 
+{ TCSSCallNthChildParams }
+
+destructor TCSSCallNthChildParams.Destroy;
+begin
+  inherited Destroy;
+end;
+
 { TCSSCallData }
 { TCSSCallData }
 
 
 destructor TCSSCallData.Destroy;
 destructor TCSSCallData.Destroy;
@@ -385,7 +435,7 @@ begin
   for i:=0 to aRule.SelectorCount-1 do
   for i:=0 to aRule.SelectorCount-1 do
   begin
   begin
     aSelector:=aRule.Selectors[i];
     aSelector:=aRule.Selectors[i];
-    Specifity:=SelectorMatches(aSelector,FNode);
+    Specifity:=SelectorMatches(aSelector,FNode,false);
     if Specifity>BestSpecifity then
     if Specifity>BestSpecifity then
       BestSpecifity:=Specifity;
       BestSpecifity:=Specifity;
   end;
   end;
@@ -420,14 +470,14 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorMatches(aSelector: TCSSElement;
 function TCSSResolver.SelectorMatches(aSelector: TCSSElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
 
 
   procedure MatchPseudo;
   procedure MatchPseudo;
   var
   var
     aNode: TCSSNode;
     aNode: TCSSNode;
   begin
   begin
     aNode:=TestNode;
     aNode:=TestNode;
-    Result:=SelectorPseudoClassMatches(TCSSPseudoClassElement(aSelector),aNode);
+    Result:=SelectorPseudoClassMatches(TCSSPseudoClassElement(aSelector),aNode,OnlySpecifity);
   end;
   end;
 
 
 var
 var
@@ -436,27 +486,28 @@ begin
   Result:=CSSSpecifityInvalid;
   Result:=CSSSpecifityInvalid;
   C:=aSelector.ClassType;
   C:=aSelector.ClassType;
   if C=TCSSIdentifierElement then
   if C=TCSSIdentifierElement then
-    Result:=SelectorIdentifierMatches(TCSSIdentifierElement(aSelector),TestNode)
+    Result:=SelectorIdentifierMatches(TCSSIdentifierElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSHashIdentifierElement then
   else if C=TCSSHashIdentifierElement then
-    Result:=SelectorHashIdentifierMatches(TCSSHashIdentifierElement(aSelector),TestNode)
+    Result:=SelectorHashIdentifierMatches(TCSSHashIdentifierElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSClassNameElement then
   else if C=TCSSClassNameElement then
-    Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode)
+    Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSPseudoClassElement then
   else if C=TCSSPseudoClassElement then
     MatchPseudo
     MatchPseudo
   else if C=TCSSBinaryElement then
   else if C=TCSSBinaryElement then
-    Result:=SelectorBinaryMatches(TCSSBinaryElement(aSelector),TestNode)
+    Result:=SelectorBinaryMatches(TCSSBinaryElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSArrayElement then
   else if C=TCSSArrayElement then
-    Result:=SelectorArrayMatches(TCSSArrayElement(aSelector),TestNode)
+    Result:=SelectorArrayMatches(TCSSArrayElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSListElement then
   else if C=TCSSListElement then
-    Result:=SelectorListMatches(TCSSListElement(aSelector),TestNode)
+    Result:=SelectorListMatches(TCSSListElement(aSelector),TestNode,OnlySpecifity)
   else if C=TCSSCallElement then
   else if C=TCSSCallElement then
-    Result:=SelectorCallMatches(TCSSCallElement(aSelector),TestNode)
+    Result:=SelectorCallMatches(TCSSCallElement(aSelector),TestNode,OnlySpecifity)
   else
   else
     Log(etError,20220908230152,'Unknown CSS selector element',aSelector);
     Log(etError,20220908230152,'Unknown CSS selector element',aSelector);
 end;
 end;
 
 
 function TCSSResolver.SelectorIdentifierMatches(
 function TCSSResolver.SelectorIdentifierMatches(
-  Identifier: TCSSIdentifierElement; const TestNode: TCSSNode): TCSSSpecifity;
+  Identifier: TCSSIdentifierElement; const TestNode: TCSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   TypeID: TCSSNumericalID;
   TypeID: TCSSNumericalID;
 begin
 begin
@@ -469,7 +520,9 @@ begin
   begin
   begin
     // universal selector
     // universal selector
     Result:=CSSSpecifityUniversal;
     Result:=CSSSpecifityUniversal;
-  end else if TypeID=CSSIDNone then
+  end else if OnlySpecifity then
+    Result:=CSSSpecifityType
+  else if TypeID=CSSIDNone then
   begin
   begin
     if croErrorOnUnknownName in Options then
     if croErrorOnUnknownName in Options then
       Log(etError,20220911230224,'Unknown CSS selector type name "'+Identifier.Name+'"',Identifier);
       Log(etError,20220911230224,'Unknown CSS selector type name "'+Identifier.Name+'"',Identifier);
@@ -479,11 +532,13 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorHashIdentifierMatches(
 function TCSSResolver.SelectorHashIdentifierMatches(
-  Identifier: TCSSHashIdentifierElement; const TestNode: TCSSNode
-  ): TCSSSpecifity;
+  Identifier: TCSSHashIdentifierElement; const TestNode: TCSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   aValue: TCSSString;
   aValue: TCSSString;
 begin
 begin
+  if OnlySpecifity then
+    exit(CSSSpecifityIdentifier);
   Result:=CSSSpecifityNoMatch;
   Result:=CSSSpecifityNoMatch;
   aValue:=Identifier.Value;
   aValue:=Identifier.Value;
   if TestNode.GetCSSID=aValue then
   if TestNode.GetCSSID=aValue then
@@ -491,10 +546,13 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorClassNameMatches(
 function TCSSResolver.SelectorClassNameMatches(
-  aClassName: TCSSClassNameElement; const TestNode: TCSSNode): TCSSSpecifity;
+  aClassName: TCSSClassNameElement; const TestNode: TCSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   aValue: TCSSString;
   aValue: TCSSString;
 begin
 begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
   aValue:=aClassName.Name;
   aValue:=aClassName.Name;
   if TestNode.HasCSSClass(aValue) then
   if TestNode.HasCSSClass(aValue) then
     Result:=CSSSpecifityClass
     Result:=CSSSpecifityClass
@@ -504,10 +562,13 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorPseudoClassMatches(
 function TCSSResolver.SelectorPseudoClassMatches(
-  aPseudoClass: TCSSPseudoClassElement; var TestNode: TCSSNode): TCSSSpecifity;
+  aPseudoClass: TCSSPseudoClassElement; var TestNode: TCSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   PseudoID: TCSSNumericalID;
   PseudoID: TCSSNumericalID;
 begin
 begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
   Result:=CSSSpecifityNoMatch;
   Result:=CSSSpecifityNoMatch;
   PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoAttribute);
   PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoAttribute);
   case PseudoID of
   case PseudoID of
@@ -547,7 +608,7 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorListMatches(aList: TCSSListElement;
 function TCSSResolver.SelectorListMatches(aList: TCSSListElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   i: Integer;
   i: Integer;
   El: TCSSElement;
   El: TCSSElement;
@@ -568,12 +629,17 @@ begin
     {$ENDIF}
     {$ENDIF}
     C:=El.ClassType;
     C:=El.ClassType;
     if (C=TCSSIdentifierElement) and (i>0) then
     if (C=TCSSIdentifierElement) and (i>0) then
-      Log(etError,20220914163218,'Type selector must be first',aList)
+    begin
+      if OnlySpecifity then
+        exit(0);
+      Log(etWarning,20220914163218,'Type selector must be first',aList);
+      exit(CSSSpecifityInvalid);
+    end
     else if C=TCSSPseudoClassElement then
     else if C=TCSSPseudoClassElement then
     begin
     begin
-      Specifity:=SelectorPseudoClassMatches(TCSSPseudoClassElement(El),aNode);
+      Specifity:=SelectorPseudoClassMatches(TCSSPseudoClassElement(El),aNode,OnlySpecifity);
     end else
     end else
-      Specifity:=SelectorMatches(El,aNode);
+      Specifity:=SelectorMatches(El,aNode,OnlySpecifity);
     if Specifity<0 then
     if Specifity<0 then
       exit(Specifity);
       exit(Specifity);
     inc(Result,Specifity);
     inc(Result,Specifity);
@@ -581,22 +647,29 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
 function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   aParent, Sibling: TCSSNode;
   aParent, Sibling: TCSSNode;
   aSpecifity: TCSSSpecifity;
   aSpecifity: TCSSSpecifity;
 begin
 begin
+  if OnlySpecifity then
+  begin
+    Result:=SelectorMatches(aBinary.Left,TestNode,true);
+    inc(Result,SelectorMatches(aBinary.Right,TestNode,true));
+    exit;
+  end;
+
   Result:=CSSSpecifityInvalid;
   Result:=CSSSpecifityInvalid;
   case aBinary.Operation of
   case aBinary.Operation of
   boGT:
   boGT:
     begin
     begin
       // child combinator >
       // child combinator >
-      Result:=SelectorMatches(aBinary.Right,TestNode);
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
       if Result<0 then exit;
       if Result<0 then exit;
       aParent:=TestNode.GetCSSParent;
       aParent:=TestNode.GetCSSParent;
       if aParent=nil then
       if aParent=nil then
         exit(CSSSpecifityNoMatch);
         exit(CSSSpecifityNoMatch);
-      aSpecifity:=SelectorMatches(aBinary.Left,aParent);
+      aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
       if aSpecifity<0 then
       if aSpecifity<0 then
         exit(aSpecifity);
         exit(aSpecifity);
       inc(Result,aSpecifity);
       inc(Result,aSpecifity);
@@ -604,12 +677,12 @@ begin
   boPlus:
   boPlus:
     begin
     begin
       // adjacent sibling combinator +
       // adjacent sibling combinator +
-      Result:=SelectorMatches(aBinary.Right,TestNode);
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
       if Result<0 then exit;
       if Result<0 then exit;
       Sibling:=TestNode.GetCSSPreviousSibling;
       Sibling:=TestNode.GetCSSPreviousSibling;
       if Sibling=nil then
       if Sibling=nil then
         exit(CSSSpecifityNoMatch);
         exit(CSSSpecifityNoMatch);
-      aSpecifity:=SelectorMatches(aBinary.Left,Sibling);
+      aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
       if aSpecifity<0 then
       if aSpecifity<0 then
         exit(aSpecifity);
         exit(aSpecifity);
       inc(Result,aSpecifity);
       inc(Result,aSpecifity);
@@ -617,12 +690,12 @@ begin
   boTilde:
   boTilde:
     begin
     begin
       // general sibling combinator ~
       // general sibling combinator ~
-      Result:=SelectorMatches(aBinary.Right,TestNode);
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
       if Result<0 then exit;
       if Result<0 then exit;
       Sibling:=TestNode.GetCSSPreviousSibling;
       Sibling:=TestNode.GetCSSPreviousSibling;
       while Sibling<>nil do
       while Sibling<>nil do
       begin
       begin
-        aSpecifity:=SelectorMatches(aBinary.Left,Sibling);
+        aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
         if aSpecifity=CSSSpecifityInvalid then
         if aSpecifity=CSSSpecifityInvalid then
           exit(aSpecifity)
           exit(aSpecifity)
         else if aSpecifity>=0 then
         else if aSpecifity>=0 then
@@ -637,14 +710,14 @@ begin
   boWhiteSpace:
   boWhiteSpace:
     begin
     begin
     // descendant combinator
     // descendant combinator
-    Result:=SelectorMatches(aBinary.Right,TestNode);
+    Result:=SelectorMatches(aBinary.Right,TestNode,false);
     if Result<0 then exit;
     if Result<0 then exit;
     aParent:=TestNode;
     aParent:=TestNode;
     repeat
     repeat
       aParent:=aParent.GetCSSParent;
       aParent:=aParent.GetCSSParent;
       if aParent=nil then
       if aParent=nil then
         exit(CSSSpecifityNoMatch);
         exit(CSSSpecifityNoMatch);
-      aSpecifity:=SelectorMatches(aBinary.Left,aParent);
+      aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
       if aSpecifity>=0 then
       if aSpecifity>=0 then
       begin
       begin
         inc(Result,aSpecifity);
         inc(Result,aSpecifity);
@@ -661,7 +734,7 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorArrayMatches(anArray: TCSSArrayElement;
 function TCSSResolver.SelectorArrayMatches(anArray: TCSSArrayElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   {$IFDEF VerboseCSSResolver}
   {$IFDEF VerboseCSSResolver}
   i: integer;
   i: integer;
@@ -672,20 +745,29 @@ var
   OldStringComparison: TCSSResStringComparison;
   OldStringComparison: TCSSResStringComparison;
   aValue: TCSSString;
   aValue: TCSSString;
 begin
 begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
+
   Result:=CSSSpecifityInvalid;
   Result:=CSSSpecifityInvalid;
   if anArray.Prefix<>nil then
   if anArray.Prefix<>nil then
-    Log(etError,20220910154004,'Invalid CSS attribute selector prefix',anArray.Prefix);
+  begin
+    Log(etWarning,20220910154004,'Invalid CSS attribute selector prefix',anArray.Prefix);
+    exit;
+  end;
   {$IFDEF VerboseCSSResolver}
   {$IFDEF VerboseCSSResolver}
   writeln('TCSSResolver.SelectorArrayMatches Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
   writeln('TCSSResolver.SelectorArrayMatches Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
   for i:=0 to anArray.ChildCount-1 do
   for i:=0 to anArray.ChildCount-1 do
     writeln('TCSSResolver.SelectorArrayMatches ',i,' ',GetCSSObj(anArray.Children[i]));
     writeln('TCSSResolver.SelectorArrayMatches ',i,' ',GetCSSObj(anArray.Children[i]));
   {$ENDIF}
   {$ENDIF}
   if anArray.ChildCount<1 then
   if anArray.ChildCount<1 then
-    Log(etError,20220910154033,'Invalid CSS attribute selector',anArray);
+  begin
+    Log(etWarning,20220910154033,'Invalid CSS attribute selector',anArray);
+    exit;
+  end;
   OldStringComparison:=StringComparison;
   OldStringComparison:=StringComparison;
   try
   try
     if anArray.ChildCount>1 then
     if anArray.ChildCount>1 then
-      begin
+    begin
       El:=anArray.Children[1];
       El:=anArray.Children[1];
       C:=El.ClassType;
       C:=El.ClassType;
       if C=TCSSIdentifierElement then
       if C=TCSSIdentifierElement then
@@ -696,13 +778,17 @@ begin
         's': FStringComparison:=crscCaseSensitive;
         's': FStringComparison:=crscCaseSensitive;
         else
         else
           if croErrorOnUnknownName in Options then
           if croErrorOnUnknownName in Options then
-            Log(etError,20220914174409,'Invalid attribute modifier "'+aValue+'"',El);
+            Log(etError,20220914174409,'Invalid attribute modifier "'+aValue+'"',El)
+          else
+            Log(etWarning,20220918084203,'Invalid attribute modifier "'+aValue+'"',El);
         end;
         end;
-      end else
-        Log(etError,20220914173643,'Invalid CSS attribute modifier',El);
+      end else begin
+        Log(etWarning,20220914173643,'Invalid CSS attribute modifier',El);
+        exit;
       end;
       end;
-    if (anArray.ChildCount>2) and (croErrorOnUnknownName in Options) then
-      Log(etError,20220914174550,'Invalid CSS attribute modifier',anArray.Children[2]);
+    end;
+    if (anArray.ChildCount>2) then
+      Log(etWarning,20220914174550,'Invalid CSS attribute modifier',anArray.Children[2]);
 
 
     El:=anArray.Children[0];
     El:=anArray.Children[0];
     C:=El.ClassType;
     C:=El.ClassType;
@@ -728,8 +814,9 @@ begin
       end;
       end;
     end else if C=TCSSBinaryElement then
     end else if C=TCSSBinaryElement then
       Result:=SelectorArrayBinaryMatches(TCSSBinaryElement(El),TestNode)
       Result:=SelectorArrayBinaryMatches(TCSSBinaryElement(El),TestNode)
-    else
+    else begin
       Log(etError,20220910153725,'Invalid CSS array selector',El);
       Log(etError,20220910153725,'Invalid CSS array selector',El);
+    end;
   finally
   finally
     FStringComparison:=OldStringComparison;
     FStringComparison:=OldStringComparison;
   end;
   end;
@@ -811,31 +898,121 @@ begin
 end;
 end;
 
 
 function TCSSResolver.SelectorCallMatches(aCall: TCSSCallElement;
 function TCSSResolver.SelectorCallMatches(aCall: TCSSCallElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
 var
 var
   CallID: TCSSNumericalID;
   CallID: TCSSNumericalID;
 begin
 begin
   Result:=CSSSpecifityNoMatch;
   Result:=CSSSpecifityNoMatch;
   CallID:=ResolveCall(aCall);
   CallID:=ResolveCall(aCall);
   case CallID of
   case CallID of
-  CSSCallID_NthChild:
-    Result:=Call_NthChild(aCall,TestNode);
+  CSSCallID_Not:
+    Result:=Call_Not(aCall,TestNode,OnlySpecifity);
+  CSSCallID_Is:
+    Result:=Call_Is(aCall,TestNode,OnlySpecifity);
+  CSSCallID_Where:
+    Result:=Call_Where(aCall,TestNode,OnlySpecifity);
+  CSSCallID_NthChild,CSSCallID_NthLastChild,CSSCallID_NthOfType, CSSCallID_NthLastOfType:
+    Result:=Call_NthChild(CallID,aCall,TestNode,OnlySpecifity);
   else
   else
-    Result:=CSSSpecifityInvalid;
+    if OnlySpecifity then
+      Result:=0
+    else
+      Result:=CSSSpecifityInvalid;
   end;
   end;
 end;
 end;
 
 
-function TCSSResolver.Call_NthChild(aCall: TCSSCallElement;
-  const TestNode: TCSSNode): TCSSSpecifity;
+function TCSSResolver.Call_Not(aCall: TCSSCallElement;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+// :not(arg1, arg2, ...)
+// :not(args) has the same specifity as :not(:is(args))
+var
+  i: Integer;
+  Specifity: TCSSSpecifity;
+  HasMatch: Boolean;
+begin
+  Result:=0;
+  HasMatch:=false;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
+    if Specifity>=0 then
+      HasMatch:=true
+    else begin
+      // the specifity of :is is the highest, independent of matching (forgiving)
+      Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
+    end;
+    if Specifity>Result then
+      Result:=Specifity;
+  end;
+  if OnlySpecifity then
+    // return best
+  else if HasMatch then
+    Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_Is(aCall: TCSSCallElement; const TestNode: TCSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  i: Integer;
+  Specifity: TCSSSpecifity;
+  ok: Boolean;
+begin
+  Result:=0;
+  ok:=false;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
+    if Specifity>=0 then
+      ok:=true
+    else begin
+      // the specifity of :is is the highest, independent of matching (forgiving)
+      Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
+    end;
+    if Specifity>Result then
+      Result:=Specifity;
+  end;
+  if (not ok) and (not OnlySpecifity) then
+    Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_Where(aCall: TCSSCallElement;
+  const TestNode: TCSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  i: Integer;
+begin
+  Result:=0;
+  if OnlySpecifity then
+    exit;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    if SelectorMatches(aCall.Args[i],TestNode,false)>=0 then
+      // Note: :where is forgiving, so invalid arguments are ignored
+      exit;
+  end;
+  Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_NthChild(CallID: TCSSNumericalID;
+  aCall: TCSSCallElement; const TestNode: TCSSNode; OnlySpecifity: boolean
+  ): TCSSSpecifity;
+
+  procedure NthWarn(const ID: TCSSMsgID; const Msg: string; PosEl: TCSSElement);
+  begin
+    Log(etWarning,ID,CSSPseudoNames[CallID]+' '+Msg,PosEl);
+  end;
+
 var
 var
   i, ArgCount, aModulo, aStart: Integer;
   i, ArgCount, aModulo, aStart: Integer;
   Arg, OffsetEl: TCSSElement;
   Arg, OffsetEl: TCSSElement;
   Str: TCSSString;
   Str: TCSSString;
-  UnaryEl: TCSSUnaryElement;
+  UnaryEl, anUnary: TCSSUnaryElement;
   Params: TCSSCallNthChildParams;
   Params: TCSSCallNthChildParams;
   CallData: TCSSCallData;
   CallData: TCSSCallData;
 begin
 begin
-  Result:=CSSSpecifityInvalid;
+  if OnlySpecifity then
+    Result:=CSSSpecifityClass
+  else
+    Result:=CSSSpecifityInvalid;
   CallData:=TCSSCallData(aCall.CustomData);
   CallData:=TCSSCallData(aCall.CustomData);
   Params:=TCSSCallNthChildParams(CallData.Params);
   Params:=TCSSCallNthChildParams(CallData.Params);
   if Params=nil then
   if Params=nil then
@@ -850,11 +1027,11 @@ begin
 
 
     i:=0;
     i:=0;
     aModulo:=0;
     aModulo:=0;
-    aStart:=0;
+    aStart:=1;
     // check step
     // check step
     if ArgCount<=i then
     if ArgCount<=i then
     begin
     begin
-      Log(etWarning,20220915143843,':nth-child missing arguments',aCall);
+      NthWarn(20220915143843,'missing arguments',aCall);
       exit;
       exit;
     end;
     end;
     Arg:=aCall.Args[i];
     Arg:=aCall.Args[i];
@@ -865,18 +1042,18 @@ begin
       // check n
       // check n
       if ArgCount<=i then
       if ArgCount<=i then
       begin
       begin
-        Log(etWarning,20220915143843,':nth-child missing arguments',aCall);
+        NthWarn(20220915143843,'missing arguments',aCall);
         exit;
         exit;
       end;
       end;
       Arg:=aCall.Args[i];
       Arg:=aCall.Args[i];
       if Arg.ClassType<>TCSSIdentifierElement then
       if Arg.ClassType<>TCSSIdentifierElement then
       begin
       begin
-        Log(etWarning,20220915144312,':nth-child expected n',Arg);
+        NthWarn(20220915144312,'expected n',Arg);
         exit;
         exit;
       end;
       end;
       if TCSSIdentifierElement(Arg).Value<>'n' then
       if TCSSIdentifierElement(Arg).Value<>'n' then
       begin
       begin
-        Log(etWarning,20220915144359,':nth-child expected n',Arg);
+        NthWarn(20220915144359,'expected n',Arg);
         exit;
         exit;
       end;
       end;
 
 
@@ -887,29 +1064,45 @@ begin
       case lowercase(Str) of
       case lowercase(Str) of
       'even':
       'even':
         begin
         begin
-        writeln('TCSSResolver.Call_NthChild EVEN');
+        //writeln('TCSSResolver.Call_NthChild EVEN');
         aModulo:=2;
         aModulo:=2;
         aStart:=2;
         aStart:=2;
         end;
         end;
       'odd':
       'odd':
         begin
         begin
-        writeln('TCSSResolver.Call_NthChild ODD');
+        //writeln('TCSSResolver.Call_NthChild ODD');
         aModulo:=2;
         aModulo:=2;
-        aStart:=1;
         end;
         end;
       'n':
       'n':
         begin
         begin
-        writeln('TCSSResolver.Call_NthChild N');
+        //writeln('TCSSResolver.Call_NthChild N');
         aModulo:=1;
         aModulo:=1;
-        aStart:=1;
         end;
         end;
       else
       else
-        Log(etWarning,20220915150332,':nth-child expected multiplier',Arg);
+        NthWarn(20220915150332,'expected multiplier',Arg);
+        exit;
+      end
+    end else if Arg.ClassType=TCSSUnaryElement then
+    begin
+      anUnary:=TCSSUnaryElement(Arg);
+      case anUnary.Operation of
+      uoMinus: aModulo:=-1;
+      uoPlus: aModulo:=1;
+      else
+        NthWarn(20220917080309,'expected multiplier',Arg);
+        exit;
+      end;
+      if (anUnary.Right.ClassType=TCSSIdentifierElement)
+          and (SameText(TCSSIdentifierElement(anUnary.Right).Value,'n')) then
+      begin
+        // ok
+      end else begin
+        NthWarn(20220917080154,'expected multiplier',Arg);
         exit;
         exit;
       end;
       end;
     end else
     end else
     begin
     begin
-      Log(etWarning,20220915144056,':nth-child expected multiplier',Arg);
+      NthWarn(20220915144056,'expected multiplier',Arg);
       exit;
       exit;
     end;
     end;
 
 
@@ -923,18 +1116,18 @@ begin
         //writeln('TCSSResolver.Call_NthChild UNARY ',UnaryEl.AsString);
         //writeln('TCSSResolver.Call_NthChild UNARY ',UnaryEl.AsString);
         if not (UnaryEl.Operation in [uoMinus,uoPlus]) then
         if not (UnaryEl.Operation in [uoMinus,uoPlus]) then
         begin
         begin
-          Log(etWarning,20220915151422,':nth-child unexpected offset',UnaryEl);
+          NthWarn(20220915151422,'unexpected offset',UnaryEl);
           exit;
           exit;
         end;
         end;
         OffsetEl:=UnaryEl.Right;
         OffsetEl:=UnaryEl.Right;
         if OffsetEl=nil then
         if OffsetEl=nil then
         begin
         begin
-          Log(etWarning,20220915151511,':nth-child unexpected offset',UnaryEl);
+          NthWarn(20220915151511,'unexpected offset',UnaryEl);
           exit;
           exit;
         end;
         end;
         if OffsetEl.ClassType<>TCSSIntegerElement then
         if OffsetEl.ClassType<>TCSSIntegerElement then
         begin
         begin
-          Log(etWarning,20220915151718,':nth-child unexpected offset',OffsetEl);
+          NthWarn(20220915151718,'unexpected offset',OffsetEl);
           exit;
           exit;
         end;
         end;
         aStart:=TCSSIntegerElement(OffsetEl).Value;
         aStart:=TCSSIntegerElement(OffsetEl).Value;
@@ -942,7 +1135,7 @@ begin
           aStart:=-aStart;
           aStart:=-aStart;
       end else
       end else
       begin
       begin
-        Log(etWarning,20220915150851,':nth-child expected offset',Arg);
+        NthWarn(20220915150851,'expected offset',Arg);
         exit;
         exit;
       end;
       end;
     end;
     end;
@@ -951,27 +1144,139 @@ begin
     CallData.Params:=Params;
     CallData.Params:=Params;
     Params.Modulo:=aModulo;
     Params.Modulo:=aModulo;
     Params.Start:=aStart;
     Params.Start:=aStart;
+
+    inc(i);
+    if (i<ArgCount) then
+    begin
+      Arg:=aCall.Args[i];
+      if (Arg.ClassType=TCSSIdentifierElement)
+          and (SameText(TCSSIdentifierElement(Arg).Value,'of')) then
+      begin
+        // An+B of Selector
+        inc(i);
+        if i=ArgCount then
+        begin
+          NthWarn(20220915150851,'expected selector',Arg);
+          exit;
+        end;
+        Arg:=aCall.Args[i];
+        Params.HasOf:=true;
+        Params.OfSelector:=Arg;
+      end;
+    end;
+
+    if (CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType]) then
+      Params.HasOf:=true;
   end else begin
   end else begin
     aModulo:=Params.Modulo;
     aModulo:=Params.Modulo;
     aStart:=Params.Start;
     aStart:=Params.Start;
   end;
   end;
 
 
+  if OnlySpecifity then
+  begin
+    if Params.OfSelector<>nil then
+      inc(Result,SelectorMatches(Params.OfSelector,TestNode,true));
+    exit;
+  end;
+
   Result:=CSSSpecifityNoMatch;
   Result:=CSSSpecifityNoMatch;
-  if aModulo<1 then
+  if aModulo=0 then
     exit;
     exit;
   i:=TestNode.GetCSSIndex;
   i:=TestNode.GetCSSIndex;
+  if Params.HasOf then
+  begin
+    // ToDo: cache
+    CollectSiblingsOf(CallID,TestNode,Params);
+    i:=GetSiblingOfIndex(Params.ChildIDs,i);
+  end;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.Call_NthChild CallID=',CallID,' ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex,' i=',i,' HasOf=',Params.HasOf,' OfChildCount=',length(Params.ChildIDs));
+  {$ENDIF}
   if i<0 then
   if i<0 then
     exit;
     exit;
-  i:=i+1-aStart;
+  if CallID in [CSSCallID_NthLastChild,CSSCallID_NthLastOfType] then
+  begin
+    if Params.HasOf then
+      i:=length(Params.ChildIDs)-i
+    else
+      i:=GetSiblingCount(TestNode)-i;
+  end else
+  begin
+    i:=i+1;
+  end;
+  dec(i,aStart);
   if i mod aModulo = 0 then
   if i mod aModulo = 0 then
   begin
   begin
     i:=i div aModulo;
     i:=i div aModulo;
     if i>=0 then
     if i>=0 then
-      Result:=CSSSpecifityClass
+      Result:=CSSSpecifityClass;
   end;
   end;
   {$IFDEF VerboseCSSResolver}
   {$IFDEF VerboseCSSResolver}
-  writeln('TCSSResolver.Call_NthChild ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex+1,' Result=',Result);
+  //writeln('TCSSResolver.Call_NthChild ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex+1,' Result=',Result);
+  {$ENDIF}
+end;
+
+procedure TCSSResolver.CollectSiblingsOf(CallID: TCSSNumericalID;
+  TestNode: TCSSNode; Params: TCSSCallNthChildParams);
+var
+  Cnt, i: Integer;
+  TestID: TCSSNumericalID;
+  aParent, aNode: TCSSNode;
+  aSelector: TCSSElement;
+begin
+  Params.HasOf:=true;
+  aParent:=TestNode.GetCSSParent;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.CollectSiblingsOf HasParent=',aParent<>nil);
   {$ENDIF}
   {$ENDIF}
+  if aParent=nil then exit;
+  Cnt:=aParent.GetCSSChildCount;
+  SetLength(Params.ChildIDs,Cnt);
+  TestID:=CSSIDNone;
+  if CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType] then
+    TestID:=TestNode.GetCSSTypeID;
+  Cnt:=0;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.CollectSiblingsOf Candidates=',length(Params.ChildIDs),' TestID=',TestID);
+  {$ENDIF}
+  aSelector:=Params.OfSelector;
+  for i:=0 to length(Params.ChildIDs)-1 do
+  begin
+    aNode:=aParent.GetCSSChild(i);
+    if (Testid<>CSSIDNone)
+        and (TestID<>aNode.GetCSSTypeID) then
+      continue;
+    if (aSelector<>nil) and (SelectorMatches(aSelector,aNode,false)<0) then
+      continue;
+    Params.ChildIDs[Cnt]:=i;
+    {$IFDEF VerboseCSSResolver}
+    //writeln('TCSSResolver.CollectSiblingsOf ',Cnt,'=>',i,' CSSTypeID=',aNode.GetCSSTypeID,' Sel=',GetCSSObj(aSelector));
+    {$ENDIF}
+    inc(Cnt);
+  end;
+  SetLength(Params.ChildIDs,Cnt);
+end;
+
+function TCSSResolver.GetSiblingOfIndex(SiblingIDs: TIntegerDynArray;
+  Index: integer): integer;
+// searches the position of Index in a sorted array
+var
+  l, r, m: Integer;
+begin
+  l:=0;
+  r:=length(SiblingIDs)-1;
+  while l<=r do
+  begin
+    m:=(l+r) div 2;
+    Result:=SiblingIDs[m];
+    if Index<Result then
+      r:=m-1
+    else if Index>Result then
+      l:=m+1
+    else
+      exit(m);
+  end;
+  Result:=-1;
 end;
 end;
 
 
 function TCSSResolver.ComputeValue(El: TCSSElement): TCSSString;
 function TCSSResolver.ComputeValue(El: TCSSElement): TCSSString;
@@ -1112,6 +1417,30 @@ begin
   until p>WordsLen;
   until p>WordsLen;
 end;
 end;
 
 
+function TCSSResolver.GetSiblingCount(aNode: TCSSNode): integer;
+var
+  aParent, CurNode: TCSSNode;
+begin
+  if aNode=nil then
+    exit(0);
+  aParent:=aNode.GetCSSParent;
+  if aParent<>nil then
+    exit(aParent.GetCSSChildCount);
+  Result:=0;
+  CurNode:=aNode;
+  while CurNode<>nil do
+  begin
+    inc(Result);
+    CurNode:=CurNode.GetCSSPreviousSibling;
+  end;
+  CurNode:=aNode.GetCSSNextSibling;
+  while CurNode<>nil do
+  begin
+    inc(Result);
+    CurNode:=CurNode.GetCSSNextSibling;
+  end;
+end;
+
 procedure TCSSResolver.MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity);
 procedure TCSSResolver.MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity);
 var
 var
   C: TClass;
   C: TClass;
@@ -1244,7 +1573,14 @@ begin
     Result:=CSSIDNone;
     Result:=CSSIDNone;
 
 
     case aName of
     case aName of
+    ':not': Result:=CSSCallID_Not;
+    ':is': Result:=CSSCallID_Is;
+    ':where': Result:=CSSCallID_Where;
+    ':has': Result:=CSSCallID_Has;
     ':nth-child': Result:=CSSCallID_NthChild;
     ':nth-child': Result:=CSSCallID_NthChild;
+    ':nth-last-child': Result:=CSSCallID_NthLastChild;
+    ':nth-of-type': Result:=CSSCallID_NthOfType;
+    ':nth-last-of-type': Result:=CSSCallID_NthLastOfType;
     else
     else
       if croErrorOnUnknownName in FOptions then
       if croErrorOnUnknownName in FOptions then
         Log(etError,20220914193946,'TCSSResolver.ResolveCall unknown "'+El.Name+'"',El);
         Log(etError,20220914193946,'TCSSResolver.ResolveCall unknown "'+El.Name+'"',El);