Repository URL to install this package:
Version:
3.0.0 ▾
|
{
$Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
This file is part of the Free Component Library (FCL)
Copyright (c) 1999-2000 by the Free Pascal development team
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
{$mode objfpc}
{$H+}
{$define NOCONTNRS}
unit fpTemplate;
interface
uses
SysUtils,
Classes;
Const
DefaultParseDepth = 100;
MaxDelimLength = 5;
Type
TParseDelimiter = String[MaxDelimLength];
Var
DefaultStartDelimiter : TParseDelimiter = '{'; //Template tag start |If you want Delphi-like, set it to '<#'
DefaultEndDelimiter : TParseDelimiter = '}'; //Template tag end | '>'
DefaultParamStartDelimiter : TParseDelimiter = '[-'; //Tag parameter start | ' '
DefaultParamEndDelimiter : TParseDelimiter = '-]'; //Tag parameter end | '"'
DefaultParamValueSeparator : TParseDelimiter = '='; //Tag parameter name/value separator | '="'
// |for tags like <#TagName paramname1="paramvalue1" paramname2="paramvalue2">
Type
TGetParamEvent = Procedure(Sender : TObject; Const ParamName : String; Out AValue : String) Of Object; //for simple template tag support only (ex: {Name})
TReplaceTagEvent = Procedure(Sender : TObject; Const TagString : String; TagParams:TStringList; Out ReplaceText : String) Of Object;//for tags with parameters support
{ TTemplateParser }
TTemplateParser = Class(TObject)
Private
FParseLevel : Integer;
FMaxParseDepth : Integer;
FEndDelimiter: TParseDelimiter;
FStartDelimiter: TParseDelimiter;
FParamStartDelimiter: TParseDelimiter;
FParamEndDelimiter: TParseDelimiter;
FParamValueSeparator: TParseDelimiter;
FAllowTagParams: Boolean; //default is false -> simple template tags allowed only [FValues, FOnGetParam (optional) used];
//if true -> template tags with parameters allowed, [FOnReplaceTag] is used for all tag replacements
FRecursive: Boolean; //when only simple tags are used in a template (AllowTagParams=false), the replacement can
FValues : TStringList; //contain further tags for recursive processing (only used when no tag params are allowed)
FOnGetParam: TGetParamEvent; //Event handler to use for templates containing simple tags only (ex: {Name})
FOnReplaceTag: TReplaceTagEvent; //Event handler to use for templates containing tags with parameters (ex: <#TagName paramname1="paramvalue1" paramname2="paramvalue2">)
function GetDelimiter(Index: integer): TParseDelimiter;
function GetNameByIndex(index : Integer): String;
function GetValue(Key : String): String;
function GetValueByIndex(index : Integer): String;
function GetValueCount: Integer;
procedure SetDelimiter(Index: integer; const AValue: TParseDelimiter);
procedure SetValue(Key : String; const AValue: String);
Function IntParseString(Src : String) : String;
Public
Constructor Create;
Destructor Destroy; override;
Procedure Clear;
Function ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;//used only when AllowTagParams = true
Function GetParam(Const Key : String; Out AValue : String) : Boolean; //used only when AllowTagParams = false
Procedure GetTagParams(var TagName:String; var TagParams : TStringList) ;
Function ParseString(Src : String) : String;
Function ParseStream(Src : TStream; Dest : TStream) : Integer; // Wrapper, Returns number of bytes written.
Procedure ParseStrings(Src : TStrings; Dest : TStrings) ; // Wrapper
Procedure ParseFiles(Const Src,Dest : String);
Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam; // Called if not found in values //used only when AllowTagParams = false
Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag; // Called if a tag found //used only when AllowTagParams = true
Property StartDelimiter : TParseDelimiter Index 1 Read GetDelimiter Write SetDelimiter;// Start char/string, default '}'
Property EndDelimiter : TParseDelimiter Index 2 Read GetDelimiter Write SetDelimiter; // end char/string, default '{'
Property ParamStartDelimiter : TParseDelimiter Index 3 Read GetDelimiter Write SetDelimiter;
Property ParamEndDelimiter : TParseDelimiter Index 4 Read GetDelimiter Write SetDelimiter;
Property ParamValueSeparator : TParseDelimiter Index 5 Read GetDelimiter Write SetDelimiter;
Property Values[Key : String] : String Read GetValue Write SetValue; // Contains static values. //used only when AllowTagParams = false
Property ValuesByIndex[index : Integer] : String Read GetValueByIndex; // Contains static values. //used only when AllowTagParams = false
Property NamesByIndex[index : Integer] : String Read GetNameByIndex; // Contains static values. //used only when AllowTagParams = false
Property ValueCount: Integer Read GetValueCount; //used only when AllowTagParams = false
Property Recursive : Boolean Read FRecursive Write FRecursive; //used only when AllowTagParams = false
Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
end;
{ TFPCustomTemplate }
TFPCustomTemplate = Class(TPersistent)
private
FEndDelimiter: TParseDelimiter;
FStartDelimiter: TParseDelimiter;
FParamStartDelimiter: TParseDelimiter;
FParamEndDelimiter: TParseDelimiter;
FParamValueSeparator: TParseDelimiter;
FFileName: String;
FTemplate: String;
FOnGetParam: TGetParamEvent; //used only when AllowTagParams = false
FOnReplaceTag: TReplaceTagEvent; //used only when AllowTagParams = true
FAllowTagParams: Boolean;
Protected
Procedure GetParam(Sender : TObject; Const ParamName : String; Out AValue : String);virtual; //used only when AllowTagParams = false
Procedure ReplaceTag(Sender : TObject; Const TagName: String; TagParams:TStringList; Out AValue: String);virtual; //used only when AllowTagParams = true
Function CreateParser : TTemplateParser; virtual;
Public
Function HasContent : Boolean;
Function GetContent : String;
Procedure Assign(Source : TPersistent); override;
Property StartDelimiter : TParseDelimiter Read FStartDelimiter Write FStartDelimiter;
Property EndDelimiter : TParseDelimiter Read FEndDelimiter Write FEndDelimiter;
Property ParamStartDelimiter : TParseDelimiter Read FParamStartDelimiter Write FParamStartDelimiter;
Property ParamEndDelimiter : TParseDelimiter Read FParamEndDelimiter Write FParamEndDelimiter;
Property ParamValueSeparator : TParseDelimiter Read FParamValueSeparator Write FParamValueSeparator;
Property FileName : String Read FFileName Write FFileName;
Property Template : String Read FTemplate Write FTemplate;
Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;
Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag;
Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
end;
TFPTemplate = Class(TFPCustomTemplate)
Published
Property FileName;
Property Template;
Property AllowTagParams;
Property OnReplaceTag;
Property StartDelimiter;
Property EndDelimiter;
Property ParamStartDelimiter;
Property ParamEndDelimiter;
Property ParamValueSeparator;
Property OnGetParam;
end;
ETemplateParser = Class(Exception);
Var
MaxParseDepth : Integer = DefaultParseDepth;
implementation
Resourcestring
SErrParseDepthExceeded = 'Maximum parse level (%d) exceeded.';
SErrNoEmptyDelimiters = 'Delimiters cannot be empty';
{ TTemplateParser }
Type
{ TStringItem }
TStringItem = Class(TObject)
Private
FValue : String;
Public
Constructor Create(AValue : String);
Property Value : String Read FValue Write FValue;
end;
{ TStringItem }
constructor TStringItem.Create(AValue: String);
begin
FValue:=AValue;
end;
{ TTemplateParser }
function TTemplateParser.GetValue(Key : String): String;
Var
I : Integer;
begin
Result:='';
If Assigned(FValues) then
begin
I:=FValues.IndexOf(Key);
If (I<>-1) then
Result:=TStringItem(FValues.Objects[i]).Value;
end;
end;
function TTemplateParser.GetValueByIndex(index : Integer): String;
begin
Result:='';
If Assigned(FValues) then
Result:=TStringItem(FValues.Objects[index]).Value;
end;
function TTemplateParser.GetValueCount: Integer;
begin
if assigned(FValues) then
result := FValues.Count
else
result := 0;
end;
function TTemplateParser.GetDelimiter(Index: integer): TParseDelimiter;
begin
case Index of
1: Result:=FStartDelimiter;
2: Result:=FEndDelimiter;
3: Result:=FParamStartDelimiter;
4: Result:=FParamEndDelimiter;
else
Result:=FParamValueSeparator;
end;
end;
function TTemplateParser.GetNameByIndex(index : Integer): String;
begin
Result:='';
If Assigned(FValues) then
Result:=FValues.ValueFromIndex[index];
end;
procedure TTemplateParser.SetDelimiter(Index: integer;
const AValue: TParseDelimiter);
begin
If Length(AValue)=0 then
Raise ETemplateParser.Create(SErrNoEmptyDelimiters);
case Index of
1: FStartDelimiter:=AValue;
2: FEndDelimiter:=AValue;
3: FParamStartDelimiter:=AValue;
4: FParamEndDelimiter:=AValue;
else
FParamValueSeparator:=AValue;
end;
end;
procedure TTemplateParser.SetValue(Key : String; const AValue: String);
Var
I : Integer;
begin
If (AValue='') then
begin
If Assigned(FValues) then
begin
I:=FValues.IndexOf(Key);
If (I<>-1) then
begin
FValues.Objects[i].Free;
FValues.Delete(I);
end;
end;
end
else
begin
if Not Assigned(FValues) then
begin
FVAlues:=TStringList.Create;
FValues.Sorted:=True;
end;
I:=FValues.IndexOf(Key);
If (I=-1) then
FValues.AddObject(Key,TStringItem.Create(AValue))
else
TStringItem(FValues.Objects[I]).Value:=AValue;
end;
end;
constructor TTemplateParser.Create;
begin
FParseLevel:=0;
FMaxParseDepth:=MaxParseDepth;
FStartDelimiter:=DefaultStartDelimiter;
FEndDelimiter:=DefaultEndDelimiter;
FParamStartDelimiter:=DefaultParamStartDelimiter;
FParamEndDelimiter:=DefaultParamEndDelimiter;
FParamValueSeparator:=DefaultParamValueSeparator;
FAllowTagParams := false;
end;
destructor TTemplateParser.Destroy;
begin
Clear;
inherited Destroy;
end;
procedure TTemplateParser.Clear;
Var
I : Integer;
begin
If Assigned(FValues) then
For I:=0 to FValues.Count-1 do
FValues.Objects[i].Free;
FreeAndNil(FValues);
end;
function TTemplateParser.GetParam(const Key: String; out AValue: String): Boolean;
Var
I : Integer;
begin
If Assigned(FValues) then
I:=FValues.IndexOf(Key)
else
I:=-1;
Result:=(I<>-1);
If Result then
AValue:=TStringItem(FValues.Objects[i]).Value
else
begin
Result:=Assigned(FOnGetParam);
If Result then
FOnGetParam(Self,Key,AValue);
end;
If Result and Recursive then
AValue:=IntParseString(AValue);
end;
function TTemplateParser.ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;
begin
Result:=Assigned(FOnReplaceTag);
If Result then
FOnReplaceTag(Self,Key,TagParams,ReplaceWith);
end;
Function FindDelimiter(SP : PChar; D : TParseDelimiter; MaxLen : Integer) : PChar; Inline;
Var
P,P2 : PChar;
I,DLen : Integer;
begin
Result:=Nil;
DLen:=Length(D);
Dec(MaxLen,(DLen-1));
If MaxLen<=0 then
exit;
P:=SP;
While (Result=Nil) and (P-SP<=MaxLen) do
begin
While (P-SP<=MaxLen) and (P^<>D[1]) do
Inc(P);
If ((P-SP)<=MaxLen) then
begin
Result:=P;
P2:=P+1;
// Check Other characters
I:=2;
While (I<=DLen) and (Result<>Nil) do
If (P2^=D[i]) then
begin
inc(i);
Inc(p2);
end
else
begin
P:=Result;
Result:=Nil;
end;
// Either result<>Nil -> match or result=nil -> no match
inc(P);
end;
end;
end;
Procedure AddToString(Var S : String; P : PChar; NChars : Integer);inline;
Var
SLen : Integer;
begin
SLen:=Length(S);
SetLength(S,SLen+NChars);
Move(P^,S[Slen+1],NChars);
end;
procedure TTemplateParser.GetTagParams(var TagName:String; var TagParams : TStringList) ;
var
I,SLen:Integer;
TS,TM,TE,SP,P : PChar;
PName, PValue, TP : String;
IsFirst:Boolean;
begin
SLen:=Length(TagName);
if SLen=0 then exit;
IsFirst := true;
SP:=PChar(TagName);
TP := TagName;
P:=SP;
while (P-SP<SLen) do
begin
TS:=FindDelimiter(P,FParamStartDelimiter,SLen-(P-SP));
if (TS<>Nil) then
begin//Found param start delimiter
if IsFirst then
begin//Get the real Tag name
IsFirst := false;
I := 1;
while not (P[I] in [#0..' ']) do Inc(I);
if i>(TS-SP) then
i := TS-SP;
SetLength(TP, I);
Move(P^, TP[1], I);
end;
inc(TS, Length(FParamStartDelimiter));
I:=TS-P;//index of param name
TM:=FindDelimiter(TS,FParamValueSeparator,SLen-I+1);
if (TM<>Nil) then
begin//Found param value separator
I:=TM-TS;//lenght of param name
SetLength(PName, I);
Move(TS^, PName[1], I);//param name
inc(TS, Length(FParamValueSeparator) + I);
I := TS - P;//index of param value
end;
TE:=FindDelimiter(TS,FParamEndDelimiter, SLen-I+1);
if (TE<>Nil) then
begin//Found param end
I:=TE-TS;//Param length
Setlength(PValue,I);
Move(TS^,PValue[1],I);//Param value
if TM=nil then
TagParams.Add(Trim(PValue))
else
TagParams.Add(Trim(PName) + '=' + PValue);//Param names cannot contain '='
P:=TE+Length(FParamEndDelimiter);
TS:=P;
end else break;
end else break;
end;
TagName := Trim(TP);
end;
function TTemplateParser.ParseString(Src: String): String;
begin
FParseLevel:=0;
Result:=IntParseString(Src);
end;
function TTemplateParser.IntParseString(Src: String): String;
Var
PN,PV,ReplaceWith : String;
i,SLen : Integer;
TS,TE,SP,P : PChar;
TagParams:TStringList;
begin
if FAllowTagParams then
begin//template tags with parameters are allowed
SLen:=Length(Src);
Result:='';
If SLen=0 then
exit;
SP:=PChar(Src);
P:=SP;
While (P-SP<SLen) do
begin
TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
If (TS=Nil) then
begin//Tag Start Delimiter not found
TS:=P;
P:=SP+SLen;
end
else
begin
I:=TS-P;
inc(TS,Length(FStartDelimiter));//points to first char of Tag name now
TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
If (TE=Nil) then
begin//Tag End Delimiter not found
TS:=P;
P:=SP+SLen;
end
else//Found start and end delimiters for the Tag
begin
// Add text prior to template tag to result
AddToString(Result,P,I);
// Retrieve the full template tag (only tag name if no params specified)
I:=TE-TS;//full Tag length
Setlength(PN,I);
Move(TS^,PN[1],I);//full Tag string (only tag name if no params specified)
TagParams := TStringList.Create;
try
TagParams.Sorted := True;
GetTagParams(PN, Tagparams);
If ReplaceTag(PN,TagParams,ReplaceWith) then
Result:=Result+ReplaceWith;
finally
TagParams.Free;
end;
P:=TE+Length(FEndDelimiter);
TS:=P;
end;
end
end;
I:=P-TS;
If (I>0) then
AddToString(Result,TS,I);
end else begin//template tags with parameters are not allowed
Inc(FParseLevel);
If FParseLevel>FMaxParseDepth then
Raise ETemplateParser.CreateFmt(SErrParseDepthExceeded,[FMaxParseDepth]);
SLen:=Length(Src); // Minimum
Result:='';
If SLen=0 then
exit;
// STLen:=Length(FStartDelimiter);
SP:=PChar(Src);
P:=SP;
While (P-SP<SLen) do
begin
TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
If (TS=Nil) then
begin
TS:=P;
P:=SP+SLen
end
else
begin
I:=TS-P;
inc(TS,Length(FStartDelimiter));
TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
If (TE=Nil) then
begin
TS:=P;
P:=SP+SLen;
end
else
begin
// Add text prior to template to result
AddToString(Result,P,I);
// retrieve template name
I:=TE-TS;
Setlength(PN,I);
Move(TS^,PN[1],I);
If GetParam(PN,PV) then
begin
Result:=Result+PV;
end;
P:=TE+Length(FEndDelimiter);
TS:=P;
end;
end
end;
I:=P-TS;
If (I>0) then
AddToString(Result,TS,I);
end;
end;
function TTemplateParser.ParseStream(Src: TStream; Dest: TStream): Integer;
Var
SS : TStringStream;
S,R : String;
begin
SS:=TStringStream.Create('');
Try
SS.CopyFrom(Src,0);
S:=SS.DataString;
Finally
SS.Free;
end;
R:=ParseString(S);
Result:=Length(R);
If (Result>0) then
Dest.Write(R[1],Result);
end;
procedure TTemplateParser.ParseStrings(Src: TStrings; Dest: TStrings);
Var
I : Integer;
begin
For I:=0 to Src.Count-1 do
Dest.Add(ParseString(Src[i]));
end;
procedure TTemplateParser.ParseFiles(const Src, Dest: String);
Var
Fin,Fout : TFileStream;
begin
Fin:=TFileStream.Create(Src,fmOpenRead or fmShareDenyWrite);
try
Fout:=TFileStream.Create(Dest,fmCreate);
try
ParseStream(Fin,Fout);
finally
Fout.Free;
end;
finally
Fin.Free;
end;
end;
{ TFPCustomTemplate }
procedure TFPCustomTemplate.GetParam(Sender: TObject; const ParamName: String; out AValue: String);
begin
If Assigned(FOnGetParam) then
FOnGetParam(Self,ParamName,AValue);
end;
procedure TFPCustomTemplate.ReplaceTag(Sender: TObject; const TagName: String; TagParams:TStringList; Out AValue: String);
begin
If Assigned(FOnReplaceTag) then
begin
FOnReplaceTag(Self,TagName,TagParams,AValue);
end;
end;
function TFPCustomTemplate.CreateParser: TTemplateParser;
begin
Result:=TTemplateParser.Create;
Result.FParseLevel := 0;
If (FStartDelimiter<>'') then
Result.StartDelimiter:=FStartDelimiter;
If (FEndDelimiter<>'') then
Result.EndDelimiter:=FEndDelimiter;
If (FParamStartDelimiter<>'') then
Result.ParamStartDelimiter:=FParamStartDelimiter;
If (FParamEndDelimiter<>'') then
Result.ParamEndDelimiter:=FParamEndDelimiter;
If (FParamValueSeparator<>'') then
Result.ParamValueSeparator:=FParamValueSeparator;
Result.OnGetParam:=@GetParam;
Result.OnReplaceTag:=@ReplaceTag;
Result.AllowTagParams:=FAllowTagParams;
end;
function TFPCustomTemplate.HasContent: Boolean;
begin
Result:=(FTemplate<>'') or (FFileName<>'');
end;
function TFPCustomTemplate.GetContent: String;
Var
P : TTemplateParser;
S : TStringStream;
F : TFileStream;
begin
F:=Nil;
S:=Nil;
If HasContent then
begin
if (FFileName<>'') then
begin
F:=TFileStream.Create(FFileName,fmOpenRead);
S:=TStringStream.Create('');
end;
Try
P:=CreateParser;
Try
If (F<>Nil) then
begin
P.ParseStream(F,S);
Result:=S.DataString;
end
else
Result:=P.IntParseString(FTemplate);
Finally
P.Free;
end;
Finally
F.Free;
S.Free;
end;
end;
end;
procedure TFPCustomTemplate.Assign(Source: TPersistent);
Var
T : TFPCustomTemplate;
begin
If Source is TFPCustomTemplate then
begin
T:=Source as TFPCustomTemplate;
FEndDelimiter:=T.EndDelimiter;
FStartDelimiter:=T.StartDelimiter;
FParamEndDelimiter:=T.ParamEndDelimiter;
FParamStartDelimiter:=T.ParamStartDelimiter;
FParamValueSeparator:=T.ParamValueSeparator;
FFileName:=T.FileName;
FTemplate:=T.Template;
FOnGetParam:=T.OnGetParam;
FOnReplaceTag:=T.OnReplaceTag;
FAllowTagParams := T.AllowTagParams;
end
else
inherited Assign(Source);
end;
end.