Browse Source

fcl-css: resolver: :root

mattias 2 years ago
parent
commit
db499b61c0

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

@@ -45,6 +45,11 @@ const
   CSSTypeID_Universal = 1; // id of type '*'
   CSSAttributeID_ID = 1; // id of attribute key 'id'
   CSSAttributeID_All = 2; // id of attribute key 'all'
+  // pseudo attribute IDs
+  CSSPseudoID_Root = 1; // :root
+  CSSPseudoID_Empty = 2; // :empty
+  CSSPseudoID_FirstChild = 3; // :first-child
+  CSSPseudoID_LastChild = 4; // :last-child
 
 type
   TCSSMsgID = int64;
@@ -72,9 +77,14 @@ type
     function HasCSSClass(const aClassName: TCSSString): boolean;
     function GetCSSParent: TCSSNode;
     function GetCSSIndex: integer; // node index in parent's children
+    function GetCSSNextSibling: TCSSNode;
     function GetCSSPreviousSibling: TCSSNode;
+    function GetCSSChildCount: integer;
+    function GetCSSChild(const anIndex: integer): TCSSNode;
     function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
     function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString;
+    function HasCSSPseudoAttribute(const AttrID: TCSSNumericalID): boolean;
+    function GetCSSPseudoAttribute(const AttrID: TCSSNumericalID): TCSSString;
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
   end;
 
