fpcssresolver.pas 93 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134
  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. type p Selects all <p> elements
  19. type.class p.intro Selects all <p> elements with class="intro"
  20. type,type div, p Selects all <div> elements and all <p> elements
  21. type type div p Selects all <p> elements inside <div> elements
  22. type>type div > p Selects all <p> elements where the parent is a <div> element
  23. type+type div + p Selects the first <p> element that is placed immediately after a <div> element
  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. Specificity:
  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, unicode bidi and custom css properties
  56. - :has()
  57. - namespaces
  58. - layers
  59. - --varname, var(), inherits
  60. - counter-reset
  61. - counter-increment
  62. - @rules:-----------------------------------------------------------------------
  63. - @media
  64. - @font-face
  65. - @keyframes
  66. - @property
  67. - Functions and Vars:-----------------------------------------------------------
  68. - attr() Returns the value of an attribute of the selected element
  69. attr(title)
  70. attr(src url)
  71. attr(data-width px, inherit);
  72. - calc() Allows you to perform calculations to determine CSS property values calc(100% - 100px)
  73. - max() min() minmax(minvalue,maxvalue) min(50%, 50px)
  74. keyword values max-content, min-content, or auto
  75. - clamp(minvalue,preferred,maxvalue) = max(MIN, min(VAL, MAX))
  76. - var() usable in property values, query custom css properties, inherits
  77. var(--name), var(--name, --default-name), var(--name, var(--foo, #FF0000))
  78. - Pseudo-elements - not case sensitive:-----------------------------------------
  79. - ::first-letter p::first-letter Selects the first letter of every <p> element
  80. - ::first-line p::first-line Selects the first line of every <p> element
  81. - ::selection ::selection Selects the portion of an element that is selected by a user
  82. - Altering:---------------------------------------------------------------------
  83. - ::after p::after Insert something after the content of each <p> element
  84. - ::before p::before Insert something before the content of each <p> element
  85. - grid-structural-selectors:----------------------------------------------------
  86. - columns combinator || col.selected || td
  87. - :nth-col()
  88. - :nth-last-col()
  89. }
  90. {$IFNDEF FPC_DOTTEDUNITS}
  91. unit fpCSSResolver;
  92. {$ENDIF FPC_DOTTEDUNITS}
  93. {$mode ObjFPC}{$H+}
  94. {$Interfaces CORBA}
  95. {$ModeSwitch AdvancedRecords}
  96. {$IF FPC_FULLVERSION>30300}
  97. {$WARN 6060 off} // Case statement does not handle all possible cases
  98. {$ENDIF}
  99. interface
  100. {$IFDEF FPC_DOTTEDUNITS}
  101. uses
  102. System.Classes, System.SysUtils, System.Types, System.Contnrs, System.StrUtils,
  103. Fcl.AVLTree, FpCss.Tree, FpCss.ValueParser;
  104. {$ELSE FPC_DOTTEDUNITS}
  105. uses
  106. Classes, SysUtils, types, Contnrs, AVL_Tree, StrUtils, fpCSSTree, fpCSSResParser;
  107. {$ENDIF FPC_DOTTEDUNITS}
  108. const
  109. CSSSpecificityInvalid = -2;
  110. CSSSpecificityNoMatch = -1;
  111. CSSSpecificityUniversal = 0;
  112. CSSSpecificityType = 1;
  113. CSSSpecificityClass = 10; // includes attribute selectors e.g. [href]
  114. CSSSpecificityIdentifier = 100;
  115. CSSSpecificityUserAgent = 1000;
  116. CSSSpecificityUser = 2000;
  117. CSSSpecificityAuthor = 3000;
  118. CSSSpecificityInline = 10000;
  119. CSSSpecificityImportant = 100000;
  120. type
  121. TCSSSpecificity = integer; // see CSSSpecificityInvalid..CSSSpecificityImportant
  122. TCSSOrigin = (
  123. cssoUserAgent,
  124. cssoUser,
  125. cssoAuthor
  126. );
  127. const
  128. CSSOriginToSpecifity: array[TCSSOrigin] of TCSSNumericalID = (
  129. CSSSpecificityUserAgent,
  130. CSSSpecificityUser,
  131. CSSSpecificityAuthor
  132. );
  133. type
  134. { ECSSResolver }
  135. ECSSResolver = class(ECSSException)
  136. end;
  137. TCSSAttributeMatchKind = (
  138. camkEqual,
  139. camkContains,
  140. camkContainsWord,
  141. camkBegins,
  142. camkEnds
  143. );
  144. TCSSAttributeMatchKinds = set of TCSSAttributeMatchKind;
  145. { ICSSNode }
  146. ICSSNode = interface
  147. function GetCSSID: TCSSString;
  148. function GetCSSTypeName: TCSSString;
  149. function GetCSSTypeID: TCSSNumericalID;
  150. function GetCSSPseudoElementName: TCSSString;
  151. function GetCSSPseudoElementID: TCSSNumericalID;
  152. // parent
  153. function GetCSSParent: ICSSNode;
  154. function GetCSSDepth: integer;
  155. function GetCSSIndex: integer; // node index in parent's children
  156. // siblings
  157. function GetCSSNextSibling: ICSSNode;
  158. function GetCSSPreviousSibling: ICSSNode;
  159. function GetCSSNextOfType: ICSSNode;
  160. function GetCSSPreviousOfType: ICSSNode;
  161. // children
  162. function GetCSSEmpty: boolean;
  163. function GetCSSChildCount: integer;
  164. function GetCSSChild(const anIndex: integer): ICSSNode;
  165. // attributes
  166. function HasCSSClass(const aClassName: TCSSString): boolean;
  167. function GetCSSAttributeClass: TCSSString; // get the 'class' attribute
  168. function GetCSSCustomAttribute(const AttrID: TCSSNumericalID): TCSSString;
  169. function HasCSSExplicitAttribute(const AttrID: TCSSNumericalID): boolean; // e.g. if the HTML has the attribute
  170. function GetCSSExplicitAttribute(const AttrID: TCSSNumericalID): TCSSString;
  171. function HasCSSPseudoClass(const AttrID: TCSSNumericalID): boolean;
  172. end;
  173. type
  174. { TCSSResCustomAttributeDesc }
  175. TCSSResCustomAttributeDesc = class(TCSSAttributeDesc)
  176. public
  177. end;
  178. TCSSResCustomAttributeDescArray = array of TCSSResCustomAttributeDesc;
  179. { TCSSResolvedAttribute - used for shared rule lists, merged by the cascade algorithm, not yet computed }
  180. TCSSResolvedAttribute = record
  181. AttrID: TCSSNumericalID;
  182. Specificity: TCSSSpecificity;
  183. DeclEl: TCSSDeclarationElement;
  184. end;
  185. TCSSResolvedAttributeArray = array of TCSSResolvedAttribute;
  186. PCSSResolvedAttribute = ^TCSSResolvedAttribute;
  187. TCSSSharedRule = record
  188. Rule: TCSSRuleElement;
  189. Specificity: TCSSSpecificity;
  190. end;
  191. PCSSSharedRule = ^TCSSSharedRule;
  192. TCSSSharedRuleArray = array of TCSSSharedRule;
  193. { TCSSSharedRuleList - elements with same CSS rules share the base attributes }
  194. TCSSSharedRuleList = class
  195. public
  196. AllDecl: TCSSDeclarationElement;
  197. AllSpecificity: TCSSSpecificity;
  198. Rules: TCSSSharedRuleArray; // sorted ascending for Specificity, secondary for source position
  199. Values: TCSSResolvedAttributeArray; // not sorted, merged, not computed
  200. destructor Destroy; override;
  201. procedure Clear;
  202. function Clone: TCSSSharedRuleList;
  203. function IndexOfAttr(AttrId: TCSSNumericalID; ForInsert: boolean = false): integer;
  204. end;
  205. { TCSSAttributeValue }
  206. TCSSAttributeValue = class
  207. public
  208. type
  209. TState = (
  210. cavsSource, // value from CSS - simple normalization, e.g. no comments, some spaces differ, floats
  211. cavsBaseKeywords, // base keywords resolved e.g. "initial" or "inherit"
  212. cavsComputed, // has final result
  213. cavsInvalid // skip this value
  214. );
  215. public
  216. AttrID: TCSSNumericalID; // the resolver has substituted all shorthands
  217. State: TState;
  218. Value: TCSSString; // the resolver has substituted all var() calls
  219. end;
  220. TCSSAttributeValueArray = array of TCSSAttributeValue;
  221. { TCSSAttributeValues }
  222. TCSSAttributeValues = class
  223. public
  224. AllValue: TCSSNumericalID;
  225. Values: TCSSAttributeValueArray; // the resolver sorts them ascending for AttrID, shorthands are already replaced with longhands
  226. procedure SortValues; virtual; // ascending AttrID
  227. function IndexOf(AttrID: TCSSNumericalID): integer;
  228. procedure SetComputedValue(AttrID: TCSSNumericalID; const aValue: TCSSString);
  229. destructor Destroy; override;
  230. end;
  231. TCSSResolverNthChildParamsCacheItem = record
  232. TypeID: TCSSNumericalID;
  233. ChildIDs: TIntegerDynArray;
  234. Cnt: integer; // = length(ChildIDs), used during creation
  235. end;
  236. PCSSNthChildParamsCacheItem = ^TCSSResolverNthChildParamsCacheItem;
  237. TCSSResolverNthChildParamsCacheItems = array of TCSSResolverNthChildParamsCacheItem;
  238. TCSSResolverNthChildParams = class;
  239. TCSSResolverNthChildParamsCache = class
  240. public
  241. Owner: TCSSResolverNthChildParams;
  242. Parent: ICSSNode;
  243. OfSelector: TCSSElement;
  244. StackDepth: integer;
  245. Items: TCSSResolverNthChildParamsCacheItems;
  246. end;
  247. TCSSResolverNthChildParamsCaches = array of TCSSResolverNthChildParamsCache;
  248. { TCSSResolverNthChildParams }
  249. TCSSResolverNthChildParams = class(TCSSNthChildParams)
  250. public
  251. StackCache: TCSSResolverNthChildParamsCaches;
  252. destructor Destroy; override;
  253. end;
  254. TCSSResolverOption = (
  255. croErrorOnUnknownName
  256. );
  257. TCSSResolverOptions = set of TCSSResolverOption;
  258. { TCSSResolverLogEntry }
  259. TCSSResolverLogEntry = class
  260. public
  261. MsgType: TEventType;
  262. ID: TCSSMsgID;
  263. Msg: TCSSString;
  264. PosEl: TCSSElement;
  265. end;
  266. TCSSResolverLogEntryClass = class of TCSSResolverLogEntry;
  267. TCSSResolverLogEntryArray = array of TCSSResolverLogEntry;
  268. TCSSResolverLogEvent = procedure(Sender: TObject; Entry: TCSSResolverLogEntry) of object;
  269. TCSSResStringComparison = (
  270. crscDefault,
  271. crscCaseInsensitive,
  272. crscCaseSensitive
  273. );
  274. TCSSResStringComparisons = set of TCSSResStringComparison;
  275. { TCSSResolver }
  276. TCSSResolver = class(TCSSBaseResolver)
  277. public
  278. type
  279. TStyleSheet = class
  280. Source: TCSSString;
  281. Name: TCSSString; // case sensitive
  282. Origin: TCSSOrigin;
  283. Element: TCSSElement;
  284. Parsed: boolean;
  285. end;
  286. TStyleSheets = array of TStyleSheet;
  287. TLayerElement = record
  288. Src: TStyleSheet;
  289. Element: TCSSElement;
  290. end;
  291. TLayerElements = array of TLayerElement;
  292. TLayer = record
  293. Name: TCSSString;
  294. Origin: TCSSOrigin;
  295. Elements: TLayerElements;
  296. ElementCount: integer;
  297. end;
  298. TLayerArray = array of TLayer;
  299. private
  300. FLayers: TLayerArray; // sorted for Origin, named layers before anonymous layers
  301. FOnLog: TCSSResolverLogEvent;
  302. FOptions: TCSSResolverOptions;
  303. FStringComparison: TCSSResStringComparison;
  304. FStyleSheets: TStyleSheets;
  305. FStyleSheetCount: integer;
  306. function GetCustomAttributes(Index: TCSSNumericalID): TCSSAttributeDesc;
  307. function GetLogCount: integer;
  308. function GetLogEntries(Index: integer): TCSSResolverLogEntry;
  309. function GetStyleSheets(Index: integer): TStyleSheet;
  310. procedure SetOptions(const AValue: TCSSResolverOptions);
  311. protected
  312. type
  313. { TMergedAttribute }
  314. TMergedAttribute = record
  315. Stamp: Integer; // only valid if equal to FMergedAttributesStamp
  316. Specificity: TCSSSpecificity;
  317. DeclEl: TCSSDeclarationElement; // can be nil if set by a shorthand
  318. Value: TCSSString;
  319. Complete: boolean;
  320. Prev, Next: TCSSNumericalID; // valid if >0, see below FMergedAttributeFirst
  321. end;
  322. PMergedAttribute = ^TMergedAttribute;
  323. TMergedAttributeArray = array of TMergedAttribute;
  324. protected
  325. FCustomAttributes: TCSSResCustomAttributeDescArray;
  326. FCustomAttributeCount: TCSSNumericalID;
  327. FCustomAttributeNameToDesc: TFPHashList;
  328. FElRules: TCSSSharedRuleArray;
  329. FElRuleCount: integer;
  330. FNode: ICSSNode;
  331. FLogEntries: TFPObjectList; // list of TCSSResolverLogEntry
  332. FSharedRuleLists: TAVLTree; // tree of TCSSSharedRuleList sorted for rules
  333. FMergedAttributes: TMergedAttributeArray;
  334. FMergedAttributesStamp: integer;
  335. FMergedAttributeFirst, FMergedAttributeLast: TCSSNumericalID; // first, last index in FMergedAttributes of linked list of attributes with current stamp
  336. FMergedAllDecl: TCSSDeclarationElement;
  337. FMergedAllSpecificity: TCSSSpecificity;
  338. FSourceSpecificity: TCSSSpecificity;
  339. FCSSRegistryStamp: TCSSNumericalID;
  340. // parse stylesheets
  341. procedure ParseSource(Index: integer); virtual;
  342. function ParseCSSSource(const Src: TCSSString; Inline: boolean): TCSSElement; virtual;
  343. procedure ClearElements; virtual;
  344. procedure ClearCustomAttributes; virtual;
  345. // resolving rules
  346. procedure ComputeElement(El: TCSSElement); virtual;
  347. procedure ComputeRule(aRule: TCSSRuleElement); virtual;
  348. function SelectorMatches(aSelector: TCSSElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  349. function SelectorIdentifierMatches(Identifier: TCSSResolvedIdentifierElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  350. function SelectorHashIdentifierMatches(Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  351. function SelectorClassNameMatches(aClassName: TCSSClassNameElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  352. function SelectorPseudoClassMatches(aPseudoClass: TCSSResolvedPseudoClassElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  353. function SelectorListMatches(aList: TCSSListElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  354. function SelectorUnaryMatches(aUnary: TCSSUnaryElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  355. function SelectorBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  356. function SelectorPseudoElementMatches(aLeft, aRight: TCSSElement; const TestNode: ICSSNode): TCSSSpecificity; virtual;
  357. function SelectorArrayMatches(anArray: TCSSArrayElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  358. function SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement; const TestNode: ICSSNode): TCSSSpecificity; virtual;
  359. function SelectorCallMatches(aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  360. function Call_Not(aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  361. function Call_Is(aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  362. function Call_Where(aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  363. function Call_NthChild(PseudoFuncID: TCSSNumericalID; aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity; virtual;
  364. function CollectSiblingsOf(PseudoFuncID: TCSSNumericalID; TestNode: ICSSNode;
  365. Params: TCSSResolverNthChildParams): TIntegerDynArray; virtual;
  366. function GetSiblingOfIndex(SiblingIDs: TIntegerDynArray; Index: integer): integer; virtual;
  367. function ComputeValue(El: TCSSElement): TCSSString; virtual;
  368. function SameValueText(const A, B: TCSSString): boolean; virtual;
  369. function SameValueText(A: PCSSChar; ALen: integer; B: PCSSChar; BLen: integer): boolean; virtual;
  370. function PosSubString(const SearchStr, Str: TCSSString): integer; virtual;
  371. function PosWord(const SearchWord, Words: TCSSString): integer; virtual;
  372. function GetSiblingCount(aNode: ICSSNode): integer; virtual;
  373. // resolving identifiers
  374. function ResolveIdentifier(El: TCSSResolvedIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
  375. // shared rules
  376. procedure ClearSharedRuleLists; virtual;
  377. procedure FindMatchingRules; virtual; // create FElRules for current FNode
  378. procedure AddRule(aRule: TCSSRuleElement; Specificity: TCSSSpecificity); // add rule to current array (FElRules)
  379. function FindSharedRuleList(const Rules: TCSSSharedRuleArray): TCSSSharedRuleList; virtual;
  380. function CreateSharedRuleList: TCSSSharedRuleList; virtual; // using FElRules, sets FMergedAttributes
  381. // merge properties
  382. procedure ClearMerge; virtual;
  383. procedure InitMerge; virtual;
  384. procedure SetMergedAttribute(AttrID, aSpecificity: TCSSNumericalID; DeclEl: TCSSDeclarationElement);
  385. procedure RemoveMergedAttribute(AttrID: TCSSNumericalID);
  386. procedure MergeAttribute(El: TCSSElement; aSpecificity: TCSSSpecificity); virtual;
  387. procedure SaveSharedMergedAttributes(SharedMerged: TCSSSharedRuleList); virtual;
  388. procedure LoadSharedMergedAttributes(SharedMerged: TCSSSharedRuleList); virtual;
  389. procedure WriteMergedAttributes(const Title: TCSSString); virtual;
  390. // var() and shorthands
  391. procedure LoadMergedValues; virtual; // load Value strings from css elements and remove longhand placeholders
  392. procedure SubstituteVarCalls; virtual; // replace all var()
  393. procedure ApplyShorthands; virtual; // replace all shorthands with their longhands
  394. function CreateValueList: TCSSAttributeValues; virtual; // from FMergedAttributes
  395. public
  396. constructor Create(AOwner: TComponent); override;
  397. destructor Destroy; override;
  398. procedure Clear; virtual;
  399. procedure Init; virtual; // call after adding stylesheets and before computing all nodes
  400. function GetElPath(El: TCSSElement): TCSSString; virtual;
  401. function GetElPos(El: TCSSElement): TCSSString; virtual;
  402. function ParseInlineStyle(const Src: TCSSString): TCSSRuleElement; virtual; // must be freed by caller
  403. procedure Compute(Node: ICSSNode;
  404. InlineStyle: TCSSRuleElement; // inline style of Node
  405. out Rules: TCSSSharedRuleList {owned by resolver};
  406. out Values: TCSSAttributeValues
  407. ); virtual;
  408. // attributes
  409. property CustomAttributes[Index: TCSSNumericalID]: TCSSAttributeDesc read GetCustomAttributes;
  410. property CustomAttributeCount: TCSSNumericalID read FCustomAttributeCount;
  411. function GetAttributeID(const aName: TCSSString; AutoCreate: boolean = false): TCSSNumericalID; override;
  412. function GetAttributeDesc(AttrId: TCSSNumericalID): TCSSAttributeDesc; override;
  413. function GetDeclarationValue(Decl: TCSSDeclarationElement): TCSSString; virtual;
  414. public
  415. property Options: TCSSResolverOptions read FOptions write SetOptions;
  416. property StringComparison: TCSSResStringComparison read FStringComparison;
  417. public
  418. // stylesheets
  419. procedure ClearStyleSheets; virtual;
  420. function AddStyleSheet(anOrigin: TCSSOrigin; const aName: TCSSString; const aSource: TCSSString): TStyleSheet; virtual;
  421. procedure ReplaceStyleSheet(Index: integer; const NewSource: TCSSString); virtual;
  422. function IndexOfStyleSheetWithElement(El: TCSSElement): integer;
  423. function IndexOfStyleSheetWithName(anOrigin: TCSSOrigin; const aName: TCSSString): integer;
  424. function FindStyleSheetWithElement(El: TCSSElement): TStyleSheet;
  425. property StyleSheetCount: integer read FStyleSheetCount;
  426. property StyleSheets[Index: integer]: TStyleSheet read GetStyleSheets;
  427. property Layers: TLayerArray read FLayers;
  428. public
  429. // logging
  430. procedure Log(MsgType: TEventType; const ID: TCSSMsgID; const Msg: TCSSString; PosEl: TCSSElement); virtual;
  431. procedure LogWarning(IsError: boolean; const ID: TCSSMsgID; const Msg: TCSSString; PosEl: TCSSElement); virtual;
  432. property LogCount: integer read GetLogCount;
  433. property LogEntries[Index: integer]: TCSSResolverLogEntry read GetLogEntries;
  434. property OnLog: TCSSResolverLogEvent read FOnLog write FOnLog;
  435. end;
  436. function ComparePointer(Data1, Data2: Pointer): integer;
  437. function CompareCSSSharedRuleArrays(const Rules1, Rules2: TCSSSharedRuleArray): integer;
  438. function CompareCSSSharedRuleLists(A, B: Pointer): integer;
  439. function CompareRulesArrayWithCSSSharedRuleList(RuleArray, SharedRuleList: Pointer): integer;
  440. implementation
  441. function ComparePointer(Data1, Data2: Pointer): integer;
  442. begin
  443. if Data1>Data2 then Result:=-1
  444. else if Data1<Data2 then Result:=1
  445. else Result:=0;
  446. end;
  447. function CompareCSSSharedRuleArrays(const Rules1, Rules2: TCSSSharedRuleArray): integer;
  448. var
  449. Len1, Len2, i: Integer;
  450. R1, R2: PCSSSharedRule;
  451. begin
  452. Len1:=length(Rules1);
  453. Len2:=length(Rules2);
  454. if Len1>Len2 then exit(1)
  455. else if Len1<Len2 then exit(-1);
  456. if Len1=0 then exit(0);
  457. R1:=@Rules1[0];
  458. R2:=@Rules2[0];
  459. for i:=0 to Len1-1 do
  460. begin
  461. if R1^.Specificity>R2^.Specificity then exit(1)
  462. else if R1^.Specificity<R2^.Specificity then exit(-1);
  463. Result:=ComparePointer(R1^.Rule,R2^.Rule);
  464. if Result<>0 then exit;
  465. inc(R1);
  466. inc(R2);
  467. end;
  468. Result:=0;
  469. end;
  470. function CompareCSSSharedRuleLists(A, B: Pointer): integer;
  471. var
  472. List1: TCSSSharedRuleList absolute A;
  473. List2: TCSSSharedRuleList absolute B;
  474. begin
  475. Result:=CompareCSSSharedRuleArrays(List1.Rules,List2.Rules);
  476. end;
  477. function CompareRulesArrayWithCSSSharedRuleList(RuleArray,
  478. SharedRuleList: Pointer): integer;
  479. var
  480. Arr: TCSSSharedRuleArray absolute RuleArray;
  481. List: TCSSSharedRuleList absolute SharedRuleList;
  482. begin
  483. Result:=CompareCSSSharedRuleArrays(Arr,List.Rules);
  484. end;
  485. { TCSSResolverNthChildParams }
  486. destructor TCSSResolverNthChildParams.Destroy;
  487. var
  488. i: Integer;
  489. begin
  490. for i:=0 to high(StackCache) do
  491. StackCache[i].Free;
  492. inherited Destroy;
  493. end;
  494. { TCSSSharedRuleList }
  495. destructor TCSSSharedRuleList.Destroy;
  496. begin
  497. Clear;
  498. inherited Destroy;
  499. end;
  500. procedure TCSSSharedRuleList.Clear;
  501. begin
  502. Rules:=nil;
  503. end;
  504. function TCSSSharedRuleList.Clone: TCSSSharedRuleList;
  505. var
  506. l: SizeInt;
  507. begin
  508. Result:=TCSSSharedRuleList.Create;
  509. Result.AllDecl:=AllDecl;
  510. Result.AllSpecificity:=AllSpecificity;
  511. l:=length(Rules);
  512. if l>0 then
  513. begin
  514. SetLength(Result.Rules,l);
  515. System.Move(Rules[0],Result.Rules[0],SizeOf(TCSSSharedRule)*l);
  516. end;
  517. l:=length(Values);
  518. if l>0 then
  519. begin
  520. SetLength(Result.Values,l);
  521. System.Move(Values[0],Result.Values[0],SizeOf(TCSSResolvedAttribute)*l);
  522. end;
  523. end;
  524. function TCSSSharedRuleList.IndexOfAttr(AttrId: TCSSNumericalID;
  525. ForInsert: boolean): integer;
  526. var
  527. Cnt, l, r: Integer;
  528. CurAttrID: TCSSNumericalID;
  529. begin
  530. Cnt:=length(Values);
  531. l:=0;
  532. r:=Cnt-1;
  533. while r>=l do
  534. begin
  535. Result:=(l+r) shr 1;
  536. CurAttrID:=Values[Result].AttrID;
  537. if CurAttrID>AttrId then
  538. l:=Result+1
  539. else if CurAttrID<AttrId then
  540. r:=Result-1
  541. else
  542. exit;
  543. end;
  544. if ForInsert then
  545. Result:=l
  546. else
  547. Result:=-1;
  548. end;
  549. { TCSSAttributeValues }
  550. procedure TCSSAttributeValues.SortValues;
  551. procedure QuickSort(L, R : integer);
  552. var
  553. I, J, PivotIdx : integer;
  554. AttrP: TCSSNumericalID;
  555. V: TCSSAttributeValue;
  556. begin
  557. repeat
  558. I := L;
  559. J := R;
  560. PivotIdx := L + ((R - L) shr 1); { same as ((L + R) div 2), but without the possibility of overflow }
  561. AttrP := Values[PivotIdx].AttrID;
  562. repeat
  563. while (I < PivotIdx) and (AttrP > Values[i].AttrID) do
  564. Inc(I);
  565. while (J > PivotIdx) and (AttrP < Values[J].AttrID) do
  566. Dec(J);
  567. if I < J then
  568. begin
  569. V := Values[I];
  570. Values[I] := Values[J];
  571. Values[J] := V;
  572. if PivotIdx = I then
  573. begin
  574. PivotIdx := J;
  575. Inc(I);
  576. end
  577. else if PivotIdx = J then
  578. begin
  579. PivotIdx := I;
  580. Dec(J);
  581. end
  582. else
  583. begin
  584. Inc(I);
  585. Dec(J);
  586. end;
  587. end;
  588. until I >= J;
  589. // sort the smaller range recursively
  590. // sort the bigger range via the loop
  591. // Reasons: memory usage is O(log(n)) instead of O(n) and loop is faster than recursion
  592. if (PivotIdx - L) < (R - PivotIdx) then
  593. begin
  594. if (L + 1) < PivotIdx then
  595. QuickSort(L, PivotIdx - 1);
  596. L := PivotIdx + 1;
  597. end
  598. else
  599. begin
  600. if (PivotIdx + 1) < R then
  601. QuickSort(PivotIdx + 1, R);
  602. if (L + 1) < PivotIdx then
  603. R := PivotIdx - 1
  604. else
  605. exit;
  606. end;
  607. until L >= R;
  608. end;
  609. var
  610. l: SizeInt;
  611. i, j: Integer;
  612. aValue: TCSSAttributeValue;
  613. begin
  614. l:=length(Values);
  615. if l<6 then
  616. begin
  617. for i:=0 to l-2 do
  618. for j:=i+1 to l-1 do
  619. if Values[i].AttrID>Values[j].AttrID then
  620. begin
  621. aValue:=Values[i];
  622. Values[i]:=Values[j];
  623. Values[j]:=aValue;
  624. end;
  625. end else begin
  626. //for i:=0 to l-1 do
  627. // writeln('TCSSAttributeValues.SortValues ',i,' ',Values[i]<>nil);
  628. QuickSort(0,l-1);
  629. for i:=0 to l-2 do
  630. if Values[i].AttrID>=Values[i+1].AttrID then
  631. raise Exception.Create('20240816160749');
  632. end;
  633. end;
  634. function TCSSAttributeValues.IndexOf(AttrID: TCSSNumericalID): integer;
  635. var
  636. l, r, m: Integer;
  637. Diff: TCSSNumericalID;
  638. begin
  639. l:=0;
  640. r:=length(Values)-1;
  641. while l<=r do
  642. begin
  643. m:=(l+r) shr 1;
  644. Diff:=Values[m].AttrID-AttrID;
  645. if Diff>0 then
  646. r:=m-1
  647. else if Diff<0 then
  648. l:=m+1
  649. else
  650. exit(m);
  651. end;
  652. Result:=-1;
  653. end;
  654. procedure TCSSAttributeValues.SetComputedValue(AttrID: TCSSNumericalID; const aValue: TCSSString);
  655. procedure AddNew;
  656. var
  657. Item: TCSSAttributeValue;
  658. i, l: integer;
  659. begin
  660. l:=length(Values);
  661. i:=l;
  662. while (i>0) and (Values[i-1].AttrID>AttrID) do dec(i);
  663. Item:=TCSSAttributeValue.Create;
  664. Item.AttrID:=AttrID;
  665. Item.State:=cavsComputed;
  666. Item.Value:=aValue;
  667. System.Insert(Item,Values,i);
  668. end;
  669. var
  670. i: Integer;
  671. begin
  672. if AttrID<=CSSAttributeID_All then
  673. raise Exception.Create('20240729084610');
  674. if Values=nil then
  675. begin
  676. AddNew;
  677. end else begin
  678. i:=IndexOf(AttrID);
  679. if i>=0 then
  680. begin
  681. Values[i].State:=cavsComputed;
  682. Values[i].Value:=aValue;
  683. end else begin
  684. AddNew;
  685. end;
  686. end;
  687. end;
  688. destructor TCSSAttributeValues.Destroy;
  689. var
  690. i: Integer;
  691. begin
  692. for i:=0 to length(Values)-1 do
  693. Values[i].Free;
  694. Values:=nil;
  695. inherited Destroy;
  696. end;
  697. { TCSSResolver }
  698. function TCSSResolver.GetLogCount: integer;
  699. begin
  700. Result:=FLogEntries.Count;
  701. end;
  702. function TCSSResolver.GetCustomAttributes(Index: TCSSNumericalID): TCSSAttributeDesc;
  703. begin
  704. Result:=FCustomAttributes[Index];
  705. end;
  706. function TCSSResolver.GetLogEntries(Index: integer): TCSSResolverLogEntry;
  707. begin
  708. Result:=TCSSResolverLogEntry(FLogEntries[Index]);
  709. end;
  710. function TCSSResolver.GetStyleSheets(Index: integer): TStyleSheet;
  711. begin
  712. Result:=FStyleSheets[Index];
  713. end;
  714. procedure TCSSResolver.SetOptions(const AValue: TCSSResolverOptions);
  715. begin
  716. if FOptions=AValue then Exit;
  717. FOptions:=AValue;
  718. end;
  719. procedure TCSSResolver.ParseSource(Index: integer);
  720. procedure AddOrigin(LayerIndex: integer; Origin: TCSSOrigin);
  721. // inserts a anonymous layer
  722. var
  723. aLayer: TLayer;
  724. begin
  725. aLayer:=Default(TLayer);
  726. aLayer.Origin:=Origin;
  727. System.Insert(aLayer,FLayers,LayerIndex);
  728. end;
  729. var
  730. Src: TCSSString;
  731. El: TCSSElement;
  732. LayerIndex: Integer;
  733. Cnt: SizeInt;
  734. aStyleSheet: TStyleSheet;
  735. begin
  736. aStyleSheet:=FStyleSheets[Index];
  737. if aStyleSheet.Parsed then exit;
  738. aStyleSheet.Parsed:=true;
  739. if aStyleSheet.Element<>nil then
  740. raise ECSSResolver.Create('20240624200924');
  741. // parse
  742. Src:=aStyleSheet.Source;
  743. if Src='' then
  744. exit;
  745. //writeln('TCSSResolver.ParseSource [',Src,'] ',StringCodePage(Src));
  746. El:=ParseCSSSource(Src,false);
  747. if El=nil then exit;
  748. aStyleSheet.Element:=El;
  749. // find last layer with this Origin or lower
  750. LayerIndex:=length(FLayers);
  751. while (LayerIndex>0) and (FLayers[LayerIndex-1].Origin>aStyleSheet.Origin) do
  752. dec(LayerIndex);
  753. if (LayerIndex=length(FLayers)) or (FLayers[LayerIndex].Origin<>aStyleSheet.Origin) then
  754. AddOrigin(LayerIndex,aStyleSheet.Origin);
  755. with FLayers[LayerIndex] do
  756. begin
  757. Cnt:=length(Elements);
  758. if Cnt=ElementCount then
  759. begin
  760. if Cnt<8 then
  761. Cnt:=8
  762. else
  763. Cnt:=Cnt*2;
  764. SetLength(Elements,Cnt);
  765. FillByte(Elements[ElementCount],SizeOf(TLayerElement)*(Cnt-ElementCount),0);
  766. end;
  767. Elements[ElementCount].Src:=aStyleSheet;
  768. Elements[ElementCount].Element:=El;
  769. inc(ElementCount);
  770. end;
  771. end;
  772. function TCSSResolver.ParseCSSSource(const Src: TCSSString; Inline: boolean
  773. ): TCSSElement;
  774. var
  775. ms: TMemoryStream;
  776. aParser: TCSSResolverParser;
  777. begin
  778. Result:=nil;
  779. if Src='' then
  780. exit;
  781. if CSSRegistry=nil then
  782. raise ECSSResolver.Create('20240630203634');
  783. if (FCSSRegistryStamp>0) then
  784. begin
  785. if (FCSSRegistryStamp<>CSSRegistry.Stamp) then
  786. raise ECSSResolver.Create('20240822143309 Clear was not called after changing CSSRegistry');
  787. end else
  788. FCSSRegistryStamp:=CSSRegistry.Stamp;
  789. aParser:=nil;
  790. ms:=TMemoryStream.Create;
  791. try
  792. ms.Write(Src[1],length(Src)*SizeOf(TCSSChar));
  793. ms.Position:=0;
  794. aParser:=TCSSResolverParser.Create(ms); // ss is freed by the parser
  795. aParser.Resolver:=Self;
  796. aParser.OnLog:=@Log;
  797. aParser.CSSNthChildParamsClass:=TCSSResolverNthChildParams;
  798. if Inline then
  799. Result:=aParser.ParseInline
  800. else
  801. Result:=aParser.Parse;
  802. finally
  803. aParser.Free;
  804. ms.Free;
  805. end;
  806. end;
  807. procedure TCSSResolver.ClearElements;
  808. var
  809. i: Integer;
  810. begin
  811. FLogEntries.Clear;
  812. ClearMerge;
  813. ClearSharedRuleLists;
  814. ClearCustomAttributes;
  815. // clear layers
  816. for i:=0 to length(FLayers)-1 do
  817. begin
  818. FLayers[i].ElementCount:=0;
  819. FLayers[i].Elements:=nil;
  820. FLayers[i].Name:='';
  821. end;
  822. FLayers:=nil;
  823. for i:=0 to FStyleSheetCount-1 do
  824. FreeAndNil(FStyleSheets[i].Element);
  825. // not referencing CSSRegistry anymore
  826. FCSSRegistryStamp:=0;
  827. end;
  828. procedure TCSSResolver.ClearCustomAttributes;
  829. var
  830. i: Integer;
  831. begin
  832. for i:=0 to FCustomAttributeCount-1 do
  833. FreeAndNil(FCustomAttributes[i]);
  834. FCustomAttributeCount:=0;
  835. FCustomAttributeNameToDesc.Clear;
  836. end;
  837. procedure TCSSResolver.AddRule(aRule: TCSSRuleElement; Specificity: TCSSSpecificity
  838. );
  839. var
  840. l: SizeInt;
  841. i: Integer;
  842. begin
  843. if aRule=nil then
  844. raise ECSSResolver.Create('20231110202417');
  845. l:=length(FElRules);
  846. if FElRuleCount=l then
  847. begin
  848. if l<8 then
  849. l:=8
  850. else
  851. l:=l*2;
  852. Setlength(FElRules,l);
  853. end;
  854. i:=FElRuleCount;
  855. FElRules[i].Rule:=aRule;
  856. FElRules[i].Specificity:=Specificity;
  857. inc(FElRuleCount);
  858. end;
  859. procedure TCSSResolver.ComputeElement(El: TCSSElement);
  860. var
  861. C: TClass;
  862. Compound: TCSSCompoundElement;
  863. i: Integer;
  864. begin
  865. if El=nil then exit;
  866. C:=El.ClassType;
  867. {$IFDEF VerboseCSSResolver}
  868. //writeln('TCSSResolver.ComputeElement ',GetCSSPath(El));
  869. {$ENDIF}
  870. if C=TCSSCompoundElement then
  871. begin
  872. Compound:=TCSSCompoundElement(El);
  873. //writeln('TCSSResolver.ComputeElement Compound.ChildCount=',Compound.ChildCount);
  874. for i:=0 to Compound.ChildCount-1 do
  875. ComputeElement(Compound.Children[i]);
  876. end else if C=TCSSRuleElement then
  877. ComputeRule(TCSSRuleElement(El))
  878. else
  879. Log(etWarning,20220908150252,'TCSSResolver.ComputeElement: Unknown CSS element',El);
  880. end;
  881. procedure TCSSResolver.ComputeRule(aRule: TCSSRuleElement);
  882. var
  883. i: Integer;
  884. BestSpecificity, Specificity: TCSSSpecificity;
  885. aSelector: TCSSElement;
  886. begin
  887. BestSpecificity:=CSSSpecificityNoMatch;
  888. for i:=0 to aRule.SelectorCount-1 do
  889. begin
  890. aSelector:=aRule.Selectors[i];
  891. Specificity:=SelectorMatches(aSelector,FNode,false);
  892. if Specificity>BestSpecificity then
  893. BestSpecificity:=Specificity;
  894. end;
  895. if BestSpecificity>=0 then
  896. begin
  897. // match -> add rule to ruleset
  898. AddRule(aRule,BestSpecificity);
  899. end;
  900. end;
  901. function TCSSResolver.FindSharedRuleList(const Rules: TCSSSharedRuleArray
  902. ): TCSSSharedRuleList;
  903. var
  904. Node: TAVLTreeNode;
  905. begin
  906. Node:=FSharedRuleLists.FindKey(Pointer(Rules),@CompareRulesArrayWithCSSSharedRuleList);
  907. if Node<>nil then
  908. Result:=TCSSSharedRuleList(Node.Data)
  909. else
  910. Result:=nil;
  911. end;
  912. function TCSSResolver.CreateSharedRuleList: TCSSSharedRuleList;
  913. var
  914. i, j: Integer;
  915. RuleArr: TCSSSharedRule;
  916. Rule: TCSSRuleElement;
  917. Specificity: TCSSSpecificity;
  918. RuleI, RuleJ: PCSSSharedRule;
  919. begin
  920. SetLength(FElRules,FElRuleCount); // needed by FindSharedRuleList
  921. // sort ascending for Specificity
  922. for i:=0 to FElRuleCount-2 do
  923. begin
  924. RuleI:=@FElRules[i];
  925. for j:=i+1 to FElRuleCount-1 do
  926. begin
  927. RuleJ:=@FElRules[j];
  928. if RuleI^.Specificity>RuleJ^.Specificity then
  929. begin
  930. Specificity:=RuleI^.Specificity;
  931. RuleI^.Specificity:=RuleJ^.Specificity;
  932. RuleJ^.Specificity:=Specificity;
  933. Rule:=RuleI^.Rule;
  934. RuleI^.Rule:=RuleJ^.Rule;
  935. RuleJ^.Rule:=Rule;
  936. end;
  937. end;
  938. end;
  939. Result:=FindSharedRuleList(FElRules);
  940. if Result<>nil then
  941. begin
  942. // already exists
  943. LoadSharedMergedAttributes(Result);
  944. end else begin
  945. // add new shared rule list
  946. Result:=TCSSSharedRuleList.Create;
  947. Result.Rules:=copy(FElRules,0,FElRuleCount);
  948. FSharedRuleLists.Add(Result);
  949. // merge shared properties
  950. ClearMerge;
  951. for i:=0 to length(Result.Rules)-1 do
  952. begin
  953. RuleArr:=Result.Rules[i];
  954. Rule:=RuleArr.Rule;
  955. Specificity:=RuleArr.Specificity;
  956. for j:=0 to Rule.ChildCount-1 do
  957. MergeAttribute(Rule.Children[j],Specificity);
  958. end;
  959. SaveSharedMergedAttributes(Result);
  960. end;
  961. end;
  962. procedure TCSSResolver.ClearMerge;
  963. var
  964. i: Integer;
  965. begin
  966. if FMergedAttributesStamp=high(FMergedAttributesStamp) then
  967. begin
  968. FMergedAttributesStamp:=1;
  969. for i:=0 to length(FMergedAttributes)-1 do
  970. FMergedAttributes[i].Stamp:=0;
  971. end else
  972. inc(FMergedAttributesStamp);
  973. FMergedAllDecl:=nil;
  974. FMergedAllSpecificity:=CSSSpecificityInvalid;
  975. FMergedAttributeFirst:=0;
  976. FMergedAttributeLast:=0;
  977. end;
  978. procedure TCSSResolver.InitMerge;
  979. var
  980. OldLen, NewLen: TCSSNumericalID;
  981. begin
  982. if FCustomAttributeCount>0 then
  983. begin
  984. if FCustomAttributes[0].Index<>CSSRegistry.AttributeCount then
  985. raise ECSSResolver.Create('20240822142652');
  986. end;
  987. OldLen:=length(FMergedAttributes);
  988. NewLen:=CSSRegistry.AttributeCount+FCustomAttributeCount;
  989. if NewLen>OldLen then
  990. begin
  991. SetLength(FMergedAttributes,NewLen);
  992. FillByte(FMergedAttributes[OldLen],(NewLen-OldLen)*SizeOf(TMergedAttribute),0);
  993. end;
  994. end;
  995. procedure TCSSResolver.SetMergedAttribute(AttrID, aSpecificity: TCSSNumericalID;
  996. DeclEl: TCSSDeclarationElement);
  997. var
  998. AttrP: PMergedAttribute;
  999. begin
  1000. if AttrID<=0 then
  1001. raise ECSSResolver.Create('20240701120038');
  1002. if AttrID>=length(FMergedAttributes) then
  1003. raise ECSSResolver.Create('20240823095544');
  1004. AttrP:=@FMergedAttributes[AttrID];
  1005. AttrP^.Specificity:=aSpecificity;
  1006. AttrP^.DeclEl:=DeclEl;
  1007. if AttrP^.Stamp<>FMergedAttributesStamp then
  1008. begin
  1009. if FMergedAttributeFirst>0 then
  1010. begin
  1011. // append to double linked list
  1012. FMergedAttributes[FMergedAttributeLast].Next:=AttrID;
  1013. AttrP^.Prev:=FMergedAttributeLast;
  1014. FMergedAttributeLast:=AttrID;
  1015. end else begin
  1016. // start double linked list
  1017. FMergedAttributeFirst:=AttrID;
  1018. FMergedAttributeLast:=AttrID;
  1019. AttrP^.Prev:=0;
  1020. end;
  1021. AttrP^.Next:=0;
  1022. AttrP^.Stamp:=FMergedAttributesStamp;
  1023. end;
  1024. end;
  1025. procedure TCSSResolver.RemoveMergedAttribute(AttrID: TCSSNumericalID);
  1026. var
  1027. AttrP: PMergedAttribute;
  1028. begin
  1029. AttrP:=@FMergedAttributes[AttrID];
  1030. if AttrP^.Stamp<>FMergedAttributesStamp then exit;
  1031. AttrP^.Stamp:=0;
  1032. if FMergedAttributeFirst=AttrID then
  1033. FMergedAttributeFirst:=AttrP^.Next;
  1034. if FMergedAttributeLast=AttrID then
  1035. FMergedAttributeLast:=AttrP^.Prev;
  1036. if AttrP^.Next>0 then
  1037. FMergedAttributes[AttrP^.Next].Prev:=AttrP^.Prev;
  1038. if AttrP^.Prev>0 then
  1039. FMergedAttributes[AttrP^.Prev].Next:=AttrP^.Next;
  1040. AttrP^.Next:=0;
  1041. AttrP^.Prev:=0;
  1042. end;
  1043. function TCSSResolver.SelectorMatches(aSelector: TCSSElement;
  1044. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1045. var
  1046. C: TClass;
  1047. begin
  1048. Result:=CSSSpecificityInvalid;
  1049. //writeln('TCSSResolver.SelectorMatches ',aSelector.ClassName,' ',TestNode.GetCSSTypeName);
  1050. C:=aSelector.ClassType;
  1051. if C=TCSSResolvedIdentifierElement then
  1052. Result:=SelectorIdentifierMatches(TCSSResolvedIdentifierElement(aSelector),TestNode,OnlySpecificity)
  1053. else if C=TCSSHashIdentifierElement then
  1054. Result:=SelectorHashIdentifierMatches(TCSSHashIdentifierElement(aSelector),TestNode,OnlySpecificity)
  1055. else if C=TCSSClassNameElement then
  1056. Result:=SelectorClassNameMatches(TCSSClassNameElement(aSelector),TestNode,OnlySpecificity)
  1057. else if C=TCSSResolvedPseudoClassElement then
  1058. Result:=SelectorPseudoClassMatches(TCSSResolvedPseudoClassElement(aSelector),TestNode,OnlySpecificity)
  1059. else if C=TCSSUnaryElement then
  1060. Result:=SelectorUnaryMatches(TCSSUnaryElement(aSelector),TestNode,OnlySpecificity)
  1061. else if C=TCSSBinaryElement then
  1062. Result:=SelectorBinaryMatches(TCSSBinaryElement(aSelector),TestNode,OnlySpecificity)
  1063. else if C=TCSSArrayElement then
  1064. Result:=SelectorArrayMatches(TCSSArrayElement(aSelector),TestNode,OnlySpecificity)
  1065. else if C=TCSSListElement then
  1066. Result:=SelectorListMatches(TCSSListElement(aSelector),TestNode,OnlySpecificity)
  1067. else if C=TCSSResolvedCallElement then
  1068. Result:=SelectorCallMatches(TCSSResolvedCallElement(aSelector),TestNode,OnlySpecificity)
  1069. else begin
  1070. // already warned by parser
  1071. {$IFDEF VerboseCSSResolver}
  1072. Log(etWarning,20240625131810,'Unknown CSS selector element '+aSelector.ClassName,aSelector);
  1073. {$ENDIF}
  1074. end;
  1075. end;
  1076. function TCSSResolver.SelectorIdentifierMatches(
  1077. Identifier: TCSSResolvedIdentifierElement; const TestNode: ICSSNode;
  1078. OnlySpecificity: boolean): TCSSSpecificity;
  1079. var
  1080. TypeID: TCSSNumericalID;
  1081. begin
  1082. Result:=CSSSpecificityNoMatch;
  1083. TypeID:=Identifier.NumericalID;
  1084. {$IFDEF VerboseCSSResolver}
  1085. writeln('TCSSResolver.SelectorIdentifierMatches ',Identifier.Value,' TypeId=',TypeID,' Node=',TestNode.GetCSSTypeID);
  1086. {$ENDIF}
  1087. if TypeID=CSSTypeID_Universal then
  1088. // universal selector
  1089. Result:=CSSSpecificityUniversal+FSourceSpecificity
  1090. else if OnlySpecificity then
  1091. Result:=CSSSpecificityType+FSourceSpecificity
  1092. else if TypeID=CSSIDNone then
  1093. begin
  1094. // already warned by parser
  1095. {$IFDEF VerboseCSSResolver}
  1096. Log(etWarning,20240625153922,'Unknown type ',Identifier);
  1097. {$ENDIF}
  1098. Result:=CSSSpecificityInvalid;
  1099. end else if TypeID=TestNode.GetCSSTypeID then
  1100. Result:=CSSSpecificityType+FSourceSpecificity;
  1101. end;
  1102. function TCSSResolver.SelectorHashIdentifierMatches(
  1103. Identifier: TCSSHashIdentifierElement; const TestNode: ICSSNode;
  1104. OnlySpecificity: boolean): TCSSSpecificity;
  1105. var
  1106. aValue: TCSSString;
  1107. begin
  1108. if OnlySpecificity then
  1109. exit(CSSSpecificityIdentifier+FSourceSpecificity);
  1110. Result:=CSSSpecificityNoMatch;
  1111. aValue:=Identifier.Value;
  1112. if TestNode.GetCSSID=aValue then
  1113. Result:=CSSSpecificityIdentifier+FSourceSpecificity;
  1114. end;
  1115. function TCSSResolver.SelectorClassNameMatches(
  1116. aClassName: TCSSClassNameElement; const TestNode: ICSSNode;
  1117. OnlySpecificity: boolean): TCSSSpecificity;
  1118. var
  1119. aValue: TCSSString;
  1120. begin
  1121. if OnlySpecificity then
  1122. exit(CSSSpecificityClass+FSourceSpecificity);
  1123. aValue:=aClassName.Name;
  1124. if TestNode.HasCSSClass(aValue) then
  1125. Result:=CSSSpecificityClass+FSourceSpecificity
  1126. else
  1127. Result:=CSSSpecificityNoMatch;
  1128. //writeln('TCSSResolver.SelectorClassNameMatches ',aValue,' ',Result);
  1129. end;
  1130. function TCSSResolver.SelectorPseudoClassMatches(aPseudoClass: TCSSResolvedPseudoClassElement;
  1131. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1132. var
  1133. PseudoID: TCSSNumericalID;
  1134. begin
  1135. if OnlySpecificity then
  1136. exit(CSSSpecificityClass+FSourceSpecificity);
  1137. Result:=CSSSpecificityNoMatch;
  1138. PseudoID:=aPseudoClass.NumericalID;
  1139. case PseudoID of
  1140. CSSIDNone:
  1141. begin
  1142. // already warned by parser
  1143. {$IFDEF VerboseCSSResolver}
  1144. Log(etWarning,20240625153950,'Unknown pseudo class',aPseudoClass);
  1145. {$ENDIF}
  1146. end;
  1147. CSSPseudoID_Root:
  1148. if TestNode.GetCSSParent=nil then
  1149. Result:=CSSSpecificityClass+FSourceSpecificity;
  1150. CSSPseudoID_Empty:
  1151. if TestNode.GetCSSEmpty then
  1152. Result:=CSSSpecificityClass+FSourceSpecificity;
  1153. CSSPseudoID_FirstChild:
  1154. if TestNode.GetCSSPreviousSibling=nil then
  1155. Result:=CSSSpecificityClass+FSourceSpecificity;
  1156. CSSPseudoID_LastChild:
  1157. if TestNode.GetCSSNextSibling=nil then
  1158. Result:=CSSSpecificityClass+FSourceSpecificity;
  1159. CSSPseudoID_OnlyChild:
  1160. if (TestNode.GetCSSNextSibling=nil)
  1161. and (TestNode.GetCSSPreviousSibling=nil) then
  1162. Result:=CSSSpecificityClass+FSourceSpecificity;
  1163. CSSPseudoID_FirstOfType:
  1164. if TestNode.GetCSSPreviousOfType=nil then
  1165. Result:=CSSSpecificityClass+FSourceSpecificity;
  1166. CSSPseudoID_LastOfType:
  1167. if TestNode.GetCSSNextOfType=nil then
  1168. Result:=CSSSpecificityClass+FSourceSpecificity;
  1169. CSSPseudoID_OnlyOfType:
  1170. if (TestNode.GetCSSNextOfType=nil)
  1171. and (TestNode.GetCSSPreviousOfType=nil) then
  1172. Result:=CSSSpecificityClass+FSourceSpecificity;
  1173. else
  1174. if TestNode.HasCSSPseudoClass(PseudoID) then
  1175. Result:=CSSSpecificityClass+FSourceSpecificity;
  1176. end;
  1177. end;
  1178. function TCSSResolver.SelectorListMatches(aList: TCSSListElement;
  1179. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1180. var
  1181. i: Integer;
  1182. El: TCSSElement;
  1183. C: TClass;
  1184. Specificity: TCSSSpecificity;
  1185. aNode: ICSSNode;
  1186. begin
  1187. Result:=0;
  1188. {$IFDEF VerboseCSSResolver}
  1189. writeln('TCSSResolver.SelectorListMatches ChildCount=',aList.ChildCount);
  1190. {$ENDIF}
  1191. aNode:=TestNode;
  1192. for i:=0 to aList.ChildCount-1 do
  1193. begin
  1194. El:=aList.Children[i];
  1195. {$IFDEF VerboseCSSResolver}
  1196. writeln('TCSSResolver.SelectorListMatches ',i,' ',GetCSSObj(El),' AsString=',El.AsString);
  1197. {$ENDIF}
  1198. C:=El.ClassType;
  1199. if (C=TCSSResolvedIdentifierElement) and (i>0) then
  1200. begin
  1201. if OnlySpecificity then
  1202. exit(0);
  1203. // already warned by parser
  1204. {$IFDEF VerboseCSSResolver}
  1205. Log(etWarning,20240625154031,'Type selector must be first',aList);
  1206. {$ENDIF}
  1207. exit(CSSSpecificityInvalid);
  1208. end else
  1209. Specificity:=SelectorMatches(El,aNode,OnlySpecificity);
  1210. if Specificity<0 then
  1211. exit(Specificity);
  1212. inc(Result,Specificity);
  1213. end;
  1214. end;
  1215. function TCSSResolver.SelectorUnaryMatches(aUnary: TCSSUnaryElement; const TestNode: ICSSNode;
  1216. OnlySpecificity: boolean): TCSSSpecificity;
  1217. begin
  1218. Result:=CSSSpecificityInvalid;
  1219. case aUnary.Operation of
  1220. uoDoubleColon:
  1221. begin
  1222. // ::PseudoElement
  1223. if OnlySpecificity then
  1224. // treat as Type::PseudoElement
  1225. Result:=CSSSpecificityType+FSourceSpecificity
  1226. +CSSSpecificityType+FSourceSpecificity
  1227. else
  1228. Result:=SelectorPseudoElementMatches(nil,aUnary.Right,TestNode);
  1229. end;
  1230. else
  1231. // already warned by parser
  1232. {$IFDEF VerboseCSSResolver}
  1233. Log(etWarning,20250225103026,'Invalid CSS unary selector '+UnaryOperators[aUnary.Operation],aUnary);
  1234. {$ENDIF}
  1235. end;
  1236. end;
  1237. function TCSSResolver.SelectorBinaryMatches(aBinary: TCSSBinaryElement;
  1238. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1239. var
  1240. aParent, Sibling: ICSSNode;
  1241. aSpecificity: TCSSSpecificity;
  1242. begin
  1243. if OnlySpecificity then
  1244. begin
  1245. Result:=SelectorMatches(aBinary.Left,TestNode,true);
  1246. inc(Result,SelectorMatches(aBinary.Right,TestNode,true));
  1247. exit;
  1248. end;
  1249. Result:=CSSSpecificityInvalid;
  1250. case aBinary.Operation of
  1251. boGT:
  1252. begin
  1253. // child combinator >
  1254. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  1255. if Result<0 then exit;
  1256. aParent:=TestNode.GetCSSParent;
  1257. if aParent=nil then
  1258. exit(CSSSpecificityNoMatch);
  1259. aSpecificity:=SelectorMatches(aBinary.Left,aParent,false);
  1260. if aSpecificity<0 then
  1261. exit(aSpecificity);
  1262. inc(Result,aSpecificity);
  1263. end;
  1264. boPlus:
  1265. begin
  1266. // adjacent sibling combinator +
  1267. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  1268. if Result<0 then exit;
  1269. Sibling:=TestNode.GetCSSPreviousSibling;
  1270. if Sibling=nil then
  1271. exit(CSSSpecificityNoMatch);
  1272. aSpecificity:=SelectorMatches(aBinary.Left,Sibling,false);
  1273. if aSpecificity<0 then
  1274. exit(aSpecificity);
  1275. inc(Result,aSpecificity);
  1276. end;
  1277. boTilde:
  1278. begin
  1279. // general sibling combinator ~
  1280. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  1281. if Result<0 then exit;
  1282. Sibling:=TestNode.GetCSSPreviousSibling;
  1283. while Sibling<>nil do
  1284. begin
  1285. aSpecificity:=SelectorMatches(aBinary.Left,Sibling,false);
  1286. if aSpecificity=CSSSpecificityInvalid then
  1287. exit(aSpecificity)
  1288. else if aSpecificity>=0 then
  1289. begin
  1290. inc(Result,aSpecificity);
  1291. exit;
  1292. end;
  1293. Sibling:=Sibling.GetCSSPreviousSibling;
  1294. end;
  1295. Result:=CSSSpecificityNoMatch;
  1296. end;
  1297. boWhiteSpace:
  1298. begin
  1299. // descendant combinator
  1300. Result:=SelectorMatches(aBinary.Right,TestNode,false);
  1301. if Result<0 then exit;
  1302. aParent:=TestNode;
  1303. repeat
  1304. aParent:=aParent.GetCSSParent;
  1305. if aParent=nil then
  1306. exit(CSSSpecificityNoMatch);
  1307. aSpecificity:=SelectorMatches(aBinary.Left,aParent,false);
  1308. if aSpecificity>=0 then
  1309. begin
  1310. inc(Result,aSpecificity);
  1311. exit;
  1312. end
  1313. else if aSpecificity=CSSSpecificityInvalid then
  1314. exit(CSSSpecificityInvalid);
  1315. until false;
  1316. end;
  1317. boDoubleColon:
  1318. Result:=SelectorPseudoElementMatches(aBinary.Left,aBinary.Right,TestNode);
  1319. else
  1320. // already warned by parser
  1321. {$IFDEF VerboseCSSResolver}
  1322. Log(etWarning,20240625154050,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
  1323. {$ENDIF}
  1324. end;
  1325. end;
  1326. function TCSSResolver.SelectorPseudoElementMatches(aLeft, aRight: TCSSElement;
  1327. const TestNode: ICSSNode): TCSSSpecificity;
  1328. // pseudo element (function)
  1329. var
  1330. ID: TCSSNumericalID;
  1331. aParent: ICSSNode;
  1332. aSpecificity: TCSSSpecificity;
  1333. begin
  1334. Result:=CSSSpecificityInvalid;
  1335. if aRight is TCSSResolvedIdentifierElement then
  1336. begin
  1337. // pseudo element
  1338. ID:=TCSSResolvedIdentifierElement(aRight).NumericalID;
  1339. if ID<=0 then
  1340. begin
  1341. // already warned by parser
  1342. {$IFDEF VerboseCSSResolver}
  1343. Log(etWarning,20250224211914,'Invalid CSS pseudo element',aRight);
  1344. {$ENDIF}
  1345. exit;
  1346. end;
  1347. if ID<>TestNode.GetCSSPseudoElementID then
  1348. exit(CSSSpecificityNoMatch);
  1349. Result:=CSSSpecificityIdentifier;
  1350. end else if aRight is TCSSResolvedCallElement then begin
  1351. // pseudo element function
  1352. ID:=TCSSResolvedCallElement(aRight).NameNumericalID;
  1353. if ID<0 then
  1354. begin
  1355. // already warned by parser
  1356. {$IFDEF VerboseCSSResolver}
  1357. Log(etWarning,20250224212143,'Invalid CSS pseudo element function',aRight);
  1358. {$ENDIF}
  1359. exit;
  1360. end;
  1361. if ID<>TestNode.GetCSSPseudoElementID then
  1362. exit(CSSSpecificityNoMatch);
  1363. // todo: check parameters
  1364. Result:=CSSSpecificityIdentifier;
  1365. end else begin
  1366. // already warned by parser
  1367. {$IFDEF VerboseCSSResolver}
  1368. Log(etWarning,20250224212301,'Invalid CSS pseudo element',aRight);
  1369. {$ENDIF}
  1370. end;
  1371. if aLeft=nil then
  1372. exit; // unary ::Name
  1373. // test left side
  1374. aParent:=TestNode.GetCSSParent;
  1375. if aParent=nil then
  1376. exit(CSSSpecificityNoMatch);
  1377. aSpecificity:=SelectorMatches(aLeft,aParent,false);
  1378. if aSpecificity<0 then
  1379. exit(aSpecificity);
  1380. inc(Result,aSpecificity);
  1381. end;
  1382. function TCSSResolver.SelectorArrayMatches(anArray: TCSSArrayElement;
  1383. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1384. var
  1385. {$IFDEF VerboseCSSResolver}
  1386. i: integer;
  1387. {$ENDIF}
  1388. El: TCSSElement;
  1389. C: TClass;
  1390. AttrID: TCSSNumericalID;
  1391. OldStringComparison: TCSSResStringComparison;
  1392. aValue: TCSSString;
  1393. begin
  1394. if OnlySpecificity then
  1395. exit(CSSSpecificityClass+FSourceSpecificity);
  1396. Result:=CSSSpecificityInvalid;
  1397. if anArray.Prefix<>nil then
  1398. begin
  1399. // already warned by parser
  1400. {$IFDEF VerboseCSSResolver}
  1401. Log(etWarning,20220910154004,'Invalid CSS attribute selector prefix',anArray.Prefix);
  1402. {$ENDIF}
  1403. exit;
  1404. end;
  1405. {$IFDEF VerboseCSSResolver}
  1406. writeln('TCSSResolver.SelectorArrayMatches Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
  1407. for i:=0 to anArray.ChildCount-1 do
  1408. writeln('TCSSResolver.SelectorArrayMatches ',i,' ',GetCSSObj(anArray.Children[i]));
  1409. {$ENDIF}
  1410. if anArray.ChildCount<1 then
  1411. begin
  1412. // already warned by parser
  1413. {$IFDEF VerboseCSSResolver}
  1414. Log(etWarning,20220910154033,'Invalid CSS attribute selector',anArray);
  1415. {$ENDIF}
  1416. exit;
  1417. end;
  1418. OldStringComparison:=StringComparison;
  1419. try
  1420. if anArray.ChildCount>1 then
  1421. begin
  1422. El:=anArray.Children[1];
  1423. C:=El.ClassType;
  1424. if C=TCSSResolvedIdentifierElement then
  1425. begin
  1426. aValue:=TCSSResolvedIdentifierElement(El).Value;
  1427. case aValue of
  1428. 'i': FStringComparison:=crscCaseInsensitive;
  1429. 's': FStringComparison:=crscCaseSensitive;
  1430. else
  1431. // already warned by parser
  1432. {$IFDEF VerboseCSSResolver}
  1433. LogWarning(croErrorOnUnknownName in Options,20220914174409,'Invalid attribute modifier "'+aValue+'"',El);
  1434. {$ENDIF}
  1435. exit;
  1436. end;
  1437. end else begin
  1438. // already warned by parser
  1439. {$IFDEF VerboseCSSResolver}
  1440. Log(etWarning,20220914173643,'Invalid CSS attribute modifier',El);
  1441. {$ENDIF}
  1442. exit;
  1443. end;
  1444. end;
  1445. if (anArray.ChildCount>2) then
  1446. begin
  1447. // already warned by parser
  1448. {$IFDEF VerboseCSSResolver}
  1449. Log(etWarning,20220914174550,'Invalid CSS attribute modifier',anArray.Children[2]);
  1450. {$ENDIF}
  1451. end;
  1452. El:=anArray.Children[0];
  1453. C:=El.ClassType;
  1454. if C=TCSSResolvedIdentifierElement then
  1455. begin
  1456. // [name] -> has explicit attribute
  1457. AttrID:=TCSSResolvedIdentifierElement(El).NumericalID;
  1458. case AttrID of
  1459. CSSIDNone:
  1460. Result:=CSSSpecificityNoMatch;
  1461. CSSAttributeID_ID,
  1462. CSSAttributeID_Class:
  1463. // id and class are always defined
  1464. Result:=CSSSpecificityClass+FSourceSpecificity;
  1465. CSSAttributeID_All:
  1466. // special CSS attributes without a value
  1467. Result:=CSSSpecificityNoMatch;
  1468. else
  1469. if TestNode.HasCSSExplicitAttribute(AttrID) then
  1470. Result:=CSSSpecificityClass+FSourceSpecificity
  1471. else
  1472. Result:=CSSSpecificityNoMatch;
  1473. end;
  1474. end else if C=TCSSBinaryElement then
  1475. Result:=SelectorArrayBinaryMatches(TCSSBinaryElement(El),TestNode)
  1476. else begin
  1477. // already warned by parser
  1478. {$IFDEF VerboseCSSResolver}
  1479. LogWarning(croErrorOnUnknownName in Options,20220910153725,'Invalid CSS array selector',El);
  1480. {$ENDIF}
  1481. end;
  1482. finally
  1483. FStringComparison:=OldStringComparison;
  1484. end;
  1485. end;
  1486. function TCSSResolver.SelectorArrayBinaryMatches(aBinary: TCSSBinaryElement;
  1487. const TestNode: ICSSNode): TCSSSpecificity;
  1488. var
  1489. Left, Right: TCSSElement;
  1490. AttrID: TCSSNumericalID;
  1491. LeftValue, RightValue: TCSSString;
  1492. C: TClass;
  1493. begin
  1494. Result:=CSSSpecificityNoMatch;
  1495. Left:=aBinary.Left;
  1496. if Left.ClassType<>TCSSResolvedIdentifierElement then
  1497. begin
  1498. // already warned by parser
  1499. {$IFDEF VerboseCSSResolver}
  1500. Log(etWarning,20220910164353,'Invalid CSS array selector, expected attribute',Left);
  1501. {$ENDIF}
  1502. exit;
  1503. end;
  1504. AttrID:=TCSSResolvedIdentifierElement(Left).NumericalID;
  1505. {$IFDEF VerboseCSSResolver}
  1506. writeln('TCSSResolver.SelectorArrayBinaryMatches AttrID=',AttrID,' Value=',TCSSResolvedIdentifierElement(Left).Value);
  1507. {$ENDIF}
  1508. case AttrID of
  1509. CSSIDNone: exit(CSSSpecificityNoMatch);
  1510. CSSAttributeID_ID:
  1511. LeftValue:=TestNode.GetCSSID;
  1512. CSSAttributeID_Class:
  1513. LeftValue:=TestNode.GetCSSAttributeClass;
  1514. CSSAttributeID_All:
  1515. exit(CSSSpecificityNoMatch);
  1516. else
  1517. LeftValue:=TestNode.GetCSSExplicitAttribute(AttrID);
  1518. end;
  1519. Right:=aBinary.Right;
  1520. C:=Right.ClassType;
  1521. if (C=TCSSStringElement) or (C=TCSSIntegerElement) or (C=TCSSFloatElement)
  1522. or (C=TCSSResolvedIdentifierElement) then
  1523. // ok
  1524. else begin
  1525. // already warned by parser
  1526. {$IFDEF VerboseCSSResolver}
  1527. Log(etWarning,20220910164921,'Invalid CSS array selector, expected string',Right);
  1528. {$ENDIF}
  1529. exit;
  1530. end;
  1531. RightValue:=ComputeValue(Right);
  1532. {$IFDEF VerboseCSSResolver}
  1533. writeln('TCSSResolver.SelectorArrayBinaryMatches Left="',LeftValue,'" Right="',RightValue,'" Op=',aBinary.Operation);
  1534. {$ENDIF}
  1535. case aBinary.Operation of
  1536. boEquals:
  1537. if SameValueText(LeftValue,RightValue) then
  1538. Result:=CSSSpecificityClass+FSourceSpecificity;
  1539. boSquaredEqual:
  1540. // begins with
  1541. if (RightValue<>'') and SameValueText(LeftStr(LeftValue,length(RightValue)),RightValue) then
  1542. Result:=CSSSpecificityClass+FSourceSpecificity;
  1543. boDollarEqual:
  1544. // ends with
  1545. if (RightValue<>'') and SameValueText(RightStr(LeftValue,length(RightValue)),RightValue) then
  1546. Result:=CSSSpecificityClass+FSourceSpecificity;
  1547. boPipeEqual:
  1548. // equal to or starts with name-hyphen
  1549. if (RightValue<>'')
  1550. and (SameValueText(LeftValue,RightValue)
  1551. or SameValueText(LeftStr(LeftValue,length(RightValue)+1),RightValue+'-')) then
  1552. Result:=CSSSpecificityClass+FSourceSpecificity;
  1553. boStarEqual:
  1554. // contains substring
  1555. if (RightValue<>'') and (Pos(RightValue,LeftValue)>0) then
  1556. Result:=CSSSpecificityClass+FSourceSpecificity;
  1557. boTildeEqual:
  1558. // contains word
  1559. if PosWord(RightValue,LeftValue)>0 then
  1560. Result:=CSSSpecificityClass+FSourceSpecificity;
  1561. else
  1562. // already warned by parser
  1563. {$IFDEF VerboseCSSResolver}
  1564. Log(etWarning,20220910164356,'Invalid CSS array selector operator',aBinary);
  1565. {$ENDIF}
  1566. Result:=CSSSpecificityInvalid;
  1567. end;
  1568. {$IFDEF VerboseCSSResolver}
  1569. writeln('TCSSResolver.SelectorArrayBinaryMatches Result=',Result);
  1570. {$ENDIF}
  1571. end;
  1572. function TCSSResolver.SelectorCallMatches(aCall: TCSSResolvedCallElement;
  1573. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1574. var
  1575. CallID: TCSSNumericalID;
  1576. begin
  1577. Result:=CSSSpecificityNoMatch;
  1578. CallID:=aCall.NameNumericalID;
  1579. //writeln('TCSSResolver.SelectorCallMatches ',CallID,' ',aCall.AsString);
  1580. case CallID of
  1581. CSSCallID_Not:
  1582. Result:=Call_Not(aCall,TestNode,OnlySpecificity);
  1583. CSSCallID_Is:
  1584. Result:=Call_Is(aCall,TestNode,OnlySpecificity);
  1585. CSSCallID_Where:
  1586. Result:=Call_Where(aCall,TestNode,OnlySpecificity);
  1587. CSSCallID_NthChild,
  1588. CSSCallID_NthLastChild,
  1589. CSSCallID_NthOfType,
  1590. CSSCallID_NthLastOfType:
  1591. Result:=Call_NthChild(CallID,aCall,TestNode,OnlySpecificity);
  1592. else
  1593. if OnlySpecificity then
  1594. Result:=0
  1595. else
  1596. Result:=CSSSpecificityInvalid;
  1597. end;
  1598. end;
  1599. function TCSSResolver.Call_Not(aCall: TCSSResolvedCallElement;
  1600. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1601. // :not(arg1, arg2, ...)
  1602. // :not(args) has the same Specificity as :not(:is(args))
  1603. var
  1604. i: Integer;
  1605. Specificity: TCSSSpecificity;
  1606. HasMatch: Boolean;
  1607. begin
  1608. Result:=0;
  1609. HasMatch:=false;
  1610. for i:=0 to aCall.ArgCount-1 do
  1611. begin
  1612. Specificity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecificity);
  1613. //writeln('TCSSResolver.Call_Not ',i,' ',TestNode.GetCSSTypeName,' Spec=',Specificity);
  1614. if Specificity>=0 then
  1615. HasMatch:=true
  1616. else begin
  1617. // the Specificity of ":not" is the highest, independent of matching (forgiving)
  1618. if not OnlySpecificity then
  1619. Specificity:=SelectorMatches(aCall.Args[i],TestNode,true);
  1620. end;
  1621. if Specificity>Result then
  1622. Result:=Specificity;
  1623. end;
  1624. if OnlySpecificity then
  1625. // return best
  1626. else if HasMatch then
  1627. Result:=CSSSpecificityNoMatch;
  1628. end;
  1629. function TCSSResolver.Call_Is(aCall: TCSSResolvedCallElement; const TestNode: ICSSNode;
  1630. OnlySpecificity: boolean): TCSSSpecificity;
  1631. var
  1632. i: Integer;
  1633. Specificity: TCSSSpecificity;
  1634. ok: Boolean;
  1635. begin
  1636. Result:=0;
  1637. //writeln('TCSSResolver.Call_Is START ',TestNode.GetCSSID,' ArgCount=',aCall.ArgCount);
  1638. ok:=false;
  1639. for i:=0 to aCall.ArgCount-1 do
  1640. begin
  1641. Specificity:=SelectorMatches(aCall.Args[i],TestNode,OnlySpecificity);
  1642. //writeln('TCSSResolver.Call_Is i=',i,' ',TestNode.GetCSSID,' ',aCall.Args[i].AsString,' Spec=',Specificity);
  1643. if Specificity>=0 then
  1644. ok:=true
  1645. else begin
  1646. // the Specificity of :is is the highest, independent of matching (forgiving)
  1647. if not OnlySpecificity then
  1648. Specificity:=SelectorMatches(aCall.Args[i],TestNode,true);
  1649. end;
  1650. if Specificity>Result then
  1651. Result:=Specificity;
  1652. end;
  1653. if (not ok) and (not OnlySpecificity) then
  1654. Result:=CSSSpecificityNoMatch;
  1655. end;
  1656. function TCSSResolver.Call_Where(aCall: TCSSResolvedCallElement;
  1657. const TestNode: ICSSNode; OnlySpecificity: boolean): TCSSSpecificity;
  1658. var
  1659. i: Integer;
  1660. begin
  1661. Result:=0;
  1662. if OnlySpecificity then
  1663. exit;
  1664. for i:=0 to aCall.ArgCount-1 do
  1665. begin
  1666. if SelectorMatches(aCall.Args[i],TestNode,false)>=0 then
  1667. // Note: :where is forgiving, so invalid arguments are ignored
  1668. exit;
  1669. end;
  1670. Result:=CSSSpecificityNoMatch;
  1671. end;
  1672. function TCSSResolver.Call_NthChild(PseudoFuncID: TCSSNumericalID;
  1673. aCall: TCSSResolvedCallElement; const TestNode: ICSSNode; OnlySpecificity: boolean
  1674. ): TCSSSpecificity;
  1675. var
  1676. i: Integer;
  1677. Params: TCSSResolverNthChildParams;
  1678. ChildIDs: TIntegerDynArray;
  1679. begin
  1680. Params:=aCall.Params as TCSSResolverNthChildParams;
  1681. if Params=nil then
  1682. exit(CSSSpecificityInvalid);
  1683. if OnlySpecificity then
  1684. Result:=CSSSpecificityClass+FSourceSpecificity
  1685. else
  1686. Result:=CSSSpecificityInvalid;
  1687. if OnlySpecificity then
  1688. begin
  1689. if Params.OfSelector<>nil then
  1690. inc(Result,SelectorMatches(Params.OfSelector,TestNode,true));
  1691. exit;
  1692. end;
  1693. Result:=CSSSpecificityNoMatch;
  1694. if Params.Modulo=0 then
  1695. exit;
  1696. i:=TestNode.GetCSSIndex;
  1697. if Params.HasOf then
  1698. begin
  1699. ChildIDs:=CollectSiblingsOf(PseudoFuncID,TestNode,Params);
  1700. i:=GetSiblingOfIndex(ChildIDs,i);
  1701. end else
  1702. ChildIDs:=nil;
  1703. {$IFDEF VerboseCSSResolver}
  1704. writeln('TCSSResolver.Call_NthChild CallID=',PseudoFuncID,' Node=',TestNode.GetCSSID,' ',Params.Modulo,' * N + ',Params.Start,' Index=',TestNode.GetCSSIndex,' i=',i,' HasOf=',Params.HasOf,' OfSelector=',GetCSSObj(Params.OfSelector));
  1705. {$ENDIF}
  1706. if i<0 then
  1707. exit;
  1708. if PseudoFuncID in [CSSCallID_NthLastChild,CSSCallID_NthLastOfType] then
  1709. begin
  1710. if Params.HasOf then
  1711. i:=length(ChildIDs)-i
  1712. else
  1713. i:=GetSiblingCount(TestNode)-i;
  1714. end else
  1715. begin
  1716. i:=i+1;
  1717. end;
  1718. dec(i,Params.Start);
  1719. if i mod Params.Modulo = 0 then
  1720. begin
  1721. i:=i div Params.Modulo;
  1722. if i>=0 then
  1723. Result:=CSSSpecificityClass+FSourceSpecificity;
  1724. end;
  1725. {$IFDEF VerboseCSSResolver}
  1726. writeln('TCSSResolver.Call_NthChild Node=',TestNode.GetCSSID,' ',Params.Modulo,' * N + ',Params.Start,' Index=',TestNode.GetCSSIndex+1,' i=',i,' Result=',Result);
  1727. {$ENDIF}
  1728. end;
  1729. function TCSSResolver.CollectSiblingsOf(PseudoFuncID: TCSSNumericalID;
  1730. TestNode: ICSSNode; Params: TCSSResolverNthChildParams): TIntegerDynArray;
  1731. var
  1732. i, Depth, ChildCount, j: Integer;
  1733. aTypeID: TCSSNumericalID;
  1734. aParent, aNode: ICSSNode;
  1735. aSelector: TCSSElement;
  1736. StackDepth: SizeInt;
  1737. Cache: TCSSResolverNthChildParamsCache;
  1738. Item: PCSSNthChildParamsCacheItem;
  1739. NeedTypeID: Boolean;
  1740. begin
  1741. Result:=nil;
  1742. aParent:=TestNode.GetCSSParent;
  1743. {$IFDEF VerboseCSSResolver}
  1744. //writeln('TCSSResolver.CollectSiblingsOf HasParent=',aParent<>nil);
  1745. {$ENDIF}
  1746. if aParent=nil then exit;
  1747. ChildCount:=aParent.GetCSSChildCount;
  1748. if ChildCount=0 then exit;
  1749. Depth:=aParent.GetCSSDepth;
  1750. StackDepth:=length(Params.StackCache);
  1751. if StackDepth<=Depth then
  1752. begin
  1753. SetLength(Params.StackCache,Depth+1);
  1754. for i:=StackDepth to Depth do
  1755. Params.StackCache[i]:=nil;
  1756. end;
  1757. Cache:=Params.StackCache[Depth];
  1758. if Cache=nil then
  1759. begin
  1760. Cache:=TCSSResolverNthChildParamsCache.Create;
  1761. Params.StackCache[Depth]:=Cache;
  1762. Cache.Owner:=Params;
  1763. Cache.StackDepth:=Depth;
  1764. end;
  1765. NeedTypeID:=PseudoFuncID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType];
  1766. aSelector:=Params.OfSelector;
  1767. if (Cache.Parent<>aParent) or (Cache.OfSelector<>aSelector) then
  1768. begin
  1769. // build cache
  1770. Cache.Parent:=aParent;
  1771. Cache.OfSelector:=aSelector;
  1772. SetLength(Cache.Items,0);
  1773. {$IFDEF VerboseCSSResolver}
  1774. writeln('TCSSResolver.CollectSiblingsOf Depth=',Depth,' Candidates=',ChildCount);
  1775. {$ENDIF}
  1776. for i:=0 to ChildCount-1 do
  1777. begin
  1778. aNode:=aParent.GetCSSChild(i);
  1779. if (aSelector<>nil) and (SelectorMatches(aSelector,aNode,false)<0) then
  1780. continue;
  1781. // put
  1782. if NeedTypeID then
  1783. aTypeID:=aNode.GetCSSTypeID
  1784. else
  1785. aTypeID:=0;
  1786. j:=length(Cache.Items)-1;
  1787. while (j>=0) and (Cache.Items[j].TypeID<>aTypeID) do dec(j);
  1788. if j<0 then
  1789. begin
  1790. j:=length(Cache.Items);
  1791. SetLength(Cache.Items,j+1);
  1792. Item:[email protected][j];
  1793. Item^.TypeID:=aTypeID;
  1794. Item^.Cnt:=0;
  1795. SetLength(Item^.ChildIDs,ChildCount);
  1796. end else
  1797. Item:[email protected][j];
  1798. Item^.ChildIDs[Item^.Cnt]:=i;
  1799. {$IFDEF VerboseCSSResolver}
  1800. writeln('TCSSResolver.CollectSiblingsOf Sel=',GetCSSObj(aSelector),' CSSTypeID=',aNode.GetCSSTypeID,' ',Item^.Cnt,'=>',i);
  1801. {$ENDIF}
  1802. inc(Item^.Cnt);
  1803. end;
  1804. for i:=0 to high(Cache.Items) do
  1805. with Cache.Items[i] do
  1806. SetLength(ChildIDs,Cnt);
  1807. end;
  1808. // use cache
  1809. if NeedTypeID then
  1810. begin
  1811. aTypeID:=TestNode.GetCSSTypeID;
  1812. for i:=0 to high(Cache.Items) do
  1813. if Cache.Items[i].TypeID=aTypeID then
  1814. exit(Cache.Items[i].ChildIDs);
  1815. end else if length(Cache.Items)>0 then
  1816. Result:=Cache.Items[0].ChildIDs;
  1817. end;
  1818. function TCSSResolver.GetSiblingOfIndex(SiblingIDs: TIntegerDynArray;
  1819. Index: integer): integer;
  1820. // searches the position of Index in a sorted array
  1821. var
  1822. l, r, m: Integer;
  1823. begin
  1824. l:=0;
  1825. r:=length(SiblingIDs)-1;
  1826. while l<=r do
  1827. begin
  1828. m:=(l+r) div 2;
  1829. Result:=SiblingIDs[m];
  1830. if Index<Result then
  1831. r:=m-1
  1832. else if Index>Result then
  1833. l:=m+1
  1834. else
  1835. exit(m);
  1836. end;
  1837. Result:=-1;
  1838. end;
  1839. function TCSSResolver.ComputeValue(El: TCSSElement): TCSSString;
  1840. var
  1841. ElData: TObject;
  1842. C: TClass;
  1843. StrEl: TCSSStringElement;
  1844. IntEl: TCSSIntegerElement;
  1845. FloatEl: TCSSFloatElement;
  1846. begin
  1847. C:=El.ClassType;
  1848. if C=TCSSResolvedIdentifierElement then
  1849. Result:=TCSSResolvedIdentifierElement(El).Value
  1850. else if (C=TCSSStringElement)
  1851. or (C=TCSSIntegerElement)
  1852. or (C=TCSSFloatElement) then
  1853. begin
  1854. ElData:=El.CustomData;
  1855. if ElData is TCSSValueData then
  1856. exit(TCSSValueData(ElData).NormValue);
  1857. if C=TCSSStringElement then
  1858. begin
  1859. StrEl:=TCSSStringElement(El);
  1860. Result:=StrEl.Value;
  1861. {$IFDEF VerboseCSSResolver}
  1862. writeln('TCSSResolver.ComputeValue String=[',Result,']');
  1863. {$ENDIF}
  1864. end
  1865. else if C=TCSSIntegerElement then
  1866. begin
  1867. IntEl:=TCSSIntegerElement(El);
  1868. Result:=IntEl.AsString;
  1869. {$IFDEF VerboseCSSResolver}
  1870. writeln('TCSSResolver.ComputeValue Integer=[',Result,']');
  1871. {$ENDIF}
  1872. end else if C=TCSSFloatElement then
  1873. begin
  1874. FloatEl:=TCSSFloatElement(El);
  1875. Result:=FloatEl.AsString;
  1876. {$IFDEF VerboseCSSResolver}
  1877. writeln('TCSSResolver.ComputeValue Float=[',Result,']');
  1878. {$ENDIF}
  1879. end;
  1880. ElData:=TCSSValueData.Create;
  1881. TCSSValueData(ElData).NormValue:=Result;
  1882. El.CustomData:=ElData;
  1883. end else begin
  1884. // already warned by parser
  1885. {$IFDEF VerboseCSSResolver}
  1886. LogWarning(croErrorOnUnknownName in Options,20220910235106,'TCSSResolver.ComputeValue not supported',El);
  1887. {$ENDIF}
  1888. end;
  1889. end;
  1890. function TCSSResolver.SameValueText(const A, B: TCSSString): boolean;
  1891. begin
  1892. if StringComparison=crscCaseInsensitive then
  1893. Result:=SameText(A,B)
  1894. else
  1895. Result:=A=B;
  1896. end;
  1897. function TCSSResolver.SameValueText(A: PCSSChar; ALen: integer; B: PCSSChar; BLen: integer
  1898. ): boolean;
  1899. var
  1900. AC, BC: TCSSChar;
  1901. i: Integer;
  1902. begin
  1903. if ALen<>BLen then exit(false);
  1904. if ALen=0 then exit(true);
  1905. if StringComparison=crscCaseInsensitive then
  1906. begin
  1907. for i:=0 to ALen-1 do
  1908. begin
  1909. AC:=A^;
  1910. BC:=B^;
  1911. if (AC<>BC) then
  1912. begin
  1913. if (AC in ['a'..'z']) then AC:=TCSSChar(ord(AC)-32);
  1914. if (BC in ['a'..'z']) then BC:=TCSSChar(ord(BC)-32);
  1915. if AC<>BC then
  1916. exit(false);
  1917. end;
  1918. inc(A);
  1919. inc(B);
  1920. end;
  1921. Result:=true;
  1922. end else
  1923. Result:=CompareMem(A,B,ALen);
  1924. end;
  1925. function TCSSResolver.PosSubString(const SearchStr, Str: TCSSString): integer;
  1926. var
  1927. SearchLen: SizeInt;
  1928. i: Integer;
  1929. SearchP, StrP: PCSSChar;
  1930. AC, BC: TCSSChar;
  1931. begin
  1932. Result:=0;
  1933. if SearchStr='' then exit;
  1934. if Str='' then exit;
  1935. if StringComparison=crscCaseInsensitive then
  1936. begin
  1937. SearchP:=PCSSChar(SearchStr);
  1938. StrP:=PCSSChar(Str);
  1939. SearchLen:=length(SearchStr);
  1940. AC:=SearchP^;
  1941. if AC in ['a'..'z'] then AC:=TCSSChar(ord(AC)-32);
  1942. for i:=0 to length(Str)-SearchLen do
  1943. begin
  1944. BC:=StrP^;
  1945. if BC in ['a'..'z'] then BC:=TCSSChar(ord(BC)-32);
  1946. if (AC=BC) and SameValueText(SearchP,SearchLen,StrP,SearchLen) then
  1947. exit(i+1);
  1948. inc(StrP);
  1949. end;
  1950. end else begin
  1951. Result:=Pos(SearchStr,Str);
  1952. end;
  1953. end;
  1954. function TCSSResolver.PosWord(const SearchWord, Words: TCSSString): integer;
  1955. // attribute selector ~=
  1956. const
  1957. Whitespace = [#9,#10,#12,#13,' '];
  1958. var
  1959. WordsLen, SearchLen: SizeInt;
  1960. p, WordStart: Integer;
  1961. begin
  1962. Result:=0;
  1963. if SearchWord='' then exit;
  1964. if Words='' then exit;
  1965. WordsLen:=length(Words);
  1966. SearchLen:=length(SearchWord);
  1967. //writeln('TCSSResolver.PosWord "',SearchWord,'" Words="',words,'"');
  1968. p:=1;
  1969. repeat
  1970. repeat
  1971. if p>WordsLen then
  1972. exit(0);
  1973. if not (Words[p] in Whitespace) then
  1974. break;
  1975. inc(p);
  1976. until false;
  1977. WordStart:=p;
  1978. while (p<=WordsLen) and not (Words[p] in Whitespace) do
  1979. inc(p);
  1980. //writeln('TCSSResolver.PosWord start=',WordStart,' p=',p);
  1981. if SameValueText(@SearchWord[1],SearchLen,@Words[WordStart],p-WordStart) then
  1982. exit(WordStart);
  1983. until p>WordsLen;
  1984. end;
  1985. function TCSSResolver.GetSiblingCount(aNode: ICSSNode): integer;
  1986. var
  1987. aParent, CurNode: ICSSNode;
  1988. begin
  1989. if aNode=nil then
  1990. exit(0);
  1991. aParent:=aNode.GetCSSParent;
  1992. if aParent<>nil then
  1993. exit(aParent.GetCSSChildCount);
  1994. Result:=0;
  1995. CurNode:=aNode;
  1996. while CurNode<>nil do
  1997. begin
  1998. inc(Result);
  1999. CurNode:=CurNode.GetCSSPreviousSibling;
  2000. end;
  2001. CurNode:=aNode.GetCSSNextSibling;
  2002. while CurNode<>nil do
  2003. begin
  2004. inc(Result);
  2005. CurNode:=CurNode.GetCSSNextSibling;
  2006. end;
  2007. end;
  2008. procedure TCSSResolver.MergeAttribute(El: TCSSElement;
  2009. aSpecificity: TCSSSpecificity);
  2010. var
  2011. C: TClass;
  2012. Decl: TCSSDeclarationElement;
  2013. aKey: TCSSElement;
  2014. AnAttrID, NextAttrID, SubAttrID: TCSSNumericalID;
  2015. AttrDesc: TCSSAttributeDesc;
  2016. KeyData: TCSSAttributeKeyData;
  2017. begin
  2018. C:=El.ClassType;
  2019. if C<>TCSSDeclarationElement then
  2020. begin
  2021. // already warned by parser
  2022. {$IFDEF VerboseCSSResolver}
  2023. Log(etWarning,20220908232359,'Unknown property',El);
  2024. {$ENDIF}
  2025. exit;
  2026. end;
  2027. Decl:=TCSSDeclarationElement(El);
  2028. if Decl.KeyCount<>1 then
  2029. begin
  2030. if Decl.KeyCount<1 then
  2031. begin
  2032. // already warned by parser
  2033. {$IFDEF VerboseCSSResolver}
  2034. Log(etWarning,20231112135955,'missing keys in declaration',Decl);
  2035. {$ENDIF}
  2036. end;
  2037. if Decl.KeyCount>1 then
  2038. begin
  2039. // already warned by parser
  2040. {$IFDEF VerboseCSSResolver}
  2041. Log(etWarning,20231112140722,'too many keys in declaration',Decl);
  2042. {$ENDIF}
  2043. end;
  2044. exit;
  2045. end;
  2046. if Decl.ChildCount=0 then
  2047. exit;
  2048. if Decl.IsImportant then
  2049. aSpecificity:=CSSSpecificityImportant;
  2050. aKey:=Decl.Keys[0];
  2051. C:=aKey.ClassType;
  2052. if C<>TCSSResolvedIdentifierElement then
  2053. begin
  2054. // already warned by parser
  2055. {$IFDEF VerboseCSSResolver}
  2056. Log(etWarning,20220908232359,'Unknown CSS key',aKey);
  2057. {$ENDIF}
  2058. exit;
  2059. end;
  2060. AnAttrID:=TCSSResolvedIdentifierElement(aKey).NumericalID;
  2061. if AnAttrID<=CSSIDNone then
  2062. begin
  2063. // already warned by parser
  2064. {$IFDEF VerboseCSSResolver}
  2065. Log(etWarning,20220909000932,'Unknown CSS property "'+TCSSResolvedIdentifierElement(aKey).Name+'"',aKey);
  2066. {$ENDIF}
  2067. exit;
  2068. end;
  2069. KeyData:=TCSSAttributeKeyData(aKey.CustomData);
  2070. if KeyData.Invalid then
  2071. begin
  2072. // already warned by parser
  2073. {$IFDEF VerboseCSSResolver}
  2074. //Log(etWarning,20240710162139,'Invalid CSS property "'+El.AsString+'"',aKey);
  2075. {$ENDIF}
  2076. exit;
  2077. end;
  2078. if AnAttrID=CSSAttributeID_All then
  2079. begin
  2080. // 'all' sets almost all attributes to a value
  2081. if FMergedAllSpecificity>aSpecificity then
  2082. exit;
  2083. FMergedAllSpecificity:=aSpecificity;
  2084. FMergedAllDecl:=Decl;
  2085. SubAttrID:=FMergedAttributeFirst;
  2086. while SubAttrID>=1 do
  2087. begin
  2088. NextAttrID:=FMergedAttributes[SubAttrID].Next;
  2089. AttrDesc:=GetAttributeDesc(SubAttrID);
  2090. if AttrDesc.All then
  2091. RemoveMergedAttribute(SubAttrID);
  2092. SubAttrID:=NextAttrID;
  2093. end;
  2094. end
  2095. else begin
  2096. // set property
  2097. AttrDesc:=GetAttributeDesc(AnAttrID);
  2098. if (FMergedAllSpecificity>aSpecificity) and AttrDesc.All then
  2099. exit; // a former 'all' has higher Specificity
  2100. with FMergedAttributes[AnAttrID] do
  2101. begin
  2102. if (Stamp=FMergedAttributesStamp) and (Specificity>aSpecificity) then
  2103. exit; // a former attribute has higher Specificity
  2104. end;
  2105. {$IFDEF VerboseCSSResolver}
  2106. writeln('TCSSResolver.MergeAttribute Node=',FNode.GetCSSID,' AttrID=',AnAttrID,' ',AttrDesc.Name,' Spec=',aSpecificity,' Decl="',Decl.AsString,'"');
  2107. {$ENDIF}
  2108. SetMergedAttribute(AnAttrID,aSpecificity,Decl);
  2109. if (AttrDesc<>nil) and (length(AttrDesc.CompProps)>0) then
  2110. begin
  2111. // shorthand -> set longhands
  2112. // Note: order matters when same Specificity, so longhands must be done during the cascade
  2113. for NextAttrID:=0 to length(AttrDesc.CompProps)-1 do
  2114. begin
  2115. SubAttrID:=AttrDesc.CompProps[NextAttrID].Index;
  2116. with FMergedAttributes[SubAttrID] do
  2117. begin
  2118. if (Stamp=FMergedAttributesStamp) and (Specificity>aSpecificity) then
  2119. continue; // a former attribute has higher Specificity
  2120. SetMergedAttribute(SubAttrID,aSpecificity,nil);
  2121. {$IFDEF VerboseCSSResolver}
  2122. writeln('TCSSResolver.MergeAttribute Longhand Node=',FNode.GetCSSID,' Shorthand=',AttrDesc.Name,' Spec=',aSpecificity,' Decl="',Decl.AsString,'" Longhand=',GetAttributeDesc(SubAttrID).Name);
  2123. {$ENDIF}
  2124. end;
  2125. end;
  2126. end;
  2127. //WriteMergedAttributes('TCSSResolver.MergeAttribute');
  2128. end;
  2129. end;
  2130. procedure TCSSResolver.SaveSharedMergedAttributes(SharedMerged: TCSSSharedRuleList);
  2131. var
  2132. Cnt: Integer;
  2133. AttrID: TCSSNumericalID;
  2134. AttrP: PMergedAttribute;
  2135. begin
  2136. SharedMerged.AllDecl:=FMergedAllDecl;
  2137. SharedMerged.AllSpecificity:=FMergedAllSpecificity;
  2138. // count attributes (skip longhands set by shorthands DeclEl=nil)
  2139. Cnt:=0;
  2140. AttrID:=FMergedAttributeFirst;
  2141. while AttrID>0 do
  2142. begin
  2143. AttrP:=@FMergedAttributes[AttrID];
  2144. if AttrP^.DeclEl<>nil then
  2145. inc(Cnt);
  2146. AttrID:=AttrP^.Next;
  2147. end;
  2148. SetLength(SharedMerged.Values,Cnt);
  2149. // save attributes
  2150. Cnt:=0;
  2151. AttrID:=FMergedAttributeFirst;
  2152. while AttrID>0 do
  2153. begin
  2154. AttrP:=@FMergedAttributes[AttrID];
  2155. if AttrP^.DeclEl<>nil then
  2156. begin
  2157. SharedMerged.Values[Cnt].AttrID:=AttrID;
  2158. SharedMerged.Values[Cnt].DeclEl:=AttrP^.DeclEl;
  2159. SharedMerged.Values[Cnt].Specificity:=AttrP^.Specificity;
  2160. inc(Cnt);
  2161. end;
  2162. AttrID:=AttrP^.Next;
  2163. end;
  2164. end;
  2165. procedure TCSSResolver.LoadSharedMergedAttributes(
  2166. SharedMerged: TCSSSharedRuleList);
  2167. var
  2168. i: Integer;
  2169. begin
  2170. ClearMerge;
  2171. FMergedAllDecl:=SharedMerged.AllDecl;
  2172. FMergedAllSpecificity:=SharedMerged.AllSpecificity;
  2173. for i:=0 to length(SharedMerged.Values)-1 do
  2174. begin
  2175. with SharedMerged.Values[i] do
  2176. SetMergedAttribute(AttrID,Specificity,DeclEl);
  2177. end;
  2178. end;
  2179. procedure TCSSResolver.WriteMergedAttributes(const Title: TCSSString);
  2180. var
  2181. AttrID, NextAttrID: TCSSNumericalID;
  2182. AttrP: PMergedAttribute;
  2183. Cnt: Integer;
  2184. AttrDesc: TCSSAttributeDesc;
  2185. begin
  2186. writeln('TCSSResolver.WriteMergedAttributes START ',Title);
  2187. Cnt:=0;
  2188. AttrID:=FMergedAttributeFirst;
  2189. while AttrID>0 do
  2190. begin
  2191. NextAttrID:=FMergedAttributes[AttrID].Next;
  2192. AttrP:=@FMergedAttributes[AttrID];
  2193. AttrDesc:=GetAttributeDesc(AttrID);
  2194. writeln(' ',Cnt,' AttrID=',AttrID,' ',AttrDesc.Name,' Spec=',AttrP^.Specificity,' Value="',AttrP^.Value,'" Complete=',AttrP^.Complete,' Decl=',AttrP^.DeclEl<>nil);
  2195. inc(Cnt);
  2196. AttrID:=NextAttrID;
  2197. end;
  2198. writeln('TCSSResolver.WriteMergedAttributes END Count=',Cnt);
  2199. end;
  2200. procedure TCSSResolver.LoadMergedValues;
  2201. var
  2202. AttrID, NextAttrID: TCSSNumericalID;
  2203. AttrP: PMergedAttribute;
  2204. Key: TCSSElement;
  2205. KeyData: TCSSAttributeKeyData;
  2206. Value: TCSSString;
  2207. begin
  2208. // load value strings from css elements
  2209. // and remove longhand placeholders set by shorthands
  2210. AttrID:=FMergedAttributeFirst;
  2211. while AttrID>0 do
  2212. begin
  2213. NextAttrID:=FMergedAttributes[AttrID].Next;
  2214. AttrP:=@FMergedAttributes[AttrID];
  2215. if AttrP^.DeclEl=nil then
  2216. // remove longhand placeholder set by shorthand
  2217. RemoveMergedAttribute(AttrID)
  2218. else begin
  2219. Key:=AttrP^.DeclEl.Keys[0];
  2220. KeyData:=Key.CustomData as TCSSAttributeKeyData;
  2221. Value:=KeyData.Value;
  2222. //writeln('TCSSResolver.LoadMergedValues AttrID=',AttrID,' Decl=',AttrP^.DeclEl.Classname,' Key=',(AttrP^.DeclEl.Keys[0] as TCSSResolvedIdentifierElement).Name,' Value=',Value);
  2223. AttrP^.Value:=Value;
  2224. if TCSSResolverParser.IsWhiteSpace(Value) then
  2225. RemoveMergedAttribute(AttrID)
  2226. else
  2227. AttrP^.Complete:=KeyData.Complete;
  2228. end;
  2229. AttrID:=NextAttrID;
  2230. end;
  2231. end;
  2232. procedure TCSSResolver.SubstituteVarCalls;
  2233. // called after CSS attribute values have been merged by cascade rules
  2234. // before replacing shorthands
  2235. const
  2236. ReplaceMax = 10;
  2237. var
  2238. AttrID, NextAttrID: TCSSNumericalID;
  2239. AttrP: PMergedAttribute;
  2240. p: PCSSChar;
  2241. ReplaceCnt: integer;
  2242. procedure SkipEscape;
  2243. begin
  2244. inc(p);
  2245. if p^>#0 then inc(p);
  2246. end;
  2247. procedure SkipString;
  2248. var
  2249. c: TCSSChar;
  2250. begin
  2251. c:=p^;
  2252. repeat
  2253. inc(p);
  2254. if p^=#0 then exit;
  2255. if p^=c then
  2256. begin
  2257. inc(p);
  2258. exit;
  2259. end;
  2260. until false;
  2261. end;
  2262. procedure SkipIdentifier;
  2263. begin
  2264. while p^ in ['-','_','a'..'z','A'..'Z'] do inc(p);
  2265. end;
  2266. procedure SkipWhiteSpace;
  2267. begin
  2268. while p^ in [' ',#9,#10,#13] do inc(p);
  2269. end;
  2270. function ReplaceVarsInRightString: boolean;
  2271. var
  2272. OldP, Lvl: integer;
  2273. VarStartP, NameStartP, NameEndP, ValueStartP, BracketCloseP: PCSSChar;
  2274. aValue, s: TCSSString;
  2275. {$IF SIZEOF(CHAR)=2}
  2276. varname: UnicodeString;
  2277. {$ELSE}
  2278. VarName: ShortString;
  2279. {$ENDIF}
  2280. Desc: TCSSResCustomAttributeDesc;
  2281. aParentNode: ICSSNode;
  2282. begin
  2283. {$IFDEF VerboseCSSVar}
  2284. writeln('ReplaceVarsInRightString p="',p,'"');
  2285. {$ENDIF}
  2286. Result:=true;
  2287. repeat
  2288. case p^ of
  2289. #0: break;
  2290. '"','''': SkipString;
  2291. '\': SkipEscape;
  2292. '@','#':
  2293. begin
  2294. inc(p);
  2295. SkipIdentifier;
  2296. end;
  2297. '-':
  2298. begin
  2299. inc(p);
  2300. if (p^ in ['a'..'z','A'..'Z','_','-']) then
  2301. SkipIdentifier;
  2302. end;
  2303. 'a'..'z','A'..'Z','_':
  2304. if (p^='v') and (p[1]='a') and (p[2]='r') and (p[3]='(') then
  2305. begin
  2306. // var() found
  2307. inc(ReplaceCnt);
  2308. if ReplaceCnt=ReplaceMax then
  2309. begin
  2310. // maybe a loop
  2311. exit(false);
  2312. end;
  2313. VarStartP:=p;
  2314. inc(p,4);
  2315. SkipWhiteSpace;
  2316. // replace var() in parameter
  2317. OldP:=p-PCSSChar(AttrP^.Value);
  2318. if not ReplaceVarsInRightString then
  2319. exit(false);
  2320. p:=PCSSChar(AttrP^.Value)+OldP;
  2321. NameStartP:=p;
  2322. NameEndP:=nil;
  2323. ValueStartP:=nil;
  2324. if (p^<>'-') or (p[1]<>'-') then
  2325. begin
  2326. {$IFDEF VerboseCSSVar}
  2327. writeln('ReplaceVarsInRightString invalid VarName (must start with --): ',NameStartP);
  2328. {$ENDIF}
  2329. exit(false);
  2330. end;
  2331. inc(p,2);
  2332. while p^ in ['a'..'z','A'..'Z','_','-'] do inc(p);
  2333. NameEndP:=p;
  2334. if NameEndP-NameStartP>255 then
  2335. begin
  2336. {$IFDEF VerboseCSSVar}
  2337. writeln('ReplaceVarsInRightString invalid VarName (too long): ',NameStartP);
  2338. {$ENDIF}
  2339. exit(false);
  2340. end;
  2341. SkipWhiteSpace;
  2342. if p^=',' then
  2343. begin
  2344. inc(p);
  2345. SkipWhiteSpace;
  2346. ValueStartP:=p;
  2347. end;
  2348. // skip to round bracket close
  2349. Lvl:=1;
  2350. BracketCloseP:=nil;
  2351. repeat
  2352. case p^ of
  2353. #0:
  2354. begin
  2355. // syntax error
  2356. {$IFDEF VerboseCSSVar}
  2357. writeln('ReplaceVarsInRightString missing closing bracket: ',NameStartP);
  2358. {$ENDIF}
  2359. exit(false);
  2360. end;
  2361. '"','''': SkipString;
  2362. '\': SkipEscape;
  2363. '(':
  2364. begin
  2365. inc(Lvl);
  2366. inc(p);
  2367. end;
  2368. ')':
  2369. if Lvl=1 then
  2370. begin
  2371. BracketCloseP:=p;
  2372. inc(p);
  2373. break;
  2374. end else begin
  2375. dec(Lvl);
  2376. inc(p);
  2377. end;
  2378. else
  2379. inc(p);
  2380. end;
  2381. until false;
  2382. // fetch value from node
  2383. SetString(VarName,NameStartP,NameEndP-NameStartP);
  2384. {$IF SIZEOF(CHAR)=2}
  2385. Desc:=TCSSResCustomAttributeDesc(FCustomAttributeNameToDesc.Find(UTF8Encode(VarName)));
  2386. {$ELSE}
  2387. Desc:=TCSSResCustomAttributeDesc(FCustomAttributeNameToDesc.Find(VarName));
  2388. {$ENDIF}
  2389. if Desc<>nil then
  2390. begin
  2391. {$IFDEF VerboseCSSVar}
  2392. writeln('ReplaceVarsInRightString VarName="',VarName,'" AttrID=',Desc.Index);
  2393. {$ENDIF}
  2394. if FMergedAttributes[Desc.Index].Stamp=FMergedAttributesStamp then
  2395. aValue:=FMergedAttributes[Desc.Index].Value
  2396. else
  2397. aValue:='';
  2398. if aValue='' then
  2399. begin
  2400. aParentNode:=FNode.GetCSSParent;
  2401. if aParentNode<>nil then
  2402. aValue:=aParentNode.GetCSSCustomAttribute(Desc.Index);
  2403. end;
  2404. end else begin
  2405. {$IFDEF VerboseCSSVar}
  2406. writeln('ReplaceVarsInRightString VarName="',VarName,'" never declared');
  2407. {$ENDIF}
  2408. aValue:='';
  2409. end;
  2410. if aValue='' then
  2411. begin
  2412. // use default value
  2413. if ValueStartP<>nil then
  2414. SetString(aValue,ValueStartP,BracketCloseP-ValueStartP);
  2415. end;
  2416. {$IFDEF VerboseCSSVar}
  2417. writeln('ReplaceVarsInRightString VarName="',VarName,'" Value="',aValue,'"');
  2418. {$ENDIF}
  2419. // replace
  2420. p:=PCSSChar(AttrP^.Value);
  2421. OldP:=VarStartP-p;
  2422. s:=AttrP^.Value;
  2423. AttrP^.Value:=LeftStr(s,VarStartP-p)+aValue+copy(s,BracketCloseP-p+2,length(s));
  2424. {$IFDEF VerboseCSSVar}
  2425. writeln('ReplaceVarsInRightString New AttrP^.Value="',AttrP^.Value,'"');
  2426. {$ENDIF}
  2427. // continue parsing
  2428. p:=PCSSChar(AttrP^.Value)+OldP;
  2429. end else
  2430. SkipIdentifier;
  2431. else
  2432. inc(p);
  2433. end;
  2434. until false;
  2435. end;
  2436. begin
  2437. AttrID:=FMergedAttributeFirst;
  2438. while AttrID>0 do
  2439. begin
  2440. NextAttrID:=FMergedAttributes[AttrID].Next;
  2441. AttrP:=@FMergedAttributes[AttrID];
  2442. if not AttrP^.Complete then
  2443. begin
  2444. // check attribute
  2445. if Pos('var(',AttrP^.Value)>0 then
  2446. begin
  2447. // can have var() calls -> parse
  2448. p:=PCSSChar(AttrP^.Value);
  2449. {$IFDEF VerboseCSSVar}
  2450. writeln('TCSSResolver.SubstituteVarCalls ',GetAttributeDesc(AttrID).Name,': "',AttrP^.Value,'"');
  2451. {$ENDIF}
  2452. ReplaceCnt:=0;
  2453. if not ReplaceVarsInRightString then
  2454. AttrP^.Value:='';
  2455. end;
  2456. if AttrP^.Value='' then
  2457. RemoveMergedAttribute(AttrID);
  2458. end;
  2459. AttrID:=NextAttrID;
  2460. end;
  2461. end;
  2462. procedure TCSSResolver.ApplyShorthands;
  2463. // called after all var() have been substituted
  2464. var
  2465. AttrID, NextAttrID, SubAttrID: TCSSNumericalID;
  2466. AttrP, SubAttrP: PMergedAttribute;
  2467. AttrDesc, SubAttrDesc: TCSSAttributeDesc;
  2468. LHAttrIDs: TCSSNumericalIDArray;
  2469. LHValues: TCSSStringArray;
  2470. i: Integer;
  2471. begin
  2472. AttrID:=FMergedAttributeFirst;
  2473. while AttrID>0 do
  2474. begin
  2475. NextAttrID:=FMergedAttributes[AttrID].Next;
  2476. AttrP:=@FMergedAttributes[AttrID];
  2477. AttrDesc:=GetAttributeDesc(AttrID);
  2478. //writeln('TCSSResolver.ApplyShorthands ',AttrID,' ',AttrDesc.Name,' ShortHand=',AttrDesc.OnSplitShorthand<>nil);
  2479. if Assigned(AttrDesc.OnSplitShorthand) then
  2480. begin
  2481. RemoveMergedAttribute(AttrID);
  2482. if AttrP^.Value>'' then
  2483. begin
  2484. // replace shorthand with longhands, keep already set longhands
  2485. LHAttrIDs:=[];
  2486. LHValues:=[];
  2487. InitParseAttr(AttrDesc,nil,AttrP^.Value);
  2488. if not (CurComp.Kind in [rvkNone,rvkInvalid]) then
  2489. begin
  2490. AttrDesc.OnSplitShorthand(Self,LHAttrIDs,LHValues);
  2491. for i:=0 to length(LHAttrIDs)-1 do
  2492. begin
  2493. SubAttrID:=LHAttrIDs[i];
  2494. SubAttrDesc:=GetAttributeDesc(SubAttrID);
  2495. if SubAttrDesc=nil then
  2496. raise ECSSResolver.Create('20240709194135');
  2497. if SubAttrDesc.OnSplitShorthand<>nil then
  2498. raise ECSSResolver.Create('20240709194634');
  2499. SubAttrP:=@FMergedAttributes[SubAttrID];
  2500. if (SubAttrP^.Stamp=FMergedAttributesStamp) and (SubAttrP^.Specificity>=AttrP^.Specificity) then
  2501. begin
  2502. // longhand already exists -> keep
  2503. end else begin
  2504. SetMergedAttribute(SubAttrID,AttrP^.Specificity,nil);
  2505. SubAttrP^.Value:=LHValues[i];
  2506. if SubAttrP^.Value='' then
  2507. SubAttrP^.Value:=SubAttrDesc.InitialValue;
  2508. SubAttrP^.Complete:=false;
  2509. // Note: if NextAttrID=0 then this was the last shorthand
  2510. end;
  2511. end;
  2512. end;
  2513. end;
  2514. end;
  2515. AttrID:=NextAttrID;
  2516. end;
  2517. end;
  2518. function TCSSResolver.CreateValueList: TCSSAttributeValues;
  2519. var
  2520. Cnt: Integer;
  2521. AttrID: TCSSNumericalID;
  2522. AttrP: PMergedAttribute;
  2523. AttrValue: TCSSAttributeValue;
  2524. begin
  2525. Result:=TCSSAttributeValues.Create;
  2526. // all
  2527. if FMergedAllDecl<>nil then
  2528. begin
  2529. // set Result.AllValue
  2530. InitParseAttr(CSSRegistry.Attributes[CSSAttributeID_All],nil,GetDeclarationValue(FMergedAllDecl));
  2531. if (CurComp.Kind=rvkKeyword) and IsBaseKeyword(CurComp.KeywordID) then
  2532. begin
  2533. Result.AllValue:=CurComp.KeywordID;
  2534. end;
  2535. end;
  2536. // count and allocate attributes
  2537. Cnt:=0;
  2538. AttrID:=FMergedAttributeFirst;
  2539. while AttrID>0 do
  2540. begin
  2541. //writeln('TCSSResolver.CreateValueList Cnt=',Cnt,' AttrID=',AttrID);
  2542. inc(Cnt);
  2543. AttrID:=FMergedAttributes[AttrID].Next;
  2544. end;
  2545. SetLength(Result.Values,Cnt);
  2546. // copy
  2547. Cnt:=0;
  2548. AttrID:=FMergedAttributeFirst;
  2549. while AttrID>0 do
  2550. begin
  2551. AttrP:=@FMergedAttributes[AttrID];
  2552. AttrValue:=TCSSAttributeValue.Create;
  2553. Result.Values[Cnt]:=AttrValue;
  2554. AttrValue.AttrID:=AttrID;
  2555. AttrValue.Value:=AttrP^.Value;
  2556. //writeln('TCSSResolver.CreateValueList ',Cnt,' ',AttrID,' "',AttrValue.Value,'"');
  2557. AttrID:=AttrP^.Next;
  2558. inc(Cnt);
  2559. end;
  2560. // sort
  2561. Result.SortValues;
  2562. end;
  2563. function TCSSResolver.ResolveIdentifier(El: TCSSResolvedIdentifierElement;
  2564. Kind: TCSSNumericalIDKind): TCSSNumericalID;
  2565. var
  2566. aName: TCSSString;
  2567. begin
  2568. Result:=El.NumericalID;
  2569. if Result=CSSIDNone then
  2570. begin
  2571. // not yet resolved
  2572. aName:=El.Name;
  2573. if Kind in [nikPseudoClass,nikPseudoElement] then
  2574. begin
  2575. // pseudo attributes and elements are ASCII case insensitive
  2576. System.Delete(aName,1,1);
  2577. aName:=lowercase(aName);
  2578. end;
  2579. Result:=CSSRegistry.IndexOfNamedItem(Kind,aName);
  2580. if Result=CSSIDNone then
  2581. begin
  2582. El.NumericalID:=-1;
  2583. Log(etWarning,20240625160211,'unknown '+CSSNumericalIDKindNames[Kind]+' "'+aName+'"',El);
  2584. end else begin
  2585. El.NumericalID:=Result;
  2586. El.Kind:=Kind;
  2587. end;
  2588. end else if Result=-1 then
  2589. Result:=CSSIDNone // name not found
  2590. else if El.Kind<>Kind then
  2591. raise ECSSResolver.Create('20240701105839');
  2592. end;
  2593. procedure TCSSResolver.LogWarning(IsError: boolean; const ID: TCSSMsgID;
  2594. const Msg: TCSSString; PosEl: TCSSElement);
  2595. var
  2596. MsgType: TEventType;
  2597. begin
  2598. if IsError then
  2599. MsgType:=etError
  2600. else
  2601. MsgType:=etWarning;
  2602. Log(MsgType,ID,Msg,PosEl);
  2603. end;
  2604. procedure TCSSResolver.Log(MsgType: TEventType; const ID: TCSSMsgID;
  2605. const Msg: TCSSString; PosEl: TCSSElement);
  2606. var
  2607. Entry: TCSSResolverLogEntry;
  2608. begin
  2609. {$IFDEF VerboseCSSResolver}
  2610. writeln('TCSSResolver.Log ',MsgType,' ID=',ID,' ',GetElPos(PosEl),': "',Msg,'"');
  2611. {$ENDIF}
  2612. if Assigned(OnLog) then
  2613. begin
  2614. Entry:=TCSSResolverLogEntry.Create;
  2615. Entry.MsgType:=MsgType;
  2616. Entry.ID:=ID;
  2617. Entry.Msg:=Msg;
  2618. Entry.PosEl:=PosEl;
  2619. FLogEntries.Add(Entry);
  2620. OnLog(Self,Entry);
  2621. end;
  2622. if MsgType=etError then
  2623. begin
  2624. raise ECSSResolver.Create('['+IntToStr(ID)+'] '+Msg+' at '+GetElPos(PosEl));
  2625. end;
  2626. end;
  2627. function TCSSResolver.GetElPos(El: TCSSElement): TCSSString;
  2628. begin
  2629. if El=nil then
  2630. Result:='no element'
  2631. else begin
  2632. Result:=El.SourceFileName+'('+IntToStr(El.SourceCol)+','+IntToStr(El.SourceCol)+')';
  2633. {$IFDEF VerboseCSSResolver}
  2634. Result:='['+GetElPath(El)+']'+Result;
  2635. {$ENDIF}
  2636. end;
  2637. end;
  2638. function TCSSResolver.ParseInlineStyle(const Src: TCSSString): TCSSRuleElement;
  2639. begin
  2640. Result:=ParseCSSSource(Src,true) as TCSSRuleElement;
  2641. end;
  2642. function TCSSResolver.GetElPath(El: TCSSElement): TCSSString;
  2643. begin
  2644. Result:=GetCSSPath(El);
  2645. end;
  2646. constructor TCSSResolver.Create(AOwner: TComponent);
  2647. begin
  2648. inherited;
  2649. FLogEntries:=TFPObjectList.Create(true);
  2650. FSharedRuleLists:=TAVLTree.Create(@CompareCSSSharedRuleLists);
  2651. FCustomAttributeNameToDesc:=TFPHashList.Create;
  2652. end;
  2653. destructor TCSSResolver.Destroy;
  2654. begin
  2655. Clear;
  2656. FreeAndNil(FCustomAttributeNameToDesc);
  2657. FreeAndNil(FSharedRuleLists);
  2658. FreeAndNil(FLogEntries);
  2659. inherited Destroy;
  2660. end;
  2661. procedure TCSSResolver.Clear;
  2662. begin
  2663. ClearStyleSheets;
  2664. end;
  2665. procedure TCSSResolver.Init;
  2666. var
  2667. i: Integer;
  2668. begin
  2669. if CSSRegistry.Modified then
  2670. begin
  2671. CSSRegistry.ConsistencyCheck;
  2672. CSSRegistry.Modified:=false;
  2673. end;
  2674. // todo: if CSSRegistry has changed, reparse all stylesheets
  2675. FMergedAttributesStamp:=1;
  2676. for i:=0 to length(FMergedAttributes)-1 do
  2677. FMergedAttributes[i].Stamp:=0;
  2678. end;
  2679. procedure TCSSResolver.ClearSharedRuleLists;
  2680. begin
  2681. FSharedRuleLists.FreeAndClear;
  2682. end;
  2683. procedure TCSSResolver.Compute(Node: ICSSNode; InlineStyle: TCSSRuleElement;
  2684. out Rules: TCSSSharedRuleList; out Values: TCSSAttributeValues);
  2685. var
  2686. i: Integer;
  2687. begin
  2688. Rules:=nil;
  2689. FNode:=Node;
  2690. try
  2691. InitMerge;
  2692. FindMatchingRules;
  2693. // create a shared rule list and merge attributes
  2694. Rules:=CreateSharedRuleList;
  2695. // apply inline attributes
  2696. if InlineStyle<>nil then
  2697. begin
  2698. for i:=0 to InlineStyle.ChildCount-1 do
  2699. MergeAttribute(InlineStyle.Children[i],CSSSpecificityInline);
  2700. end;
  2701. LoadMergedValues;
  2702. SubstituteVarCalls; // replace var() calls
  2703. ApplyShorthands;
  2704. // create sorted map AttrId to Value
  2705. Values:=CreateValueList;
  2706. finally
  2707. FNode:=nil;
  2708. end;
  2709. end;
  2710. function TCSSResolver.GetAttributeID(const aName: TCSSString; AutoCreate: boolean): TCSSNumericalID;
  2711. var
  2712. Desc: TCSSResCustomAttributeDesc;
  2713. Cnt: TCSSNumericalID;
  2714. begin
  2715. Result:=CSSRegistry.IndexOfAttributeName(aName);
  2716. if Result<0 then
  2717. begin
  2718. Desc:=TCSSResCustomAttributeDesc(FCustomAttributeNameToDesc.Find(aName));
  2719. if Desc<>nil then
  2720. exit(Desc.Index);
  2721. if AutoCreate
  2722. and (length(aName)>2) and (aName[1]='-') and (aName[2]='-')
  2723. and (length(aName)<256) then
  2724. begin
  2725. // create custom attribute
  2726. Cnt:=FCustomAttributeCount;
  2727. if Cnt=length(FCustomAttributes) then
  2728. begin
  2729. if Cnt<32 then
  2730. Cnt:=32
  2731. else
  2732. Cnt:=Cnt*2;
  2733. SetLength(FCustomAttributes,Cnt);
  2734. FillByte(FCustomAttributes[FCustomAttributeCount],SizeOf(Pointer)*(Cnt-FCustomAttributeCount),0);
  2735. end;
  2736. Desc:=TCSSResCustomAttributeDesc.Create;
  2737. Desc.Name:=aName;
  2738. Desc.Index:=CSSRegistry.AttributeCount+FCustomAttributeCount;
  2739. Desc.Inherits:=true;
  2740. FCustomAttributes[FCustomAttributeCount]:=Desc;
  2741. FCustomAttributeNameToDesc.Add(aName,Desc);
  2742. inc(FCustomAttributeCount);
  2743. Result:=Desc.Index;
  2744. Cnt:=GetAttributeID(aName);
  2745. if Cnt<>Result then
  2746. raise ECSSResolver.Create('20240822173412');
  2747. if GetAttributeDesc(Result)<>Desc then
  2748. raise ECSSResolver.Create('20240822174053');
  2749. end;
  2750. end;
  2751. end;
  2752. procedure TCSSResolver.FindMatchingRules;
  2753. var
  2754. aLayerIndex, i: Integer;
  2755. begin
  2756. FElRuleCount:=0;
  2757. // find all matching rules in all stylesheets
  2758. for aLayerIndex:=0 to length(FLayers)-1 do
  2759. with FLayers[aLayerIndex] do begin
  2760. FSourceSpecificity:=CSSOriginToSpecifity[Origin];
  2761. for i:=0 to ElementCount-1 do
  2762. ComputeElement(Elements[i].Element);
  2763. end;
  2764. end;
  2765. function TCSSResolver.GetAttributeDesc(AttrId: TCSSNumericalID
  2766. ): TCSSAttributeDesc;
  2767. begin
  2768. Result:=nil;
  2769. if AttrID<CSSRegistry.AttributeCount then
  2770. Result:=CSSRegistry.Attributes[AttrId]
  2771. else begin
  2772. dec(AttrID,CSSRegistry.AttributeCount);
  2773. if AttrID<FCustomAttributeCount then
  2774. Result:=FCustomAttributes[AttrId];
  2775. end;
  2776. end;
  2777. function TCSSResolver.GetDeclarationValue(Decl: TCSSDeclarationElement): TCSSString;
  2778. var
  2779. KeyData: TCSSAttributeKeyData;
  2780. begin
  2781. Result:='';
  2782. if Decl=nil then exit;
  2783. if Decl.KeyCount=0 then exit;
  2784. KeyData:=TCSSAttributeKeyData(Decl.Keys[0].CustomData);
  2785. if KeyData=nil then exit;
  2786. Result:=KeyData.Value;
  2787. end;
  2788. procedure TCSSResolver.ClearStyleSheets;
  2789. var
  2790. i: Integer;
  2791. begin
  2792. ClearElements;
  2793. // clear stylesheets
  2794. for i:=0 to FStyleSheetCount-1 do
  2795. begin
  2796. FreeAndNil(FStyleSheets[i].Element);
  2797. FreeAndNil(FStyleSheets[i]);
  2798. end;
  2799. FStyleSheetCount:=0;
  2800. end;
  2801. function TCSSResolver.AddStyleSheet(anOrigin: TCSSOrigin; const aName: TCSSString;
  2802. const aSource: TCSSString): TStyleSheet;
  2803. var
  2804. Cnt, i: SizeInt;
  2805. begin
  2806. if aName>'' then
  2807. begin
  2808. i:=IndexOfStyleSheetWithName(anOrigin,aName);
  2809. if i>=0 then
  2810. begin
  2811. ReplaceStyleSheet(i,aSource);
  2812. exit;
  2813. end;
  2814. end;
  2815. Cnt:=length(FStyleSheets);
  2816. if Cnt=FStyleSheetCount then
  2817. begin
  2818. if Cnt<32 then
  2819. Cnt:=32
  2820. else
  2821. Cnt:=Cnt*2;
  2822. SetLength(FStyleSheets,Cnt);
  2823. FillByte(FStyleSheets[FStyleSheetCount],SizeOf(Pointer)*(Cnt-FStyleSheetCount),0);
  2824. end;
  2825. Result:=FStyleSheets[FStyleSheetCount];
  2826. if Result=nil then
  2827. begin
  2828. Result:=TStyleSheet.Create;
  2829. FStyleSheets[FStyleSheetCount]:=Result;
  2830. end;
  2831. inc(FStyleSheetCount);
  2832. with Result do begin
  2833. Name:=aName;
  2834. Origin:=anOrigin;
  2835. Source:=aSource;
  2836. Parsed:=false;
  2837. if Element<>nil then
  2838. FreeAndNil(Element);
  2839. end;
  2840. ParseSource(FStyleSheetCount-1);
  2841. end;
  2842. procedure TCSSResolver.ReplaceStyleSheet(Index: integer; const NewSource: TCSSString);
  2843. var
  2844. Sheet: TStyleSheet;
  2845. begin
  2846. Sheet:=StyleSheets[Index];
  2847. if NewSource=Sheet.Source then exit;
  2848. ClearMerge;
  2849. ClearSharedRuleLists;
  2850. FreeAndNil(Sheet.Element);
  2851. Sheet.Parsed:=false;
  2852. Sheet.Source:=NewSource;
  2853. ParseSource(Index);
  2854. end;
  2855. function TCSSResolver.IndexOfStyleSheetWithElement(El: TCSSElement): integer;
  2856. var
  2857. aParent: TCSSElement;
  2858. i: Integer;
  2859. begin
  2860. Result:=-1;
  2861. if El=nil then exit;
  2862. repeat
  2863. aParent:=El.Parent;
  2864. if aParent=nil then break;
  2865. El:=aParent
  2866. until false;
  2867. for i:=0 to FStyleSheetCount-1 do
  2868. if FStyleSheets[i].Element=El then
  2869. exit(i);
  2870. end;
  2871. function TCSSResolver.IndexOfStyleSheetWithName(anOrigin: TCSSOrigin; const aName: TCSSString
  2872. ): integer;
  2873. var
  2874. Sheet: TStyleSheet;
  2875. begin
  2876. for Result:=0 to FStyleSheetCount-1 do
  2877. begin
  2878. Sheet:=FStyleSheets[Result];
  2879. if (Sheet.Origin=anOrigin) and (aName=Sheet.Name) then
  2880. exit;
  2881. end;
  2882. Result:=-1;
  2883. end;
  2884. function TCSSResolver.FindStyleSheetWithElement(El: TCSSElement): TStyleSheet;
  2885. var
  2886. i: Integer;
  2887. begin
  2888. i:=IndexOfStyleSheetWithElement(El);
  2889. if i>=0 then
  2890. Result:=FStyleSheets[i]
  2891. else
  2892. Result:=nil;
  2893. end;
  2894. end.