[lazarus] First Pass at the light xml parser

Cliff Baeseman vbman at pcpros.net
Fri Mar 31 02:08:18 EST 2000


Here is the parser unit and a simple test app and a xml file. In order to use
it you create a decendant class of TXMLParser and override the event
trigger virtual methods. There is a TTestParser defined in the unit that
shows how to implement the parser. I still need to do a little code
purtification on it before committing.

This allows you to create a class like TLazarusConfig = class(TXMLParser) and
implement a simple state machine that loads the config interface with data for
reading by the application. 

xtest.pp is the app
xparser is the parser
test.xml is a example config file

Cliff

unit xparser;
{$mode objfpc}

interface
uses
 Classes,Sysutils;

type
 TXMLElement = class(TObject)
  public
   StartTag: boolean;
   EndTag: boolean;
   VersionTag: boolean;
   DataTag: boolean;
   Data: string;
  end;

 TXMLParser = class(TObject)
 protected
   procedure LoadStream(xmlstream: TStream);
   procedure OnStartTag(sData:string);virtual;
   procedure OnEndTag(eData:string);virtual;
   procedure OnData(dData:string);virtual;
   procedure Run;

 private
   FStream: TStream;
   FList: TList;
   FBuffer: string;
   procedure SplitStream;
   procedure LoadElement(vdata:string);
   procedure LoadDataElement;
   procedure DumpBuffer;
   procedure AppendBuffer(vdata:char);
   function IsData:boolean;
   function StripStartTagData(sdata:string):string;
   function StripEndTagData(sdata:string):string;
   procedure FireEvents;
 end;
 TTestParser = class(TXMLParser)
  public
   procedure Parse;
   procedure SetData(xdata: TStream);
   procedure OnStartTag(sData:string);override;
   procedure OnEndTag(eData:string);override;
   procedure OnData(dData:string);override;
  end;

implementation

{ TXMLParser }


