123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058 |
- {
- 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
- element p Selects all <p> elements
- element.class p.intro Selects all <p> elements with class="intro"
- element,element div, p Selects all <div> elements and all <p> elements
- element element div p Selects all <p> elements inside <div> elements
- element>element div > p Selects all <p> elements where the parent is a <div> element
- element+element div + p Selects the first <p> element that is placed immediately after <div> elements
- 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
- - namespaces
- - layers
- - @rules:-----------------------------------------------------------------------
- - @media
- - @font-face
- - @keyframes
- - columns combinator || col.selected || td
- - :nth-col()
- - :nth-last-col()
- - 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)
- - --varname
- - counter-reset
- - counter-increment
- }
- {$IFNDEF FPC_DOTTEDUNITS}
- unit fpCSSResolver;
- {$ENDIF FPC_DOTTEDUNITS}
- {$mode ObjFPC}{$H+}
- {$Interfaces CORBA}
- {$WARN 6060 off} // Case statement does not handle all possible cases
- 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; // :nth-child
- CSSCallID_Is = CSSCallID_Not+1; // :nth-child
- CSSCallID_Where = CSSCallID_Is+1; // :nth-child
- CSSCallID_Has = CSSCallID_Where+1; // :nth-child
- CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child
- CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-child
- CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-child
- CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-child
- 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 HasCSSPseudo(const AttrID: TCSSNumericalID): boolean;
- function GetCSSPseudo(const AttrID: TCSSNumericalID): TCSSString;
- function GetCSSEmpty: boolean;
- function GetCSSDepth: integer;
- procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
- function CheckCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement): boolean;
- end;
- type
- TCSSNumericalIDKind = (
- nikType,
- nikAttribute,
- nikPseudoAttribute
- );
- TCSSNumericalIDKinds = set of TCSSNumericalIDKind;
- const
- CSSNumericalIDKindNames: array[TCSSNumericalIDKind] of TCSSString = (
- 'Type',
- 'Attribute',
- 'PseudoAttribute'
- );
- 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,nikPseudoAttribute);
- 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.GetCSSPseudo(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;
- nikPseudoAttribute:
- 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.
|