fpcssparser.pp 39 KB

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