{------------------------------------------------------------------------------}
{*************************WARNING**********************************************}
{ Change Back to trim after RTL is fixed                                       }
{------------------------------------------------------------------------------}
function StringTrim(const S: string): string;
var Ofs, Len: integer;
begin
  len := Length(S);
  while (Len>0) and (S[Len] = ' ') or (S[Len] = #10) or (S[Len] = #13) do
   dec(Len);
  Ofs := 1;
  while (Ofs<=Len) and (S[Ofs] = ' ') or (S[Ofs] = #10) or (S[Len] = #13) do
   Inc(Ofs);
  result := Copy(S, Ofs, 1 + Len - Ofs);
end ;


{------------------------------------------------------------------------------}
{ Start Class Decendant Virtual Event Triggers                                       }
{------------------------------------------------------------------------------}
procedure TXMLParser.OnStartTag(sData:string);
begin
end;

procedure TXMLParser.OnEndTag(eData:string);
begin
end;

procedure TXMLParser.OnData(dData:string);
begin
end;
{------------------------------------------------------------------------------}
{ End Class Decendant Virtual Event Triggers                                       }
{------------------------------------------------------------------------------}


{------------------------------------------------------------------------------}
{ Fire virtual functions in decendant parser class, based on data in stack     }
{------------------------------------------------------------------------------}
procedure TXMLParser.FireEvents;
var
lIndex: integer;
vElement: TXMLElement;
begin
  for lIndex := 0 to FList.Count -1 do
    begin
       vElement := TXMLElement(FList[lIndex]);
       if vElement.StartTag then
         OnStartTag(vElement.Data);
       if vElement.EndTag then
         OnEndTag(vElement.Data);
       if vElement.DataTag then
         OnData(vElement.Data);
     end;
end;

{------------------------------------------------------------------------------}
{ Strip off start tag markers                                                  }
{------------------------------------------------------------------------------}
function TXMLParser.StripStartTagData(sdata:string):string;
begin
 result := Copy(StringTrim(sdata),2,length(StringTrim(sdata)) - 2);
end;

{------------------------------------------------------------------------------}
{ Strip off end tag markers                                                    }
{------------------------------------------------------------------------------}
function TXMLParser.StripEndTagData(sdata:string):string;
begin
  result := Copy(StringTrim(sdata),3,length(StringTrim(sdata)) -3);
end;

{------------------------------------------------------------------------------}
{ Appends data to the buffer                                                   }
{------------------------------------------------------------------------------}
procedure TXMLParser.AppendBuffer(vdata: char);
begin
   FBuffer := FBuffer + vdata;
end;

{------------------------------------------------------------------------------}
{ Loads a data element to the parsed stack                                     }
{------------------------------------------------------------------------------}
procedure TXMLParser.LoadElement(vdata: string);
var
velement:TXMLElement;

begin

  {version junk throw it away}
  if Pos('?>',vdata) > 0 then
    exit;

  if IsData then
    begin
      LoadDataElement;
      exit;
    end;

  if Pos('</',vdata) > 0 then
    begin
      velement := TXMLElement.Create;
      velement.EndTag := true;
      velement.Data := StripEndTagData(vdata);
      FList.Add(velement);
      exit;
    end;

  if Pos('<',vdata) > 0 then
    begin
      velement := TXMLElement.Create;
      velement.StartTag := true;
      velement.Data := StripStartTagData(vdata);
      FList.Add(velement);
      exit;
    end;

end;

{------------------------------------------------------------------------------}
{ Dumps the contents of the data buffer                                        }
{------------------------------------------------------------------------------}
procedure TXMLParser.DumpBuffer;
begin
    LoadElement(FBuffer);
    FBuffer := '';
end;

{------------------------------------------------------------------------------}
{ Sets the internal private stream                                             }
{------------------------------------------------------------------------------}
procedure TXMLParser.LoadStream(xmlstream: TStream);
begin
 FStream := xmlstream;
end;

{------------------------------------------------------------------------------}
{ Runs the parser                                                              }
{ The run  method loads a element stack containing the data and just does a    }
{  loop on it firing data the virtual event methods                            }
{------------------------------------------------------------------------------}
procedure TXMLParser.Run;
begin
  if not assigned(FList) then
    FList := TList.Create;

  SplitStream;
  FireEvents;
end;

{------------------------------------------------------------------------------}
{ Splits the stream into tag chunks                                            }
{------------------------------------------------------------------------------}
procedure TXMLParser.SplitStream;
var
count: integer;
vchar: char;
begin
  for count := 0 to FStream.Size -1 do
    begin
       FStream.Read(vchar,1);
       if (vchar = '>') then
         begin
           AppendBuffer(vchar);
           DumpBuffer;
         end
       else
         AppendBuffer(vchar);
    end;
end;

{------------------------------------------------------------------------------}
{ Tests a tag set to determine if it contains data                             }
{------------------------------------------------------------------------------}
function TXMLParser.IsData: boolean;
var
vBuffer: string;
begin
 vBuffer := StringTrim(FBuffer);
 if Pos('<',vBuffer) > 2 then
   begin
    result := true;
   end
 else
   result := false;
end;

{------------------------------------------------------------------------------}
{ Strips a data element                                                   }
{------------------------------------------------------------------------------}
procedure TXMLParser.LoadDataElement;
var
ldata: string;
rdata: string;
epos: integer;
lElement: TXMLElement;

begin

   { At this point we are going to have data
   on the left side of the tagged line we need
   to strip it into a data and tag element and
   add it to the element list }
   epos := Pos('<',FBuffer);
   ldata := Copy(FBuffer,0,epos-1);
   rdata := Copy(FBuffer,epos,length(FBuffer));
   { load left data element }
   lElement := TXMLElement.Create;
   lElement.DataTag := true;
   lElement.Data := ldata;
   FList.Add(lElement);
   { load close tag element }
   lElement := TXMLElement.Create;
   lElement.EndTag := true;
   lElement.Data := StripEndTagData(rdata);
   FList.Add(lElement);
end;
{******************* inherited test parser **************}
 procedure TTestParser.Parse;
 begin
  self.Run;
 end;

 procedure TTestParser.SetData(xdata: TStream);
 begin
   self.LoadStream(xdata);
 end;

 procedure TTestParser.OnStartTag(sData:string);
 begin
  writeln('start tag = ' + sData);
 end;

 procedure TTestParser.OnEndTag(eData:string);
 begin
  writeln('end tag =' + eData);
 end;

 procedure TTestParser.OnData(dData:string);
 begin
  if trim(dData) > '' then
    writeln('data =' + dData);
 end;
end.

program xtest;

{$mode objfpc}

uses classes, xparser;

var
parser: TTestParser;
FData: TStream;

begin
parser := TTestParser.Create;
FData := TFileStream.Create('test.xml',fmOpenRead);
parser.SetData(FData);
parser.Parse;
FData.Free;
end.


<?xml version="1.0"?>
  <idesettings>
    <toolbar>
       <height>100</height>
       <width>400</width>
       <left>10</left>
       <top>10</top>
    </toolbar>
    <toolstrings>
      <language>
        <english>
          <caption>Lazarus</caption>
        </english>
        <german>
          <caption>Whatever in German</caption>
        </german>
      </language>
    </toolstrings>
  </idesettings>




More information about the Lazarus mailing list