Recent

Author Topic: CSS parser?  (Read 2140 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1800
CSS parser?
« on: December 26, 2025, 05:07:14 am »
Hello,

I have a fcl-web-server application. The client side is controlled by HTML, js, css.
I'd like to make a Lazarus app that can custom-edit each of css, like editing .ini files or as if editing via ValueListEditor.
So I'm looking css parser, and found Fresnel from Google search, and Free Pascal wiki page on Fresnel.

For now, what would be the most feasible approach?
« Last Edit: January 04, 2026, 06:30:02 am by egsuh »

Thaddy

  • Hero Member
  • *****
  • Posts: 19275
  • Glad to be alive.
Re: CSS editor?
« Reply #1 on: December 26, 2025, 07:44:58 am »
Visual Studio Code? (One of the default supported formats is the css family)
Since VSC is cross-platform and a good Freepascal plugin is available that combination is a great way to write server applications with fcl-web.
« Last Edit: December 26, 2025, 07:50:49 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: CSS editor?
« Reply #2 on: December 26, 2025, 07:46:54 am »
Quote
Visual Studio Code? (One of the default supported formats is the css family)

No. Kind of editor runnable within Lazarus application.

Thaddy

  • Hero Member
  • *****
  • Posts: 19275
  • Glad to be alive.
Re: CSS editor?
« Reply #3 on: December 26, 2025, 07:52:57 am »
I do not use Lazarus for server apps, but you can have both editors open.
objects are fine constructs. You can even initialize them with constructors.

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: CSS editor?
« Reply #4 on: December 26, 2025, 11:13:44 am »
Hi
'Fresnel' is MvC's new take on a LCL, that bases its widgets' appearance on CSS... It's a work in progress, but looking good so far  8)
It's somewhere on GitLab and I do believe you can run it side by side with the old LCL... 8-)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: CSS parser?
« Reply #5 on: January 04, 2026, 06:37:49 am »
Sorry. I should have explained more.

I do not want to apply CSS to my Lazarus forms / apps. I'd like my web server program to create css file/texts, based on users' choices, like background color, text color, text font, etc. of HTML content.

I'd like to make customization form (i.e. user interface which enables users to select colors, fonts, etc.) in Lazarus, and then my webserver will output the definitions into css file (or text), in the same way as HTML content is returned by Lazarus web server.

If I allow only fixed elements (like default text font, color, etc.), then this is not a difficult issue. I'm searching for ways to read in css files created by human beings, parse them ant let users to customize them. So, what I need is not css editor, but css parser.


Thaddy

  • Hero Member
  • *****
  • Posts: 19275
  • Glad to be alive.
Re: CSS parser?
« Reply #6 on: January 04, 2026, 08:07:48 am »
FresNel contains a css parser. You can lift it out.
objects are fine constructs. You can even initialize them with constructors.

paweld

  • Hero Member
  • *****
  • Posts: 1644
Re: CSS parser?
« Reply #7 on: January 04, 2026, 01:21:13 pm »
@egsuh: something like this:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ValEdit, EditBtn;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     FileNameEdit1: TFileNameEdit;
  17.     Label1: TLabel;
  18.     Label2: TLabel;
  19.     ListBox1: TListBox;
  20.     ValueListEditor1: TValueListEditor;
  21.     procedure Button1Click(Sender: TObject);
  22.     procedure FormCreate(Sender: TObject);
  23.     procedure ListBox1Click(Sender: TObject);
  24.   private
  25.     procedure LoadCSSClasses(aFileName: String);
  26.     procedure LoadCSSClassProperties(aClassName, aFileName: String);
  27.   public
  28.  
  29.   end;
  30.  
  31. var
  32.   Form1: TForm1;
  33.  
  34. implementation
  35.  
  36. {$R *.lfm}
  37.  
  38. uses
  39.   fgl, fpcssparser, fpcssscanner, fpcsstree;
  40.  
  41. type
  42.   TKeyValList = specialize TFPGMap<String, String>;
  43.  
  44.   { TClassVisitor }
  45.  
  46.   TClassVisitor = Class(TCSSTreeVisitor)
  47.   private
  48.     FList: TStrings;
  49.   public
  50.     constructor Create(aList: TStrings);
  51.     procedure Visit(csse: TCSSElement); override;
  52.   end;
  53.  
  54.   { TPropertiesVisitor }
  55.  
  56.   TPropertiesVisitor = Class(TCSSTreeVisitor)
  57.   private
  58.     FList: TKeyValList;
  59.     FClassName: String;
  60.     FSubClass: String;
  61.     FInClass: Boolean;
  62.     FInSubClass: Boolean;
  63.     FCurrClass: String;
  64.     FCurrSubclass: String;
  65.   public
  66.     constructor Create(aList: TKeyValList; aClassName: String);
  67.     procedure Visit(csse: TCSSElement); override;
  68.   end;
  69.  
  70. { TClassVisitor }
  71.  
  72. constructor TClassVisitor.Create(aList: TStrings);
  73. begin
  74.   FList := aList;
  75. end;
  76.  
  77. procedure TClassVisitor.Visit(csse: TCSSElement);
  78. begin
  79.   if csse.CSSType = csstClassname then
  80.     FList.Add(csse.AsString);
  81.   if csse.CSSType = csstPseudoClass then
  82.     FList[FList.Count - 1] := FList[FList.Count - 1] + csse.AsString;
  83. end;
  84.  
  85. { TPropertiesVisitor }
  86.  
  87. constructor TPropertiesVisitor.Create(aList: TKeyValList; aClassName: String);
  88. var
  89.   carr: TStringArray;
  90. begin
  91.   FClassName := '';
  92.   FSubClass := '';
  93.   FList := aList;
  94.   carr := aClassName.Split([':'], TStringSplitOptions.ExcludeEmpty);
  95.   if Length(carr) > 0 then
  96.     FClassName := carr[0];
  97.   if Length(carr) > 1 then
  98.     FSubClass := carr[1];
  99.   FInClass := False;
  100.   FInSubClass := False;
  101.   FCurrClass := '';
  102.   FCurrSubclass := '';
  103. end;
  104.  
  105. procedure TPropertiesVisitor.Visit(csse: TCSSElement);
  106. begin
  107.   if csse.CSSType = csstClassname then
  108.   begin
  109.     FCurrClass := csse.AsString;
  110.     FInClass := FCurrClass = FClassName;
  111.     if FSubClass = '' then
  112.       FInSubClass := True
  113.     else
  114.       FInSubClass := False;
  115.   end;
  116.   if FInClass and (csse.CSSType = csstPseudoClass) then
  117.   begin
  118.     FCurrSubClass := csse.AsString;
  119.     FInSubClass := FCurrSubclass = FSubClass;
  120.   end;
  121.   if FInClass and FInSubClass and (csse.CSSType = csstIdentifier) and ((csse.AsString + ' :') = Copy(csse.Parent.AsString, 1, Length((csse.AsString + ' :')))) then
  122.     FList.Add(csse.AsString)
  123.   else if FInClass and FInSubClass and ((csse.CSSType = csstIdentifier) or (csse.CSSType = csstIdentifier) or (csse.CSSType = csstInteger) or (csse.CSSType = csstString) or (csse.CSSType = csstFloat)) and
  124.     (csse.Parent.AsString <> FClassName) then
  125.     FList.Data[FList.Count - 1] := Trim(FList.Data[FList.Count - 1]) + ' ' + csse.AsFormattedString;
  126. end;
  127.  
  128. { TForm1 }
  129.  
  130. procedure TForm1.Button1Click(Sender: TObject);
  131. begin
  132.   if not FileExists(FileNameEdit1.FileName) then
  133.   begin
  134.     ShowMessage('Select correct file!');
  135.     exit;
  136.   end;
  137.   LoadCSSClasses(FileNameEdit1.FileName);
  138. end;
  139.  
  140. procedure TForm1.FormCreate(Sender: TObject);
  141. begin
  142.   FileNameEdit1.DefaultExt := '.css';
  143.   FileNameEdit1.Filter := 'CSS file|*.css';
  144. end;
  145.  
  146. procedure TForm1.ListBox1Click(Sender: TObject);
  147. begin
  148.   LoadCSSClassProperties(ListBox1.Items[ListBox1.ItemIndex], FileNameEdit1.FileName);
  149. end;
  150.  
  151. procedure TForm1.LoadCSSClasses(aFileName: String);
  152. var
  153.   fs: TFileStream;
  154.   cssp : TCSSParser;
  155.   csse : TCSSElement;
  156.   cv: TClassVisitor;
  157.   sl: TStringList;
  158.   i: Integer;
  159. begin
  160.   ListBox1.Items.BeginUpdate;
  161.   ListBox1.Items.Clear;
  162.   sl := TStringList.Create;
  163.   fs := TFileStream.Create(aFileName, fmOpenRead);
  164.   cssp := TCSSParser.Create(fs, []);
  165.   try
  166.     csse := cssp.Parse;
  167.     cv := TClassVisitor.Create(sl);
  168.     try
  169.       csse.Iterate(cv);
  170.     finally
  171.       cv.Free;
  172.     end;
  173.     for i := 0 to sl.Count - 1 do
  174.       ListBox1.Items.Add(sl[i]);
  175.   finally
  176.     csse.Free;
  177.     cssp.Free;
  178.     fs.Free;
  179.     sl.Free;
  180.     ListBox1.Items.EndUpdate;
  181.   end;
  182. end;
  183.  
  184. procedure TForm1.LoadCSSClassProperties(aClassName, aFileName: String);
  185. var
  186.   fs: TFileStream;
  187.   cssp : TCSSParser;
  188.   csse : TCSSElement;
  189.   pv: TPropertiesVisitor;
  190.   kvl: TKeyValList;
  191.   i: Integer;
  192. begin
  193.   ValueListEditor1.BeginUpdate;
  194.   ValueListEditor1.RowCount := 1;
  195.   kvl := TKeyValList.Create;
  196.   fs := TFileStream.Create(aFileName, fmOpenRead);
  197.   cssp := TCSSParser.Create(fs, []);
  198.   try
  199.     csse := cssp.Parse;
  200.     pv := TPropertiesVisitor.Create(kvl, aClassName);
  201.     try
  202.       csse.Iterate(pv);
  203.     finally
  204.       pv.Free;
  205.     end;
  206.     ValueListEditor1.RowCount := kvl.Count + 1;
  207.     for i := 0 to kvl.Count - 1 do
  208.     begin
  209.       ValueListEditor1.Keys[i + 1] := kvl.Keys[i];
  210.       ValueListEditor1.Values[kvl.Keys[i]] := kvl.Data[i];
  211.     end;
  212.   finally
  213.     csse.Free;
  214.     cssp.Free;
  215.     fs.Free;
  216.     kvl.Free;
  217.     ValueListEditor1.EndUpdate;
  218.   end;
  219. end;
  220.  
  221. end.
  222.  

All you need to do is place the following files in the project directory: https://gitlab.com/freepascal.org/fpc/source/-/tree/main/packages/fcl-css/src?ref_type=heads
Best regards / Pozdrawiam
paweld

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12905
  • FPC developer.
Re: CSS parser?
« Reply #8 on: January 04, 2026, 11:35:47 pm »
There is a package fcl-css in trunk/main, but it afaik undocumented

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: CSS parser?
« Reply #9 on: January 05, 2026, 05:22:16 am »
@paweld:

Thank you for you advice. I tried your program. Your example seems to support css for HTML classes, not for tags or ids. Anyway I'll look into the fcl-css.

Thaddy

  • Hero Member
  • *****
  • Posts: 19275
  • Glad to be alive.
Re: CSS parser?
« Reply #10 on: January 05, 2026, 06:58:36 am »
Paweld just happened to use classes. The fcl-css supports full css. Examine the scanner and the tree.
There are two more examples in /packages/fcl-css/examples
objects are fine constructs. You can even initialize them with constructors.

paweld

  • Hero Member
  • *****
  • Posts: 1644
Re: CSS parser?
« Reply #11 on: January 05, 2026, 07:20:36 am »
I think this can be done using fcl-css. Below is a modified procedure for retrieving classes, IDs, and tags. You just need to expand the procedure that retrieves properties.
Code: Pascal  [Select][+][-]
  1. uses
  2.   fgl, fpcssparser, fpcssscanner, fpcsstree;
  3.  
  4. type
  5.   TKeyValList = specialize TFPGMap<String, String>;
  6.  
  7.   { TClassVisitor }
  8.  
  9.   TClassVisitor = Class(TCSSTreeVisitor)
  10.   private
  11.     FList: TKeyValList;
  12.     FInRule: Boolean;
  13.   public
  14.     constructor Create(aList: TKeyValList);
  15.     procedure Visit(csse: TCSSElement); override;
  16.   end;
  17.  
  18.   { TPropertiesVisitor }
  19.  
  20.   //...
  21.  
  22. { TClassVisitor }
  23.  
  24. constructor TClassVisitor.Create(aList: TKeyValList);
  25. begin
  26.   FList := aList;
  27.   FInRule := False;
  28. end;
  29.  
  30. procedure TClassVisitor.Visit(csse: TCSSElement);
  31. begin
  32.   if csse.CSSType = csstRule then
  33.     FInRule := True
  34.   else if FInRule and (csse.CSSType = csstDeclaration) then
  35.     FInRule := False;
  36.   if FInRule then
  37.   begin
  38.     if csse.CSSType = csstClassname then
  39.       FList.Add(csse.AsString, 'class');
  40.     if csse.CSSType = csstPseudoClass then
  41.       FList.Keys[FList.Count - 1] := FList.Keys[FList.Count - 1] + csse.AsString;
  42.     if csse.CSSType = csstHashIdentifier then
  43.       FList.Add(csse.AsString, 'id');
  44.     if csse.CSSType = csstIdentifier then
  45.       FList.Add(csse.AsString, 'tag');
  46.   end;
  47. end;
  48.  
  49. { TPropertiesVisitor }
  50.  
  51. //...
  52.  
  53. { TForm1 }
  54.  
  55. //...
  56.  
  57. procedure TForm1.LoadCSSClasses(aFileName: String);
  58. var
  59.   fs: TFileStream;
  60.   cssp : TCSSParser;
  61.   csse : TCSSElement;
  62.   cv: TClassVisitor;
  63.   sl: TKeyValList;
  64.   i: Integer;
  65. begin
  66.   Memo1.Lines.Clear;
  67.   ListBox1.Items.BeginUpdate;
  68.   ListBox1.Items.Clear;
  69.   sl := TKeyValList.Create;
  70.   fs := TFileStream.Create(aFileName, fmOpenRead);
  71.   cssp := TCSSParser.Create(fs, []);
  72.   try
  73.     csse := cssp.Parse;
  74.     cv := TClassVisitor.Create(sl);
  75.     try
  76.       csse.Iterate(cv);
  77.     finally
  78.       cv.Free;
  79.     end;
  80.     for i := 0 to sl.Count - 1 do
  81.       ListBox1.Items.Add(sl.Keys[i] + ' [ ' + sl.Data[i] +' ]');
  82.   finally
  83.     csse.Free;
  84.     cssp.Free;
  85.     fs.Free;
  86.     sl.Free;
  87.     ListBox1.Items.EndUpdate;
  88.   end;
  89. end;
  90.  
Best regards / Pozdrawiam
paweld

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: CSS parser?
« Reply #12 on: January 05, 2026, 09:14:28 am »
Hi,

Really thank you for your examples. In this case, when I click any item in the ListBox, nothing is displayed in the ValueListEditor.  I'm looking into the codes but haven't found the solution yet. --- you don't have to hurry.

Regarding parsing itself, I make my comments, which might be helpful in developing as AFAIK this is rather early stage.

- Firstly, @import, or :root parts are not processed.

Code: CSS  [Select][+][-]
  1.         @charset "utf-8";
  2.         @import url('https://fonts.googleapis.com/css?family=Noto+Serif+KR');
  3.  
  4.         :root {
  5.                 --bkColor: white;
  6.                 --errColor: red;
  7.         }  

Secondly, "nested" definitions do not seem to be processed correctly. I tried following definitions.

Code: CSS  [Select][+][-]
  1.         input[type=checkbox]    {      transform : scale(1.5);    }
  2.  
  3.         li.QT2, li.QT3 {  
  4.                 text-indent: -1.5em;
  5.                 text-align: left;
  6.                 list-style-type:none;
  7.         }        

The first one returns three tags --- input, checkbox, and type.

Second example returns separate tags & classes --- li, .qt2, li, and .qt3.

Hope this might be helpful. 

paweld

  • Hero Member
  • *****
  • Posts: 1644
Re: CSS parser?
« Reply #13 on: January 05, 2026, 12:41:54 pm »
The modified design is attached. I added support for :root and identifiers beginning with @.
There are certainly a few more things to handle, such as nested definitions.
Best regards / Pozdrawiam
paweld

Thaddy

  • Hero Member
  • *****
  • Posts: 19275
  • Glad to be alive.
Re: CSS parser?
« Reply #14 on: January 05, 2026, 01:13:54 pm »
Tnx, Pawel!

I might add that opening a new connection to obtain external resources is of course a security risk and does not need fixing as far as I am concerned.
Code: CSS  [Select][+][-]
  1. @import url('https://fonts.googleapis.com/css?family=Noto+Serif+KR');
Really?
Then again, in private I use that too, but not for work.
« Last Edit: January 05, 2026, 01:18:14 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

 

TinyPortal © 2005-2018