Browse Source

fcl-css: resolver: adjacent and general sibling combinator

mattias 2 years ago
parent
commit
a6afacd633
2 changed files with 142 additions and 9 deletions
  1. 40 6
      packages/fcl-css/src/fpcssresolver.pas
  2. 102 3
      packages/fcl-css/tests/tccssresolver.pp

+ 40 - 6
packages/fcl-css/src/fpcssresolver.pas

@@ -55,6 +55,8 @@ type
     function GetCSSTypeID: TCSSNumericalID;
     function GetCSSTypeID: TCSSNumericalID;
     function HasCSSClass(const aClassName: TCSSString): boolean;
     function HasCSSClass(const aClassName: TCSSString): boolean;
     function GetCSSParent: TCSSNode;
     function GetCSSParent: TCSSNode;
+    function GetCSSIndex: integer; // node index in parent's children
+    function GetCSSPreviousSibling: TCSSNode;
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
   end;
   end;
 
 
@@ -372,23 +374,55 @@ end;
 function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
 function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
   const TestNode: TCSSNode): TCSSSpecifity;
   const TestNode: TCSSNode): TCSSSpecifity;
 var
 var
-  aParent: TCSSNode;
-  ParentSpecifity: TCSSSpecifity;
+  aParent, Sibling: TCSSNode;
+  aSpecifity: TCSSSpecifity;
 begin
 begin
   Result:=-1;
   Result:=-1;
   case aBinary.Operation of
   case aBinary.Operation of
   boGT:
   boGT:
     begin
     begin
+      // child combinator
       Result:=SelectorMatches(aBinary.Right,TestNode);
       Result:=SelectorMatches(aBinary.Right,TestNode);
       if Result<0 then exit;
       if Result<0 then exit;
       aParent:=TestNode.GetCSSParent;
       aParent:=TestNode.GetCSSParent;
       if aParent=nil then
       if aParent=nil then
         exit(-1);
         exit(-1);
-      ParentSpecifity:=SelectorMatches(aBinary.Left,aParent);
-      if ParentSpecifity<0 then
+      aSpecifity:=SelectorMatches(aBinary.Left,aParent);
+      if aSpecifity<0 then
         exit(-1);
         exit(-1);
-      inc(Result,ParentSpecifity);
-    end
+      inc(Result,aSpecifity);
+    end;
+  boPlus:
+    begin
+      // adjacent sibling combinator
+      Result:=SelectorMatches(aBinary.Right,TestNode);
+      if Result<0 then exit;
+      Sibling:=TestNode.GetCSSPreviousSibling;
+      if Sibling=nil then
+        exit(-1);
+      aSpecifity:=SelectorMatches(aBinary.Left,Sibling);
+      if aSpecifity<0 then
+        exit(-1);
+      inc(Result,aSpecifity);
+    end;
+  boTilde:
+    begin
+      // general sibling combinator
+      Result:=SelectorMatches(aBinary.Right,TestNode);
+      if Result<0 then exit;
+      Sibling:=TestNode.GetCSSPreviousSibling;
+      while Sibling<>nil do
+      begin
+        aSpecifity:=SelectorMatches(aBinary.Left,Sibling);
+        if aSpecifity>=0 then
+        begin
+          inc(Result,aSpecifity);
+          exit;
+        end;
+        Sibling:=Sibling.GetCSSPreviousSibling;
+      end;
+      Result:=-1;
+    end;
   else
   else
     DoError(20220910123724,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
     DoError(20220910123724,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
   end;
   end;

+ 102 - 3
packages/fcl-css/tests/tccssresolver.pp

@@ -92,6 +92,8 @@ type
     function HasCSSClass(const aClassName: TCSSString): boolean; virtual;
     function HasCSSClass(const aClassName: TCSSString): boolean; virtual;
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement); virtual;
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement); virtual;
     function GetCSSParent: TCSSNode; virtual;
     function GetCSSParent: TCSSNode; virtual;
+    function GetCSSIndex: integer; virtual;
+    function GetCSSPreviousSibling: TCSSNode; virtual;
     property Parent: TDemoNode read FParent write SetParent;
     property Parent: TDemoNode read FParent write SetParent;
     property NodeCount: integer read GetNodeCount;
     property NodeCount: integer read GetNodeCount;
     property Nodes[Index: integer]: TDemoNode read GetNodes; default;
     property Nodes[Index: integer]: TDemoNode read GetNodes; default;
@@ -118,6 +120,14 @@ type
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeID: TCSSNumericalID; override;
   end;
   end;
 
 
