fpcssresolver.pas 61 KB

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