fpcssresolver.pas 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058
  1. {
  2. This file is part of the Free Pascal Run time library.
  3. Copyright (c) 2022 by Michael Van Canneyt ([email protected])
  4. This file contains CSS utility class
  5. See the File COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************
  11. Works:
  12. Selector Example Example description
  13. .class .intro Selects all elements with class="intro"
  14. .class1.class2 .name1.name2 Selects all elements with both name1 and name2 set within its class attribute
  15. .class1 .class2 .name1 .name2 Selects all elements with name2 that is a descendant of an element with name1
  16. #id #firstname Selects the element with name="firstname"
  17. * * Selects all elements
  18. element p Selects all <p> elements
  19. element.class p.intro Selects all <p> elements with class="intro"
  20. element,element div, p Selects all <div> elements and all <p> elements
  21. element element div p Selects all <p> elements inside <div> elements
  22. element>element div > p Selects all <p> elements where the parent is a <div> element
  23. element+element div + p Selects the first <p> element that is placed immediately after <div> elements
  24. element1~element2 p ~ ul Selects every <ul> element that is preceded by a <p> element
  25. [attribute] [target] Selects all elements with a target attribute
  26. [attribute=value] [target=_blank] Selects all elements with target="_blank"
  27. [attribute~=value] [title~=flower] Selects all elements with a title attribute containing the *word* "flower"
  28. [attribute|=value] [lang|=en] Selects all elements with a lang attribute value equal to "en" or starting with "en-" (hyphen)
  29. [attribute^=value] a[href^="https"] Selects every <a> element whose href attribute value begins with "https"
  30. [attribute$=value] a[href$=".pdf"] Selects every <a> element whose href attribute value ends with ".pdf"
  31. [attribute*=value] a[href*="w3schools"] Selects every <a> element whose href attribute value contains the substring "w3schools"
  32. :root :root Selects the document's root element
  33. :empty p:empty Selects every <p> element that has no children (including text nodes)
  34. :first-child p:first-child Selects every <p> element that is the first child of its parent
  35. :first-of-type p:first-of-type Selects every <p> element that is the first <p> element of its parent
  36. :last-child p:last-child Selects every <p> element that is the last child of its parent
  37. :last-of-type p:last-of-type Selects every <p> element that is the last <p> element of its parent
  38. :not(selector) :not(p) Selects every element that is not a <p> element
  39. :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).
  40. :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
  41. :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
  42. :nth-of-type(n) p:nth-of-type(2) Selects every <p> element that is the second <p> element of its parent
  43. :only-of-type p:only-of-type Selects every <p> element that is the only <p> element of its parent
  44. :only-child p:only-child Selects every <p> element that is the only child of its parent
  45. :is()
  46. :where()
  47. Specifity:
  48. important: 10000
  49. inline: 1000
  50. id: 100 #menu
  51. class+attribute selectors: 10 .button, :hover, [href]
  52. element/type: 1 p, :before
  53. *: 0
  54. ToDo:
  55. - 'all' attribute: resets all properties, except direction and unicode bidi
  56. - :has()
  57. - TCSSResolver.FindComputedAttribute use binary search for >8 elements
  58. - namespaces
  59. - layers
  60. - @rules:-----------------------------------------------------------------------
  61. - @media
  62. - @font-face
  63. - @keyframes
  64. - columns combinator || col.selected || td
  65. - :nth-col()
  66. - :nth-last-col()
  67. - Pseudo-elements - not case sensitive:-----------------------------------------
  68. - ::first-letter p::first-letter Selects the first letter of every <p> element
  69. - ::first-line p::first-line Selects the first line of every <p> element
  70. - ::selection ::selection Selects the portion of an element that is selected by a user
  71. - Altering:---------------------------------------------------------------------
  72. - ::after p::after Insert something after the content of each <p> element
  73. - ::before p::before Insert something before the content of each <p> element
  74. - Functions and Vars:-----------------------------------------------------------
  75. - attr() Returns the value of an attribute of the selected element
  76. - calc() Allows you to perform calculations to determine CSS property values calc(100% - 100px)
  77. - max() min() min(50%, 50px)
  78. - --varname
  79. - counter-reset
  80. - counter-increment
  81. }
  82. {$IFNDEF FPC_DOTTEDUNITS}
  83. unit fpCSSResolver;
  84. {$ENDIF FPC_DOTTEDUNITS}
  85. {$mode ObjFPC}{$H+}
  86. {$Interfaces CORBA}
  87. {$WARN 6060 off} // Case statement does not handle all possible cases
  88. interface
  89. {$IFDEF FPC_DOTTEDUNITS}
  90. uses
  91. System.Classes, System.SysUtils, System.Types, System.Contnrs, System.StrUtils, FPCSS.Tree;
  92. {$ELSE FPC_DOTTEDUNITS}
  93. uses
  94. Classes, SysUtils, types, Contnrs, StrUtils, fpCSSTree;
  95. {$ENDIF FPC_DOTTEDUNITS}
  96. const
  97. CSSSpecifityInvalid = -2;
  98. CSSSpecifityNoMatch = -1;
  99. CSSSpecifityUniversal = 0;
  100. CSSSpecifityType = 1;
  101. CSSSpecifityClass = 10; // includes attribute selectors e.g. [href]
  102. CSSSpecifityIdentifier = 100;
  103. CSSSpecifityInline = 1000;
  104. CSSSpecifityImportant = 10000;
  105. CSSIDNone = 0;
  106. // built-in type IDs
  107. CSSTypeID_Universal = 1; // id of type '*'
  108. CSSLastTypeID = CSSTypeID_Universal;
  109. // built-in attribute IDs
  110. CSSAttributeID_ID = 1; // id of attribute key 'id'
  111. CSSAttributeID_Class = 2; // id of attribute key 'class'
  112. CSSAttributeID_All = 3; // id of attribute key 'all'
  113. CSSLastAttributeID = CSSAttributeID_All;
  114. // pseudo attribute and call IDs
  115. CSSPseudoID_Root = 1; // :root
  116. CSSPseudoID_Empty = CSSPseudoID_Root+1; // :empty
  117. CSSPseudoID_FirstChild = CSSPseudoID_Empty+1; // :first-child
  118. CSSPseudoID_LastChild = CSSPseudoID_FirstChild+1; // :last-child
  119. CSSPseudoID_OnlyChild = CSSPseudoID_LastChild+1; // :only-child
  120. CSSPseudoID_FirstOfType = CSSPseudoID_OnlyChild+1; // :first-of-type
  121. CSSPseudoID_LastOfType = CSSPseudoID_FirstOfType+1; // :last-of-type
  122. CSSPseudoID_OnlyOfType = CSSPseudoID_LastOfType+1; // :only-of-type
  123. CSSCallID_Not = CSSPseudoID_OnlyOfType+1; // :nth-child
  124. CSSCallID_Is = CSSCallID_Not+1; // :nth-child
  125. CSSCallID_Where = CSSCallID_Is+1; // :nth-child
  126. CSSCallID_Has = CSSCallID_Where+1; // :nth-child
  127. CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child
  128. CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-child
  129. CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-child
  130. CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-child
  131. CSSLastPseudoID = CSSCallID_NthLastOfType;
  132. const
  133. CSSPseudoNames: array[0..CSSLastPseudoID] of string = (
  134. '?',
  135. ':root',
  136. ':empty',
  137. ':first-child',
  138. ':last-child',
  139. ':only-child',
  140. ':first-of-type',
  141. ':last-of-type',
  142. ':only-of-type',
  143. ':not()',
  144. ':is()',
  145. ':where()',
  146. ':has()',
  147. ':nth-child(n)',
  148. ':nth-last-child(n)',
  149. ':nth-of-type(n)',
  150. ':nth-last-of-type(n)'
  151. );
  152. type
  153. TCSSMsgID = int64;
  154. TCSSNumericalID = integer;
  155. TCSSSpecifity = integer;
  156. ECSSResolver = class(ECSSException)
  157. end;
  158. TCSSAttributeMatchKind = (
  159. camkEqual,
  160. camkContains,
  161. camkContainsWord,
  162. camkBegins,
  163. camkEnds
  164. );
  165. TCSSAttributeMatchKinds = set of TCSSAttributeMatchKind;
  166. { ICSSNode }
  167. ICSSNode = interface
  168. function GetCSSID: TCSSString;
  169. function GetCSSTypeName: TCSSString;
  170. function GetCSSTypeID: TCSSNumericalID;
  171. function HasCSSClass(const aClassName: TCSSString): boolean;
  172. function GetCSSAttributeClass: TCSSString;
  173. function GetCSSParent: ICSSNode;
  174. function GetCSSIndex: integer; // node index in parent's children
  175. function GetCSSNextSibling: ICSSNode;
  176. function GetCSSPreviousSibling: ICSSNode;
  177. function GetCSSChildCount: integer;
  178. function GetCSSChild(const anIndex: integer): ICSSNode;
  179. function GetCSSNextOfType: ICSSNode;
  180. function GetCSSPreviousOfType: ICSSNode;
  181. function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean;
  182. function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString;
  183. function HasCSSPseudo(const AttrID: TCSSNumericalID): boolean;
  184. function GetCSSPseudo(const AttrID: TCSSNumericalID): TCSSString;
  185. function GetCSSEmpty: boolean;
  186. function GetCSSDepth: integer;
  187. procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement);
  188. function CheckCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement): boolean;
  189. end;
  190. type
  191. TCSSNumericalIDKind = (
  192. nikType,
  193. nikAttribute,
  194. nikPseudoAttribute
  195. );
  196. TCSSNumericalIDKinds = set of TCSSNumericalIDKind;
  197. const
  198. CSSNumericalIDKindNames: array[TCSSNumericalIDKind] of TCSSString = (
  199. 'Type',
  200. 'Attribute',
  201. 'PseudoAttribute'
  202. );
  203. type
  204. { TCSSNumericalIDs }
  205. TCSSNumericalIDs = class
  206. private
  207. FKind: TCSSNumericalIDKind;
  208. fList: TFPHashList;
  209. function GetCount: Integer;
  210. function GetIDs(const aName: TCSSString): TCSSNumericalID;
  211. procedure SetIDs(const aName: TCSSString; const AValue: TCSSNumericalID);
  212. public
  213. constructor Create(aKind: TCSSNumericalIDKind);
  214. destructor Destroy; override;
  215. procedure Clear;
  216. property IDs[const aName: TCSSString]: TCSSNumericalID read GetIDs write SetIDs; default;
  217. property Kind: TCSSNumericalIDKind read FKind;
  218. property Count: Integer read GetCount;
  219. end;
  220. TCSSComputedAttribute = record
  221. AttrID: TCSSNumericalID;
  222. Specifity: TCSSSpecifity;
  223. Value: TCSSElement;
  224. end;
  225. TCSSComputedAttributeArray = array of TCSSComputedAttribute;
  226. PCSSComputedAttribute = ^TCSSComputedAttribute;
  227. TCSSElResolverData = class
  228. public
  229. Element: TCSSElement;
  230. Next, Prev: TCSSElResolverData;
  231. end;
  232. TCSSValueValidity = (
  233. cvvNone,
  234. cvvValid,
  235. cvvInvalid
  236. );
  237. TCSSValueValidities = set of TCSSValueValidity;
  238. TCSSIdentifierData = class(TCSSElResolverData)
  239. public
  240. NumericalID: TCSSNumericalID;
  241. Kind: TCSSNumericalIDKind;
  242. ValueValid: TCSSValueValidity;
  243. end;
  244. TCSSValueData = class(TCSSElResolverData)
  245. public
  246. NormValue: string;
  247. end;
  248. { TCSSCallData }
  249. TCSSCallData = class(TCSSElResolverData)
  250. public
  251. NumericalID: TCSSNumericalID;
  252. Params: TObject;
  253. destructor Destroy; override;
  254. end;
  255. TCSSCallNthChildParams = class;
  256. TCSSCallNthChildParamsCacheItem = record
  257. TypeID: TCSSNumericalID;
  258. ChildIDs: TIntegerDynArray;
  259. Cnt: integer; // = length(ChildIDs), used during creation
  260. end;
  261. PCSSCallNthChildParamsCacheItem = ^TCSSCallNthChildParamsCacheItem;
  262. TCSSCallNthChildParamsCacheItems = array of TCSSCallNthChildParamsCacheItem;
  263. TCSSCallNthChildParamsCache = class
  264. public
  265. Owner: TCSSCallNthChildParams;
  266. Parent: ICSSNode;
  267. StackDepth: integer;
  268. Items: TCSSCallNthChildParamsCacheItems;
  269. end;
  270. TCSSCallNthChildParamsCaches = array of TCSSCallNthChildParamsCache;
  271. { TCSSCallNthChildParams }
  272. TCSSCallNthChildParams = class
  273. Modulo: integer;
  274. Start: integer;
  275. HasOf: boolean;
  276. OfSelector: TCSSElement;
  277. StackCache: TCSSCallNthChildParamsCaches;
  278. destructor Destroy; override;
  279. end;
  280. TCSSResolverOption = (
  281. croErrorOnUnknownName
  282. );
  283. TCSSResolverOptions = set of TCSSResolverOption;
  284. TCSSComputeOption = (
  285. ccoCommit
  286. );
  287. TCSSComputeOptions = set of TCSSComputeOption;
  288. const
  289. DefaultCSSComputeOptions = [ccoCommit];
  290. type
  291. TCSSResolverLogEntry = class
  292. public
  293. MsgType: TEventType;
  294. ID: TCSSMsgID;
  295. Msg: string;
  296. PosEl: TCSSElement;
  297. end;
  298. TCSSResolverLogEvent = procedure(Sender: TObject; Entry: TCSSResolverLogEntry) of object;
  299. TCSSResStringComparison = (
  300. crscDefault,
  301. crscCaseInsensitive,
  302. crscCaseSensitive
  303. );
  304. TCSSResStringComparisons = set of TCSSResStringComparison;
  305. { TCSSResolver }
  306. TCSSResolver = class(TComponent)
  307. private
  308. FNumericalIDs: array[TCSSNumericalIDKind] of TCSSNumericalIDs;
  309. FOnLog: TCSSResolverLogEvent;
  310. FOptions: TCSSResolverOptions;
  311. FStringComparison: TCSSResStringComparison;
  312. FStyles: TCSSElementArray;
  313. FOwnsStyle: boolean;
  314. FFirstElData: TCSSElResolverData;
  315. FLastElData: TCSSElResolverData;
  316. function GetAttributes(Index: integer): PCSSComputedAttribute;
  317. function GetLogCount: integer;
  318. function GetLogEntries(Index: integer): TCSSResolverLogEntry;
  319. function GetNumericalIDs(Kind: TCSSNumericalIDKind): TCSSNumericalIDs;
  320. function GetStyleCount: integer;
  321. function GetStyles(Index: integer): TCSSElement;
  322. procedure SetNumericalIDs(Kind: TCSSNumericalIDKind;
  323. const AValue: TCSSNumericalIDs);
  324. procedure SetOptions(const AValue: TCSSResolverOptions);
  325. procedure SetStyles(Index: integer; const AValue: TCSSElement);
  326. protected
  327. FAttributes: TCSSComputedAttributeArray;
  328. FAttributeCount: integer;
  329. FNode: ICSSNode;
  330. FLogEntries: TFPObjectList; // list of TCSSResolverLogEntry
  331. procedure ComputeElement(El: TCSSElement); virtual;
  332. procedure ComputeRule(aRule: TCSSRuleElement); virtual;
  333. procedure ComputeInline(El: TCSSElement); virtual;
  334. procedure ComputeInlineRule(aRule: TCSSRuleElement); virtual;
  335. function SelectorMatches(aSelector: TCSSElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  336. function SelectorIdentifierMatches(Identifier: TCSSIdentifierElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  337. function SelectorHashIdentifierMatches(Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  338. function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  339. function SelectorPseudoClassMatches(aPseudoClass: TCSSPseudoClassElement; var TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  340. function SelectorListMatches(aList: TCSSListElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  341. function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  342. function SelectorArrayMatches(anArray: TCSSArrayElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  343. function SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode): TCSSSpecifity; virtual;
  344. function SelectorCallMatches(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  345. function Call_Not(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  346. function Call_Is(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  347. function Call_Where(aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  348. function Call_NthChild(CallID: TCSSNumericalID; aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity; virtual;
  349. function CollectSiblingsOf(CallID: TCSSNumericalID; TestNode: ICSSNode;
  350. Params: TCSSCallNthChildParams): TIntegerDynArray; virtual;
  351. function GetSiblingOfIndex(SiblingIDs: TIntegerDynArray; Index: integer): integer; virtual;
  352. function ComputeValue(El: TCSSElement): TCSSString; virtual;
  353. function SameValueText(const A, B: TCSSString): boolean; virtual;
  354. function SameValueText(A: PAnsiChar; ALen: integer; B: PAnsiChar; BLen: integer): boolean; virtual;
  355. function PosSubString(const SearchStr, Str: TCSSString): integer; virtual;
  356. function PosWord(const SearchWord, Words: TCSSString): integer; virtual;
  357. function GetSiblingCount(aNode: ICSSNode): integer; virtual;
  358. procedure MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity); virtual;
  359. function CheckAttrValueValidity(AttrID: TCSSNumericalID; aKey, aValue: TCSSElement): boolean; virtual;
  360. function ResolveIdentifier(El: TCSSIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
  361. function ResolveCall(El: TCSSCallElement): TCSSNumericalID; virtual;
  362. procedure AddElData(El: TCSSElement; ElData: TCSSElResolverData); virtual;
  363. function AddElValueData(El: TCSSElement; const aValue: TCSSString): TCSSValueData; virtual;
  364. function FindComputedAttribute(AttrID: TCSSNumericalID): PCSSComputedAttribute;
  365. function AddComputedAttribute(TheAttrID: TCSSNumericalID; aSpecifity: TCSSSpecifity;
  366. aValue: TCSSElement): PCSSComputedAttribute;
  367. public
  368. constructor Create(AOwner: TComponent); override;
  369. destructor Destroy; override;
  370. function GetElPath(El: TCSSElement): string; virtual;
  371. function GetElPos(El: TCSSElement): string; virtual;
  372. function IndexOfStyle(aStyle: TCSSElement): integer; virtual;
  373. procedure AddStyle(aStyle: TCSSElement); virtual;
  374. procedure Clear; virtual;
  375. procedure ClearStyleCustomData; virtual;
  376. procedure ClearStyles; virtual;
  377. procedure Commit; virtual;
  378. procedure Compute(Node: ICSSNode; NodeStyle: TCSSElement = nil;
  379. const CompOptions: TCSSComputeOptions = DefaultCSSComputeOptions); virtual;
  380. procedure DeleteStyle(aIndex: integer); virtual;
  381. procedure Log(MsgType: TEventType; const ID: TCSSMsgID; Msg: string; PosEl: TCSSElement); virtual;
  382. procedure LogWarning(IsError: boolean; const ID: TCSSMsgID; Msg: string; PosEl: TCSSElement); virtual;
  383. procedure RemoveStyle(aStyle: TCSSElement); virtual;
  384. property AttributeCount: integer read FAttributeCount;
  385. property Attributes[Index: integer]: PCSSComputedAttribute read GetAttributes;
  386. property LogCount: integer read GetLogCount;
  387. property LogEntries[Index: integer]: TCSSResolverLogEntry read GetLogEntries;
  388. property NumericalIDs[Kind: TCSSNumericalIDKind]: TCSSNumericalIDs read GetNumericalIDs write SetNumericalIDs;
  389. property OnLog: TCSSResolverLogEvent read FOnLog write FOnLog;
  390. property Options: TCSSResolverOptions read FOptions write SetOptions;
  391. property OwnsStyle: boolean read FOwnsStyle write FOwnsStyle default false;
  392. property StringComparison: TCSSResStringComparison read FStringComparison;
  393. property StyleCount: integer read GetStyleCount;
  394. property Styles[Index: integer]: TCSSElement read GetStyles write SetStyles;
  395. end;
  396. implementation
  397. { TCSSCallNthChildParams }
  398. destructor TCSSCallNthChildParams.Destroy;
  399. var
  400. i: Integer;
  401. begin
  402. for i:=0 to high(StackCache) do
  403. StackCache[i].Free;
  404. inherited Destroy;
  405. end;
  406. { TCSSCallData }
  407. destructor TCSSCallData.Destroy;
  408. begin
  409. FreeAndNil(Params);
  410. inherited Destroy;
  411. end;
  412. { TCSSNumericalIDs }
  413. function TCSSNumericalIDs.GetIDs(const aName: TCSSString): TCSSNumericalID;
  414. begin
  415. {$WARN 4056 off : Conversion between ordinals and pointers is not portable}
  416. Result:=TCSSNumericalID(fList.Find(aName));
  417. {$WARN 4056 on}
  418. end;
  419. function TCSSNumericalIDs.GetCount: Integer;
  420. begin
  421. Result:=fList.Count;
  422. end;
  423. procedure TCSSNumericalIDs.SetIDs(const aName: TCSSString;
  424. const AValue: TCSSNumericalID);
  425. var
  426. i: Integer;
  427. begin
  428. i:=fList.FindIndexOf(aName);
  429. if i>=0 then
  430. fList.Delete(i);
  431. if AValue=CSSIDNone then
  432. exit;
  433. {$WARN 4056 off : Conversion between ordinals and pointers is not portable}
  434. fList.Add(aName,Pointer(AValue));
  435. {$WARN 4056 on}
  436. end;
  437. constructor TCSSNumericalIDs.Create(aKind: TCSSNumericalIDKind);
  438. begin
  439. FKind:=aKind;
  440. fList:=TFPHashList.Create;
  441. end;
  442. destructor TCSSNumericalIDs.Destroy;
  443. begin
  444. FreeAndNil(fList);
  445. inherited Destroy;
  446. end;
  447. procedure TCSSNumericalIDs.Clear;
  448. begin
  449. fList.Clear;
  450. end;
  451. { TCSSResolver }
  452. function TCSSResolver.GetNumericalIDs(Kind: TCSSNumericalIDKind
  453. ): TCSSNumericalIDs;
  454. begin
  455. Result:=FNumericalIDs[Kind];
  456. end;
  457. function TCSSResolver.GetStyleCount: integer;
  458. begin
  459. Result:=length(FStyles);
  460. end;
  461. function TCSSResolver.GetStyles(Index: integer): TCSSElement;
  462. begin
  463. Result:=FStyles[Index];
  464. end;
  465. function TCSSResolver.GetAttributes(Index: integer): PCSSComputedAttribute;
  466. begin
  467. if (Index<0) or (Index>=FAttributeCount) then
  468. raise ECSSResolver.Create('TCSSResolver.GetAttributes index out of bounds');
  469. Result:=@FAttributes[Index];
  470. end;
  471. function TCSSResolver.GetLogCount: integer;
  472. begin
  473. Result:=FLogEntries.Count;
  474. end;
  475. function TCSSResolver.GetLogEntries(Index: integer): TCSSResolverLogEntry;
  476. begin
  477. Result:=TCSSResolverLogEntry(FLogEntries[Index]);
  478. end;
  479. procedure TCSSResolver.SetNumericalIDs(Kind: TCSSNumericalIDKind;
  480. const AValue: TCSSNumericalIDs);
  481. begin
  482. FNumericalIDs[Kind]:=AValue;
  483. end;
  484. procedure TCSSResolver.SetOptions(const AValue: TCSSResolverOptions);
  485. begin
  486. if FOptions=AValue then Exit;
  487. FOptions:=AValue;
  488. end;
  489. procedure TCSSResolver.SetStyles(Index: integer; const AValue: TCSSElement);
  490. begin
  491. if (Index<0) or (Index>=length(FStyles)) then
  492. raise ECSSResolver.Create('TCSSResolver.SetStyles index '+IntToStr(Index)+' out of bounds '+IntToStr(length(FStyles)));
  493. if FStyles[Index]=AValue then exit;
  494. if OwnsStyle then
  495. FStyles[Index].Free;
  496. FStyles[Index]:=AValue;
  497. end;
  498. procedure TCSSResolver.ComputeElement(El: TCSSElement);
  499. var
  500. C: TClass;
  501. Compound: TCSSCompoundElement;
  502. i: Integer;
  503. begin
  504. if El=nil then exit;
  505. C:=El.ClassType;
  506. {$IFDEF VerboseCSSResolver}
  507. //writeln('TCSSResolver.ComputeElement ',GetCSSPath(El));
  508. {$ENDIF}
  509. if C=TCSSCompoundElement then
  510. begin
  511. Compound:=TCSSCompoundElement(El);
  512. //writeln('TCSSResolver.ComputeElement Compound.ChildCount=',Compound.ChildCount);
  513. for i:=0 to Compound.ChildCount-1 do
  514. ComputeElement(Compound.Children[i]);
  515. end else if C=TCSSRuleElement then
  516. ComputeRule(TCSSRuleElement(El))
  517. else
  518. Log(etWarning,20220908150252,'TCSSResolver.ComputeElement: Unknown CSS element',El);
  519. end;
  520. procedure TCSSResolver.ComputeRule(aRule: TCSSRuleElement);
  521. var
  522. i: Integer;
  523. BestSpecifity, Specifity: TCSSSpecifity;
  524. aSelector: TCSSElement;
  525. begin
  526. BestSpecifity:=CSSSpecifityNoMatch;
  527. for i:=0 to aRule.SelectorCount-1 do
  528. begin
  529. aSelector:=aRule.Selectors[i];
  530. Specifity:=SelectorMatches(aSelector,FNode,false);
  531. if Specifity>BestSpecifity then
  532. BestSpecifity:=Specifity;
  533. end;
  534. if BestSpecifity>=0 then
  535. begin
  536. // match -> apply properties
  537. for i:=0 to aRule.ChildCount-1 do
  538. MergeProperty(aRule.Children[i],BestSpecifity);
  539. end;
  540. end;
  541. procedure TCSSResolver.ComputeInline(El: TCSSElement);
  542. var
  543. C: TClass;
  544. begin
  545. if El=nil then exit;
  546. C:=El.ClassType;
  547. if C=TCSSRuleElement then
  548. ComputeInlineRule(TCSSRuleElement(El))
  549. else
  550. Log(etWarning,20220915140402,'TCSSResolver.ComputeInline Not yet supported inline element',El);
  551. end;
  552. procedure TCSSResolver.ComputeInlineRule(aRule: TCSSRuleElement);
  553. var
  554. i: Integer;
  555. begin
  556. if aRule.SelectorCount>0 then
  557. exit;
  558. for i:=0 to aRule.ChildCount-1 do
  559. MergeProperty(aRule.Children[i],CSSSpecifityInline);
  560. end;
  561. function TCSSResolver.SelectorMatches(aSelector: TCSSElement;
  562. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  563. procedure MatchPseudo;
  564. var
  565. aNode: ICSSNode;
  566. begin
  567. aNode:=TestNode;
  568. Result:=SelectorPseudoClassMatches(TCSSPseudoClassElement(aSelector),aNode,OnlySpecifity);
  569. end;
  570. var
  571. C: TClass;
  572. begin
  573. Result:=CSSSpecifityInvalid;
  574. //writeln('TCSSResolver.SelectorMatches ',aSelector.ClassName,' ',TestNode.GetCSSTypeName);
  575. C:=aSelector.ClassType;
  576. if C=TCSSIdentifierElement then
  577. Result:=SelectorIdentifierMatches(TCSSIdentifierElement(aSelector),TestNode,OnlySpecifity)
  578. else if C=TCSSHashIdentifierElement then
  579. Result:=SelectorHashIdentifierMatches(TCSSHashIdentifierElement(aSelector),TestNode,OnlySpecifity)
  580. else if C=TCSSClassNameElement then
  581. Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode,OnlySpecifity)
  582. else if C=TCSSPseudoClassElement then
  583. MatchPseudo
  584. else if C=TCSSBinaryElement then
  585. Result:=SelectorBinaryMatches(TCSSBinaryElement(aSelector),TestNode,OnlySpecifity)
  586. else if C=TCSSArrayElement then
  587. Result:=SelectorArrayMatches(TCSSArrayElement(aSelector),TestNode,OnlySpecifity)
  588. else if C=TCSSListElement then
  589. Result:=SelectorListMatches(TCSSListElement(aSelector),TestNode,OnlySpecifity)
  590. else if C=TCSSCallElement then
  591. Result:=SelectorCallMatches(TCSSCallElement(aSelector),TestNode,OnlySpecifity)
  592. else
  593. Log(etWarning,20220908230152,'Unknown CSS selector element',aSelector);
  594. end;
  595. function TCSSResolver.SelectorIdentifierMatches(
  596. Identifier: TCSSIdentifierElement; const TestNode: ICSSNode;
  597. OnlySpecifity: boolean): TCSSSpecifity;
  598. var
  599. TypeID: TCSSNumericalID;
  600. begin
  601. Result:=CSSSpecifityNoMatch;
  602. TypeID:=ResolveIdentifier(Identifier,nikType);
  603. {$IFDEF VerboseCSSResolver}
  604. writeln('TCSSResolver.SelectorIdentifierMatches ',Identifier.Value,' TypeId=',TypeID);
  605. {$ENDIF}
  606. if TypeID=CSSTypeID_Universal then
  607. begin
  608. // universal selector
  609. Result:=CSSSpecifityUniversal;
  610. end else if OnlySpecifity then
  611. Result:=CSSSpecifityType
  612. else if TypeID=CSSIDNone then
  613. begin
  614. LogWarning(croErrorOnUnknownName in Options,20220911230224,'Unknown CSS selector type name "'+Identifier.Name+'"',Identifier);
  615. Result:=CSSSpecifityInvalid;
  616. end else if TypeID=TestNode.GetCSSTypeID then
  617. Result:=CSSSpecifityType;
  618. end;
  619. function TCSSResolver.SelectorHashIdentifierMatches(
  620. Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode;
  621. OnlySpecifity: boolean): TCSSSpecifity;
  622. var
  623. aValue: TCSSString;
  624. begin
  625. if OnlySpecifity then
  626. exit(CSSSpecifityIdentifier);
  627. Result:=CSSSpecifityNoMatch;
  628. aValue:=Identifier.Value;
  629. if TestNode.GetCSSID=aValue then
  630. Result:=CSSSpecifityIdentifier;
  631. end;
  632. function TCSSResolver.SelectorClassNameMatches(
  633. aClassName: TCSSClassNameElement; const TestNode: ICSSNode;
  634. OnlySpecifity: boolean): TCSSSpecifity;
  635. var
  636. aValue: TCSSString;
  637. begin
  638. if OnlySpecifity then
  639. exit(CSSSpecifityClass);
  640. aValue:=aClassName.Name;
  641. if TestNode.HasCSSClass(aValue) then
  642. Result:=CSSSpecifityClass
  643. else
  644. Result:=CSSSpecifityNoMatch;
  645. //writeln('TCSSResolver.SelectorClassNameMatches ',aValue,' ',Result);
  646. end;
  647. function TCSSResolver.SelectorPseudoClassMatches(
  648. aPseudoClass: TCSSPseudoClassElement; var TestNode: ICSSNode;
  649. OnlySpecifity: boolean): TCSSSpecifity;
  650. var
  651. PseudoID: TCSSNumericalID;
  652. begin
  653. if OnlySpecifity then
  654. exit(CSSSpecifityClass);
  655. Result:=CSSSpecifityNoMatch;
  656. PseudoID:=ResolveIdentifier(aPseudoClass,nikPseudoAttribute);
  657. case PseudoID of
  658. CSSIDNone:
  659. LogWarning(croErrorOnUnknownName in Options,20220911205605,'Unknown CSS selector pseudo attribute name "'+aPseudoClass.Name+'"',aPseudoClass);
  660. CSSPseudoID_Root:
  661. if TestNode.GetCSSParent=nil then
  662. Result:=CSSSpecifityClass;
  663. CSSPseudoID_Empty:
  664. if TestNode.GetCSSEmpty then
  665. Result:=CSSSpecifityClass;
  666. CSSPseudoID_FirstChild:
  667. if TestNode.GetCSSPreviousSibling=nil then
  668. Result:=CSSSpecifityClass;
  669. CSSPseudoID_LastChild:
  670. if TestNode.GetCSSNextSibling=nil then
  671. Result:=CSSSpecifityClass;
  672. CSSPseudoID_OnlyChild:
  673. if (TestNode.GetCSSNextSibling=nil)
  674. and (TestNode.GetCSSPreviousSibling=nil) then
  675. Result:=CSSSpecifityClass;
  676. CSSPseudoID_FirstOfType:
  677. if TestNode.GetCSSPreviousOfType=nil then
  678. Result:=CSSSpecifityClass;
  679. CSSPseudoID_LastOfType:
  680. if TestNode.GetCSSNextOfType=nil then
  681. Result:=CSSSpecifityClass;
  682. CSSPseudoID_OnlyOfType:
  683. if (TestNode.GetCSSNextOfType=nil)
  684. and (TestNode.GetCSSPreviousOfType=nil) then
  685. Result:=CSSSpecifityClass;
  686. else
  687. if TestNode.GetCSSPseudo(PseudoID)<>'' then
  688. Result:=CSSSpecifityClass;
  689. end;
  690. end;
  691. function TCSSResolver.SelectorListMatches(aList: TCSSListElement;
  692. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  693. var
  694. i: Integer;
  695. El: TCSSElement;
  696. C: TClass;
  697. Specifity: TCSSSpecifity;
  698. aNode: ICSSNode;
  699. begin
  700. Result:=0;
  701. {$IFDEF VerboseCSSResolver}
  702. writeln('TCSSResolver.SelectorListMatches ChildCount=',aList.ChildCount);
  703. {$ENDIF}
  704. aNode:=TestNode;
  705. for i:=0 to aList.ChildCount-1 do
  706. begin
  707. El:=aList.Children[i];
  708. {$IFDEF VerboseCSSResolver}
  709. writeln('TCSSResolver.SelectorListMatches ',i,' ',GetCSSObj(El),' AsString=',El.AsString);
  710. {$ENDIF}
  711. C:=El.ClassType;
  712. if (C=TCSSIdentifierElement) and (i>0) then
  713. begin
  714. if OnlySpecifity then
  715. exit(0);
  716. Log(etWarning,20220914163218,'Type selector must be first',aList);
  717. exit(CSSSpecifityInvalid);
  718. end
  719. else if C=TCSSPseudoClassElement then
  720. begin
  721. Specifity:=SelectorPseudoClassMatches(TCSSPseudoClassElement(El),aNode,OnlySpecifity);
  722. end else
  723. Specifity:=SelectorMatches(El,aNode,OnlySpecifity);
  724. if Specifity<0 then
  725. exit(Specifity);
  726. inc(Result,Specifity);
  727. end;
  728. end;
  729. function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
  730. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  731. var
  732. aParent, Sibling: ICSSNode;
  733. aSpecifity: TCSSSpecifity;
  734. begin
  735. if OnlySpecifity then
  736. begin
  737. Result:=SelectorMatches(aBinary.Left,TestNode,true);
  738. inc(Result,SelectorMatches(aBinary.Right,TestNode,true));
  739. exit;
  740. end;
  741. Result:=CSSSpecifityInvalid;
  742. case aBinary.Operation of
  743. boGT:
  744. begin
  745. // child combinator >
  746. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  747. if Result<0 then exit;
  748. aParent:=TestNode.GetCSSParent;
  749. if aParent=nil then
  750. exit(CSSSpecifityNoMatch);
  751. aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
  752. if aSpecifity<0 then
  753. exit(aSpecifity);
  754. inc(Result,aSpecifity);
  755. end;
  756. boPlus:
  757. begin
  758. // adjacent sibling combinator +
  759. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  760. if Result<0 then exit;
  761. Sibling:=TestNode.GetCSSPreviousSibling;
  762. if Sibling=nil then
  763. exit(CSSSpecifityNoMatch);
  764. aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
  765. if aSpecifity<0 then
  766. exit(aSpecifity);
  767. inc(Result,aSpecifity);
  768. end;
  769. boTilde:
  770. begin
  771. // general sibling combinator ~
  772. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  773. if Result<0 then exit;
  774. Sibling:=TestNode.GetCSSPreviousSibling;
  775. while Sibling<>nil do
  776. begin
  777. aSpecifity:=SelectorMatches(aBinary.Left,Sibling,false);
  778. if aSpecifity=CSSSpecifityInvalid then
  779. exit(aSpecifity)
  780. else if aSpecifity>=0 then
  781. begin
  782. inc(Result,aSpecifity);
  783. exit;
  784. end;
  785. Sibling:=Sibling.GetCSSPreviousSibling;
  786. end;
  787. Result:=CSSSpecifityNoMatch;
  788. end;
  789. boWhiteSpace:
  790. begin
  791. // descendant combinator
  792. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  793. if Result<0 then exit;
  794. aParent:=TestNode;
  795. repeat
  796. aParent:=aParent.GetCSSParent;
  797. if aParent=nil then
  798. exit(CSSSpecifityNoMatch);
  799. aSpecifity:=SelectorMatches(aBinary.Left,aParent,false);
  800. if aSpecifity>=0 then
  801. begin
  802. inc(Result,aSpecifity);
  803. exit;
  804. end
  805. else if aSpecifity=CSSSpecifityInvalid then
  806. exit(CSSSpecifityInvalid);
  807. until false;
  808. end
  809. else
  810. LogWarning(croErrorOnUnknownName in Options,20220910123724,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
  811. end;
  812. end;
  813. function TCSSResolver.SelectorArrayMatches(anArray: TCSSArrayElement;
  814. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  815. var
  816. {$IFDEF VerboseCSSResolver}
  817. i: integer;
  818. {$ENDIF}
  819. El: TCSSElement;
  820. C: TClass;
  821. AttrID: TCSSNumericalID;
  822. OldStringComparison: TCSSResStringComparison;
  823. aValue: TCSSString;
  824. begin
  825. if OnlySpecifity then
  826. exit(CSSSpecifityClass);
  827. Result:=CSSSpecifityInvalid;
  828. if anArray.Prefix<>nil then
  829. begin
  830. Log(etWarning,20220910154004,'Invalid CSS attribute selector prefix',anArray.Prefix);
  831. exit;
  832. end;
  833. {$IFDEF VerboseCSSResolver}
  834. writeln('TCSSResolver.SelectorArrayMatches Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
  835. for i:=0 to anArray.ChildCount-1 do
  836. writeln('TCSSResolver.SelectorArrayMatches ',i,' ',GetCSSObj(anArray.Children[i]));
  837. {$ENDIF}
  838. if anArray.ChildCount<1 then
  839. begin
  840. Log(etWarning,20220910154033,'Invalid CSS attribute selector',anArray);
  841. exit;
  842. end;
  843. OldStringComparison:=StringComparison;
  844. try
  845. if anArray.ChildCount>1 then
  846. begin
  847. El:=anArray.Children[1];
  848. C:=El.ClassType;
  849. if C=TCSSIdentifierElement then
  850. begin
  851. aValue:=TCSSIdentifierElement(El).Value;
  852. case aValue of
  853. 'i': FStringComparison:=crscCaseInsensitive;
  854. 's': FStringComparison:=crscCaseSensitive;
  855. else
  856. LogWarning(croErrorOnUnknownName in Options,20220914174409,'Invalid attribute modifier "'+aValue+'"',El);
  857. exit;
  858. end;
  859. end else begin
  860. Log(etWarning,20220914173643,'Invalid CSS attribute modifier',El);
  861. exit;
  862. end;
  863. end;
  864. if (anArray.ChildCount>2) then
  865. Log(etWarning,20220914174550,'Invalid CSS attribute modifier',anArray.Children[2]);
  866. El:=anArray.Children[0];
  867. C:=El.ClassType;
  868. if C=TCSSIdentifierElement then
  869. begin
  870. // [name] -> has attribute name
  871. AttrID:=ResolveIdentifier(TCSSIdentifierElement(El),nikAttribute);
  872. case AttrID of
  873. CSSIDNone:
  874. Result:=CSSSpecifityNoMatch;
  875. CSSAttributeID_ID,
  876. CSSAttributeID_Class:
  877. // basic CSS attributes are always defined
  878. Result:=CSSSpecifityClass;
  879. CSSAttributeID_All:
  880. // special CSS attributes without a value
  881. Result:=CSSSpecifityNoMatch;
  882. else
  883. if TestNode.HasCSSAttribute(AttrID) then
  884. Result:=CSSSpecifityClass
  885. else
  886. Result:=CSSSpecifityNoMatch;
  887. end;
  888. end else if C=TCSSBinaryElement then
  889. Result:=SelectorArrayBinaryMatches(TCSSBinaryElement(El),TestNode)
  890. else begin
  891. LogWarning(croErrorOnUnknownName in Options,20220910153725,'Invalid CSS array selector',El);
  892. end;
  893. finally
  894. FStringComparison:=OldStringComparison;
  895. end;
  896. end;
  897. function TCSSResolver.SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement;
  898. const TestNode: ICSSNode): TCSSSpecifity;
  899. var
  900. Left, Right: TCSSElement;
  901. AttrID: TCSSNumericalID;
  902. LeftValue, RightValue: TCSSString;
  903. C: TClass;
  904. begin
  905. Result:=CSSSpecifityNoMatch;
  906. Left:=aBinary.Left;
  907. if Left.ClassType<>TCSSIdentifierElement then
  908. Log(etError,20220910164353,'Invalid CSS array selector, expected attribute',Left);
  909. AttrID:=ResolveIdentifier(TCSSIdentifierElement(Left),nikAttribute);
  910. {$IFDEF VerboseCSSResolver}
  911. writeln('TCSSResolver.SelectorArrayBinaryMatches AttrID=',AttrID,' Value=',TCSSIdentifierElement(Left).Value);
  912. {$ENDIF}
  913. case AttrID of
  914. CSSIDNone: exit(CSSSpecifityNoMatch);
  915. CSSAttributeID_ID:
  916. LeftValue:=TestNode.GetCSSID;
  917. CSSAttributeID_Class:
  918. LeftValue:=TestNode.GetCSSAttributeClass;
  919. CSSAttributeID_All: exit(CSSSpecifityNoMatch);
  920. else
  921. LeftValue:=TestNode.GetCSSAttribute(AttrID);
  922. end;
  923. Right:=aBinary.Right;
  924. C:=Right.ClassType;
  925. if (C=TCSSStringElement) or (C=TCSSIntegerElement) or (C=TCSSFloatElement)
  926. or (C=TCSSIdentifierElement) then
  927. // ok
  928. else
  929. Log(etError,20220910164921,'Invalid CSS array selector, expected string',Right);
  930. RightValue:=ComputeValue(Right);
  931. {$IFDEF VerboseCSSResolver}
  932. writeln('TCSSResolver.SelectorArrayBinaryMatches Left="',LeftValue,'" Right="',RightValue,'" Op=',aBinary.Operation);
  933. {$ENDIF}
  934. case aBinary.Operation of
  935. boEquals:
  936. if SameValueText(LeftValue,RightValue) then
  937. Result:=CSSSpecifityClass;
  938. boSquaredEqual:
  939. // begins with
  940. if (RightValue<>'') and SameValueText(LeftStr(LeftValue,length(RightValue)),RightValue) then
  941. Result:=CSSSpecifityClass;
  942. boDollarEqual:
  943. // ends with
  944. if (RightValue<>'') and SameValueText(RightStr(LeftValue,length(RightValue)),RightValue) then
  945. Result:=CSSSpecifityClass;
  946. boPipeEqual:
  947. // equal to or starts with name-hyphen
  948. if (RightValue<>'')
  949. and (SameValueText(LeftValue,RightValue)
  950. or SameValueText(LeftStr(LeftValue,length(RightValue)+1),RightValue+'-')) then
  951. Result:=CSSSpecifityClass;
  952. boStarEqual:
  953. // contains substring
  954. if (RightValue<>'') and (Pos(RightValue,LeftValue)>0) then
  955. Result:=CSSSpecifityClass;
  956. boTildeEqual:
  957. // contains word
  958. if PosWord(RightValue,LeftValue)>0 then
  959. Result:=CSSSpecifityClass;
  960. else
  961. LogWarning(croErrorOnUnknownName in Options,20220910164356,'Invalid CSS array selector operator',aBinary);
  962. Result:=CSSSpecifityInvalid;
  963. end;
  964. {$IFDEF VerboseCSSResolver}
  965. writeln('TCSSResolver.SelectorArrayBinaryMatches Result=',Result);
  966. {$ENDIF}
  967. end;
  968. function TCSSResolver.SelectorCallMatches(aCall: TCSSCallElement;
  969. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  970. var
  971. CallID: TCSSNumericalID;
  972. begin
  973. Result:=CSSSpecifityNoMatch;
  974. CallID:=ResolveCall(aCall);
  975. case CallID of
  976. CSSCallID_Not:
  977. Result:=Call_Not(aCall,TestNode,OnlySpecifity);
  978. CSSCallID_Is:
  979. Result:=Call_Is(aCall,TestNode,OnlySpecifity);
  980. CSSCallID_Where:
  981. Result:=Call_Where(aCall,TestNode,OnlySpecifity);
  982. CSSCallID_NthChild,CSSCallID_NthLastChild,CSSCallID_NthOfType, CSSCallID_NthLastOfType:
  983. Result:=Call_NthChild(CallID,aCall,TestNode,OnlySpecifity);
  984. else
  985. if OnlySpecifity then
  986. Result:=0
  987. else
  988. Result:=CSSSpecifityInvalid;
  989. end;
  990. end;
  991. function TCSSResolver.Call_Not(aCall: TCSSCallElement;
  992. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  993. // :not(arg1, arg2, ...)
  994. // :not(args) has the same specifity as :not(:is(args))
  995. var
  996. i: Integer;
  997. Specifity: TCSSSpecifity;
  998. HasMatch: Boolean;
  999. begin
  1000. Result:=0;
  1001. HasMatch:=false;
  1002. for i:=0 to aCall.ArgCount-1 do
  1003. begin
  1004. Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
  1005. if Specifity>=0 then
  1006. HasMatch:=true
  1007. else begin
  1008. // the specifity of :is is the highest, independent of matching (forgiving)
  1009. Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
  1010. end;
  1011. if Specifity>Result then
  1012. Result:=Specifity;
  1013. end;
  1014. if OnlySpecifity then
  1015. // return best
  1016. else if HasMatch then
  1017. Result:=CSSSpecifityNoMatch;
  1018. end;
  1019. function TCSSResolver.Call_Is(aCall: TCSSCallElement; const TestNode: ICSSNode;
  1020. OnlySpecifity: boolean): TCSSSpecifity;
  1021. var
  1022. i: Integer;
  1023. Specifity: TCSSSpecifity;
  1024. ok: Boolean;
  1025. begin
  1026. Result:=0;
  1027. ok:=false;
  1028. for i:=0 to aCall.ArgCount-1 do
  1029. begin
  1030. Specifity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecifity);
  1031. if Specifity>=0 then
  1032. ok:=true
  1033. else begin
  1034. // the specifity of :is is the highest, independent of matching (forgiving)
  1035. Specifity:=SelectorMatches(aCall.Args[i],TestNode,true);
  1036. end;
  1037. if Specifity>Result then
  1038. Result:=Specifity;
  1039. end;
  1040. if (not ok) and (not OnlySpecifity) then
  1041. Result:=CSSSpecifityNoMatch;
  1042. end;
  1043. function TCSSResolver.Call_Where(aCall: TCSSCallElement;
  1044. const TestNode: ICSSNode; OnlySpecifity: boolean): TCSSSpecifity;
  1045. var
  1046. i: Integer;
  1047. begin
  1048. Result:=0;
  1049. if OnlySpecifity then
  1050. exit;
  1051. for i:=0 to aCall.ArgCount-1 do
  1052. begin
  1053. if SelectorMatches(aCall.Args[i],TestNode,false)>=0 then
  1054. // Note: :where is forgiving, so invalid arguments are ignored
  1055. exit;
  1056. end;
  1057. Result:=CSSSpecifityNoMatch;
  1058. end;
  1059. function TCSSResolver.Call_NthChild(CallID: TCSSNumericalID;
  1060. aCall: TCSSCallElement; const TestNode: ICSSNode; OnlySpecifity: boolean
  1061. ): TCSSSpecifity;
  1062. procedure NthWarn(const ID: TCSSMsgID; const Msg: string; PosEl: TCSSElement);
  1063. begin
  1064. Log(etWarning,ID,CSSPseudoNames[CallID]+' '+Msg,PosEl);
  1065. end;
  1066. var
  1067. i, ArgCount, aModulo, aStart: Integer;
  1068. Arg, OffsetEl: TCSSElement;
  1069. Str: TCSSString;
  1070. UnaryEl, anUnary: TCSSUnaryElement;
  1071. Params: TCSSCallNthChildParams;
  1072. CallData: TCSSCallData;
  1073. ChildIDs: TIntegerDynArray;
  1074. begin
  1075. if OnlySpecifity then
  1076. Result:=CSSSpecifityClass
  1077. else
  1078. Result:=CSSSpecifityInvalid;
  1079. CallData:=TCSSCallData(aCall.CustomData);
  1080. Params:=TCSSCallNthChildParams(CallData.Params);
  1081. if Params=nil then
  1082. begin
  1083. ArgCount:=aCall.ArgCount;
  1084. {$IFDEF VerboseCSSResolver}
  1085. writeln('TCSSResolver.Call_NthChild ',aCall.ArgCount);
  1086. for i:=0 to aCall.ArgCount-1 do
  1087. writeln('TCSSResolver.Call_NthChild ',i,' ',GetCSSObj(aCall.Args[i]),' AsString=',aCall.Args[i].AsString);
  1088. {$ENDIF}
  1089. // An+B[of S], odd, even, An
  1090. i:=0;
  1091. aModulo:=0;
  1092. aStart:=1;
  1093. // check step
  1094. if ArgCount<=i then
  1095. begin
  1096. NthWarn(20220915143843,'missing arguments',aCall);
  1097. exit;
  1098. end;
  1099. Arg:=aCall.Args[i];
  1100. if Arg.ClassType=TCSSIntegerElement then
  1101. begin
  1102. aModulo:=TCSSIntegerElement(Arg).Value;
  1103. inc(i);
  1104. // check n
  1105. if ArgCount<=i then
  1106. begin
  1107. NthWarn(20220915143843,'missing arguments',aCall);
  1108. exit;
  1109. end;
  1110. Arg:=aCall.Args[i];
  1111. if Arg.ClassType<>TCSSIdentifierElement then
  1112. begin
  1113. NthWarn(20220915144312,'expected n',Arg);
  1114. exit;
  1115. end;
  1116. if TCSSIdentifierElement(Arg).Value<>'n' then
  1117. begin
  1118. NthWarn(20220915144359,'expected n',Arg);
  1119. exit;
  1120. end;
  1121. end
  1122. else if Arg.ClassType=TCSSIdentifierElement then
  1123. begin
  1124. Str:=TCSSIdentifierElement(Arg).Value;
  1125. case lowercase(Str) of
  1126. 'even':
  1127. begin
  1128. //writeln('TCSSResolver.Call_NthChild EVEN');
  1129. aModulo:=2;
  1130. aStart:=2;
  1131. end;
  1132. 'odd':
  1133. begin
  1134. //writeln('TCSSResolver.Call_NthChild ODD');
  1135. aModulo:=2;
  1136. end;
  1137. 'n':
  1138. begin
  1139. //writeln('TCSSResolver.Call_NthChild N');
  1140. aModulo:=1;
  1141. end;
  1142. else
  1143. NthWarn(20220915150332,'expected multiplier',Arg);
  1144. exit;
  1145. end
  1146. end else if Arg.ClassType=TCSSUnaryElement then
  1147. begin
  1148. anUnary:=TCSSUnaryElement(Arg);
  1149. case anUnary.Operation of
  1150. uoMinus: aModulo:=-1;
  1151. uoPlus: aModulo:=1;
  1152. else
  1153. NthWarn(20220917080309,'expected multiplier',Arg);
  1154. exit;
  1155. end;
  1156. if (anUnary.Right.ClassType=TCSSIdentifierElement)
  1157. and (SameText(TCSSIdentifierElement(anUnary.Right).Value,'n')) then
  1158. begin
  1159. // ok
  1160. end else begin
  1161. NthWarn(20220917080154,'expected multiplier',Arg);
  1162. exit;
  1163. end;
  1164. end else
  1165. begin
  1166. NthWarn(20220915144056,'expected multiplier',Arg);
  1167. exit;
  1168. end;
  1169. inc(i);
  1170. if ArgCount>i then
  1171. begin
  1172. Arg:=aCall.Args[i];
  1173. if Arg.ClassType=TCSSUnaryElement then
  1174. begin
  1175. UnaryEl:=TCSSUnaryElement(Arg);
  1176. //writeln('TCSSResolver.Call_NthChild UNARY ',UnaryEl.AsString);
  1177. if not (UnaryEl.Operation in [uoMinus,uoPlus]) then
  1178. begin
  1179. NthWarn(20220915151422,'unexpected offset',UnaryEl);
  1180. exit;
  1181. end;
  1182. OffsetEl:=UnaryEl.Right;
  1183. if OffsetEl=nil then
  1184. begin
  1185. NthWarn(20220915151511,'unexpected offset',UnaryEl);
  1186. exit;
  1187. end;
  1188. if OffsetEl.ClassType<>TCSSIntegerElement then
  1189. begin
  1190. NthWarn(20220915151718,'unexpected offset',OffsetEl);
  1191. exit;
  1192. end;
  1193. aStart:=TCSSIntegerElement(OffsetEl).Value;
  1194. if UnaryEl.Operation=uoMinus then
  1195. aStart:=-aStart;
  1196. end else
  1197. begin
  1198. NthWarn(20220915150851,'expected offset',Arg);
  1199. exit;
  1200. end;
  1201. end;
  1202. Params:=TCSSCallNthChildParams.Create;
  1203. CallData.Params:=Params;
  1204. Params.Modulo:=aModulo;
  1205. Params.Start:=aStart;
  1206. inc(i);
  1207. if (i<ArgCount) then
  1208. begin
  1209. Arg:=aCall.Args[i];
  1210. if (Arg.ClassType=TCSSIdentifierElement)
  1211. and (SameText(TCSSIdentifierElement(Arg).Value,'of')) then
  1212. begin
  1213. // An+B of Selector
  1214. inc(i);
  1215. if i=ArgCount then
  1216. begin
  1217. NthWarn(20220915150851,'expected selector',Arg);
  1218. exit;
  1219. end;
  1220. Arg:=aCall.Args[i];
  1221. Params.HasOf:=true;
  1222. Params.OfSelector:=Arg;
  1223. end;
  1224. end;
  1225. if (CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType]) then
  1226. Params.HasOf:=true;
  1227. end else begin
  1228. aModulo:=Params.Modulo;
  1229. aStart:=Params.Start;
  1230. end;
  1231. if OnlySpecifity then
  1232. begin
  1233. if Params.OfSelector<>nil then
  1234. inc(Result,SelectorMatches(Params.OfSelector,TestNode,true));
  1235. exit;
  1236. end;
  1237. Result:=CSSSpecifityNoMatch;
  1238. if aModulo=0 then
  1239. exit;
  1240. i:=TestNode.GetCSSIndex;
  1241. if Params.HasOf then
  1242. begin
  1243. ChildIDs:=CollectSiblingsOf(CallID,TestNode,Params);
  1244. i:=GetSiblingOfIndex(ChildIDs,i);
  1245. end else
  1246. ChildIDs:=nil;
  1247. {$IFDEF VerboseCSSResolver}
  1248. //writeln('TCSSResolver.Call_NthChild CallID=',CallID,' ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex,' i=',i,' HasOf=',Params.HasOf,' OfChildCount=',length(Params.ChildIDs));
  1249. {$ENDIF}
  1250. if i<0 then
  1251. exit;
  1252. if CallID in [CSSCallID_NthLastChild,CSSCallID_NthLastOfType] then
  1253. begin
  1254. if Params.HasOf then
  1255. i:=length(ChildIDs)-i
  1256. else
  1257. i:=GetSiblingCount(TestNode)-i;
  1258. end else
  1259. begin
  1260. i:=i+1;
  1261. end;
  1262. dec(i,aStart);
  1263. if i mod aModulo = 0 then
  1264. begin
  1265. i:=i div aModulo;
  1266. if i>=0 then
  1267. Result:=CSSSpecifityClass;
  1268. end;
  1269. {$IFDEF VerboseCSSResolver}
  1270. //writeln('TCSSResolver.Call_NthChild ',aModulo,' * N + ',aStart,' Index=',TestNode.GetCSSIndex+1,' Result=',Result);
  1271. {$ENDIF}
  1272. end;
  1273. function TCSSResolver.CollectSiblingsOf(CallID: TCSSNumericalID;
  1274. TestNode: ICSSNode; Params: TCSSCallNthChildParams): TIntegerDynArray;
  1275. var
  1276. i, Depth, ChildCount, j: Integer;
  1277. aTypeID: TCSSNumericalID;
  1278. aParent, aNode: ICSSNode;
  1279. aSelector: TCSSElement;
  1280. StackDepth: SizeInt;
  1281. Cache: TCSSCallNthChildParamsCache;
  1282. Item: PCSSCallNthChildParamsCacheItem;
  1283. NeedTypeID: Boolean;
  1284. begin
  1285. Result:=nil;
  1286. aParent:=TestNode.GetCSSParent;
  1287. {$IFDEF VerboseCSSResolver}
  1288. //writeln('TCSSResolver.CollectSiblingsOf HasParent=',aParent<>nil);
  1289. {$ENDIF}
  1290. if aParent=nil then exit;
  1291. ChildCount:=aParent.GetCSSChildCount;
  1292. if ChildCount=0 then exit;
  1293. Depth:=aParent.GetCSSDepth;
  1294. StackDepth:=length(Params.StackCache);
  1295. if StackDepth<=Depth then
  1296. begin
  1297. SetLength(Params.StackCache,Depth+1);
  1298. for i:=StackDepth to Depth do
  1299. Params.StackCache[i]:=nil;
  1300. end;
  1301. Cache:=Params.StackCache[Depth];
  1302. if Cache=nil then
  1303. begin
  1304. Cache:=TCSSCallNthChildParamsCache.Create;
  1305. Params.StackCache[Depth]:=Cache;
  1306. Cache.Owner:=Params;
  1307. Cache.StackDepth:=Depth;
  1308. end;
  1309. NeedTypeID:=CallID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType];
  1310. if Cache.Parent<>aParent then
  1311. begin
  1312. // build cache
  1313. Cache.Parent:=aParent;
  1314. SetLength(Cache.Items,0);
  1315. {$IFDEF VerboseCSSResolver}
  1316. writeln('TCSSResolver.CollectSiblingsOf Depth=',Depth,' Candidates=',ChildCount);
  1317. {$ENDIF}
  1318. aSelector:=Params.OfSelector;
  1319. for i:=0 to ChildCount-1 do
  1320. begin
  1321. aNode:=aParent.GetCSSChild(i);
  1322. if (aSelector<>nil) and (SelectorMatches(aSelector,aNode,false)<0) then
  1323. continue;
  1324. if NeedTypeID then
  1325. aTypeID:=aNode.GetCSSTypeID
  1326. else
  1327. aTypeID:=0;
  1328. j:=length(Cache.Items)-1;
  1329. while (j>=0) and (Cache.Items[j].TypeID<>aTypeID) do dec(j);
  1330. if j<0 then
  1331. begin
  1332. j:=length(Cache.Items);
  1333. SetLength(Cache.Items,j+1);
  1334. Item:[email protected][j];
  1335. Item^.TypeID:=aTypeID;
  1336. Item^.Cnt:=0;
  1337. SetLength(Item^.ChildIDs,ChildCount);
  1338. end else
  1339. Item:[email protected][j];
  1340. Item^.ChildIDs[Item^.Cnt]:=i;
  1341. {$IFDEF VerboseCSSResolver}
  1342. writeln('TCSSResolver.CollectSiblingsOf Sel=',GetCSSObj(aSelector),' CSSTypeID=',aNode.GetCSSTypeID,' ',Item^.Cnt,'=>',i);
  1343. {$ENDIF}
  1344. inc(Item^.Cnt);
  1345. end;
  1346. for i:=0 to high(Cache.Items) do
  1347. with Cache.Items[i] do
  1348. SetLength(ChildIDs,Cnt);
  1349. end;
  1350. // use cache
  1351. if NeedTypeID then
  1352. begin
  1353. aTypeID:=TestNode.GetCSSTypeID;
  1354. for i:=0 to high(Cache.Items) do
  1355. if Cache.Items[i].TypeID=aTypeID then
  1356. exit(Cache.Items[i].ChildIDs);
  1357. end else
  1358. Result:=Cache.Items[0].ChildIDs;
  1359. end;
  1360. function TCSSResolver.GetSiblingOfIndex(SiblingIDs: TIntegerDynArray;
  1361. Index: integer): integer;
  1362. // searches the position of Index in a sorted array
  1363. var
  1364. l, r, m: Integer;
  1365. begin
  1366. l:=0;
  1367. r:=length(SiblingIDs)-1;
  1368. while l<=r do
  1369. begin
  1370. m:=(l+r) div 2;
  1371. Result:=SiblingIDs[m];
  1372. if Index<Result then
  1373. r:=m-1
  1374. else if Index>Result then
  1375. l:=m+1
  1376. else
  1377. exit(m);
  1378. end;
  1379. Result:=-1;
  1380. end;
  1381. function TCSSResolver.ComputeValue(El: TCSSElement): TCSSString;
  1382. var
  1383. ElData: TObject;
  1384. C: TClass;
  1385. StrEl: TCSSStringElement;
  1386. IntEl: TCSSIntegerElement;
  1387. FloatEl: TCSSFloatElement;
  1388. begin
  1389. C:=El.ClassType;
  1390. if C=TCSSIdentifierElement then
  1391. Result:=TCSSIdentifierElement(El).Value
  1392. else if (C=TCSSStringElement)
  1393. or (C=TCSSIntegerElement)
  1394. or (C=TCSSFloatElement) then
  1395. begin
  1396. ElData:=El.CustomData;
  1397. if ElData is TCSSValueData then
  1398. exit(TCSSValueData(ElData).NormValue);
  1399. if C=TCSSStringElement then
  1400. begin
  1401. StrEl:=TCSSStringElement(El);
  1402. Result:=StrEl.Value;
  1403. {$IFDEF VerboseCSSResolver}
  1404. writeln('TCSSResolver.ComputeValue String=[',Result,']');
  1405. {$ENDIF}
  1406. end
  1407. else if C=TCSSIntegerElement then
  1408. begin
  1409. IntEl:=TCSSIntegerElement(El);
  1410. Result:=IntEl.AsString;
  1411. {$IFDEF VerboseCSSResolver}
  1412. writeln('TCSSResolver.ComputeValue Integer=[',Result,']');
  1413. {$ENDIF}
  1414. end else if C=TCSSFloatElement then
  1415. begin
  1416. FloatEl:=TCSSFloatElement(El);
  1417. Result:=FloatEl.AsString;
  1418. {$IFDEF VerboseCSSResolver}
  1419. writeln('TCSSResolver.ComputeValue Float=[',Result,']');
  1420. {$ENDIF}
  1421. end;
  1422. AddElValueData(El,Result);
  1423. end else begin
  1424. LogWarning(croErrorOnUnknownName in Options,20220910235106,'TCSSResolver.ComputeValue not supported',El);
  1425. end;
  1426. end;
  1427. function TCSSResolver.SameValueText(const A, B: TCSSString): boolean;
  1428. begin
  1429. if StringComparison=crscCaseInsensitive then
  1430. Result:=SameText(A,B)
  1431. else
  1432. Result:=A=B;
  1433. end;
  1434. function TCSSResolver.SameValueText(A: PAnsiChar; ALen: integer; B: PAnsiChar;
  1435. BLen: integer): boolean;
  1436. var
  1437. AC, BC: AnsiChar;
  1438. i: Integer;
  1439. begin
  1440. if ALen<>BLen then exit(false);
  1441. if ALen=0 then exit(true);
  1442. if StringComparison=crscCaseInsensitive then
  1443. begin
  1444. for i:=0 to ALen-1 do
  1445. begin
  1446. AC:=A^;
  1447. BC:=B^;
  1448. if (AC<>BC) and (UpCase(AC)<>UpCase(BC)) then
  1449. exit(false);
  1450. inc(A);
  1451. inc(B);
  1452. end;
  1453. Result:=true;
  1454. end else
  1455. Result:=CompareMem(A,B,ALen);
  1456. end;
  1457. function TCSSResolver.PosSubString(const SearchStr, Str: TCSSString): integer;
  1458. var
  1459. SearchLen: SizeInt;
  1460. i: Integer;
  1461. SearchP, StrP: PAnsiChar;
  1462. AC, BC: AnsiChar;
  1463. begin
  1464. Result:=0;
  1465. if SearchStr='' then exit;
  1466. if Str='' then exit;
  1467. if StringComparison=crscCaseInsensitive then
  1468. begin
  1469. SearchP:=PAnsiChar(SearchStr);
  1470. StrP:=PAnsiChar(Str);
  1471. SearchLen:=length(SearchStr);
  1472. AC:=SearchP^;
  1473. for i:=0 to length(Str)-SearchLen do
  1474. begin
  1475. BC:=StrP^;
  1476. if (upcase(AC)=upcase(BC)) and SameValueText(SearchP,SearchLen,StrP,SearchLen) then
  1477. exit(i+1);
  1478. inc(StrP);
  1479. end;
  1480. end else begin
  1481. Result:=Pos(SearchStr,Str);
  1482. end;
  1483. end;
  1484. function TCSSResolver.PosWord(const SearchWord, Words: TCSSString): integer;
  1485. // attribute selector ~=
  1486. const
  1487. Whitespace = [#9,#10,#12,#13,' '];
  1488. var
  1489. WordsLen, SearchLen: SizeInt;
  1490. p, WordStart: Integer;
  1491. begin
  1492. Result:=0;
  1493. if SearchWord='' then exit;
  1494. if Words='' then exit;
  1495. WordsLen:=length(Words);
  1496. SearchLen:=length(SearchWord);
  1497. //writeln('TCSSResolver.PosWord "',SearchWord,'" Words="',words,'"');
  1498. p:=1;
  1499. repeat
  1500. repeat
  1501. if p>WordsLen then
  1502. exit(0);
  1503. if not (Words[p] in Whitespace) then
  1504. break;
  1505. inc(p);
  1506. until false;
  1507. WordStart:=p;
  1508. while (p<=WordsLen) and not (Words[p] in Whitespace) do
  1509. inc(p);
  1510. //writeln('TCSSResolver.PosWord start=',WordStart,' p=',p);
  1511. if SameValueText(@SearchWord[1],SearchLen,@Words[WordStart],p-WordStart) then
  1512. exit(WordStart);
  1513. until p>WordsLen;
  1514. end;
  1515. function TCSSResolver.GetSiblingCount(aNode: ICSSNode): integer;
  1516. var
  1517. aParent, CurNode: ICSSNode;
  1518. begin
  1519. if aNode=nil then
  1520. exit(0);
  1521. aParent:=aNode.GetCSSParent;
  1522. if aParent<>nil then
  1523. exit(aParent.GetCSSChildCount);
  1524. Result:=0;
  1525. CurNode:=aNode;
  1526. while CurNode<>nil do
  1527. begin
  1528. inc(Result);
  1529. CurNode:=CurNode.GetCSSPreviousSibling;
  1530. end;
  1531. CurNode:=aNode.GetCSSNextSibling;
  1532. while CurNode<>nil do
  1533. begin
  1534. inc(Result);
  1535. CurNode:=CurNode.GetCSSNextSibling;
  1536. end;
  1537. end;
  1538. procedure TCSSResolver.MergeProperty(El: TCSSElement; Specifity: TCSSSpecifity);
  1539. var
  1540. C: TClass;
  1541. Decl: TCSSDeclarationElement;
  1542. aKey, aValue: TCSSElement;
  1543. AttrID: TCSSNumericalID;
  1544. CompAttr: PCSSComputedAttribute;
  1545. begin
  1546. C:=El.ClassType;
  1547. if C=TCSSDeclarationElement then
  1548. begin
  1549. Decl:=TCSSDeclarationElement(El);
  1550. if Decl.KeyCount<>1 then begin
  1551. Log(etWarning,20220908232213,'Not yet implemented CSS declaration with KeyCount='+IntToStr(Decl.KeyCount),El);
  1552. exit;
  1553. end;
  1554. if Decl.ChildCount<>1 then begin
  1555. Log(etWarning,20220908232324,'Not yet implemented CSS declaration with ChildCount='+IntToStr(Decl.ChildCount),El);
  1556. exit;
  1557. end;
  1558. aKey:=Decl.Keys[0];
  1559. aValue:=Decl.Children[0];
  1560. if Decl.IsImportant then
  1561. Specifity:=CSSSpecifityImportant;
  1562. C:=aKey.ClassType;
  1563. if C=TCSSIdentifierElement then
  1564. begin
  1565. AttrID:=ResolveIdentifier(TCSSIdentifierElement(aKey),nikAttribute);
  1566. if AttrID=CSSIDNone then
  1567. Log(etWarning,20220909000932,'Unknown CSS property "'+TCSSIdentifierElement(aKey).Name+'"',aKey)
  1568. else if AttrID=CSSAttributeID_All then
  1569. // 'all'
  1570. Log(etWarning,20220909001019,'Not yet implemented CSS property "'+TCSSIdentifierElement(aKey).Name+'"',aKey)
  1571. else begin
  1572. // set property
  1573. CompAttr:=FindComputedAttribute(AttrID);
  1574. if CompAttr<>nil then
  1575. begin
  1576. if CompAttr^.Specifity>Specifity then
  1577. exit;
  1578. if not CheckAttrValueValidity(AttrID,aKey,aValue) then
  1579. exit;
  1580. CompAttr^.Specifity:=Specifity;
  1581. CompAttr^.Value:=aValue;
  1582. end else begin
  1583. if not CheckAttrValueValidity(AttrID,aKey,aValue) then
  1584. exit;
  1585. AddComputedAttribute(AttrID,Specifity,aValue);
  1586. end;
  1587. end;
  1588. end else
  1589. Log(etWarning,20220908232359,'Unknown CSS key',aKey);
  1590. end else
  1591. Log(etWarning,20220908230855,'Unknown CSS property',El);
  1592. end;
  1593. function TCSSResolver.CheckAttrValueValidity(AttrID: TCSSNumericalID; aKey,
  1594. aValue: TCSSElement): boolean;
  1595. var
  1596. Data: TCSSIdentifierData;
  1597. begin
  1598. if not (aKey.CustomData is TCSSIdentifierData) then
  1599. raise Exception.Create('TCSSResolver.CheckAttrValueValidity 20221019173901');
  1600. Data:=TCSSIdentifierData(aKey.CustomData);
  1601. case Data.ValueValid of
  1602. cvvValid: exit(true);
  1603. cvvInvalid: exit(false);
  1604. end;
  1605. Result:=FNode.CheckCSSValue(AttrID,aValue);
  1606. if Result then
  1607. Data.ValueValid:=cvvValid
  1608. else
  1609. Data.ValueValid:=cvvInvalid;
  1610. end;
  1611. function TCSSResolver.ResolveIdentifier(El: TCSSIdentifierElement;
  1612. Kind: TCSSNumericalIDKind): TCSSNumericalID;
  1613. var
  1614. Data: TObject;
  1615. IdentData: TCSSIdentifierData;
  1616. aName: TCSSString;
  1617. begin
  1618. Data:=El.CustomData;
  1619. if Data<>nil then
  1620. begin
  1621. IdentData:=TCSSIdentifierData(Data);
  1622. Result:=IdentData.NumericalID;
  1623. {$IFDEF VerboseCSSResolver}
  1624. if IdentData.Kind<>Kind then
  1625. Log(etError,20220908235300,'TCSSResolver.ResolveIdentifier',El);
  1626. {$ENDIF}
  1627. end else
  1628. begin
  1629. aName:=El.Name;
  1630. Result:=CSSIDNone;
  1631. // check built-in names
  1632. case Kind of
  1633. nikType:
  1634. case aName of
  1635. '*': Result:=CSSTypeID_Universal;
  1636. end;
  1637. nikAttribute:
  1638. case aName of
  1639. 'id': Result:=CSSAttributeID_ID;
  1640. 'class': Result:=CSSAttributeID_Class;
  1641. 'all': Result:=CSSAttributeID_All;
  1642. end;
  1643. nikPseudoAttribute:
  1644. begin
  1645. aName:=lowercase(aName); // pseudo attributes are ASCII case insensitive
  1646. case aName of
  1647. ':root': Result:=CSSPseudoID_Root;
  1648. ':empty': Result:=CSSPseudoID_Empty;
  1649. ':first-child': Result:=CSSPseudoID_FirstChild;
  1650. ':last-child': Result:=CSSPseudoID_LastChild;
  1651. ':only-child': Result:=CSSPseudoID_OnlyChild;
  1652. ':first-of-type': Result:=CSSPseudoID_FirstOfType;
  1653. ':last-of-type': Result:=CSSPseudoID_LastOfType;
  1654. ':only-of-type': Result:=CSSPseudoID_OnlyOfType;
  1655. end;
  1656. end;
  1657. end;
  1658. // resolve user defined names
  1659. //writeln('TCSSResolver.ResolveIdentifier ',Kind,' "',aName,'"');
  1660. if Result=CSSIDNone then
  1661. Result:=FNumericalIDs[Kind][aName];
  1662. if Result=CSSIDNone then
  1663. begin
  1664. LogWarning(croErrorOnUnknownName in FOptions,20220908235919,'TCSSResolver.ResolveIdentifier unknown '+CSSNumericalIDKindNames[Kind]+' "'+El.Name+'"',El);
  1665. exit;
  1666. end;
  1667. IdentData:=TCSSIdentifierData.Create;
  1668. IdentData.Kind:=Kind;
  1669. IdentData.NumericalID:=Result;
  1670. AddElData(El,IdentData);
  1671. end;
  1672. end;
  1673. function TCSSResolver.ResolveCall(El: TCSSCallElement): TCSSNumericalID;
  1674. var
  1675. Data: TObject;
  1676. CallData: TCSSCallData;
  1677. aName: TCSSString;
  1678. begin
  1679. Data:=El.CustomData;
  1680. if Data<>nil then
  1681. begin
  1682. CallData:=TCSSCallData(Data);
  1683. Result:=CallData.NumericalID;
  1684. end else
  1685. begin
  1686. aName:=El.Name;
  1687. Result:=CSSIDNone;
  1688. case aName of
  1689. ':not': Result:=CSSCallID_Not;
  1690. ':is': Result:=CSSCallID_Is;
  1691. ':where': Result:=CSSCallID_Where;
  1692. ':has': Result:=CSSCallID_Has;
  1693. ':nth-child': Result:=CSSCallID_NthChild;
  1694. ':nth-last-child': Result:=CSSCallID_NthLastChild;
  1695. ':nth-of-type': Result:=CSSCallID_NthOfType;
  1696. ':nth-last-of-type': Result:=CSSCallID_NthLastOfType;
  1697. else
  1698. LogWarning(croErrorOnUnknownName in FOptions,20220914193946,'TCSSResolver.ResolveCall unknown "'+El.Name+'"',El);
  1699. exit;
  1700. end;
  1701. CallData:=TCSSCallData.Create;
  1702. CallData.NumericalID:=Result;
  1703. AddElData(El,CallData);
  1704. end;
  1705. end;
  1706. procedure TCSSResolver.AddElData(El: TCSSElement; ElData: TCSSElResolverData);
  1707. begin
  1708. El.CustomData:=ElData;
  1709. ElData.Element:=El;
  1710. if FFirstElData=nil then
  1711. begin
  1712. FFirstElData:=ElData;
  1713. end else begin
  1714. FLastElData.Next:=ElData;
  1715. ElData.Prev:=FLastElData;
  1716. end;
  1717. FLastElData:=ElData;
  1718. end;
  1719. function TCSSResolver.AddElValueData(El: TCSSElement; const aValue: TCSSString
  1720. ): TCSSValueData;
  1721. begin
  1722. Result:=TCSSValueData.Create;
  1723. Result.NormValue:=aValue;
  1724. AddElData(El,Result);
  1725. end;
  1726. function TCSSResolver.FindComputedAttribute(AttrID: TCSSNumericalID
  1727. ): PCSSComputedAttribute;
  1728. var
  1729. i: Integer;
  1730. begin
  1731. for i:=0 to FAttributeCount-1 do
  1732. if FAttributes[i].AttrID=AttrID then
  1733. exit(@FAttributes[i]);
  1734. Result:=nil;
  1735. end;
  1736. function TCSSResolver.AddComputedAttribute(TheAttrID: TCSSNumericalID;
  1737. aSpecifity: TCSSSpecifity; aValue: TCSSElement): PCSSComputedAttribute;
  1738. var
  1739. NewLength: Integer;
  1740. begin
  1741. if FAttributeCount=length(FAttributes) then
  1742. begin
  1743. NewLength:=FAttributeCount*2;
  1744. if NewLength<16 then
  1745. NewLength:=16;
  1746. SetLength(FAttributes,NewLength);
  1747. end;
  1748. with FAttributes[FAttributeCount] do
  1749. begin
  1750. AttrID:=TheAttrID;
  1751. Specifity:=aSpecifity;
  1752. Value:=aValue;
  1753. end;
  1754. Result:=@FAttributes[FAttributeCount];
  1755. inc(FAttributeCount);
  1756. end;
  1757. procedure TCSSResolver.LogWarning(IsError: boolean; const ID: TCSSMsgID;
  1758. Msg: string; PosEl: TCSSElement);
  1759. var
  1760. MsgType: TEventType;
  1761. begin
  1762. if IsError then
  1763. MsgType:=etError
  1764. else
  1765. MsgType:=etWarning;
  1766. Log(MsgType,ID,Msg,PosEl);
  1767. end;
  1768. procedure TCSSResolver.Log(MsgType: TEventType; const ID: TCSSMsgID;
  1769. Msg: string; PosEl: TCSSElement);
  1770. var
  1771. Entry: TCSSResolverLogEntry;
  1772. i: Integer;
  1773. begin
  1774. if Assigned(OnLog) then
  1775. begin
  1776. for i:=0 to FLogEntries.Count-1 do
  1777. begin
  1778. Entry:=LogEntries[i];
  1779. if (Entry.PosEl=PosEl)
  1780. and (Entry.ID=ID)
  1781. and (Entry.MsgType=MsgType)
  1782. and (Entry.Msg=Msg) then
  1783. exit; // this warning was already logged
  1784. end;
  1785. Entry:=TCSSResolverLogEntry.Create;
  1786. Entry.MsgType:=MsgType;
  1787. Entry.ID:=ID;
  1788. Entry.Msg:=Msg;
  1789. Entry.PosEl:=PosEl;
  1790. FLogEntries.Add(Entry);
  1791. OnLog(Self,Entry);
  1792. end;
  1793. if (MsgType=etError) or (FOnLog=nil) then
  1794. begin
  1795. Msg:='['+IntToStr(ID)+'] '+Msg+' at '+GetElPos(PosEl);
  1796. raise ECSSResolver.Create(Msg);
  1797. end;
  1798. end;
  1799. function TCSSResolver.GetElPos(El: TCSSElement): string;
  1800. begin
  1801. if El=nil then
  1802. Result:='no element'
  1803. else begin
  1804. Result:=El.SourceFileName+'('+IntToStr(El.SourceCol)+','+IntToStr(El.SourceCol)+')';
  1805. {$IFDEF VerboseCSSResolver}
  1806. Result:='['+GetElPath(El)+']'+Result;
  1807. {$ENDIF}
  1808. end;
  1809. end;
  1810. function TCSSResolver.GetElPath(El: TCSSElement): string;
  1811. begin
  1812. Result:=GetCSSPath(El);
  1813. end;
  1814. constructor TCSSResolver.Create(AOwner: TComponent);
  1815. begin
  1816. inherited;
  1817. FLogEntries:=TFPObjectList.Create(true);
  1818. end;
  1819. destructor TCSSResolver.Destroy;
  1820. begin
  1821. Clear;
  1822. FreeAndNil(FLogEntries);
  1823. inherited Destroy;
  1824. end;
  1825. procedure TCSSResolver.Clear;
  1826. begin
  1827. FLogEntries.Clear;
  1828. ClearStyleCustomData;
  1829. ClearStyles;
  1830. end;
  1831. procedure TCSSResolver.ClearStyleCustomData;
  1832. var
  1833. Data: TCSSElResolverData;
  1834. begin
  1835. while FLastElData<>nil do
  1836. begin
  1837. Data:=FLastElData;
  1838. FLastElData:=Data.Prev;
  1839. if FLastElData<>nil then
  1840. FLastElData.Next:=nil
  1841. else
  1842. FFirstElData:=nil;
  1843. if Data.Element.CustomData<>Data then
  1844. Log(etError,20220908234726,'TCSSResolver.ClearStyleCustomData',Data.Element);
  1845. Data.Element.CustomData:=nil;
  1846. Data.Free;
  1847. end;
  1848. end;
  1849. procedure TCSSResolver.Compute(Node: ICSSNode; NodeStyle: TCSSElement;
  1850. const CompOptions: TCSSComputeOptions);
  1851. var
  1852. i: Integer;
  1853. begin
  1854. FNode:=Node;
  1855. try
  1856. FAttributeCount:=0;
  1857. for i:=0 to high(FStyles) do
  1858. ComputeElement(Styles[i]);
  1859. ComputeInline(NodeStyle);
  1860. if ccoCommit in CompOptions then
  1861. Commit;
  1862. finally
  1863. FNode:=nil;
  1864. end;
  1865. end;
  1866. procedure TCSSResolver.Commit;
  1867. var
  1868. i: Integer;
  1869. begin
  1870. //writeln('TCSSResolver.Commit FAttributeCount=',FAttributeCount);
  1871. for i:=0 to FAttributeCount-1 do
  1872. with FAttributes[i] do
  1873. FNode.SetCSSValue(AttrID,Value);
  1874. end;
  1875. procedure TCSSResolver.AddStyle(aStyle: TCSSElement);
  1876. begin
  1877. if aStyle=nil then exit;
  1878. Insert(aStyle,FStyles,length(FStyles));
  1879. end;
  1880. function TCSSResolver.IndexOfStyle(aStyle: TCSSElement): integer;
  1881. begin
  1882. Result:=high(FStyles);
  1883. while (Result>=0) and (FStyles[Result]<>aStyle) do dec(Result);
  1884. end;
  1885. procedure TCSSResolver.RemoveStyle(aStyle: TCSSElement);
  1886. var
  1887. i: Integer;
  1888. begin
  1889. i:=IndexOfStyle(aStyle);
  1890. if i<0 then exit;
  1891. DeleteStyle(i);
  1892. end;
  1893. procedure TCSSResolver.DeleteStyle(aIndex: integer);
  1894. begin
  1895. if (aIndex<0) or (aIndex>=length(FStyles)) then
  1896. raise ECSSResolver.Create('TCSSResolver.DeleteStyle index '+IntToStr(aIndex)+' out of bounds '+IntToStr(length(FStyles)));
  1897. if OwnsStyle then
  1898. FStyles[aIndex].Free;
  1899. Delete(FStyles,aIndex,1);
  1900. end;
  1901. procedure TCSSResolver.ClearStyles;
  1902. var
  1903. i: Integer;
  1904. begin
  1905. if OwnsStyle then
  1906. for i:=0 to high(FStyles) do
  1907. FStyles[i].Free;
  1908. FStyles:=nil;
  1909. end;
  1910. end.