+  { TDemoSpan }
+
+  TDemoSpan = class(TDemoNode)
+  public
+    class function CSSTypeName: TCSSString; override;
+    class function CSSTypeID: TCSSNumericalID; override;
+  end;
+
   { TDemoButton }
   { TDemoButton }
 
 
   TDemoButton = class(TDemoNode)
   TDemoButton = class(TDemoNode)
@@ -174,10 +184,12 @@ type
     procedure Test_Selector_Type;
     procedure Test_Selector_Type;
     procedure Test_Selector_Id;
     procedure Test_Selector_Id;
     procedure Test_Selector_Class;
     procedure Test_Selector_Class;
-    procedure Test_Selector_ClassClass; // and operator
+    procedure Test_Selector_ClassClass; // and combinator
     procedure Test_Selector_ClassSpaceClass; // descendant combinator
     procedure Test_Selector_ClassSpaceClass; // descendant combinator
-    procedure Test_Selector_TypeCommaType; // or operator
+    procedure Test_Selector_TypeCommaType; // or combinator
     procedure Test_Selector_ClassGTClass; // child combinator
     procedure Test_Selector_ClassGTClass; // child combinator
+    procedure Test_Selector_TypePlusType; // adjacent sibling combinator
+    procedure Test_Selector_TypeTildeType; // general sibling combinator
   end;
   end;
 
 
 function LinesToStr(const Args: array of const): string;
 function LinesToStr(const Args: array of const): string;
@@ -359,6 +371,62 @@ begin
   AssertEquals('Div2.width','12px',Div2.Width);
   AssertEquals('Div2.width','12px',Div2.Width);
 end;
 end;
 
 
+procedure TTestCSSResolver.Test_Selector_TypePlusType;
+var
+  Button1, Button2, Button3: TDemoButton;
+  Div1: TDemoDiv;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Button1:=TDemoButton.Create(Doc);
+  Button1.Parent:=Doc.Root;
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Button2:=TDemoButton.Create(Doc);
+  Button2.Parent:=Doc.Root;
+
+  Button3:=TDemoButton.Create(Doc);
+  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','',Button3.Left);
+end;
+
+procedure TTestCSSResolver.Test_Selector_TypeTildeType;
+var
+  Button1, Button2, Button3: TDemoButton;
+  Div1: TDemoDiv;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Button1:=TDemoButton.Create(Doc);
+  Button1.Parent:=Doc.Root;
+
+  Div1:=TDemoDiv.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Button2:=TDemoButton.Create(Doc);
+  Button2.Parent:=Doc.Root;
+
+  Button3:=TDemoButton.Create(Doc);
+  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);
+end;
+
 { TDemoDiv }
 { TDemoDiv }
 
 
 class function TDemoDiv.CSSTypeName: TCSSString;
 class function TDemoDiv.CSSTypeName: TCSSString;
@@ -371,6 +439,18 @@ begin
   Result:=101;
   Result:=101;
 end;
 end;
 
 
+{ TDemoSpan }
+
+class function TDemoSpan.CSSTypeName: TCSSString;
+begin
+  Result:='span';
+end;
+
+class function TDemoSpan.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=102;
+end;
+
 { TDemoButton }
 { TDemoButton }
 
 
 class function TDemoButton.CSSTypeName: TCSSString;
 class function TDemoButton.CSSTypeName: TCSSString;
@@ -380,7 +460,7 @@ end;
 
 
 class function TDemoButton.CSSTypeID: TCSSNumericalID;
 class function TDemoButton.CSSTypeID: TCSSNumericalID;
 begin
 begin
-  Result:=102;
+  Result:=103;
 end;
 end;
 
 
 { TDemoDocument }
 { TDemoDocument }
@@ -648,6 +728,25 @@ begin
   Result:=Parent;
   Result:=Parent;
 end;
 end;
 
 
+function TDemoNode.GetCSSIndex: integer;
+begin
+  if Parent=nil then
+    Result:=-1
+  else
+    Result:=Parent.FNodes.IndexOf(Self);
+end;
+
+function TDemoNode.GetCSSPreviousSibling: TCSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if i<1 then
+    Result:=nil
+  else
+    Result:=Parent.Nodes[i-1];
+end;
+
 function TDemoNode.GetCSSTypeName: TCSSString;
 function TDemoNode.GetCSSTypeName: TCSSString;
 begin
 begin
   Result:=CSSTypeName;
   Result:=CSSTypeName;