@@ -176,6 +186,7 @@ type
     function SelectorMatches(aSelector: TCSSElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorIdentifierMatches(Identifier: TCSSIdentifierElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
+    function SelectorPseudoClassMatches(aPseudoClass: TCSSPseudoClassElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorStringMatches(aString: TCSSStringElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorListMatches(aList: TCSSListElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
     function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: TCSSNode): TCSSSpecifity; virtual;
@@ -335,6 +346,8 @@ begin
     Result:=SelectorIdentifierMatches(TCSSIdentifierElement(aSelector),TestNode)
   else if C=TCSSClassNameElement then
     Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode)
+  else if C=TCSSPseudoClassElement then
+    Result:=SelectorPseudoClassMatches(TCSSPseudoClassElement(aSelector),TestNode)
   else if C=TCSSStringElement then
     Result:=SelectorStringMatches(TCSSStringElement(aSelector),TestNode)
   else if C=TCSSBinaryElement then
@@ -378,6 +391,35 @@ begin
     Result:=-1;
 end;
 
+function TCSSResolver.SelectorPseudoClassMatches(
+  aPseudoClass: TCSSPseudoClassElement; const TestNode: TCSSNode
+  ): TCSSSpecifity;
+var
+  PseudoID: TCSSNumericalID;
+begin
+  Result:=-1;
+  PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoAttribute);
+  case PseudoID of
+  CSSIDNone:
+    if roErrorOnUnknownName in Options then
+      DoError(20220911205605,'Unknown CSS selector pseudo attribute name "'+aPseudoClass.Name+'"',aPseudoClass);
+  CSSPseudoID_Root:
+    if TestNode.GetCSSParent=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_Empty:
+    if TestNode.GetCSSChildCount=0 then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_FirstChild:
+    if TestNode.GetCSSPreviousSibling=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_LastChild:
+    if TestNode.GetCSSNextSibling=nil then
+      Result:=CSSSpecifityClass;
+  else
+    TestNode.GetCSSPseudoAttribute(PseudoID);
+  end;
+end;
+
 function TCSSResolver.SelectorStringMatches(aString: TCSSStringElement;
   const TestNode: TCSSNode): TCSSSpecifity;
 var
@@ -724,6 +766,13 @@ begin
       'id': Result:=CSSAttributeID_ID;
       'all': Result:=CSSAttributeID_All;
       end;
+    nikPseudoAttribute:
+      case aName of
+      ':root': Result:=CSSPseudoID_Root;
+      ':empty': Result:=CSSPseudoID_Empty;
+      ':first-child': Result:=CSSPseudoID_FirstChild;
+      ':last-child': Result:=CSSPseudoID_LastChild;
+      end;
     end;
 
     // resolve user defined names

+ 178 - 0
packages/fcl-css/tests/tccssresolver.pp

@@ -93,9 +93,14 @@ type
     procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement); virtual;
     function GetCSSParent: TCSSNode; virtual;
     function GetCSSIndex: integer; virtual;
+    function GetCSSNextSibling: TCSSNode; virtual;
     function GetCSSPreviousSibling: TCSSNode; virtual;
+    function GetCSSChildCount: integer; virtual;
+    function GetCSSChild(const anIndex: integer): TCSSNode; virtual;
     function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean; virtual;
     function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
+    function HasCSSPseudoAttribute(const AttrID: TCSSNumericalID): boolean; virtual;
+    function GetCSSPseudoAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
     property Parent: TDemoNode read FParent write SetParent;
     property NodeCount: integer read GetNodeCount;
     property Nodes[Index: integer]: TDemoNode read GetNodes; default;
@@ -208,6 +213,28 @@ type
     procedure Test_Selector_AttributeBeginsWithHyphen;
     procedure Test_Selector_AttributeContainsWord;
     procedure Test_Selector_AttributeContainsSubstring;
+    // ToDo: all
+    procedure Test_Selector_Root;
+    procedure Test_Selector_Empty;
+    procedure Test_Selector_FirstChild;
+    procedure Test_Selector_LastChild;
+    // ToDo: :first-of-type
+    // ToDo: :last-of-type
+    // ToDo: :not(selector)
+    // ToDo: nth-child(n)
+    // ToDo: nth-last-child(n)
+    // ToDo: nth-of-type(n)
+    // ToDo: nth-last-of-type(n)
+    // ToDo: :only-of-type
+    // ToDo: :only-child
+    // ToDo: :defined
+    // ToDo: div:has(>img)
+    // ToDo: div:has(+img)
+    // ToDo: :is()
+    // ToDo: :where()
+    // ToDo: :lang()
+    // ToDo: inline style
+    // ToDo: specifity
   end;
 
 function LinesToStr(const Args: array of const): string;
@@ -601,6 +628,124 @@ begin
   AssertEquals('Button1.Width','5px',Button1.Width);
 end;
 
+procedure TTestCSSResolver.Test_Selector_Root;
+var
+  Button1: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+  Doc.Root.Left:='Foo';
+
+  Button1:=TDemoButton.Create(Doc);
+  Button1.Parent:=Doc.Root;
+
+  Doc.Style:=LinesToStr([
+  ':root { top: 4px; }',
+  '']);
+  Doc.ApplyStyle;
+  AssertEquals('Root.Top','4px',Doc.Root.Top);
+  AssertEquals('Button1.Top','',Button1.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_Empty;
+var
+  Div1, Div11, Div2: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoButton.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoButton.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Div2:=TDemoButton.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  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','x',Div1.Left);
+  AssertEquals('Div1.Top','',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div1.Left);
+  AssertEquals('Div11.Top','2px',Div1.Top);
+  AssertEquals('Div2.Left','1px',Div1.Left);
+  AssertEquals('Div2.Top','',Div1.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_FirstChild;
+var
+  Div1, Div11, Div12, Div2: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoButton.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoButton.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Div12:=TDemoButton.Create(Doc);
+  Div12.Parent:=Div1;
+
+  Div2:=TDemoButton.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  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','',Div1.Top);
+  AssertEquals('Div11.Left','1px',Div1.Left);
+  AssertEquals('Div11.Top','2px',Div1.Top);
+  AssertEquals('Div12.Left','',Div1.Left);
+  AssertEquals('Div12.Top','',Div1.Top);
+  AssertEquals('Div2.Left','',Div1.Left);
+  AssertEquals('Div2.Top','',Div1.Top);
+end;
+
+procedure TTestCSSResolver.Test_Selector_LastChild;
+var
+  Div1, Div11, Div12, Div2: TDemoButton;
+begin
+  Doc.Root:=TDemoNode.Create(nil);
+
+  Div1:=TDemoButton.Create(Doc);
+  Div1.Parent:=Doc.Root;
+
+  Div11:=TDemoButton.Create(Doc);
+  Div11.Parent:=Div1;
+
+  Div12:=TDemoButton.Create(Doc);
+  Div12.Parent:=Div1;
+
+  Div2:=TDemoButton.Create(Doc);
+  Div2.Parent:=Doc.Root;
+
+  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','',Div1.Left);
+  AssertEquals('Div11.Top','',Div1.Top);
+  AssertEquals('Div12.Left','6px',Div1.Left);
+  AssertEquals('Div12.Top','7px',Div1.Top);
+  AssertEquals('Div2.Left','6px',Div1.Left);
+  AssertEquals('Div2.Top','',Div1.Top);
+end;
+
 { TDemoDiv }
 
 class function TDemoDiv.CSSTypeName: TCSSString;
@@ -945,6 +1090,17 @@ begin
     Result:=Parent.FNodes.IndexOf(Self);
 end;
 
+function TDemoNode.GetCSSNextSibling: TCSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if i>=NodeCount then
+    Result:=nil
+  else
+    Result:=Parent.Nodes[i+1];
+end;
+
 function TDemoNode.GetCSSPreviousSibling: TCSSNode;
 var
   i: Integer;
@@ -956,6 +1112,16 @@ begin
     Result:=Parent.Nodes[i-1];
 end;
 
+function TDemoNode.GetCSSChildCount: integer;
+begin
+  Result:=NodeCount;
+end;
+
+function TDemoNode.GetCSSChild(const anIndex: integer): TCSSNode;
+begin
+  Result:=Nodes[anIndex];
+end;
+
 function TDemoNode.HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
 begin
   Result:=(AttrID>=DemoAttrIDBase) and (AttrID<=DemoAttrIDBase+ord(High(TDemoNodeAttribute)));
@@ -971,6 +1137,18 @@ begin
   Result:=Attribute[Attr];
 end;
 
+function TDemoNode.HasCSSPseudoAttribute(const AttrID: TCSSNumericalID
+  ): boolean;
+begin
+  Result:=false;
+end;
+
+function TDemoNode.GetCSSPseudoAttribute(const AttrID: TCSSNumericalID
+  ): TCSSString;
+begin
+  Result:='';
+end;
+
 function TDemoNode.GetCSSTypeName: TCSSString;
 begin
   Result:=CSSTypeName;

+ 3 - 10
packages/fcl-css/tests/tccsstree.pp

@@ -455,27 +455,20 @@ begin
 end;
 
 procedure TCSSTreeAsStringTest.TestRULESelector;
-
-Var
-  aIdent : TCSSIdentifierElement;
-
 begin
   CreateElement(TCSSRuleElement);
   CreateDeclaration('a','b',amChild);
-  aIdent:=CreateIdentifier('c',amSelector);
+  CreateIdentifier('c',amSelector);
   AssertEquals('Value','c { a : b; }',Element.AsString);
   AssertEquals('Value','c {'+sLineBreak+'  a : b;'+sLineBreak+'}',Element.AsFormattedString);
 end;
 
 procedure TCSSTreeAsStringTest.TestRULE2Selectors;
-Var
-  aIdent : TCSSIdentifierElement;
-
 begin
   CreateElement(TCSSRuleElement);
   CreateDeclaration('a','b',amChild);
-  aIdent:=CreateIdentifier('c',amSelector);
-  aIdent:=CreateIdentifier('d',amSelector);
+  CreateIdentifier('c',amSelector);
+  CreateIdentifier('d',amSelector);
   AssertEquals('Value','c, d { a : b; }',Element.AsString);
   AssertEquals('Value','c,'+sLineBreak+'d {'+sLineBreak+'  a : b;'+sLineBreak+'}',Element.AsFormattedString);
 end;