Browse Source

fcl-css: resolver: test warnings

mattias 11 months ago
parent
commit
ccbec9ee7d

+ 0 - 1
packages/fcl-css/src/fpcssresolver.pas

@@ -857,7 +857,6 @@ begin
     ms.Write(Src[1],length(Src)*SizeOf(TCSSChar));
     ms.Write(Src[1],length(Src)*SizeOf(TCSSChar));
     ms.Position:=0;
     ms.Position:=0;
     aParser:=TCSSResolverParser.Create(ms); // ss is freed by the parser
     aParser:=TCSSResolverParser.Create(ms); // ss is freed by the parser
-    aParser.Registry:=CSSRegistry;
     aParser.Resolver:=Self;
     aParser.Resolver:=Self;
     aParser.OnLog:=@Log;
     aParser.OnLog:=@Log;
     aParser.CSSNthChildParamsClass:=TCSSResolverNthChildParams;
     aParser.CSSNthChildParamsClass:=TCSSResolverNthChildParams;

+ 70 - 26
packages/fcl-css/src/fpcssresparser.pas

@@ -407,7 +407,7 @@ type
     function IndexOfPseudoClassName(const aName: TCSSString): TCSSNumericalID; overload;
     function IndexOfPseudoClassName(const aName: TCSSString): TCSSNumericalID; overload;
     property PseudoClassCount: TCSSNumericalID read FPseudoClassCount;
     property PseudoClassCount: TCSSNumericalID read FPseudoClassCount;
   public
   public
-    // pseudo functions
+    // pseudo functions lowercase (they are parsed case insensitive)
     PseudoFunctions: TCSSStringArray;
     PseudoFunctions: TCSSStringArray;
     function AddPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
     function AddPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
     function IndexOfPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
     function IndexOfPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
@@ -564,6 +564,13 @@ type
     procedure SkipToEndOfAttribute(var p: PCSSChar);
     procedure SkipToEndOfAttribute(var p: PCSSChar);
     function SkipString(var p: PCSSChar): boolean;
     function SkipString(var p: PCSSChar): boolean;
     function SkipBrackets(var p: PCSSChar; Lvl: integer = 1): boolean;
     function SkipBrackets(var p: PCSSChar; Lvl: integer = 1): boolean;
+    // registry
+    function GetAttributeID(const aName: TCSSString): TCSSNumericalID; virtual;
+    function GetAttributeDesc(AttrID: TCSSNumericalID): TCSSAttributeDesc; virtual;
+    function GetTypeID(const aName: TCSSString): TCSSNumericalID; virtual;
+    function GetPseudoClassID(const aName: TCSSString): TCSSNumericalID; virtual;
+    function GetPseudoFunctionID(const aName: TCSSString): TCSSNumericalID; virtual;
+
     property CSSRegistry: TCSSRegistry read FCSSRegistry write SetCSSRegistry;
     property CSSRegistry: TCSSRegistry read FCSSRegistry write SetCSSRegistry;
   end;
   end;
 
 
@@ -574,10 +581,10 @@ type
   TCSSResolverParser = class(TCSSParser)
   TCSSResolverParser = class(TCSSParser)
   private
   private
     FOnLog: TCSSValueParserLogEvent;
     FOnLog: TCSSValueParserLogEvent;
-    FRegistry: TCSSRegistry;
     FResolver: TCSSBaseResolver;
     FResolver: TCSSBaseResolver;
   protected
   protected
-    function ResolveIdentifier(El: TCSSResolvedIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
+    function ResolveAttribute(El: TCSSResolvedIdentifierElement): TCSSNumericalID; virtual;
+    function ResolveType(El: TCSSResolvedIdentifierElement): TCSSNumericalID; virtual;
     function ResolvePseudoClass(El: TCSSResolvedPseudoClassElement): TCSSNumericalID; virtual;
     function ResolvePseudoClass(El: TCSSResolvedPseudoClassElement): TCSSNumericalID; virtual;
     function ResolvePseudoFunction(El: TCSSResolvedCallElement): TCSSNumericalID; virtual;
     function ResolvePseudoFunction(El: TCSSResolvedCallElement): TCSSNumericalID; virtual;
     function ParseCall(aName: TCSSString; IsSelector: boolean): TCSSCallElement; override;
     function ParseCall(aName: TCSSString; IsSelector: boolean): TCSSCallElement; override;
@@ -597,7 +604,6 @@ type
     destructor Destroy; override;
     destructor Destroy; override;
     procedure Log(MsgType: TEventType; const ID: TCSSMsgID; const Msg: TCSSString; PosEl: TCSSElement); virtual;
     procedure Log(MsgType: TEventType; const ID: TCSSMsgID; const Msg: TCSSString; PosEl: TCSSElement); virtual;
     class function IsWhiteSpace(const s: TCSSString): boolean; virtual; overload;
     class function IsWhiteSpace(const s: TCSSString): boolean; virtual; overload;
-    property Registry: TCSSRegistry read FRegistry write FRegistry;
     property Resolver: TCSSBaseResolver read FResolver write FResolver;
     property Resolver: TCSSBaseResolver read FResolver write FResolver;
     property OnLog: TCSSValueParserLogEvent read FOnLog write FOnLog;
     property OnLog: TCSSValueParserLogEvent read FOnLog write FOnLog;
   end;
   end;
@@ -1175,6 +1181,8 @@ begin
     raise ECSSParser.Create('missing name');
     raise ECSSParser.Create('missing name');
   if length(aName)>255 then
   if length(aName)>255 then
     raise ECSSParser.Create('pseudo function name too long');
     raise ECSSParser.Create('pseudo function name too long');
+  if aName<>LowerCase(aName) then
+    raise ECSSParser.Create('pseudo function name not lowercase');
   Result:=IndexOfKeyword(aName);
   Result:=IndexOfKeyword(aName);
   if Result>0 then
   if Result>0 then
     raise ECSSParser.Create('duplicate pseudo function "'+aName+'"');
     raise ECSSParser.Create('duplicate pseudo function "'+aName+'"');
@@ -2044,30 +2052,67 @@ begin
   until false;
   until false;
 end;
 end;
 
 
+function TCSSBaseResolver.GetAttributeID(const aName: TCSSString): TCSSNumericalID;
+begin
+  Result:=CSSRegistry.IndexOfAttributeName(aName);
+end;
+
+function TCSSBaseResolver.GetAttributeDesc(AttrID: TCSSNumericalID): TCSSAttributeDesc;
+begin
+  if (AttrID>0) and (AttrID<CSSRegistry.AttributeCount) then
+    Result:=CSSRegistry.Attributes[AttrID]
+  else
+    Result:=nil;
+end;
+
+function TCSSBaseResolver.GetTypeID(const aName: TCSSString): TCSSNumericalID;
+begin
+  Result:=CSSRegistry.IndexOfTypeName(aName);
+end;
+
+function TCSSBaseResolver.GetPseudoClassID(const aName: TCSSString): TCSSNumericalID;
+begin
+  Result:=CSSRegistry.IndexOfPseudoClassName(aName);
+end;
+
+function TCSSBaseResolver.GetPseudoFunctionID(const aName: TCSSString): TCSSNumericalID;
+begin
+  Result:=CSSRegistry.IndexOfPseudoFunction(aName);
+end;
+
 { TCSSResolverParser }
 { TCSSResolverParser }
 
 
-function TCSSResolverParser.ResolveIdentifier(El: TCSSResolvedIdentifierElement;
-  Kind: TCSSNumericalIDKind): TCSSNumericalID;
+function TCSSResolverParser.ResolveAttribute(El: TCSSResolvedIdentifierElement): TCSSNumericalID;
 var
 var
   aName: TCSSString;
   aName: TCSSString;
 begin
 begin
   if El.NumericalID<>CSSIDNone then
   if El.NumericalID<>CSSIDNone then
     raise Exception.Create('20240701143234');
     raise Exception.Create('20240701143234');
   aName:=El.Name;
   aName:=El.Name;
-  if Kind=nikPseudoClass then
+  El.Kind:=nikAttribute;
+  Result:=Resolver.GetAttributeID(aName);
+  writeln('AAA1 TCSSResolverParser.ResolveAttribute ',aName,' ',Result);
+  if Result<=CSSIDNone then
   begin
   begin
-    // pseudo classes are ASCII case insensitive
-    System.Delete(aName,1,1);
-    aName:=lowercase(aName);
-  end;
+    El.NumericalID:=-1;
+    Log(etWarning,20240625130648,'unknown attribute "'+aName+'"',El);
+  end else
+    El.NumericalID:=Result;
+end;
 
 
-  El.Kind:=Kind;
-  Result:=Registry.IndexOfNamedItem(Kind,aName);
-  //writeln('TCSSResolverParser.ResolveIdentifier ',aName,' ID=',Result);
-  if Result=CSSIDNone then
+function TCSSResolverParser.ResolveType(El: TCSSResolvedIdentifierElement): TCSSNumericalID;
+var
+  aName: TCSSString;
+begin
+  if El.NumericalID<>CSSIDNone then
+    raise Exception.Create('20240822133813');
+  aName:=El.Name;
+  El.Kind:=nikType;
+  Result:=Resolver.GetTypeID(aName);
+  if Result<=CSSIDNone then
   begin
   begin
     El.NumericalID:=-1;
     El.NumericalID:=-1;
-    Log(etWarning,20240625130648,'unknown '+CSSNumericalIDKindNames[Kind]+' "'+aName+'"',El);
+    Log(etWarning,20240822133816,'unknown type "'+aName+'"',El);
   end else
   end else
     El.NumericalID:=Result;
     El.NumericalID:=Result;
 end;
 end;
@@ -2086,7 +2131,7 @@ begin
     raise Exception.Create('20240701143234');
     raise Exception.Create('20240701143234');
 
 
   El.Kind:=nikPseudoClass;
   El.Kind:=nikPseudoClass;
-  Result:=Registry.IndexOfNamedItem(nikPseudoClass,aName);
+  Result:=Resolver.GetPseudoClassID(aName);
   //writeln('TCSSResolverParser.ResolvePseudoClass ',aName,' ID=',Result);
   //writeln('TCSSResolverParser.ResolvePseudoClass ',aName,' ID=',Result);
   if Result<=CSSIDNone then
   if Result<=CSSIDNone then
   begin
   begin
@@ -2112,12 +2157,12 @@ begin
   aName:=lowercase(aName);
   aName:=lowercase(aName);
 
 
   El.Kind:=nikPseudoFunction;
   El.Kind:=nikPseudoFunction;
-  Result:=Registry.IndexOfNamedItem(nikPseudoFunction,aName);
+  Result:=Resolver.GetPseudoFunctionID(aName);
   //writeln('TCSSResolverParser.ResolvePseudoFunction ',aName,' ID=',Result);
   //writeln('TCSSResolverParser.ResolvePseudoFunction ',aName,' ID=',Result);
-  if Result=CSSIDNone then
+  if Result<=CSSIDNone then
   begin
   begin
     El.NameNumericalID:=-1;
     El.NameNumericalID:=-1;
-    Log(etWarning,20240625130648,'unknown pseudo class "'+aName+'"',El);
+    Log(etWarning,20240625130648,'unknown pseudo function "'+aName+'"',El);
   end else
   end else
     El.NameNumericalID:=Result;
     El.NameNumericalID:=Result;
 end;
 end;
@@ -2165,8 +2210,7 @@ begin
   aKey:=Result.Keys[0];
   aKey:=Result.Keys[0];
   if aKey is TCSSResolvedIdentifierElement then
   if aKey is TCSSResolvedIdentifierElement then
   begin
   begin
-    // todo: custom attributes
-    AttrId:=ResolveIdentifier(TCSSResolvedIdentifierElement(aKey),nikAttribute);
+    AttrId:=ResolveAttribute(TCSSResolvedIdentifierElement(aKey));
 
 
     if aKey.CustomData<>nil then
     if aKey.CustomData<>nil then
       raise Exception.Create('20240626113536');
       raise Exception.Create('20240626113536');
@@ -2188,7 +2232,7 @@ begin
 
 
     if AttrId>=CSSAttributeID_All then
     if AttrId>=CSSAttributeID_All then
     begin
     begin
-      Desc:=Registry.Attributes[AttrId];
+      Desc:=Resolver.GetAttributeDesc(AttrId);
 
 
       if Pos('var(',AttrData.Value)>0 then
       if Pos('var(',AttrData.Value)>0 then
       begin
       begin
@@ -2221,7 +2265,7 @@ begin
   C:=El.ClassType;
   C:=El.ClassType;
   if C=TCSSResolvedIdentifierElement then
   if C=TCSSResolvedIdentifierElement then
     // e.g. div {}
     // e.g. div {}
-    ResolveIdentifier(TCSSResolvedIdentifierElement(El),nikType)
+    ResolveType(TCSSResolvedIdentifierElement(El))
   else if C=TCSSHashIdentifierElement then
   else if C=TCSSHashIdentifierElement then
     // e.g. #id {}
     // e.g. #id {}
   else if C=TCSSClassNameElement then
   else if C=TCSSClassNameElement then
@@ -2293,7 +2337,7 @@ begin
   if C=TCSSResolvedIdentifierElement then
   if C=TCSSResolvedIdentifierElement then
   begin
   begin
     // [name]  ->  has explicit attribute
     // [name]  ->  has explicit attribute
-    ResolveIdentifier(TCSSResolvedIdentifierElement(El),nikAttribute);
+    ResolveAttribute(TCSSResolvedIdentifierElement(El));
   end else if C=TCSSBinaryElement then
   end else if C=TCSSBinaryElement then
     CheckSelectorArrayBinary(TCSSBinaryElement(El))
     CheckSelectorArrayBinary(TCSSBinaryElement(El))
   else begin
   else begin
@@ -2313,7 +2357,7 @@ begin
     Log(etWarning,20240625154314,'Invalid CSS array selector, expected attribute',Left);
     Log(etWarning,20240625154314,'Invalid CSS array selector, expected attribute',Left);
     exit;
     exit;
   end;
   end;
-  ResolveIdentifier(TCSSResolvedIdentifierElement(Left),nikAttribute);
+  ResolveAttribute(TCSSResolvedIdentifierElement(Left));
 
 
   Right:=aBinary.Right;
   Right:=aBinary.Right;
   C:=Right.ClassType;
   C:=Right.ClassType;

+ 1693 - 1629
packages/fcl-css/tests/tccssresolver.pp

@@ -322,6 +322,7 @@ type
   private
   private
     FCSSResolver: TCSSResolver;
     FCSSResolver: TCSSResolver;
     FStyle: TCSSString;
     FStyle: TCSSString;
+    procedure OnResolverLog(Sender: TObject; Entry: TCSSResolverLogEntry);
   protected
   protected
     procedure ApplyTypeStyles; virtual;
     procedure ApplyTypeStyles; virtual;
     procedure SetStyle(const AValue: TCSSString); virtual;
     procedure SetStyle(const AValue: TCSSString); virtual;
@@ -344,6 +345,8 @@ type
   protected
   protected
     procedure SetUp; override;
     procedure SetUp; override;
     procedure TearDown; override;
     procedure TearDown; override;
+    procedure ApplyStyle; virtual;
+    procedure CheckWarnings; virtual;
   public
   public
     property Doc: TDemoDocument read FDoc;
     property Doc: TDemoDocument read FDoc;
   end;
   end;
@@ -421,6 +424,8 @@ type
     procedure Test_Origin_Id_Class;
     procedure Test_Origin_Id_Class;
 
 
     // var()
     // var()
+    procedure Test_Var;
+    // todo: Test_Var_Inline; // var() in inline, custom attr in inline
 
 
     // skipping for forward compatibility
     // skipping for forward compatibility
     // ToDo: invalid token in selector makes selector invalid
     // ToDo: invalid token in selector makes selector invalid
@@ -459,2121 +464,2180 @@ begin
   Result:=s;
   Result:=s;
 end;
 end;
 
 
-{ TCustomTestNewCSSResolver }
+{ TDemoDiv }
 
 
-procedure TCustomTestNewCSSResolver.SetUp;
-var
-  AttrDesc: TCSSAttributeDesc;
+class function TDemoDiv.CSSTypeName: TCSSString;
 begin
 begin
-  inherited SetUp;
-
-  TDemoNode.CSSRegistry:=TDemoCSSRegistry.Create();
+  Result:=DemoElementTypeNames[detDiv];
+end;
 
 
-  // register button attribute 'caption'
-  AttrDesc:=TDemoNode.CSSRegistry.AddAttribute('caption');
-  TDemoButton.CSSCaptionID:=AttrDesc.Index;
+class function TDemoDiv.GetClassCSSTypeID: TCSSNumericalID;
+begin
+  Result:=FDemoDivTypeID;
+end;
 
 
-  FDoc:=TDemoDocument.Create(nil);
+class procedure TDemoDiv.SetClassCSSTypeID(aID: TCSSNumericalID);
+begin
+  FDemoDivTypeID:=aID;
 end;
 end;
 
 
-procedure TCustomTestNewCSSResolver.TearDown;
+class function TDemoDiv.GetCSSTypeStyle: TCSSString;
 begin
 begin
-  FreeAndNil(FDoc);
-  FreeAndNil(TDemoNode.CSSRegistry);
-  inherited TearDown;
+  Result:='div{ display: block }';
 end;
 end;
 
 
-{ TTestNewCSSResolver }
+{ TDemoSpan }
 
 
-procedure TTestNewCSSResolver.Test_ParseAttr_Keyword;
+class function TDemoSpan.CSSTypeName: TCSSString;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Style:='* { direction: ltr; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.direction','ltr',Doc.Root.Direction);
+  Result:=DemoElementTypeNames[detSpan];
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_ParseAttr_Keyword_SkipInvalid;
+class function TDemoSpan.GetClassCSSTypeID: TCSSNumericalID;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Style:='* { direction: something ltr; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.direction','ltr',Doc.Root.Direction);
+  Result:=FDemoSpanTypeID;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_ParseAttr_Float;
-var
-  Div1: TDemoDiv;
+class procedure TDemoSpan.SetClassCSSTypeID(aID: TCSSNumericalID);
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Style:=
-     ':root {'
-    +'  left: 10px;'
-    +'  top: .1px;'
-    +'  width: 3e2em;'
-    +'  height: 3e-2px;'
-    +'}'
-    +'div {'
-    +'  left: -4mm;'
-    +'  top: -.5pc;'
-    +'  width: .6cm;'
-    +'  height: 6E+1rem;'
-    +'}';
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','10px',Doc.Root.Left);
-  AssertEquals('Root.Top','0.1px',Doc.Root.Top);
-  AssertEquals('Root.Width','300em',Doc.Root.Width);
-  AssertEquals('Root.Height','0.03px',Doc.Root.Height);
-  AssertEquals('Div1.Left','-4mm',Div1.Left);
-  AssertEquals('Div1.Top','-0.5pc',Div1.Top);
-  AssertEquals('Div1.Width','0.6cm',Div1.Width);
-  AssertEquals('Div1.Height','60rem',Div1.Height);
+  FDemoSpanTypeID:=aID;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_ParseAttr_Float_SkipInvalid;
+class function TDemoSpan.GetCSSTypeStyle: TCSSString;
 begin
 begin
