CSS parser? lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- egsuh · 2025-12-26 04:07 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? ---------------------------------------------------------------------- Thaddy · 2025-12-26 06:44 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. ---------------------------------------------------------------------- egsuh · 2025-12-26 06:46 QuoteVisual Studio Code? (One of the default supported formats is the css family) No. Kind of editor runnable within Lazarus application. ---------------------------------------------------------------------- Thaddy · 2025-12-26 06:52 I do not use Lazarus for server apps, but you can have both editors open. ---------------------------------------------------------------------- cdbc · 2025-12-26 10:13 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  It's somewhere on GitLab and I do believe you can run it side by side with the old LCL... Regards Benny ---------------------------------------------------------------------- egsuh · 2026-01-04 05:37 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 · 2026-01-04 07:07 FresNel contains a css parser. You can lift it out. ---------------------------------------------------------------------- paweld · 2026-01-04 12:21 @egsuh: something like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ValEdit, EditBtn; type   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    FileNameEdit1: TFileNameEdit;    Label1: TLabel;    Label2: TLabel;  ListBox1: TListBox;    ValueListEditor1: TValueListEditor;    procedure Button1Click(Sender: TObject);    procedure FormCreate(Sender: TObject);    procedure ListBox1Click(Sender: TObject);  private    procedure LoadCSSClasses(aFileName: String);    procedure LoadCSSClassProperties(aClassName, aFileName: String);  public   end; var  Form1: TForm1; implementation {$R *.lfm} uses  fgl, fpcssparser, fpcssscanner, fpcsstree; type  TKeyValList = specialize TFPGMap;   { TClassVisitor }   TClassVisitor = Class(TCSSTreeVisitor)  private    FList: TStrings;  public    constructor Create(aList: TStrings);    procedure Visit(csse: TCSSElement); override;  end;   { TPropertiesVisitor }   TPropertiesVisitor = Class(TCSSTreeVisitor)  private    FList: TKeyValList;    FClassName: String;    FSubClass: String;    FInClass: Boolean;    FInSubClass: Boolean;    FCurrClass: String;    FCurrSubclass: String;  public    constructor Create(aList: TKeyValList; aClassName: String);    procedure Visit(csse: TCSSElement); override;  end; { TClassVisitor } constructor TClassVisitor.Create(aList: TStrings);begin  FList := aList;end; procedure TClassVisitor.Visit(csse: TCSSElement);begin  if csse.CSSType = csstClassname then    FList.Add(csse.AsString);  if csse.CSSType = csstPseudoClass then    FList[FList.Count - 1] := FList[FList.Count - 1] + csse.AsString;end; { TPropertiesVisitor } constructor TPropertiesVisitor.Create(aList: TKeyValList; aClassName: String);var  carr: TStringArray;begin  FClassName := '';  FSubClass := '';  FList := aList;  carr := aClassName.Split([':'], TStringSplitOptions.ExcludeEmpty);  if Length(carr) > 0 then    FClassName := carr[0];  if Length(carr) > 1 then    FSubClass := carr[1];  FInClass := False;  FInSubClass := False;  FCurrClass := '';  FCurrSubclass := '';end; procedure TPropertiesVisitor.Visit(csse: TCSSElement);begin  if csse.CSSType = csstClassname then  begin    FCurrClass := csse.AsString;    FInClass := FCurrClass = FClassName;  if FSubClass = '' then      FInSubClass := True    else      FInSubClass := False;  end;  if FInClass and (csse.CSSType = csstPseudoClass) then  begin    FCurrSubClass := csse.AsString;    FInSubClass := FCurrSubclass = FSubClass;  end;  if FInClass and FInSubClass and (csse.CSSType = csstIdentifier) and ((csse.AsString + ' :') = Copy(csse.Parent.AsString, 1, Length((csse.AsString + ' :')))) then    FList.Add(csse.AsString)  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    (csse.Parent.AsString <> FClassName) then    FList.Data[FList.Count - 1] := Trim(FList.Data[FList.Count - 1]) + ' ' + csse.AsFormattedString;end; { TForm1 } procedure TForm1.Button1Click(Sender: TObject);begin  if not FileExists(FileNameEdit1.FileName) then  begin    ShowMessage('Select correct file!');    exit;  end;  LoadCSSClasses(FileNameEdit1.FileName);end; procedure TForm1.FormCreate(Sender: TObject);begin  FileNameEdit1.DefaultExt := '.css';  FileNameEdit1.Filter := 'CSS file|*.css';end; procedure TForm1.ListBox1Click(Sender: TObject);begin  LoadCSSClassProperties(ListBox1.Items[ListBox1.ItemIndex], FileNameEdit1.FileName);end; procedure TForm1.LoadCSSClasses(aFileName: String);var  fs: TFileStream;  cssp : TCSSParser;  csse : TCSSElement;  cv: TClassVisitor;  sl: TStringList;  i: Integer;begin  ListBox1.Items.BeginUpdate;  ListBox1.Items.Clear;  sl := TStringList.Create;  fs := TFileStream.Create(aFileName, fmOpenRead);  cssp := TCSSParser.Create(fs, []);  try    csse := cssp.Parse;    cv := TClassVisitor.Create(sl);    try      csse.Iterate(cv);    finally    cv.Free;    end;    for i := 0 to sl.Count - 1 do      ListBox1.Items.Add(sl[i]);  finally    csse.Free;    cssp.Free;    fs.Free;    sl.Free;    ListBox1.Items.EndUpdate;  end;end; procedure TForm1.LoadCSSClassProperties(aClassName, aFileName: String);var  fs: TFileStream;  cssp : TCSSParser;  csse : TCSSElement;  pv: TPropertiesVisitor;  kvl: TKeyValList;  i: Integer;begin  ValueListEditor1.BeginUpdate;  ValueListEditor1.RowCount := 1;  kvl := TKeyValList.Create;  fs := TFileStream.Create(aFileName, fmOpenRead);  cssp := TCSSParser.Create(fs, []);  try    csse := cssp.Parse;    pv := TPropertiesVisitor.Create(kvl, aClassName);    try      csse.Iterate(pv);    finally      pv.Free;    end;    ValueListEditor1.RowCount := kvl.Count + 1;    for i := 0 to kvl.Count - 1 do    begin      ValueListEditor1.Keys[i + 1] := kvl.Keys[i];    ValueListEditor1.Values[kvl.Keys[i]] := kvl.Data[i];    end;  finally    csse.Free;    cssp.Free;    fs.Free;    kvl.Free;    ValueListEditor1.EndUpdate;  end;end; end.  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 ---------------------------------------------------------------------- marcov · 2026-01-04 22:35 There is a package fcl-css in trunk/main, but it afaik undocumented ---------------------------------------------------------------------- egsuh · 2026-01-05 04:22 @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 · 2026-01-05 05:58 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 ---------------------------------------------------------------------- paweld · 2026-01-05 06:20 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  fgl, fpcssparser, fpcssscanner, fpcsstree; type  TKeyValList = specialize TFPGMap;   { TClassVisitor }   TClassVisitor = Class(TCSSTreeVisitor)  private    FList: TKeyValList;    FInRule: Boolean;  public    constructor Create(aList: TKeyValList);    procedure Visit(csse: TCSSElement); override;  end;   { TPropertiesVisitor }   //... { TClassVisitor } constructor TClassVisitor.Create(aList: TKeyValList);begin  FList := aList;  FInRule := False;end; procedure TClassVisitor.Visit(csse: TCSSElement);begin  if csse.CSSType = csstRule then    FInRule := True  else if FInRule and (csse.CSSType = csstDeclaration) then    FInRule := False;  if FInRule then  begin    if csse.CSSType = csstClassname then      FList.Add(csse.AsString, 'class');    if csse.CSSType = csstPseudoClass then      FList.Keys[FList.Count - 1] := FList.Keys[FList.Count - 1] + csse.AsString;    if csse.CSSType = csstHashIdentifier then      FList.Add(csse.AsString, 'id');    if csse.CSSType = csstIdentifier then      FList.Add(csse.AsString, 'tag');  end;end; { TPropertiesVisitor } //... { TForm1 } //... procedure TForm1.LoadCSSClasses(aFileName: String);var  fs: TFileStream;  cssp : TCSSParser;  csse : TCSSElement;  cv: TClassVisitor;  sl: TKeyValList;  i: Integer;begin  Memo1.Lines.Clear;  ListBox1.Items.BeginUpdate;  ListBox1.Items.Clear;  sl := TKeyValList.Create;  fs := TFileStream.Create(aFileName, fmOpenRead);  cssp := TCSSParser.Create(fs, []);  try    csse := cssp.Parse;    cv := TClassVisitor.Create(sl);    try      csse.Iterate(cv);    finally    cv.Free;    end;    for i := 0 to sl.Count - 1 do      ListBox1.Items.Add(sl.Keys[i] + ' [ ' + sl.Data[i] +' ]');  finally  csse.Free;    cssp.Free;    fs.Free;    sl.Free;    ListBox1.Items.EndUpdate;  end;end; ---------------------------------------------------------------------- egsuh · 2026-01-05 08:14 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i         @charset "utf-8";        @import url('https://fonts.googleapis.com/css?family=Noto+Serif+KR');         :root {                --bkColor: white;                --errColor: red;         } Secondly, "nested" definitions do not seem to be processed correctly. I tried following definitions. Code: CSS  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i         input[type=checkbox]    {      transform : scale(1.5);    }         li.QT2, li.QT3 {                   text-indent: -1.5em;               text-align: left;                list-style-type:none;        }     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 · 2026-01-05 11:41 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. ---------------------------------------------------------------------- Thaddy · 2026-01-05 12:13 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i @import url('https://fonts.googleapis.com/css?family=Noto+Serif+KR');Really? Then again, in private I use that too, but not for work.