fpcssparser.pp 40 KB

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