-  exit;
+  Result:='span{display: inline-block }';
+end;
 
 
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Style:=
-     ':root {'
-    +'  left: something 10px;'
-    +'  top: 1 px;' // no space between number
-    +'  width: 0 px;' // the px is ignored because of the space, 0 without unit is allowed
-    +'  height: -4cm;' // no negative
-    +'}';
+{ TDemoButton }
 
 
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','10px',Doc.Root.Left);
-  AssertEquals('Root.Top','invalid',Doc.Root.Top);
-  AssertEquals('Root.Width','0',Doc.Root.Width);
-  AssertEquals('Root.Height','invalid',Doc.Root.Height);
+procedure TDemoButton.SetCaption(const AValue: TCSSString);
+begin
+  if FCaption=AValue then Exit;
+  FCaption:=AValue;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Universal;
+class function TDemoButton.CSSTypeName: TCSSString;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Style:='* { left: 10px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','10px',Doc.Root.Left);
+  Result:=DemoElementTypeNames[detButton];
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Type;
-var
-  Button: TDemoButton;
+class function TDemoButton.GetClassCSSTypeID: TCSSNumericalID;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Button:=TDemoButton.Create(nil);
-  Button.Parent:=Doc.Root;
-  Doc.Style:='button { left: 11px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button.left','11px',Button.Left);
+  Result:=FDemoButtonTypeID;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Type_Spaces;
-var
-  Button1, Button2: TDemoButton;
+class procedure TDemoButton.SetClassCSSTypeID(aID: TCSSNumericalID);
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-
-  Button2:=TDemoButton.Create(nil);
-  Button2.Parent:=Doc.Root;
-
-  Doc.Style:='div, button ,span { left: 11px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','11px',Button1.Left);
-  AssertEquals('Button2.left','11px',Button2.Left);
+  FDemoButtonTypeID:=aID;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Id;
-var
-  Button1: TDemoButton;
+class function TDemoButton.GetCSSTypeStyle: TCSSString;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
-  Button1.Parent:=Doc.Root;
-  Doc.Style:='#Button1 { left: 12px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','12px',Button1.Left);
+  Result:='button{display: inline-block }';
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Class;
-var
-  Button1: TDemoButton;
+function TDemoButton.HasCSSExplicitAttribute(const AttrID: TCSSNumericalID
+  ): boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Button1:=TDemoButton.Create(nil);
-  Button1.CSSClasses.Add('west');
-  Button1.Parent:=Doc.Root;
-  Doc.Style:='.west { left: 13px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','13px',Button1.Left);
+  //writeln('TDemoButton.HasCSSExplicitAttribute ',AttrID,' CSSCaptionID=',CSSCaptionID);
+  if AttrID=CSSCaptionID then
+    Result:=ExplicitCaption<>''
+  else
+    Result:=inherited HasCSSExplicitAttribute(AttrID);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_ClassClass;
-var
-  Button1, Button2: TDemoButton;
+function TDemoButton.GetCSSExplicitAttribute(const AttrID: TCSSNumericalID
+  ): TCSSString;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.CSSClasses.Add('west');
-  Button1.Parent:=Doc.Root;
+  if AttrID=CSSCaptionID then
+    Result:=ExplicitCaption
+  else
+    Result:=inherited GetCSSExplicitAttribute(AttrID);
+end;
 
 
-  Button2:=TDemoButton.Create(nil);
-  Button2.CSSClasses.DelimitedText:='west south';
-  AssertEquals('Button2.CSSClasses.Count',2,Button2.CSSClasses.Count);
-  Button2.Parent:=Doc.Root;
+{ TDemoDocument }
 
 
-  Doc.Style:='.west.south { left: 10px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','',Button1.Left);
-  AssertEquals('Button2.left','10px',Button2.Left);
+procedure TDemoDocument.SetStyle(const AValue: TCSSString);
+begin
+  if FStyle=AValue then Exit;
+  FStyle:=AValue;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_ClassSpaceClass;
-var
-  Button1: TDemoButton;
+constructor TDemoDocument.Create(AOwner: TComponent);
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.CSSClasses.Add('bird');
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.CSSClasses.Add('west');
-  Button1.Parent:=Doc.Root;
+  inherited Create(AOwner);
 
 
-  Doc.Style:='.bird .west { left: 10px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','10px',Button1.Left);
+  // create the css resolver
+  FCSSResolver:=TCSSResolver.Create(nil);
+  FCSSResolver.CSSRegistry:=TDemoNode.CSSRegistry;
+  FCSSResolver.OnLog:=@OnResolverLog;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_TypeCommaType;
-var
-  Button1: TDemoButton;
-  Div1: TDemoDiv;
+destructor TDemoDocument.Destroy;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
+  FreeAndNil(Root);
+  FreeAndNil(FCSSResolver);
+  inherited Destroy;
+end;
 
 
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
+procedure TDemoDocument.ApplyStyle;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+  procedure Traverse(Node: TDemoNode);
+  var
+    i: Integer;
+  begin
+    Node.ApplyCSS(CSSResolver);
+    for i:=0 to Node.NodeCount-1 do
+      Traverse(Node[i]);
+  end;
 
 
-  Doc.Style:='div, button { left: 10px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','10px',Button1.Left);
-  AssertEquals('Div1.left','10px',Div1.Left);
+begin
+  ApplyTypeStyles;
+
+  CSSResolver.AddStyleSheet(cssoAuthor,'test.css',Style);
+  CSSResolver.Init;
+  Traverse(Root);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_ClassGTClass;
-var
-  Div1, Div2: TDemoDiv;
+procedure TDemoDocument.OnResolverLog(Sender: TObject; Entry: TCSSResolverLogEntry);
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-  Doc.Root.CSSClasses.Add('lvl1');
+  if Sender=nil then ;
+  if Entry=nil then ;
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.CSSClasses.Add('lvl2');
-  Div1.Parent:=Doc.Root;
-
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.CSSClasses.Add('lvl3');
-  Div2.Parent:=Div1;
-
-  Doc.Style:=LinesToStr([
-  '.lvl1>.lvl2 { left: 10px; }', // set
-  '.lvl1>.lvl3 { top: 11px; }', // not set, not direct children
-  '.lvl2>.lvl3 { width: 12px; }', // set
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Root.top','',Doc.Root.Top);
-  AssertEquals('Root.width','',Doc.Root.Width);
-  AssertEquals('Div1.left','10px',Div1.Left);
-  AssertEquals('Div1.top','',Div1.Top);
-  AssertEquals('Div1.width','',Div1.Width);
-  AssertEquals('Div2.left','',Div2.Left);
-  AssertEquals('Div2.top','',Div2.Top);
-  AssertEquals('Div2.width','12px',Div2.Width);
-end;
-
-procedure TTestNewCSSResolver.Test_Selector_TypePlusType;
+procedure TDemoDocument.ApplyTypeStyles;
 var
 var
-  Button1, Button2, Button3: TDemoButton;
-  Div1: TDemoDiv;
-begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
-  Button1.Parent:=Doc.Root;
+  FoundStyles: array of TDemoNodeClass;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
+  procedure AddTypeStyle(NodeClass: TDemoNodeClass);
+  var
+    i: Integer;
+    Src, ParentSrc: TCSSString;
+    ParentNodeClass: TDemoNodeClass;
+  begin
+    for i:=0 to length(FoundStyles)-1 do
+      if FoundStyles[i]=NodeClass then exit;
+    Insert(NodeClass,FoundStyles,length(FoundStyles));
 
 
-  Button2:=TDemoButton.Create(nil);
-  Button2.Name:='Button2';
-  Button2.Parent:=Doc.Root;
+    Src:=NodeClass.GetCSSTypeStyle;
+    //writeln('AddTypeStyle ',NodeClass.ClassName,' Src="',Src,'"');
+    if Src='' then exit;
+    if NodeClass.ClassType<>TDemoNode then
+    begin
+      ParentNodeClass:=TDemoNodeClass(NodeClass.ClassParent);
+      AddTypeStyle(ParentNodeClass);
+      ParentSrc:=ParentNodeClass.GetCSSTypeStyle;
+      if Src=ParentSrc then exit;
+    end;
+    //writeln('AddTypeStyle ',NodeClass.ClassName,' [',Src,']');
+    FCSSResolver.AddStyleSheet(cssoUserAgent,NodeClass.ClassName,Src);
+  end;
 
 
-  Button3:=TDemoButton.Create(nil);
-  Button3.Name:='Button3';
-  Button3.Parent:=Doc.Root;
+  procedure CollectTypeStyles(Node: TDemoNode);
+  var
+    NodeClass: TDemoNodeClass;
+    i: Integer;
+  begin
+    NodeClass:=TDemoNodeClass(Node.ClassType);
+    AddTypeStyle(NodeClass);
+    for i:=0 to Node.NodeCount-1 do
+      CollectTypeStyles(Node[i]);
+  end;
 
 
-  Doc.Style:='div+button { left: 10px; }'; // only Button2 has a prev sibling div
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','',Button1.Left);
-  AssertEquals('Div1.left','',Div1.Left);
-  AssertEquals('Button2.left','10px',Button2.Left);
-  AssertEquals('Button3.left','',Button3.Left);
+begin
+  FoundStyles:=[];
+  CollectTypeStyles(Root);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_TypeTildeType;
+{ TDemoCSSRegistry }
+
+function TDemoCSSRegistry.OnCheck_Border(Resolver: TCSSBaseResolver): boolean;
 var
 var
-  Button1, Button2, Button3: TDemoButton;
-  Div1: TDemoDiv;
+  HasWidth, HasColor: Boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
-
-  Button2:=TDemoButton.Create(nil);
-  Button2.Parent:=Doc.Root;
-
-  Button3:=TDemoButton.Create(nil);
-  Button3.Parent:=Doc.Root;
-
-  Doc.Style:='div~button { left: 10px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Root.left','',Doc.Root.Left);
-  AssertEquals('Button1.left','',Button1.Left);
-  AssertEquals('Div1.left','',Div1.Left);
-  AssertEquals('Button2.left','10px',Button2.Left);
-  AssertEquals('Button3.left','10px',Button3.Left);
+  HasWidth:=false;
+  HasColor:=false;
+  repeat
+    case Resolver.CurComp.Kind of
+    rvkFloat:
+      if not HasWidth then
+        HasWidth:=Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths);
+    rvkKeyword:
+      if not HasColor then
+        HasColor:=(Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor);
+    end;
+  until not Resolver.ReadNext;
+  Result:=HasWidth or HasColor;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_HasAttribute;
-var
-  Button1: TDemoButton;
+function TDemoCSSRegistry.OnCheck_BorderColor(Resolver: TCSSBaseResolver): boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-  Doc.Root.ExplicitAttributes[naLeft]:='100px';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='2px';
-  Button1.ExplicitCaption:='Click Button1';
-
-  Doc.Style:=LinesToStr([
-  '[left] { top: 3px; }',
-  '[caption] { width: 4px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','3px',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Button1.Top','3px',Button1.Top);
-  AssertEquals('Button1.Width','4px',Button1.Width);
+  Result:=Resolver.CheckAttribute_Color([]);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeEquals;
-var
-  Button1: TDemoButton;
+function TDemoCSSRegistry.OnCheck_BorderWidth(Resolver: TCSSBaseResolver): boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='2px';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='3px';
-  Button1.ExplicitAttributes[naColor]:='maybe black';
-
-  Doc.Style:=LinesToStr([
-  '[left=2px] { top: 4px; }',
-  '[color="maybe black"] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Button1.Top','',Button1.Top);
-  AssertEquals('Button1.Width','5px',Button1.Width);
+  Result:=Resolver.CheckAttribute_Dimension(Chk_BorderWidth);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeEqualsI;
+procedure TDemoCSSRegistry.OnSplit_Border(Resolver: TCSSBaseResolver;
+  var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray);
 var
 var
-  Button1: TDemoButton;
+  aWidth, aColor: TCSSString;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='2px';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='3px';
-  Button1.ExplicitAttributes[naColor]:='maybe Black';
+  aWidth:='';
+  aColor:='';
+  repeat
+    case Resolver.CurComp.Kind of
+    rvkFloat:
+      if aWidth='' then begin
+        if Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths) then
+          aWidth:=Resolver.CurComp.FloatAsString;
+      end;
+    rvkKeyword:
+      if aColor='' then
+      begin
+        if (Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor) then
+          aColor:=Keywords[Resolver.CurComp.KeywordID];
+      end;
+    end;
+  until not Resolver.ReadNext;
+  SetLength(AttrIDs,2);
+  SetLength(Values,2);
+  AttrIDs[0]:=DemoAttrs[naBorderWidth].Index;
+  Values[0]:=aWidth;
+  AttrIDs[1]:=DemoAttrs[naBorderColor].Index;
+  Values[1]:=aColor;
+end;
 
 
-  Doc.Style:=LinesToStr([
-  '[left="2Px" i] { top: 4px; }',
-  '[color="Maybe bLack" i] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Button1.Top','',Button1.Top);
-  AssertEquals('Button1.Width','5px',Button1.Width);
+function TDemoCSSRegistry.OnCheck_Direction(Resolver: TCSSBaseResolver): boolean;
+begin
+  Result:=Resolver.CheckAttribute_Keyword(Chk_DirectionAllowedKeywordIDs);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeBeginsWith;
-var
-  Button1: TDemoButton;
+function TDemoCSSRegistry.OnCheck_Display(Resolver: TCSSBaseResolver): boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
+  Result:=Resolver.CheckAttribute_Keyword(Chk_DisplayAllowedKeywordIDs);
+end;
 
 
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
+function TDemoCSSRegistry.OnCheck_LeftTop(Resolver: TCSSBaseResolver): boolean;
+begin
+  Result:=Resolver.CheckAttribute_Dimension(Chk_LeftTop);
+end;
 
 
-  Doc.Style:=LinesToStr([
-  '[left^=Fo] { top: 4px; }',
-  '[left^="Foo B"] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Button1.Top','4px',Button1.Top);
-  AssertEquals('Button1.Width','5px',Button1.Width);
+function TDemoCSSRegistry.OnCheck_WidthHeight(Resolver: TCSSBaseResolver): boolean;
+begin
+  Result:=Resolver.CheckAttribute_Dimension(Chk_WidthHeight);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeEndsWith;
+procedure TDemoCSSRegistry.OnCompute_Direction(Resolver: TCSSResolver;
+  Node: TDemoNode; Value: TCSSAttributeValue);
 var
 var
-  Button1: TDemoButton;
+  Invalid: boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
-
-  Doc.Style:=LinesToStr([
-  '[left$=o] { top: 4px; }',
-  '[left$="o Bar"] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Button1.Top','',Button1.Top);
-  AssertEquals('Button1.Width','5px',Button1.Width);
+  if Resolver.ReadAttribute_Keyword(Invalid,Chk_DirectionAllowedKeywordIDs) then
+  begin
+    Value.Value:=Keywords[Resolver.CurComp.KeywordID];
+    Value.State:=cavsComputed;
+  end
+  else begin
+    Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
+  if Node=nil then ;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeBeginsWithHyphen;
+procedure TDemoCSSRegistry.OnCompute_LeftTop(Resolver: TCSSResolver;
+  Node: TDemoNode; Value: TCSSAttributeValue);
 var
 var
-  Button1: TDemoButton;
+  Invalid: boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='Foo-Bar';
-
-  Doc.Style:=LinesToStr([
-  '[left|=Foo] { top: 4px; }',
-  '[left|="Fo"] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Button1.Top','4px',Button1.Top);
-  AssertEquals('Button1.Width','',Button1.Width);
+  if Resolver.ReadAttribute_Dimension(Invalid,Chk_LeftTop) then
+  begin
+    case Resolver.CurComp.Kind of
+    rvkFloat:
+      Value.Value:=Resolver.CurComp.FloatAsString;
+    rvkKeyword:
+      Value.Value:=Keywords[Resolver.CurComp.KeywordID];
+    end;
+    Value.State:=cavsComputed;
+  end
+  else begin
+    Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
+  if Node=nil then ;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeContainsWord;
+procedure TDemoCSSRegistry.OnCompute_WidthHeight(Resolver: TCSSResolver;
+  Node: TDemoNode; Value: TCSSAttributeValue);
 var
 var
-  Button1: TDemoButton;
+  Invalid: boolean;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-  Doc.Root.ExplicitAttributes[naLeft]:='One Two Three';
-
-  Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='Four Five';
-
-  Doc.Style:=LinesToStr([
-  '[left~=One] { top: 4px; }',
-  '[left~=Two] { width: 5px; }',
-  '[left~=Three] { height: 6px; }',
-  '[left~="Four Five"] { color: #123; }',  // not one word, so does not match!
-  '[left~=our] { display: none; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Root.Width','5px',Doc.Root.Width);
-  AssertEquals('Root.Height','6px',Doc.Root.Height);
-  AssertEquals('Root.Color','',Doc.Root.Color);
-  AssertEquals('Root.Display','',Doc.Root.Display);
-  AssertEquals('Button1.Top','',Button1.Top);
-  AssertEquals('Button1.Width','',Button1.Width);
-  AssertEquals('Button1.Height','',Button1.Height);
-  AssertEquals('Button1.Color','',Button1.Color);
-  AssertEquals('Button1.Display','inline-block',Button1.Display);
+  if Resolver.ReadAttribute_Dimension(Invalid,Chk_WidthHeight) then
+  begin
+    Value.Value:=Resolver.CurComp.FloatAsString;
+    Value.State:=cavsComputed;
+  end
+  else begin
+    Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
+  if Node=nil then ;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_AttributeContainsSubstring;
-var
-  Button1: TDemoButton;
-begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
+constructor TDemoCSSRegistry.Create;
 
 
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
-  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
+  procedure SetDemoElementTypeID(aClass: TDemoNodeClass);
+  var
+    Desc: TCSSTypeDesc;
+  begin
+    Desc:=FindType(aClass.CSSTypeName);
+    if Desc=nil then
+      raise Exception.Create('20240625190912');
+    aClass.SetClassCSSTypeID(Desc.Index);
+  end;
 
 
-  Doc.Style:=LinesToStr([
-  '[left*=oo] { top: 4px; }',
-  '[left*="o B"] { width: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Button1.Top','4px',Button1.Top);
-  AssertEquals('Button1.Width','5px',Button1.Width);
-end;
+  procedure SetCompProps(ShorthandID: TDemoNodeAttribute; Longhands: array of TDemoNodeAttribute);
+  var
+    i: Integer;
+  begin
+    SetLength(DemoAttrs[ShorthandID].CompProps,length(Longhands));
+    for i:=0 to length(Longhands)-1 do
+      DemoAttrs[ShorthandID].CompProps[i]:=DemoAttrs[Longhands[i]];
+  end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Root;
 var
 var
-  Button1: TDemoButton;
+  Attr: TDemoNodeAttribute;
+  PseudoClass: TDemoPseudoClass;
+  aType: TDemoElementType;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
+  inherited Create;
+  Init;
 
 
-  Button1:=TDemoButton.Create(nil);
-  Button1.Parent:=Doc.Root;
+  // register demo attributes
+  for Attr in TDemoNodeAttribute do
+    AddDemoAttr(Attr);
+  DemoAttrIDBase:=DemoAttrs[low(TDemoNodeAttribute)].Index;
+  if FindAttribute(DemoAttributeNames[naBackground]).Index<>DemoAttrIDBase+ord(naBackground) then
+    raise Exception.Create('20240617200337');
 
 
-  Doc.Style:=LinesToStr([
-  ':roOt { top: 4px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Top','4px',Doc.Root.Top);
-  AssertEquals('Button1.Top','',Button1.Top);
-end;
+  // register demo pseudo classes
+  for PseudoClass in TDemoPseudoClass do
+    AddDemoPseudoClass(PseudoClass);
+  DemoPseudoClassIDBase:=DemoPseudoClasses[low(TDemoPseudoClass)].Index;
+  if FindPseudoClass(DemoPseudoClassNames[pcHover]).Index<>DemoPseudoClassIDBase+ord(pcHover) then
+    raise Exception.Create('20231008232201');
 
 
-procedure TTestNewCSSResolver.Test_Selector_Empty;
-var
-  Div1, Div11, Div2: TDemoDiv;
-begin
-  Doc.Root:=TDemoNode.Create(nil);
+  // register demo element types
+  for aType in TDemoElementType do
+    AddDemoType(aType);
+  DemoElementTypeIDBase:=DemoTypes[low(TDemoElementType)].Index;
+  if FindType(DemoElementTypeNames[detButton]).Index<>DemoElementTypeIDBase+ord(detButton) then
+    raise Exception.Create('20240625181725');
+  SetDemoElementTypeID(TDemoNode);
+  SetDemoElementTypeID(TDemoDiv);
+  SetDemoElementTypeID(TDemoSpan);
+  SetDemoElementTypeID(TDemoButton);
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+  kwRed:=AddKeyword('red');
+  kwFirstColor:=kwRed;
+  kwGreen:=AddKeyword('green');
+  kwBlue:=AddKeyword('blue');
+  kwWhite:=AddKeyword('white');
+  kwBlack:=AddKeyword('black');
+  kwLastColor:=kwBlack;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
+  kwBlock:=AddKeyword('block');
+  kwInline_Block:=AddKeyword('inline-block');
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+  kwLTR:=AddKeyword('ltr');
+  kwRTL:=AddKeyword('rtl');
 
 
-  Doc.Style:=LinesToStr([
-  ':eMpty { left: 1px; }',
-  'div:emPty { top: 2px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','1px',Div11.Left);
-  AssertEquals('Div11.Top','2px',Div11.Top);
-  AssertEquals('Div2.Left','1px',Div2.Left);
-  AssertEquals('Div2.Top','2px',Div2.Top);
-end;
+  // check parameters - - - - - - - - - - - - - - - - - - - - - - - -
 
 
-procedure TTestNewCSSResolver.Test_Selector_FirstChild;
-var
-  Div1, Div11, Div12, Div2: TDemoDiv;
-begin
-  Doc.Root:=TDemoNode.Create(nil);
+  // border-color
+  DemoAttrs[naBorderColor].OnCheck:=@OnCheck_BorderColor;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+  // border-width
+  DemoAttrs[naBorderWidth].OnCheck:=@OnCheck_BorderWidth;
+  Chk_BorderWidth.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_BorderWidth.AllowFrac:=true;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
+  // border shorthand
+  SetCompProps(naBorder,[naBorderColor,naBorderWidth]);
+  DemoAttrs[naBorder].OnCheck:=@OnCheck_Border;
+  DemoAttrs[naBorder].OnSplitShorthand:=@OnSplit_Border;
 
 
-  Div12:=TDemoDiv.Create(nil);
-  Div12.Parent:=Div1;
+  // direction
+  DemoAttrs[naDirection].OnCheck:=@OnCheck_Direction;
+  Chk_DirectionAllowedKeywordIDs:=[kwLTR,kwRTL];
+  DemoAttrs[naDirection].OnCompute:=@OnCompute_Direction;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+  // display
+  DemoAttrs[naDisplay].OnCheck:=@OnCheck_Display;
+  Chk_DisplayAllowedKeywordIDs:=[kwBlock,kwInline_Block];
 
 
-  Doc.Style:=LinesToStr([
-  ':first-child { left: 1px; }',
-  'div:first-child { top: 2px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','1px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','1px',Div1.Left);
-  AssertEquals('Div1.Top','2px',Div1.Top);
-  AssertEquals('Div11.Left','1px',Div11.Left);
-  AssertEquals('Div11.Top','2px',Div11.Top);
-  AssertEquals('Div12.Left','',Div12.Left);
-  AssertEquals('Div12.Top','',Div12.Top);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div2.Top','',Div2.Top);
+  // left, top
+  DemoAttrs[naLeft].OnCheck:=@OnCheck_LeftTop;
+  DemoAttrs[naLeft].OnCompute:=@OnCompute_LeftTop;
+  DemoAttrs[naTop].OnCheck:=@OnCheck_LeftTop;
+  DemoAttrs[naTop].OnCompute:=@OnCompute_LeftTop;
+  Chk_LeftTop.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_LeftTop.AllowNegative:=true;
+  Chk_LeftTop.AllowFrac:=true;
+
+  // width, height
+  DemoAttrs[naWidth].OnCheck:=@OnCheck_WidthHeight;
+  DemoAttrs[naWidth].OnCompute:=@OnCompute_WidthHeight;
+  DemoAttrs[naHeight].OnCheck:=@OnCheck_WidthHeight;
+  DemoAttrs[naHeight].OnCompute:=@OnCompute_WidthHeight;
+  Chk_WidthHeight.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_WidthHeight.AllowFrac:=true;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_LastChild;
-var
-  Div1, Div11, Div2: TDemoDiv;
-  Button12: TDemoButton;
+function TDemoCSSRegistry.AddDemoAttr(Attr: TDemoNodeAttribute
+  ): TDemoCSSAttributeDesc;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
-
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
-
-  Button12:=TDemoButton.Create(nil);
-  Button12.Parent:=Div1;
+  Result:=TDemoCSSAttributeDesc(AddAttribute(DemoAttributeNames[Attr],
+     DemoAttributeInitialValues[Attr],
+     Attr in DemoAttributesInherited,
+     not (Attr in DemoAttributesNotAll),
+     TDemoCSSAttributeDesc));
+  Result.DemoID:=Attr;
+  DemoAttrs[Attr]:=Result;
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+function TDemoCSSRegistry.AddDemoPseudoClass(PC: TDemoPseudoClass
+  ): TDemoCSSPseudoClassDesc;
+begin
+  Result:=TDemoCSSPseudoClassDesc(AddPseudoClass(DemoPseudoClassNames[PC],
+     TDemoCSSPseudoClassDesc));
+  Result.DemoID:=PC;
+  DemoPseudoClasses[PC]:=Result;
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':last-child { left: 6px; }',
-  'div:last-child { top: 7px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','6px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','',Div11.Left);
-  AssertEquals('Div11.Top','',Div11.Top);
-  AssertEquals('Button12.Left','6px',Button12.Left);
-  AssertEquals('Button12.Top','',Button12.Top);
-  AssertEquals('Div2.Left','6px',Div2.Left);
-  AssertEquals('Div2.Top','7px',Div2.Top);
+function TDemoCSSRegistry.AddDemoType(aType: TDemoElementType
+  ): TDemoCSSTypeDesc;
+begin
+  Result:=TDemoCSSTypeDesc(AddType(DemoElementTypeNames[aType],
+     TDemoCSSTypeDesc));
+  Result.DemoID:=aType;
+  DemoTypes[aType]:=Result;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_OnlyChild;
+{ TDemoNode }
+
+function TDemoNode.GetAttribute(DemoAttr: TDemoNodeAttribute): TCSSString;
 var
 var
-  Div1, Div11, Div2: TDemoDiv;
-  Button12: TDemoButton;
+  AttrDesc: TDemoCSSAttributeDesc;
+  i: Integer;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
+  AttrDesc:=CSSRegistry.DemoAttrs[DemoAttr];
+  i:=Values.IndexOf(AttrDesc.Index);
+  if i>=0 then
+    Result:=Values.Values[i].Value
+  else
+    Result:='';
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
+function TDemoNode.GetNodeCount: integer;
+begin
+  Result:=FNodes.Count;
+end;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Name:='Div11';
-  Div11.Parent:=Div1;
+function TDemoNode.GetNodes(Index: integer): TDemoNode;
+begin
+  Result:=TDemoNode(FNodes[Index]);
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Doc.Root;
+function TDemoNode.GetPseudoClasses(PseudoClass: TDemoPseudoClass): boolean;
+begin
+  Result:=FPseudoClasses[PseudoClass];
+end;
 
 
-  Button12:=TDemoButton.Create(nil);
-  Button12.Name:='Button12';
-  Button12.Parent:=Div2;
+procedure TDemoNode.SetParent(const AValue: TDemoNode);
+begin
+  if FParent=AValue then Exit;
+  if AValue=Self then
+    raise Exception.Create('cycle');
 
 
-  Doc.Style:=LinesToStr([
-  ':only-child { left: 8px; }',
-  'div:only-child { top: 9px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','8px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','8px',Div11.Left);
-  AssertEquals('Div11.Top','9px',Div11.Top);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div2.Top','',Div2.Top);
-  AssertEquals('Button12.Left','8px',Button12.Left);
-  AssertEquals('Button12.Top','',Button12.Top);
+  if FParent<>nil then
+  begin
+    FParent.FNodes.Remove(Self);
+  end;
+  FParent:=AValue;
+  if FParent<>nil then
+  begin
+    FParent.FNodes.Add(Self);
+    FreeNotification(FParent);
+  end;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Not;
-var
-  Div1, Div11, Div2: TDemoDiv;
-  Button12: TDemoButton;
+procedure TDemoNode.SetInlineStyleElements(const AValue: TCSSRuleElement);
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
+  if FInlineStyleElements=AValue then Exit;
+  FreeAndNil(FInlineStyleElements);
+  FInlineStyleElements:=AValue;
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
+procedure TDemoNode.SetInlineStyle(const AValue: TCSSString);
+begin
+  if FInlineStyle=AValue then Exit;
+  FInlineStyle:=AValue;
+  FreeAndNil(FInlineStyleElements);
+end;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Name:='Div11';
-  Div11.Parent:=Div1;
+procedure TDemoNode.SetPseudoClasses(PseudoClass: TDemoPseudoClass;
+  const AValue: boolean);
+begin
+  FPseudoClasses[PseudoClass]:=AValue;
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Doc.Root;
+procedure TDemoNode.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if AComponent=Self then exit;
+  if Operation=opRemove then
+  begin
+    if FNodes<>nil then
+      FNodes.Remove(AComponent);
+  end;
+end;
 
 
-  Button12:=TDemoButton.Create(nil);
-  Button12.Name:='Button12';
-  Button12.Parent:=Div2;
+constructor TDemoNode.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FNodes:=TFPObjectList.Create(false);
+  FCSSClasses:=TStringList.Create;
+  FCSSClasses.Delimiter:=' ';
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':not(:only-child) { left: 8px; }',
-  ':not(div:only-child) { top: 9px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Root.Top','9px',Doc.Root.Top);
-  AssertEquals('Div1.Left','8px',Div1.Left);
-  AssertEquals('Div1.Top','9px',Div1.Top);
-  AssertEquals('Div11.Left','',Div11.Left);
-  AssertEquals('Div11.Top','',Div11.Top);
-  AssertEquals('Div2.Left','8px',Div2.Left);
-  AssertEquals('Div2.Top','9px',Div2.Top);
-  AssertEquals('Button12.Left','',Button12.Left);
-  AssertEquals('Button12.Top','9px',Button12.Top);
+destructor TDemoNode.Destroy;
+begin
+  Clear;
+  FreeAndNil(FNodes);
+  FreeAndNil(FCSSClasses);
+  inherited Destroy;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_NthChild;
+procedure TDemoNode.Clear;
 var
 var
-  Div1, Div2, Div3, Div4: TDemoDiv;
+  i: Integer;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
+  Rules:=nil;
+  FreeAndNil(Values);
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+  FCSSClasses.Clear;
+  FreeAndNil(FInlineStyleElements);
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+  for i:=NodeCount-1 downto 0 do
+    Nodes[i].Free;
+  if FNodes.Count>0 then
+    raise Exception.Create('20240710174459');
+end;
 
 
-  Div3:=TDemoDiv.Create(nil);
-  Div3.Parent:=Doc.Root;
+procedure TDemoNode.ApplyCSS(Resolver: TCSSResolver);
+var
+  AttrDesc: TDemoCSSAttributeDesc;
+  i: Integer;
+  AttrID: TCSSNumericalID;
+  CurValue: TCSSAttributeValue;
+begin
+  if (InlineStyleElement=nil) and (InlineStyle<>'') then
+    InlineStyleElement:=Resolver.ParseInlineStyle(InlineStyle) as TCSSRuleElement;
 
 
-  Div4:=TDemoDiv.Create(nil);
-  Div4.Parent:=Doc.Root;
+  Resolver.Compute(Self,InlineStyleElement,Rules,Values);
 
 
-  Doc.Style:=LinesToStr([
-  'div:nth-child(2n+1) { left: 8px; }',
-  'div:nth-child(n+3) { border-width: 6px; }',
-  'div:nth-child(-n+2) { height: 7em; }',
-  'div:nth-child(even) { top: 3px; }',
-  'div:nth-child(odd) { width: 4px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Root.BorderWidth','',Doc.Root.BorderWidth);
-  AssertEquals('Root.Height','',Doc.Root.Height);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Root.Width','',Doc.Root.Width);
-  AssertEquals('Div1.Left','8px',Div1.Left);
-  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
-  AssertEquals('Div1.Height','7em',Div1.Height);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div1.Width','4px',Div1.Width);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div2.BorderWidth','',Div2.BorderWidth);
-  AssertEquals('Div2.Height','7em',Div2.Height);
-  AssertEquals('Div2.Top','3px',Div2.Top);
-  AssertEquals('Div2.Width','',Div2.Width);
-  AssertEquals('Div3.Left','8px',Div3.Left);
-  AssertEquals('Div3.BorderWidth','6px',Div3.BorderWidth);
-  AssertEquals('Div3.Height','',Div3.Height);
-  AssertEquals('Div3.Top','',Div3.Top);
-  AssertEquals('Div3.Width','4px',Div3.Width);
-  AssertEquals('Div4.Left','',Div4.Left);
-  AssertEquals('Div4.BorderWidth','6px',Div4.BorderWidth);
-  AssertEquals('Div4.Height','',Div4.Height);
-  AssertEquals('Div4.Top','3px',Div4.Top);
-  AssertEquals('Div4.Width','',Div4.Width);
+  {$IFDEF VerboseCSSResolver}
+  writeln('TDemoNode.ApplyCSS ',Name,' length(Values)=',length(Values.Values),' All="',CSSRegistry.Keywords[Values.AllValue],'"');
+  for i:=0 to length(Values.Values)-1 do begin
+    AttrID:=Values.Values[i].AttrID;
+    writeln('TDemoNode.ApplyCSS ',Name,' resolved ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',Values.Values[i].Value,'"');
+  end;
+  {$ENDIF}
+  // compute values
+  for i:=0 to length(Values.Values)-1 do
+  begin
+    CurValue:=Values.Values[i];
+    case CurValue.State of
+      cavsSource, cavsBaseKeywords:
+        begin
+          AttrID:=CurValue.AttrID;
+          AttrDesc:=CSSRegistry.Attributes[AttrID] as TDemoCSSAttributeDesc;
+          if AttrDesc.OnCompute<>nil then
+          begin
+            Resolver.CurComp.EndP:=PChar(CurValue.Value);
+            Resolver.ReadNext;
+            AttrDesc.OnCompute(Resolver,Self,CurValue);
+            {$IFDEF VerboseCSSResolver}
+            writeln('TDemoNode.ApplyCSS ',Name,' computed ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',CurValue.Value,'"');
+            {$ENDIF}
+          end else
+            CurValue.State:=cavsComputed;
+        end;
+      cavsComputed: ;
+      cavsInvalid: ;
+    end;
+  end;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_NthLastChild;
+function TDemoNode.GetCSSID: TCSSString;
+begin
+  Result:=Name;
+end;
+
+class function TDemoNode.CSSTypeName: TCSSString;
+begin
+  Result:=DemoElementTypeNames[detNode];
+end;
+
+function TDemoNode.HasCSSClass(const aClassName: TCSSString): boolean;
 var
 var
-  Div1, Div2, Div3, Div4: TDemoDiv;
+  i: Integer;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
+  for i:=0 to CSSClasses.Count-1 do
+    if aClassName=CSSClasses[i] then
+      exit(true);
+  Result:=false;
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+function TDemoNode.GetCSSParent: ICSSNode;
+begin
+  Result:=Parent;
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+function TDemoNode.GetCSSIndex: integer;
+begin
+  if Parent=nil then
+    Result:=-1
+  else
+    Result:=Parent.FNodes.IndexOf(Self);
+end;
 
 
-  Div3:=TDemoDiv.Create(nil);
-  Div3.Parent:=Doc.Root;
+function TDemoNode.GetCSSNextSibling: ICSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if (i<0) or (i+1>=Parent.NodeCount) then
+    Result:=nil
+  else
+    Result:=Parent.Nodes[i+1];
+end;
 
 
-  Div4:=TDemoDiv.Create(nil);
-  Div4.Parent:=Doc.Root;
+function TDemoNode.GetCSSPreviousSibling: ICSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if i<1 then
+    Result:=nil
+  else
+    Result:=Parent.Nodes[i-1];
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':nth-last-child(2n+1) { left: 8px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div2.Left','8px',Div2.Left);
-  AssertEquals('Div3.Left','',Div3.Left);
-  AssertEquals('Div4.Left','8px',Div4.Left);
+function TDemoNode.GetCSSChildCount: integer;
+begin
+  Result:=NodeCount;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_NthChildOf;
+function TDemoNode.GetCSSChild(const anIndex: integer): ICSSNode;
+begin
+  Result:=Nodes[anIndex];
+end;
+
+function TDemoNode.GetCSSNextOfType: ICSSNode;
 var
 var
-  Div1, Div2, Div3, Div4: TDemoDiv;
+  i, Cnt: Integer;
+  MyID: TCSSNumericalID;
+  aNode: TDemoNode;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
+  Result:=nil;
+  i:=GetCSSIndex;
+  if i<0 then exit;
+  inc(i);
+  MyID:=GetClassCSSTypeID;
+  Cnt:=Parent.NodeCount;
+  while i<Cnt do
+  begin
+    aNode:=Parent.Nodes[i];
+    if aNode.GetClassCSSTypeID=MyID then
+      exit(aNode);
+    inc(i);
+  end;
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
+function TDemoNode.GetCSSPreviousOfType: ICSSNode;
+var
+  i: Integer;
+  MyID: TCSSNumericalID;
+  aNode: TDemoNode;
+begin
+  Result:=nil;
+  i:=GetCSSIndex;
+  if i<0 then exit;
+  dec(i);
+  MyID:=GetClassCSSTypeID;
+  while i>=0 do
+  begin
+    aNode:=Parent.Nodes[i];
+    if aNode.GetClassCSSTypeID=MyID then
+      exit(aNode);
+    dec(i);
+  end;
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Doc.Root;
-  Div2.ExplicitAttributes[naTop]:='3px';
+function TDemoNode.GetCSSAttributeClass: TCSSString;
+begin
+  FCSSClasses.Delimiter:=' ';
+  Result:=FCSSClasses.DelimitedText;
+end;
 
 
-  Div3:=TDemoDiv.Create(nil);
-  Div3.Name:='Div3';
-  Div3.Parent:=Doc.Root;
-  Div3.ExplicitAttributes[naTop]:='3px';
+function TDemoNode.HasCSSExplicitAttribute(const AttrID: TCSSNumericalID): boolean;
+var
+  b: TCSSNumericalID;
+  Attr: TDemoNodeAttribute;
+begin
+  b:=CSSRegistry.DemoAttrIDBase;
+  if (AttrID<b) or (AttrID>b+ord(High(TDemoNodeAttribute))) then
+    exit(false);
+  Attr:=TDemoNodeAttribute(AttrID-b);
+  Result:=ExplicitAttributes[Attr]<>'';
+end;
 
 
-  Div4:=TDemoDiv.Create(nil);
-  Div4.Name:='Div4';
-  Div4.Parent:=Doc.Root;
-  Div4.ExplicitAttributes[naTop]:='3px';
+function TDemoNode.GetCSSExplicitAttribute(const AttrID: TCSSNumericalID): TCSSString;
+var
+  Attr: TDemoNodeAttribute;
+  b: TCSSNumericalID;
+begin
+  b:=CSSRegistry.DemoAttrIDBase;
+  if (AttrID<b) or (AttrID>b+ord(High(TDemoNodeAttribute))) then
+    exit('');
+  Attr:=TDemoNodeAttribute(AttrID-b);
+  Result:=ExplicitAttributes[Attr];
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':nth-child(2n+1 of [top=3px]) { left: 5px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div2.Left','5px',Div2.Left);
-  AssertEquals('Div3.Left','',Div3.Left);
-  AssertEquals('Div4.Left','5px',Div4.Left);
+function TDemoNode.HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean;
+var
+  b: TCSSNumericalID;
+begin
+  b:=CSSRegistry.DemoPseudoClassIDBase;
+  if (AttrID>=b) and (AttrID<=b+ord(High(TDemoPseudoClass))) then
+    Result:=HasPseudoClass[TDemoPseudoClass(AttrID-b)]
+  else
+    Result:=false;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_FirstOfType;
+function TDemoNode.GetCSSEmpty: boolean;
+begin
+  Result:=NodeCount=0;
+end;
+
+function TDemoNode.GetCSSDepth: integer;
 var
 var
-  Div1, Div11, Div13, Div2: TDemoDiv;
-  Button12: TDemoButton;
+  Node: TDemoNode;
 begin
 begin
-  Doc.Root:=TDemoNode.Create(nil);
+  Result:=0;
+  Node:=Parent;
+  while Node<>nil do
+  begin
+    inc(Result);
+    Node:=Node.Parent;
+  end;
+end;
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
+function TDemoNode.GetCSSTypeName: TCSSString;
+begin
+  Result:=CSSTypeName;
+end;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
+class function TDemoNode.GetClassCSSTypeID: TCSSNumericalID;
+begin
+  Result:=FDemoNodeTypeID;
+end;
 
 
-  Button12:=TDemoButton.Create(nil);
-  Button12.Parent:=Div1;
+class procedure TDemoNode.SetClassCSSTypeID(aID: TCSSNumericalID);
+begin
+  FDemoNodeTypeID:=aID;
+end;
 
 
-  Div13:=TDemoDiv.Create(nil);
-  Div13.Parent:=Div1;
+function TDemoNode.GetCSSTypeID: TCSSNumericalID;
+begin
+  Result:=GetClassCSSTypeID;
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+class function TDemoNode.GetCSSTypeStyle: TCSSString;
+begin
+  Result:='';
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':first-of-type { left: 6px; }',
-  'div:first-of-type { top: 7px; }',
-  '']);
+{ TCustomTestNewCSSResolver }
+
+procedure TCustomTestNewCSSResolver.SetUp;
+var
+  AttrDesc: TCSSAttributeDesc;
+begin
+  inherited SetUp;
+
+  TDemoNode.CSSRegistry:=TDemoCSSRegistry.Create();
+
+  // register button attribute 'caption'
+  AttrDesc:=TDemoNode.CSSRegistry.AddAttribute('caption');
+  TDemoButton.CSSCaptionID:=AttrDesc.Index;
+
+  FDoc:=TDemoDocument.Create(nil);
+end;
+
+procedure TCustomTestNewCSSResolver.TearDown;
+begin
+  FreeAndNil(FDoc);
+  FreeAndNil(TDemoNode.CSSRegistry);
+  inherited TearDown;
+end;
+
+procedure TCustomTestNewCSSResolver.ApplyStyle;
+begin
   Doc.ApplyStyle;
   Doc.ApplyStyle;
-  AssertEquals('Root.Left','6px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','6px',Div1.Left);
-  AssertEquals('Div1.Top','7px',Div1.Top);
-  AssertEquals('Div11.Left','6px',Div11.Left);
-  AssertEquals('Div11.Top','7px',Div11.Top);
-  AssertEquals('Button12.Left','6px',Button12.Left);
-  AssertEquals('Button12.Top','',Button12.Top);
-  AssertEquals('Div13.Left','',Div13.Left);
-  AssertEquals('Div13.Top','',Div13.Top);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div2.Top','',Div2.Top);
+  CheckWarnings;
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_LastOfType;
+procedure TCustomTestNewCSSResolver.CheckWarnings;
 var
 var
-  Div1, Div11, Div13, Div2: TDemoDiv;
-  Button12: TDemoButton;
+  aResolver: TCSSResolver;
+  i: Integer;
+  Entry: TCSSResolverLogEntry;
+  s: String;
+begin
+  aResolver:=FDoc.CSSResolver;
+  if aResolver.LogCount=0 then exit;
+  writeln('TCustomTestNewCSSResolver.CheckWarnings LogCount=',aResolver.LogCount);
+  for i:=0 to aResolver.LogCount-1 do
+  begin
+    Entry:=aResolver.LogEntries[i];
+    s:='';
+    if Entry.PosEl<>nil then
+      s:=' at '+Entry.PosEl.SourceFileName+'('+IntToStr(Entry.PosEl.SourceRow)+','+IntToStr(Entry.PosEl.SourceRow)+')';
+    writeln('  ',Entry.MsgType,': ',Entry.ID,' ',Entry.Msg,s);
+  end;
+end;
+
+{ TTestNewCSSResolver }
+
+procedure TTestNewCSSResolver.Test_ParseAttr_Keyword;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Style:='* { direction: ltr; }';
+  ApplyStyle;
+  AssertEquals('Root.direction','ltr',Doc.Root.Direction);
+end;
 
 
+procedure TTestNewCSSResolver.Test_ParseAttr_Keyword_SkipInvalid;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Style:='* { direction: something ltr; }';
+  ApplyStyle;
+  AssertEquals('Root.direction','ltr',Doc.Root.Direction);
+end;
+
+procedure TTestNewCSSResolver.Test_ParseAttr_Float;
+var
+  Div1: TDemoDiv;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Style:=
+     ':root {'
+    +'  left: 10px;'
+    +'  top: .1px;'
+    +'  width: 3e2em;'
+    +'  height: 3e-2px;'
+    +'}'
+    +'div {'
+    +'  left: -4mm;'
+    +'  top: -.5pc;'
+    +'  width: .6cm;'
+    +'  height: 6E+1rem;'
+    +'}';
   Div1:=TDemoDiv.Create(nil);
   Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
   Div1.Parent:=Doc.Root;
   Div1.Parent:=Doc.Root;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
+  ApplyStyle;
+  AssertEquals('Root.Left','10px',Doc.Root.Left);
+  AssertEquals('Root.Top','0.1px',Doc.Root.Top);
+  AssertEquals('Root.Width','300em',Doc.Root.Width);
+  AssertEquals('Root.Height','0.03px',Doc.Root.Height);
+  AssertEquals('Div1.Left','-4mm',Div1.Left);
+  AssertEquals('Div1.Top','-0.5pc',Div1.Top);
+  AssertEquals('Div1.Width','0.6cm',Div1.Width);
+  AssertEquals('Div1.Height','60rem',Div1.Height);
+end;
 
 
-  Button12:=TDemoButton.Create(nil);
-  Button12.Parent:=Div1;
+procedure TTestNewCSSResolver.Test_ParseAttr_Float_SkipInvalid;
+begin
+  exit;
 
 
-  Div13:=TDemoDiv.Create(nil);
-  Div13.Parent:=Div1;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Style:=
+     ':root {'
+    +'  left: something 10px;'
+    +'  top: 1 px;' // no space between number
+    +'  width: 0 px;' // the px is ignored because of the space, 0 without unit is allowed
+    +'  height: -4cm;' // no negative
+    +'}';
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
+  ApplyStyle;
+  AssertEquals('Root.Left','10px',Doc.Root.Left);
+  AssertEquals('Root.Top','invalid',Doc.Root.Top);
+  AssertEquals('Root.Width','0',Doc.Root.Width);
+  AssertEquals('Root.Height','invalid',Doc.Root.Height);
+end;
 
 
-  Doc.Style:=LinesToStr([
-  ':last-of-type { left: 6px; }',
-  'div:last-of-type { top: 7px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','6px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','',Div11.Left);
-  AssertEquals('Div11.Top','',Div11.Top);
-  AssertEquals('Button12.Left','6px',Button12.Left);
-  AssertEquals('Button12.Top','',Button12.Top);
-  AssertEquals('Div13.Left','6px',Div13.Left);
-  AssertEquals('Div13.Top','7px',Div13.Top);
-  AssertEquals('Div2.Left','6px',Div2.Left);
-  AssertEquals('Div2.Top','7px',Div2.Top);
+procedure TTestNewCSSResolver.Test_Selector_Universal;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Style:='* { left: 10px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','10px',Doc.Root.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_OnlyOfType;
+procedure TTestNewCSSResolver.Test_Selector_Type;
 var
 var
-  Div1, Div11, Div2: TDemoDiv;
-  Button12: TDemoButton;
+  Button: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
-
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Parent:=Div1;
-
-  Button12:=TDemoButton.Create(nil);
-  Button12.Parent:=Div1;
-
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
-
-  Doc.Style:=LinesToStr([
-  ':only-of-type { left: 6px; }',
-  'div:only-of-type { top: 7px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','6px',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Div11.Left','6px',Div11.Left);
-  AssertEquals('Div11.Top','7px',Div11.Top);
-  AssertEquals('Button12.Left','6px',Button12.Left);
-  AssertEquals('Button12.Top','',Button12.Top);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div2.Top','',Div2.Top);
+  Button:=TDemoButton.Create(nil);
+  Button.Parent:=Doc.Root;
+  Doc.Style:='button { left: 11px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button.left','11px',Button.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_NthOfType;
+procedure TTestNewCSSResolver.Test_Selector_Type_Spaces;
 var
 var
-  Div1, Div2, Div3, Div4: TDemoDiv;
   Button1, Button2: TDemoButton;
   Button1, Button2: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
 
 
   Button1:=TDemoButton.Create(nil);
   Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
   Button1.Parent:=Doc.Root;
   Button1.Parent:=Doc.Root;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Doc.Root;
-
-  Div3:=TDemoDiv.Create(nil);
-  Div3.Name:='Div3';
-  Div3.Parent:=Doc.Root;
-
   Button2:=TDemoButton.Create(nil);
   Button2:=TDemoButton.Create(nil);
-  Button2.Name:='Button2';
   Button2.Parent:=Doc.Root;
   Button2.Parent:=Doc.Root;
 
 
-  Div4:=TDemoDiv.Create(nil);
-  Div4.Name:='Div4';
-  Div4.Parent:=Doc.Root;
-
-  Doc.Style:=LinesToStr([
-  ':nth-of-type(2n+1) { left: 8px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','8px',Div1.Left);
-  AssertEquals('Button1.Left','8px',Button1.Left);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Div3.Left','8px',Div3.Left);
-  AssertEquals('Button2.Left','',Button2.Left);
-  AssertEquals('Div4.Left','',Div4.Left);
+  Doc.Style:='div, button ,span { left: 11px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','11px',Button1.Left);
+  AssertEquals('Button2.left','11px',Button2.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_NthLastOfType;
+procedure TTestNewCSSResolver.Test_Selector_Id;
 var
 var
-  Div1, Div2, Div3, Div4: TDemoDiv;
-  Button1, Button2: TDemoButton;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Parent:=Doc.Root;
-
   Button1:=TDemoButton.Create(nil);
   Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
   Button1.Parent:=Doc.Root;
   Button1.Parent:=Doc.Root;
+  Doc.Style:='#Button1 { left: 12px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','12px',Button1.Left);
+end;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
-
-  Div3:=TDemoDiv.Create(nil);
-  Div3.Parent:=Doc.Root;
-
-  Button2:=TDemoButton.Create(nil);
-  Button2.Parent:=Doc.Root;
-
-  Div4:=TDemoDiv.Create(nil);
-  Div4.Parent:=Doc.Root;
-
-  Doc.Style:=LinesToStr([
-  ':nth-last-of-type(2n+1) { left: 8px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','',Div1.Left);
-  AssertEquals('Button1.Left','',Button1.Left);
-  AssertEquals('Div2.Left','8px',Div2.Left);
-  AssertEquals('Div3.Left','',Div3.Left);
-  AssertEquals('Button2.Left','8px',Button2.Left);
-  AssertEquals('Div4.Left','8px',Div4.Left);
+procedure TTestNewCSSResolver.Test_Selector_Class;
+var
+  Button1: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Button1:=TDemoButton.Create(nil);
+  Button1.CSSClasses.Add('west');
+  Button1.Parent:=Doc.Root;
+  Doc.Style:='.west { left: 13px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','13px',Button1.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Is;
+procedure TTestNewCSSResolver.Test_Selector_ClassClass;
 var
 var
-  Div1, Div2: TDemoDiv;
   Button1, Button2: TDemoButton;
   Button1, Button2: TDemoButton;
-  Span1: TDemoSpan;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.ExplicitAttributes[naTop]:='3px';
 
 
   Button1:=TDemoButton.Create(nil);
   Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
+  Button1.CSSClasses.Add('west');
   Button1.Parent:=Doc.Root;
   Button1.Parent:=Doc.Root;
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Parent:=Doc.Root;
-
-  Span1:=TDemoSpan.Create(nil);
-  Span1.Parent:=Doc.Root;
-  Span1.ExplicitAttributes[naTop]:='3px';
-
   Button2:=TDemoButton.Create(nil);
   Button2:=TDemoButton.Create(nil);
+  Button2.CSSClasses.DelimitedText:='west south';
+  AssertEquals('Button2.CSSClasses.Count',2,Button2.CSSClasses.Count);
   Button2.Parent:=Doc.Root;
   Button2.Parent:=Doc.Root;
-  Button2.ExplicitAttributes[naTop]:='3px';
 
 
-  Doc.Style:=LinesToStr([
-  ':is(div, button)[top=3px] { left: 7px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','7px',Div1.Left);
-  AssertEquals('Button1.Left','',Button1.Left);
-  AssertEquals('Div2.Left','',Div2.Left);
-  AssertEquals('Span1.Left','',Div2.Left);
-  AssertEquals('Button2.Left','7px',Button2.Left);
+  Doc.Style:='.west.south { left: 10px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','',Button1.Left);
+  AssertEquals('Button2.left','10px',Button2.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Where;
+procedure TTestNewCSSResolver.Test_Selector_ClassSpaceClass;
 var
 var
-  Div1, Div2: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.ExplicitAttributes[naTop]:='3px';
+  Doc.Root.CSSClasses.Add('bird');
 
 
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Div1;
-  Div2.ExplicitAttributes[naTop]:='3px';
+  Button1:=TDemoButton.Create(nil);
+  Button1.CSSClasses.Add('west');
+  Button1.Parent:=Doc.Root;
 
 
-  Doc.Style:=LinesToStr([
-  ':where(div[top=3px]) { left: 1px; }',
-  'div div { left: 2px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','1px',Div1.Left);
-  AssertEquals('Div2.Left','2px',Div2.Left);
+  Doc.Style:='.bird .west { left: 10px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','10px',Button1.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Selector_Hover;
+procedure TTestNewCSSResolver.Test_Selector_TypeCommaType;
 var
 var
-  Div1, Div11: TDemoDiv;
   Button1: TDemoButton;
   Button1: TDemoButton;
+  Div1: TDemoDiv;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
-  Doc.Root.Name:='root';
-
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.Hover:=true;
 
 
   Button1:=TDemoButton.Create(nil);
   Button1:=TDemoButton.Create(nil);
-  Button1.Name:='Button1';
-  Button1.Parent:=Div1;
-  Button1.Hover:=true;
+  Button1.Parent:=Doc.Root;
 
 
-  Div11:=TDemoDiv.Create(nil);
-  Div11.Name:='Div11';
-  Div11.Parent:=Div1;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-  Doc.Style:=LinesToStr([
-  ':hover { left: 1px; }',
-  'button:hover { top: 2px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Root.Top','',Doc.Root.Top);
-  AssertEquals('Div1.Left','1px',Div1.Left);
-  AssertEquals('Div1.Top','',Div1.Top);
-  AssertEquals('Button1.Left','1px',Button1.Left);
-  AssertEquals('Button1.Top','2px',Button1.Top);
-  AssertEquals('Div11.Left','',Div11.Left);
-  AssertEquals('Div11.Top','',Div11.Top);
+  Doc.Style:='div, button { left: 10px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','10px',Button1.Left);
+  AssertEquals('Div1.left','10px',Div1.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_InlineStyle;
+procedure TTestNewCSSResolver.Test_Selector_ClassGTClass;
 var
 var
-  Div1: TDemoDiv;
+  Div1, Div2: TDemoDiv;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
+  Doc.Root.CSSClasses.Add('lvl1');
 
 
   Div1:=TDemoDiv.Create(nil);
   Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.CSSClasses.Add('lvl2');
   Div1.Parent:=Doc.Root;
   Div1.Parent:=Doc.Root;
-  Div1.InlineStyle:='left: 10px; top: 5px';
+
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.CSSClasses.Add('lvl3');
+  Div2.Parent:=Div1;
 
 
   Doc.Style:=LinesToStr([
   Doc.Style:=LinesToStr([
-  'div { left: 6px; }',
+  '.lvl1>.lvl2 { left: 10px; }', // set
+  '.lvl1>.lvl3 { top: 11px; }', // not set, not direct children
+  '.lvl2>.lvl3 { width: 12px; }', // set
   '']);
   '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','10px',Div1.Left);
-  AssertEquals('Div1.Top','5px',Div1.Top);
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Root.top','',Doc.Root.Top);
+  AssertEquals('Root.width','',Doc.Root.Width);
+  AssertEquals('Div1.left','10px',Div1.Left);
+  AssertEquals('Div1.top','',Div1.Top);
+  AssertEquals('Div1.width','',Div1.Width);
+  AssertEquals('Div2.left','',Div2.Left);
+  AssertEquals('Div2.top','',Div2.Top);
+  AssertEquals('Div2.width','12px',Div2.Width);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Id_Class;
+procedure TTestNewCSSResolver.Test_Selector_TypePlusType;
 var
 var
+  Button1, Button2, Button3: TDemoButton;
   Div1: TDemoDiv;
   Div1: TDemoDiv;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
+
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Doc.Root;
 
 
   Div1:=TDemoDiv.Create(nil);
   Div1:=TDemoDiv.Create(nil);
   Div1.Name:='Div1';
   Div1.Name:='Div1';
   Div1.Parent:=Doc.Root;
   Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
 
 
-  Doc.Style:=LinesToStr([
-  '.bird { left: 6px; }',
-  '#Div1 { left: 7px; top: 8px; }', // id has higher specifity, no matter if before or after a .class
-  '.bird { top: 9px; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','7px',Div1.Left);
-  AssertEquals('Div1.Top','8px',Div1.Top);
+  Button2:=TDemoButton.Create(nil);
+  Button2.Name:='Button2';
+  Button2.Parent:=Doc.Root;
+
+  Button3:=TDemoButton.Create(nil);
+  Button3.Name:='Button3';
+  Button3.Parent:=Doc.Root;
+
+  Doc.Style:='div+button { left: 10px; }'; // only Button2 has a prev sibling div
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','',Button1.Left);
+  AssertEquals('Div1.left','',Div1.Left);
+  AssertEquals('Button2.left','10px',Button2.Left);
+  AssertEquals('Button3.left','',Button3.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Important;
+procedure TTestNewCSSResolver.Test_Selector_TypeTildeType;
 var
 var
+  Button1, Button2, Button3: TDemoButton;
   Div1: TDemoDiv;
   Div1: TDemoDiv;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
 
 
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+
   Div1:=TDemoDiv.Create(nil);
   Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
   Div1.Parent:=Doc.Root;
   Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
 
 
-  Doc.Style:=LinesToStr([
-  '.bird { left: 6px !important; }',
-  '#Div1 { left: 7px; top: 8px; }',
-  '.bird { top: 9px ! important; }',
-  '']);
-  Doc.ApplyStyle;
-  AssertEquals('Root.Left','',Doc.Root.Left);
-  AssertEquals('Div1.Left','6px',Div1.Left);
-  AssertEquals('Div1.Top','9px',Div1.Top);
+  Button2:=TDemoButton.Create(nil);
+  Button2.Parent:=Doc.Root;
+
+  Button3:=TDemoButton.Create(nil);
+  Button3.Parent:=Doc.Root;
+
+  Doc.Style:='div~button { left: 10px; }';
+  ApplyStyle;
+  AssertEquals('Root.left','',Doc.Root.Left);
+  AssertEquals('Button1.left','',Button1.Left);
+  AssertEquals('Div1.left','',Div1.Left);
+  AssertEquals('Button2.left','10px',Button2.Left);
+  AssertEquals('Button3.left','10px',Button3.Left);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Shorthand_OneRule;
+procedure TTestNewCSSResolver.Test_Selector_HasAttribute;
 var
 var
-  Div1: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
+  Doc.Root.ExplicitAttributes[naLeft]:='100px';
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='2px';
+  Button1.ExplicitCaption:='Click Button1';
 
 
-  Doc.Style:='.bird { border-color: blue; border: 6px red; border-width: 7px; }';
-  Doc.ApplyStyle;
-  AssertEquals('Div1.BorderColor','red',Div1.BorderColor);
-  AssertEquals('Div1.BorderWidth','7px',Div1.BorderWidth);
+  Doc.Style:=LinesToStr([
+  '[left] { top: 3px; }',
+  '[caption] { width: 4px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','3px',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Button1.Top','3px',Button1.Top);
+  AssertEquals('Button1.Width','4px',Button1.Width);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Shorthand_ClassClass;
+procedure TTestNewCSSResolver.Test_Selector_AttributeEquals;
 var
 var
-  Div1: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='2px';
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
-  Div1.CSSClasses.Add('eagle');
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='3px';
+  Button1.ExplicitAttributes[naColor]:='maybe black';
 
 
   Doc.Style:=LinesToStr([
   Doc.Style:=LinesToStr([
-  '.bird.eagle { border-color: blue; }',
-  '.bird { border-width: 6px; }',
-  '.bird { border: 7px red; }',
+  '[left=2px] { top: 4px; }',
+  '[color="maybe black"] { width: 5px; }',
   '']);
   '']);
-  Doc.ApplyStyle;
-  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
-  AssertEquals('Div1.BorderWidth','7px',Div1.BorderWidth);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Button1.Top','',Button1.Top);
+  AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Longhand_All_Longhand;
+procedure TTestNewCSSResolver.Test_Selector_AttributeEqualsI;
 var
 var
-  Div1: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='2px';
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
-  Div1.CSSClasses.Add('eagle');
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='3px';
+  Button1.ExplicitAttributes[naColor]:='maybe Black';
 
 
   Doc.Style:=LinesToStr([
   Doc.Style:=LinesToStr([
-  '.bird.eagle { border-color: blue; }',
-  '.bird { border-width: 7px; }',
-  '.bird { all: initial; }',
-  '.bird { background: red; }',
+  '[left="2Px" i] { top: 4px; }',
+  '[color="Maybe bLack" i] { width: 5px; }',
   '']);
   '']);
-  Doc.ApplyStyle;
-  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
-  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
-  AssertEquals('Div1.Background','red',Div1.Background);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Button1.Top','',Button1.Top);
+  AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Specifity_Shorthand_All_Shorthand;
+procedure TTestNewCSSResolver.Test_Selector_AttributeBeginsWith;
 var
 var
-  Div1, Div2: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-  Div1.CSSClasses.Add('bird');
-
-  Div2:=TDemoDiv.Create(nil);
-  Div2.Name:='Div2';
-  Div2.Parent:=Doc.Root;
-  Div2.CSSClasses.Add('eagle');
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
 
 
   Doc.Style:=LinesToStr([
   Doc.Style:=LinesToStr([
-  '.bird { border: 7px blue; }',
-  '.bird { all: initial; }',
-  '.eagle { all: initial; }',
-  '.eagle { border: 8px red; }',
+  '[left^=Fo] { top: 4px; }',
+  '[left^="Foo B"] { width: 5px; }',
   '']);
   '']);
-  Doc.ApplyStyle;
-  AssertEquals('Div1.BorderColor','',Div1.BorderColor);
-  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
-  AssertEquals('Div2.BorderColor','red',Div2.BorderColor);
-  AssertEquals('Div2.BorderWidth','8px',Div2.BorderWidth);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Button1.Top','4px',Button1.Top);
+  AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 end;
 
 
-procedure TTestNewCSSResolver.Test_Origin_Id_Class;
+procedure TTestNewCSSResolver.Test_Selector_AttributeEndsWith;
 var
 var
-  Div1: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
   Doc.Root:=TDemoNode.Create(nil);
   Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
 
 
-  Div1:=TDemoDiv.Create(nil);
-  Div1.Name:='Div1';
-  Div1.Parent:=Doc.Root;
-
-  writeln('TTestNewCSSResolver.Test_Origin_Id_Class ',Doc.CSSResolver.ClassName);
-  Doc.CSSResolver.AddStyleSheet(cssoUserAgent,'testagent',
-  '#Div1 { border-width: 2px;'
-  +' border-color: blue !important;'
-  +' background: green; }'
-  );
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
 
 
   Doc.Style:=LinesToStr([
   Doc.Style:=LinesToStr([
-  'div { border-width: 3px; ', // although class has lower spec than id, author origin wins
-  ' border-color: orange;', // former important always wins
-  '}']);
-  Doc.ApplyStyle;
-  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
-  AssertEquals('Div1.BorderWidth','3px',Div1.BorderWidth);
-  AssertEquals('Div1.Background','green',Div1.Background);
+  '[left$=o] { top: 4px; }',
+  '[left$="o Bar"] { width: 5px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Button1.Top','',Button1.Top);
+  AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 end;
 
 
-{ TDemoDiv }
-
-class function TDemoDiv.CSSTypeName: TCSSString;
+procedure TTestNewCSSResolver.Test_Selector_AttributeBeginsWithHyphen;
+var
+  Button1: TDemoButton;
 begin
 begin
-  Result:=DemoElementTypeNames[detDiv];
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
 
 
-class function TDemoDiv.GetClassCSSTypeID: TCSSNumericalID;
-begin
-  Result:=FDemoDivTypeID;
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='Foo-Bar';
+
+  Doc.Style:=LinesToStr([
+  '[left|=Foo] { top: 4px; }',
+  '[left|="Fo"] { width: 5px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Button1.Top','4px',Button1.Top);
+  AssertEquals('Button1.Width','',Button1.Width);
 end;
 end;
 
 
-class procedure TDemoDiv.SetClassCSSTypeID(aID: TCSSNumericalID);
+procedure TTestNewCSSResolver.Test_Selector_AttributeContainsWord;
+var
+  Button1: TDemoButton;
 begin
 begin
-  FDemoDivTypeID:=aID;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
+  Doc.Root.ExplicitAttributes[naLeft]:='One Two Three';
+
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='Four Five';
+
+  Doc.Style:=LinesToStr([
+  '[left~=One] { top: 4px; }',
+  '[left~=Two] { width: 5px; }',
+  '[left~=Three] { height: 6px; }',
+  '[left~="Four Five"] { color: #123; }',  // not one word, so does not match!
+  '[left~=our] { display: none; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Root.Width','5px',Doc.Root.Width);
+  AssertEquals('Root.Height','6px',Doc.Root.Height);
+  AssertEquals('Root.Color','',Doc.Root.Color);
+  AssertEquals('Root.Display','',Doc.Root.Display);
+  AssertEquals('Button1.Top','',Button1.Top);
+  AssertEquals('Button1.Width','',Button1.Width);
+  AssertEquals('Button1.Height','',Button1.Height);
+  AssertEquals('Button1.Color','',Button1.Color);
+  AssertEquals('Button1.Display','inline-block',Button1.Display);
 end;
 end;
 
 
-class function TDemoDiv.GetCSSTypeStyle: TCSSString;
+procedure TTestNewCSSResolver.Test_Selector_AttributeContainsSubstring;
+var
+  Button1: TDemoButton;
 begin
 begin
-  Result:='div{ display: block }';
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
 
 
-{ TDemoSpan }
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
+  Button1.ExplicitAttributes[naLeft]:='Foo Bar';
 
 
-class function TDemoSpan.CSSTypeName: TCSSString;
-begin
-  Result:=DemoElementTypeNames[detSpan];
+  Doc.Style:=LinesToStr([
+  '[left*=oo] { top: 4px; }',
+  '[left*="o B"] { width: 5px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Button1.Top','4px',Button1.Top);
+  AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 end;
 
 
-class function TDemoSpan.GetClassCSSTypeID: TCSSNumericalID;
+procedure TTestNewCSSResolver.Test_Selector_Root;
+var
+  Button1: TDemoButton;
 begin
 begin
-  Result:=FDemoSpanTypeID;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.ExplicitAttributes[naLeft]:='Foo';
 
 
-class procedure TDemoSpan.SetClassCSSTypeID(aID: TCSSNumericalID);
-begin
-  FDemoSpanTypeID:=aID;
-end;
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
 
 
-class function TDemoSpan.GetCSSTypeStyle: TCSSString;
-begin
-  Result:='span{display: inline-block }';
+  Doc.Style:=LinesToStr([
+  ':roOt { top: 4px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Button1.Top','',Button1.Top);
 end;
 end;
 
 
-{ TDemoButton }
-
-procedure TDemoButton.SetCaption(const AValue: TCSSString);
+procedure TTestNewCSSResolver.Test_Selector_Empty;
+var
+  Div1, Div11, Div2: TDemoDiv;
 begin
 begin
-  if FCaption=AValue then Exit;
-  FCaption:=AValue;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-class function TDemoButton.CSSTypeName: TCSSString;
-begin
-  Result:=DemoElementTypeNames[detButton];
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-class function TDemoButton.GetClassCSSTypeID: TCSSNumericalID;
-begin
-  Result:=FDemoButtonTypeID;
-end;
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-class procedure TDemoButton.SetClassCSSTypeID(aID: TCSSNumericalID);
-begin
-  FDemoButtonTypeID:=aID;
-end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-class function TDemoButton.GetCSSTypeStyle: TCSSString;
-begin
-  Result:='button{display: inline-block }';
+  Doc.Style:=LinesToStr([
+  ':eMpty { left: 1px; }',
+  'div:emPty { top: 2px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div11.Left);
+  AssertEquals('Div11.Top','2px',Div11.Top);
+  AssertEquals('Div2.Left','1px',Div2.Left);
+  AssertEquals('Div2.Top','2px',Div2.Top);
 end;
 end;
 
 
-function TDemoButton.HasCSSExplicitAttribute(const AttrID: TCSSNumericalID
-  ): boolean;
+procedure TTestNewCSSResolver.Test_Selector_FirstChild;
+var
+  Div1, Div11, Div12, Div2: TDemoDiv;
 begin
 begin
-  //writeln('TDemoButton.HasCSSExplicitAttribute ',AttrID,' CSSCaptionID=',CSSCaptionID);
-  if AttrID=CSSCaptionID then
-    Result:=ExplicitCaption<>''
-  else
-    Result:=inherited HasCSSExplicitAttribute(AttrID);
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoButton.GetCSSExplicitAttribute(const AttrID: TCSSNumericalID
-  ): TCSSString;
-begin
-  if AttrID=CSSCaptionID then
-    Result:=ExplicitCaption
-  else
-    Result:=inherited GetCSSExplicitAttribute(AttrID);
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-{ TDemoDocument }
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-procedure TDemoDocument.SetStyle(const AValue: TCSSString);
-begin
-  if FStyle=AValue then Exit;
-  FStyle:=AValue;
-end;
+  Div12:=TDemoDiv.Create(nil);
+  Div12.Parent:=Div1;
 
 
-constructor TDemoDocument.Create(AOwner: TComponent);
-begin
-  inherited Create(AOwner);
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-  // create the css resolver
-  FCSSResolver:=TCSSResolver.Create(nil);
-  FCSSResolver.CSSRegistry:=TDemoNode.CSSRegistry;
+  Doc.Style:=LinesToStr([
+  ':first-child { left: 1px; }',
+  'div:first-child { top: 2px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','1px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','1px',Div1.Left);
+  AssertEquals('Div1.Top','2px',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div11.Left);
+  AssertEquals('Div11.Top','2px',Div11.Top);
+  AssertEquals('Div12.Left','',Div12.Left);
+  AssertEquals('Div12.Top','',Div12.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
 end;
 end;
 
 
-destructor TDemoDocument.Destroy;
+procedure TTestNewCSSResolver.Test_Selector_LastChild;
+var
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
 begin
 begin
-  FreeAndNil(Root);
-  FreeAndNil(FCSSResolver);
-  inherited Destroy;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-procedure TDemoDocument.ApplyStyle;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-  procedure Traverse(Node: TDemoNode);
-  var
-    i: Integer;
-  begin
-    Node.ApplyCSS(CSSResolver);
-    for i:=0 to Node.NodeCount-1 do
-      Traverse(Node[i]);
-  end;
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-begin
-  ApplyTypeStyles;
+  Button12:=TDemoButton.Create(nil);
+  Button12.Parent:=Div1;
 
 
-  CSSResolver.AddStyleSheet(cssoAuthor,'test.css',Style);
-  CSSResolver.Init;
-  Traverse(Root);
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':last-child { left: 6px; }',
+  'div:last-child { top: 7px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div2.Left','6px',Div2.Left);
+  AssertEquals('Div2.Top','7px',Div2.Top);
 end;
 end;
 
 
-procedure TDemoDocument.ApplyTypeStyles;
+procedure TTestNewCSSResolver.Test_Selector_OnlyChild;
 var
 var
-  FoundStyles: array of TDemoNodeClass;
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-  procedure AddTypeStyle(NodeClass: TDemoNodeClass);
-  var
-    i: Integer;
-    Src, ParentSrc: TCSSString;
-    ParentNodeClass: TDemoNodeClass;
-  begin
-    for i:=0 to length(FoundStyles)-1 do
-      if FoundStyles[i]=NodeClass then exit;
-    Insert(NodeClass,FoundStyles,length(FoundStyles));
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
 
 
-    Src:=NodeClass.GetCSSTypeStyle;
-    //writeln('AddTypeStyle ',NodeClass.ClassName,' Src="',Src,'"');
-    if Src='' then exit;
-    if NodeClass.ClassType<>TDemoNode then
-    begin
-      ParentNodeClass:=TDemoNodeClass(NodeClass.ClassParent);
-      AddTypeStyle(ParentNodeClass);
-      ParentSrc:=ParentNodeClass.GetCSSTypeStyle;
-      if Src=ParentSrc then exit;
-    end;
-    //writeln('AddTypeStyle ',NodeClass.ClassName,' [',Src,']');
-    FCSSResolver.AddStyleSheet(cssoUserAgent,NodeClass.ClassName,Src);
-  end;
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Name:='Div11';
+  Div11.Parent:=Div1;
 
 
-  procedure CollectTypeStyles(Node: TDemoNode);
-  var
-    NodeClass: TDemoNodeClass;
-    i: Integer;
-  begin
-    NodeClass:=TDemoNodeClass(Node.ClassType);
-    AddTypeStyle(NodeClass);
-    for i:=0 to Node.NodeCount-1 do
-      CollectTypeStyles(Node[i]);
-  end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Doc.Root;
 
 
-begin
-  FoundStyles:=[];
-  CollectTypeStyles(Root);
+  Button12:=TDemoButton.Create(nil);
+  Button12.Name:='Button12';
+  Button12.Parent:=Div2;
+
+  Doc.Style:=LinesToStr([
+  ':only-child { left: 8px; }',
+  'div:only-child { top: 9px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','8px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','8px',Div11.Left);
+  AssertEquals('Div11.Top','9px',Div11.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
+  AssertEquals('Button12.Left','8px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
 end;
 end;
 
 
-{ TDemoCSSRegistry }
-
-function TDemoCSSRegistry.OnCheck_Border(Resolver: TCSSBaseResolver): boolean;
+procedure TTestNewCSSResolver.Test_Selector_Not;
 var
 var
-  HasWidth, HasColor: Boolean;
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
 begin
 begin
-  HasWidth:=false;
-  HasColor:=false;
-  repeat
-    case Resolver.CurComp.Kind of
-    rvkFloat:
-      if not HasWidth then
-        HasWidth:=Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths);
-    rvkKeyword:
-      if not HasColor then
-        HasColor:=(Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor);
-    end;
-  until not Resolver.ReadNext;
-  Result:=HasWidth or HasColor;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-function TDemoCSSRegistry.OnCheck_BorderColor(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Color([]);
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
 
 
-function TDemoCSSRegistry.OnCheck_BorderWidth(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Dimension(Chk_BorderWidth);
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Name:='Div11';
+  Div11.Parent:=Div1;
+
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Doc.Root;
+
+  Button12:=TDemoButton.Create(nil);
+  Button12.Name:='Button12';
+  Button12.Parent:=Div2;
+
+  Doc.Style:=LinesToStr([
+  ':not(:only-child) { left: 8px; }',
+  ':not(div:only-child) { top: 9px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Root.Top','9px',Doc.Root.Top);
+  AssertEquals('Div1.Left','8px',Div1.Left);
+  AssertEquals('Div1.Top','9px',Div1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
+  AssertEquals('Div2.Left','8px',Div2.Left);
+  AssertEquals('Div2.Top','9px',Div2.Top);
+  AssertEquals('Button12.Left','',Button12.Left);
+  AssertEquals('Button12.Top','9px',Button12.Top);
 end;
 end;
 
 
-procedure TDemoCSSRegistry.OnSplit_Border(Resolver: TCSSBaseResolver;
-  var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray);
+procedure TTestNewCSSResolver.Test_Selector_NthChild;
 var
 var
-  aWidth, aColor: TCSSString;
+  Div1, Div2, Div3, Div4: TDemoDiv;
 begin
 begin
-  aWidth:='';
-  aColor:='';
-  repeat
-    case Resolver.CurComp.Kind of
-    rvkFloat:
-      if aWidth='' then begin
-        if Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths) then
-          aWidth:=Resolver.CurComp.FloatAsString;
-      end;
-    rvkKeyword:
-      if aColor='' then
-      begin
-        if (Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor) then
-          aColor:=Keywords[Resolver.CurComp.KeywordID];
-      end;
-    end;
-  until not Resolver.ReadNext;
-  SetLength(AttrIDs,2);
-  SetLength(Values,2);
-  AttrIDs[0]:=DemoAttrs[naBorderWidth].Index;
-  Values[0]:=aWidth;
-  AttrIDs[1]:=DemoAttrs[naBorderColor].Index;
-  Values[1]:=aColor;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoCSSRegistry.OnCheck_Direction(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Keyword(Chk_DirectionAllowedKeywordIDs);
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-function TDemoCSSRegistry.OnCheck_Display(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Keyword(Chk_DisplayAllowedKeywordIDs);
-end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-function TDemoCSSRegistry.OnCheck_LeftTop(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Dimension(Chk_LeftTop);
-end;
+  Div3:=TDemoDiv.Create(nil);
+  Div3.Parent:=Doc.Root;
 
 
-function TDemoCSSRegistry.OnCheck_WidthHeight(Resolver: TCSSBaseResolver): boolean;
-begin
-  Result:=Resolver.CheckAttribute_Dimension(Chk_WidthHeight);
-end;
+  Div4:=TDemoDiv.Create(nil);
+  Div4.Parent:=Doc.Root;
 
 
-procedure TDemoCSSRegistry.OnCompute_Direction(Resolver: TCSSResolver;
-  Node: TDemoNode; Value: TCSSAttributeValue);
-var
-  Invalid: boolean;
-begin
-  if Resolver.ReadAttribute_Keyword(Invalid,Chk_DirectionAllowedKeywordIDs) then
-  begin
-    Value.Value:=Keywords[Resolver.CurComp.KeywordID];
-    Value.State:=cavsComputed;
-  end
-  else begin
-    Value.Value:='invalid';
-    Value.State:=cavsInvalid;
-  end;
-  if Node=nil then ;
+  Doc.Style:=LinesToStr([
+  'div:nth-child(2n+1) { left: 8px; }',
+  'div:nth-child(n+3) { border-width: 6px; }',
+  'div:nth-child(-n+2) { height: 7em; }',
+  'div:nth-child(even) { top: 3px; }',
+  'div:nth-child(odd) { width: 4px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Root.BorderWidth','',Doc.Root.BorderWidth);
+  AssertEquals('Root.Height','',Doc.Root.Height);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Root.Width','',Doc.Root.Width);
+  AssertEquals('Div1.Left','8px',Div1.Left);
+  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
+  AssertEquals('Div1.Height','7em',Div1.Height);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div1.Width','4px',Div1.Width);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.BorderWidth','',Div2.BorderWidth);
+  AssertEquals('Div2.Height','7em',Div2.Height);
+  AssertEquals('Div2.Top','3px',Div2.Top);
+  AssertEquals('Div2.Width','',Div2.Width);
+  AssertEquals('Div3.Left','8px',Div3.Left);
+  AssertEquals('Div3.BorderWidth','6px',Div3.BorderWidth);
+  AssertEquals('Div3.Height','',Div3.Height);
+  AssertEquals('Div3.Top','',Div3.Top);
+  AssertEquals('Div3.Width','4px',Div3.Width);
+  AssertEquals('Div4.Left','',Div4.Left);
+  AssertEquals('Div4.BorderWidth','6px',Div4.BorderWidth);
+  AssertEquals('Div4.Height','',Div4.Height);
+  AssertEquals('Div4.Top','3px',Div4.Top);
+  AssertEquals('Div4.Width','',Div4.Width);
 end;
 end;
 
 
-procedure TDemoCSSRegistry.OnCompute_LeftTop(Resolver: TCSSResolver;
-  Node: TDemoNode; Value: TCSSAttributeValue);
+procedure TTestNewCSSResolver.Test_Selector_NthLastChild;
 var
 var
-  Invalid: boolean;
+  Div1, Div2, Div3, Div4: TDemoDiv;
 begin
 begin
-  if Resolver.ReadAttribute_Dimension(Invalid,Chk_LeftTop) then
-  begin
-    case Resolver.CurComp.Kind of
-    rvkFloat:
-      Value.Value:=Resolver.CurComp.FloatAsString;
-    rvkKeyword:
-      Value.Value:=Keywords[Resolver.CurComp.KeywordID];
-    end;
-    Value.State:=cavsComputed;
-  end
-  else begin
-    Value.Value:='invalid';
-    Value.State:=cavsInvalid;
-  end;
-  if Node=nil then ;
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
+
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
+
+  Div3:=TDemoDiv.Create(nil);
+  Div3.Parent:=Doc.Root;
+
+  Div4:=TDemoDiv.Create(nil);
+  Div4.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':nth-last-child(2n+1) { left: 8px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div2.Left','8px',Div2.Left);
+  AssertEquals('Div3.Left','',Div3.Left);
+  AssertEquals('Div4.Left','8px',Div4.Left);
 end;
 end;
 
 
-procedure TDemoCSSRegistry.OnCompute_WidthHeight(Resolver: TCSSResolver;
-  Node: TDemoNode; Value: TCSSAttributeValue);
+procedure TTestNewCSSResolver.Test_Selector_NthChildOf;
 var
 var
-  Invalid: boolean;
+  Div1, Div2, Div3, Div4: TDemoDiv;
 begin
 begin
-  if Resolver.ReadAttribute_Dimension(Invalid,Chk_WidthHeight) then
-  begin
-    Value.Value:=Resolver.CurComp.FloatAsString;
-    Value.State:=cavsComputed;
-  end
-  else begin
-    Value.Value:='invalid';
-    Value.State:=cavsInvalid;
-  end;
-  if Node=nil then ;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-constructor TDemoCSSRegistry.Create;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
 
 
-  procedure SetDemoElementTypeID(aClass: TDemoNodeClass);
-  var
-    Desc: TCSSTypeDesc;
-  begin
-    Desc:=FindType(aClass.CSSTypeName);
-    if Desc=nil then
-      raise Exception.Create('20240625190912');
-    aClass.SetClassCSSTypeID(Desc.Index);
-  end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Doc.Root;
+  Div2.ExplicitAttributes[naTop]:='3px';
 
 
-  procedure SetCompProps(ShorthandID: TDemoNodeAttribute; Longhands: array of TDemoNodeAttribute);
-  var
-    i: Integer;
-  begin
-    SetLength(DemoAttrs[ShorthandID].CompProps,length(Longhands));
-    for i:=0 to length(Longhands)-1 do
-      DemoAttrs[ShorthandID].CompProps[i]:=DemoAttrs[Longhands[i]];
-  end;
+  Div3:=TDemoDiv.Create(nil);
+  Div3.Name:='Div3';
+  Div3.Parent:=Doc.Root;
+  Div3.ExplicitAttributes[naTop]:='3px';
+
+  Div4:=TDemoDiv.Create(nil);
+  Div4.Name:='Div4';
+  Div4.Parent:=Doc.Root;
+  Div4.ExplicitAttributes[naTop]:='3px';
+
+  Doc.Style:=LinesToStr([
+  ':nth-child(2n+1 of [top=3px]) { left: 5px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div2.Left','5px',Div2.Left);
+  AssertEquals('Div3.Left','',Div3.Left);
+  AssertEquals('Div4.Left','5px',Div4.Left);
+end;
 
 
+procedure TTestNewCSSResolver.Test_Selector_FirstOfType;
 var
 var
-  Attr: TDemoNodeAttribute;
-  PseudoClass: TDemoPseudoClass;
-  aType: TDemoElementType;
+  Div1, Div11, Div13, Div2: TDemoDiv;
+  Button12: TDemoButton;
 begin
 begin
-  inherited Create;
-  Init;
-
-  // register demo attributes
-  for Attr in TDemoNodeAttribute do
-    AddDemoAttr(Attr);
-  DemoAttrIDBase:=DemoAttrs[low(TDemoNodeAttribute)].Index;
-  if FindAttribute(DemoAttributeNames[naBackground]).Index<>DemoAttrIDBase+ord(naBackground) then
-    raise Exception.Create('20240617200337');
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-  // register demo pseudo classes
-  for PseudoClass in TDemoPseudoClass do
-    AddDemoPseudoClass(PseudoClass);
-  DemoPseudoClassIDBase:=DemoPseudoClasses[low(TDemoPseudoClass)].Index;
-  if FindPseudoClass(DemoPseudoClassNames[pcHover]).Index<>DemoPseudoClassIDBase+ord(pcHover) then
-    raise Exception.Create('20231008232201');
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-  // register demo element types
-  for aType in TDemoElementType do
-    AddDemoType(aType);
-  DemoElementTypeIDBase:=DemoTypes[low(TDemoElementType)].Index;
-  if FindType(DemoElementTypeNames[detButton]).Index<>DemoElementTypeIDBase+ord(detButton) then
-    raise Exception.Create('20240625181725');
-  SetDemoElementTypeID(TDemoNode);
-  SetDemoElementTypeID(TDemoDiv);
-  SetDemoElementTypeID(TDemoSpan);
-  SetDemoElementTypeID(TDemoButton);
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-  kwRed:=AddKeyword('red');
-  kwFirstColor:=kwRed;
-  kwGreen:=AddKeyword('green');
-  kwBlue:=AddKeyword('blue');
-  kwWhite:=AddKeyword('white');
-  kwBlack:=AddKeyword('black');
-  kwLastColor:=kwBlack;
+  Button12:=TDemoButton.Create(nil);
+  Button12.Parent:=Div1;
 
 
-  kwBlock:=AddKeyword('block');
-  kwInline_Block:=AddKeyword('inline-block');
+  Div13:=TDemoDiv.Create(nil);
+  Div13.Parent:=Div1;
 
 
-  kwLTR:=AddKeyword('ltr');
-  kwRTL:=AddKeyword('rtl');
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-  // check parameters - - - - - - - - - - - - - - - - - - - - - - - -
+  Doc.Style:=LinesToStr([
+  ':first-of-type { left: 6px; }',
+  'div:first-of-type { top: 7px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','6px',Div1.Left);
+  AssertEquals('Div1.Top','7px',Div1.Top);
+  AssertEquals('Div11.Left','6px',Div11.Left);
+  AssertEquals('Div11.Top','7px',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div13.Left','',Div13.Left);
+  AssertEquals('Div13.Top','',Div13.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
+end;
 
 
-  // border-color
-  DemoAttrs[naBorderColor].OnCheck:=@OnCheck_BorderColor;
+procedure TTestNewCSSResolver.Test_Selector_LastOfType;
+var
+  Div1, Div11, Div13, Div2: TDemoDiv;
+  Button12: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-  // border-width
-  DemoAttrs[naBorderWidth].OnCheck:=@OnCheck_BorderWidth;
-  Chk_BorderWidth.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  Chk_BorderWidth.AllowFrac:=true;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-  // border shorthand
-  SetCompProps(naBorder,[naBorderColor,naBorderWidth]);
-  DemoAttrs[naBorder].OnCheck:=@OnCheck_Border;
-  DemoAttrs[naBorder].OnSplitShorthand:=@OnSplit_Border;
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-  // direction
-  DemoAttrs[naDirection].OnCheck:=@OnCheck_Direction;
-  Chk_DirectionAllowedKeywordIDs:=[kwLTR,kwRTL];
-  DemoAttrs[naDirection].OnCompute:=@OnCompute_Direction;
+  Button12:=TDemoButton.Create(nil);
+  Button12.Parent:=Div1;
 
 
-  // display
-  DemoAttrs[naDisplay].OnCheck:=@OnCheck_Display;
-  Chk_DisplayAllowedKeywordIDs:=[kwBlock,kwInline_Block];
+  Div13:=TDemoDiv.Create(nil);
+  Div13.Parent:=Div1;
 
 
-  // left, top
-  DemoAttrs[naLeft].OnCheck:=@OnCheck_LeftTop;
-  DemoAttrs[naLeft].OnCompute:=@OnCompute_LeftTop;
-  DemoAttrs[naTop].OnCheck:=@OnCheck_LeftTop;
-  DemoAttrs[naTop].OnCompute:=@OnCompute_LeftTop;
-  Chk_LeftTop.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  Chk_LeftTop.AllowNegative:=true;
-  Chk_LeftTop.AllowFrac:=true;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-  // width, height
-  DemoAttrs[naWidth].OnCheck:=@OnCheck_WidthHeight;
-  DemoAttrs[naWidth].OnCompute:=@OnCompute_WidthHeight;
-  DemoAttrs[naHeight].OnCheck:=@OnCheck_WidthHeight;
-  DemoAttrs[naHeight].OnCompute:=@OnCompute_WidthHeight;
-  Chk_WidthHeight.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  Chk_WidthHeight.AllowFrac:=true;
+  Doc.Style:=LinesToStr([
+  ':last-of-type { left: 6px; }',
+  'div:last-of-type { top: 7px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div13.Left','6px',Div13.Left);
+  AssertEquals('Div13.Top','7px',Div13.Top);
+  AssertEquals('Div2.Left','6px',Div2.Left);
+  AssertEquals('Div2.Top','7px',Div2.Top);
 end;
 end;
 
 
-function TDemoCSSRegistry.AddDemoAttr(Attr: TDemoNodeAttribute
-  ): TDemoCSSAttributeDesc;
+procedure TTestNewCSSResolver.Test_Selector_OnlyOfType;
+var
+  Div1, Div11, Div2: TDemoDiv;
+  Button12: TDemoButton;
 begin
 begin
-  Result:=TDemoCSSAttributeDesc(AddAttribute(DemoAttributeNames[Attr],
-     DemoAttributeInitialValues[Attr],
-     Attr in DemoAttributesInherited,
-     not (Attr in DemoAttributesNotAll),
-     TDemoCSSAttributeDesc));
-  Result.DemoID:=Attr;
-  DemoAttrs[Attr]:=Result;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoCSSRegistry.AddDemoPseudoClass(PC: TDemoPseudoClass
-  ): TDemoCSSPseudoClassDesc;
-begin
-  Result:=TDemoCSSPseudoClassDesc(AddPseudoClass(DemoPseudoClassNames[PC],
-     TDemoCSSPseudoClassDesc));
-  Result.DemoID:=PC;
-  DemoPseudoClasses[PC]:=Result;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-function TDemoCSSRegistry.AddDemoType(aType: TDemoElementType
-  ): TDemoCSSTypeDesc;
-begin
-  Result:=TDemoCSSTypeDesc(AddType(DemoElementTypeNames[aType],
-     TDemoCSSTypeDesc));
-  Result.DemoID:=aType;
-  DemoTypes[aType]:=Result;
-end;
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Parent:=Div1;
 
 
-{ TDemoNode }
+  Button12:=TDemoButton.Create(nil);
+  Button12.Parent:=Div1;
 
 
-function TDemoNode.GetAttribute(DemoAttr: TDemoNodeAttribute): TCSSString;
-var
-  AttrDesc: TDemoCSSAttributeDesc;
-  i: Integer;
-begin
-  AttrDesc:=CSSRegistry.DemoAttrs[DemoAttr];
-  i:=Values.IndexOf(AttrDesc.Index);
-  if i>=0 then
-    Result:=Values.Values[i].Value
-  else
-    Result:='';
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':only-of-type { left: 6px; }',
+  'div:only-of-type { top: 7px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','6px',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','6px',Div11.Left);
+  AssertEquals('Div11.Top','7px',Div11.Top);
+  AssertEquals('Button12.Left','6px',Button12.Left);
+  AssertEquals('Button12.Top','',Button12.Top);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div2.Top','',Div2.Top);
 end;
 end;
 
 
-function TDemoNode.GetNodeCount: integer;
+procedure TTestNewCSSResolver.Test_Selector_NthOfType;
+var
+  Div1, Div2, Div3, Div4: TDemoDiv;
+  Button1, Button2: TDemoButton;
 begin
 begin
-  Result:=FNodes.Count;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Doc.Root;
+
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Doc.Root;
+
+  Div3:=TDemoDiv.Create(nil);
+  Div3.Name:='Div3';
+  Div3.Parent:=Doc.Root;
+
+  Button2:=TDemoButton.Create(nil);
+  Button2.Name:='Button2';
+  Button2.Parent:=Doc.Root;
+
+  Div4:=TDemoDiv.Create(nil);
+  Div4.Name:='Div4';
+  Div4.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':nth-of-type(2n+1) { left: 8px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','8px',Div1.Left);
+  AssertEquals('Button1.Left','8px',Button1.Left);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Div3.Left','8px',Div3.Left);
+  AssertEquals('Button2.Left','',Button2.Left);
+  AssertEquals('Div4.Left','',Div4.Left);
 end;
 end;
 
 
-function TDemoNode.GetNodes(Index: integer): TDemoNode;
+procedure TTestNewCSSResolver.Test_Selector_NthLastOfType;
+var
+  Div1, Div2, Div3, Div4: TDemoDiv;
+  Button1, Button2: TDemoButton;
 begin
 begin
-  Result:=TDemoNode(FNodes[Index]);
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoNode.GetPseudoClasses(PseudoClass: TDemoPseudoClass): boolean;
-begin
-  Result:=FPseudoClasses[PseudoClass];
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
 
 
-procedure TDemoNode.SetParent(const AValue: TDemoNode);
-begin
-  if FParent=AValue then Exit;
-  if AValue=Self then
-    raise Exception.Create('cycle');
+  Button1:=TDemoButton.Create(nil);
+  Button1.Parent:=Doc.Root;
 
 
-  if FParent<>nil then
-  begin
-    FParent.FNodes.Remove(Self);
-  end;
-  FParent:=AValue;
-  if FParent<>nil then
-  begin
-    FParent.FNodes.Add(Self);
-    FreeNotification(FParent);
-  end;
-end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-procedure TDemoNode.SetInlineStyleElements(const AValue: TCSSRuleElement);
-begin
-  if FInlineStyleElements=AValue then Exit;
-  FreeAndNil(FInlineStyleElements);
-  FInlineStyleElements:=AValue;
-end;
+  Div3:=TDemoDiv.Create(nil);
+  Div3.Parent:=Doc.Root;
 
 
-procedure TDemoNode.SetInlineStyle(const AValue: TCSSString);
-begin
-  if FInlineStyle=AValue then Exit;
-  FInlineStyle:=AValue;
-  FreeAndNil(FInlineStyleElements);
-end;
+  Button2:=TDemoButton.Create(nil);
+  Button2.Parent:=Doc.Root;
 
 
-procedure TDemoNode.SetPseudoClasses(PseudoClass: TDemoPseudoClass;
-  const AValue: boolean);
-begin
-  FPseudoClasses[PseudoClass]:=AValue;
-end;
+  Div4:=TDemoDiv.Create(nil);
+  Div4.Parent:=Doc.Root;
 
 
-procedure TDemoNode.Notification(AComponent: TComponent; Operation: TOperation);
-begin
-  inherited Notification(AComponent, Operation);
-  if AComponent=Self then exit;
-  if Operation=opRemove then
-  begin
-    if FNodes<>nil then
-      FNodes.Remove(AComponent);
-  end;
+  Doc.Style:=LinesToStr([
+  ':nth-last-of-type(2n+1) { left: 8px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','',Div1.Left);
+  AssertEquals('Button1.Left','',Button1.Left);
+  AssertEquals('Div2.Left','8px',Div2.Left);
+  AssertEquals('Div3.Left','',Div3.Left);
+  AssertEquals('Button2.Left','8px',Button2.Left);
+  AssertEquals('Div4.Left','8px',Div4.Left);
 end;
 end;
 
 
-constructor TDemoNode.Create(AOwner: TComponent);
+procedure TTestNewCSSResolver.Test_Selector_Is;
+var
+  Div1, Div2: TDemoDiv;
+  Button1, Button2: TDemoButton;
+  Span1: TDemoSpan;
 begin
 begin
-  inherited Create(AOwner);
-  FNodes:=TFPObjectList.Create(false);
-  FCSSClasses:=TStringList.Create;
-  FCSSClasses.Delimiter:=' ';
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-destructor TDemoNode.Destroy;
-begin
-  Clear;
-  FreeAndNil(FNodes);
-  FreeAndNil(FCSSClasses);
-  inherited Destroy;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.ExplicitAttributes[naTop]:='3px';
 
 
-procedure TDemoNode.Clear;
-var
-  i: Integer;
-begin
-  Rules:=nil;
-  FreeAndNil(Values);
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Doc.Root;
 
 
-  FCSSClasses.Clear;
-  FreeAndNil(FInlineStyleElements);
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Parent:=Doc.Root;
 
 
-  for i:=NodeCount-1 downto 0 do
-    Nodes[i].Free;
-  if FNodes.Count>0 then
-    raise Exception.Create('20240710174459');
+  Span1:=TDemoSpan.Create(nil);
+  Span1.Parent:=Doc.Root;
+  Span1.ExplicitAttributes[naTop]:='3px';
+
+  Button2:=TDemoButton.Create(nil);
+  Button2.Parent:=Doc.Root;
+  Button2.ExplicitAttributes[naTop]:='3px';
+
+  Doc.Style:=LinesToStr([
+  ':is(div, button)[top=3px] { left: 7px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','7px',Div1.Left);
+  AssertEquals('Button1.Left','',Button1.Left);
+  AssertEquals('Div2.Left','',Div2.Left);
+  AssertEquals('Span1.Left','',Div2.Left);
+  AssertEquals('Button2.Left','7px',Button2.Left);
 end;
 end;
 
 
-procedure TDemoNode.ApplyCSS(Resolver: TCSSResolver);
+procedure TTestNewCSSResolver.Test_Selector_Where;
 var
 var
-  AttrDesc: TDemoCSSAttributeDesc;
-  i: Integer;
-  AttrID: TCSSNumericalID;
-  CurValue: TCSSAttributeValue;
+  Div1, Div2: TDemoDiv;
 begin
 begin
-  if (InlineStyleElement=nil) and (InlineStyle<>'') then
-    InlineStyleElement:=Resolver.ParseInlineStyle(InlineStyle) as TCSSRuleElement;
-
-  Resolver.Compute(Self,InlineStyleElement,Rules,Values);
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-  {$IFDEF VerboseCSSResolver}
-  writeln('TDemoNode.ApplyCSS ',Name,' length(Values)=',length(Values.Values),' All="',CSSRegistry.Keywords[Values.AllValue],'"');
-  for i:=0 to length(Values.Values)-1 do begin
-    AttrID:=Values.Values[i].AttrID;
-    writeln('TDemoNode.ApplyCSS ',Name,' resolved ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',Values.Values[i].Value,'"');
-  end;
-  {$ENDIF}
-  // compute values
-  for i:=0 to length(Values.Values)-1 do
-  begin
-    CurValue:=Values.Values[i];
-    case CurValue.State of
-      cavsSource, cavsBaseKeywords:
-        begin
-          AttrID:=CurValue.AttrID;
-          AttrDesc:=CSSRegistry.Attributes[AttrID] as TDemoCSSAttributeDesc;
-          if AttrDesc.OnCompute<>nil then
-          begin
-            Resolver.CurComp.EndP:=PChar(CurValue.Value);
-            Resolver.ReadNext;
-            AttrDesc.OnCompute(Resolver,Self,CurValue);
-            {$IFDEF VerboseCSSResolver}
-            writeln('TDemoNode.ApplyCSS ',Name,' computed ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',CurValue.Value,'"');
-            {$ENDIF}
-          end else
-            CurValue.State:=cavsComputed;
-        end;
-      cavsComputed: ;
-      cavsInvalid: ;
-    end;
-  end;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.ExplicitAttributes[naTop]:='3px';
 
 
-function TDemoNode.GetCSSID: TCSSString;
-begin
-  Result:=Name;
-end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Div1;
+  Div2.ExplicitAttributes[naTop]:='3px';
 
 
-class function TDemoNode.CSSTypeName: TCSSString;
-begin
-  Result:=DemoElementTypeNames[detNode];
+  Doc.Style:=LinesToStr([
+  ':where(div[top=3px]) { left: 1px; }',
+  'div div { left: 2px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','1px',Div1.Left);
+  AssertEquals('Div2.Left','2px',Div2.Left);
 end;
 end;
 
 
-function TDemoNode.HasCSSClass(const aClassName: TCSSString): boolean;
+procedure TTestNewCSSResolver.Test_Selector_Hover;
 var
 var
-  i: Integer;
+  Div1, Div11: TDemoDiv;
+  Button1: TDemoButton;
 begin
 begin
-  for i:=0 to CSSClasses.Count-1 do
-    if aClassName=CSSClasses[i] then
-      exit(true);
-  Result:=false;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Name:='root';
 
 
-function TDemoNode.GetCSSParent: ICSSNode;
-begin
-  Result:=Parent;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.Hover:=true;
 
 
-function TDemoNode.GetCSSIndex: integer;
-begin
-  if Parent=nil then
-    Result:=-1
-  else
-    Result:=Parent.FNodes.IndexOf(Self);
-end;
+  Button1:=TDemoButton.Create(nil);
+  Button1.Name:='Button1';
+  Button1.Parent:=Div1;
+  Button1.Hover:=true;
 
 
-function TDemoNode.GetCSSNextSibling: ICSSNode;
-var
-  i: Integer;
-begin
-  i:=GetCSSIndex;
-  if (i<0) or (i+1>=Parent.NodeCount) then
-    Result:=nil
-  else
-    Result:=Parent.Nodes[i+1];
+  Div11:=TDemoDiv.Create(nil);
+  Div11.Name:='Div11';
+  Div11.Parent:=Div1;
+
+  Doc.Style:=LinesToStr([
+  ':hover { left: 1px; }',
+  'button:hover { top: 2px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Root.Top','',Doc.Root.Top);
+  AssertEquals('Div1.Left','1px',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Button1.Left','1px',Button1.Left);
+  AssertEquals('Button1.Top','2px',Button1.Top);
+  AssertEquals('Div11.Left','',Div11.Left);
+  AssertEquals('Div11.Top','',Div11.Top);
 end;
 end;
 
 
-function TDemoNode.GetCSSPreviousSibling: ICSSNode;
+procedure TTestNewCSSResolver.Test_InlineStyle;
 var
 var
-  i: Integer;
+  Div1: TDemoDiv;
 begin
 begin
-  i:=GetCSSIndex;
-  if i<1 then
-    Result:=nil
-  else
-    Result:=Parent.Nodes[i-1];
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoNode.GetCSSChildCount: integer;
-begin
-  Result:=NodeCount;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Parent:=Doc.Root;
+  Div1.InlineStyle:='left: 10px; top: 5px';
 
 
-function TDemoNode.GetCSSChild(const anIndex: integer): ICSSNode;
-begin
-  Result:=Nodes[anIndex];
+  Doc.Style:=LinesToStr([
+  'div { left: 6px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','10px',Div1.Left);
+  AssertEquals('Div1.Top','5px',Div1.Top);
 end;
 end;
 
 
-function TDemoNode.GetCSSNextOfType: ICSSNode;
+procedure TTestNewCSSResolver.Test_Specifity_Id_Class;
 var
 var
-  i, Cnt: Integer;
-  MyID: TCSSNumericalID;
-  aNode: TDemoNode;
+  Div1: TDemoDiv;
 begin
 begin
-  Result:=nil;
-  i:=GetCSSIndex;
-  if i<0 then exit;
-  inc(i);
-  MyID:=GetClassCSSTypeID;
-  Cnt:=Parent.NodeCount;
-  while i<Cnt do
-  begin
-    aNode:=Parent.Nodes[i];
-    if aNode.GetClassCSSTypeID=MyID then
-      exit(aNode);
-    inc(i);
-  end;
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
+
+  Doc.Style:=LinesToStr([
+  '.bird { left: 6px; }',
+  '#Div1 { left: 7px; top: 8px; }', // id has higher specifity, no matter if before or after a .class
+  '.bird { top: 9px; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','7px',Div1.Left);
+  AssertEquals('Div1.Top','8px',Div1.Top);
 end;
 end;
 
 
-function TDemoNode.GetCSSPreviousOfType: ICSSNode;
+procedure TTestNewCSSResolver.Test_Specifity_Important;
 var
 var
-  i: Integer;
-  MyID: TCSSNumericalID;
-  aNode: TDemoNode;
+  Div1: TDemoDiv;
 begin
 begin
-  Result:=nil;
-  i:=GetCSSIndex;
-  if i<0 then exit;
-  dec(i);
-  MyID:=GetClassCSSTypeID;
-  while i>=0 do
-  begin
-    aNode:=Parent.Nodes[i];
-    if aNode.GetClassCSSTypeID=MyID then
-      exit(aNode);
-    dec(i);
-  end;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoNode.GetCSSAttributeClass: TCSSString;
-begin
-  FCSSClasses.Delimiter:=' ';
-  Result:=FCSSClasses.DelimitedText;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
+
+  Doc.Style:=LinesToStr([
+  '.bird { left: 6px !important; }',
+  '#Div1 { left: 7px; top: 8px; }',
+  '.bird { top: 9px ! important; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Root.Left','',Doc.Root.Left);
+  AssertEquals('Div1.Left','6px',Div1.Left);
+  AssertEquals('Div1.Top','9px',Div1.Top);
 end;
 end;
 
 
-function TDemoNode.HasCSSExplicitAttribute(const AttrID: TCSSNumericalID): boolean;
+procedure TTestNewCSSResolver.Test_Specifity_Shorthand_OneRule;
 var
 var
-  b: TCSSNumericalID;
-  Attr: TDemoNodeAttribute;
+  Div1: TDemoDiv;
 begin
 begin
-  b:=CSSRegistry.DemoAttrIDBase;
-  if (AttrID<b) or (AttrID>b+ord(High(TDemoNodeAttribute))) then
-    exit(false);
-  Attr:=TDemoNodeAttribute(AttrID-b);
-  Result:=ExplicitAttributes[Attr]<>'';
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
+
+  Doc.Style:='.bird { border-color: blue; border: 6px red; border-width: 7px; }';
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','red',Div1.BorderColor);
+  AssertEquals('Div1.BorderWidth','7px',Div1.BorderWidth);
 end;
 end;
 
 
-function TDemoNode.GetCSSExplicitAttribute(const AttrID: TCSSNumericalID): TCSSString;
+procedure TTestNewCSSResolver.Test_Specifity_Shorthand_ClassClass;
 var
 var
-  Attr: TDemoNodeAttribute;
-  b: TCSSNumericalID;
+  Div1: TDemoDiv;
 begin
 begin
-  b:=CSSRegistry.DemoAttrIDBase;
-  if (AttrID<b) or (AttrID>b+ord(High(TDemoNodeAttribute))) then
-    exit('');
-  Attr:=TDemoNodeAttribute(AttrID-b);
-  Result:=ExplicitAttributes[Attr];
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
+  Div1.CSSClasses.Add('eagle');
+
+  Doc.Style:=LinesToStr([
+  '.bird.eagle { border-color: blue; }',
+  '.bird { border-width: 6px; }',
+  '.bird { border: 7px red; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
+  AssertEquals('Div1.BorderWidth','7px',Div1.BorderWidth);
 end;
 end;
 
 
-function TDemoNode.HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean;
+procedure TTestNewCSSResolver.Test_Specifity_Longhand_All_Longhand;
 var
 var
-  b: TCSSNumericalID;
+  Div1: TDemoDiv;
 begin
 begin
-  b:=CSSRegistry.DemoPseudoClassIDBase;
-  if (AttrID>=b) and (AttrID<=b+ord(High(TDemoPseudoClass))) then
-    Result:=HasPseudoClass[TDemoPseudoClass(AttrID-b)]
-  else
-    Result:=false;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoNode.GetCSSEmpty: boolean;
-begin
-  Result:=NodeCount=0;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
+  Div1.CSSClasses.Add('eagle');
+
+  Doc.Style:=LinesToStr([
+  '.bird.eagle { border-color: blue; }',
+  '.bird { border-width: 7px; }',
+  '.bird { all: initial; }',
+  '.bird { background: red; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
+  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
+  AssertEquals('Div1.Background','red',Div1.Background);
 end;
 end;
 
 
-function TDemoNode.GetCSSDepth: integer;
+procedure TTestNewCSSResolver.Test_Specifity_Shorthand_All_Shorthand;
 var
 var
-  Node: TDemoNode;
+  Div1, Div2: TDemoDiv;
 begin
 begin
-  Result:=0;
-  Node:=Parent;
-  while Node<>nil do
-  begin
-    inc(Result);
-    Node:=Node.Parent;
-  end;
-end;
+  Doc.Root:=TDemoNode.Create(nil);
 
 
-function TDemoNode.GetCSSTypeName: TCSSString;
-begin
-  Result:=CSSTypeName;
-end;
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+  Div1.CSSClasses.Add('bird');
 
 
-class function TDemoNode.GetClassCSSTypeID: TCSSNumericalID;
-begin
-  Result:=FDemoNodeTypeID;
-end;
+  Div2:=TDemoDiv.Create(nil);
+  Div2.Name:='Div2';
+  Div2.Parent:=Doc.Root;
+  Div2.CSSClasses.Add('eagle');
 
 
-class procedure TDemoNode.SetClassCSSTypeID(aID: TCSSNumericalID);
-begin
-  FDemoNodeTypeID:=aID;
+  Doc.Style:=LinesToStr([
+  '.bird { border: 7px blue; }',
+  '.bird { all: initial; }',
+  '.eagle { all: initial; }',
+  '.eagle { border: 8px red; }',
+  '']);
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','',Div1.BorderColor);
+  AssertEquals('Div1.BorderWidth','',Div1.BorderWidth);
+  AssertEquals('Div2.BorderColor','red',Div2.BorderColor);
+  AssertEquals('Div2.BorderWidth','8px',Div2.BorderWidth);
 end;
 end;
 
 
-function TDemoNode.GetCSSTypeID: TCSSNumericalID;
+procedure TTestNewCSSResolver.Test_Origin_Id_Class;
+var
+  Div1: TDemoDiv;
 begin
 begin
-  Result:=GetClassCSSTypeID;
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+
+  Doc.CSSResolver.AddStyleSheet(cssoUserAgent,'testagent',
+  '#Div1 { border-width: 2px;'
+  +' border-color: blue !important;'
+  +' background: green; }'
+  );
+
+  Doc.Style:=LinesToStr([
+  'div { border-width: 3px; ', // although class has lower spec than id, author origin wins
+  ' border-color: orange;', // former important always wins
+  '}']);
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','blue',Div1.BorderColor);
+  AssertEquals('Div1.BorderWidth','3px',Div1.BorderWidth);
+  AssertEquals('Div1.Background','green',Div1.Background);
 end;
 end;
 
 
-class function TDemoNode.GetCSSTypeStyle: TCSSString;
+procedure TTestNewCSSResolver.Test_Var;
+var
+  Div1: TDemoDiv;
 begin
 begin
-  Result:='';
+  exit;
+
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
+  Div1.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  'div {',
+  '  --bird-color: red;',
+  '}',
+  'div {',
+  '  border-color: var(--bird-color);',
+  '  border-width: var(--bird-width);',
+  '}',
+  'div {',
+  '  --bird-width: 3px;',
+  '}']);
+  ApplyStyle;
+  AssertEquals('Div1.BorderColor','red',Div1.BorderColor);
 end;
 end;
 
 
 initialization
 initialization