Bladeren bron

added fcl-css to compile the non-skia version with fpc 3.2.2

mattias 1 jaar geleden
bovenliggende
commit
ca9562e147

+ 1644 - 0
src/base/fcl-css/fpcssparser.pp

@@ -0,0 +1,1644 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2022- by Michael Van Canneyt ([email protected])
+
+    This file contains a CSS parser
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpCSSParser;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode ObjFPC}{$H+}
+{$WARN 6060 off} // Case statement does not handle all possible cases
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses
+  System.TypInfo, System.Classes, System.SysUtils, FPCSS.Tree, FPCSS.Scanner;
+{$ELSE FPC_DOTTEDUNITS}
+uses
+  TypInfo, Classes, SysUtils, fpcsstree, fpcssscanner;
+{$ENDIF FPC_DOTTEDUNITS}
+
+Type
+  ECSSParser = Class(ECSSException);
+
+  { TCSSParser }
+
+  TCSSParser = class(TObject)
+  private
+    FInput : TStream;
+    FScanner: TCSSScanner;
+    FPrevious : TCSSToken;
+    FCurrent : TCSSToken;
+    FCurrentTokenString : TCSSString;
+    FPeekToken : TCSSToken;
+    FPeekTokenString : TCSSString;
+    FFreeScanner : Boolean;
+    FRuleLevel : Integer;
+    function GetAtEOF: Boolean;
+    function GetCurSource: TCSSString;
+    Function GetCurLine : Integer;
+    Function GetCurPos : Integer;
+  protected
+    function CreateElement(aClass: TCSSElementClass): TCSSElement; virtual;
+    class function GetAppendElement(aList: TCSSListElement): TCSSElement;
+    Procedure DoWarn(const Msg : TCSSString); virtual;
+    Procedure DoWarn(const Fmt : TCSSString; const Args : Array of const);
+    Procedure DoWarnExpectedButGot(const Expected: string);
+    Procedure DoError(const Msg : TCSSString); virtual;
+    Procedure DoError(const Fmt : TCSSString; const Args : Array of const);
+    Procedure DoErrorExpectedButGot(const Expected: string);
+    Procedure Consume(aToken : TCSSToken); virtual;
+    Procedure SkipWhiteSpace;
+    function ParseComponentValueList(AllowRules: Boolean=True): TCSSElement; virtual;
+    function ParseComponentValue: TCSSElement; virtual;
+    function ParseExpression: TCSSElement; virtual;
+    function ParseRule: TCSSElement; virtual;
+    function ParseAtUnknownRule: TCSSElement; virtual;
+    function ParseAtMediaRule: TCSSAtRuleElement; virtual;
+    function ParseAtSimpleRule: TCSSAtRuleElement; virtual;
+    function ParseMediaCondition: TCSSElement; virtual;
+    function ParseRuleList(aStopOn : TCSStoken = ctkEOF): TCSSElement; virtual;
+    function ParseSelector: TCSSElement; virtual;
+    function ParseAttributeSelector: TCSSElement; virtual;
+    function ParseWQName: TCSSElement;
+    function ParseDeclaration(aIsAt : Boolean = false): TCSSDeclarationElement; virtual;
+    function ParseCall(aName: TCSSString): TCSSElement; virtual;
+    procedure ParseSelectorCommaList(aCall: TCSSCallElement); virtual;
+    procedure ParseRelationalSelectorCommaList(aCall: TCSSCallElement); virtual;
+    procedure ParseNthChildParams(aCall: TCSSCallElement); virtual;
+    function ParseUnary: TCSSElement; virtual;
+    function ParseUnit: TCSSUnits; virtual;
+    function ParseIdentifier : TCSSIdentifierElement; virtual;
+    function ParseHashIdentifier : TCSSHashIdentifierElement; virtual;
+    function ParseClassName : TCSSClassNameElement; virtual;
+    function ParseParenthesis: TCSSElement; virtual;
+    function ParsePseudo: TCSSElement; virtual;
+    Function ParseRuleBody(aRule: TCSSRuleElement; aIsAt : Boolean = False) : integer; virtual;
+    function ParseInteger: TCSSElement; virtual;
+    function ParseFloat: TCSSElement; virtual;
+    function ParseString: TCSSElement; virtual;
+    Function ParseUnicodeRange : TCSSElement; virtual;
+    function ParseArray(aPrefix: TCSSElement): TCSSElement; virtual;
+    function ParseURL: TCSSElement; virtual;
+    function ParseInvalidToken: TCSSElement; virtual;
+    Property CurrentSource : TCSSString Read GetCurSource;
+    Property CurrentLine : Integer Read GetCurLine;
+    Property CurrentPos : Integer Read GetCurPos;
+  Public
+    CSSArrayElementClass: TCSSArrayElementClass;
+    CSSAtRuleElementClass: TCSSAtRuleElementClass;
+    CSSBinaryElementClass: TCSSBinaryElementClass;
+    CSSCallElementClass: TCSSCallElementClass;
+    CSSClassNameElementClass: TCSSClassNameElementClass;
+    CSSCompoundElementClass: TCSSCompoundElementClass;
+    CSSDeclarationElementClass: TCSSDeclarationElementClass;
+    CSSFloatElementClass: TCSSFloatElementClass;
+    CSSHashIdentifierElementClass: TCSSHashIdentifierElementClass;
+    CSSIdentifierElementClass: TCSSIdentifierElementClass;
+    CSSIntegerElementClass: TCSSIntegerElementClass;
+    CSSListElementClass: TCSSListElementClass;
+    CSSPseudoClassElementClass: TCSSPseudoClassElementClass;
+    CSSRuleElementClass: TCSSRuleElementClass;
+    CSSStringElementClass: TCSSStringElementClass;
+    CSSUnaryElementClass: TCSSUnaryElementClass;
+    CSSUnicodeRangeElementClass: TCSSUnicodeRangeElementClass;
+    CSSURLElementClass: TCSSURLElementClass;
+    Constructor Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []); overload;
+    Constructor Create(AScanner : TCSSScanner); virtual; overload;
+    Destructor Destroy; override;
+    Function Parse : TCSSElement;
+    Function ParseInline : TCSSElement;
+    Property CurrentToken : TCSSToken Read FCurrent;
+    Property CurrentTokenString : TCSSString Read FCurrentTokenString;
+    Function GetNextToken : TCSSToken;
+    Function PeekNextToken : TCSSToken;
+    Property Scanner : TCSSScanner Read FScanner;
+    Property atEOF : Boolean Read GetAtEOF;
+  end;
+
+Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
+Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
+
+implementation
+
+Resourcestring
+  SBinaryInvalidToken = 'Invalid token for binary operation: %s';
+  SUnaryInvalidToken = 'Invalid token for unary operation: %s';
+  SErrFileSource = 'Error: file "%s" line %d, pos %d: ';
+  SErrSource = 'Error: line %d, pos %d: ';
+  SErrUnexpectedToken = 'Unexpected token: Got %s (as string: "%s"), expected: %s ';
+  SErrInvalidFloat = 'Invalid float: %s';
+  SErrUnexpectedEndOfFile = 'Unexpected EOF while scanning function args: %s';
+
+Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
+
+begin
+  Case aToken of
+    ctkEquals : Result:=boEquals;
+    ctkPlus : Result:=boPlus;
+    ctkMinus:  Result:=boMinus;
+    ctkAnd : result:=boAnd;
+    ctkGE : Result:=boGE;
+    ctkGT : Result:=boGT;
+    ctkLE : Result:=boLE;
+    ctkLT : Result:=boLT;
+    ctkDIV : Result:=boDIV;
+    ctkStar : Result:=boStar;
+    ctkSTAREQUAL : Result:=boStarEqual;
+    ctkTilde : Result:=boTilde;
+    ctkTILDEEQUAL : Result:=boTildeEqual;
+    ctkSquared : Result:=boSquared;
+    ctkSQUAREDEQUAL : Result:=boSquaredEqual;
+    ctkPIPE : Result:=boPipe;
+    ctkPIPEEQUAL : Result:=boPipeEqual;
+    ctkDOLLAR : Result:=boDollar;
+    ctkDOLLAREQUAL : Result:=boDollarEqual;
+    ctkColon : Result:=boCOLON;
+    ctkDoubleColon : Result:=boDoubleColon;
+  else
+    Raise ECSSParser.CreateFmt(SBinaryInvalidToken,[GetEnumName(TypeInfo(aToken),Ord(aToken))]);
+    // Result:=boEquals;
+  end;
+end;
+
+Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
+
+begin
+  Case aToken of
+    ctkDOUBLECOLON: Result:=uoDoubleColon;
+    ctkMinus: Result:=uoMinus;
+    ctkPlus: Result:=uoPlus;
+    ctkDiv: Result:=uoDiv;
+    ctkGT: Result:=uoGT;
+    ctkTILDE: Result:=uoTilde;
+  else
+    Raise ECSSParser.CreateFmt(SUnaryInvalidToken,[GetEnumName(TypeInfo(aToken),Ord(aToken))]);
+  end;
+end;
+
+{ TCSSParser }
+
+function TCSSParser.GetAtEOF: Boolean;
+begin
+  Result:=(CurrentToken=ctkEOF);
+end;
+
+procedure TCSSParser.DoError(const Msg: TCSSString);
+Var
+  ErrAt : TCSSString;
+
+begin
+  If Assigned(FScanner) then
+    If FScanner.CurFilename<>'' then
+      ErrAt:=SafeFormat(SErrFileSource,[FScanner.CurFileName,FScanner.CurRow,FScanner.CurColumn])
+    else
+      ErrAt:=SafeFormat(SErrSource,[FScanner.Currow,FScanner.CurColumn]);
+  Raise ECSSParser.Create(ErrAt+Msg)
+end;
+
+procedure TCSSParser.DoError(const Fmt: TCSSString; const Args: array of const);
+begin
+  DoError(SafeFormat(Fmt,Args));
+end;
+
+procedure TCSSParser.DoErrorExpectedButGot(const Expected: string);
+begin
+  DoError(SErrUnexpectedToken ,[
+           GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+           CurrentTokenString,
+           Expected
+           ]);
+end;
+
+procedure TCSSParser.Consume(aToken: TCSSToken);
+begin
+  if CurrentToken<>aToken then
+    DoError(SErrUnexpectedToken ,[
+             GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+             CurrentTokenString,
+             GetEnumName(TypeInfo(TCSSToken),Ord(aToken))
+             ]);
+  GetNextToken;
+end;
+
+procedure TCSSParser.SkipWhiteSpace;
+begin
+  while CurrentToken=ctkWHITESPACE do
+    GetNextToken;
+end;
+
+function TCSSParser.GetCurSource: TCSSString;
+begin
+  If Assigned(FScanner) then
+    Result:=FScanner.CurFileName
+  else
+    Result:='';
+end;
+
+function TCSSParser.GetCurLine: Integer;
+begin
+  if Assigned(FScanner) then
+    Result:=FScanner.CurRow
+  else
+    Result:=0;
+end;
+
+function TCSSParser.GetCurPos: Integer;
+begin
+  if Assigned(FScanner) then
+    Result:=FScanner.CurColumn
+  else
+    Result:=0;
+end;
+
+procedure TCSSParser.DoWarn(const Msg: TCSSString);
+begin
+  if Assigned(Scanner.OnWarn) then
+    Scanner.OnWarn(Self,Msg)
+  else
+    DoError(Msg);
+end;
+
+procedure TCSSParser.DoWarn(const Fmt: TCSSString; const Args: array of const);
+begin
+  DoWarn(SafeFormat(Fmt,Args));
+end;
+
+procedure TCSSParser.DoWarnExpectedButGot(const Expected: string);
+begin
+  DoWarn(SErrUnexpectedToken ,[
+           GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+           CurrentTokenString,
+           Expected
+           ]);
+end;
+
+constructor TCSSParser.Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []);
+begin
+  FInput:=AInput;
+  Create(TCSSScanner.Create(FInput));
+  FScanner.Options:=FScanner.Options+ExtraScannerOptions;
+  FFreeScanner:=True;
+end;
+
+constructor TCSSParser.Create(AScanner: TCSSScanner);
+begin
+  FCurrent:=ctkUNKNOWN;
+  FPeekToken:=ctkUNKNOWN;
+  FPeekTokenString:='';
+  FScanner:=aScanner;
+  CSSArrayElementClass:=TCSSArrayElement;
+  CSSAtRuleElementClass:=TCSSAtRuleElement;
+  CSSBinaryElementClass:=TCSSBinaryElement;
+  CSSCallElementClass:=TCSSCallElement;
+  CSSClassNameElementClass:=TCSSClassNameElement;
+  CSSCompoundElementClass:=TCSSCompoundElement;
+  CSSDeclarationElementClass:=TCSSDeclarationElement;
+  CSSFloatElementClass:=TCSSFloatElement;
+  CSSHashIdentifierElementClass:=TCSSHashIdentifierElement;
+  CSSIdentifierElementClass:=TCSSIdentifierElement;
+  CSSIntegerElementClass:=TCSSIntegerElement;
+  CSSListElementClass:=TCSSListElement;
+  CSSPseudoClassElementClass:=TCSSPseudoClassElement;
+  CSSRuleElementClass:=TCSSRuleElement;
+  CSSStringElementClass:=TCSSStringElement;
+  CSSUnaryElementClass:=TCSSUnaryElement;
+  CSSUnicodeRangeElementClass:=TCSSUnicodeRangeElement;
+  CSSURLElementClass:=TCSSURLElement;
+end;
+
+destructor TCSSParser.Destroy;
+begin
+  if FFreeScanner then
+    FreeAndNil(FScanner);
+  inherited Destroy;
+end;
+
+class function TCSSParser.GetAppendElement(aList: TCSSListElement): TCSSElement;
+
+begin
+  Case aList.ChildCount of
+    0 : Result:=Nil;
+    1 : Result:=aList.ExtractElement(0);
+  else
+    Result:=aList;
+  end;
+  if Result<>aList then
+    aList.Free;
+end;
+
+function TCSSParser.ParseAtUnknownRule: TCSSElement;
+// read unknown at-rule
+
+Var
+  aRule : TCSSRuleElement;
+  aSel : TCSSElement;
+  Term : TCSSTokens;
+  aLast : TCSSToken;
+  aList : TCSSListElement;
+  {$ifdef VerboseCSSParser}
+  aAt : TCSSString;
+  {$endif}
+
+begin
+  Inc(FRuleLevel);
+{$ifdef VerboseCSSParser}
+  aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
+  Writeln('Parse @ rule');
+{$endif}
+  Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
+  aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
+  TCSSAtRuleElement(aRule).AtKeyWord:=CurrentTokenString;
+  GetNextToken;
+  aList:=nil;
+  try
+    aList:=TCSSListElement(CreateElement(CSSListElementClass));
+    While Not (CurrentToken in Term) do
+      begin
+      aSel:=ParseComponentValue;
+      aList.AddChild(aSel);
+      if CurrentToken=ctkCOMMA then
+        begin
+        Consume(ctkCOMMA);
+        aRule.AddSelector(GetAppendElement(aList));
+        aList:=TCSSListElement(CreateElement(CSSListElementClass));
+        end;
+      end;
+    aRule.AddSelector(GetAppendElement(aList));
+    aList:=nil;
+    aLast:=CurrentToken;
+    if (aLast<>ctkSEMICOLON) then
+      begin
+      Consume(ctkLBRACE);
+      aRule.AddChild(ParseRuleList(ctkRBRACE));
+      Consume(ctkRBRACE);
+      end;
+    Result:=aRule;
+    aRule:=nil;
+{$ifdef VerboseCSSParser}  Writeln('Done Parse @ rule ',aAt); {$endif}
+    Inc(FRuleLevel);
+  finally
+    aRule.Free;
+  end;
+end;
+
+function TCSSParser.ParseAtMediaRule: TCSSAtRuleElement;
+
+Var
+  {$ifdef VerboseCSSParser}
+  aAt : TCSSString;
+  {$endif}
+  aRule : TCSSAtRuleElement;
+  Term : TCSSTokens;
+  aLast , aToken: TCSSToken;
+  aList : TCSSListElement;
+
+begin
+  Inc(FRuleLevel);
+{$ifdef VerboseCSSParser}
+  aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
+  Writeln('Parse @media rule');
+{$endif}
+  Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
+  aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
+  aRule.AtKeyWord:=CurrentTokenString;
+  GetNextToken;
+  aList:=nil;
+  try
+    aList:=TCSSListElement(CreateElement(CSSListElementClass));
+    While Not (CurrentToken in Term) do
+      begin
+      aToken:=CurrentToken;
+      //  writeln('TCSSParser.ParseAtMediaRule Token=',CurrentToken);
+      case aToken of
+      ctkIDENTIFIER:
+        aList.AddChild(ParseIdentifier);
+      ctkLPARENTHESIS:
+        aList.AddChild(ParseMediaCondition);
+      else
+        Consume(ctkIDENTIFIER);
+      end;
+      if CurrentToken=ctkCOMMA then
+        begin
+        Consume(ctkCOMMA);
+        aRule.AddSelector(GetAppendElement(aList));
+        aList:=TCSSListElement(CreateElement(CSSListElementClass));
+        end;
+      end;
+    aRule.AddSelector(GetAppendElement(aList));
+    aList:=nil;
+    aLast:=CurrentToken;
+    if (aLast<>ctkSEMICOLON) then
+      begin
+      Consume(ctkLBRACE);
+      aRule.AddChild(ParseRuleList(ctkRBRACE));
+      Consume(ctkRBRACE);
+      end;
+    Result:=aRule;
+    aRule:=nil;
+{$ifdef VerboseCSSParser}  Writeln('Done Parse @ rule ',aAt); {$endif}
+    Inc(FRuleLevel);
+  finally
+    aRule.Free;
+  end;
+end;
+
+function TCSSParser.ParseAtSimpleRule: TCSSAtRuleElement;
+var
+  {$ifdef VerboseCSSParser}
+  aAt : TCSSString;
+  {$endif}
+  aRule: TCSSAtRuleElement;
+begin
+  Result:=nil;
+  Inc(FRuleLevel);
+{$ifdef VerboseCSSParser}
+  aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
+  Writeln('Parse @font-face rule');
+{$endif}
+  aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
+  try
+    aRule.AtKeyWord:=CurrentTokenString;
+    GetNextToken;
+
+    // read {
+    repeat
+      case CurrentToken of
+      ctkEOF:
+        DoErrorExpectedButGot('{');
+      ctkRBRACE, ctkRPARENTHESIS, ctkSEMICOLON:
+        begin
+        DoWarnExpectedButGot('{');
+        Result:=aRule;
+        aRule:=nil;
+        exit;
+        end;
+      ctkLBRACE:
+        break;
+      end;
+    until false;
+    GetNextToken;
+
+    // read declarations
+    ParseRuleBody(aRule);
+    if CurrentToken=ctkRBRACE then
+      GetNextToken;
+
+    Result:=aRule;
+    aRule:=nil;
+    {$ifdef VerboseCSSParser}  Writeln('Done Parse @ rule ',aAt); {$endif}
+    Inc(FRuleLevel);
+  finally
+    aRule.Free;
+  end;
+end;
+
+function TCSSParser.ParseMediaCondition: TCSSElement;
+// for example:
+//   (color)
+//   (color: #fff)
+//   (30em <= width)
+//   (30em >= width > 20em)
+//   (not(MediaCondition))
+var
+  El: TCSSElement;
+  Bin: TCSSBinaryElement;
+  List: TCSSListElement;
+  aToken: TCSSToken;
+begin
+  Consume(ctkLPARENTHESIS);
+  {$IFDEF VerboseCSSParser}
+  writeln('TCSSParser.ParseMediaCondition START ',CurrentToken);
+  {$ENDIF}
+
+  El:=nil;
+  Bin:=nil;
+  List:=nil;
+  try
+    case CurrentToken of
+    ctkIDENTIFIER:
+      begin
+      El:=ParseIdentifier;
+      if TCSSIdentifierElement(El).Value='not' then
+        begin
+        // (not(mediacondition))
+        List:=TCSSListElement(CreateElement(CSSListElementClass));
+        List.AddChild(El);
+        El:=nil;
+        List.AddChild(ParseMediaCondition());
+        Result:=List;
+        List:=nil;
+        exit;
+        end
+      else if CurrentToken=ctkCOLON then
+        begin
+        // (mediaproperty: value)
+        Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+        Bin.Left:=El;
+        El:=nil;
+        Consume(ctkCOLON);
+        Bin.Right:=ParseComponentValue;
+        Consume(ctkRPARENTHESIS);
+        Result:=Bin;
+        Bin:=nil;
+        exit;
+        end;
+      end;
+    ctkSTRING:
+      El:=ParseString;
+    ctkINTEGER:
+      El:=ParseInteger;
+    ctkFLOAT:
+      El:=ParseFloat;
+    else
+      Consume(ctkIDENTIFIER);
+    end;
+
+    // read binaryoperator operand til bracket close
+    repeat
+      aToken:=CurrentToken;
+      {$IFDEF VerboseCSSResolver}
+      writeln('TCSSParser.ParseMediaCondition NEXT ',CurrentToken);
+      {$ENDIF}
+      case aToken of
+      ctkRPARENTHESIS:
+        begin
+        Result:=El;
+        GetNextToken;
+        break;
+        end;
+      ctkEQUALS,
+      ctkGE,ctkGT,ctkLE,ctkLT:
+        begin
+        Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+        Bin.Left:=El;
+        Bin.Operation:=TokenToBinaryOperation(aToken);
+        GetNextToken;
+        end;
+      else
+        Consume(ctkRPARENTHESIS);
+      end;
+
+      case CurrentToken of
+      ctkIDENTIFIER:
+        Bin.Right:=ParseIdentifier;
+      ctkSTRING:
+        Bin.Right:=ParseString;
+      ctkINTEGER:
+        Bin.Right:=ParseInteger;
+      ctkFLOAT:
+        Bin.Right:=ParseFloat;
+      else
+        Consume(ctkIDENTIFIER);
+      end;
+      El:=Bin;
+      Bin:=nil;
+    until false;
+
+  finally
+    List.Free;
+    Bin.Free;
+    El.Free;
+  end;
+
+  {$IFDEF VerboseCSSParser}
+  writeln('TCSSParser.ParseMediaCondition END');
+  {$ENDIF}
+end;
+
+function TCSSParser.ParseExpression: TCSSElement;
+
+Const
+  RuleTokens =
+       [ctkIDENTIFIER,ctkCLASSNAME,ctkHASH,ctkINTEGER,
+        ctkPSEUDO,ctkPSEUDOFUNCTION,
+        ctkCOLON,ctkDOUBLECOLON,ctkSTAR,ctkTILDE,ctkLBRACKET];
+
+begin
+  if CurrentToken in RuleTokens then
+    Result:=ParseRule
+  else if CurrentToken=ctkATKEYWORD then
+    case lowercase(CurrentTokenString) of
+    '@media': Result:=ParseAtMediaRule;
+    '@font-face',
+    '@page': Result:=ParseAtSimpleRule;
+    else
+      Result:=ParseAtUnknownRule;
+    end
+  else
+    Result:=ParseComponentValueList;
+end;
+
+function TCSSParser.ParseRuleList(aStopOn : TCSStoken = ctkEOF): TCSSElement;
+
+Var
+  aList : TCSSCompoundElement;
+  aEl : TCSSElement;
+  Terms : TCSSTokens;
+begin
+  Terms:=[ctkEOF,aStopOn];
+  aList:=TCSSCompoundElement(CreateElement(CSSCompoundElementClass));
+  Try
+    While not (CurrentToken in Terms) do
+      begin
+      aEl:=ParseExpression;
+      aList.AddChild(aEl);
+      if CurrentToken=ctkSEMICOLON then
+        Consume(ctkSEMICOLON);
+      end;
+    Result:=aList;
+    aList:=nil;
+  finally
+    aList.Free;
+  end;
+end;
+
+function TCSSParser.Parse: TCSSElement;
+begin
+  GetNextToken;
+  if CurrentToken=ctkLBRACE then
+    Result:=ParseRule
+  else
+    Result:=ParseRuleList;
+end;
+
+function TCSSParser.ParseInline: TCSSElement;
+var
+  aRule: TCSSRuleElement;
+begin
+  GetNextToken;
+  aRule:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
+  try
+    ParseRuleBody(aRule);
+    Result:=aRule;
+    aRule:=nil;
+  finally
+    aRule.Free;
+  end;
+end;
+
+function TCSSParser.GetNextToken: TCSSToken;
+begin
+  FPrevious:=FCurrent;
+  If (FPeekToken<>ctkUNKNOWN) then
+    begin
+    FCurrent:=FPeekToken;
+    FCurrentTokenString:=FPeekTokenString;
+    FPeekToken:=ctkUNKNOWN;
+    FPeekTokenString:='';
+    end
+  else
+    begin
+    FCurrent:=FScanner.FetchToken;
+    FCurrentTokenString:=FScanner.CurTokenString;
+    end;
+  Result:=FCurrent;
+  {$ifdef VerboseCSSParser}
+     Writeln('GetNextToken returns ',
+       GetEnumName(TypeInfo(TCSSToken),Ord(FCurrent)),
+       '(String: "',FCurrentTokenString,'")',
+       ' at (',FScanner.CurRow,',',FScanner.CurColumn,'): ',
+       FSCanner.CurLine);
+  {$endif VerboseCSSParser}
+end;
+
+function TCSSParser.PeekNextToken: TCSSToken;
+begin
+  If (FPeekToken=ctkUNKNOWN) then
+    begin
+    FPeekToken:=FScanner.FetchToken;
+    FPeekTokenString:=FScanner.CurTokenString;
+    end;
+  {$ifdef VerboseCSSParser}Writeln('PeekNextToken : ',GetEnumName(TypeInfo(TCSSToken),Ord(FPeekToken)), ' As TCSSString: ',FPeekTokenString);{$endif VerboseCSSParser}
+  Result:=FPeekToken;
+end;
+
+function TCSSParser.ParseUnit : TCSSUnits;
+
+begin
+  Result:=cuNone;
+  if (CurrentToken in [ctkIDENTIFIER,ctkPERCENTAGE]) then
+    begin
+    Case currentTokenString of
+    '%'   : Result:=cuPERCENT;
+    'px'  : Result:=cuPX;
+    'rem' : Result:=cuREM;
+    'em'  : Result:=cuEM;
+    'fr'  : Result:=cuFR;
+    'vw'  : Result:=cuVW;
+    'vh'  : Result:=cuVH;
+    'pt'  : Result:=cuPT;
+    'deg' : Result:=cuDEG;
+    else
+      // Ignore. For instance margin: 0 auto
+    end;
+    if Result<>cuNone then
+      Consume(CurrentToken);
+    end;
+end;
+
+function TCSSParser.CreateElement(aClass : TCSSElementClass): TCSSElement;
+
+begin
+  Result:=aClass.Create(CurrentSource,CurrentLine,CurrentPos);
+end;
+
+function TCSSParser.ParseIdentifier: TCSSIdentifierElement;
+
+Var
+  aValue : TCSSString;
+
+begin
+  aValue:=CurrentTokenString;
+  Result:=TCSSIdentifierElement(CreateElement(CSSIdentifierElementClass));
+  Result.Value:=aValue;
+  GetNextToken;
+end;
+
+function TCSSParser.ParseHashIdentifier: TCSSHashIdentifierElement;
+
+Var
+  aValue : TCSSString;
+
+begin
+  aValue:=CurrentTokenString;
+  system.delete(aValue,1,1);
+  Result:=TCSSHashIdentifierElement(CreateElement(CSSHashIdentifierElementClass));
+  Result.Value:=aValue;
+  GetNextToken;
+end;
+
+function TCSSParser.ParseClassName: TCSSClassNameElement;
+
+Var
+  aValue : TCSSString;
+
+begin
+  aValue:=CurrentTokenString;
+  system.delete(aValue,1,1);
+  Result:=TCSSClassNameElement(CreateElement(CSSClassNameElementClass));
+  Result.Value:=aValue;
+  GetNextToken;
+end;
+
+function TCSSParser.ParseInteger: TCSSElement;
+
+Var
+  aValue : Integer;
+  aInt : TCSSIntegerElement;
+
+begin
+  aValue:=StrToInt(CurrentTokenString);
+  aInt:=TCSSIntegerElement(CreateElement(CSSIntegerElementClass));
+  try
+    aInt.Value:=aValue;
+    Consume(ctkINTEGER);
+    aInt.Units:=ParseUnit;
+    Result:=aInt;
+    aInt:=nil;
+  finally
+    aInt.Free;
+  end;
+end;
+
+function TCSSParser.ParseFloat: TCSSElement;
+Var
+  aCode : Integer;
+  aValue : Double;
+  aFloat : TCSSFloatElement;
+
+begin
+  Val(CurrentTokenString,aValue,aCode);
+  if aCode<>0 then
+    DoError(SErrInvalidFloat,[CurrentTokenString]);
+  aFloat:=TCSSFloatElement(CreateElement(CSSFloatElementClass));
+  try
+    Consume(ctkFloat);
+    aFloat.Value:=aValue;
+    aFloat.Units:=ParseUnit;
+    Result:=aFloat;
+    aFloat:=nil;
+  finally
+    aFloat.Free;
+  end;
+end;
+
+
+function TCSSParser.ParseParenthesis: TCSSElement;
+
+var
+  aList: TCSSElement;
+begin
+  Consume(ctkLPARENTHESIS);
+  aList:=ParseComponentValueList;
+  try
+    Consume(ctkRPARENTHESIS);
+    Result:=aList;
+    aList:=nil;
+  finally
+    aList.Free;
+  end;
+end;
+
+function TCSSParser.ParseURL: TCSSElement;
+
+Var
+  aURL : TCSSURLElement;
+
+begin
+  aURL:=TCSSURLElement(CreateElement(CSSURLElementClass));
+  try
+    aURL.Value:=CurrentTokenString;
+    if CurrentToken=ctkURL then
+      Consume(ctkURL)
+    else
+      Consume(ctkBADURL);
+     Result:=aURL;
+     aURL:=nil;
+  finally
+    aURL.Free;
+  end;
+end;
+
+function TCSSParser.ParseInvalidToken: TCSSElement;
+begin
+  Result:=TCSSElement(CreateElement(TCSSElement));
+  GetNextToken;
+end;
+
+function TCSSParser.ParsePseudo: TCSSElement;
+
+Var
+  aPseudo : TCSSPseudoClassElement;
+  aValue : TCSSString;
+
+begin
+  aValue:=CurrentTokenString;
+  aPseudo:=TCSSPseudoClassElement(CreateElement(CSSPseudoClassElementClass));
+  try
+    Consume(ctkPseudo);
+    aPseudo.Value:=aValue;
+    Result:=aPseudo;
+    aPseudo:=nil;
+  finally
+    aPseudo.Free;
+  end;
+end;
+
+function TCSSParser.ParseRuleBody(aRule: TCSSRuleElement; aIsAt: Boolean = false): integer;
+
+Var
+  aDecl : TCSSElement;
+
+begin
+  aDecl:=nil;
+  if not (CurrentToken in [ctkRBRACE,ctkSEMICOLON]) then
+    begin
+    aDecl:=ParseDeclaration(aIsAt);
+    aRule.AddChild(aDecl);
+    end;
+  While Not (CurrentToken in [ctkEOF,ctkRBRACE]) do
+    begin
+    While CurrentToken=ctkSEMICOLON do
+      Consume(ctkSEMICOLON);
+    if Not (CurrentToken in [ctkEOF,ctkRBRACE]) then
+      begin
+      if CurrentToken=ctkATKEYWORD then
+        aDecl:=ParseAtUnknownRule
+      else
+        aDecl:=ParseDeclaration(aIsAt);
+      aRule.AddChild(aDecl);
+      end;
+    end;
+  Result:=aRule.ChildCount;
+end;
+
+function TCSSParser.ParseRule: TCSSElement;
+
+Var
+  aRule : TCSSRuleElement;
+  aSel : TCSSElement;
+  Term : TCSSTokens;
+  aLast : TCSSToken;
+  aList: TCSSListElement;
+{$IFDEF VerboseCSSParser}
+  aAt : TCSSString;
+{$ENDIF}
+
+begin
+  Inc(FRuleLevel);
+{$IFDEF VerboseCSSParser}
+  aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
+  Writeln('Parse rule.: ',aAt);
+{$ENDIF}
+  case CurrentToken of
+  ctkEOF: exit(nil);
+  ctkSEMICOLON:
+    begin
+    Result:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
+    exit;
+    end;
+  end;
+
+  Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
+  aRule:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
+  aList:=nil;
+  try
+    aList:=TCSSListElement(CreateElement(CSSListElementClass));
+    While Not (CurrentToken in Term) do
+      begin
+      aSel:=ParseSelector;
+      aRule.AddSelector(aSel);
+      if CurrentToken=ctkCOMMA then
+        begin
+        Consume(ctkCOMMA);
+        aRule.AddSelector(GetAppendElement(aList));
+        aList:=TCSSListElement(CreateElement(CSSListElementClass));
+        end;
+      end;
+    // Note: no selectors is allowed
+    aRule.AddSelector(GetAppendElement(aList));
+    aList:=nil;
+    aLast:=CurrentToken;
+    if (aLast<>ctkSEMICOLON) then
+      begin
+      Consume(ctkLBrace);
+      ParseRuleBody(aRule);
+      Consume(ctkRBRACE);
+      end;
+    Result:=aRule;
+    aRule:=nil;
+    {$IFDEF VerboseCSSParser}
+    Writeln('Rule started at ',aAt,' done');
+    {$endif}
+    Dec(FRuleLevel);
+  finally
+    aRule.Free;
+    aList.Free;
+  end;
+end;
+
+function TCSSParser.ParseUnary: TCSSElement;
+
+var
+  Un : TCSSUnaryElement;
+  Op : TCSSUnaryOperation;
+
+begin
+  Result:=nil;
+  if not (CurrentToken in [ctkDOUBLECOLON, ctkMinus, ctkPlus, ctkDiv, ctkGT, ctkTILDE]) then
+    Raise ECSSParser.CreateFmt(SUnaryInvalidToken,[CurrentTokenString]);
+  Un:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
+  try
+    op:=TokenToUnaryOperation(CurrentToken);
+    Un.Operation:=op;
+    Consume(CurrentToken);
+    Un.Right:=ParseComponentValue;
+    Result:=Un;
+    Un:=nil;
+  finally
+    Un.Free;
+  end;
+end;
+
+function TCSSParser.ParseComponentValueList(AllowRules : Boolean = True): TCSSElement;
+
+Const
+  TermSeps = [ctkEquals,ctkPlus,ctkMinus,ctkAnd,ctkLT,ctkDIV,
+              ctkStar,ctkTilde,ctkColon, ctkDoubleColon,
+              ctkSquared,ctkGT, ctkPIPE, ctkDOLLAR];
+  ListTerms = [ctkEOF,ctkLBRACE,ctkATKEYWORD,ctkComma];
+
+  function DoBinary(var aLeft : TCSSElement) : TCSSElement;
+  var
+    Bin : TCSSBinaryElement;
+  begin
+    Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+    try
+      Bin.Left:=ALeft;
+      aLeft:=Nil;
+      Bin.Operation:=TokenToBinaryOperation(CurrentToken);
+      Consume(CurrentToken);
+      Bin.Right:=ParseComponentValue;
+      if Bin.Right=nil then
+        DoError(SErrUnexpectedToken ,[
+               GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+               CurrentTokenString,
+               'value'
+               ]);
+      Result:=Bin;
+      Bin:=nil;
+    finally
+      Bin.Free;
+    end;
+  end;
+
+Var
+  List : TCSSListElement;
+  aFactor : TCSSelement;
+
+begin
+  aFactor:=Nil;
+  List:=TCSSListElement(CreateElement(CSSListElementClass));
+  try
+    if AllowRules and (CurrentToken in [ctkLBRACE,ctkATKEYWORD]) then
+      begin
+      if CurrentToken=ctkATKEYWORD then
+        aFactor:=ParseAtUnknownRule
+      else
+        aFactor:=ParseRule;
+      end
+    else
+      aFactor:=ParseComponentValue;
+    if aFactor=nil then
+      DoError(SErrUnexpectedToken ,[
+             GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+             CurrentTokenString,
+             'value'
+             ]);
+    While Assigned(aFactor) do
+      begin
+      While CurrentToken in TermSeps do
+        aFactor:=DoBinary(aFactor);
+      List.AddChild(aFactor);
+      aFactor:=Nil;
+      if not (CurrentToken in ListTerms) then
+        aFactor:=ParseComponentValue;
+      end;
+    Result:=GetAppendElement(List);
+    List:=nil;
+  finally
+    List.Free;
+    aFactor.Free;
+  end;
+end;
+
+
+function TCSSParser.ParseComponentValue: TCSSElement;
+
+Const
+  FinalTokens =
+     [ctkLPARENTHESIS,ctkURL,ctkColon,ctkLBRACE, ctkLBRACKET,
+      ctkDOUBLECOLON,ctkMinus,ctkPlus,ctkDiv,ctkSTAR,ctkTILDE];
+
+var
+  aToken : TCSSToken;
+
+begin
+  aToken:=CurrentToken;
+  Case aToken of
+    ctkLPARENTHESIS: Result:=ParseParenthesis;
+    ctkURL: Result:=ParseURL;
+    ctkPSEUDO: Result:=ParsePseudo;
+    ctkLBRACE: Result:=ParseRule;
+    ctkLBRACKET: Result:=ParseArray(Nil);
+    ctkMinus,
+    ctkPlus,
+    ctkDiv,
+    ctkGT,
+    ctkTilde: Result:=ParseUnary;
+    ctkUnicodeRange: Result:=ParseUnicodeRange;
+    ctkSTRING,
+    ctkHASH : Result:=ParseString;
+    ctkINTEGER: Result:=ParseInteger;
+    ctkFloat : Result:=ParseFloat;
+    ctkPSEUDOFUNCTION,
+    ctkFUNCTION : Result:=ParseCall('');
+    ctkSTAR: Result:=ParseInvalidToken;
+    ctkIDENTIFIER: Result:=ParseIdentifier;
+    ctkCLASSNAME : Result:=ParseClassName;
+  else
+    Result:=nil;
+//    Consume(aToken);// continue
+  end;
+  if aToken in FinalTokens then
+    exit;
+  if (CurrentToken=ctkLBRACKET) then
+    Result:=ParseArray(Result);
+end;
+
+function TCSSParser.ParseSelector: TCSSElement;
+
+  function ParseSub: TCSSElement;
+  begin
+    Result:=nil;
+    Case CurrentToken of
+      ctkSTAR,
+      ctkIDENTIFIER : Result:=ParseIdentifier;
+      ctkHASH : Result:=ParseHashIdentifier;
+      ctkCLASSNAME : Result:=ParseClassName;
+      ctkLBRACKET: Result:=ParseAttributeSelector;
+      ctkPSEUDO: Result:=ParsePseudo;
+      ctkPSEUDOFUNCTION: Result:=ParseCall('');
+    else
+      DoWarn(SErrUnexpectedToken ,[
+               GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+               CurrentTokenString,
+               'selector'
+               ]);
+      case CurrentToken of
+      ctkINTEGER: Result:=ParseInteger;
+      ctkFLOAT: Result:=ParseFloat;
+      else Result:=ParseInvalidToken;
+      end;
+    end;
+  end;
+
+var
+  ok, OldReturnWhiteSpace: Boolean;
+  Bin: TCSSBinaryElement;
+  El: TCSSElement;
+  List: TCSSListElement;
+begin
+  Result:=nil;
+  if CurrentToken in [ctkLBRACE,ctkRBRACE,ctkRPARENTHESIS,ctkEOF] then
+    exit;
+  El:=nil;
+  Bin:=nil;
+  List:=nil;
+  ok:=false;
+  //writeln('TCSSParser.ParseSelector START ',CurrentToken);
+  OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
+  Scanner.ReturnWhiteSpace:=true;
+  try
+    repeat
+      {$IFDEF VerbosecSSParser}
+      writeln('TCSSParser.ParseSelector LIST START ',CurrentToken,' ',CurrentTokenString);
+      {$ENDIF}
+      // read list
+      List:=nil;
+      El:=ParseSub;
+      {$IFDEF VerbosecSSParser}
+      writeln('TCSSParser.ParseSelector LIST NEXT ',CurrentToken,' ',CurrentTokenString,' El=',GetCSSObj(El));
+      {$ENDIF}
+      while CurrentToken in [ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION] do
+        begin
+        if List=nil then
+          begin
+          List:=TCSSListElement(CreateElement(CSSListElementClass));
+          List.AddChild(El);
+          El:=List;
+          end;
+        List.AddChild(ParseSub);
+        end;
+      List:=nil;
+
+      // use element
+      if Bin<>nil then
+        Bin.Right:=El
+      else
+        Result:=El;
+      El:=nil;
+
+      SkipWhiteSpace;
+      {$IFDEF VerbosecSSParser}
+      writeln('TCSSParser.ParseSelector LIST END ',CurrentToken,' ',CurrentTokenString);
+      {$ENDIF}
+
+      case CurrentToken of
+      ctkLBRACE,ctkRBRACE,ctkRBRACKET,ctkRPARENTHESIS,ctkEOF,ctkSEMICOLON,ctkCOMMA:
+        break;
+      ctkGT,ctkPLUS,ctkTILDE,ctkPIPE:
+        begin
+        // combinator
+        Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+        Bin.Left:=Result;
+        Result:=Bin;
+        Bin.Operation:=TokenToBinaryOperation(CurrentToken);
+        GetNextToken;
+        SkipWhiteSpace;
+        end;
+      ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION:
+        begin
+        // decendant combinator
+        Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+        Bin.Left:=Result;
+        Result:=Bin;
+        Bin.Operation:=boWhiteSpace;
+        end;
+      else
+        break;
+      end;
+    until false;
+    ok:=true;
+  finally
+    Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
+    if not ok then
+      begin
+      Result.Free;
+      El.Free;
+      List.Free;
+      Bin.Free;
+      end;
+  end;
+end;
+
+function TCSSParser.ParseAttributeSelector: TCSSElement;
+
+Var
+  aEl : TCSSElement;
+  aArray : TCSSArrayElement;
+  Bin: TCSSBinaryElement;
+  StrEl: TCSSStringElement;
+  aToken: TCSSToken;
+
+begin
+  Result:=Nil;
+  aArray:=TCSSArrayElement(CreateElement(CSSArrayElementClass));
+  try
+    Consume(ctkLBRACKET);
+    SkipWhiteSpace;
+    aEl:=ParseWQName;
+    SkipWhiteSpace;
+    aToken:=CurrentToken;
+    case aToken of
+    ctkEQUALS,ctkTILDEEQUAL,ctkPIPEEQUAL,ctkSQUAREDEQUAL,ctkDOLLAREQUAL,ctkSTAREQUAL:
+      begin
+      // parse attr-matcher
+      Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
+      aArray.AddChild(Bin);
+      Bin.Left:=aEl;
+      Bin.Operation:=TokenToBinaryOperation(aToken);
+      GetNextToken;
+      SkipWhiteSpace;
+      // parse value
+      case CurrentToken of
+      ctkIDENTIFIER:
+        Bin.Right:=ParseIdentifier;
+      ctkSTRING:
+        begin
+        StrEl:=TCSSStringElement(CreateElement(CSSStringElementClass));
+        StrEl.Value:=CurrentTokenString;
+        Bin.Right:=StrEl;
+        GetNextToken;
+        end;
+      ctkINTEGER:
+        Bin.Right:=ParseInteger;
+      ctkFLOAT:
+        Bin.Right:=ParseFloat;
+      else
+        DoError(SErrUnexpectedToken ,[
+                 GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+                 CurrentTokenString,
+                 'attribute value'
+                 ]);
+      end;
+      end;
+    else
+      aArray.AddChild(aEl);
+    end;
+    SkipWhiteSpace;
+    while CurrentToken=ctkIDENTIFIER do
+      begin
+      // attribute modifier
+      // with CSS 5 there is only i and s, but for future compatibility read all
+      aArray.AddChild(ParseIdentifier);
+      SkipWhiteSpace;
+      end;
+    Consume(ctkRBRACKET);
+
+    Result:=aArray;
+    aArray:=nil;
+  finally
+    aArray.Free;
+  end;
+end;
+
+function TCSSParser.ParseWQName: TCSSElement;
+begin
+  if CurrentToken<>ctkIDENTIFIER then
+    DoError(SErrUnexpectedToken ,[
+             GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
+             CurrentTokenString,
+             'identifier'
+             ]);
+  Result:=ParseIdentifier;
+  // todo: parse optional ns-prefix
+end;
+
+function TCSSParser.ParseDeclaration(aIsAt: Boolean = false): TCSSDeclarationElement;
+
+Var
+  aDecl : TCSSDeclarationElement;
+  aKey,aValue : TCSSElement;
+  aPrevDisablePseudo : Boolean;
+  aList : TCSSListElement;
+
+begin
+  aList:=nil;
+  aDecl:= TCSSDeclarationElement(CreateElement(CSSDeclarationElementClass));
+  try
+    aPrevDisablePseudo:= Scanner.DisablePseudo;
+    Scanner.DisablePseudo:=True;
+    aKey:=ParseComponentValue;
+    aDecl.AddKey(aKey);
+    if aIsAt then
+      begin
+      While (CurrentToken=ctkCOMMA) do
+        begin
+        while (CurrentToken=ctkCOMMA) do
+          Consume(ctkCOMMA);
+        aKey:=ParseComponentValue;
+        aDecl.AddKey(aKey);
+        end;
+      end;
+    if Not aIsAt then
+      begin
+      aDecl.Colon:=True;
+      Consume(ctkCOLON);
+      end
+    else
+      begin
+      aDecl.Colon:=CurrentToken=ctkColon;
+      if aDecl.Colon then
+        Consume(ctkColon)
+      end;
+    Scanner.DisablePseudo:=aPrevDisablePseudo;
+    aValue:=ParseComponentValue;
+    aList:=TCSSListElement(CreateElement(CSSListElementClass));
+    aList.AddChild(aValue);
+    if aDecl.Colon then
+      begin
+      While not (CurrentToken in [ctkEOF,ctkSemicolon,ctkRBRACE,ctkImportant]) do
+        begin
+        While CurrentToken=ctkCOMMA do
+          begin
+          Consume(ctkCOMMA);
+          aDecl.AddChild(GetAppendElement(aList));
+          aList:=TCSSListElement(CreateElement(CSSListElementClass));
+          end;
+        aValue:=ParseComponentValue;
+        if aValue=nil then break;
+        aList.AddChild(aValue);
+        end;
+      if CurrentToken=ctkImportant then
+        begin
+        Consume(ctkImportant);
+        aDecl.IsImportant:=True;
+        end;
+      end;
+    aDecl.AddChild(GetAppendElement(aList));
+    aList:=nil;
+    Result:=aDecl;
+    aDecl:=nil;
+  finally
+    Scanner.DisablePseudo:=False;
+    aDecl.Free;
+    aList.Free;
+  end;
+end;
+
+function TCSSParser.ParseCall(aName : TCSSString): TCSSElement;
+
+var
+  aCall : TCSSCallElement;
+  l : Integer;
+  OldReturnWhiteSpace: Boolean;
+  aValue: TCSSElement;
+begin
+  OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
+  Scanner.ReturnWhiteSpace:=false;
+  aCall:=TCSSCallElement(CreateELement(CSSCallElementClass));
+  try
+    if (aName='') then
+      aName:=CurrentTokenString;
+    L:=Length(aName);
+    if (L>0) and (aName[L]='(') then
+      aName:=Copy(aName,1,L-1);
+    aCall.Name:=aName;
+    if CurrentToken=ctkPSEUDOFUNCTION then
+      begin
+      Consume(ctkPSEUDOFUNCTION);
+      case aName of
+      ':not',':is',':where':
+        ParseSelectorCommaList(aCall);
+      ':has':
+        ParseRelationalSelectorCommaList(aCall);
+      ':nth-child',':nth-last-child',':nth-of-type',':nth-last-of-type':
+        ParseNthChildParams(aCall);
+      end;
+      end
+    else
+      Consume(ctkFUNCTION);
+    // Call argument list can be empty: mask()
+    While not (CurrentToken in [ctkRPARENTHESIS,ctkEOF]) do
+      begin
+      aValue:=ParseComponentValue;
+      if aValue=nil then
+      begin
+        aValue:=TCSSElement(CreateElement(TCSSElement));
+        GetNextToken;
+      end;
+      aCall.AddArg(aValue);
+      if (CurrentToken=ctkCOMMA) then
+        Consume(ctkCOMMA);
+      end;
+    if CurrentToken=ctkEOF then
+      DoError(SErrUnexpectedEndOfFile,[aName]);
+    Consume(ctkRPARENTHESIS);
+    Result:=aCall;
+    aCall:=nil;
+  finally
+    Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
+    aCall.Free;
+  end;
+end;
+
+procedure TCSSParser.ParseSelectorCommaList(aCall: TCSSCallElement);
+var
+  El: TCSSElement;
+begin
+  while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
+    begin
+    El:=ParseSelector;
+    if EL=nil then exit;
+    aCall.AddArg(El);
+    SkipWhiteSpace;
+    if CurrentToken<>ctkCOMMA then
+      exit;
+    GetNextToken;
+  end;
+end;
+
+procedure TCSSParser.ParseRelationalSelectorCommaList(aCall: TCSSCallElement);
+var
+  El: TCSSElement;
+  aToken: TCSSToken;
+  IsUnary: Boolean;
+  Unary: TCSSUnaryElement;
+begin
+  while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
+    begin
+    IsUnary:=false;
+    aToken:=CurrentToken;
+    if aToken in [ctkGT,ctkPLUS,ctkTILDE] then
+      begin
+      IsUnary:=true;
+      GetNextToken;
+      end;
+    El:=ParseSelector;
+    if El=nil then exit;
+    if IsUnary then
+      begin
+      Unary:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
+      aCall.AddArg(Unary);
+      Unary.Right:=El;
+      Unary.Operation:=TokenToUnaryOperation(aToken);
+      end
+    else
+      aCall.AddArg(El);
+    if CurrentToken<>ctkCOMMA then
+      exit;
+    GetNextToken;
+  end;
+end;
+
+procedure TCSSParser.ParseNthChildParams(aCall: TCSSCallElement);
+// Examples:
+// odd
+// even
+//  n
+//  +n
+// -2n
+// 2n+1
+//  even of :not(:hidden)
+// 2n+1 of [:not(display=none)]
+var
+  aUnary: TCSSUnaryElement;
+  IdentEl: TCSSIdentifierElement;
+begin
+  case CurrentToken of
+  ctkIDENTIFIER:
+    case lowercase(CurrentTokenString) of
+    'odd','even','n':
+      aCall.AddArg(ParseIdentifier);
+    '-n':
+      begin
+        aUnary:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
+        aCall.AddArg(aUnary);
+        aUnary.Operation:=uoMinus;
+        IdentEl:=TCSSIdentifierElement(CreateElement(CSSIdentifierElementClass));
+        aUnary.Right:=IdentEl;
+        IdentEl.Value:='n';
+        GetNextToken;
+      end;
+    else
+      DoWarnExpectedButGot('An+B');
+      aCall.AddArg(ParseIdentifier);
+      exit;
+    end;
+  ctkINTEGER:
+    begin
+    aCall.AddArg(ParseInteger);
+    if (CurrentToken<>ctkIDENTIFIER) then
+      begin
+      DoWarnExpectedButGot('An+B');
+      exit;
+      end;
+    if (lowercase(CurrentTokenString)<>'n') then
+      begin
+      DoWarnExpectedButGot('An+B');
+      exit;
+      end;
+    aCall.AddArg(ParseIdentifier);
+    end;
+  else
+    DoWarnExpectedButGot('An+B');
+    exit;
+  end;
+
+  if CurrentToken in [ctkMINUS,ctkPLUS] then
+    aCall.AddArg(ParseUnary);
+  if CurrentToken=ctkINTEGER then
+    aCall.AddArg(ParseInteger);
+  if (CurrentToken=ctkIDENTIFIER) and SameText(CurrentTokenString,'of') then
+    begin
+    aCall.AddArg(ParseIdentifier);
+    aCall.AddArg(ParseSelector);
+    end;
+end;
+
+function TCSSParser.ParseString: TCSSElement;
+
+Var
+  aValue : TCSSString;
+  aEl : TCSSElement;
+  aStr : TCSSStringElement;
+
+begin
+  aValue:=CurrentTokenString;
+  aStr:=TCSSStringElement(CreateElement(CSSStringElementClass));
+  try
+    if CurrentToken=ctkSTRING then
+      Consume(ctkSTRING)
+    else
+      Consume(ctkHASH); // e.g. #rrggbb
+    aStr.Value:=aValue;
+    While (CurrentToken in [ctkIDENTIFIER,ctkSTRING,ctkINTEGER,ctkFLOAT,ctkHASH]) do
+      begin
+      aEl:=ParseComponentValue;
+      aStr.Children.Add(aEl);
+      end;
+    Result:=aStr;
+    aStr:=nil;
+  finally
+    aStr.Free;
+  end;
+end;
+
+function TCSSParser.ParseUnicodeRange: TCSSElement;
+Var
+  aValue : TCSSString;
+  aRange : TCSSUnicodeRangeElement;
+
+begin
+  aValue:=CurrentTokenString;
+  aRange:=TCSSUnicodeRangeElement(CreateElement(CSSUnicodeRangeElementClass));
+  try
+    Consume(ctkUnicodeRange);
+    aRange.Value:=aValue;
+    Result:=aRange;
+    aRange:=nil;
+  finally
+    aRange.Free;
+  end;
+end;
+
+function TCSSParser.ParseArray(aPrefix: TCSSElement): TCSSElement;
+
+Var
+  aEl : TCSSElement;
+  aArray : TCSSArrayElement;
+
+begin
+  Result:=Nil;
+  aArray:=TCSSArrayElement(CreateElement(CSSArrayElementClass));
+  try
+    aArray.Prefix:=aPrefix;
+    Consume(ctkLBRACKET);
+    While CurrentToken<>ctkRBRACKET do
+      begin
+      aEl:=ParseComponentValueList;
+      aArray.AddChild(aEl);
+      end;
+    Consume(ctkRBRACKET);
+    Result:=aArray;
+    aArray:=nil;
+  finally
+    aArray.Free;
+  end;
+end;
+
+end.
+

