fpcssparser.pp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764
  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 a CSS parser
  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. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit fpCSSParser;
  13. {$ENDIF FPC_DOTTEDUNITS}
  14. {$mode ObjFPC}{$H+}
  15. {$IF FPC_FULLVERSION>30300}
  16. {$WARN 6060 off} // Case statement does not handle all possible cases
  17. {$WARN 6058 off} // Call to subroutine "$1" marked as inline is not inlined
  18. {$ENDIF}
  19. interface
  20. {$IFDEF FPC_DOTTEDUNITS}
  21. uses
  22. System.TypInfo, System.Classes, System.SysUtils, FPCSS.Tree, FPCSS.Scanner;
  23. {$ELSE FPC_DOTTEDUNITS}
  24. uses
  25. TypInfo, Classes, SysUtils, fpcsstree, fpcssscanner;
  26. {$ENDIF FPC_DOTTEDUNITS}
  27. Type
  28. ECSSParser = Class(ECSSException);
  29. { TCSSParser }
  30. TCSSParser = class(TObject)
  31. private
  32. FInput : TStream;
  33. FScanner: TCSSScanner;
  34. FPrevious : TCSSToken;
  35. FCurrent : TCSSToken;
  36. FCurrentTokenString : TCSSString;
  37. FPeekToken : TCSSToken;
  38. FPeekTokenString : TCSSString;
  39. FFreeScanner : Boolean;
  40. FRuleLevel : Integer;
  41. function GetAtEOF: Boolean;
  42. function GetCurSource: TCSSString;
  43. Function GetCurLine : Integer;
  44. Function GetCurPos : Integer;
  45. protected
  46. function CreateElement(aClass: TCSSElementClass): TCSSElement; virtual;
  47. class function GetAppendElement(aList: TCSSListElement): TCSSElement;
  48. Procedure DoWarn(const Msg : TCSSString); virtual;
  49. Procedure DoWarn(const Fmt : TCSSString; const Args : Array of const);
  50. Procedure DoWarnExpectedButGot(const Expected: string);
  51. Procedure DoError(const Msg : TCSSString); virtual;
  52. Procedure DoError(const Fmt : TCSSString; const Args : Array of const);
  53. Procedure DoErrorExpectedButGot(const Expected: string);
  54. Procedure Consume(aToken : TCSSToken); virtual;
  55. Procedure SkipWhiteSpace;
  56. function ParseComponentValueList(AllowRules: Boolean=True): TCSSElement; virtual;
  57. function ParseComponentValue: TCSSElement; virtual;
  58. function ParseExpression: TCSSElement; virtual;
  59. function ParseRule: TCSSElement; virtual;
  60. function ParseAtUnknownRule: TCSSElement; virtual;
  61. function ParseAtMediaRule: TCSSAtRuleElement; virtual;
  62. function ParseAtSimpleRule: TCSSAtRuleElement; virtual;
  63. function ParseMediaCondition: TCSSElement; virtual;
  64. function ParseRuleList(aStopOn : TCSStoken = ctkEOF): TCSSElement; virtual;
  65. function ParseSelector: TCSSElement; virtual;
  66. function ParseAttributeSelector: TCSSElement; virtual;
  67. function ParseWQName: TCSSElement;
  68. function ParseDeclaration(aIsAt : Boolean = false): TCSSDeclarationElement; virtual;
  69. function ParseCall(aName: TCSSString; IsSelector: boolean): TCSSCallElement; virtual;
  70. procedure ParseSelectorCommaList(aCall: TCSSCallElement); virtual;
  71. procedure ParseRelationalSelectorCommaList(aCall: TCSSCallElement); virtual;
  72. procedure ParseNthChildParams(aCall: TCSSCallElement); virtual;
  73. function ParseUnary: TCSSElement; virtual;
  74. function ParseUnit: TCSSUnit; virtual;
  75. function ParseIdentifier : TCSSIdentifierElement; virtual;
  76. function ParseHashIdentifier : TCSSHashIdentifierElement; virtual;
  77. function ParseClassName : TCSSClassNameElement; virtual;
  78. function ParseParenthesis: TCSSElement; virtual;
  79. function ParsePseudoClass: TCSSElement; virtual;
  80. function ParsePseudoElement: TCSSElement; virtual;
  81. function ParseRuleBody(aRule: TCSSRuleElement; aIsAt : Boolean = False) : integer; virtual;
  82. function ParseInteger: TCSSElement; virtual;
  83. function ParseFloat: TCSSElement; virtual;
  84. function ParseString: TCSSElement; virtual;
  85. function ParseColor: TCSSElement; virtual;
  86. Function ParseUnicodeRange : TCSSElement; virtual;
  87. function ParseArray(aPrefix: TCSSElement): TCSSElement; virtual;
  88. function ParseURL: TCSSElement; virtual;
  89. function ParseInvalidToken: TCSSElement; virtual;
  90. Property CurrentSource : TCSSString Read GetCurSource;
  91. Property CurrentLine : Integer Read GetCurLine;
  92. Property CurrentPos : Integer Read GetCurPos;
  93. Public
  94. CSSArrayElementClass: TCSSArrayElementClass;
  95. CSSAtRuleElementClass: TCSSAtRuleElementClass;
  96. CSSBinaryElementClass: TCSSBinaryElementClass;
  97. CSSCallElementClass: TCSSCallElementClass;
  98. CSSClassNameElementClass: TCSSClassNameElementClass;
  99. CSSCompoundElementClass: TCSSCompoundElementClass;
  100. CSSDeclarationElementClass: TCSSDeclarationElementClass;
  101. CSSFloatElementClass: TCSSFloatElementClass;
  102. CSSHashIdentifierElementClass: TCSSHashIdentifierElementClass;
  103. CSSIdentifierElementClass: TCSSIdentifierElementClass;
  104. CSSIntegerElementClass: TCSSIntegerElementClass;
  105. CSSListElementClass: TCSSListElementClass;
  106. CSSPseudoClassElementClass: TCSSPseudoClassElementClass;
  107. CSSRuleElementClass: TCSSRuleElementClass;
  108. CSSStringElementClass: TCSSStringElementClass;
  109. CSSUnaryElementClass: TCSSUnaryElementClass;
  110. CSSUnicodeRangeElementClass: TCSSUnicodeRangeElementClass;
  111. CSSURLElementClass: TCSSURLElementClass;
  112. Constructor Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []); overload; // AInput is not freed
  113. Constructor Create(AScanner : TCSSScanner); virtual; overload;
  114. Destructor Destroy; override;
  115. Function Parse : TCSSElement;
  116. Function ParseInline : TCSSElement;
  117. Property CurrentToken : TCSSToken Read FCurrent;
  118. Property CurrentTokenString : TCSSString Read FCurrentTokenString;
  119. Function GetNextToken : TCSSToken;
  120. Function PeekNextToken : TCSSToken;
  121. Property Scanner : TCSSScanner Read FScanner;
  122. Property atEOF : Boolean Read GetAtEOF;
  123. end;
  124. Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
  125. Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
  126. Function IsValidCSSAttributeName(const aName: TCSSString): boolean;
  127. implementation
  128. Resourcestring
  129. SBinaryInvalidToken = 'Invalid token for binary operation: %s';
  130. SUnaryInvalidToken = 'Invalid token for unary operation: %s';
  131. SErrFileSource = 'Error: file "%s" line %d, pos %d: ';
  132. SErrSource = 'Error: line %d, pos %d: ';
  133. SErrUnexpectedToken = 'Unexpected token: Got %s (as string: "%s"), expected: %s ';
  134. SErrInvalidFloat = 'Invalid float: %s';
  135. SErrUnexpectedEndOfFile = 'Unexpected EOF while scanning function args: %s';
  136. Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
  137. begin
  138. Case aToken of
  139. ctkEquals : Result:=boEquals;
  140. ctkPlus : Result:=boPlus;
  141. ctkMinus: Result:=boMinus;
  142. ctkAnd : result:=boAnd;
  143. ctkGE : Result:=boGE;
  144. ctkGT : Result:=boGT;
  145. ctkLE : Result:=boLE;
  146. ctkLT : Result:=boLT;
  147. ctkDIV : Result:=boDIV;
  148. ctkStar : Result:=boStar;
  149. ctkSTAREQUAL : Result:=boStarEqual;
  150. ctkTilde : Result:=boTilde;
  151. ctkTILDEEQUAL : Result:=boTildeEqual;
  152. ctkSquared : Result:=boSquared;
  153. ctkSQUAREDEQUAL : Result:=boSquaredEqual;
  154. ctkPIPE : Result:=boPipe;
  155. ctkPIPEEQUAL : Result:=boPipeEqual;
  156. ctkDOLLAR : Result:=boDollar;
  157. ctkDOLLAREQUAL : Result:=boDollarEqual;
  158. ctkColon : Result:=boCOLON;
  159. ctkDoubleColon : Result:=boDoubleColon;
  160. else
  161. Raise ECSSParser.CreateFmt(SBinaryInvalidToken,[GetEnumName(TypeInfo(aToken),Ord(aToken))]);
  162. // Result:=boEquals;
  163. end;
  164. end;
  165. Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
  166. begin
  167. Case aToken of
  168. ctkDOUBLECOLON: Result:=uoDoubleColon;
  169. ctkMinus: Result:=uoMinus;
  170. ctkPlus: Result:=uoPlus;
  171. ctkDiv: Result:=uoDiv;
  172. ctkGT: Result:=uoGT;
  173. ctkTILDE: Result:=uoTilde;
  174. else
  175. Raise ECSSParser.CreateFmt(SUnaryInvalidToken,[GetEnumName(TypeInfo(aToken),Ord(aToken))]);
  176. end;
  177. end;
  178. function IsValidCSSAttributeName(const aName: TCSSString): boolean;
  179. var
  180. p, StartP: PCSSChar;
  181. begin
  182. if aName='' then exit(false);
  183. StartP:=PCSSChar(aName);
  184. p:=StartP;
  185. if p^='-' then
  186. begin
  187. inc(p);
  188. if p^='-' then
  189. inc(p);
  190. if not (p^ in ['A'..'Z','a'..'z']) then
  191. exit;
  192. inc(p);
  193. end;
  194. while p^ in ['A'..'Z','a'..'z','_','-'] do inc(p);
  195. Result:=p=StartP+length(aName);
  196. end;
  197. { TCSSParser }
  198. function TCSSParser.GetAtEOF: Boolean;
  199. begin
  200. Result:=(CurrentToken=ctkEOF);
  201. end;
  202. procedure TCSSParser.DoError(const Msg: TCSSString);
  203. Var
  204. ErrAt : TCSSString;
  205. begin
  206. If Assigned(FScanner) then
  207. If FScanner.CurFilename<>'' then
  208. ErrAt:=SafeFormat(SErrFileSource,[FScanner.CurFileName,FScanner.CurRow,FScanner.CurColumn])
  209. else
  210. ErrAt:=SafeFormat(SErrSource,[FScanner.Currow,FScanner.CurColumn]);
  211. Raise ECSSParser.Create(ErrAt+Msg)
  212. end;
  213. procedure TCSSParser.DoError(const Fmt: TCSSString; const Args: array of const);
  214. begin
  215. DoError(SafeFormat(Fmt,Args));
  216. end;
  217. procedure TCSSParser.DoErrorExpectedButGot(const Expected: string);
  218. begin
  219. DoError(SErrUnexpectedToken ,[
  220. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  221. CurrentTokenString,
  222. Expected
  223. ]);
  224. end;
  225. procedure TCSSParser.Consume(aToken: TCSSToken);
  226. begin
  227. if CurrentToken<>aToken then
  228. DoError(SErrUnexpectedToken ,[
  229. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  230. CurrentTokenString,
  231. GetEnumName(TypeInfo(TCSSToken),Ord(aToken))
  232. ]);
  233. GetNextToken;
  234. end;
  235. procedure TCSSParser.SkipWhiteSpace;
  236. begin
  237. while CurrentToken=ctkWHITESPACE do
  238. GetNextToken;
  239. end;
  240. function TCSSParser.GetCurSource: TCSSString;
  241. begin
  242. If Assigned(FScanner) then
  243. Result:=FScanner.CurFileName
  244. else
  245. Result:='';
  246. end;
  247. function TCSSParser.GetCurLine: Integer;
  248. begin
  249. if Assigned(FScanner) then
  250. Result:=FScanner.CurRow
  251. else
  252. Result:=0;
  253. end;
  254. function TCSSParser.GetCurPos: Integer;
  255. begin
  256. if Assigned(FScanner) then
  257. Result:=FScanner.CurColumn
  258. else
  259. Result:=0;
  260. end;
  261. procedure TCSSParser.DoWarn(const Msg: TCSSString);
  262. begin
  263. if Assigned(Scanner.OnWarn) then
  264. begin
  265. if Scanner.OnWarn(Self,Msg,Scanner.CurRow,Scanner.CurColumn) then
  266. exit;
  267. end;
  268. DoError(Msg);
  269. end;
  270. procedure TCSSParser.DoWarn(const Fmt: TCSSString; const Args: array of const);
  271. begin
  272. DoWarn(SafeFormat(Fmt,Args));
  273. end;
  274. procedure TCSSParser.DoWarnExpectedButGot(const Expected: string);
  275. begin
  276. DoWarn(SErrUnexpectedToken ,[
  277. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  278. CurrentTokenString,
  279. Expected
  280. ]);
  281. end;
  282. constructor TCSSParser.Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []);
  283. begin
  284. FInput:=AInput;
  285. Create(TCSSScanner.Create(FInput));
  286. FScanner.Options:=FScanner.Options+ExtraScannerOptions;
  287. FFreeScanner:=True;
  288. end;
  289. constructor TCSSParser.Create(AScanner: TCSSScanner);
  290. begin
  291. FCurrent:=ctkUNKNOWN;
  292. FPeekToken:=ctkUNKNOWN;
  293. FPeekTokenString:='';
  294. FScanner:=aScanner;
  295. CSSArrayElementClass:=TCSSArrayElement;
  296. CSSAtRuleElementClass:=TCSSAtRuleElement;
  297. CSSBinaryElementClass:=TCSSBinaryElement;
  298. CSSCallElementClass:=TCSSCallElement;
  299. CSSClassNameElementClass:=TCSSClassNameElement;
  300. CSSCompoundElementClass:=TCSSCompoundElement;
  301. CSSDeclarationElementClass:=TCSSDeclarationElement;
  302. CSSFloatElementClass:=TCSSFloatElement;
  303. CSSHashIdentifierElementClass:=TCSSHashIdentifierElement;
  304. CSSIdentifierElementClass:=TCSSIdentifierElement;
  305. CSSIntegerElementClass:=TCSSIntegerElement;
  306. CSSListElementClass:=TCSSListElement;
  307. CSSPseudoClassElementClass:=TCSSPseudoClassElement;
  308. CSSRuleElementClass:=TCSSRuleElement;
  309. CSSStringElementClass:=TCSSStringElement;
  310. CSSUnaryElementClass:=TCSSUnaryElement;
  311. CSSUnicodeRangeElementClass:=TCSSUnicodeRangeElement;
  312. CSSURLElementClass:=TCSSURLElement;
  313. end;
  314. destructor TCSSParser.Destroy;
  315. begin
  316. if FFreeScanner then
  317. FreeAndNil(FScanner);
  318. inherited Destroy;
  319. end;
  320. class function TCSSParser.GetAppendElement(aList: TCSSListElement): TCSSElement;
  321. begin
  322. Case aList.ChildCount of
  323. 0 : Result:=Nil;
  324. 1 : Result:=aList.ExtractElement(0);
  325. else
  326. Result:=aList;
  327. end;
  328. if Result<>aList then
  329. aList.Free;
  330. end;
  331. function TCSSParser.ParseAtUnknownRule: TCSSElement;
  332. // read unknown at-rule
  333. Var
  334. aRule : TCSSRuleElement;
  335. aSel : TCSSElement;
  336. Term : TCSSTokens;
  337. aLast : TCSSToken;
  338. aList : TCSSListElement;
  339. {$ifdef VerboseCSSParser}
  340. aAt : TCSSString;
  341. {$endif}
  342. begin
  343. Inc(FRuleLevel);
  344. {$ifdef VerboseCSSParser}
  345. aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
  346. Writeln('Parse @ rule');
  347. {$endif}
  348. Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
  349. aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
  350. TCSSAtRuleElement(aRule).AtKeyWord:=CurrentTokenString;
  351. GetNextToken;
  352. aList:=nil;
  353. try
  354. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  355. While Not (CurrentToken in Term) do
  356. begin
  357. aSel:=ParseComponentValue;
  358. aList.AddChild(aSel);
  359. if CurrentToken=ctkCOMMA then
  360. begin
  361. Consume(ctkCOMMA);
  362. aRule.AddSelector(GetAppendElement(aList));
  363. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  364. end;
  365. end;
  366. aRule.AddSelector(GetAppendElement(aList));
  367. aList:=nil;
  368. aLast:=CurrentToken;
  369. if (aLast<>ctkSEMICOLON) then
  370. begin
  371. Consume(ctkLBRACE);
  372. aRule.AddChild(ParseRuleList(ctkRBRACE));
  373. Consume(ctkRBRACE);
  374. end;
  375. Result:=aRule;
  376. aRule:=nil;
  377. {$ifdef VerboseCSSParser} Writeln('Done Parse @ rule ',aAt); {$endif}
  378. Inc(FRuleLevel);
  379. finally
  380. aRule.Free;
  381. end;
  382. end;
  383. function TCSSParser.ParseAtMediaRule: TCSSAtRuleElement;
  384. Var
  385. {$ifdef VerboseCSSParser}
  386. aAt : TCSSString;
  387. {$endif}
  388. aRule : TCSSAtRuleElement;
  389. Term : TCSSTokens;
  390. aLast , aToken: TCSSToken;
  391. aList : TCSSListElement;
  392. begin
  393. Inc(FRuleLevel);
  394. {$ifdef VerboseCSSParser}
  395. aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
  396. Writeln('Parse @media rule');
  397. {$endif}
  398. Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
  399. aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
  400. aRule.AtKeyWord:=CurrentTokenString;
  401. GetNextToken;
  402. aList:=nil;
  403. try
  404. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  405. While Not (CurrentToken in Term) do
  406. begin
  407. aToken:=CurrentToken;
  408. // writeln('TCSSParser.ParseAtMediaRule Token=',CurrentToken);
  409. case aToken of
  410. ctkIDENTIFIER:
  411. aList.AddChild(ParseIdentifier);
  412. ctkLPARENTHESIS:
  413. aList.AddChild(ParseMediaCondition);
  414. else
  415. Consume(ctkIDENTIFIER);
  416. end;
  417. if CurrentToken=ctkCOMMA then
  418. begin
  419. Consume(ctkCOMMA);
  420. aRule.AddSelector(GetAppendElement(aList));
  421. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  422. end;
  423. end;
  424. aRule.AddSelector(GetAppendElement(aList));
  425. aList:=nil;
  426. aLast:=CurrentToken;
  427. if (aLast<>ctkSEMICOLON) then
  428. begin
  429. Consume(ctkLBRACE);
  430. aRule.AddChild(ParseRuleList(ctkRBRACE));
  431. Consume(ctkRBRACE);
  432. end;
  433. Result:=aRule;
  434. aRule:=nil;
  435. {$ifdef VerboseCSSParser} Writeln('Done Parse @ rule ',aAt); {$endif}
  436. Inc(FRuleLevel);
  437. finally
  438. aRule.Free;
  439. end;
  440. end;
  441. function TCSSParser.ParseAtSimpleRule: TCSSAtRuleElement;
  442. var
  443. {$ifdef VerboseCSSParser}
  444. aAt : TCSSString;
  445. {$endif}
  446. aRule: TCSSAtRuleElement;
  447. begin
  448. Result:=nil;
  449. Inc(FRuleLevel);
  450. {$ifdef VerboseCSSParser}
  451. aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
  452. Writeln('Parse @font-face rule');
  453. {$endif}
  454. aRule:=TCSSAtRuleElement(CreateElement(CSSAtRuleElementClass));
  455. try
  456. aRule.AtKeyWord:=CurrentTokenString;
  457. GetNextToken;
  458. // read {
  459. repeat
  460. case CurrentToken of
  461. ctkEOF:
  462. DoErrorExpectedButGot('{');
  463. ctkRBRACE, ctkRPARENTHESIS, ctkSEMICOLON:
  464. begin
  465. DoWarnExpectedButGot('{');
  466. Result:=aRule;
  467. aRule:=nil;
  468. exit;
  469. end;
  470. ctkLBRACE:
  471. break;
  472. end;
  473. until false;
  474. GetNextToken;
  475. // read declarations
  476. ParseRuleBody(aRule);
  477. if CurrentToken=ctkRBRACE then
  478. GetNextToken;
  479. Result:=aRule;
  480. aRule:=nil;
  481. {$ifdef VerboseCSSParser} Writeln('Done Parse @ rule ',aAt); {$endif}
  482. Inc(FRuleLevel);
  483. finally
  484. aRule.Free;
  485. end;
  486. end;
  487. function TCSSParser.ParseMediaCondition: TCSSElement;
  488. // for example:
  489. // (color)
  490. // (color: #fff)
  491. // (30em <= width)
  492. // (30em >= width > 20em)
  493. // (not(MediaCondition))
  494. var
  495. El: TCSSElement;
  496. Bin: TCSSBinaryElement;
  497. List: TCSSListElement;
  498. aToken: TCSSToken;
  499. begin
  500. Consume(ctkLPARENTHESIS);
  501. {$IFDEF VerboseCSSParser}
  502. writeln('TCSSParser.ParseMediaCondition START ',CurrentToken);
  503. {$ENDIF}
  504. El:=nil;
  505. Bin:=nil;
  506. List:=nil;
  507. try
  508. case CurrentToken of
  509. ctkIDENTIFIER:
  510. begin
  511. El:=ParseIdentifier;
  512. if TCSSIdentifierElement(El).Value='not' then
  513. begin
  514. // (not(mediacondition))
  515. List:=TCSSListElement(CreateElement(CSSListElementClass));
  516. List.AddChild(El);
  517. El:=nil;
  518. List.AddChild(ParseMediaCondition());
  519. Result:=List;
  520. List:=nil;
  521. exit;
  522. end
  523. else if CurrentToken=ctkCOLON then
  524. begin
  525. // (mediaproperty: value)
  526. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  527. Bin.Left:=El;
  528. El:=nil;
  529. Consume(ctkCOLON);
  530. Bin.Right:=ParseComponentValue;
  531. Consume(ctkRPARENTHESIS);
  532. Result:=Bin;
  533. Bin:=nil;
  534. exit;
  535. end;
  536. end;
  537. ctkSTRING:
  538. El:=ParseString;
  539. ctkINTEGER:
  540. El:=ParseInteger;
  541. ctkFLOAT:
  542. El:=ParseFloat;
  543. else
  544. Consume(ctkIDENTIFIER);
  545. end;
  546. // read binaryoperator operand til bracket close
  547. repeat
  548. aToken:=CurrentToken;
  549. {$IFDEF VerboseCSSResolver}
  550. writeln('TCSSParser.ParseMediaCondition NEXT ',CurrentToken);
  551. {$ENDIF}
  552. case aToken of
  553. ctkRPARENTHESIS:
  554. begin
  555. Result:=El;
  556. GetNextToken;
  557. break;
  558. end;
  559. ctkEQUALS,
  560. ctkGE,ctkGT,ctkLE,ctkLT:
  561. begin
  562. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  563. Bin.Left:=El;
  564. Bin.Operation:=TokenToBinaryOperation(aToken);
  565. GetNextToken;
  566. end;
  567. else
  568. Consume(ctkRPARENTHESIS);
  569. end;
  570. case CurrentToken of
  571. ctkIDENTIFIER:
  572. Bin.Right:=ParseIdentifier;
  573. ctkSTRING:
  574. Bin.Right:=ParseString;
  575. ctkINTEGER:
  576. Bin.Right:=ParseInteger;
  577. ctkFLOAT:
  578. Bin.Right:=ParseFloat;
  579. else
  580. Consume(ctkIDENTIFIER);
  581. end;
  582. El:=Bin;
  583. Bin:=nil;
  584. until false;
  585. finally
  586. List.Free;
  587. Bin.Free;
  588. El.Free;
  589. end;
  590. {$IFDEF VerboseCSSParser}
  591. writeln('TCSSParser.ParseMediaCondition END');
  592. {$ENDIF}
  593. end;
  594. function TCSSParser.ParseExpression: TCSSElement;
  595. Const
  596. RuleTokens =
  597. [ctkIDENTIFIER,ctkCLASSNAME,ctkHASH,ctkINTEGER,
  598. ctkPSEUDO,ctkPSEUDOFUNCTION,
  599. ctkCOLON,ctkDOUBLECOLON,ctkSTAR,ctkTILDE,ctkLBRACKET];
  600. begin
  601. if CurrentToken in RuleTokens then
  602. Result:=ParseRule
  603. else if CurrentToken=ctkATKEYWORD then
  604. case lowercase(CurrentTokenString) of
  605. '@media': Result:=ParseAtMediaRule;
  606. '@font-face',
  607. '@page': Result:=ParseAtSimpleRule;
  608. else
  609. Result:=ParseAtUnknownRule;
  610. end
  611. else
  612. Result:=ParseComponentValueList;
  613. end;
  614. function TCSSParser.ParseRuleList(aStopOn : TCSStoken = ctkEOF): TCSSElement;
  615. Var
  616. aList : TCSSCompoundElement;
  617. aEl : TCSSElement;
  618. Terms : TCSSTokens;
  619. begin
  620. Terms:=[ctkEOF,aStopOn];
  621. aList:=TCSSCompoundElement(CreateElement(CSSCompoundElementClass));
  622. Try
  623. While not (CurrentToken in Terms) do
  624. begin
  625. aEl:=ParseExpression;
  626. aList.AddChild(aEl);
  627. if CurrentToken=ctkSEMICOLON then
  628. Consume(ctkSEMICOLON);
  629. end;
  630. Result:=aList;
  631. aList:=nil;
  632. finally
  633. aList.Free;
  634. end;
  635. end;
  636. function TCSSParser.Parse: TCSSElement;
  637. begin
  638. GetNextToken;
  639. if CurrentToken=ctkLBRACE then
  640. Result:=ParseRule
  641. else
  642. Result:=ParseRuleList;
  643. end;
  644. function TCSSParser.ParseInline: TCSSElement;
  645. var
  646. aRule: TCSSRuleElement;
  647. begin
  648. GetNextToken;
  649. aRule:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
  650. try
  651. ParseRuleBody(aRule);
  652. Result:=aRule;
  653. aRule:=nil;
  654. finally
  655. aRule.Free;
  656. end;
  657. end;
  658. function TCSSParser.GetNextToken: TCSSToken;
  659. begin
  660. FPrevious:=FCurrent;
  661. If (FPeekToken<>ctkUNKNOWN) then
  662. begin
  663. FCurrent:=FPeekToken;
  664. FCurrentTokenString:=FPeekTokenString;
  665. FPeekToken:=ctkUNKNOWN;
  666. FPeekTokenString:='';
  667. end
  668. else
  669. begin
  670. FCurrent:=FScanner.FetchToken;
  671. FCurrentTokenString:=FScanner.CurTokenString;
  672. end;
  673. Result:=FCurrent;
  674. {$ifdef VerboseCSSParser}
  675. Writeln('GetNextToken returns ',
  676. GetEnumName(TypeInfo(TCSSToken),Ord(FCurrent)),
  677. '(String: "',FCurrentTokenString,'")',
  678. ' at (',FScanner.CurRow,',',FScanner.CurColumn,'): ',
  679. FSCanner.CurLine);
  680. {$endif VerboseCSSParser}
  681. end;
  682. function TCSSParser.PeekNextToken: TCSSToken;
  683. begin
  684. If (FPeekToken=ctkUNKNOWN) then
  685. begin
  686. FPeekToken:=FScanner.FetchToken;
  687. FPeekTokenString:=FScanner.CurTokenString;
  688. end;
  689. {$ifdef VerboseCSSParser}Writeln('PeekNextToken : ',GetEnumName(TypeInfo(TCSSToken),Ord(FPeekToken)), ' As TCSSString: ',FPeekTokenString);{$endif VerboseCSSParser}
  690. Result:=FPeekToken;
  691. end;
  692. function TCSSParser.ParseUnit : TCSSUnit;
  693. var
  694. p: PCSSChar;
  695. U: TCSSUnit;
  696. begin
  697. Result:=cuNone;
  698. case CurrentToken of
  699. ctkPERCENTAGE:
  700. begin
  701. Result:=cuPercent;
  702. Consume(CurrentToken);
  703. end;
  704. ctkIDENTIFIER:
  705. begin
  706. p:=PCSSChar(CurrentTokenString);
  707. for U:=Succ(cuNone) to High(TCSSUnit) do
  708. if CompareMem(p,PCSSChar(CSSUnitNames[U]),SizeOf(TCSSChar)*length(CSSUnitNames[U])) then
  709. begin
  710. Result:=U;
  711. Consume(CurrentToken);
  712. break;
  713. end;
  714. end;
  715. ctkWHITESPACE:
  716. Consume(CurrentToken);
  717. end;
  718. end;
  719. function TCSSParser.CreateElement(aClass : TCSSElementClass): TCSSElement;
  720. begin
  721. Result:=aClass.Create(CurrentSource,CurrentLine,CurrentPos);
  722. end;
  723. function TCSSParser.ParseIdentifier: TCSSIdentifierElement;
  724. Var
  725. aValue : TCSSString;
  726. begin
  727. aValue:=CurrentTokenString;
  728. Result:=TCSSIdentifierElement(CreateElement(CSSIdentifierElementClass));
  729. Result.Value:=aValue;
  730. GetNextToken;
  731. end;
  732. function TCSSParser.ParseHashIdentifier: TCSSHashIdentifierElement;
  733. Var
  734. aValue : TCSSString;
  735. begin
  736. aValue:=CurrentTokenString;
  737. system.delete(aValue,1,1);
  738. Result:=TCSSHashIdentifierElement(CreateElement(CSSHashIdentifierElementClass));
  739. Result.Value:=aValue;
  740. GetNextToken;
  741. end;
  742. function TCSSParser.ParseClassName: TCSSClassNameElement;
  743. Var
  744. aValue : TCSSString;
  745. begin
  746. aValue:=CurrentTokenString;
  747. system.delete(aValue,1,1);
  748. Result:=TCSSClassNameElement(CreateElement(CSSClassNameElementClass));
  749. Result.Value:=aValue;
  750. GetNextToken;
  751. end;
  752. function TCSSParser.ParseInteger: TCSSElement;
  753. Var
  754. aCode, aValue : Integer;
  755. aInt : TCSSIntegerElement;
  756. OldReturnWhiteSpace: Boolean;
  757. begin
  758. Val(CurrentTokenString,aValue,aCode);
  759. if aCode<>0 then
  760. begin
  761. DoError(SErrInvalidFloat,[CurrentTokenString]);
  762. GetNextToken;
  763. exit(nil);
  764. end;
  765. aInt:=TCSSIntegerElement(CreateElement(CSSIntegerElementClass));
  766. OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
  767. try
  768. aInt.Value:=aValue;
  769. Scanner.ReturnWhiteSpace:=true;
  770. Consume(ctkINTEGER);
  771. aInt.Units:=ParseUnit;
  772. Result:=aInt;
  773. aInt:=nil;
  774. finally
  775. aInt.Free;
  776. Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
  777. SkipWhiteSpace;
  778. end;
  779. end;
  780. function TCSSParser.ParseFloat: TCSSElement;
  781. Var
  782. aCode : Integer;
  783. aValue : Double;
  784. aFloat : TCSSFloatElement;
  785. OldReturnWhiteSpace: Boolean;
  786. begin
  787. Val(CurrentTokenString,aValue,aCode);
  788. if aCode<>0 then
  789. begin
  790. DoError(SErrInvalidFloat,[CurrentTokenString]);
  791. GetNextToken;
  792. exit(nil);
  793. end;
  794. aFloat:=TCSSFloatElement(CreateElement(CSSFloatElementClass));
  795. OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
  796. try
  797. aFloat.Value:=aValue;
  798. Scanner.ReturnWhiteSpace:=true;
  799. Consume(ctkFloat);
  800. aFloat.Units:=ParseUnit;
  801. if CurrentToken=ctkWHITESPACE then
  802. GetNextToken;
  803. Result:=aFloat;
  804. aFloat:=nil;
  805. finally
  806. Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
  807. aFloat.Free;
  808. end;
  809. end;
  810. function TCSSParser.ParseParenthesis: TCSSElement;
  811. var
  812. aList: TCSSElement;
  813. begin
  814. Consume(ctkLPARENTHESIS);
  815. aList:=ParseComponentValueList;
  816. try
  817. Consume(ctkRPARENTHESIS);
  818. Result:=aList;
  819. aList:=nil;
  820. finally
  821. aList.Free;
  822. end;
  823. end;
  824. function TCSSParser.ParseURL: TCSSElement;
  825. Var
  826. aURL : TCSSURLElement;
  827. begin
  828. aURL:=TCSSURLElement(CreateElement(CSSURLElementClass));
  829. try
  830. aURL.Value:=CurrentTokenString;
  831. if CurrentToken=ctkURL then
  832. Consume(ctkURL)
  833. else
  834. Consume(ctkBADURL);
  835. Result:=aURL;
  836. aURL:=nil;
  837. finally
  838. aURL.Free;
  839. end;
  840. end;
  841. function TCSSParser.ParseInvalidToken: TCSSElement;
  842. begin
  843. Result:=TCSSElement(CreateElement(TCSSElement));
  844. GetNextToken;
  845. end;
  846. function TCSSParser.ParsePseudoClass: TCSSElement;
  847. Var
  848. aPseudo : TCSSPseudoClassElement;
  849. aValue : TCSSString;
  850. begin
  851. aValue:=CurrentTokenString;
  852. aPseudo:=TCSSPseudoClassElement(CreateElement(CSSPseudoClassElementClass));
  853. try
  854. Consume(ctkPseudo);
  855. aPseudo.Value:=aValue;
  856. Result:=aPseudo;
  857. aPseudo:=nil;
  858. finally
  859. aPseudo.Free;
  860. end;
  861. end;
  862. function TCSSParser.ParsePseudoElement: TCSSElement;
  863. begin
  864. if CurrentToken<>ctkDOUBLECOLON then
  865. raise Exception.Create('20250224201230');
  866. GetNextToken;
  867. case CurrentToken of
  868. ctkIDENTIFIER: Result:=ParseIdentifier;
  869. ctkFUNCTION: Result:=ParseCall('',false);
  870. else
  871. DoWarnExpectedButGot('pseudo element name');
  872. Result:=nil;
  873. end;
  874. end;
  875. function TCSSParser.ParseRuleBody(aRule: TCSSRuleElement; aIsAt: Boolean = false): integer;
  876. Var
  877. aDecl : TCSSElement;
  878. begin
  879. aDecl:=nil;
  880. while CurrentToken=ctkUNKNOWN do
  881. GetNextToken;
  882. if not (CurrentToken in [ctkRBRACE,ctkSEMICOLON]) then
  883. begin
  884. aDecl:=ParseDeclaration(aIsAt);
  885. aRule.AddChild(aDecl);
  886. end;
  887. While Not (CurrentToken in [ctkEOF,ctkRBRACE]) do
  888. begin
  889. While CurrentToken=ctkSEMICOLON do
  890. Consume(ctkSEMICOLON);
  891. if Not (CurrentToken in [ctkEOF,ctkRBRACE]) then
  892. begin
  893. if CurrentToken=ctkATKEYWORD then
  894. aDecl:=ParseAtUnknownRule
  895. else
  896. aDecl:=ParseDeclaration(aIsAt);
  897. aRule.AddChild(aDecl);
  898. end;
  899. end;
  900. Result:=aRule.ChildCount;
  901. end;
  902. function TCSSParser.ParseRule: TCSSElement;
  903. Var
  904. aRule : TCSSRuleElement;
  905. aSel : TCSSElement;
  906. Term : TCSSTokens;
  907. aLast : TCSSToken;
  908. aList: TCSSListElement;
  909. {$IFDEF VerboseCSSParser}
  910. aAt : TCSSString;
  911. {$ENDIF}
  912. begin
  913. Inc(FRuleLevel);
  914. {$IFDEF VerboseCSSParser}
  915. aAt:=Format(' Level %d at (%d:%d)',[FRuleLevel,CurrentLine,CurrentPos]);
  916. Writeln('Parse rule.: ',aAt);
  917. {$ENDIF}
  918. case CurrentToken of
  919. ctkEOF: exit(nil);
  920. ctkSEMICOLON:
  921. begin
  922. Result:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
  923. exit;
  924. end;
  925. end;
  926. Term:=[ctkLBRACE,ctkEOF,ctkSEMICOLON];
  927. aRule:=TCSSRuleElement(CreateElement(CSSRuleElementClass));
  928. aList:=nil;
  929. try
  930. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  931. While Not (CurrentToken in Term) do
  932. begin
  933. aSel:=ParseSelector;
  934. aRule.AddSelector(aSel);
  935. if CurrentToken=ctkCOMMA then
  936. begin
  937. Consume(ctkCOMMA);
  938. aRule.AddSelector(GetAppendElement(aList));
  939. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  940. end;
  941. end;
  942. // Note: no selectors is allowed
  943. aRule.AddSelector(GetAppendElement(aList));
  944. aList:=nil;
  945. aLast:=CurrentToken;
  946. if (aLast<>ctkSEMICOLON) then
  947. begin
  948. Consume(ctkLBrace);
  949. ParseRuleBody(aRule);
  950. Consume(ctkRBRACE);
  951. end;
  952. Result:=aRule;
  953. aRule:=nil;
  954. {$IFDEF VerboseCSSParser}
  955. Writeln('Rule started at ',aAt,' done');
  956. {$endif}
  957. Dec(FRuleLevel);
  958. finally
  959. aRule.Free;
  960. aList.Free;
  961. end;
  962. end;
  963. function TCSSParser.ParseUnary: TCSSElement;
  964. var
  965. Un : TCSSUnaryElement;
  966. Op : TCSSUnaryOperation;
  967. El: TCSSElement;
  968. begin
  969. Result:=nil;
  970. if not (CurrentToken in [ctkDOUBLECOLON, ctkMinus, ctkPlus, ctkDiv, ctkGT, ctkTILDE]) then
  971. Raise ECSSParser.CreateFmt(SUnaryInvalidToken,[CurrentTokenString]);
  972. op:=TokenToUnaryOperation(CurrentToken);
  973. Consume(CurrentToken);
  974. if CurrentToken=ctkWHITESPACE then
  975. Raise ECSSParser.CreateFmt(SUnaryInvalidToken,['white space']);
  976. El:=ParseComponentValue;
  977. Un:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
  978. Un.Operation:=op;
  979. Un.Right:=El;
  980. Result:=Un;
  981. end;
  982. function TCSSParser.ParseComponentValueList(AllowRules : Boolean = True): TCSSElement;
  983. Const
  984. TermSeps = [ctkEquals,ctkPlus,ctkMinus,ctkAnd,ctkLT,ctkDIV,
  985. ctkStar,ctkTilde,ctkColon, ctkDoubleColon,
  986. ctkSquared,ctkGT, ctkPIPE, ctkDOLLAR];
  987. ListTerms = [ctkEOF,ctkLBRACE,ctkATKEYWORD,ctkComma];
  988. function DoBinary(var aLeft : TCSSElement) : TCSSElement;
  989. var
  990. Bin : TCSSBinaryElement;
  991. begin
  992. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  993. try
  994. Bin.Left:=ALeft;
  995. aLeft:=Nil;
  996. Bin.Operation:=TokenToBinaryOperation(CurrentToken);
  997. Consume(CurrentToken);
  998. Bin.Right:=ParseComponentValue;
  999. if Bin.Right=nil then
  1000. DoError(SErrUnexpectedToken ,[
  1001. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  1002. CurrentTokenString,
  1003. 'value'
  1004. ]);
  1005. Result:=Bin;
  1006. Bin:=nil;
  1007. finally
  1008. Bin.Free;
  1009. end;
  1010. end;
  1011. Var
  1012. List : TCSSListElement;
  1013. aFactor : TCSSelement;
  1014. begin
  1015. aFactor:=Nil;
  1016. List:=TCSSListElement(CreateElement(CSSListElementClass));
  1017. try
  1018. if AllowRules and (CurrentToken in [ctkLBRACE,ctkATKEYWORD]) then
  1019. begin
  1020. if CurrentToken=ctkATKEYWORD then
  1021. aFactor:=ParseAtUnknownRule
  1022. else
  1023. aFactor:=ParseRule;
  1024. end
  1025. else
  1026. aFactor:=ParseComponentValue;
  1027. if aFactor=nil then
  1028. DoError(SErrUnexpectedToken ,[
  1029. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  1030. CurrentTokenString,
  1031. 'value'
  1032. ]);
  1033. While Assigned(aFactor) do
  1034. begin
  1035. While CurrentToken in TermSeps do
  1036. aFactor:=DoBinary(aFactor);
  1037. List.AddChild(aFactor);
  1038. aFactor:=Nil;
  1039. if not (CurrentToken in ListTerms) then
  1040. aFactor:=ParseComponentValue;
  1041. end;
  1042. Result:=GetAppendElement(List);
  1043. List:=nil;
  1044. finally
  1045. List.Free;
  1046. aFactor.Free;
  1047. end;
  1048. end;
  1049. function TCSSParser.ParseComponentValue: TCSSElement;
  1050. Const
  1051. FinalTokens =
  1052. [ctkLPARENTHESIS,ctkURL,ctkColon,ctkLBRACE, ctkLBRACKET,
  1053. ctkDOUBLECOLON,ctkMinus,ctkPlus,ctkDiv,ctkSTAR,ctkTILDE];
  1054. var
  1055. aToken : TCSSToken;
  1056. begin
  1057. aToken:=CurrentToken;
  1058. if aToken=ctkUNKNOWN then
  1059. begin
  1060. DoError('invalid');
  1061. repeat
  1062. GetNextToken;
  1063. until CurrentToken<>ctkUNKNOWN;
  1064. aToken:=CurrentToken;
  1065. end;
  1066. Case aToken of
  1067. ctkEOF: exit(nil);
  1068. ctkLPARENTHESIS: Result:=ParseParenthesis;
  1069. ctkURL: Result:=ParseURL;
  1070. ctkPSEUDO: Result:=ParsePseudoClass;
  1071. ctkLBRACE: Result:=ParseRule;
  1072. ctkLBRACKET: Result:=ParseArray(Nil);
  1073. ctkMinus,
  1074. ctkPlus,
  1075. ctkDiv,
  1076. ctkGT,
  1077. ctkTilde: Result:=ParseUnary;
  1078. ctkUnicodeRange: Result:=ParseUnicodeRange;
  1079. ctkSTRING: Result:=ParseString;
  1080. ctkHASH: Result:=ParseColor;
  1081. ctkINTEGER: Result:=ParseInteger;
  1082. ctkFloat : Result:=ParseFloat;
  1083. ctkPSEUDOFUNCTION,
  1084. ctkFUNCTION : Result:=ParseCall('',false);
  1085. ctkSTAR: Result:=ParseInvalidToken;
  1086. ctkIDENTIFIER,ctkPERCENTAGE: Result:=ParseIdentifier;
  1087. ctkCLASSNAME : Result:=ParseClassName;
  1088. else
  1089. Result:=nil;
  1090. end;
  1091. if aToken in FinalTokens then
  1092. exit;
  1093. if (CurrentToken=ctkLBRACKET) then
  1094. Result:=ParseArray(Result);
  1095. end;
  1096. function TCSSParser.ParseSelector: TCSSElement;
  1097. function ParseBinaryPseudoElement(var El: TCSSElement): boolean;
  1098. var
  1099. Bin: TCSSBinaryElement;
  1100. begin
  1101. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  1102. Bin.Left:=El;
  1103. El:=Bin;
  1104. Bin.Operation:=boDoubleColon;
  1105. Bin.Right:=ParsePseudoElement;
  1106. Result:=Bin.Right<>nil;
  1107. end;
  1108. function ParseUnaryPseudoElement: TCSSElement;
  1109. var
  1110. Un: TCSSUnaryElement;
  1111. begin
  1112. Un:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
  1113. Result:=Un;
  1114. Un.Operation:=uoDoubleColon;
  1115. Un.Right:=ParsePseudoElement;
  1116. end;
  1117. function ParseSub: TCSSElement;
  1118. begin
  1119. Result:=nil;
  1120. Case CurrentToken of
  1121. ctkSTAR,
  1122. ctkIDENTIFIER : Result:=ParseIdentifier;
  1123. ctkHASH : Result:=ParseHashIdentifier;
  1124. ctkCLASSNAME : Result:=ParseClassName;
  1125. ctkLBRACKET: Result:=ParseAttributeSelector;
  1126. ctkPSEUDO: Result:=ParsePseudoClass;
  1127. ctkPSEUDOFUNCTION: Result:=ParseCall('',true);
  1128. ctkDOUBLECOLON: Result:=ParseUnaryPseudoElement;
  1129. else
  1130. DoWarn(SErrUnexpectedToken ,[
  1131. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  1132. CurrentTokenString,
  1133. 'selector'
  1134. ]);
  1135. case CurrentToken of
  1136. ctkINTEGER: Result:=ParseInteger;
  1137. ctkFLOAT: Result:=ParseFloat;
  1138. else Result:=ParseInvalidToken;
  1139. end;
  1140. end;
  1141. end;
  1142. var
  1143. ok, OldReturnWhiteSpace: Boolean;
  1144. Bin: TCSSBinaryElement;
  1145. El, Sub: TCSSElement;
  1146. List: TCSSListElement;
  1147. begin
  1148. Result:=nil;
  1149. if CurrentToken in [ctkLBRACE,ctkRBRACE,ctkRPARENTHESIS,ctkEOF] then
  1150. exit;
  1151. El:=nil;
  1152. Bin:=nil;
  1153. List:=nil;
  1154. ok:=false;
  1155. //writeln('TCSSParser.ParseSelector START ',CurrentToken);
  1156. OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
  1157. Scanner.ReturnWhiteSpace:=true;
  1158. try
  1159. repeat
  1160. {$IFDEF VerboseCSSParser}
  1161. writeln('TCSSParser.ParseSelector LIST START ',CurrentToken,' ',CurrentTokenString);
  1162. {$ENDIF}
  1163. // read list
  1164. List:=nil;
  1165. El:=ParseSub;
  1166. {$IFDEF VerboseCSSParser}
  1167. writeln('TCSSParser.ParseSelector LIST NEXT ',CurrentToken,' ',CurrentTokenString,' El=',GetCSSObj(El));
  1168. {$ENDIF}
  1169. if El=nil then
  1170. exit;
  1171. while CurrentToken in [ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION] do
  1172. begin
  1173. if List=nil then
  1174. begin
  1175. List:=TCSSListElement(CreateElement(CSSListElementClass));
  1176. List.AddChild(El);
  1177. El:=List;
  1178. end;
  1179. Sub:=ParseSub;
  1180. if Sub=nil then break;
  1181. List.AddChild(Sub);
  1182. end;
  1183. List:=nil;
  1184. // read postfix pseudo elements
  1185. while CurrentToken=ctkDOUBLECOLON do
  1186. if not ParseBinaryPseudoElement(El) then break;
  1187. // use element
  1188. if Bin<>nil then
  1189. Bin.Right:=El
  1190. else
  1191. Result:=El;
  1192. El:=nil;
  1193. SkipWhiteSpace;
  1194. {$IFDEF VerboseCSSParser}
  1195. writeln('TCSSParser.ParseSelector LIST END ',CurrentToken,' ',CurrentTokenString);
  1196. {$ENDIF}
  1197. case CurrentToken of
  1198. ctkLBRACE,ctkRBRACE,ctkRBRACKET,ctkRPARENTHESIS,ctkEOF,ctkSEMICOLON,ctkCOMMA:
  1199. break;
  1200. ctkGT,ctkPLUS,ctkTILDE,ctkPIPE:
  1201. begin
  1202. // combinator
  1203. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  1204. Bin.Left:=Result;
  1205. Result:=Bin;
  1206. Bin.Operation:=TokenToBinaryOperation(CurrentToken);
  1207. GetNextToken;
  1208. SkipWhiteSpace;
  1209. end;
  1210. ctkSTAR,ctkHASH,ctkIDENTIFIER,ctkCLASSNAME,ctkLBRACKET,ctkPSEUDO,ctkPSEUDOFUNCTION:
  1211. begin
  1212. // descendant combinator
  1213. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  1214. Bin.Left:=Result;
  1215. Result:=Bin;
  1216. Bin.Operation:=boWhiteSpace;
  1217. end;
  1218. else
  1219. break;
  1220. end;
  1221. until false;
  1222. ok:=true;
  1223. finally
  1224. Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
  1225. if not ok then
  1226. begin
  1227. if Result=Bin then Bin:=nil;
  1228. if El=List then List:=nil;
  1229. if Result=El then El:=nil;
  1230. Result.Free;
  1231. El.Free;
  1232. List.Free;
  1233. Bin.Free;
  1234. end;
  1235. end;
  1236. SkipWhiteSpace;
  1237. end;
  1238. function TCSSParser.ParseAttributeSelector: TCSSElement;
  1239. Var
  1240. aEl : TCSSElement;
  1241. aArray : TCSSArrayElement;
  1242. Bin: TCSSBinaryElement;
  1243. StrEl: TCSSStringElement;
  1244. aToken: TCSSToken;
  1245. begin
  1246. Result:=Nil;
  1247. aArray:=TCSSArrayElement(CreateElement(CSSArrayElementClass));
  1248. try
  1249. Consume(ctkLBRACKET);
  1250. SkipWhiteSpace;
  1251. aEl:=ParseWQName;
  1252. SkipWhiteSpace;
  1253. aToken:=CurrentToken;
  1254. case aToken of
  1255. ctkEQUALS,ctkTILDEEQUAL,ctkPIPEEQUAL,ctkSQUAREDEQUAL,ctkDOLLAREQUAL,ctkSTAREQUAL:
  1256. begin
  1257. // parse attr-matcher
  1258. Bin:=TCSSBinaryElement(CreateElement(CSSBinaryElementClass));
  1259. aArray.AddChild(Bin);
  1260. Bin.Left:=aEl;
  1261. Bin.Operation:=TokenToBinaryOperation(aToken);
  1262. GetNextToken;
  1263. SkipWhiteSpace;
  1264. // parse value
  1265. case CurrentToken of
  1266. ctkIDENTIFIER:
  1267. Bin.Right:=ParseIdentifier;
  1268. ctkSTRING:
  1269. begin
  1270. StrEl:=TCSSStringElement(CreateElement(CSSStringElementClass));
  1271. StrEl.Value:=CurrentTokenString;
  1272. Bin.Right:=StrEl;
  1273. GetNextToken;
  1274. end;
  1275. ctkINTEGER:
  1276. Bin.Right:=ParseInteger;
  1277. ctkFLOAT:
  1278. Bin.Right:=ParseFloat;
  1279. else
  1280. DoError(SErrUnexpectedToken ,[
  1281. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  1282. CurrentTokenString,
  1283. 'attribute value'
  1284. ]);
  1285. end;
  1286. end;
  1287. else
  1288. aArray.AddChild(aEl);
  1289. end;
  1290. SkipWhiteSpace;
  1291. while CurrentToken=ctkIDENTIFIER do
  1292. begin
  1293. // attribute modifier
  1294. // with CSS 5 there is only i and s, but for future compatibility read all
  1295. aArray.AddChild(ParseIdentifier);
  1296. SkipWhiteSpace;
  1297. end;
  1298. Consume(ctkRBRACKET);
  1299. Result:=aArray;
  1300. aArray:=nil;
  1301. finally
  1302. aArray.Free;
  1303. end;
  1304. end;
  1305. function TCSSParser.ParseWQName: TCSSElement;
  1306. begin
  1307. if CurrentToken<>ctkIDENTIFIER then
  1308. DoError(SErrUnexpectedToken ,[
  1309. GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
  1310. CurrentTokenString,
  1311. 'identifier'
  1312. ]);
  1313. Result:=ParseIdentifier;
  1314. // todo: parse optional ns-prefix
  1315. end;
  1316. function TCSSParser.ParseDeclaration(aIsAt: Boolean = false): TCSSDeclarationElement;
  1317. Var
  1318. aDecl : TCSSDeclarationElement;
  1319. aKey,aValue : TCSSElement;
  1320. aList : TCSSListElement;
  1321. OldOptions: TCSSScannerOptions;
  1322. begin
  1323. aList:=nil;
  1324. OldOptions:=Scanner.Options;
  1325. aDecl:=TCSSDeclarationElement(CreateElement(CSSDeclarationElementClass));
  1326. try
  1327. // read attribute names
  1328. Scanner.DisablePseudo:=True;
  1329. aKey:=ParseComponentValue;
  1330. aDecl.AddKey(aKey);
  1331. if aIsAt then
  1332. begin
  1333. While (CurrentToken=ctkCOMMA) do
  1334. begin
  1335. while (CurrentToken=ctkCOMMA) do
  1336. GetNextToken;
  1337. aKey:=ParseComponentValue;
  1338. aDecl.AddKey(aKey);
  1339. end;
  1340. end;
  1341. if Not aIsAt then
  1342. begin
  1343. aDecl.Colon:=True;
  1344. Consume(ctkCOLON);
  1345. end
  1346. else
  1347. begin
  1348. aDecl.Colon:=CurrentToken=ctkColon;
  1349. if aDecl.Colon then
  1350. Consume(ctkColon)
  1351. end;
  1352. aValue:=ParseComponentValue;
  1353. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  1354. aList.AddChild(aValue);
  1355. if aDecl.Colon then
  1356. begin
  1357. // read attribute value
  1358. // + and - must be enclosed in whitespace, +3 and -4 are values
  1359. While not (CurrentToken in [ctkEOF,ctkSemicolon,ctkRBRACE,ctkImportant]) do
  1360. begin
  1361. While CurrentToken=ctkCOMMA do
  1362. begin
  1363. Consume(ctkCOMMA);
  1364. aDecl.AddChild(GetAppendElement(aList));
  1365. aList:=TCSSListElement(CreateElement(CSSListElementClass));
  1366. end;
  1367. aValue:=ParseComponentValue;
  1368. if aValue=nil then break;
  1369. aList.AddChild(aValue);
  1370. end;
  1371. if CurrentToken=ctkImportant then
  1372. begin
  1373. Consume(ctkImportant);
  1374. aDecl.IsImportant:=True;
  1375. end;
  1376. end;
  1377. aDecl.AddChild(GetAppendElement(aList));
  1378. aList:=nil;
  1379. Result:=aDecl;
  1380. aDecl:=nil;
  1381. finally
  1382. Scanner.Options:=OldOptions;
  1383. aDecl.Free;
  1384. aList.Free;
  1385. end;
  1386. end;
  1387. function TCSSParser.ParseCall(aName: TCSSString; IsSelector: boolean
  1388. ): TCSSCallElement;
  1389. var
  1390. aCall : TCSSCallElement;
  1391. l : Integer;
  1392. aValue: TCSSElement;
  1393. begin
  1394. aCall:=TCSSCallElement(CreateElement(CSSCallElementClass));
  1395. try
  1396. if (aName='') then
  1397. aName:=CurrentTokenString;
  1398. L:=Length(aName);
  1399. if (L>0) and (aName[L]='(') then
  1400. aName:=Copy(aName,1,L-1);
  1401. aCall.Name:=aName;
  1402. if IsSelector and (CurrentToken=ctkPSEUDOFUNCTION) then
  1403. begin
  1404. Consume(ctkPSEUDOFUNCTION);
  1405. SkipWhiteSpace;
  1406. case aName of
  1407. ':not',':is',':where':
  1408. ParseSelectorCommaList(aCall);
  1409. ':has':
  1410. ParseRelationalSelectorCommaList(aCall);
  1411. ':nth-child',':nth-last-child',':nth-of-type',':nth-last-of-type':
  1412. ParseNthChildParams(aCall);
  1413. end;
  1414. end
  1415. else begin
  1416. Consume(ctkFUNCTION);
  1417. end;
  1418. // Call argument list can be empty: mask()
  1419. While not (CurrentToken in [ctkRPARENTHESIS,ctkEOF]) do
  1420. begin
  1421. aValue:=ParseComponentValue;
  1422. if aValue=nil then
  1423. begin
  1424. aValue:=TCSSElement(CreateElement(TCSSElement));
  1425. GetNextToken;
  1426. end;
  1427. aCall.AddArg(aValue);
  1428. if (CurrentToken=ctkCOMMA) then
  1429. GetNextToken;
  1430. end;
  1431. if CurrentToken=ctkEOF then
  1432. DoError(SErrUnexpectedEndOfFile,[aName]);
  1433. Consume(ctkRPARENTHESIS);
  1434. Result:=aCall;
  1435. aCall:=nil;
  1436. finally
  1437. aCall.Free;
  1438. end;
  1439. end;
  1440. procedure TCSSParser.ParseSelectorCommaList(aCall: TCSSCallElement);
  1441. var
  1442. El: TCSSElement;
  1443. begin
  1444. while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
  1445. begin
  1446. El:=ParseSelector;
  1447. if El=nil then exit;
  1448. aCall.AddArg(El);
  1449. if CurrentToken<>ctkCOMMA then
  1450. exit;
  1451. GetNextToken;
  1452. SkipWhiteSpace;
  1453. end;
  1454. end;
  1455. procedure TCSSParser.ParseRelationalSelectorCommaList(aCall: TCSSCallElement);
  1456. var
  1457. El: TCSSElement;
  1458. aToken: TCSSToken;
  1459. IsUnary: Boolean;
  1460. Unary: TCSSUnaryElement;
  1461. begin
  1462. while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
  1463. begin
  1464. IsUnary:=false;
  1465. aToken:=CurrentToken;
  1466. if aToken in [ctkGT,ctkPLUS,ctkTILDE] then
  1467. begin
  1468. IsUnary:=true;
  1469. GetNextToken;
  1470. end;
  1471. El:=ParseSelector;
  1472. if El=nil then exit;
  1473. if IsUnary then
  1474. begin
  1475. Unary:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
  1476. aCall.AddArg(Unary);
  1477. Unary.Right:=El;
  1478. Unary.Operation:=TokenToUnaryOperation(aToken);
  1479. end
  1480. else
  1481. aCall.AddArg(El);
  1482. if CurrentToken<>ctkCOMMA then
  1483. exit;
  1484. GetNextToken;
  1485. end;
  1486. end;
  1487. procedure TCSSParser.ParseNthChildParams(aCall: TCSSCallElement);
  1488. // Examples:
  1489. // odd
  1490. // even
  1491. // n
  1492. // +n
  1493. // -2n
  1494. // 2n+1
  1495. // even of :not(:hidden)
  1496. // 2n+1 of [:not(display=none)]
  1497. var
  1498. aUnary: TCSSUnaryElement;
  1499. IdentEl: TCSSIdentifierElement;
  1500. begin
  1501. case CurrentToken of
  1502. ctkIDENTIFIER:
  1503. case lowercase(CurrentTokenString) of
  1504. 'odd','even','n':
  1505. aCall.AddArg(ParseIdentifier);
  1506. '-n':
  1507. begin
  1508. aUnary:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
  1509. aCall.AddArg(aUnary);
  1510. aUnary.Operation:=uoMinus;
  1511. IdentEl:=TCSSIdentifierElement(CreateElement(CSSIdentifierElementClass));
  1512. aUnary.Right:=IdentEl;
  1513. IdentEl.Value:='n';
  1514. GetNextToken;
  1515. end;
  1516. else
  1517. DoWarnExpectedButGot('An+B');
  1518. aCall.AddArg(ParseIdentifier);
  1519. exit;
  1520. end;
  1521. ctkINTEGER:
  1522. begin
  1523. aCall.AddArg(ParseInteger);
  1524. if (CurrentToken<>ctkIDENTIFIER) then
  1525. begin
  1526. DoWarnExpectedButGot('An+B');
  1527. exit;
  1528. end;
  1529. if (lowercase(CurrentTokenString)<>'n') then
  1530. begin
  1531. DoWarnExpectedButGot('An+B');
  1532. exit;
  1533. end;
  1534. aCall.AddArg(ParseIdentifier);
  1535. end;
  1536. else
  1537. DoWarnExpectedButGot('An+B');
  1538. exit;
  1539. end;
  1540. if CurrentToken in [ctkMINUS,ctkPLUS] then
  1541. aCall.AddArg(ParseUnary);
  1542. if (CurrentToken=ctkIDENTIFIER) and SameText(CurrentTokenString,'of') then
  1543. begin
  1544. aCall.AddArg(ParseIdentifier);
  1545. SkipWhiteSpace;
  1546. aCall.AddArg(ParseSelector);
  1547. SkipWhiteSpace;
  1548. end;
  1549. end;
  1550. function TCSSParser.ParseString: TCSSElement;
  1551. var
  1552. aStr: TCSSStringElement;
  1553. aValue: TCSSString;
  1554. begin
  1555. aValue:=CurrentTokenString;
  1556. aStr:=TCSSStringElement(CreateElement(CSSStringElementClass));
  1557. try
  1558. aStr.Value:=aValue;
  1559. Consume(ctkSTRING);
  1560. Result:=aStr;
  1561. aStr:=nil;
  1562. finally
  1563. aStr.Free;
  1564. end;
  1565. end;
  1566. function TCSSParser.ParseColor: TCSSElement;
  1567. var
  1568. aStr: TCSSStringElement;
  1569. aValue: TCSSString;
  1570. begin
  1571. aValue:=CurrentTokenString;
  1572. aStr:=TCSSStringElement(CreateElement(CSSStringElementClass));
  1573. try
  1574. aStr.Value:=aValue;
  1575. Consume(ctkHASH); // e.g. #rrggbb
  1576. Result:=aStr;
  1577. aStr:=nil;
  1578. finally
  1579. aStr.Free;
  1580. end;
  1581. end;
  1582. function TCSSParser.ParseUnicodeRange: TCSSElement;
  1583. Var
  1584. aValue : TCSSString;
  1585. aRange : TCSSUnicodeRangeElement;
  1586. begin
  1587. aValue:=CurrentTokenString;
  1588. aRange:=TCSSUnicodeRangeElement(CreateElement(CSSUnicodeRangeElementClass));
  1589. try
  1590. Consume(ctkUnicodeRange);
  1591. aRange.Value:=aValue;
  1592. Result:=aRange;
  1593. aRange:=nil;
  1594. finally
  1595. aRange.Free;
  1596. end;
  1597. end;
  1598. function TCSSParser.ParseArray(aPrefix: TCSSElement): TCSSElement;
  1599. Var
  1600. aEl : TCSSElement;
  1601. aArray : TCSSArrayElement;
  1602. begin
  1603. Result:=Nil;
  1604. aArray:=TCSSArrayElement(CreateElement(CSSArrayElementClass));
  1605. try
  1606. aArray.Prefix:=aPrefix;
  1607. Consume(ctkLBRACKET);
  1608. While CurrentToken<>ctkRBRACKET do
  1609. begin
  1610. aEl:=ParseComponentValueList;
  1611. aArray.AddChild(aEl);
  1612. end;
  1613. Consume(ctkRBRACKET);
  1614. Result:=aArray;
  1615. aArray:=nil;
  1616. finally
  1617. aArray.Free;
  1618. end;
  1619. end;
  1620. end.