+ 2061 - 0
src/base/fcl-css/fpcssresolver.pas

@@ -0,0 +1,2061 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2022 by Michael Van Canneyt ([email protected])
+
+    This file contains CSS utility class
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************
+
+Works:
+
+Selector 	Example 	Example description
+.class  	.intro  	Selects all elements with class="intro"
+.class1.class2 	.name1.name2 	Selects all elements with both name1 and name2 set within its class attribute
+.class1 .class2 	.name1 .name2 	Selects all elements with name2 that is a descendant of an element with name1
+#id 	#firstname 	Selects the element with name="firstname"
+* 	* 	Selects all elements
+type 	p 	Selects all <p> elements
+type.class 	p.intro 	Selects all <p> elements with class="intro"
+type,type 	div, p 	Selects all <div> elements and all <p> elements
+type type 	div p 	Selects all <p> elements inside <div> elements
+type>type 	div > p 	Selects all <p> elements where the parent is a <div> element
+type+type 	div + p 	Selects the first <p> element that is placed immediately after a <div> element
+element1~element2 	p ~ ul 	Selects every <ul> element that is preceded by a <p> element
+[attribute] 	[target] 	Selects all elements with a target attribute
+[attribute=value] 	[target=_blank] 	Selects all elements with target="_blank"
+[attribute~=value] 	[title~=flower] 	Selects all elements with a title attribute containing the *word* "flower"
+[attribute|=value] 	[lang|=en] 	Selects all elements with a lang attribute value equal to "en" or starting with "en-" (hyphen)
+[attribute^=value] 	a[href^="https"] 	Selects every <a> element whose href attribute value begins with "https"
+[attribute$=value] 	a[href$=".pdf"] 	Selects every <a> element whose href attribute value ends with ".pdf"
+[attribute*=value] 	a[href*="w3schools"] 	Selects every <a> element whose href attribute value contains the substring "w3schools"
+:root 	:root 	Selects the document's root element
+:empty 	p:empty 	Selects every <p> element that has no children (including text nodes)
+:first-child 	p:first-child 	Selects every <p> element that is the first child of its parent
+:first-of-type 	p:first-of-type 	Selects every <p> element that is the first <p> element of its parent
+:last-child 	p:last-child 	Selects every <p> element that is the last child of its parent
+:last-of-type 	p:last-of-type 	Selects every <p> element that is the last <p> element of its parent
+:not(selector) 	:not(p) 	Selects every element that is not a <p> element
+:nth-child(n) 	p:nth-child(2) 	Selects every <p> element that is the second child of its parent. n can be a number, a keyword (odd or even), or a formula (like an + b).
+:nth-last-child(n) 	p:nth-last-child(2) 	Selects every <p> element that is the second child of its parent, counting from the last child
+:nth-last-of-type(n) 	p:nth-last-of-type(2) 	Selects every <p> element that is the second <p> element of its parent, counting from the last child
+:nth-of-type(n) 	p:nth-of-type(2) 	Selects every <p> element that is the second <p> element of its parent
+:only-of-type 	p:only-of-type 	Selects every <p> element that is the only <p> element of its parent
+:only-child 	p:only-child 	Selects every <p> element that is the only child of its parent
+:is()
+:where()
+
+Specifity:
+important: 10000
+inline: 1000
+id: 100 #menu
+class+attribute selectors: 10 .button, :hover, [href]
+element/type: 1 p, :before
+*: 0
+
+ToDo:
+- 'all' attribute: resets all properties, except direction and unicode bidi
+- :has()
+- TCSSResolver.FindComputedAttribute  use binary search for >8 elements
+- TCSSNumericalIDs: once initialized sort and use binary search
+- namespaces
+- layers
+- --varname
+- counter-reset
+- counter-increment
+- @rules:-----------------------------------------------------------------------
+  - @media
+  - @font-face
+  - @keyframes
+- Pseudo-elements - not case sensitive:-----------------------------------------
+  - ::first-letter 	p::first-letter 	Selects the first letter of every <p> element
+  - ::first-line 	p::first-line 	Selects the first line of every <p> element
+  - ::selection 	::selection 	Selects the portion of an element that is selected by a user
+- Altering:---------------------------------------------------------------------
+  - ::after 	p::after 	Insert something after the content of each <p> element
+  - ::before 	p::before 	Insert something before the content of each <p> element
+- Functions and Vars:-----------------------------------------------------------
+  - attr() 	Returns the value of an attribute of the selected element
+  - calc() 	Allows you to perform calculations to determine CSS property values  calc(100% - 100px)
+  - max() min()  min(50%, 50px)
+- columns:----------------------------------------------------------------------
+  - columns combinator ||     col.selected || td
+  - :nth-col()
+  - :nth-last-col()
+
+}
+
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpCSSResolver;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode ObjFPC}{$H+}
+{$Interfaces CORBA}
+{$IF FPC_FULLVERSION>30300}
+  {$WARN 6060 off} // Case statement does not handle all possible cases
+{$ENDIF}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses
+  System.Classes, System.SysUtils, System.Types, System.Contnrs, System.StrUtils, FPCSS.Tree;
+{$ELSE FPC_DOTTEDUNITS}
+uses
+  Classes, SysUtils, types, Contnrs, StrUtils, fpCSSTree;
+{$ENDIF FPC_DOTTEDUNITS}
+
+const
+  CSSSpecifityInvalid = -2;
+  CSSSpecifityNoMatch = -1;
+  CSSSpecifityUniversal = 0;
+  CSSSpecifityType = 1;
+  CSSSpecifityClass = 10; // includes attribute selectors e.g. [href]
+  CSSSpecifityIdentifier = 100;
+  CSSSpecifityInline = 1000;
+  CSSSpecifityImportant = 10000;
+
+  CSSIDNone = 0;
+  // built-in type IDs
+  CSSTypeID_Universal = 1; // id of type '*'
+  CSSLastTypeID = CSSTypeID_Universal;
+  // built-in attribute IDs
+  CSSAttributeID_ID = 1; // id of attribute key 'id'
+  CSSAttributeID_Class = 2; // id of attribute key 'class'
+  CSSAttributeID_All = 3; // id of attribute key 'all'
+  CSSLastAttributeID = CSSAttributeID_All;
+  // pseudo attribute and call IDs
+  CSSPseudoID_Root = 1; // :root
+  CSSPseudoID_Empty = CSSPseudoID_Root+1; // :empty
+  CSSPseudoID_FirstChild = CSSPseudoID_Empty+1; // :first-child
+  CSSPseudoID_LastChild = CSSPseudoID_FirstChild+1; // :last-child
+  CSSPseudoID_OnlyChild = CSSPseudoID_LastChild+1; // :only-child
+  CSSPseudoID_FirstOfType = CSSPseudoID_OnlyChild+1; // :first-of-type
+  CSSPseudoID_LastOfType = CSSPseudoID_FirstOfType+1; // :last-of-type
+  CSSPseudoID_OnlyOfType = CSSPseudoID_LastOfType+1; // :only-of-type
+  CSSCallID_Not = CSSPseudoID_OnlyOfType+1; // :not()
+  CSSCallID_Is = CSSCallID_Not+1; // :is()
+  CSSCallID_Where = CSSCallID_Is+1; // :where()
+  CSSCallID_Has = CSSCallID_Where+1; // :has()
+  CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child(n)
+  CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-last-child(n)
+  CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-of-type(n)
+  CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-last-of-type(n)
+  CSSLastPseudoID = CSSCallID_NthLastOfType;
+
+const
+  CSSPseudoNames: array[0..CSSLastPseudoID] of string = (
+    '?',
+    ':root',
+    ':empty',
+    ':first-child',
+    ':last-child',
+    ':only-child',
+    ':first-of-type',
+    ':last-of-type',
+    ':only-of-type',
+    ':not()',
+    ':is()',
+    ':where()',
+    ':has()',
+    ':nth-child(n)',
+    ':nth-last-child(n)',
+    ':nth-of-type(n)',
+    ':nth-last-of-type(n)'
+    );
+
+type
+  TCSSMsgID = int64;
+  TCSSNumericalID = integer;
+  TCSSSpecifity = integer;
+
+  ECSSResolver = class(ECSSException)
+  end;
+
+  TCSSAttributeMatchKind = (
+    camkEqual,
+    camkContains,
+    camkContainsWord,
+    camkBegins,
+    camkEnds
+    );
+  TCSSAttributeMatchKinds = set of TCSSAttributeMatchKind;
+
+  { ICSSNode }
+
+  ICSSNode = interface
+    function GetCSSID: TCSSString;
+    function GetCSSTypeName: TCSSString;
+    function GetCSSTypeID: TCSSNumericalID;
+    function HasCSSClass(const aClassName: TCSSString): boolean;
+    function GetCSSAttributeClass: TCSSString;
+    function GetCSSParent: ICSSNode;
+    function GetCSSIndex: integer; // node index in parent's children
+    function GetCSSNextSibling: ICSSNode;
+    function GetCSSPreviousSibling: ICSSNode;
+    function GetCSSChildCount: integer;
+    function GetCSSChild(const anIndex: integer): ICSSNode;
+    function GetCSSNextOfType: ICSSNode;
+    function GetCSSPreviousOfType: ICSSNode;
+    function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
+    function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString;
+    function HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean;
+    function GetCSSEmpty: boolean;
+    function GetCSSDepth: integer;
+    procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
+    function CheckCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement): boolean;
+  end;
+
+type
+  TCSSNumericalIDKind = (
+    nikType,
+    nikAttribute,
+    nikPseudoClass
+    );
+  TCSSNumericalIDKinds = set of TCSSNumericalIDKind;
+
+const
+  CSSNumericalIDKindNames: array[TCSSNumericalIDKind] of TCSSString = (
+    'Type',
+    'Attribute',
+    'PseudoClass'
+    );
+
+type
+
+  { TCSSNumericalIDs }
+
+  TCSSNumericalIDs = class
+  private
+    FKind: TCSSNumericalIDKind;
+    fList: TFPHashList;
+    function GetCount: Integer;
+    function GetIDs(const aName: TCSSString): TCSSNumericalID;
+    procedure SetIDs(const aName: TCSSString; const AValue: TCSSNumericalID);
+  public
+    constructor Create(aKind: TCSSNumericalIDKind);
+    destructor Destroy; override;
+    procedure Clear;
+    property IDs[const aName: TCSSString]: TCSSNumericalID read GetIDs write SetIDs; default;
+    property Kind: TCSSNumericalIDKind read FKind;
+    property Count: Integer read GetCount;
+  end;
+
+  TCSSComputedAttribute = record
+    AttrID: TCSSNumericalID;
+    Specifity: TCSSSpecifity;
+    Value: TCSSElement;
+  end;
+  TCSSComputedAttributeArray = array of TCSSComputedAttribute;
+  PCSSComputedAttribute = ^TCSSComputedAttribute;
+
+  TCSSElResolverData = class
+  public
+    Element: TCSSElement;
+    Next, Prev: TCSSElResolverData;
+  end;
+
+  TCSSValueValidity = (
+    cvvNone,
+    cvvValid,
+    cvvInvalid
+    );
+  TCSSValueValidities = set of TCSSValueValidity;
+
+  TCSSIdentifierData = class(TCSSElResolverData)
+  public
+    NumericalID: TCSSNumericalID;
+    Kind: TCSSNumericalIDKind;
+    ValueValid: TCSSValueValidity;
+  end;
+
+  TCSSValueData = class(TCSSElResolverData)
+  public
+    NormValue: string;
+  end;
+
+  { TCSSCallData }
+
+  TCSSCallData = class(TCSSElResolverData)
+  public
+    NumericalID: TCSSNumericalID;
+    Params: TObject;
+    destructor Destroy; override;
+  end;
+
+  TCSSCallNthChildParams = class;
+
+  TCSSCallNthChildParamsCacheItem = record
+    TypeID: TCSSNumericalID;
+    ChildIDs: TIntegerDynArray;
+    Cnt: integer; // = length(ChildIDs), used during creation
+  end;
+  PCSSCallNthChildParamsCacheItem = ^TCSSCallNthChildParamsCacheItem;
+  TCSSCallNthChildParamsCacheItems = array of TCSSCallNthChildParamsCacheItem;
+
+  TCSSCallNthChildParamsCache = class
+  public
+    Owner: TCSSCallNthChildParams;
+    Parent: ICSSNode;
+    StackDepth: integer;
+    Items: TCSSCallNthChildParamsCacheItems;
+  end;
+  TCSSCallNthChildParamsCaches = array of TCSSCallNthChildParamsCache;
+
+  { TCSSCallNthChildParams }
+
+  TCSSCallNthChildParams = class
+    Modulo: integer;
+    Start: integer;
+    HasOf: boolean;
+    OfSelector: TCSSElement;
+    StackCache: TCSSCallNthChildParamsCaches;
+    destructor Destroy; override;
+  end;
+
+  TCSSResolverOption = (
+    croErrorOnUnknownName
+    );
+  TCSSResolverOptions = set of TCSSResolverOption;
+
+  TCSSComputeOption = (
+    ccoCommit
+    );
+  TCSSComputeOptions = set of TCSSComputeOption;
+
+const
+  DefaultCSSComputeOptions = [ccoCommit];
+
+type
+  TCSSResolverLogEntry = class
+  public
+    MsgType: TEventType;
+    ID: TCSSMsgID;
+    Msg: string;
+    PosEl: TCSSElement;
+  end;
+
+  TCSSResolverLogEvent = procedure(Sender: TObject; Entry: TCSSResolverLogEntry) of object;
+
+  TCSSResStringComparison = (
+    crscDefault,
+    crscCaseInsensitive,
+    crscCaseSensitive
+    );
+  TCSSResStringComparisons = set of TCSSResStringComparison;
+
+  { TCSSResolver }
+
+  TCSSResolver = class(TComponent)
+  private
+    FNumericalIDs: array[TCSSNumericalIDKind] of TCSSNumericalIDs;
+    FOnLog: TCSSResolverLogEvent;
+    FOptions: TCSSResolverOptions;
+    FStringComparison: TCSSResStringComparison;
+    FStyles: TCSSElementArray;
+    FOwnsStyle: boolean;
+    FFirstElData: TCSSElResolverData;
+    FLastElData: TCSSElResolverData;
+    function GetAttributes(Index: integer): PCSSComputedAttribute;
+    function GetLogCount: integer;
+    function GetLogEntries(Index: integer): TCSSResolverLogEntry;
+    function GetNumericalIDs(Kind: TCSSNumericalIDKind): TCSSNumericalIDs;
+    function GetStyleCount: integer;
+    function GetStyles(Index: integer): TCSSElement;
+    procedure SetNumericalIDs(Kind: TCSSNumericalIDKind;
+      const AValue: TCSSNumericalIDs);
+    procedure SetOptions(const AValue: TCSSResolverOptions);
+    procedure SetStyles(Index: integer; const AValue: TCSSElement);
+  protected
+    FAttributes: TCSSComputedAttributeArray;
+    FAttributeCount: integer;
+    FNode: ICSSNode;
+    FLogEntries: TFPObjectList; // list of TCSSResolverLogEntry
+    procedure ComputeElement(El: TCSSElement); virtual;
+    procedure ComputeRule(aRule: TCSSRuleElement); virtual;
+    procedure ComputeInline(El: TCSSElement); virtual;
+    procedure ComputeInlineRule(aRule: TCSSRuleElement); virtual;
+    function SelectorMatches(aSelector: TCSSElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorIdentifierMatches(Identifier: TCSSIdentifierElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorHashIdentifierMatches(Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorPseudoClassMatches(aPseudoClass: TCSSPseudoClassElement; var TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorListMatches(aList: TCSSListElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorArrayMatches(anArray: TCSSArrayElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode): TCSSSpecifity; virtual;
+    function SelectorCallMatches(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Not(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Is(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_Where(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function Call_NthChild(CallID: TCSSNumericalID; aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
+    function CollectSiblingsOf(CallID: TCSSNumericalID; TestNode: ICSSNode;
+      Params: TCSSCallNthChildParams): TIntegerDynArray; virtual;
+    function GetSiblingOfIndex(SiblingIDs: TIntegerDynArray; Index: integer): integer; virtual;
+    function ComputeValue(El: TCSSElement): TCSSString; virtual;
+    function SameValueText(const A, B: TCSSString): boolean; virtual;
+    function SameValueText(A: PAnsiChar; ALen: integer; B: PAnsiChar; BLen: integer): boolean; virtual;
+    function PosSubString(const SearchStr, Str: TCSSString): integer; virtual;
+    function PosWord(const SearchWord, Words: TCSSString): integer; virtual;
+    function GetSiblingCount(aNode: ICSSNode): integer; virtual;
+    procedure MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity); virtual;
+    function CheckAttrValueValidity(AttrID: TCSSNumericalID; aKey, aValue: TCSSElement): boolean; virtual;
+    function ResolveIdentifier(El: TCSSIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
+    function ResolveCall(El: TCSSCallElement): TCSSNumericalID; virtual;
+    procedure AddElData(El: TCSSElement; ElData: TCSSElResolverData); virtual;
+    function AddElValueData(El: TCSSElement; const aValue: TCSSString): TCSSValueData; virtual;
+    function FindComputedAttribute(AttrID: TCSSNumericalID): PCSSComputedAttribute;
+    function AddComputedAttribute(TheAttrID: TCSSNumericalID; aSpecifity: TCSSSpecifity;
+                          aValue: TCSSElement): PCSSComputedAttribute;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    function GetElPath(El: TCSSElement): string; virtual;
+    function GetElPos(El: TCSSElement): string; virtual;
+    function IndexOfStyle(aStyle: TCSSElement): integer; virtual;
+    procedure AddStyle(aStyle: TCSSElement); virtual;
+    procedure Clear; virtual;
+    procedure ClearStyleCustomData; virtual;
+    procedure ClearStyles; virtual;
+    procedure Commit; virtual;
+    procedure Compute(Node: ICSSNode; NodeStyle: TCSSElement = nil;
+      const CompOptions: TCSSComputeOptions = DefaultCSSComputeOptions); virtual;
+    procedure DeleteStyle(aIndex: integer); virtual;
+    procedure Log(MsgType: TEventType; const ID: TCSSMsgID; Msg: string; PosEl: TCSSElement); virtual;
+    procedure LogWarning(IsError: boolean; const ID: TCSSMsgID; Msg: string; PosEl: TCSSElement); virtual;
+    procedure RemoveStyle(aStyle: TCSSElement); virtual;
+    property AttributeCount: integer read FAttributeCount;
+    property Attributes[Index: integer]: PCSSComputedAttribute read GetAttributes;
+    property LogCount: integer read GetLogCount;
+    property LogEntries[Index: integer]: TCSSResolverLogEntry read GetLogEntries;
+    property NumericalIDs[Kind: TCSSNumericalIDKind]: TCSSNumericalIDs read GetNumericalIDs write SetNumericalIDs;
+    property OnLog: TCSSResolverLogEvent read FOnLog write FOnLog;
+    property Options: TCSSResolverOptions read FOptions write SetOptions;
+    property OwnsStyle: boolean read FOwnsStyle write FOwnsStyle default false;
+    property StringComparison: TCSSResStringComparison read FStringComparison;
+    property StyleCount: integer read GetStyleCount;
+    property Styles[Index: integer]: TCSSElement read GetStyles write SetStyles;
+  end;
+
+implementation
+
+{ TCSSCallNthChildParams }
+
+destructor TCSSCallNthChildParams.Destroy;
+var
+  i: Integer;
+begin
+  for i:=0 to high(StackCache) do
+    StackCache[i].Free;
+  inherited Destroy;
+end;
+
+{ TCSSCallData }
+
+destructor TCSSCallData.Destroy;
+begin
+  FreeAndNil(Params);
+  inherited Destroy;
+end;
+
+{ TCSSNumericalIDs }
+
+function TCSSNumericalIDs.GetIDs(const aName: TCSSString): TCSSNumericalID;
+begin
+  {$WARN 4056 off : Conversion between ordinals and pointers is not portable}
+  Result:=TCSSNumericalID(fList.Find(aName));
+  {$WARN 4056 on}
+end;
+
+function TCSSNumericalIDs.GetCount: Integer;
+begin
+  Result:=fList.Count;
+end;
+
+procedure TCSSNumericalIDs.SetIDs(const aName: TCSSString;
+  const AValue: TCSSNumericalID);
+var
+  i: Integer;
+begin
+  i:=fList.FindIndexOf(aName);
+  if i>=0 then
+    fList.Delete(i);
+  if AValue=CSSIDNone then
+    exit;
+  {$WARN 4056 off : Conversion between ordinals and pointers is not portable}
+  fList.Add(aName,Pointer(AValue));
+  {$WARN 4056 on}
+end;
+
+constructor TCSSNumericalIDs.Create(aKind: TCSSNumericalIDKind);
+begin
+  FKind:=aKind;
+  fList:=TFPHashList.Create;
+end;
+
+destructor TCSSNumericalIDs.Destroy;
+begin
+  FreeAndNil(fList);
+  inherited Destroy;
+end;
+
+procedure TCSSNumericalIDs.Clear;
+begin
+  fList.Clear;
+end;
+
+{ TCSSResolver }
+
+function TCSSResolver.GetNumericalIDs(Kind: TCSSNumericalIDKind
+  ): TCSSNumericalIDs;
+begin
+  Result:=FNumericalIDs[Kind];
+end;
+
+function TCSSResolver.GetStyleCount: integer;
+begin
+  Result:=length(FStyles);
+end;
+
+function TCSSResolver.GetStyles(Index: integer): TCSSElement;
+begin
+  Result:=FStyles[Index];
+end;
+
+function TCSSResolver.GetAttributes(Index: integer): PCSSComputedAttribute;
+begin
+  if (Index<0) or (Index>=FAttributeCount) then
+    raise ECSSResolver.Create('TCSSResolver.GetAttributes index out of bounds');
+  Result:=@FAttributes[Index];
+end;
+
+function TCSSResolver.GetLogCount: integer;
+begin
+  Result:=FLogEntries.Count;
+end;
+
+function TCSSResolver.GetLogEntries(Index: integer): TCSSResolverLogEntry;
+begin
+  Result:=TCSSResolverLogEntry(FLogEntries[Index]);
+end;
+
+procedure TCSSResolver.SetNumericalIDs(Kind: TCSSNumericalIDKind;
+  const AValue: TCSSNumericalIDs);
+begin
+  FNumericalIDs[Kind]:=AValue;
+end;
+
+procedure TCSSResolver.SetOptions(const AValue: TCSSResolverOptions);
+begin
+  if FOptions=AValue then Exit;
+  FOptions:=AValue;
+end;
+
+procedure TCSSResolver.SetStyles(Index: integer; const AValue: TCSSElement);
+begin
+  if (Index<0) or (Index>=length(FStyles)) then
+    raise ECSSResolver.Create('TCSSResolver.SetStyles index '+IntToStr(Index)+' out of bounds '+IntToStr(length(FStyles)));
+  if FStyles[Index]=AValue then exit;
+  if OwnsStyle then
+    FStyles[Index].Free;
+  FStyles[Index]:=AValue;
+end;
+
+procedure TCSSResolver.ComputeElement(El: TCSSElement);
+var
+  C: TClass;
+  Compound: TCSSCompoundElement;
+  i: Integer;
+begin
+  if El=nil then exit;
+  C:=El.ClassType;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.ComputeElement ',GetCSSPath(El));
+  {$ENDIF}
+  if C=TCSSCompoundElement then
+  begin
+    Compound:=TCSSCompoundElement(El);
+    //writeln('TCSSResolver.ComputeElement Compound.ChildCount=',Compound.ChildCount);
+    for i:=0 to Compound.ChildCount-1 do
+      ComputeElement(Compound.Children[i]);
+  end else if C=TCSSRuleElement then
+    ComputeRule(TCSSRuleElement(El))
+  else
+    Log(etWarning,20220908150252,'TCSSResolver.ComputeElement: Unknown CSS element',El);
+end;
+
+procedure TCSSResolver.ComputeRule(aRule: TCSSRuleElement);
+var
+  i: Integer;
+  BestSpecifity, Specifity: TCSSSpecifity;
+  aSelector: TCSSElement;
+begin
+  BestSpecifity:=CSSSpecifityNoMatch;
+  for i:=0 to aRule.SelectorCount-1 do
+  begin
+    aSelector:=aRule.Selectors[i];
+    Specifity:=SelectorMatches(aSelector,FNode,false);
+    if Specifity>BestSpecifity then
+      BestSpecifity:=Specifity;
+  end;
+  if BestSpecifity>=0 then
+  begin
+    // match -> apply properties
+    for i:=0 to aRule.ChildCount-1 do
+      MergeProperty(aRule.Children[i],BestSpecifity);
+  end;
+end;
+
+procedure TCSSResolver.ComputeInline(El: TCSSElement);
+var
+  C: TClass;
+begin
+  if El=nil then exit;
+  C:=El.ClassType;
+  if C=TCSSRuleElement then
+    ComputeInlineRule(TCSSRuleElement(El))
+  else
+    Log(etWarning,20220915140402,'TCSSResolver.ComputeInline Not yet supported inline element',El);
+end;
+
+procedure TCSSResolver.ComputeInlineRule(aRule: TCSSRuleElement);
+var
+  i: Integer;
+begin
+  if aRule.SelectorCount>0 then
+    exit;
+  for i:=0 to aRule.ChildCount-1 do
+    MergeProperty(aRule.Children[i],CSSSpecifityInline);
+end;
+
+function TCSSResolver.SelectorMatches(aSelector: TCSSElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+
+  procedure MatchPseudo;
+  var
+    aNode: ICSSNode;
+  begin
+    aNode:=TestNode;
+    Result:=SelectorPseudoClassMatches(TCSSPseudoClassElement(aSelector),aNode,OnlySpecifity);
+  end;
+
+var
+  C: TClass;
+begin
+  Result:=CSSSpecifityInvalid;
+  //writeln('TCSSResolver.SelectorMatches ',aSelector.ClassName,' ',TestNode.GetCSSTypeName);
+  C:=aSelector.ClassType;
+  if C=TCSSIdentifierElement then
+    Result:=SelectorIdentifierMatches(TCSSIdentifierElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSHashIdentifierElement then
+    Result:=SelectorHashIdentifierMatches(TCSSHashIdentifierElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSClassNameElement then
+    Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSPseudoClassElement then
+    MatchPseudo
+  else if C=TCSSBinaryElement then
+    Result:=SelectorBinaryMatches(TCSSBinaryElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSArrayElement then
+    Result:=SelectorArrayMatches(TCSSArrayElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSListElement then
+    Result:=SelectorListMatches(TCSSListElement(aSelector),TestNode,OnlySpecifity)
+  else if C=TCSSCallElement then
+    Result:=SelectorCallMatches(TCSSCallElement(aSelector),TestNode,OnlySpecifity)
+  else
+    Log(etWarning,20220908230152,'Unknown CSS selector element',aSelector);
+end;
+
+function TCSSResolver.SelectorIdentifierMatches(
+  Identifier: TCSSIdentifierElement; const TestNode: ICSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  TypeID: TCSSNumericalID;
+begin
+  Result:=CSSSpecifityNoMatch;
+  TypeID:=ResolveIdentifier(Identifier,nikType);
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorIdentifierMatches ',Identifier.Value,' TypeId=',TypeID);
+  {$ENDIF}
+  if TypeID=CSSTypeID_Universal then
+  begin
+    // universal selector
+    Result:=CSSSpecifityUniversal;
+  end else if OnlySpecifity then
+    Result:=CSSSpecifityType
+  else if TypeID=CSSIDNone then
+  begin
+    LogWarning(croErrorOnUnknownName in Options,20220911230224,'Unknown CSS selector type name "'+Identifier.Name+'"',Identifier);
+    Result:=CSSSpecifityInvalid;
+  end else if TypeID=TestNode.GetCSSTypeID then
+    Result:=CSSSpecifityType;
+end;
+
+function TCSSResolver.SelectorHashIdentifierMatches(
+  Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  aValue: TCSSString;
+begin
+  if OnlySpecifity then
+    exit(CSSSpecifityIdentifier);
+  Result:=CSSSpecifityNoMatch;
+  aValue:=Identifier.Value;
+  if TestNode.GetCSSID=aValue then
+    Result:=CSSSpecifityIdentifier;
+end;
+
+function TCSSResolver.SelectorClassNameMatches(
+  aClassName: TCSSClassNameElement; const TestNode: ICSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  aValue: TCSSString;
+begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
+  aValue:=aClassName.Name;
+  if TestNode.HasCSSClass(aValue) then
+    Result:=CSSSpecifityClass
+  else
+    Result:=CSSSpecifityNoMatch;
+  //writeln('TCSSResolver.SelectorClassNameMatches ',aValue,' ',Result);
+end;
+
+function TCSSResolver.SelectorPseudoClassMatches(
+  aPseudoClass: TCSSPseudoClassElement; var TestNode: ICSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  PseudoID: TCSSNumericalID;
+begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
+  Result:=CSSSpecifityNoMatch;
+  PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoClass);
+  case PseudoID of
+  CSSIDNone:
+    LogWarning(croErrorOnUnknownName in Options,20220911205605,'Unknown CSS selector pseudo attribute name "'+aPseudoClass.Name+'"',aPseudoClass);
+  CSSPseudoID_Root:
+    if TestNode.GetCSSParent=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_Empty:
+    if TestNode.GetCSSEmpty then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_FirstChild:
+    if TestNode.GetCSSPreviousSibling=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_LastChild:
+    if TestNode.GetCSSNextSibling=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_OnlyChild:
+    if (TestNode.GetCSSNextSibling=nil)
+        and (TestNode.GetCSSPreviousSibling=nil) then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_FirstOfType:
+    if TestNode.GetCSSPreviousOfType=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_LastOfType:
+    if TestNode.GetCSSNextOfType=nil then
+      Result:=CSSSpecifityClass;
+  CSSPseudoID_OnlyOfType:
+    if (TestNode.GetCSSNextOfType=nil)
+        and (TestNode.GetCSSPreviousOfType=nil) then
+      Result:=CSSSpecifityClass;
+  else
+    if TestNode.HasCSSPseudoClass(PseudoID) then
+      Result:=CSSSpecifityClass;
+  end;
+end;
+
+function TCSSResolver.SelectorListMatches(aList: TCSSListElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  i: Integer;
+  El: TCSSElement;
+  C: TClass;
+  Specifity: TCSSSpecifity;
+  aNode: ICSSNode;
+begin
+  Result:=0;
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorListMatches ChildCount=',aList.ChildCount);
+  {$ENDIF}
+  aNode:=TestNode;
+  for i:=0 to aList.ChildCount-1 do
+    begin
+    El:=aList.Children[i];
+    {$IFDEF VerboseCSSResolver}
+    writeln('TCSSResolver.SelectorListMatches ',i,' ',GetCSSObj(El),' AsString=',El.AsString);
+    {$ENDIF}
+    C:=El.ClassType;
+    if (C=TCSSIdentifierElement) and (i>0) then
+    begin
+      if OnlySpecifity then
+        exit(0);
+      Log(etWarning,20220914163218,'Type selector must be first',aList);
+      exit(CSSSpecifityInvalid);
+    end
+    else if C=TCSSPseudoClassElement then
+    begin
+      Specifity:=SelectorPseudoClassMatches(TCSSPseudoClassElement(El),aNode,OnlySpecifity);
+    end else
+      Specifity:=SelectorMatches(El,aNode,OnlySpecifity);
+    if Specifity<0 then
+      exit(Specifity);
+    inc(Result,Specifity);
+    end;
+end;
+
+function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  aParent, Sibling: ICSSNode;
+  aSpecifity: TCSSSpecifity;
+begin
+  if OnlySpecifity then
+  begin
+    Result:=SelectorMatches(aBinary.Left,TestNode,true);
+    inc(Result,SelectorMatches(aBinary.Right,TestNode,true));
+    exit;
+  end;
+
+  Result:=CSSSpecifityInvalid;
+  case aBinary.Operation of
+  boGT:
+    begin
+      // child combinator >
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
+      if Result<0 then exit;
+      aParent:=TestNode.GetCSSParent;
+      if aParent=nil then
+        exit(CSSSpecifityNoMatch);
+      aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
+      if aSpecifity<0 then
+        exit(aSpecifity);
+      inc(Result,aSpecifity);
+    end;
+  boPlus:
+    begin
+      // adjacent sibling combinator +
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
+      if Result<0 then exit;
+      Sibling:=TestNode.GetCSSPreviousSibling;
+      if Sibling=nil then
+        exit(CSSSpecifityNoMatch);
+      aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
+      if aSpecifity<0 then
+        exit(aSpecifity);
+      inc(Result,aSpecifity);
+    end;
+  boTilde:
+    begin
+      // general sibling combinator ~
+      Result:=SelectorMatches(aBinary.Right,TestNode,false);
+      if Result<0 then exit;
+      Sibling:=TestNode.GetCSSPreviousSibling;
+      while Sibling<>nil do
+      begin
+        aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
+        if aSpecifity=CSSSpecifityInvalid then
+          exit(aSpecifity)
+        else if aSpecifity>=0 then
+        begin
+          inc(Result,aSpecifity);
+          exit;
+        end;
+        Sibling:=Sibling.GetCSSPreviousSibling;
+      end;
+      Result:=CSSSpecifityNoMatch;
+    end;
+  boWhiteSpace:
+    begin
+    // descendant combinator
+    Result:=SelectorMatches(aBinary.Right,TestNode,false);
+    if Result<0 then exit;
+    aParent:=TestNode;
+    repeat
+      aParent:=aParent.GetCSSParent;
+      if aParent=nil then
+        exit(CSSSpecifityNoMatch);
+      aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
+      if aSpecifity>=0 then
+      begin
+        inc(Result,aSpecifity);
+        exit;
+      end
+      else if aSpecifity=CSSSpecifityInvalid then
+        exit(CSSSpecifityInvalid);
+    until false;
+    end
+  else
+    LogWarning(croErrorOnUnknownName in Options,20220910123724,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
+  end;
+end;
+
+function TCSSResolver.SelectorArrayMatches(anArray: TCSSArrayElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  {$IFDEF VerboseCSSResolver}
+  i: integer;
+  {$ENDIF}
+  El: TCSSElement;
+  C: TClass;
+  AttrID: TCSSNumericalID;
+  OldStringComparison: TCSSResStringComparison;
+  aValue: TCSSString;
+begin
+  if OnlySpecifity then
+    exit(CSSSpecifityClass);
+
+  Result:=CSSSpecifityInvalid;
+  if anArray.Prefix<>nil then
+  begin
+    Log(etWarning,20220910154004,'Invalid CSS attribute selector prefix',anArray.Prefix);
+    exit;
+  end;
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorArrayMatches Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
+  for i:=0 to anArray.ChildCount-1 do
+    writeln('TCSSResolver.SelectorArrayMatches ',i,' ',GetCSSObj(anArray.Children[i]));
+  {$ENDIF}
+  if anArray.ChildCount<1 then
+  begin
+    Log(etWarning,20220910154033,'Invalid CSS attribute selector',anArray);
+    exit;
+  end;
+  OldStringComparison:=StringComparison;
+  try
+    if anArray.ChildCount>1 then
+    begin
+      El:=anArray.Children[1];
+      C:=El.ClassType;
+      if C=TCSSIdentifierElement then
+      begin
+        aValue:=TCSSIdentifierElement(El).Value;
+        case aValue of
+        'i': FStringComparison:=crscCaseInsensitive;
+        's': FStringComparison:=crscCaseSensitive;
+        else
+          LogWarning(croErrorOnUnknownName in Options,20220914174409,'Invalid attribute modifier "'+aValue+'"',El);
+          exit;
+        end;
+      end else begin
+        Log(etWarning,20220914173643,'Invalid CSS attribute modifier',El);
+        exit;
+      end;
+    end;
+    if (anArray.ChildCount>2) then
+      Log(etWarning,20220914174550,'Invalid CSS attribute modifier',anArray.Children[2]);
+
+    El:=anArray.Children[0];
+    C:=El.ClassType;
+    if C=TCSSIdentifierElement then
+    begin
+      // [name]  ->  has attribute name
+      AttrID:=ResolveIdentifier(TCSSIdentifierElement(El),nikAttribute);
+      case AttrID of
+      CSSIDNone:
+        Result:=CSSSpecifityNoMatch;
+      CSSAttributeID_ID,
+      CSSAttributeID_Class:
+        // basic CSS attributes are always defined
+        Result:=CSSSpecifityClass;
+      CSSAttributeID_All:
+        // special CSS attributes without a value
+        Result:=CSSSpecifityNoMatch;
+      else
+        if TestNode.HasCSSAttribute(AttrID) then
+          Result:=CSSSpecifityClass
+        else
+          Result:=CSSSpecifityNoMatch;
+      end;
+    end else if C=TCSSBinaryElement then
+      Result:=SelectorArrayBinaryMatches(TCSSBinaryElement(El),TestNode)
+    else begin
+      LogWarning(croErrorOnUnknownName in Options,20220910153725,'Invalid CSS array selector',El);
+    end;
+  finally
+    FStringComparison:=OldStringComparison;
+  end;
+end;
+
+function TCSSResolver.SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement;
+  const TestNode: ICSSNode): TCSSSpecifity;
+var
+  Left, Right: TCSSElement;
+  AttrID: TCSSNumericalID;
+  LeftValue, RightValue: TCSSString;
+  C: TClass;
+begin
+  Result:=CSSSpecifityNoMatch;
+  Left:=aBinary.Left;
+  if Left.ClassType<>TCSSIdentifierElement then
+    Log(etError,20220910164353,'Invalid CSS array selector, expected attribute',Left);
+  AttrID:=ResolveIdentifier(TCSSIdentifierElement(Left),nikAttribute);
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorArrayBinaryMatches AttrID=',AttrID,' Value=',TCSSIdentifierElement(Left).Value);
+  {$ENDIF}
+  case AttrID of
+  CSSIDNone: exit(CSSSpecifityNoMatch);
+  CSSAttributeID_ID:
+    LeftValue:=TestNode.GetCSSID;
+  CSSAttributeID_Class:
+    LeftValue:=TestNode.GetCSSAttributeClass;
+  CSSAttributeID_All: exit(CSSSpecifityNoMatch);
+  else
+    LeftValue:=TestNode.GetCSSAttribute(AttrID);
+  end;
+
+  Right:=aBinary.Right;
+  C:=Right.ClassType;
+  if (C=TCSSStringElement) or (C=TCSSIntegerElement) or (C=TCSSFloatElement)
+      or (C=TCSSIdentifierElement) then
+    // ok
+  else
+    Log(etError,20220910164921,'Invalid CSS array selector, expected string',Right);
+  RightValue:=ComputeValue(Right);
+
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorArrayBinaryMatches Left="',LeftValue,'" Right="',RightValue,'" Op=',aBinary.Operation);
+  {$ENDIF}
+  case aBinary.Operation of
+  boEquals:
+    if SameValueText(LeftValue,RightValue) then
+      Result:=CSSSpecifityClass;
+  boSquaredEqual:
+    // begins with
+    if (RightValue<>'') and SameValueText(LeftStr(LeftValue,length(RightValue)),RightValue) then
+      Result:=CSSSpecifityClass;
+  boDollarEqual:
+    // ends with
+    if (RightValue<>'') and SameValueText(RightStr(LeftValue,length(RightValue)),RightValue) then
+      Result:=CSSSpecifityClass;
+  boPipeEqual:
+    // equal to or starts with name-hyphen
+    if (RightValue<>'')
+        and (SameValueText(LeftValue,RightValue)
+          or SameValueText(LeftStr(LeftValue,length(RightValue)+1),RightValue+'-')) then
+      Result:=CSSSpecifityClass;
+  boStarEqual:
+    // contains substring
+    if (RightValue<>'') and (Pos(RightValue,LeftValue)>0) then
+      Result:=CSSSpecifityClass;
+  boTildeEqual:
+    // contains word
+    if PosWord(RightValue,LeftValue)>0 then
+      Result:=CSSSpecifityClass;
+  else
+    LogWarning(croErrorOnUnknownName in Options,20220910164356,'Invalid CSS array selector operator',aBinary);
+    Result:=CSSSpecifityInvalid;
+  end;
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolver.SelectorArrayBinaryMatches Result=',Result);
+  {$ENDIF}
+end;
+
+function TCSSResolver.SelectorCallMatches(aCall: TCSSCallElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  CallID: TCSSNumericalID;
+begin
+  Result:=CSSSpecifityNoMatch;
+  CallID:=ResolveCall(aCall);
+  case CallID of
+  CSSCallID_Not:
+    Result:=Call_Not(aCall,TestNode,OnlySpecifity);
+  CSSCallID_Is:
+    Result:=Call_Is(aCall,TestNode,OnlySpecifity);
+  CSSCallID_Where:
+    Result:=Call_Where(aCall,TestNode,OnlySpecifity);
+  CSSCallID_NthChild,CSSCallID_NthLastChild,CSSCallID_NthOfType, CSSCallID_NthLastOfType:
+    Result:=Call_NthChild(CallID,aCall,TestNode,OnlySpecifity);
+  else
+    if OnlySpecifity then
+      Result:=0
+    else
+      Result:=CSSSpecifityInvalid;
+  end;
+end;
+
+function TCSSResolver.Call_Not(aCall: TCSSCallElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+// :not(arg1, arg2, ...)
+// :not(args) has the same specifity as :not(:is(args))
+var
+  i: Integer;
+  Specifity: TCSSSpecifity;
+  HasMatch: Boolean;
+begin
+  Result:=0;
+  HasMatch:=false;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
+    if Specifity>=0 then
+      HasMatch:=true
+    else begin
+      // the specifity of :is is the highest, independent of matching (forgiving)
+      Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
+    end;
+    if Specifity>Result then
+      Result:=Specifity;
+  end;
+  if OnlySpecifity then
+    // return best
+  else if HasMatch then
+    Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_Is(aCall: TCSSCallElement; const TestNode: ICSSNode;
+  OnlySpecifity: boolean): TCSSSpecifity;
+var
+  i: Integer;
+  Specifity: TCSSSpecifity;
+  ok: Boolean;
+begin
+  Result:=0;
+  ok:=false;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
+    if Specifity>=0 then
+      ok:=true
+    else begin
+      // the specifity of :is is the highest, independent of matching (forgiving)
+      Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
+    end;
+    if Specifity>Result then
+      Result:=Specifity;
+  end;
+  if (not ok) and (not OnlySpecifity) then
+    Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_Where(aCall: TCSSCallElement;
+  const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
+var
+  i: Integer;
+begin
+  Result:=0;
+  if OnlySpecifity then
+    exit;
+  for i:=0 to aCall.ArgCount-1 do
+  begin
+    if SelectorMatches(aCall.Args[i],TestNode,false)>=0 then
+      // Note: :where is forgiving, so invalid arguments are ignored
+      exit;
+  end;
+  Result:=CSSSpecifityNoMatch;
+end;
+
+function TCSSResolver.Call_NthChild(CallID: TCSSNumericalID;
+  aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean
+  ): TCSSSpecifity;
+
+  procedure NthWarn(const ID: TCSSMsgID; const Msg: string; PosEl: TCSSElement);
+  begin
+    Log(etWarning,ID,CSSPseudoNames[CallID]+' '+Msg,PosEl);
+  end;
+
+var
+  i, ArgCount, aModulo, aStart: Integer;
+  Arg, OffsetEl: TCSSElement;
+  Str: TCSSString;
+  UnaryEl, anUnary: TCSSUnaryElement;
+  Params: TCSSCallNthChildParams;
+  CallData: TCSSCallData;
+  ChildIDs: TIntegerDynArray;
+begin
+  if OnlySpecifity then
+    Result:=CSSSpecifityClass
+  else
+    Result:=CSSSpecifityInvalid;
+  CallData:=TCSSCallData(aCall.CustomData);
+  Params:=TCSSCallNthChildParams(CallData.Params);
+  if Params=nil then
+  begin
+    ArgCount:=aCall.ArgCount;
+    {$IFDEF VerboseCSSResolver}
+    writeln('TCSSResolver.Call_NthChild ',aCall.ArgCount);
+    for i:=0 to aCall.ArgCount-1 do
+      writeln('TCSSResolver.Call_NthChild ',i,' ',GetCSSObj(aCall.Args[i]),' AsString=',aCall.Args[i].AsString);
+    {$ENDIF}
+    // An+B[of S], odd, even, An
+
+    i:=0;
+    aModulo:=0;
+    aStart:=1;
+    // check step
+    if ArgCount<=i then
+    begin
+      NthWarn(20220915143843,'missing arguments',aCall);
+      exit;
+    end;
+    Arg:=aCall.Args[i];
+    if Arg.ClassType=TCSSIntegerElement then
+    begin
+      aModulo:=TCSSIntegerElement(Arg).Value;
+      inc(i);
+      // check n
+      if ArgCount<=i then
+      begin
+        NthWarn(20220915143843,'missing arguments',aCall);
+        exit;
+      end;
+      Arg:=aCall.Args[i];
+      if Arg.ClassType<>TCSSIdentifierElement then
+      begin
+        NthWarn(20220915144312,'expected n',Arg);
+        exit;
+      end;
+      if TCSSIdentifierElement(Arg).Value<>'n' then
+      begin
+        NthWarn(20220915144359,'expected n',Arg);
+        exit;
+      end;
+
+    end
+    else if Arg.ClassType=TCSSIdentifierElement then
+    begin
+      Str:=TCSSIdentifierElement(Arg).Value;
+      case lowercase(Str) of
+      'even':
+        begin
+        //writeln('TCSSResolver.Call_NthChild EVEN');
+        aModulo:=2;
+        aStart:=2;
+        end;
+      'odd':
+        begin
+        //writeln('TCSSResolver.Call_NthChild ODD');
+        aModulo:=2;
+        end;
+      'n':
+        begin
+        //writeln('TCSSResolver.Call_NthChild N');
+        aModulo:=1;
+        end;
+      else
+        NthWarn(20220915150332,'expected multiplier',Arg);
+        exit;
+      end
+    end else if Arg.ClassType=TCSSUnaryElement then
+    begin
+      anUnary:=TCSSUnaryElement(Arg);
+      case anUnary.Operation of
+      uoMinus: aModulo:=-1;
+      uoPlus: aModulo:=1;
+      else
+        NthWarn(20220917080309,'expected multiplier',Arg);
+        exit;
+      end;
+      if (anUnary.Right.ClassType=TCSSIdentifierElement)
+          and (SameText(TCSSIdentifierElement(anUnary.Right).Value,'n')) then
+      begin
+        // ok
+      end else begin
+        NthWarn(20220917080154,'expected multiplier',Arg);
+        exit;
+      end;
+    end else
+    begin
+      NthWarn(20220915144056,'expected multiplier',Arg);
+      exit;
+    end;
+
+    inc(i);
+    if ArgCount>i then
+    begin
+      Arg:=aCall.Args[i];
+      if Arg.ClassType=TCSSUnaryElement then
+      begin
+        UnaryEl:=TCSSUnaryElement(Arg);
+        //writeln('TCSSResolver.Call_NthChild UNARY ',UnaryEl.AsString);
+        if not (UnaryEl.Operation in [uoMinus,uoPlus]) then
+        begin
+          NthWarn(20220915151422,'unexpected offset',UnaryEl);
+          exit;
+        end;
+        OffsetEl:=UnaryEl.Right;
+        if OffsetEl=nil then
+        begin
+          NthWarn(20220915151511,'unexpected offset',UnaryEl);
+          exit;
+        end;
+        if OffsetEl.ClassType<>TCSSIntegerElement then
+        begin
+          NthWarn(20220915151718,'unexpected offset',OffsetEl);
+          exit;
+        end;
+        aStart:=TCSSIntegerElement(OffsetEl).Value;
+        if UnaryEl.Operation=uoMinus then
+          aStart:=-aStart;
+      end else
+      begin
+        NthWarn(20220915150851,'expected offset',Arg);
+        exit;
+      end;
+    end;
+
+    Params:=TCSSCallNthChildParams.Create;
+    CallData.Params:=Params;
+    Params.Modulo:=aModulo;
+    Params.Start:=aStart;
+
+    inc(i);
+    if (i<ArgCount) then
+    begin
+      Arg:=aCall.Args[i];
+      if (Arg.ClassType=TCSSIdentifierElement)
+          and (SameText(TCSSIdentifierElement(Arg).Value,'of')) then
+      begin
+        // An+B of Selector
+        inc(i);
+        if i=ArgCount then
+        begin
+          NthWarn(20220915150851,'expected selector',Arg);
+          exit;
+        end;
+        Arg:=aCall.Args[i];
+        Params.HasOf:=true;
+        Params.OfSelector:=Arg;
+      end;
+    end;
+
+    if (CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType]) then
+      Params.HasOf:=true;
+  end else begin
+    aModulo:=Params.Modulo;
+    aStart:=Params.Start;
+  end;
+
+  if OnlySpecifity then
+  begin
+    if Params.OfSelector<>nil then
+      inc(Result,SelectorMatches(Params.OfSelector,TestNode,true));
+    exit;
+  end;
+
+  Result:=CSSSpecifityNoMatch;
+  if aModulo=0 then
+    exit;
+  i:=TestNode.GetCSSIndex;
+  if Params.HasOf then
+  begin
+    ChildIDs:=CollectSiblingsOf(CallID,TestNode,Params);
+    i:=GetSiblingOfIndex(ChildIDs,i);
+  end else
+    ChildIDs:=nil;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.Call_NthChild CallID=',CallID,' ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex,' i=',i,' HasOf=',Params.HasOf,' OfChildCount=',length(Params.ChildIDs));
+  {$ENDIF}
+  if i<0 then
+    exit;
+  if CallID in [CSSCallID_NthLastChild,CSSCallID_NthLastOfType] then
+  begin
+    if Params.HasOf then
+      i:=length(ChildIDs)-i
+    else
+      i:=GetSiblingCount(TestNode)-i;
+  end else
+  begin
+    i:=i+1;
+  end;
+  dec(i,aStart);
+  if i mod aModulo = 0 then
+  begin
+    i:=i div aModulo;
+    if i>=0 then
+      Result:=CSSSpecifityClass;
+  end;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.Call_NthChild ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex+1,' Result=',Result);
+  {$ENDIF}
+end;
+
+function TCSSResolver.CollectSiblingsOf(CallID: TCSSNumericalID;
+  TestNode: ICSSNode; Params: TCSSCallNthChildParams): TIntegerDynArray;
+var
+  i, Depth, ChildCount, j: Integer;
+  aTypeID: TCSSNumericalID;
+  aParent, aNode: ICSSNode;
+  aSelector: TCSSElement;
+  StackDepth: SizeInt;
+  Cache: TCSSCallNthChildParamsCache;
+  Item: PCSSCallNthChildParamsCacheItem;
+  NeedTypeID: Boolean;
+begin
+  Result:=nil;
+  aParent:=TestNode.GetCSSParent;
+  {$IFDEF VerboseCSSResolver}
+  //writeln('TCSSResolver.CollectSiblingsOf HasParent=',aParent<>nil);
+  {$ENDIF}
+  if aParent=nil then exit;
+  ChildCount:=aParent.GetCSSChildCount;
+  if ChildCount=0 then exit;
+
+  Depth:=aParent.GetCSSDepth;
+  StackDepth:=length(Params.StackCache);
+  if StackDepth<=Depth then
+  begin
+    SetLength(Params.StackCache,Depth+1);
+    for i:=StackDepth to Depth do
+      Params.StackCache[i]:=nil;
+  end;
+  Cache:=Params.StackCache[Depth];
+  if Cache=nil then
+  begin
+    Cache:=TCSSCallNthChildParamsCache.Create;
+    Params.StackCache[Depth]:=Cache;
+    Cache.Owner:=Params;
+    Cache.StackDepth:=Depth;
+  end;
+
+  NeedTypeID:=CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType];
+
+  if Cache.Parent<>aParent then
+  begin
+    // build cache
+    Cache.Parent:=aParent;
+    SetLength(Cache.Items,0);
+    {$IFDEF VerboseCSSResolver}
+    writeln('TCSSResolver.CollectSiblingsOf Depth=',Depth,' Candidates=',ChildCount);
+    {$ENDIF}
+    aSelector:=Params.OfSelector;
+    for i:=0 to ChildCount-1 do
+    begin
+      aNode:=aParent.GetCSSChild(i);
+      if (aSelector<>nil) and (SelectorMatches(aSelector,aNode,false)<0) then
+        continue;
+
+      if NeedTypeID then
+        aTypeID:=aNode.GetCSSTypeID
+      else
+        aTypeID:=0;
+      j:=length(Cache.Items)-1;
+      while (j>=0) and (Cache.Items[j].TypeID<>aTypeID) do dec(j);
+      if j<0 then
+      begin
+        j:=length(Cache.Items);
+        SetLength(Cache.Items,j+1);
+        Item:[email protected][j];
+        Item^.TypeID:=aTypeID;
+        Item^.Cnt:=0;
+        SetLength(Item^.ChildIDs,ChildCount);
+      end else
+        Item:[email protected][j];
+      Item^.ChildIDs[Item^.Cnt]:=i;
+      {$IFDEF VerboseCSSResolver}
+      writeln('TCSSResolver.CollectSiblingsOf Sel=',GetCSSObj(aSelector),' CSSTypeID=',aNode.GetCSSTypeID,' ',Item^.Cnt,'=>',i);
+      {$ENDIF}
+      inc(Item^.Cnt);
+    end;
+
+    for i:=0 to high(Cache.Items) do
+      with Cache.Items[i] do
+        SetLength(ChildIDs,Cnt);
+  end;
+
+  // use cache
+  if NeedTypeID then
+  begin
+    aTypeID:=TestNode.GetCSSTypeID;
+    for i:=0 to high(Cache.Items) do
+      if Cache.Items[i].TypeID=aTypeID then
+        exit(Cache.Items[i].ChildIDs);
+  end else
+    Result:=Cache.Items[0].ChildIDs;
+end;
+
+function TCSSResolver.GetSiblingOfIndex(SiblingIDs: TIntegerDynArray;
+  Index: integer): integer;
+// searches the position of Index in a sorted array
+var
+  l, r, m: Integer;
+begin
+  l:=0;
+  r:=length(SiblingIDs)-1;
+  while l<=r do
+  begin
+    m:=(l+r) div 2;
+    Result:=SiblingIDs[m];
+    if Index<Result then
+      r:=m-1
+    else if Index>Result then
+      l:=m+1
+    else
+      exit(m);
+  end;
+  Result:=-1;
+end;
+
+function TCSSResolver.ComputeValue(El: TCSSElement): TCSSString;
+var
+  ElData: TObject;
+  C: TClass;
+  StrEl: TCSSStringElement;
+  IntEl: TCSSIntegerElement;
+  FloatEl: TCSSFloatElement;
+begin
+  C:=El.ClassType;
+  if C=TCSSIdentifierElement then
+    Result:=TCSSIdentifierElement(El).Value
+  else if (C=TCSSStringElement)
+      or (C=TCSSIntegerElement)
+      or (C=TCSSFloatElement) then
+  begin
+    ElData:=El.CustomData;
+    if ElData is TCSSValueData then
+      exit(TCSSValueData(ElData).NormValue);
+    if C=TCSSStringElement then
+    begin
+      StrEl:=TCSSStringElement(El);
+      Result:=StrEl.Value;
+      {$IFDEF VerboseCSSResolver}
+      writeln('TCSSResolver.ComputeValue String=[',Result,']');
+      {$ENDIF}
+    end
+    else if C=TCSSIntegerElement then
+    begin
+      IntEl:=TCSSIntegerElement(El);
+      Result:=IntEl.AsString;
+      {$IFDEF VerboseCSSResolver}
+      writeln('TCSSResolver.ComputeValue Integer=[',Result,']');
+      {$ENDIF}
+    end else if C=TCSSFloatElement then
+    begin
+      FloatEl:=TCSSFloatElement(El);
+      Result:=FloatEl.AsString;
+      {$IFDEF VerboseCSSResolver}
+      writeln('TCSSResolver.ComputeValue Float=[',Result,']');
+      {$ENDIF}
+    end;
+    AddElValueData(El,Result);
+  end else begin
+    LogWarning(croErrorOnUnknownName in Options,20220910235106,'TCSSResolver.ComputeValue not supported',El);
+  end;
+end;
+
+function TCSSResolver.SameValueText(const A, B: TCSSString): boolean;
+begin
+  if StringComparison=crscCaseInsensitive then
+    Result:=SameText(A,B)
+  else
+    Result:=A=B;
+end;
+
+function TCSSResolver.SameValueText(A: PAnsiChar; ALen: integer; B: PAnsiChar;
+  BLen: integer): boolean;
+var
+  AC, BC: AnsiChar;
+  i: Integer;
+begin
+  if ALen<>BLen then exit(false);
+  if ALen=0 then exit(true);
+  if StringComparison=crscCaseInsensitive then
+  begin
+    for i:=0 to ALen-1 do
+    begin
+      AC:=A^;
+      BC:=B^;
+      if (AC<>BC) and (UpCase(AC)<>UpCase(BC)) then
+        exit(false);
+      inc(A);
+      inc(B);
+    end;
+    Result:=true;
+  end else
+    Result:=CompareMem(A,B,ALen);
+end;
+
+function TCSSResolver.PosSubString(const SearchStr, Str: TCSSString): integer;
+var
+  SearchLen: SizeInt;
+  i: Integer;
+  SearchP, StrP: PAnsiChar;
+  AC, BC: AnsiChar;
+begin
+  Result:=0;
+  if SearchStr='' then exit;
+  if Str='' then exit;
+  if StringComparison=crscCaseInsensitive then
+  begin
+    SearchP:=PAnsiChar(SearchStr);
+    StrP:=PAnsiChar(Str);
+    SearchLen:=length(SearchStr);
+    AC:=SearchP^;
+    for i:=0 to length(Str)-SearchLen do
+    begin
+      BC:=StrP^;
+      if (upcase(AC)=upcase(BC)) and SameValueText(SearchP,SearchLen,StrP,SearchLen) then
+        exit(i+1);
+      inc(StrP);
+    end;
+  end else begin
+    Result:=Pos(SearchStr,Str);
+  end;
+end;
+
+function TCSSResolver.PosWord(const SearchWord, Words: TCSSString): integer;
+// attribute selector ~=
+const
+  Whitespace = [#9,#10,#12,#13,' '];
+var
+  WordsLen, SearchLen: SizeInt;
+  p, WordStart: Integer;
+begin
+  Result:=0;
+  if SearchWord='' then exit;
+  if Words='' then exit;
+  WordsLen:=length(Words);
+  SearchLen:=length(SearchWord);
+  //writeln('TCSSResolver.PosWord "',SearchWord,'" Words="',words,'"');
+  p:=1;
+  repeat
+    repeat
+      if p>WordsLen then
+        exit(0);
+      if not (Words[p] in Whitespace) then
+        break;
+      inc(p);
+    until false;
+    WordStart:=p;
+    while (p<=WordsLen) and not (Words[p] in Whitespace) do
+      inc(p);
+    //writeln('TCSSResolver.PosWord start=',WordStart,' p=',p);
+    if SameValueText(@SearchWord[1],SearchLen,@Words[WordStart],p-WordStart) then
+      exit(WordStart);
+  until p>WordsLen;
+end;
+
+function TCSSResolver.GetSiblingCount(aNode: ICSSNode): integer;
+var
+  aParent, CurNode: ICSSNode;
+begin
+  if aNode=nil then
+    exit(0);
+  aParent:=aNode.GetCSSParent;
+  if aParent<>nil then
+    exit(aParent.GetCSSChildCount);
+  Result:=0;
+  CurNode:=aNode;
+  while CurNode<>nil do
+  begin
+    inc(Result);
+    CurNode:=CurNode.GetCSSPreviousSibling;
+  end;
+  CurNode:=aNode.GetCSSNextSibling;
+  while CurNode<>nil do
+  begin
+    inc(Result);
+    CurNode:=CurNode.GetCSSNextSibling;
+  end;
+end;
+
+procedure TCSSResolver.MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity);
+var
+  C: TClass;
+  Decl: TCSSDeclarationElement;
+  aKey, aValue: TCSSElement;
+  AttrID: TCSSNumericalID;
+  CompAttr: PCSSComputedAttribute;
+begin
+  C:=El.ClassType;
+  if C=TCSSDeclarationElement then
+  begin
+    Decl:=TCSSDeclarationElement(El);
+    if Decl.KeyCount<>1 then begin
+      Log(etWarning,20220908232213,'Not yet implemented CSS declaration with KeyCount='+IntToStr(Decl.KeyCount),El);
+      exit;
+    end;
+    if Decl.ChildCount<>1 then begin
+      Log(etWarning,20220908232324,'Not yet implemented CSS declaration with ChildCount='+IntToStr(Decl.ChildCount),El);
+      exit;
+    end;
+
+    aKey:=Decl.Keys[0];
+    aValue:=Decl.Children[0];
+    if Decl.IsImportant then
+      Specifity:=CSSSpecifityImportant;
+
+    C:=aKey.ClassType;
+    if C=TCSSIdentifierElement then
+    begin
+      AttrID:=ResolveIdentifier(TCSSIdentifierElement(aKey),nikAttribute);
+      if AttrID=CSSIDNone then
+        Log(etWarning,20220909000932,'Unknown CSS property "'+TCSSIdentifierElement(aKey).Name+'"',aKey)
+      else if AttrID=CSSAttributeID_All then
+        // 'all'
+        Log(etWarning,20220909001019,'Not yet implemented CSS property "'+TCSSIdentifierElement(aKey).Name+'"',aKey)
+      else begin
+        // set property
+        CompAttr:=FindComputedAttribute(AttrID);
+        if CompAttr<>nil then
+        begin
+          if CompAttr^.Specifity>Specifity then
+            exit;
+          if not CheckAttrValueValidity(AttrID,aKey,aValue) then
+            exit;
+          CompAttr^.Specifity:=Specifity;
+          CompAttr^.Value:=aValue;
+        end else begin
+          if not CheckAttrValueValidity(AttrID,aKey,aValue) then
+            exit;
+          AddComputedAttribute(AttrID,Specifity,aValue);
+        end;
+      end;
+    end else
+      Log(etWarning,20220908232359,'Unknown CSS key',aKey);
+  end else
+    Log(etWarning,20220908230855,'Unknown CSS property',El);
+end;
+
+function TCSSResolver.CheckAttrValueValidity(AttrID: TCSSNumericalID; aKey,
+  aValue: TCSSElement): boolean;
+var
+  Data: TCSSIdentifierData;
+begin
+  if not (aKey.CustomData is TCSSIdentifierData) then
+    raise Exception.Create('TCSSResolver.CheckAttrValueValidity 20221019173901');
+  Data:=TCSSIdentifierData(aKey.CustomData);
+  case Data.ValueValid of
+  cvvValid: exit(true);
+  cvvInvalid: exit(false);
+  end;
+  Result:=FNode.CheckCSSValue(AttrID,aValue);
+  if Result then
+    Data.ValueValid:=cvvValid
+  else
+    Data.ValueValid:=cvvInvalid;
+end;
+
+function TCSSResolver.ResolveIdentifier(El: TCSSIdentifierElement;
+  Kind: TCSSNumericalIDKind): TCSSNumericalID;
+var
+  Data: TObject;
+  IdentData: TCSSIdentifierData;
+  aName: TCSSString;
+begin
+  Data:=El.CustomData;
+  if Data<>nil then
+  begin
+    IdentData:=TCSSIdentifierData(Data);
+    Result:=IdentData.NumericalID;
+    {$IFDEF VerboseCSSResolver}
+    if IdentData.Kind<>Kind then
+      Log(etError,20220908235300,'TCSSResolver.ResolveIdentifier',El);
+    {$ENDIF}
+  end else
+  begin
+    aName:=El.Name;
+    Result:=CSSIDNone;
+
+    // check built-in names
+    case Kind of
+    nikType:
+      case aName of
+      '*': Result:=CSSTypeID_Universal;
+      end;
+    nikAttribute:
+      case aName of
+      'id': Result:=CSSAttributeID_ID;
+      'class': Result:=CSSAttributeID_Class;
+      'all': Result:=CSSAttributeID_All;
+      end;
+    nikPseudoClass:
+      begin
+      aName:=lowercase(aName); // pseudo attributes are ASCII case insensitive
+      case aName of
+      ':root': Result:=CSSPseudoID_Root;
+      ':empty': Result:=CSSPseudoID_Empty;
+      ':first-child': Result:=CSSPseudoID_FirstChild;
+      ':last-child': Result:=CSSPseudoID_LastChild;
+      ':only-child': Result:=CSSPseudoID_OnlyChild;
+      ':first-of-type': Result:=CSSPseudoID_FirstOfType;
+      ':last-of-type': Result:=CSSPseudoID_LastOfType;
+      ':only-of-type': Result:=CSSPseudoID_OnlyOfType;
+      end;
+      end;
+    end;
+
+    // resolve user defined names
+    //writeln('TCSSResolver.ResolveIdentifier ',Kind,' "',aName,'"');
+    if Result=CSSIDNone then
+      Result:=FNumericalIDs[Kind][aName];
+
+    if Result=CSSIDNone then
+    begin
+      LogWarning(croErrorOnUnknownName in FOptions,20220908235919,'TCSSResolver.ResolveIdentifier unknown '+CSSNumericalIDKindNames[Kind]+' "'+El.Name+'"',El);
+      exit;
+    end;
+    IdentData:=TCSSIdentifierData.Create;
+    IdentData.Kind:=Kind;
+    IdentData.NumericalID:=Result;
+    AddElData(El,IdentData);
+  end;
+end;
+
+function TCSSResolver.ResolveCall(El: TCSSCallElement): TCSSNumericalID;
+var
+  Data: TObject;
+  CallData: TCSSCallData;
+  aName: TCSSString;
+begin
+  Data:=El.CustomData;
+  if Data<>nil then
+  begin
+    CallData:=TCSSCallData(Data);
+    Result:=CallData.NumericalID;
+  end else
+  begin
+    aName:=El.Name;
+    Result:=CSSIDNone;
+
+    case aName of
+    ':not': Result:=CSSCallID_Not;
+    ':is': Result:=CSSCallID_Is;
+    ':where': Result:=CSSCallID_Where;
+    ':has': Result:=CSSCallID_Has;
+    ':nth-child': Result:=CSSCallID_NthChild;
+    ':nth-last-child': Result:=CSSCallID_NthLastChild;
+    ':nth-of-type': Result:=CSSCallID_NthOfType;
+    ':nth-last-of-type': Result:=CSSCallID_NthLastOfType;
+    else
+      LogWarning(croErrorOnUnknownName in FOptions,20220914193946,'TCSSResolver.ResolveCall unknown "'+El.Name+'"',El);
+      exit;
+    end;
+    CallData:=TCSSCallData.Create;
+    CallData.NumericalID:=Result;
+    AddElData(El,CallData);
+  end;
+end;
+
+procedure TCSSResolver.AddElData(El: TCSSElement; ElData: TCSSElResolverData);
+begin
+  El.CustomData:=ElData;
+  ElData.Element:=El;
+  if FFirstElData=nil then
+  begin
+    FFirstElData:=ElData;
+  end else begin
+    FLastElData.Next:=ElData;
+    ElData.Prev:=FLastElData;
+  end;
+  FLastElData:=ElData;
+end;
+
+function TCSSResolver.AddElValueData(El: TCSSElement; const aValue: TCSSString
+  ): TCSSValueData;
+begin
+  Result:=TCSSValueData.Create;
+  Result.NormValue:=aValue;
+  AddElData(El,Result);
+end;
+
+function TCSSResolver.FindComputedAttribute(AttrID: TCSSNumericalID
+  ): PCSSComputedAttribute;
+var
+  i: Integer;
+begin
+  for i:=0 to FAttributeCount-1 do
+    if FAttributes[i].AttrID=AttrID then
+      exit(@FAttributes[i]);
+  Result:=nil;
+end;
+
+function TCSSResolver.AddComputedAttribute(TheAttrID: TCSSNumericalID;
+  aSpecifity: TCSSSpecifity; aValue: TCSSElement): PCSSComputedAttribute;
+var
+  NewLength: Integer;
+begin
+  if FAttributeCount=length(FAttributes) then
+  begin
+    NewLength:=FAttributeCount*2;
+    if NewLength<16 then
+      NewLength:=16;
+    SetLength(FAttributes,NewLength);
+  end;
+  with FAttributes[FAttributeCount] do
+  begin
+    AttrID:=TheAttrID;
+    Specifity:=aSpecifity;
+    Value:=aValue;
+  end;
+  Result:=@FAttributes[FAttributeCount];
+  inc(FAttributeCount);
+end;
+
+procedure TCSSResolver.LogWarning(IsError: boolean; const ID: TCSSMsgID;
+  Msg: string; PosEl: TCSSElement);
+var
+  MsgType: TEventType;
+begin
+  if IsError then
+    MsgType:=etError
+  else
+    MsgType:=etWarning;
+  Log(MsgType,ID,Msg,PosEl);
+end;
+
+procedure TCSSResolver.Log(MsgType: TEventType; const ID: TCSSMsgID;
+  Msg: string; PosEl: TCSSElement);
+var
+  Entry: TCSSResolverLogEntry;
+  i: Integer;
+begin
+  if Assigned(OnLog) then
+  begin
+    for i:=0 to FLogEntries.Count-1 do
+    begin
+      Entry:=LogEntries[i];
+      if (Entry.PosEl=PosEl)
+          and (Entry.ID=ID)
+          and (Entry.MsgType=MsgType)
+          and (Entry.Msg=Msg) then
+        exit; // this warning was already logged
+    end;
+    Entry:=TCSSResolverLogEntry.Create;
+    Entry.MsgType:=MsgType;
+    Entry.ID:=ID;
+    Entry.Msg:=Msg;
+    Entry.PosEl:=PosEl;
+    FLogEntries.Add(Entry);
+    OnLog(Self,Entry);
+  end;
+  if (MsgType=etError) or (FOnLog=nil) then
+  begin
+    Msg:='['+IntToStr(ID)+'] '+Msg+' at '+GetElPos(PosEl);
+    raise ECSSResolver.Create(Msg);
+  end;
+end;
+
+function TCSSResolver.GetElPos(El: TCSSElement): string;
+begin
+  if El=nil then
+    Result:='no element'
+  else begin
+    Result:=El.SourceFileName+'('+IntToStr(El.SourceCol)+','+IntToStr(El.SourceCol)+')';
+    {$IFDEF VerboseCSSResolver}
+    Result:='['+GetElPath(El)+']'+Result;
+    {$ENDIF}
+  end;
+end;
+
+function TCSSResolver.GetElPath(El: TCSSElement): string;
+begin
+  Result:=GetCSSPath(El);
+end;
+
+constructor TCSSResolver.Create(AOwner: TComponent);
+begin
+  inherited;
+  FLogEntries:=TFPObjectList.Create(true);
+end;
+
+destructor TCSSResolver.Destroy;
+begin
+  Clear;
+  FreeAndNil(FLogEntries);
+  inherited Destroy;
+end;
+
+procedure TCSSResolver.Clear;
+begin
+  FLogEntries.Clear;
+  ClearStyleCustomData;
+  ClearStyles;
+end;
+
+procedure TCSSResolver.ClearStyleCustomData;
+var
+  Data: TCSSElResolverData;
+begin
+  while FLastElData<>nil do
+  begin
+    Data:=FLastElData;
+    FLastElData:=Data.Prev;
+    if FLastElData<>nil then
+      FLastElData.Next:=nil
+    else
+      FFirstElData:=nil;
+    if Data.Element.CustomData<>Data then
+      Log(etError,20220908234726,'TCSSResolver.ClearStyleCustomData',Data.Element);
+    Data.Element.CustomData:=nil;
+    Data.Free;
+  end;
+end;
+
+procedure TCSSResolver.Compute(Node: ICSSNode; NodeStyle: TCSSElement;
+  const CompOptions: TCSSComputeOptions);
+var
+  i: Integer;
+begin
+  FNode:=Node;
+  try
+    FAttributeCount:=0;
+    for i:=0 to high(FStyles) do
+      ComputeElement(Styles[i]);
+    ComputeInline(NodeStyle);
+    if ccoCommit in CompOptions then
+      Commit;
+  finally
+    FNode:=nil;
+  end;
+end;
+
+procedure TCSSResolver.Commit;
+var
+  i: Integer;
+begin
+  //writeln('TCSSResolver.Commit FAttributeCount=',FAttributeCount);
+  for i:=0 to FAttributeCount-1 do
+    with FAttributes[i] do
+      FNode.SetCSSValue(AttrID,Value);
+end;
+
+procedure TCSSResolver.AddStyle(aStyle: TCSSElement);
+begin
+  if aStyle=nil then exit;
+  Insert(aStyle,FStyles,length(FStyles));
+end;
+
+function TCSSResolver.IndexOfStyle(aStyle: TCSSElement): integer;
+begin
+  Result:=high(FStyles);
+  while (Result>=0) and (FStyles[Result]<>aStyle) do dec(Result);
+end;
+
+procedure TCSSResolver.RemoveStyle(aStyle: TCSSElement);
+var
+  i: Integer;
+begin
+  i:=IndexOfStyle(aStyle);
+  if i<0 then exit;
+  DeleteStyle(i);
+end;
+
+procedure TCSSResolver.DeleteStyle(aIndex: integer);
+begin
+  if (aIndex<0) or (aIndex>=length(FStyles)) then
+    raise ECSSResolver.Create('TCSSResolver.DeleteStyle index '+IntToStr(aIndex)+' out of bounds '+IntToStr(length(FStyles)));
+  if OwnsStyle then
+    FStyles[aIndex].Free;
+  Delete(FStyles,aIndex,1);
+end;
+
+procedure TCSSResolver.ClearStyles;
+var
+  i: Integer;
+begin
+  if OwnsStyle then
+    for i:=0 to high(FStyles) do
+      FStyles[i].Free;
+  FStyles:=nil;
+end;
+
+end.
+

+ 1089 - 0
src/base/fcl-css/fpcssscanner.pp

@@ -0,0 +1,1089 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2022- by Michael Van Canneyt ([email protected])
+
+    This file contains CSS scanner and tokenizer
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpCSSScanner;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses
+  System.Classes, System.SysUtils, FpCss.Tree;
+{$ELSE FPC_DOTTEDUNITS}
+uses
+  Classes, SysUtils, fpCSSTree;
+{$ENDIF FPC_DOTTEDUNITS}
+
+Type
+  TCSSToken =  (
+    ctkUNKNOWN,
+    ctkEOF,
+    ctkWHITESPACE,
+    ctkCOMMENT,
+    ctkSEMICOLON,
+    ctkLPARENTHESIS,
+    ctkRPARENTHESIS,
+    ctkLBRACE,
+    ctkRBRACE,
+    ctkLBRACKET,
+    ctkRBRACKET,
+    ctkCOMMA,
+    ctkEQUALS,
+    ctkAND,
+    ctkTILDE,
+    ctkTILDEEQUAL,
+    ctkPLUS,
+    ctkCOLON,
+    ctkDOUBLECOLON,
+    ctkDOT,
+    ctkDIV,
+    ctkGT,
+    ctkGE,
+    ctkLT,
+    ctkLE,
+    ctkPERCENTAGE,
+    ctkMINUS,
+    ctkSTAR,
+    ctkSTAREQUAL,
+    ctkINTEGER,
+    ctkFLOAT,
+    ctkHASH,
+    ctkSTRING,
+    ctkIDENTIFIER,
+    ctkATKEYWORD,
+    ctkURL,
+    ctkBADURL,
+    ctkIMPORTANT,
+    ctkCLASSNAME,
+    ctkFUNCTION,
+    ctkPSEUDO,
+    ctkPSEUDOFUNCTION,
+    ctkSQUARED,
+    ctkSQUAREDEQUAL,
+    ctkUNICODERANGE,
+    ctkPIPE,
+    ctkPIPEEQUAL,
+    ctkDOLLAR,
+    ctkDOLLAREQUAL,
+    ctkINVALID
+   );
+  TCSSTokens = Set of TCSSToken;
+
+  TCSSString = UTF8String;
+
+resourcestring
+  SErrInvalidCharacter = 'Invalid character ''%s''';
+  SErrOpenString = 'String exceeds end of line';
+  SErrIncludeFileNotFound = 'Could not find include file ''%s''';
+  SInvalidHexadecimalNumber = 'Invalid decimal number';
+  SErrUnknownCharacter = 'Unknown character: %s';
+
+Type
+  ECSSScanner = Class(ECSSException);
+
+  TLineReader = class
+  public
+    function IsEOF: Boolean; virtual; abstract;
+    function ReadLine: TCSSString; virtual; abstract;
+  end;
+
+  { TStreamLineReader }
+
+  TStreamLineReader = class(TLineReader)
+  private
+    FStream : TStream;
+    Buffer : Array[0..1024] of Byte;
+    FBufPos,
+    FBufLen : Integer;
+    procedure FillBuffer;
+  public
+    Constructor Create(AStream : TStream);
+    function IsEOF: Boolean; override;
+    function ReadLine: TCSSString; override;
+  end;
+
+  TFileLineReader = class(TLineReader)
+  private
+    FTextFile: Text;
+    FileOpened: Boolean;
+  public
+    constructor Create(const AFilename: TCSSString);
+    destructor Destroy; override;
+    function IsEOF: Boolean; override;
+    function ReadLine: TCSSString; override;
+  end;
+
+  { TCSSScanner }
+
+  TCSSScannerOption = (csoExtendedIdentifiers,csoReturnComments,csoReturnWhiteSpace);
+  TCSSScannerOptions = set of TCSSScannerOption;
+  TCSSScannerWarnEvent = procedure(Sender: TObject; Msg: string) of object;
+
+  TCSSScanner = class
+  private
+    FDisablePseudo: Boolean;
+    FOnWarn: TCSSScannerWarnEvent;
+    FOptions: TCSSScannerOptions;
+    FSourceFile: TLineReader;
+    FSourceFilename: TCSSString;
+    FCurRow: Integer;
+    FCurToken: TCSSToken;
+    FCurTokenString: TCSSString;
+    FCurLine: TCSSString;
+    TokenStr: PAnsiChar;
+    FSourceStream : TStream;
+    FOwnSourceFile : Boolean;
+    function DoHash: TCSSToken;
+    function DoIdentifierLike : TCSSToken;
+    function DoInvalidChars : TCSSToken;
+    function DoMultiLineComment: TCSSToken;
+    function CommentDiv: TCSSToken;
+    function DoNumericLiteral: TCSSToken;
+    function DoSingleLineComment: TCSSToken;
+    function DoStringLiteral: TCSSToken;
+    function DoWhiteSpace: TCSSToken;
+    function EatBadURL: TCSSToken;
+    Function DoUnicodeRange : TCSSTOKEN;
+    function FetchLine: Boolean;
+    function GetCurColumn: Integer;
+    function GetReturnComments: Boolean;
+    function GetReturnWhiteSpace: Boolean;
+    function ReadUnicodeEscape: WideChar;
+    procedure SetReturnComments(AValue: Boolean);
+    procedure SetReturnWhiteSpace(AValue: Boolean);
+    class function UnknownCharToStr(C: AnsiChar): TCSSString;
+  protected
+    procedure DoError(const Msg: TCSSString; Args: array of const); overload;
+    procedure DoError(const Msg: TCSSString); overload;
+    function DoFetchToken: TCSSToken; virtual;
+  public
+    constructor Create(ALineReader: TLineReader);
+    constructor Create(AStream : TStream);
+    destructor Destroy; override;
+    procedure OpenFile(const AFilename: TCSSString);
+    Function FetchToken: TCSSToken;
+    function IsUTF8BOM: boolean;
+    Property ReturnComments : Boolean Read GetReturnComments Write SetReturnComments;
+    Property ReturnWhiteSpace : Boolean Read GetReturnWhiteSpace Write SetReturnWhiteSpace;
+    Property Options : TCSSScannerOptions Read FOptions Write FOptions;
+    property SourceFile: TLineReader read FSourceFile;
+    property CurFilename: TCSSString read FSourceFilename;
+    property CurLine: TCSSString read FCurLine;
+    property CurRow: Integer read FCurRow;
+    property CurColumn: Integer read GetCurColumn;
+    property CurToken: TCSSToken read FCurToken;
+    property CurTokenString: TCSSString read FCurTokenString;
+    property DisablePseudo : Boolean Read FDisablePseudo Write FDisablePseudo;
+    property OnWarn: TCSSScannerWarnEvent read FOnWarn write FOnWarn;
+  end;
+
+function SafeFormat(const Fmt: string; const Args: array of const): string;
+
+implementation
+
+Const
+  Alpha = ['A'..'Z','a'..'z'];
+  Num   = ['0'..'9'];
+  AlNum = Alpha+Num;
+  AlNumIden = Alpha+Num+['-'];
+  WhiteSpace = [' ',#9];
+
+type
+  TMessageArgs = array of string;
+
+procedure CreateMsgArgs(var MsgArgs: TMessageArgs; const Args: array of const);
+var
+  i: Integer;
+  A : AnsiString;
+  U : UnicodeString;
+  {$ifdef pas2js}
+  v: jsvalue;
+  {$endif}
+begin
+  SetLength(MsgArgs, High(Args)-Low(Args)+1);
+  for i:=Low(Args) to High(Args) do
+    {$ifdef pas2js}
+    begin
+    v:=Args[i];
+    if isBoolean(v) then
+      MsgArgs[i] := BoolToStr(Boolean(v))
+    else if isString(v) then
+      MsgArgs[i] := String(v)
+    else if isNumber(v) then
+      begin
+      if IsInteger(v) then
+        MsgArgs[i] := str(NativeInt(v))
+      else
+        MsgArgs[i] := str(double(v));
+      end
+    else
+      MsgArgs[i]:='';
+    end;
+    {$else}
+    case Args[i].VType of
+      vtInteger:      MsgArgs[i] := IntToStr(Args[i].VInteger);
+      vtBoolean:      MsgArgs[i] := BoolToStr(Args[i].VBoolean);
+      vtChar:         MsgArgs[i] := Args[i].VChar;
+      {$ifndef FPUNONE}
+      vtExtended:     ; //  Args[i].VExtended^;
+      {$ENDIF}
+      vtString:       MsgArgs[i] := Args[i].VString^;
+      vtPointer:      ; //  Args[i].VPointer;
+      vtPChar:        MsgArgs[i] := Args[i].VPChar;
+      vtObject:       ; //  Args[i].VObject;
+      vtClass:        ; //  Args[i].VClass;
+      vtWideChar:
+        begin
+        U:=Args[i].VWideChar;
+        MsgArgs[i] := String(U);
+        end;
+      vtPWideChar:
+        begin
+        U:=Args[i].VPWideChar;
+        MsgArgs[i] := String(U);
+        end;
+      vtAnsiString:
+        begin
+        A:=AnsiString(Args[i].VAnsiString);
+        MsgArgs[i]:=A;
+        end;
+      vtCurrency:     ; //  Args[i].VCurrency^);
+      vtVariant:      ; //  Args[i].VVariant^);
+      vtInterface:    ; //  Args[i].VInterface^);
+      vtWidestring:
+        begin
+        U:=WideString(Args[i].VWideString);
+        MsgArgs[i] := String(U);
+        end;
+      vtInt64:        MsgArgs[i] := IntToStr(Args[i].VInt64^);
+      vtQWord:        MsgArgs[i] := IntToStr(Args[i].VQWord^);
+      vtUnicodeString:
+        begin
+        U:=UnicodeString(Args[i].VUnicodeString);
+        MsgArgs[i] := String(U);
+        end;
+    end;
+    {$endif}
+end;
+
+function SafeFormat(const Fmt: string; const Args: array of const): string;
+var
+  MsgArgs: TMessageArgs;
+  i: Integer;
+begin
+  try
+    Result:=Format(Fmt,Args);
+  except
+    Result:='';
+    MsgArgs:=nil;
+    CreateMsgArgs(MsgArgs,Args);
+    for i:=0 to length(MsgArgs)-1 do
+      begin
+      if i>0 then
+        Result:=Result+',';
+      Result:=Result+MsgArgs[i];
+      end;
+    Result:='{'+Fmt+'}['+Result+']';
+  end;
+end;
+
+constructor TFileLineReader.Create(const AFilename: TCSSString);
+begin
+  inherited Create;
+  Assign(FTextFile, AFilename);
+  Reset(FTextFile);
+  FileOpened := true;
+end;
+
+destructor TFileLineReader.Destroy;
+begin
+  if FileOpened then
+    Close(FTextFile);
+  inherited Destroy;
+end;
+
+function TFileLineReader.IsEOF: Boolean;
+begin
+  Result := EOF(FTextFile);
+end;
+
+function TFileLineReader.ReadLine: TCSSString;
+begin
+  ReadLn(FTextFile, Result);
+end;
+
+constructor TCSSScanner.Create(ALineReader: TLineReader);
+begin
+  inherited Create;
+  FSourceFile := ALineReader;
+end;
+
+constructor TCSSScanner.Create(AStream: TStream);
+begin
+  FSourceStream:=ASTream;
+  FOwnSourceFile:=True;
+  Create(TStreamLineReader.Create(AStream));
+end;
+
+destructor TCSSScanner.Destroy;
+begin
+  If FOwnSourceFile then
+    FSourceFile.Free;
+  inherited Destroy;
+end;
+
+procedure TCSSScanner.OpenFile(const AFilename: TCSSString);
+begin
+  FSourceFile := TFileLineReader.Create(AFilename);
+  FSourceFilename := AFilename;
+end;
+
+function TCSSScanner.FetchLine: Boolean;
+begin
+  if FSourceFile.IsEOF then
+  begin
+    FCurLine := '';
+    TokenStr := nil;
+    Result := false;
+  end else
+  begin
+    FCurLine := FSourceFile.ReadLine;
+    TokenStr := PAnsiChar(CurLine);
+    Result := true;
+    Inc(FCurRow);
+  end;
+end;
+
+function TCSSScanner.DoWhiteSpace : TCSSToken;
+
+begin
+  Result:=ctkWhitespace;
+  repeat
+    Inc(TokenStr);
+    if TokenStr[0] = #0 then
+      if not FetchLine then
+       begin
+       FCurToken := Result;
+       exit;
+       end;
+  until not (TokenStr[0] in [#9, ' ']);
+end;
+
+function TCSSScanner.DoSingleLineComment : TCSSToken;
+
+Var
+  TokenStart : PAnsiChar;
+  Len : Integer;
+
+begin
+  Inc(TokenStr);
+  TokenStart := TokenStr;
+  while TokenStr[0] <> #0 do
+     Inc(TokenStr);
+  Len:=TokenStr-TokenStart;
+  SetLength(FCurTokenString, Len);
+  if (Len>0) then
+    Move(TokenStart^,FCurTokenString[1],Len);
+  Result := ctkComment;
+end;
+
+function TCSSScanner.DoMultiLineComment : TCSSToken;
+
+Var
+  TokenStart : PAnsiChar;
+  Len,OLen : Integer;
+  PrevToken : AnsiChar;
+
+begin
+  Inc(TokenStr);
+  TokenStart := TokenStr;
+  FCurTokenString := '';
+  OLen:= 0;
+  PrevToken:=#0;
+  while Not ((TokenStr[0]='/') and (PrevToken='*')) do
+    begin
+    if (TokenStr[0]=#0) then
+      begin
+      Len:=TokenStr-TokenStart+1;
+      SetLength(FCurTokenString,OLen+Len);
+      if Len>1 then
+        Move(TokenStart^,FCurTokenString[OLen+1],Len-1);
+      Inc(OLen,Len);
+      FCurTokenString[OLen]:=#10;
+      if not FetchLine then
+        begin
+        Result := ctkEOF;
+        FCurToken := Result;
+        exit;
+        end;
+      TokenStart := TokenStr;
+      PrevToken:=#0;
+      end
+    else
+      begin
+      PrevToken:=TokenStr[0];
+      Inc(TokenStr);
+      end;
+    end;
+  Len:=TokenStr-TokenStart-1; // -1 for *
+  SetLength(FCurTokenString, Olen+Len);
+  if (Len>0) then
+    Move(TokenStart^, FCurTokenString[Olen + 1], Len);
+  Inc(TokenStr);
+  Result := ctkComment;
+end;
+
+function TCSSScanner.CommentDiv : TCSSToken;
+
+begin
+  FCurTokenString := '';
+  Inc(TokenStr);
+  if (TokenStr[0] = '/') then       // Single-line comment
+    Result:=DoSingleLineComment
+  else if (TokenStr[0]='*') then
+    Result:=DoMultiLineComment
+  else
+    Result:=ctkDiv;
+end;
+
+function TCSSScanner.ReadUnicodeEscape: WideChar;
+
+const
+  Hex = ['0'..'9','A'..'F','a'..'f' ];
+
+Var
+  S : TCSSString;
+  I : Integer;
+  HaveHex : Boolean;
+
+begin
+  S:='';
+  I:=1;
+  Repeat
+    S:=S+Upcase(TokenStr[0]);
+    HaveHex:=TokenStr[1] in Hex;
+    if HaveHex then
+      Inc(TokenStr);
+    Inc(I);
+  Until (I>4) or not HaveHex;
+  // Takes care of conversion... This needs improvement !!
+  Result:=WideChar(StrToInt('$'+S));
+end;
+
+procedure TCSSScanner.SetReturnComments(AValue: Boolean);
+begin
+  if AValue then
+    Include(FOptions,csoReturnComments)
+  else
+    Exclude(FOptions,csoReturnComments)
+end;
+
+procedure TCSSScanner.SetReturnWhiteSpace(AValue: Boolean);
+begin
+  if AValue then
+    Include(FOptions,csoReturnWhiteSpace)
+  else
+    Exclude(FOptions,csoReturnWhiteSpace)
+end;
+
+
+function TCSSScanner.DoStringLiteral: TCSSToken;
+
+Var
+  Delim : AnsiChar;
+  TokenStart : PAnsiChar;
+  Len,OLen: Integer;
+  S : TCSSString;
+
+begin
+  Delim:=TokenStr[0];
+  Inc(TokenStr);
+  TokenStart := TokenStr;
+  OLen := 0;
+  FCurTokenString := '';
+  while not (TokenStr[0] in [#0,Delim]) do
+    begin
+    if (TokenStr[0]='\') then
+      begin
+      // Save length
+      Len := TokenStr - TokenStart;
+      Inc(TokenStr);
+      // Read escaped token
+      Case TokenStr[0] of
+        '"' : S:='"';
+        'a'..'f',
+        'A'..'F',
+        '0'..'9':
+              begin
+              S:=UTF8Encode(ReadUniCodeEscape);
+              end;
+        #0  : DoError(SErrOpenString);
+      else
+        DoError(SErrInvalidCharacter, [TokenStr[0]]);
+      end;
+      SetLength(FCurTokenString, OLen + Len+1+Length(S));
+      if Len > 0 then
+        Move(TokenStart^, FCurTokenString[OLen + 1], Len);
+      Move(S[1],FCurTokenString[OLen + Len+1],Length(S));
+      Inc(OLen, Len+Length(S));
+      // Next AnsiChar
+      // Inc(TokenStr);
+      TokenStart := TokenStr+1;
+      end;
+    if TokenStr[0] = #0 then
+      DoError(SErrOpenString);
+    Inc(TokenStr);
+    end;
+  if TokenStr[0] = #0 then
+    DoError(SErrOpenString);
+  Len := TokenStr - TokenStart;
+  SetLength(FCurTokenString, OLen + Len);
+  if Len > 0 then
+    Move(TokenStart^, FCurTokenString[OLen+1], Len);
+  Inc(TokenStr);
+  Result := ctkSTRING;
+end;
+
+function TCSSScanner.DoNumericLiteral :TCSSToken;
+
+Var
+  TokenStart : PAnsiChar;
+  Len : Integer;
+  isEscape : Boolean;
+
+begin
+  Result := ctkINTEGER;
+  isEscape:=TokenStr[0]='\';
+  if IsEscape then
+    Inc(TokenStr);
+  TokenStart := TokenStr;
+  while true do
+    begin
+    Inc(TokenStr);
+    case TokenStr[0] of
+      '.':
+        if IsEscape then
+          Break
+        else
+          begin
+            Result := ctkFLOAT;
+            if TokenStr[1] in ['0'..'9'] then
+            begin
+              Inc(TokenStr);
+              repeat
+                Inc(TokenStr);
+              until not (TokenStr[0] in ['0'..'9']);
+            end;
+            break;
+          end;
+      '0'..'9': ;
+      else
+        break;
+    end;
+  end;
+  Len:=TokenStr-TokenStart;
+  Setlength(FCurTokenString, Len);
+  if (Len>0) then
+  Move(TokenStart^,FCurTokenString[1],Len);
+  if IsEscape then
+    begin
+    Result:=ctkString;
+    FCurTokenString:=AnsiChar(StrToInt(FCurTokenString));
+    end;
+end;
+
+function TCSSScanner.DoHash :TCSSToken;
+
+Var
+  TokenStart : PAnsiChar;
+  Len : Integer;
+
+begin
+  Result := ctkHASH;
+  TokenStart := TokenStr;
+  Inc(TokenStr);
+  while (TokenStr[0]<>'#') and (TokenStr[0] in AlNumIden) do
+    inc(TokenStr);
+  Len:=TokenStr-TokenStart;
+  Setlength(FCurTokenString, Len);
+  if (Len>0) then
+  Move(TokenStart^,FCurTokenString[1],Len);
+end;
+
+
+function TCSSScanner.EatBadURL: TCSSToken;
+
+var
+  TokenStart : PAnsiChar;
+  C : AnsiChar;
+  len,oldlen : integer;
+
+begin
+  Result:=ctkURL;
+  While not (TokenStr[0] in [#0,')']) do
+    begin
+    TokenStart:=TokenStr;
+    While not (TokenStr[0] in [#0,')']) do
+      begin
+      C:=TokenStr[0];
+      if (Ord(C)<=Ord(' ')) or (Ord(C)>127) then
+        Result:=ctkBADURL;
+      inc(TokenStr);
+      end;
+    Len:=TokenStr-TokenStart;
+    oldLen:=Length(FCurTokenString);
+    Setlength(FCurTokenString, OldLen+Len);
+    if (Len>0) then
+      Move(TokenStart^,FCurTokenString[OldLen+1],Len);
+    if TokenStr[0]=#0 then
+      if not FetchLine then
+        Exit(ctkEOF);
+    end;
+end;
+
+function TCSSScanner.DoUnicodeRange: TCSSTOKEN;
+Var
+  TokenStart:PAnsiChar;
+  Len : Integer;
+  Tokens : Set of AnsiChar;
+
+begin
+  Tokens:= ['A'..'F', 'a'..'f', '0'..'9', '-'];
+  Result:=ctkUNICODERANGE;
+  TokenStart := TokenStr;
+  Inc(TokenStr,2); // U+
+  repeat
+    if (TokenStr[0]='-') then
+      Tokens:=Tokens-['-'];
+    Inc(TokenStr);
+    //If (TokenStr[0]='\') and (TokenStr[1]='u') then
+  until not (TokenStr[0] in Tokens);
+  Len:=(TokenStr-TokenStart);
+  SetLength(FCurTokenString,Len);
+  if Len > 0 then
+    Move(TokenStart^,FCurTokenString[1],Len);
+
+end;
+
+class function TCSSScanner.UnknownCharToStr(C: AnsiChar): TCSSString;
+
+begin
+  if C=#0 then
+    Result:='EOF'
+  else if (C in WhiteSpace) then
+    Result:='#'+IntToStr(Ord(C))
+  else
+    Result:='"'+C+'"';
+end;
+
+function TCSSScanner.DoIdentifierLike : TCSSToken;
+
+Var
+  TokenStart:PAnsiChar;
+  Len,oLen : Integer;
+  IsEscape,IsAt, IsPseudo, IsFunc : Boolean;
+
+begin
+  Result:=ctkIDENTIFIER;
+  TokenStart := TokenStr;
+  IsPseudo:=False;
+  IsAt:=TokenStr[0]='@';
+  IsFunc:=false;
+  For Len:=1 to 2 do
+    if TokenStr[0]=':' then
+      begin
+      IsPseudo:=True;
+      Inc(TokenStr);
+      end;
+  Repeat
+    if not (TokenStr[0]='\') then
+      repeat
+        Inc(TokenStr);
+        //If (TokenStr[0]='\') and (TokenStr[1]='u') then
+      until not (TokenStr[0] in ['A'..'Z', 'a'..'z', '0'..'9', '_','-']);
+    IsEscape:=TokenStr[0]='\';
+    if IsEscape then
+      begin
+      if ((TokenStr[0] in WhiteSpace) or (TokenStr[0]=#0))  then
+        DoError(SErrUnknownCharacter ,[UnknownCharToStr(TokenStr[0])])
+      end
+    else if not IsAt then
+      begin
+      IsFunc:=TokenStr[0]='(';
+      if IsFunc then
+        Inc(TokenStr);
+      end;
+    Len:=(TokenStr-TokenStart);
+    oLen:=Length(FCurTokenString);
+    SetLength(FCurTokenString,Olen+Len);
+    if Len > 0 then
+      Move(TokenStart^,FCurTokenString[Olen+1],Len);
+    if IsEscape then
+      Inc(TokenStr);
+    TokenStart := TokenStr;
+  until Not IsEscape;
+  // Some specials
+  if (CurTokenString[1]='.') and not IsFunc then
+    Result:=ctkCLASSNAME
+  else if isAt then
+    Result:=ctkATKEYWORD
+  else if CurTokenString='!important' then
+    Result:=ctkIMPORTANT
+  else if (CurtokenString='url(') then
+    begin
+    Result:=ctkURL;
+    If TokenStr[0] in ['"',''''] then
+      DoStringLiteral
+    else
+      begin
+      result:=EatBadURL;
+      end;
+    If (result<>ctkEOF) and (TokenStr[0] in [')']) then
+      Inc(TokenStr);
+    end
+  else if IsPseudo then
+    begin
+    if IsFunc then
+      Result:=ctkPSEUDOFUNCTION
+    else
+      Result:=ctkPSEUDO;
+    end
+  else if IsFunc then
+    Result:=ctkFUNCTION;
+end;
+
+function TCSSScanner.DoInvalidChars: TCSSToken;
+var
+  TokenStart: PAnsiChar;
+  Len: SizeUInt;
+begin
+  Result:=ctkINVALID;
+  TokenStart := TokenStr;
+  repeat
+    writeln('TCSSScanner.DoInvalidChars ',hexstr(ord(TokenStr^),2));
+    Inc(TokenStr);
+  until (TokenStr[0] in [#0,#9,#10,#13,#32..#127]);
+  Len:=TokenStr-TokenStart;
+  SetLength(FCurTokenString,Len);
+  if Len > 0 then
+    Move(TokenStart^,FCurTokenString[1],Len);
+end;
+
+function TCSSScanner.FetchToken: TCSSToken;
+
+var
+  CanStop : Boolean;
+
+begin
+  Repeat
+    Result:=DoFetchToken;
+    if (Result=ctkINVALID) and IsUTF8BOM then
+      CanStop:=false
+    else
+      CanStop:=(Not (Result in [ctkComment,ctkWhiteSpace]))
+             or ((ReturnComments and (Result=ctkComment))
+                  or
+                 (ReturnWhiteSpace and (Result=ctkWhiteSpace))
+                )
+  Until CanStop;
+end;
+
+function TCSSScanner.IsUTF8BOM: boolean;
+begin
+  Result:=(length(FCurTokenString)=3)
+      and (FCurTokenString[1]=#$EF)
+      and (FCurTokenString[2]=#$BB)
+      and (FCurTokenString[3]=#$BF);
+end;
+
+function TCSSScanner.DoFetchToken: TCSSToken;
+
+
+  Procedure CharToken(aToken : TCSSToken);
+
+  begin
+    FCurTokenString:=TokenStr[0];
+    Inc(TokenStr);
+    Result:=aToken;
+  end;
+
+  Procedure TwoCharsToken(aToken : TCSSToken);
+
+  begin
+    FCurTokenString:=TokenStr[0]+TokenStr[1];
+    Inc(TokenStr,2);
+    Result:=aToken;
+  end;
+
+begin
+  if TokenStr = nil then
+    begin
+    if not FetchLine then
+      begin
+      Result := ctkEOF;
+      FCurToken := Result;
+      exit;
+      end;
+    end;
+  //CurPos:=TokenStr;
+  FCurTokenString := '';
+  case TokenStr[0] of
+    #0:         // Empty line
+      begin
+      FetchLine;
+      Result := ctkWhitespace;
+      end;
+    '''','"':
+       Result:=DoStringLiteral;
+    '/' :
+       Result:=CommentDiv;
+    #9, ' ':
+       Result := DoWhiteSpace;
+    '#':
+       Result:=DoHash;
+    '\':
+       begin
+       if TokenStr[1] in ['0'..'9'] then
+         Result:=DoNumericLiteral
+       else
+         begin
+         if (TokenStr[1] in WhiteSpace) or (TokenStr[1]=#0) then
+           DoError(SErrUnknownCharacter ,[UnknownCharToStr(TokenStr[1])])
+         else
+           Result:=DoIdentifierLike
+         end;
+       end;
+    '0'..'9':
+       Result:=DoNumericLiteral;
+    '&': CharToken(ctkAnd);
+    '{': CharToken( ctkLBRACE);
+    '}': CharToken(ctkRBRACE);
+    '*': if TokenStr[1]='=' then
+           TwoCharsToken(ctkSTAREQUAL)
+         else if (csoExtendedIdentifiers in Options) and (TokenStr[1] in AlNumIden) then
+           Result:=DoIdentifierLike
+         else
+           CharToken(ctkSTAR);
+    '^':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkSQUAREDEQUAL)
+      else
+        CharToken(ctkSQUARED);
+    ',': CharToken(ctkCOMMA);
+    '~':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkTILDEEQUAL)
+      else
+        CharToken(ctkTILDE);
+    '|':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkPIPEEQUAL)
+      else
+        CharToken(ctkPIPE);
+    '$':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkDOLLAREQUAL)
+      else
+        CharToken(ctkDOLLAR);
+    ';': CharToken(ctkSEMICOLON);
+    '@': Result:=DoIdentifierLike;
+    ':':
+      begin
+      if DisablePseudo then
+        CharToken(ctkCOLON)
+      else if (TokenStr[1]=':') then
+        begin
+        if (TokenStr[2] in AlNumIden) then
+          Result:=DoIdentifierLike
+        else
+          Result:=ctkDoubleCOLON
+        end
+      else if (TokenStr[1] in AlNumIden) then
+        Result:=DoIdentifierLike
+      else
+        CharToken(ctkCOLON);
+      end;
+    '.':
+      begin
+      if (TokenStr[1] in AlNum) then
+        Result:=Self.DoIdentifierLike
+      else
+        CharToken(ctkDOT);
+      end;
+    '>':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkGE)
+      else
+        CharToken(ctkGT);
+    '<':
+      if TokenStr[1]='=' then
+        TwoCharsToken(ctkLE)
+      else
+        CharToken(ctkLT);
+    '(': CharToken(ctkLPARENTHESIS);
+    ')': CharToken(ctkRPARENTHESIS);
+    '[': CharToken(ctkLBRACKET);
+    ']': CharToken(ctkRBRACKET);
+    '=': CharToken(ctkEQUALS);
+    '-':
+      case TokenStr[1] of
+      '0'..'9':
+        Result:=DoNumericLiteral;
+      '.':
+        if TokenStr[2] in ['0'..'9'] then
+          Result:=DoNumericLiteral
+        else
+          CharToken(ctkMINUS);
+      #9,' ',#0:
+        CharToken(ctkMINUS);
+      else
+        Result:=DoIdentifierLike;
+      end;
+    '+': CharToken(ctkPLUS);
+    '%': CharToken(ctkPERCENTAGE);
+    '_','!',
+    'a'..'z',
+    'A'..'Z':
+       begin
+       if (TokenStr[0] in ['u','U']) and (TokenStr[1]='+') then
+         Result:=DoUnicodeRange
+       else
+         Result:=DoIdentifierLike;
+       end;
+  else
+    writeln('TCSSScanner.DoFetchToken ',Ord(TokenStr[0]));
+    If Ord(TokenStr[0])>127 then
+      Result:=DoInvalidChars
+    else
+      DoError(SErrUnknownCharacter ,['"'+TokenStr[0]+'"']);
+
+  end; // Case
+end;
+
+procedure TCSSScanner.DoError(const Msg: TCSSString; Args: array of const);
+begin
+  DoError(Format(Msg,Args));
+end;
+
+procedure TCSSScanner.DoError(const Msg: TCSSString);
+
+Var
+  S : TCSSString;
+
+begin
+  S:=Format('Error at (%d,%d): ',[CurRow,CurColumn])+Msg;
+  Raise ECSSScanner.Create(S);
+end;
+
+function TCSSScanner.GetCurColumn: Integer;
+begin
+  if (TokenStr=Nil) or (Length(CurLine)=0) then
+    Result:=0
+  else
+    Result := TokenStr - PAnsiChar(CurLine);
+end;
+
+function TCSSScanner.GetReturnComments: Boolean;
+begin
+  Result:=(csoReturnComments in FOptions);
+end;
+
+function TCSSScanner.GetReturnWhiteSpace: Boolean;
+begin
+  Result:=(csoReturnWhiteSpace in FOptions);
+end;
+
+{ TStreamLineReader }
+
+constructor TStreamLineReader.Create(AStream: TStream);
+begin
+  FStream:=AStream;
+  FBufPos:=0;
+  FBufLen:=0;
+end;
+
+function TStreamLineReader.IsEOF: Boolean;
+begin
+  Result:=(FBufPos>=FBufLen);
+  If Result then
+    begin
+    FillBuffer;
+    Result:=(FBufLen=0);
+    end;
+end;
+
+procedure TStreamLineReader.FillBuffer;
+
+begin
+  FBufLen:=FStream.Read(Buffer,SizeOf(Buffer)-1);
+  Buffer[FBufLen]:=0;
+  FBufPos:=0;
+end;
+
+function TStreamLineReader.ReadLine: TCSSString;
+
+Var
+  FPos,OLen,Len: Integer;
+  PRun : PByte;
+
+begin
+  Result:='';
+  FPos:=FBufPos;
+  Repeat
+    PRun:=@Buffer[FBufPos];
+    While (FBufPos<FBufLen) and Not (PRun^ in [10,13]) do
+      begin
+      Inc(PRun);
+      Inc(FBufPos);
+      end;
+    If (FBufPos=FBufLen) then
+      begin
+      Len:=FBufPos-FPos;
+      If (Len>0) then
+        begin
+        Olen:=Length(Result);
+        SetLength(Result,OLen+Len);
+        Move(Buffer[FPos],Result[OLen+1],Len);
+        end;
+      FillBuffer;
+      FPos:=FBufPos;
+      end;
+  until (FBufPos=FBufLen) or (PRun^ in [10,13]);
+  Len:=FBufPos-FPos;
+  If (Len>0) then
+    begin
+    Olen:=Length(Result);
+    SetLength(Result,OLen+Len);
+    Move(Buffer[FPos],Result[OLen+1],Len)
+    end;
+  If (PRun^ in [10,13]) and (FBufPos<FBufLen) then
+    begin
+    Inc(FBufPos);
+    // Check #13#10
+    If (PRun^=13) then
+      begin
+      If (FBufPos=FBufLen) then
+        FillBuffer;
+      If (FBufPos<FBufLen) and (Buffer[FBufpos]=10) then
+        Inc(FBufPos);
+      end;
+    end;
+end;
+
+end.
+

+ 1615 - 0
src/base/fcl-css/fpcsstree.pp

@@ -0,0 +1,1615 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2022- by Michael Van Canneyt ([email protected])
+
+    This file contains the implementation of objects representing a CSS AST
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpCSSTree;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode ObjFPC}{$H+}
+{$codepage utf8}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses System.Contnrs, System.RtlConsts, System.SysUtils, System.Classes, System.Math;
+{$ELSE FPC_DOTTEDUNITS}
+uses Contnrs, RtlConsts, SysUtils, Classes, Math;
+{$ENDIF FPC_DOTTEDUNITS}
+
+
+Type
+  ECSSException = class(Exception);
+
+  TCSSString = UTF8String;
+  TCSSStringDynArray = array of TCSSString;
+  TCSSUnits = (cuNONE, cuPX,cuPERCENT,cuREM,cuEM,cuPT,cuFR,cuVW,cuVH,cuDEG);
+  TCSSType = (
+    csstUnknown,
+    csstInteger, csstString, csstFloat,
+    csstIdentifier, // name
+    csstHashIdentifier, // #name
+    csstClassname, // .name
+    csstPseudoClass, // :name, ::name
+    csstCompound,
+    csstRule,
+    csstDeclaration,
+    csstBinaryOp,
+    csstCall, // name(, :name(, ::name(
+    csstUnaryOp,
+    csstArray, // []
+    csstURL, // url()
+    csstUnicodeRange,
+    csstList);
+
+  TCSSElement = class;
+
+  TCSSTreeVisitor = class
+  public
+    procedure Visit(obj: TCSSElement); virtual; abstract;
+  end;
+
+  { TCSSVisitorFreeCustomData }
+
+  TCSSVisitorFreeCustomData = class(TCSSTreeVisitor)
+  public
+    procedure Visit(obj: TCSSElement); override;
+  end;
+
+  { TCSSElementOwnedData - base class for TCSSElement.CustomData which automatically freed }
+
+  TCSSElementOwnedData = class
+  end;
+
+  { TCSSElement }
+
+  TCSSElement = Class(TObject)
+  private
+    FCol: Integer;
+    FData: TObject;
+    FFileName: TCSSString;
+    FParent: TCSSElement;
+    FRow: Integer;
+    function GetAsUnFormattedString: TCSSString;
+    function GetAsFormattedString: TCSSString;
+  Protected
+    procedure SetParent(const AValue: TCSSElement);
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; virtual;
+    function SubElEquals(ElA, ElB: TCSSElement): boolean;
+    procedure IterateChildren(aVisitor : TCSSTreeVisitor); virtual;
+  Public
+    Constructor Create(const aFileName : TCSSString; aRow,aCol : Integer); virtual;
+    destructor Destroy; override;
+    Class function CSSType : TCSSType; virtual;
+    function Equals(Obj: TObject): boolean; override;
+    Procedure Iterate(aVisitor : TCSSTreeVisitor);
+    Procedure FreeCustomData; virtual; // free recursively CustomData
+    Property CustomData : TObject Read FData Write FData;
+    Property SourceRow : Integer Read FRow;
+    Property SourceCol : Integer Read FCol;
+    Property SourceFileName : TCSSString Read FFileName;
+    Property AsFormattedString : TCSSString Read GetAsFormattedString;
+    Property AsString : TCSSString Read GetAsUnformattedString;
+    Property Parent: TCSSElement read FParent write SetParent;
+  end;
+  TCSSElementClass = Class of TCSSElement;
+  TCSSElementArray = Array of TCSSElement;
+
+  { TCSSElementList }
+
+  TCSSElementList = Class
+  private
+    FElementParent: TCSSElement;
+    FList: TFPObjectList;
+    function GetCapacity: Integer;
+    function GetCount: Integer;
+    function GetElement(aIndex : Integer): TCSSElement;
+    procedure SetCapacity(const AValue: Integer);
+  Public
+    constructor Create(ElParent: TCSSElement);
+    destructor Destroy; override;
+    Function Add(El: TCSSElement): Integer;
+    procedure Clear;
+    Procedure Delete(Index: Integer);
+    function Equals(Obj: TObject): boolean; override;
+    Procedure Exchange(Index1, Index2: Integer);
+    Function Extract(Index: Integer): TCSSElement; // remove without free
+    Function IndexOf(El: TCSSElement): Integer;
+    Procedure Insert(Index: Integer; El: TCSSElement);
+    Function First: TCSSElement;
+    Function Last: TCSSElement;
+    Procedure Move(CurIndex, NewIndex: Integer);
+    Procedure Assign(aList: TCSSElementList);
+    Procedure Pack;
+    Procedure Sort(const Compare: TListSortCompare);
+    Procedure Iterate(aVisitor : TCSSTreeVisitor);
+    property Capacity: Integer read GetCapacity write SetCapacity;
+    property Count: Integer read GetCount;
+    property Elements[aIndex : Integer] : TCSSElement Read GetElement; default;
+    property ElementParent: TCSSElement read FElementParent;
+  end;
+
+  { TCSSIntegerElement }
+
+  TCSSIntegerElement = class(TCSSElement)
+  private
+    FIsEscaped: Boolean;
+    FUnits: TCSSUnits;
+    FValue: Integer;
+  protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Value : Integer Read FValue Write FValue;
+    Property IsEscaped : Boolean Read FIsEscaped Write FIsEscaped;
+    Property Units : TCSSUnits Read FUnits Write FUnits;
+  end;
+  TCSSIntegerElementClass = class of TCSSIntegerElement;
+
+  { TCSSFloatElement }
+
+  TCSSFloatElement = class(TCSSElement)
+  private
+    FUnits: TCSSUnits;
+    FValue: Double;
+  protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+  Public
+    Class function CSSType : TCSSType; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Value : Double Read FValue Write FValue;
+    Property Units : TCSSUnits Read FUnits Write FUnits;
+  end;
+  TCSSFloatElementClass = class of TCSSFloatElement;
+
+  { TCSSBaseUnaryElement }
+
+  TCSSBaseUnaryElement = Class(TCSSElement)
+  private
+    FRight: TCSSElement;
+    procedure SetRight(AValue: TCSSElement);
+  protected
+    Procedure IterateChildren(aVisitor : TCSSTreeVisitor); override;
+  Public
+    Destructor Destroy; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Right : TCSSElement Read FRight Write SetRight;
+  end;
+
+  { TCSSUnaryElement }
+  TCSSUnaryOperation = (uoDoubleColon,uoMinus,uoPlus,uoDiv,uoGT,uoTilde);
+  TCSSUnaryElement = Class(TCSSBaseUnaryElement)
+  private
+    FOperation: TCSSUnaryOperation;
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Operation : TCSSUnaryOperation Read FOperation Write FOperation;
+  end;
+  TCSSUnaryElementClass = class of TCSSUnaryElement;
+
+  { TCSSBinaryElement }
+  TCSSBinaryOperation = (boEquals,boPlus,boMinus,boAnd,boLE,boLT,boGE,boGT,boDIV,
+                         boStar,boTilde,boColon, boDoubleColon,boSquared,
+                         boPipe, boDollar, boWhiteSpace,
+                         boStarEqual,boTildeEqual,boSquaredEqual,boPipeEqual,boDollarEqual);
+  TCSSBinaryElement = Class(TCSSBaseUnaryElement)
+  private
+    FLeft: TCSSElement;
+    FOperation: TCSSBinaryOperation;
+    procedure SetLeft(AValue: TCSSElement);
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+    procedure IterateChildren(aVisitor: TCSSTreeVisitor); override;
+  Public
+    Destructor Destroy; override;
+    Class function CSSType : TCSSType; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Left : TCSSElement Read FLeft Write SetLeft;
+    Property Operation : TCSSBinaryOperation Read FOperation Write FOperation;
+  end;
+  TCSSBinaryElementClass = class of TCSSBinaryElement;
+
+  { TCSSBaseStringElement }
+
+  TCSSBaseStringElement = Class(TCSSElement)
+  private
+    FValue: TCSSString;
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    function Equals(Obj: TObject): boolean; override;
+    Property Value : TCSSString Read FValue Write FValue;
+  end;
+
+  { TCSSUnicodeRangeElement }
+
+  TCSSUnicodeRangeElement = class(TCSSBaseStringElement)
+  Public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSUnicodeRangeElementClass = class of TCSSUnicodeRangeElement;
+
+  { TCSSURLElement }
+
+  TCSSURLElement = Class(TCSSBaseStringElement)
+  public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSURLElementClass = class of TCSSURLElement;
+
+  { TCSSStringElement }
+
+  TCSSStringElement = Class(TCSSBaseStringElement)
+  private
+    FChildren : TCSSElementList;
+    function GetChildren: TCSSElementList;
+  protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+    procedure IterateChildren(aVisitor : TCSSTreeVisitor); override;
+  Public
+    Class function CSSType : TCSSType; override;
+    Destructor Destroy; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Children : TCSSElementList Read GetChildren;
+  end;
+  TCSSStringElementClass = class of TCSSStringElement;
+
+  { TCSSIdentifierElement }
+
+  TCSSIdentifierElement = Class(TCSSBaseStringElement)
+  private
+    function GetName: TCSSString;
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+    Property Name : TCSSString Read GetName;
+  end;
+  TCSSIdentifierElementClass = class of TCSSIdentifierElement;
+
+  { TCSSHashIdentifierElement }
+
+  TCSSHashIdentifierElement = Class(TCSSIdentifierElement)
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSHashIdentifierElementClass = class of TCSSHashIdentifierElement;
+
+  { TCSSClassNameElement }
+
+  TCSSClassNameElement = Class(TCSSIdentifierElement)
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSClassNameElementClass = class of TCSSClassNameElement;
+
+  { TCSSPseudoClassElement }
+
+  TCSSPseudoClassElement = Class(TCSSIdentifierElement)
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSPseudoClassElementClass = class of TCSSPseudoClassElement;
+
+  { TCSSChildrenElement }
+
+  TCSSChildrenElement = Class(TCSSElement)
+  private
+    FChildren : TCSSElementList;
+    function GetChild(aIndex : Integer): TCSSElement;
+    function GetChildCount: Integer;
+  Protected
+    procedure IterateChildren(aVisitor : TCSSTreeVisitor); override;
+  Public
+    Destructor Destroy; override;
+    Procedure AddChild(aChild : TCSSElement); virtual;
+    function Equals(Obj: TObject): boolean; override;
+    Property Children[aIndex : Integer] : TCSSElement Read GetChild; default;
+    Property ChildCount : Integer Read GetChildCount;
+  end;
+
+  { TCSSArrayElement }
+
+  TCSSArrayElement = Class(TCSSChildrenElement)
+  private
+    FPrefix : TCSSElement;
+    procedure SetPrefix(AValue: TCSSElement);
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Destructor Destroy; override;
+    Class function CSSType : TCSSType; override;
+    function Equals(Obj: TObject): boolean; override;
+    Property Prefix : TCSSElement Read FPrefix Write SetPrefix;
+  end;
+  TCSSArrayElementClass = class of TCSSArrayElement;
+
+  { TCSSCallElement }
+
+  TCSSCallElement = Class(TCSSChildrenElement)
+  private
+    FName: TCSSString;
+    function GetArg(aIndex : Integer): TCSSElement;
+    function GetArgCount: Integer;
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
+  Public
+    Class function CSSType : TCSSType; override;
+    Procedure AddArg(aArg : TCSSElement); virtual;
+    function Equals(Obj: TObject): boolean; override;
+    Property Args[aIndex : Integer] : TCSSElement Read GetArg; default;
+    Property ArgCount : Integer Read GetArgCount;
+    Property Name : TCSSString Read FName Write FName;
+  end;
+  TCSSCallElementClass = class of TCSSCallElement;
+
+  { TCSSDeclarationElement }
+
+  TCSSDeclarationElement = class(TCSSChildrenElement)
+  private
+    FIsImportant: Boolean;
+    FKeys : TCSSElementList;
+    FColon: Boolean;
+    function GetKeyCount: Integer;
+    function GetKeys(aIndex : Integer): TCSSElement;
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+    procedure IterateChildren(aVisitor : TCSSTreeVisitor); override;
+  Public
+    Class function CSSType : TCSSType; override;
+    Destructor Destroy; override;
+    Procedure AddKey(aKey : TCSSElement); virtual;
+    function Equals(Obj: TObject): boolean; override;
+    Property Keys [aIndex : Integer] : TCSSElement Read GetKeys;
+    Property KeyCount : Integer Read GetKeyCount;
+    Property IsImportant : Boolean Read FIsImportant Write FIsImportant;
+    Property Colon : Boolean Read FColon Write FColon;
+  end;
+  TCSSDeclarationElementClass = class of TCSSDeclarationElement;
+
+  { TCSSListElement }
+
+  TCSSListElement = class(TCSSChildrenElement)
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+  Public
+    Function ExtractElement(aIndex : Integer) : TCSSElement;
+  end;
+  TCSSListElementClass = class of TCSSListElement;
+
+  { TCSSCompoundElement }
+
+  TCSSCompoundElement = Class(TCSSChildrenElement)
+  Protected
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+  Public
+    Class function CSSType : TCSSType; override;
+  end;
+  TCSSCompoundElementClass = class of TCSSCompoundElement;
+
+  { TCSSRuleElement }
+
+  TCSSRuleElement = class(TCSSChildrenElement)
+  Private
+    FSelectors : TCSSElementList;
+    function GetSelector(aIndex : Integer): TCSSElement;
+    function GetSelectorCount: Integer;
+  Protected
+    function DoGetAsString(const aPrefix : TCSSString; aFormat : Boolean; const aIndent : TCSSString): TCSSString; virtual;
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+    procedure IterateChildren(aVisitor : TCSSTreeVisitor); override;
+  Public
+    Class function CSSType : TCSSType; override;
+    Destructor Destroy; override;
+    Procedure AddSelector(aSelector : TCSSElement);
+    function Equals(Obj: TObject): boolean; override;
+    Property Selectors [aIndex : Integer] : TCSSElement Read GetSelector;
+    Property SelectorCount : Integer Read GetSelectorCount;
+  end;
+  TCSSRuleElementClass = class of TCSSRuleElement;
+  TCSSRuleElementArray = array of TCSSRuleElement;
+
+  { TCSSAtRuleElement }
+
+  TCSSAtRuleElement = class(TCSSRuleElement)
+  private
+    FAtKeyWord: TCSSString;
+  Public
+    function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
+    function Equals(Obj: TObject): boolean; override;
+    Property AtKeyWord : TCSSString Read FAtKeyWord Write FAtKeyWord;
+  end;
+  TCSSAtRuleElementClass = class of TCSSAtRuleElement;
+
+
+// Convert unicode codepoints to \0000 notation
+Function StringToCSSString(const S : TCSSString) : TCSSString;
+// Escapes non-identifier characters C to \C
+Function StringToIdentifier(const S : TCSSString) : TCSSString;
+
+Function GetCSSObj(El: TCSSElement): TCSSString;
+Function GetCSSPath(El: TCSSElement): TCSSString;
+
+Function CSSElementListEquals(ListA, ListB: TCSSElementList): boolean;
+
+Const
+  CSSUnitNames : Array[TCSSUnits] of TCSSString =
+        ('','px','%','rem','em','pt','fr','vw','vh','deg');
+  UnaryOperators : Array[TCSSUnaryOperation] of TCSSString =
+        ('::','-','+','/','>','~');
+  BinaryOperators : Array[TCSSBinaryOperation] of TCSSString =
+        ('=','+','-','and','<=','<','>=','>','/','*','~',':','::','^','|','$',' ',
+         '*=','~=','^=','|=','$=');
+
+implementation
+
+Const
+  sIndent = '  ';
+
+Function  u8length(s : AnsiChar) : Byte;
+
+const u8_length : Array[0..15] of byte = (
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+   1,1,1,1,1,1,1,1,0,0,0,0,2,2,3,4
+) ;
+
+begin
+ Result:=u8_length[Ord(S) shr 4];
+end;
+
+function StringToCSSString(const S: TCSSString): TCSSString;
+
+Var
+  iIn,iOut,I,L : Integer;
+  O : TCSSString;
+  u : TCSSString;
+  W : Unicodestring;
+  C : AnsiChar;
+
+  Procedure AddO;
+  var
+    J : Integer;
+
+  begin
+    For J:=1 to Length(O) do
+      begin
+      Inc(iOut);
+      Result[iOut]:=O[J];
+      end;
+  end;
+
+begin
+  Result:='';
+  L:=Length(S);
+  SetLength(Result,4*L);
+  iIn:=1;
+  iOut:=0;
+  While iIn<=L do
+    begin
+    C:=S[iIn];
+    If C in [#0..' ','"'] then
+      begin
+      O:='\'+HexStr(Ord(C),2);
+      AddO;
+      end
+    else if Ord(C)<128 then
+      begin
+      inc(iOut);
+      Result[iOut]:=C;
+      end
+    else
+      begin
+      I:=U8length(C);
+      if (I>0) then
+        begin
+        U:=Copy(S,iIn,I);
+        W:=Utf8Decode(U);
+        for I:=1 to Length(W) do
+          begin
+          O:='\'+HexStr(Ord(W[I]),4);
+          AddO;
+          end;
+        inc(iIn,I);
+        continue;
+        end;
+      end;
+    Inc(iIn);
+    end;
+  SetLength(Result,iOut);
+end;
+
+function StringToIdentifier(const S: TCSSString): TCSSString;
+
+Var
+  iIn,iOut,L : Integer;
+  C : AnsiChar;
+
+begin
+  Result:='';
+  SetLength(Result,2*Length(S));
+  iIn:=1;
+  iOut:=0;
+  L:=Length(S);
+  While iIn<=L do
+    begin
+    C:=S[iIn];
+    If Not (C in ['a'..'z','A'..'Z','_','-','0'..'9']) then
+      begin
+      inc(iOut);
+      Result[iOut]:='\';
+      end;
+    inc(iOut);
+    Result[iOut]:=C;
+    Inc(iIn);
+    end;
+  SetLength(Result,iOut);
+end;
+
+function GetCSSObj(El: TCSSElement): TCSSString;
+begin
+  if El=nil then
+    Result:='nil'
+  else if El is TCSSIdentifierElement then
+    Result:=El.ClassName+'"'+TCSSIdentifierElement(El).Name+'"'
+  else
+    Result:=El.ClassName;
+end;
+
+function GetCSSPath(El: TCSSElement): TCSSString;
+begin
+  if El=nil then
+    exit('nil');
+  Result:='';
+  while El<>nil do
+    begin
+    if Result<>'' then
+      Result:='.'+Result;
+    Result:=GetCSSObj(El)+Result;
+    El:=El.Parent;
+    end;
+end;
+
+function CSSElementListEquals(ListA, ListB: TCSSElementList): boolean;
+begin
+  if (ListA=nil) or (ListA.Count=0) then
+    Result:=(ListB=nil) or (ListB.Count=0)
+  else
+    begin
+    if (ListB=nil) or (ListB.Count=0) then exit(false);
+    Result:=ListA.Equals(ListB);
+    end;
+end;
+
+{ TCSSListElement }
+
+function TCSSListElement.GetAsString(aFormat: Boolean; const aIndent: TCSSString
+  ): TCSSString;
+
+Var
+  I : integer;
+
+begin
+  Result:='';
+  For I:=0 to ChildCount-1 do
+    begin
+    if I>0 then
+      Result:=Result+' ';
+    Result:=Result+Children[I].GetAsString(aFormat,aIndent);
+    end;
+end;
+
+function TCSSListElement.ExtractElement(aIndex: Integer): TCSSElement;
+begin
+  Result:=FChildren.Extract(aIndex);
+end;
+
+{ TCSSAtRuleElement }
+
+function TCSSAtRuleElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Result:=DoGetAsString(AtKeyWord+' ',aFormat, aIndent);
+end;
+
+function TCSSAtRuleElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSAtRuleElement absolute Obj;
+begin
+  if Obj is TCSSAtRuleElement then
+    begin
+    if FAtKeyWord<>Src.FAtKeyWord then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSBaseStringElement }
+
+function TCSSBaseStringElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Result:=Value;
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+function TCSSBaseStringElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSBaseStringElement absolute Obj;
+begin
+  if Obj is TCSSBaseStringElement then
+    begin
+    if FValue<>Src.FValue then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TUnicodeRangeElement }
+
+class function TCSSUnicodeRangeElement.CSSType: TCSSType;
+begin
+  Result:=csstUnicodeRange;
+end;
+
+{ TCSSURLElement }
+
+class function TCSSURLElement.CSSType: TCSSType;
+begin
+  Result:=csstURL;
+end;
+
+
+{ TCSSCompoundElement }
+
+function TCSSCompoundElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+
+Var
+  I : Integer;
+
+begin
+  Result:='';
+  For I:=0 to ChildCount-1 do
+    begin
+    if (i>0) and aFormat then
+      Result:=Result+sLineBreak+aIndent;
+    Result:=Result+Children[I].GetAsString(aFormat,aIndent);
+    end;
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+class function TCSSCompoundElement.CSSType: TCSSType;
+begin
+  Result:=csstCompound;
+end;
+
+{ TCSSDeclarationElement }
+
+function TCSSDeclarationElement.GetKeyCount: Integer;
+begin
+  If Assigned(FKeys) then
+    Result:=FKeys.Count
+  else
+    Result:=0;
+end;
+
+function TCSSDeclarationElement.GetKeys(aIndex : Integer): TCSSElement;
+begin
+  if Not Assigned(FKeys) then
+    Raise EListError.CreateFmt(SListIndexError,[aIndex]);
+  Result:=FKeys[aIndex];
+end;
+
+function TCSSDeclarationElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+
+var
+  I : Integer;
+
+begin
+  Result:='';
+  For I:=0 to KeyCount-1 do
+    begin
+    if (I>0) then
+      begin
+      Result:=Result+','+sLineBreak;
+      if aFormat then
+        Result:=Result+aIndent;
+      end;
+    Result:=Result+Keys[I].GetAsString(aFormat,aIndent);
+    end;
+  Result:=Result+' : ';
+  For I:=0 to ChildCount-1 do
+    begin
+    if (I>0) then
+      Result:=Result+', ';
+    Result:=Result+Children[I].GetAsString(aFormat,aIndent);
+    end;
+  Result:=aIndent+Result;
+end;
+
+procedure TCSSDeclarationElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  if Assigned(FKeys) then
+    FKeys.Iterate(aVisitor);
+  inherited IterateChildren(aVisitor);
+end;
+
+class function TCSSDeclarationElement.CSSType: TCSSType;
+begin
+  Result:=csstDeclaration;
+end;
+
+destructor TCSSDeclarationElement.Destroy;
+begin
+  FreeAndNil(FKeys);
+  inherited Destroy;
+end;
+
+procedure TCSSDeclarationElement.AddKey(aKey: TCSSElement);
+begin
+  if aKey=Nil then exit;
+  if Not Assigned(FKeys) then
+    FKeys:=TCSSElementList.Create(Self);
+  FKeys.Add(aKey);
+end;
+
+function TCSSDeclarationElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSDeclarationElement absolute Obj;
+begin
+  if Obj is TCSSDeclarationElement then
+    begin
+    if (FIsImportant<>Src.FIsImportant)
+        or (FColon<>Src.FColon)
+        or (not CSSElementListEquals(FKeys,Src.FKeys)) then
+      exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSUnaryElement }
+
+class function TCSSUnaryElement.CSSType: TCSSType;
+begin
+  Result:=csstUnaryOp;
+end;
+
+function TCSSUnaryElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSUnaryElement absolute Obj;
+begin
+  if Obj is TCSSUnaryElement then
+    begin
+    if FOperation<>Src.FOperation then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+function TCSSUnaryElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+
+begin
+  Result:=UnaryOperators[Self.Operation];
+  if Not (Operation in [uoDoubleColon]) then
+    Result:=Result+' ';
+  if Assigned(Right) then
+    Result:=Result+Right.GetAsString(aFormat,aIndent);
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+{ TCSSRuleElement }
+
+function TCSSRuleElement.GetSelector(aIndex : Integer): TCSSElement;
+
+begin
+  if not assigned(FSelectors) then
+     Raise EListError.CreateFmt(SListIndexError,[aIndex]);
+  Result:=FSelectors.Elements[aIndex];
+end;
+
+
+function TCSSRuleElement.GetSelectorCount: Integer;
+
+begin
+  if Assigned(FSelectors) then
+    Result:=FSelectors.Count
+  else
+    Result:=0;
+end;
+
+
+function TCSSRuleElement.GetAsString(aFormat: Boolean; const aIndent: TCSSString
+  ): TCSSString;
+
+begin
+  Result:=DoGetAsString('',aFormat,aIndent);
+end;
+
+function TCSSRuleElement.DoGetAsString(const aPrefix: TCSSString;
+  aFormat: Boolean; const aIndent: TCSSString): TCSSString;
+
+var
+  I : Integer;
+  lIndent : TCSSString;
+
+begin
+  Result:='';
+  For I:=0 to SelectorCount-1 do
+    begin
+    if (I>0) then
+      begin
+      Result:=Result+',';
+      if aFormat then
+        Result:=Result+sLineBreak+aIndent
+      else
+        Result:=Result+' ';
+      end;
+    Result:=Result+Selectors[I].GetAsString(aFormat,aIndent);
+    end;
+  if (ChildCount=0) and (aPrefix<>'') then
+    Result:=aIndent+aPrefix+Result+';'
+  else
+    begin
+    if SelectorCount>0 then
+      Result:=Result+' ';
+    Result:=Result+'{';
+    lIndent:=aIndent;
+    if aFormat then
+      begin
+      lIndent:=lIndent+sIndent;
+      Result:=Result+sLineBreak;
+      end
+    else
+      Result:=Result+' ';
+    For I:=0 to ChildCount-1 do
+      begin
+      if (I>0) then
+        begin
+        if aFormat then
+          Result:=Result+sLineBreak
+        else
+          Result:=Result+' ';
+        end;
+      Result:=Result+Children[I].GetAsString(aFormat,lIndent)+';';
+      end;
+    if aFormat then
+      Result:=Result+sLineBreak+aIndent
+    else
+      Result:=Result+' ';
+    Result:=Result+'}';
+    Result:=aPrefix+Result;
+    if aFormat then
+      Result:=aIndent+Result;
+    end;
+end;
+
+procedure TCSSRuleElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  if Assigned(FSelectors) then
+    FSelectors.Iterate(aVisitor);
+  inherited IterateChildren(aVisitor);
+end;
+
+class function TCSSRuleElement.CSSType: TCSSType;
+begin
+  Result:=csstRule;
+end;
+
+destructor TCSSRuleElement.Destroy;
+begin
+  FreeAndNil(FSelectors);
+  Inherited Destroy;
+end;
+
+procedure TCSSRuleElement.AddSelector(aSelector: TCSSElement);
+begin
+  if Not Assigned(aSelector) then
+    exit;
+  if not Assigned(FSelectors) then
+    FSelectors:=TCSSElementList.Create(Self);
+  FSelectors.Add(aSelector);
+end;
+
+function TCSSRuleElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSRuleElement absolute Obj;
+begin
+  if Obj is TCSSRuleElement then
+    begin
+    if not CSSElementListEquals(FSelectors,Src.FSelectors) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSPseudoClassElement }
+
+function TCSSPseudoClassElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+
+Var
+  I : Integer;
+
+begin
+  if aFormat then ;
+  if aIndent='' then ;
+  I:=1;
+  if (Length(Value)>2) and (Value[2]=':') then
+    I:=2;
+  Result:=Copy(Value,1,I)+StringToIdentifier(Copy(Value,I+1,Length(Value)-I));
+end;
+
+class function TCSSPseudoClassElement.CSSType: TCSSType;
+begin
+  Result:=csstPseudoClass;
+end;
+
+{ TCSSChildrenElement }
+
+function TCSSChildrenElement.GetChild(aIndex : Integer): TCSSElement;
+begin
+  if not Assigned(FChildren) then
+    Raise EListError.CreateFmt(SListIndexError,[aIndex]);
+  Result:=FChildren[AIndex];
+end;
+
+function TCSSChildrenElement.GetChildCount: Integer;
+begin
+  if not Assigned(FChildren) then
+    Result:=0
+  else
+    Result:=FChildren.Count;
+end;
+
+procedure TCSSChildrenElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  inherited IterateChildren(aVisitor);
+  If Assigned(FChildren) then
+    FChildren.Iterate(aVisitor);
+end;
+
+destructor TCSSChildrenElement.Destroy;
+begin
+  FreeAndNil(FChildren);
+  inherited Destroy;
+end;
+
+
+procedure TCSSChildrenElement.AddChild(aChild: TCSSElement);
+begin
+  if Not Assigned(aChild) then
+    exit;
+  if FChildren=Nil then
+    FChildren:=TCSSElementList.Create(Self);
+  FChildren.Add(aChild);
+end;
+
+function TCSSChildrenElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSChildrenElement absolute Obj;
+begin
+  if Obj is TCSSChildrenElement then
+    begin
+    if not CSSElementListEquals(FChildren,Src.FChildren) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+
+{ TCSSCallElement }
+
+function TCSSCallElement.GetArg(aIndex : Integer): TCSSElement;
+begin
+  Result:=Children[AIndex];
+end;
+
+function TCSSCallElement.GetArgCount: Integer;
+begin
+  Result:=ChildCount;
+end;
+
+function TCSSCallElement.GetAsString(aFormat: Boolean; const aIndent: TCSSString
+  ): TCSSString;
+
+Var
+  I : Integer;
+
+begin
+  Result:=Name+'(';
+  For I:=0 to ChildCount-1 do
+    begin
+    if I>0 then
+      Result:=Result+', ';
+    Result:=Result+Children[I].GetAsString(aFormat,aIndent);
+    end;
+  Result:=Result+')';
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+class function TCSSCallElement.CSSType: TCSSType;
+begin
+  Result:=csstCall;
+end;
+
+procedure TCSSCallElement.AddArg(aArg: TCSSElement);
+begin
+  AddChild(aArg);
+end;
+
+function TCSSCallElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSCallElement absolute Obj;
+begin
+  if Obj is TCSSCallElement then
+    begin
+    if FName<>Src.FName then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSFloatElement }
+
+function TCSSFloatElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Str(Value:5:2,Result);
+  Result:=TrimLeft(Result); // Space for positive numbers
+  Result:=Result+CSSUnitNames[Units];
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+class function TCSSFloatElement.CSSType: TCSSType;
+begin
+  Result:=csstFloat;
+end;
+
+function TCSSFloatElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSFloatElement absolute Obj;
+begin
+  if Obj is TCSSFloatElement then
+    begin
+    if (FUnits<>Src.FUnits)
+        or (not SameValue(FValue,Src.FValue)) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSStringElement }
+
+function TCSSStringElement.GetChildren: TCSSElementList;
+begin
+  if FChildren=Nil then
+    FChildren:=TCSSElementList.Create(Self);
+  Result:=FChildren;
+end;
+
+function TCSSStringElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Result:=StringToCSSString(Value);
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+procedure TCSSStringElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  inherited IterateChildren(aVisitor);
+  if Assigned(FChildren) then
+    FChildren.Iterate(aVisitor);
+end;
+
+class function TCSSStringElement.CSSType: TCSSType;
+begin
+  Result:=csstString;
+end;
+
+destructor TCSSStringElement.Destroy;
+begin
+  FreeAndNil(FChildren);
+  inherited Destroy;
+end;
+
+function TCSSStringElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSStringElement absolute Obj;
+begin
+  if Obj is TCSSStringElement then
+    begin
+    if not CSSElementListEquals(FChildren,Src.FChildren) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSClassNameElement }
+
+function TCSSClassNameElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  if aFormat then ;
+  if aIndent='' then ;
+  Result:='.'+StringToIdentifier(Value);
+end;
+
+class function TCSSClassNameElement.CSSType: TCSSType;
+begin
+  Result:=csstClassname;
+end;
+
+{ TCSSIdentifierElement }
+
+function TCSSIdentifierElement.GetName: TCSSString;
+begin
+  Result:=Value;
+end;
+
+function TCSSIdentifierElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  if aFormat then ;
+  if aIndent='' then ;
+  Result:=StringToIdentifier(Value);
+end;
+
+class function TCSSIdentifierElement.CSSType: TCSSType;
+begin
+  Result:=csstIdentifier;
+end;
+
+{ TCSSHashIdentifierElement }
+
+function TCSSHashIdentifierElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  if aFormat then ;
+  if aIndent='' then ;
+  Result:='#'+StringToIdentifier(Value);
+end;
+
+class function TCSSHashIdentifierElement.CSSType: TCSSType;
+begin
+  Result:=csstHashIdentifier;
+end;
+
+{ TCSSArrayElement }
+
+procedure TCSSArrayElement.SetPrefix(AValue: TCSSElement);
+begin
+  if FPrefix=AValue then Exit;
+  FreeAndNil(FPrefix);
+  FPrefix:=AValue;
+end;
+
+function TCSSArrayElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+var
+  I : integer;
+begin
+  Result:='[';
+  For I:=0 to ChildCount-1 do
+    begin
+    if I>0 then
+      Result:=Result+' ';
+    Result:=Result+Children[I].GetAsString(aFormat, aIndent);
+    end;
+  Result:=aIndent+Result+']';
+end;
+
+destructor TCSSArrayElement.Destroy;
+begin
+  Prefix:=Nil;
+  inherited Destroy;
+end;
+
+class function TCSSArrayElement.CSSType: TCSSType;
+begin
+  Result:=csstArray;
+end;
+
+function TCSSArrayElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSArrayElement absolute Obj;
+begin
+  if Obj is TCSSArrayElement then
+    begin
+    if not SubElEquals(FPrefix,Src.FPrefix) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+
+{ TCSSElementList }
+
+function TCSSElementList.GetElement(aIndex : Integer): TCSSElement;
+begin
+  Result:=TCSSElement(FList[aIndex]);
+end;
+
+function TCSSElementList.GetCapacity: Integer;
+begin
+  Result:=FList.Capacity;
+end;
+
+function TCSSElementList.GetCount: Integer;
+begin
+  Result:=FList.Count;
+end;
+
+procedure TCSSElementList.SetCapacity(const AValue: Integer);
+begin
+  FList.Capacity:=AValue;
+end;
+
+constructor TCSSElementList.Create(ElParent: TCSSElement);
+begin
+  FElementParent:=ElParent;
+  FList:=TFPObjectList.Create(true);
+end;
+
+destructor TCSSElementList.Destroy;
+begin
+  FreeAndNil(FList);
+  inherited Destroy;
+end;
+
+procedure TCSSElementList.Clear;
+begin
+  FList.Clear;
+end;
+
+function TCSSElementList.Add(El: TCSSElement): Integer;
+begin
+  Result:=FList.Add(El);
+  El.Parent:=ElementParent;
+end;
+
+procedure TCSSElementList.Delete(Index: Integer);
+begin
+  FList.Delete(Index);
+end;
+
+function TCSSElementList.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSElementList absolute Obj;
+  i: Integer;
+begin
+  if Obj is TCSSElementList then
+    begin
+    Result:=false;
+    if Count<>Src.Count then
+      exit;
+    for i:=0 to Count-1 do
+      if not Elements[i].Equals(Src.Elements[i]) then
+        exit;
+    Result:=true;
+    end
+  else
+    Result:=inherited Equals(Obj);
+end;
+
+procedure TCSSElementList.Exchange(Index1, Index2: Integer);
+begin
+  FList.Exchange(Index1, Index2);
+end;
+
+function TCSSElementList.Extract(Index: Integer): TCSSElement;
+begin
+  Result:=TCSSElement(FList[Index]);
+  Result.Parent:=nil;
+  FList.OwnsObjects:=false;
+  try
+    FList.Delete(Index);
+  finally
+    FList.OwnsObjects:=true;
+  end;
+end;
+
+function TCSSElementList.IndexOf(El: TCSSElement): Integer;
+begin
+  Result:=FList.IndexOf(El);
+end;
+
+procedure TCSSElementList.Insert(Index: Integer; El: TCSSElement);
+begin
+  FList.Insert(Index,El);
+end;
+
+function TCSSElementList.First: TCSSElement;
+begin
+  Result:=TCSSElement(FList.First);
+end;
+
+function TCSSElementList.Last: TCSSElement;
+begin
+  Result:=TCSSElement(FList.Last);
+end;
+
+procedure TCSSElementList.Move(CurIndex, NewIndex: Integer);
+begin
+  FList.Move(CurIndex,NewIndex);
+end;
+
+procedure TCSSElementList.Assign(aList: TCSSElementList);
+begin
+  if Self=aList then exit;
+  FList.Assign(aList.FList);
+end;
+
+procedure TCSSElementList.Pack;
+begin
+  FList.Pack;
+end;
+
+procedure TCSSElementList.Sort(const Compare: TListSortCompare);
+begin
+  FList.Sort(Compare);
+end;
+
+procedure TCSSElementList.Iterate(aVisitor: TCSSTreeVisitor);
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Count-1 do
+    Elements[i].Iterate(aVisitor);
+end;
+
+{ TCSSIntegerElement }
+
+function TCSSIntegerElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Result:=IntToStr(Value)+CSSUnitNames[Units];
+  if aFormat then
+    Result:=aIndent+Result;
+end;
+
+class function TCSSIntegerElement.CSSType: TCSSType;
+begin
+  Result:=csstInteger;
+end;
+
+function TCSSIntegerElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSIntegerElement absolute Obj;
+begin
+  if Obj is TCSSIntegerElement then
+    begin
+    if (FIsEscaped<>Src.FIsEscaped)
+        or (FUnits<>Src.FUnits)
+        or (FValue<>Src.FValue) then
+      exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+{ TCSSBinaryElement }
+
+procedure TCSSBinaryElement.SetLeft(AValue: TCSSElement);
+begin
+  if FLeft=AValue then Exit;
+  if FLeft<>nil then
+    FLeft.Parent:=nil;
+  FreeAndNil(FLeft);
+  FLeft:=AValue;
+  if FLeft<>nil then
+    FLeft.Parent:=Self;
+end;
+
+function TCSSBinaryElement.GetAsString(aFormat: Boolean;
+  const aIndent: TCSSString): TCSSString;
+begin
+  Result:='';
+  if Assigned(Left) then
+    Result:=Left.GetAsString(aFormat,aIndent);
+  if Not (Operation in [boColon,boDoubleColon]) then
+    Result:=Result+' '+BinaryOperators[Operation]+' '
+  else
+    Result:=Result+BinaryOperators[Operation];
+  if Assigned(Right) then
+    Result:=Result+Right.GetAsString(aFormat,aIndent);
+end;
+
+destructor TCSSBinaryElement.Destroy;
+begin
+  Left:=Nil;
+  inherited Destroy;
+end;
+
+class function TCSSBinaryElement.CSSType: TCSSType;
+begin
+  Result:=csstBinaryOp;
+end;
+
+function TCSSBinaryElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSBinaryElement absolute Obj;
+begin
+  if Obj is TCSSBinaryElement then
+    begin
+    if FOperation<>Src.FOperation then exit(false);
+    if not SubElEquals(FLeft,Src.FLeft) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+procedure TCSSBinaryElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  inherited IterateChildren(aVisitor);
+  if Assigned(FLeft) then
+    FLeft.Iterate(aVisitor);
+end;
+
+{ TCSSUnaryElement }
+
+procedure TCSSBaseUnaryElement.SetRight(AValue: TCSSElement);
+begin
+  if FRight=AValue then Exit;
+  if FRight<>nil then
+    FRight.Parent:=nil;
+  FreeAndNil(FRight);
+  FRight:=AValue;
+  if FRight<>nil then
+    FRight.Parent:=Self;
+end;
+
+destructor TCSSBaseUnaryElement.Destroy;
+begin
+  Right:=Nil;
+  inherited Destroy;
+end;
+
+function TCSSBaseUnaryElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSBaseUnaryElement absolute Obj;
+begin
+  if Obj is TCSSBaseUnaryElement then
+    begin
+    if not SubElEquals(FRight,Src.FRight) then exit(false);
+    end;
+  Result:=inherited Equals(Obj);
+end;
+
+procedure TCSSBaseUnaryElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  inherited IterateChildren(aVisitor);
+  If Assigned(FRight) then
+    FRight.Iterate(aVisitor);
+end;
+
+{ TCSSElement }
+
+function TCSSElement.GetAsUnFormattedString: TCSSString;
+begin
+  Result:=GetAsString(False,'');
+end;
+
+function TCSSElement.GetAsFormattedString: TCSSString;
+begin
+  Result:=GetAsString(True,'');
+end;
+
+procedure TCSSElement.SetParent(const AValue: TCSSElement);
+begin
+  if FParent=AValue then Exit;
+  FParent:=AValue;
+end;
+
+function TCSSElement.GetAsString(aFormat: Boolean; const aIndent: TCSSString
+  ): TCSSString;
+begin
+  if aFormat then ;
+  if aIndent='' then ;
+  Result:='';
+end;
+
+function TCSSElement.SubElEquals(ElA, ElB: TCSSElement): boolean;
+begin
+  if ElA=nil then
+    Result:=ElB=nil
+  else
+    begin
+    if ElB=nil then exit(false);
+    Result:=ElA.Equals(ElB);
+    end;
+end;
+
+procedure TCSSElement.IterateChildren(aVisitor: TCSSTreeVisitor);
+begin
+  if Assigned(aVisitor) then ;
+end;
+
+constructor TCSSElement.Create(const aFileName: TCSSString; aRow, aCol: Integer);
+begin
+  FFileName:=aFileName;
+  FRow:=aRow;
+  FCol:=aCol;
+end;
+
+destructor TCSSElement.Destroy;
+begin
+  if FData is TCSSElementOwnedData then
+  begin
+    FData.Free;
+    FData:=nil;
+  end;
+  inherited Destroy;
+end;
+
+class function TCSSElement.CSSType: TCSSType;
+begin
+  Result:=csstUnknown;
+end;
+
+function TCSSElement.Equals(Obj: TObject): boolean;
+var
+  Src: TCSSElement absolute Obj;
+begin
+  if Obj is TCSSElement then
+    begin
+    Result:=(FCol=Src.FCol)
+        and (FData=Src.FData)
+        and (FFileName=Src.FFileName)
+        and (FRow=Src.FRow);
+    end
+  else
+    Result:=inherited Equals(Obj);
+end;
+
+procedure TCSSElement.Iterate(aVisitor: TCSSTreeVisitor);
+begin
+  aVisitor.Visit(Self);
+  IterateChildren(aVisitor);
+end;
+
+procedure TCSSElement.FreeCustomData;
+var
+  Visitor: TCSSVisitorFreeCustomData;
+begin
+  Visitor:=TCSSVisitorFreeCustomData.Create;
+  try
+    Iterate(Visitor);
+  finally
+    Visitor.Free;
+  end;
+end;
+
+{ TCSSVisitorFreeCustomData }
+
+procedure TCSSVisitorFreeCustomData.Visit(obj: TCSSElement);
+var
+  d: TObject;
+begin
+  if obj.CustomData=nil then exit;
+  d:=obj.CustomData;
+  obj.CustomData:=nil;
+  d.Free;
+end;
+
+end.
+

+ 207 - 0
src/base/fcl-css/fpcssutils.pp

@@ -0,0 +1,207 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2022 by Michael Van Canneyt ([email protected])
+
+    This file contains CSS utility class
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpcssutils;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode objfpc}{$H+}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses
+  System.TypInfo, System.Classes, System.SysUtils, System.Types, FPCSS.Tree, FPCSS.Parser, FPCSS.Scanner;
+{$ELSE FPC_DOTTEDUNITS}
+uses
+  TypInfo, Classes, SysUtils, types, fpcsstree, fpcssparser, fpcssscanner;
+{$ENDIF FPC_DOTTEDUNITS}
+
+Type
+
+  { TClassNameVisitor }
+
+  TClassNameVisitor = Class(TCSSTreeVisitor)
+  private
+    FList: TStrings;
+  public
+    Constructor Create(aList: TStrings);
+    Procedure Visit(obj: TCSSElement); override;
+    property List : TStrings Read FList;
+  end;
+
+  { TCSSUtils }
+
+  TCSSUtils = class(TComponent)
+  private
+    FExtraScannerOptions: TCSSScannerOptions;
+  published
+    Procedure ExtractClassNames(Const aFileName : String; aList : TStrings);
+    Procedure ExtractClassNames(Const aStream : TStream; aList : TStrings);
+    Procedure ExtractClassNames(Const aElement : TCSSElement; aList : TStrings);
+    Function ExtractClassNames(Const aFileName : String) : TStringDynArray;
+    Function ExtractClassNames(Const aStream : TStream) : TStringDynArray;
+    Function ExtractClassNames(Const aElement : TCSSElement) : TStringDynArray;
+    Procedure Minimize(aInput,aOutput : TStream);
+    Property ExtraScannerOptions : TCSSScannerOptions Read FExtraScannerOptions Write FExtraScannerOptions;
+  end;
+
+implementation
+
+{ TClassNameVisitor }
+
+constructor TClassNameVisitor.Create(aList: TStrings);
+begin
+  FList:=aList;
+end;
+
+procedure TClassNameVisitor.Visit(obj: TCSSElement);
+begin
+  if Obj.CSSType=csstCLASSNAME then
+    FList.Add(Obj.AsString);
+end;
+
+{ TCSSUtils }
+
+procedure TCSSUtils.ExtractClassNames(const aFileName: String; aList: TStrings);
+
+Var
+  S : TStringStream;
+
+begin
+  S:=TStringStream.Create;
+  try
+    S.LoadFromFile(aFileName);
+    ExtractClassNames(S,aList);
+  finally
+    S.Free;
+  end;
+end;
+
+function TCSSUtils.ExtractClassNames(const aFileName: String): TStringDynArray;
+
+Var
+  L : TStrings;
+
+begin
+  L:=TStringList.Create;
+  try
+    ExtractClassNames(aFileName,L);
+    Result:=L.ToStringArray;
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TCSSUtils.ExtractClassNames(const aStream: TStream; aList: TStrings);
+
+Var
+  aParser : TCSSParser;
+  aElement : TCSSElement;
+
+begin
+  aElement:=Nil;
+  aParser:=TCSSParser.Create(aStream,ExtraScannerOptions);
+  try
+    aElement:=aParser.Parse;
+    ExtractClassNames(aElement,aList);
+  finally
+    aElement.Free;
+    aParser.Free;
+  end;
+end;
+
+procedure TCSSUtils.ExtractClassNames(const aElement: TCSSElement; aList: TStrings);
+
+Var
+  aVis : TClassNameVisitor;
+
+begin
+  aVis:=TClassNameVisitor.Create(aList);
+  try
+    aElement.Iterate(aVis);
+  finally
+    aVis.Free;
+  end;
+end;
+
+function TCSSUtils.ExtractClassNames(const aStream: TStream): TStringDynArray;
+
+Var
+  L : TStrings;
+
+begin
+  L:=TStringList.Create;
+  try
+    ExtractClassNames(aStream,L);
+    Result:=L.ToStringArray;
+  finally
+    L.Free;
+  end;
+end;
+
+function TCSSUtils.ExtractClassNames(const aElement: TCSSElement): TStringDynArray;
+
+Var
+  L : TStrings;
+
+begin
+  L:=TStringList.Create;
+  try
+    ExtractClassNames(aElement,L);
+    Result:=L.ToStringArray;
+  finally
+    L.Free;
+  end;
+
+end;
+
+procedure TCSSUtils.Minimize(aInput, aOutput: TStream);
+
+Var
+  aScanner : TCSSScanner;
+  aToken,aPreviousToken : TCSSToken;
+  S : UTF8String;
+
+begin
+  aPrevioustoken:=ctkWHITESPACE;
+  AScanner:=TCSSScanner.Create(aInput);
+  try
+    aScanner.ReturnWhiteSpace:=True;
+    aToken:=aScanner.FetchToken;
+    While (aToken<>ctkEOF) do
+      begin
+      if aToken=ctkSTRING then
+        S:=StringToCSSString(aScanner.CurTokenString)
+      else if aToken<>ctkWHITESPACE then
+        S:=aScanner.CurTokenString
+      else if aPreviousToken<>ctkWHITESPACE then
+        S:=' '
+      else
+        S:='';
+      // writeln(GetEnumName(TypeInfo(TCSSTOKEN),Ord(aToken)),' -> S : >',S,'<');
+      if S<>'' then
+        aOutput.WriteBuffer(S[1],length(S));
+      aPreviousToken:=aToken;
+      aToken:=aScanner.FetchToken;
+      end;
+
+  finally
+    aScanner.Free;
+  end;
+end;
+
+end.
+

+ 65 - 4
src/base/fcl.events.pas

@@ -2,7 +2,11 @@ unit FCL.Events;
 
 {$mode ObjFPC}
 {$H+}
-{$modeswitch functionreferences}
+
+{$IF FPC_FULLVERSION>30300}
+  {$DEFINE HasFunctionReferences}
+  {$modeswitch functionreferences}
+{$ENDIF}
 
 interface
 
@@ -36,7 +40,9 @@ Type
 
   TEventHandler = Procedure(Event : TAbstractEvent) of object;
   TEventCallBack = Procedure(Event : TAbstractEvent);
+  {$IFDEF HasFunctionReferences}
   TEventHandlerRef = Reference to Procedure(Event : TAbstractEvent);
+  {$ENDIF}
 
   { TEventDef }
 
@@ -116,7 +122,9 @@ Type
     Procedure CallHandler(aEvent : TAbstractEvent); virtual; abstract;
     function Match(aEVent : TAbstractEvent) : Boolean; virtual;
     function MatchHandler(aHandler : TEventHandler; aEventID: TEventID) : Boolean; virtual;
+    {$IFDEF HasFunctionReferences}
     function MatchHandler(aHandler : TEventHandlerRef; aEventID: TEventID) : Boolean; virtual;
+    {$ENDIF}
     function MatchHandler(aHandler : TEventCallBack; aEventID: TEventID) : Boolean; virtual;
     Procedure ScheduleDelete; virtual;
   Public
@@ -143,7 +151,9 @@ Type
     Function GetEventName(aID : TEventID) : TEVentName;
     Function FindHandler(aHandler : TEventHandler; aEventID : TEventID) : TEventHandlerItem;
     Function FindHandler(aHandler : TEventCallBack; aEventID : TEventID) : TEventHandlerItem;
+    {$IFDEF HasFunctionReferences}
     Function FindHandler(aHandler : TEventHandlerRef; aEventID : TEventID) : TEventHandlerItem;
+    {$ENDIF}
     Function ContinueEvent(aEvent : TAbstractEvent) : Boolean; virtual;
     Procedure DeleteScheduled;
   Public
@@ -178,6 +188,7 @@ Type
     Property EventHandler : TEventCallback Read FEventHandler;
   end;
 
+  {$IFDEF HasFunctionReferences}
   { TReferenceEventHandlerItem }
 
   TReferenceEventHandlerItem = Class(TEventHandlerItem)
@@ -189,13 +200,15 @@ Type
   Public
      Property EventHandler : TEventHandlerRef Read FEventHandler;
   end;
-
+  {$ENDIF}
 
   { TEventDispatcher }
 
   TEventSetupHandler = Procedure(Event : TAbstractEvent) of object;
   TEventSetupCallBack = Procedure(Event : TAbstractEvent);
+  {$IFDEF HasFunctionReferences}
   TEventSetupHandlerRef = Reference to Procedure(Event : TAbstractEvent);
+  {$ENDIF}
 
   // This class can be used by any other class to send events on its behalf.
 
@@ -212,20 +225,28 @@ Type
     Function CreateHandlerList : TEventHandlerList; virtual;
     Function CreateHandlerItem(aHandler : TEventHandler) : TEventHandlerItem; virtual;
     Function CreateHandlerItem(aHandler : TEventCallBack) : TEventHandlerItem; virtual;
+    {$IFDEF HasFunctionReferences}
     Function CreateHandlerItem(aHandler : TEventHandlerRef) : TEventHandlerItem; virtual;
+    {$ENDIF}
     Function DoRegisterHandler(aHandler : TEventHandler; aEventID : TEventID) : TEventHandlerItem; virtual;
     Function DoRegisterHandler(aHandler : TEventCallBack; aEventID : TEventID) : TEventHandlerItem; virtual;
+    {$IFDEF HasFunctionReferences}
     Function DoRegisterHandler(aHandler : TEventHandlerRef; aEventID : TEventID) : TEventHandlerItem; virtual;
+    {$ENDIF}
   Public
     Constructor Create(aDefaultSender : TObject); virtual;
     Destructor Destroy; override;
     // Various forms to register an event handler
     Function RegisterHandler(aHandler : TEventHandler; aEventID : TEventID) : TEventHandlerItem;
     Function RegisterHandler(aHandler : TEventCallBack; aEventID : TEventID) : TEventHandlerItem;
+    {$IFDEF HasFunctionReferences}
     Function RegisterHandler(aHandler : TEventHandlerRef; aEventID : TEventID) : TEventHandlerItem;
+    {$ENDIF}
     Function RegisterHandler(aHandler : TEventCallback; aEventName : TEventName) : TEventHandlerItem;
     Function RegisterHandler(aHandler : TEventHandler; aEventName : TEventName) : TEventHandlerItem;
+    {$IFDEF HasFunctionReferences}
     Function RegisterHandler(aHandler : TEventHandlerRef; aEventName : TEventName) : TEventHandlerItem;
+    {$ENDIF}
     // Remove all event handlers for a given ID/name.
     Procedure UnregisterHandler(aEventID : TEventID);
     Procedure UnregisterHandler(aEventName : TEventName);
@@ -233,17 +254,23 @@ Type
     // Remove all event handlers for a given callback
     Procedure UnRegisterHandler(aHandler : TEventHandler);
     Procedure UnRegisterHandler(aHandler : TEventCallBack);
+    {$IFDEF HasFunctionReferences}
     Procedure UnRegisterHandler(aHandler : TEventHandlerRef);
+    {$ENDIF}
     // Return single handler using it's ID
     Procedure UnregisterHandler(aItem : TEventHandlerItem);
     // Remove single handler using combination of handler/event ID
     Procedure UnRegisterHandler(aHandler : TEventHandler; aEventID : TEventID);
     Procedure UnRegisterHandler(aHandler : TEventCallBack; aEventID : TEventID);
+    {$IFDEF HasFunctionReferences}
     Procedure UnRegisterHandler(aHandler : TEventHandlerRef; aEventID : TEventID);
+    {$ENDIF}
     // Remove single handler using combination of handler/event name
     Procedure UnRegisterHandler(aHandler : TEventCallback; aEventName : TEventName);
     Procedure UnRegisterHandler(aHandler : TEventHandler; aEventName : TEventName);
+    {$IFDEF HasFunctionReferences}
     Procedure UnRegisterHandler(aHandler : TEventHandlerRef; aEventName : TEventName);
+    {$ENDIF}
     // Create an event
     Function CreateEvent(aSender: TObject; aEventID : TEventID) : TAbstractEvent;
     Function CreateEvent(aSender: TObject; aEventName : TEventName) : TAbstractEvent;
@@ -256,18 +283,26 @@ Type
     Function DispatchEvent(aEventID : TEventID; aSender : TObject) : Integer;
     Function DispatchEvent(aEventID : TEventID; aSender : TObject; aOnSetup : TEventSetupHandler) : Integer;
     Function DispatchEvent(aEventID : TEventID; aSender : TObject; aOnSetup : TEventSetupCallBack) : Integer;
+    {$IFDEF HasFunctionReferences}
     Function DispatchEvent(aEventID : TEventID; aSender : TObject; aOnSetup : TEventSetupHandlerRef) : Integer;
+    {$ENDIF}
     Function DispatchEvent(aEventID : TEventID; aOnSetup : TEventSetupHandler) : Integer;
     Function DispatchEvent(aEventID : TEventID; aOnSetup : TEventSetupCallBack) : Integer;
+    {$IFDEF HasFunctionReferences}
     Function DispatchEvent(aEventID : TEventID; aOnSetup : TEventSetupHandlerRef) : Integer;
+    {$ENDIF}
     Function DispatchEvent(aEventName : TEventName) : Integer;
     Function DispatchEvent(aEventName : TEventName; aSender : TObject) : Integer;
     Function DispatchEvent(aEventName : TEventName; aSender : TObject; aOnSetup : TEventSetupHandler) : Integer;
     Function DispatchEvent(aEventName : TEventName; aSender : TObject; aOnSetup : TEventSetupCallBack) : Integer;
+    {$IFDEF HasFunctionReferences}
     Function DispatchEvent(aEventName : TEventName; aSender : TObject; aOnSetup : TEventSetupHandlerRef) : Integer;
+    {$ENDIF}
     Function DispatchEvent(aEventName : TEventName; aOnSetup : TEventSetupHandler) : Integer;
     Function DispatchEvent(aEventName : TEventName; aOnSetup : TEventSetupCallBack) : Integer;
+    {$IFDEF HasFunctionReferences}
     Function DispatchEvent(aEventName : TEventName; aOnSetup : TEventSetupHandlerRef) : Integer;
+    {$ENDIF}
     Property DefaultSender : TObject Read FDefaultSender;
     Property Count : Integer Read GetCount;
     Property Registry : TEventRegistry Read GetRegistry Write FRegistry;
@@ -346,6 +381,7 @@ begin
   end;
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventHandlerList.FindHandler(aHandler: TEventHandlerRef;
   aEventID: TEventID): TEventHandlerItem;
 Var
@@ -364,6 +400,7 @@ begin
     EndTraverse;
   end;
 end;
+{$ENDIF}
 
 function TEventHandlerList.ContinueEvent(aEvent: TAbstractEvent): Boolean;
 begin
@@ -454,11 +491,13 @@ begin
   Result:=(FEventID=aEventID) and (aHandler=Nil);
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventHandlerItem.MatchHandler(aHandler: TEventHandlerRef;
   aEventID: TEventID): Boolean;
 begin
   Result:=(FEventID=aEventID) and (aHandler=Nil);
 end;
+{$ENDIF}
 
 function TEventHandlerItem.MatchHandler(aHandler: TEventCallBack;
   aEventID: TEventID): Boolean;
@@ -472,6 +511,7 @@ begin
 end;
 
 
+{$IFDEF HasFunctionReferences}
 
 { TReferenceEventHandlerItem }
 
@@ -485,7 +525,7 @@ procedure TReferenceEventHandlerItem.CallHandler(aEvent: TAbstractEvent);
 begin
   FEventHandler(aEvent);
 end;
-
+{$ENDIF}
 
 { TCallBackEventHandlerItem }
 
@@ -557,12 +597,14 @@ begin
   TCallBackEventHandlerItem(Result).FEventHandler:=aHandler;
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.CreateHandlerItem(aHandler: TEventHandlerRef
   ): TEventHandlerItem;
 begin
   Result:=TReferenceEventHandlerItem.Create(FHandlerList);
   TReferenceEventHandlerItem(Result).FEventHandler:=aHandler;
 end;
+{$ENDIF}
 
 function TEventDispatcher.DoRegisterHandler(aHandler: TEventHandler;
   aEventID: TEventID): TEventHandlerItem;
@@ -579,12 +621,14 @@ begin
   Result.FEventID:=aEventID;
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.DoRegisterHandler(aHandler: TEventHandlerRef;
   aEventID: TEventID): TEventHandlerItem;
 begin
   Result:=CreateHandlerItem(aHandler);
   Result.FEventID:=aEventID;
 end;
+{$ENDIF}
 
 constructor TEventDispatcher.Create(aDefaultSender: TObject);
 begin
@@ -611,11 +655,13 @@ begin
   Result:=DoRegisterHandler(aHandler,aEventID);
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.RegisterHandler(aHandler: TEventHandlerRef;
   aEventID: TEventID): TEventHandlerItem;
 begin
   Result:=DoRegisterHandler(aHandler,aEventID);
 end;
+{$ENDIF}
 
 function TEventDispatcher.RegisterHandler(aHandler: TEventCallback;
   aEventName: TEventName): TEventHandlerItem;
@@ -629,12 +675,14 @@ begin
   Result:=DoRegisterHandler(aHandler,Registry.GetEventID(aEventName));
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.RegisterHandler(aHandler: TEventHandlerRef;
   aEventName: TEventName): TEventHandlerItem;
 begin
   Result:=DoRegisterHandler(aHandler,Registry.GetEventID(aEventName));
 
 end;
+{$ENDIF}
 
 procedure TEventDispatcher.UnregisterHandler(aItem: TEventHandlerItem);
 begin
@@ -698,6 +746,7 @@ begin
     end;
 end;
 
+{$IFDEF HasFunctionReferences}
 procedure TEventDispatcher.UnRegisterHandler(aHandler: TEventHandlerRef);
 Var
   I : Integer;
@@ -711,6 +760,7 @@ begin
       FHandlerList.UnregisterHandler(Itm);
     end;
 end;
+{$ENDIF}
 
 procedure TEventDispatcher.UnRegisterHandler(aHandler: TEventHandler;
   aEventID: TEventID);
@@ -734,6 +784,7 @@ begin
   UnregisterHandler(aItem);
 end;
 
+{$IFDEF HasFunctionReferences}
 procedure TEventDispatcher.UnRegisterHandler(aHandler: TEventHandlerRef;
   aEventID: TEventID);
 
@@ -744,6 +795,7 @@ begin
   aItem:=FHandlerList.FindHandler(aHandler,aEventID);
   UnregisterHandler(aItem);
 end;
+{$ENDIF}
 
 procedure TEventDispatcher.UnRegisterHandler(aHandler: TEventCallback;
   aEventName: TEventName);
@@ -757,11 +809,13 @@ begin
   UnregisterHandler(aHandler,Registry.FindEventID(aEventName));
 end;
 
+{$IFDEF HasFunctionReferences}
 procedure TEventDispatcher.UnRegisterHandler(aHandler: TEventHandlerRef;
   aEventName: TEventName);
 begin
   UnregisterHandler(aHandler,Registry.FindEventID(aEventName));
 end;
+{$ENDIF}
 
 function TEventDispatcher.CreateEvent(aSender: TObject; aEventID: TEventID
   ): TAbstractEvent;
@@ -801,12 +855,14 @@ begin
   Result:=DispatchEvent(aEventID,DefaultSender,aOnSetup);
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.DispatchEvent(aEventID: TEventID; aOnSetup: TEventSetupHandlerRef): Integer;
 begin
   If DefaultSender=Nil then
     Raise EEvents.Create('Cannot dispatch without sender: defaultsender not set');
   Result:=DispatchEvent(aEventID,DefaultSender,aOnSetup);
 end;
+{$ENDIF}
 
 function TEventDispatcher.DispatchEvent(aEventName: TEventName): Integer;
 begin
@@ -828,10 +884,12 @@ begin
   Result:=DispatchEvent(Registry.GetEventID(aEventName),aSender,aOnSetup);
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.DispatchEvent(aEventName: TEventName; aSender: TObject; aOnSetup: TEventSetupHandlerRef): Integer;
 begin
   Result:=DispatchEvent(Registry.GetEventID(aEventName),aSender,aOnSetup);
 end;
+{$ENDIF}
 
 function TEventDispatcher.DispatchEvent(aEventName: TEventName; aOnSetup: TEventSetupHandler): Integer;
 begin
@@ -843,10 +901,12 @@ begin
   Result:=DispatchEvent(Registry.GetEventID(aEventName),aOnSetup);
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.DispatchEvent(aEventName: TEventName; aOnSetup: TEventSetupHandlerRef): Integer;
 begin
   Result:=DispatchEvent(Registry.GetEventID(aEventName),aOnSetup);
 end;
+{$ENDIF}
 
 function TEventDispatcher.DispatchEvent(aEventID: TEventID): Integer;
 begin
@@ -894,6 +954,7 @@ begin
   end;
 end;
 
+{$IFDEF HasFunctionReferences}
 function TEventDispatcher.DispatchEvent(aEventID: TEventID; aSender: TObject;
   aOnSetup: TEventSetupHandlerRef): Integer;
 Var
@@ -909,7 +970,7 @@ begin
     Evt.Free;
   end;
 end;
-
+{$ENDIF}
 
 { TEventDef }
 

+ 7 - 2
src/base/fresnel.classes.pas

@@ -1,15 +1,20 @@
 unit Fresnel.Classes;
 
 {$mode objfpc}{$H+}
-{$WARN 6060 off} // Case statement does not handle all possible cases
+{$IF FPC_FULLVERSION>=30301}
+  {$WARN 6060 off} // Case statement does not handle all possible cases
+{$ENDIF}
 {$ModeSwitch AdvancedRecords}
 
 interface
 
 uses
-  Classes, SysUtils, Math, Types;
+  Classes, SysUtils, Math, Types, fpCSSScanner;
 
 type
+  {$IF FPC_FULLVERSION<30301}
+  RTLString = string;
+  {$ENDIF}
 
   { EFresnel }
 

+ 18 - 3
src/base/fresnel.dom.pas

@@ -27,7 +27,10 @@ unit Fresnel.DOM;
 interface
 
 uses
-  Classes, SysUtils, Math, FPImage, sortbase,
+  Classes, SysUtils, Math, FPImage,
+  {$IF FPC_FULLVERSION>30300}
+  sortbase,
+  {$ENDIF}
   fpCSSResolver, fpCSSTree, fpCSSParser, FCL.Events,
   Fresnel.Classes, Fresnel.Events;
 
@@ -288,7 +291,11 @@ type
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
     function GetRoot: TFresnelLayoutNode;
-    procedure SortNodes(const Compare: TListSortComparer_Context; Context: Pointer); virtual;
+    {$IF FPC_FULLVERSION>=30301}
+    procedure SortNodes(const Compare: TListSortComparer_Context; Context: Pointer = nil); virtual;
+    {$ELSE}
+    procedure SortNodes(const Compare: TListSortCompare); virtual;
+    {$ENDIF}
     property Parent: TFresnelLayoutNode read FParent write SetParent;
     property Element: TFresnelElement read FElement write SetElement;
     property NodeCount: integer read GetNodeCount;
@@ -1069,11 +1076,19 @@ begin
     Result:=Result.Parent;
 end;
 
+{$IF FPC_FULLVERSION>=30301}
 procedure TFresnelLayoutNode.SortNodes(
   const Compare: TListSortComparer_Context; Context: Pointer);
 begin
   FNodes.Sort(Compare,Context,SortBase.DefaultSortingAlgorithm);
 end;
+{$ELSE}
+procedure TFresnelLayoutNode.SortNodes(
+  const Compare: TListSortCompare);
+begin
+  FNodes.Sort(Compare);
+end;
+{$ENDIF}
 
 { TFresnelFontDesc }
 
@@ -1508,7 +1523,7 @@ begin
   FCSSResolver:=TCSSResolver.Create(nil);
   FCSSResolver.OwnsStyle:=false;
   FCSSResolver.OnLog:=@CSSResolverLog;
-  FStylesheet:=TStringList.Create(false);
+  FStylesheet:=TStringList.Create{$IF FPC_FULLVERSION>=30301}(false){$ENDIF};
   FStylesheet.FPOAttachObserver(Self);
 
   if not FFresnelEventsRegistered then

+ 6 - 0
src/base/fresnel.events.pas

@@ -85,13 +85,19 @@ Type
 
   TFresnelEventHandler = TEventHandler;
   TFresnelEventCallBack = TEventCallBack;
+  {$IFDEF HasFunctionReferences}
   TFresnelEventHandlerRef = TEventHandlerRef;
+  {$ENDIF}
 
   TFresnelUIEvent = class(TFresnelEvent)
 
   end;
 
+  {$IF FPC_FULLVERSION>30300}
   TMouseButton = System.UITypes.TMouseButton;
+  {$ELSE}
+  TMouseButton = (mbLeft, mbRight, mbMiddle, mbExtra1, mbExtra2);
+  {$ENDIF}
   TMouseButtons = set of TMouseButton;
 
 Const

+ 2 - 2
src/base/fresnel.images.pas

@@ -1196,14 +1196,14 @@ var
   Function GetWord: word; inline;
 
   begin
-    HexToBin(PAnsiChar(@(S[N])),Result{%H-},2);
+    HexToBin(PAnsiChar(@(S[N])),@Result{%H-},2);
     Inc(N,4);
   end;
 
   Function GetByte: word; inline;
 
   begin
-    HexToBin(PChar(@(S[N])),Result{%H-},1);
+    HexToBin(PChar(@(S[N])),@Result{%H-},1);
     Inc(N,2);
   end;
 

+ 3 - 3
src/base/fresnel.layouter.pas

@@ -194,11 +194,11 @@ type
     // font-style  normal italic oblique bold
   end;
 
-function CompareLayoutNodesZIndexDomIndex(Item1, Item2, {%H-}Context: Pointer): integer;
+function CompareLayoutNodesZIndexDomIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
 
 implementation
 
-function CompareLayoutNodesZIndexDomIndex(Item1, Item2, Context: Pointer): integer;
+function CompareLayoutNodesZIndexDomIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
 var
   Node1: TSimpleFresnelLayoutNode absolute Item1;
   Node2: TSimpleFresnelLayoutNode absolute Item2;
@@ -1387,7 +1387,7 @@ procedure TViewportLayouter.SortStackingContext(LNode: TSimpleFresnelLayoutNode
 
 begin
   if IsSorted then exit;
-  LNode.SortNodes(@CompareLayoutNodesZIndexDomIndex,nil);
+  LNode.SortNodes(@CompareLayoutNodesZIndexDomIndex);
   {$IFDEF VerboseFresnelLayouter}
   if not IsSorted then
     raise Exception.Create('20221031180116 TSimpleFresnelLayouter.SortLayoutNodes');

+ 27 - 2
src/base/fresnelbase.lpk

@@ -6,7 +6,7 @@
     <CompilerOptions>
       <Version Value="11"/>
       <SearchPaths>
-        <OtherUnitFiles Value="$(FPCSrcDir)/packages/fcl-css/src;css;lcl"/>
+        <OtherUnitFiles Value="fcl-css"/>
         <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
       </SearchPaths>
       <Parsing>
@@ -80,7 +80,32 @@
       </Item>
       <Item>
         <Filename Value="fresnel.asynccalls.pp"/>
-        <UnitName Value="fresnel.asynccalls"/>
+        <UnitName Value="Fresnel.AsyncCalls"/>
+      </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcssparser.pp"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpCSSParser"/>
+      </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcssresolver.pas"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpCSSResolver"/>
+      </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcssscanner.pp"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpCSSScanner"/>
+      </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcsstree.pp"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpCSSTree"/>
+      </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcssutils.pp"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpcssutils"/>
       </Item>
     </Files>
     <UsageOptions>

+ 1 - 1
src/fresnel.pas

@@ -1,7 +1,7 @@
 unit Fresnel;
 
 {$IF FPC_FULLVERSION < 30301}
-  {$ERROR Fresnel requires at least FPC 3.3.1}
+  { $ERROR Fresnel requires at least FPC 3.3.1}
 {$ENDIF}
 
 interface

+ 1 - 1
src/lcl/fresnel.lcl.pas

@@ -279,7 +279,7 @@ class procedure TLCLWSForm.InitMouseXYEvent(out EvtInit: TFresnelMouseEventInit;
 
 begin
   EvtInit:=Default(TFresnelMouseEventInit);
-  evtInit.Button:=Button;
+  evtInit.Button:=Fresnel.Events.TMouseButton(Button);
   if ssLeft in Shift then
     Include(EvtInit.Buttons,mbLeft);
   if ssMiddle in Shift then

+ 5 - 8
src/lcl/fresnel.lclevents.pas

@@ -25,9 +25,9 @@ Type
     procedure DoEnter(aInit : TFresnelMouseEventInit; aOld, aNew: TFresnelElement);
     procedure DoLeave(aInit : TFresnelMouseEventInit; aNew, aOld: TFresnelElement);
     procedure HandleClick(Sender: TObject);
-    procedure HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+    procedure HandleMouseDown(Sender: TObject; Button: Controls.TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
-    procedure HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+    procedure HandleMouseUp(Sender: TObject; Button: Controls.TMouseButton; Shift: TShiftState; X, Y: Integer);
   Public
     Constructor Create(aOwner : TComponent; AControl : TWinControl; AViewPort: TFresnelViewPort);  reintroduce;
     Procedure HookEvents;
@@ -54,10 +54,8 @@ begin
 end;
 
 procedure TFresnelLCLEventControl.HookEvents;
-
 var
   C : THC;
-
 begin
   if not Assigned(Control) then
     exit;
@@ -66,7 +64,6 @@ begin
   C.OnMouseDown:=@HandleMouseDown;
   C.OnMouseUp:=@HandleMouseUp;
   C.OnMouseMove:=@HandleMouseMove;
-
 end;
 
 procedure TFresnelLCLEventControl.InitMouseEvent(out aInit: TFresnelMouseEventInit);
@@ -106,7 +103,7 @@ begin
   end;
 end;
 
-procedure TFresnelLCLEventControl.HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+procedure TFresnelLCLEventControl.HandleMouseDown(Sender: TObject; Button: Controls.TMouseButton; Shift: TShiftState; X, Y: Integer);
 Var
   aInit : TFresnelMouseEventInit;
   aEl : TFresnelElement;
@@ -213,7 +210,7 @@ Var
   evt : TFresnelMouseEvent;
 
 begin
-  TLCLWSForm.InitMouseXYEvent(aInit,Shift,X,Y,mbLeft);
+  TLCLWSForm.InitMouseXYEvent(aInit,Shift,X,Y,Controls.mbLeft);
   aEl:=FViewport.GetElementAt(aInit.PagePos.X,aInit.PagePos.Y);
   if aEl=Nil then
     aEl:=Self.Viewport;
@@ -232,7 +229,7 @@ begin
   end;
 end;
 
-procedure TFresnelLCLEventControl.HandleMouseUp(Sender: TObject;  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+procedure TFresnelLCLEventControl.HandleMouseUp(Sender: TObject;  Button: Controls.TMouseButton; Shift: TShiftState; X, Y: Integer);
 
 Var
   aInit : TFresnelMouseEventInit;