1{
2    $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
3    This file is part of the Free Component Library (FCL)
4    Copyright (c) 1999-2000 by the Free Pascal development team
5
6    See the file COPYING.FPC, included in this distribution,
7    for details about the copyright.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 **********************************************************************}
14{$mode objfpc}
15{$H+}
16{$define NOCONTNRS}
17unit fpTemplate;
18
19
20interface
21
22uses
23  SysUtils,
24  Classes;
25
26Const
27  DefaultParseDepth = 100;
28  MaxDelimLength    = 5;
29
30Type
31  TParseDelimiter = String[MaxDelimLength];
32
33Var
34  DefaultStartDelimiter : TParseDelimiter = '{';           //Template tag start                  |If you want Delphi-like, set it to '<#'
35  DefaultEndDelimiter  : TParseDelimiter = '}';            //Template tag end                    |                                   '>'
36  DefaultParamStartDelimiter  : TParseDelimiter = '[-';    //Tag parameter start                 |                                   ' '
37  DefaultParamEndDelimiter    : TParseDelimiter = '-]';    //Tag parameter end                   |                                   '"'
38  DefaultParamValueSeparator  : TParseDelimiter = '=';     //Tag parameter name/value separator  |                                   '="'
39                                                           //                                    |for tags like <#TagName paramname1="paramvalue1" paramname2="paramvalue2">
40
41Type
42  TGetParamEvent = Procedure(Sender : TObject; Const ParamName : String; Out AValue : String) Of Object;                              //for simple template tag support only (ex: {Name})
43  TReplaceTagEvent = Procedure(Sender : TObject; Const TagString : String; TagParams:TStringList; Out ReplaceText : String) Of Object;//for tags with parameters support
44
45
46  { TTemplateParser }
47
48  TTemplateParser = Class(TObject)
49  Private
50    FParseLevel : Integer;
51    FMaxParseDepth : Integer;
52    FEndDelimiter: TParseDelimiter;
53    FStartDelimiter: TParseDelimiter;
54    FParamStartDelimiter: TParseDelimiter;
55    FParamEndDelimiter: TParseDelimiter;
56    FParamValueSeparator: TParseDelimiter;
57    FAllowTagParams: Boolean; //default is false -> simple template tags allowed only [FValues, FOnGetParam (optional) used];
58                              //if true -> template tags with parameters allowed, [FOnReplaceTag] is used for all tag replacements
59    FRecursive: Boolean;                                   //when only simple tags are used in a template (AllowTagParams=false), the replacement can
60    FValues : TStringList;                                 //contain further tags for recursive processing (only used when no tag params are allowed)
61    FOnGetParam: TGetParamEvent;                           //Event handler to use for templates containing simple tags only (ex: {Name})
62    FOnReplaceTag: TReplaceTagEvent;                       //Event handler to use for templates containing tags with parameters (ex: <#TagName paramname1="paramvalue1" paramname2="paramvalue2">)
63    function GetDelimiter(Index: integer): TParseDelimiter;
64    function GetNameByIndex(index : Integer): String;
65    function GetValue(Key : String): String;
66    function GetValueByIndex(index : Integer): String;
67    function GetValueCount: Integer;
68    procedure SetDelimiter(Index: integer; const AValue: TParseDelimiter);
69    procedure SetValue(Key : String; const AValue: String);
70    Function IntParseString(Src : String) : String;
71  Public
72    Constructor Create;
73    Destructor Destroy; override;
74    Procedure Clear;
75    Function ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;//used only when AllowTagParams = true
76    Function GetParam(Const Key : String; Out AValue : String) : Boolean;                           //used only when AllowTagParams = false
77    Procedure GetTagParams(var TagName:String; var TagParams : TStringList) ;
78    Function ParseString(Src : String) : String;
79    Function ParseStream(Src : TStream; Dest : TStream) : Integer; // Wrapper, Returns number of bytes written.
80    Procedure ParseStrings(Src : TStrings; Dest : TStrings) ;      // Wrapper
81    Procedure ParseFiles(Const Src,Dest : String);
82    Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;               // Called if not found in values  //used only when AllowTagParams = false
83    Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag;       // Called if a tag found          //used only when AllowTagParams = true
84    Property StartDelimiter : TParseDelimiter Index 1 Read GetDelimiter Write SetDelimiter;// Start char/string, default '}'
85    Property EndDelimiter : TParseDelimiter Index 2 Read GetDelimiter Write SetDelimiter;  // end char/string, default '{'
86    Property ParamStartDelimiter : TParseDelimiter Index 3 Read GetDelimiter Write SetDelimiter;
87    Property ParamEndDelimiter : TParseDelimiter Index 4 Read GetDelimiter Write SetDelimiter;
88    Property ParamValueSeparator : TParseDelimiter Index 5 Read GetDelimiter Write SetDelimiter;
89    Property Values[Key : String] : String Read GetValue Write SetValue; // Contains static values.                          //used only when AllowTagParams = false
90    Property ValuesByIndex[index : Integer] : String Read GetValueByIndex; // Contains static values.                        //used only when AllowTagParams = false
91    Property NamesByIndex[index : Integer] : String Read GetNameByIndex;  // Contains static values.                        //used only when AllowTagParams = false
92    Property ValueCount: Integer Read GetValueCount;                                                                         //used only when AllowTagParams = false
93    Property Recursive : Boolean Read FRecursive Write FRecursive;                                                           //used only when AllowTagParams = false
94    Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
95  end;
96
97  { TFPCustomTemplate }
98
99  TFPCustomTemplate = Class(TPersistent)
100  private
101    FEndDelimiter: TParseDelimiter;
102    FStartDelimiter: TParseDelimiter;
103    FParamStartDelimiter: TParseDelimiter;
104    FParamEndDelimiter: TParseDelimiter;
105    FParamValueSeparator: TParseDelimiter;
106    FFileName: String;
107    FTemplate: String;
108    FOnGetParam: TGetParamEvent;                                                                                             //used only when AllowTagParams = false
109    FOnReplaceTag: TReplaceTagEvent;                                                                                         //used only when AllowTagParams = true
110    FAllowTagParams: Boolean;
111  Protected
112    Procedure GetParam(Sender : TObject; Const ParamName : String; Out AValue : String);virtual;                             //used only when AllowTagParams = false
113    Procedure ReplaceTag(Sender : TObject; Const TagName: String; TagParams:TStringList; Out AValue: String);virtual;        //used only when AllowTagParams = true
114    Function CreateParser : TTemplateParser; virtual;
115  Public
116    Function HasContent : Boolean;
117    Function GetContent : String;
118    Procedure Assign(Source : TPersistent); override;
119    Property StartDelimiter : TParseDelimiter Read FStartDelimiter Write FStartDelimiter;
120    Property EndDelimiter : TParseDelimiter Read FEndDelimiter Write FEndDelimiter;
121    Property ParamStartDelimiter : TParseDelimiter Read FParamStartDelimiter Write FParamStartDelimiter;
122    Property ParamEndDelimiter : TParseDelimiter Read FParamEndDelimiter Write FParamEndDelimiter;
123    Property ParamValueSeparator : TParseDelimiter Read FParamValueSeparator Write FParamValueSeparator;
124    Property FileName : String Read FFileName Write FFileName;
125    Property Template : String Read FTemplate Write FTemplate;
126    Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;
127    Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag;
128    Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
129  end;
130
131  TFPTemplate = Class(TFPCustomTemplate)
132  Published
133    Property FileName;
134    Property Template;
135    Property AllowTagParams;
136    Property OnReplaceTag;
137    Property StartDelimiter;
138    Property EndDelimiter;
139    Property ParamStartDelimiter;
140    Property ParamEndDelimiter;
141    Property ParamValueSeparator;
142    Property OnGetParam;
143  end;
144
145  ETemplateParser = Class(Exception);
146
147Var
148  MaxParseDepth : Integer = DefaultParseDepth;
149
150
151implementation
152
153Resourcestring
154  SErrParseDepthExceeded = 'Maximum parse level (%d) exceeded.';
155  SErrNoEmptyDelimiters = 'Delimiters cannot be empty';
156
157{ TTemplateParser }
158Type
159
160  { TStringItem }
161
162  TStringItem = Class(TObject)
163  Private
164    FValue : String;
165  Public
166    Constructor Create(AValue : String);
167    Property Value : String Read FValue Write FValue;
168  end;
169
170{ TStringItem }
171
172constructor TStringItem.Create(AValue: String);
173begin
174  FValue:=AValue;
175end;
176
177{ TTemplateParser }
178
179function TTemplateParser.GetValue(Key : String): String;
180
181Var
182  I : Integer;
183
184begin
185  Result:='';
186  If Assigned(FValues) then
187    begin
188    I:=FValues.IndexOf(Key);
189    If (I<>-1) then
190      Result:=TStringItem(FValues.Objects[i]).Value;
191    end;
192end;
193
194function TTemplateParser.GetValueByIndex(index : Integer): String;
195begin
196  Result:='';
197  If Assigned(FValues) then
198    Result:=TStringItem(FValues.Objects[index]).Value;
199end;
200
201function TTemplateParser.GetValueCount: Integer;
202begin
203  if assigned(FValues) then
204    result := FValues.Count
205  else
206    result := 0;
207end;
208
209function TTemplateParser.GetDelimiter(Index: integer): TParseDelimiter;
210begin
211  case Index of
212  1: Result:=FStartDelimiter;
213  2: Result:=FEndDelimiter;
214  3: Result:=FParamStartDelimiter;
215  4: Result:=FParamEndDelimiter;
216    else
217     Result:=FParamValueSeparator;
218  end;
219end;
220
221function TTemplateParser.GetNameByIndex(index : Integer): String;
222begin
223  Result:='';
224  If Assigned(FValues) then
225    Result:=FValues.ValueFromIndex[index];
226end;
227
228procedure TTemplateParser.SetDelimiter(Index: integer;
229  const AValue: TParseDelimiter);
230begin
231  If Length(AValue)=0 then
232    Raise ETemplateParser.Create(SErrNoEmptyDelimiters);
233  case Index of
234    1: FStartDelimiter:=AValue;
235    2: FEndDelimiter:=AValue;
236    3: FParamStartDelimiter:=AValue;
237    4: FParamEndDelimiter:=AValue;
238      else
239       FParamValueSeparator:=AValue;
240  end;
241
242end;
243
244procedure TTemplateParser.SetValue(Key : String; const AValue: String);
245
246Var
247  I : Integer;
248
249begin
250  If (AValue='') then
251    begin
252    If Assigned(FValues) then
253      begin
254      I:=FValues.IndexOf(Key);
255      If (I<>-1) then
256        begin
257        FValues.Objects[i].Free;
258        FValues.Delete(I);
259        end;
260      end;
261    end
262  else
263    begin
264    if Not Assigned(FValues) then
265      begin
266      FVAlues:=TStringList.Create;
267      FValues.Sorted:=True;
268      end;
269    I:=FValues.IndexOf(Key);
270    If (I=-1) then
271      FValues.AddObject(Key,TStringItem.Create(AValue))
272    else
273      TStringItem(FValues.Objects[I]).Value:=AValue;
274    end;
275end;
276
277constructor TTemplateParser.Create;
278
279begin
280  FParseLevel:=0;
281  FMaxParseDepth:=MaxParseDepth;
282  FStartDelimiter:=DefaultStartDelimiter;
283  FEndDelimiter:=DefaultEndDelimiter;
284  FParamStartDelimiter:=DefaultParamStartDelimiter;
285  FParamEndDelimiter:=DefaultParamEndDelimiter;
286  FParamValueSeparator:=DefaultParamValueSeparator;
287  FAllowTagParams := false;
288end;
289
290destructor TTemplateParser.Destroy;
291
292begin
293  Clear;
294  inherited Destroy;
295end;
296
297procedure TTemplateParser.Clear;
298
299Var
300  I : Integer;
301
302begin
303  If Assigned(FValues) then
304    For I:=0 to FValues.Count-1 do
305      FValues.Objects[i].Free;
306  FreeAndNil(FValues);
307end;
308
309function TTemplateParser.GetParam(const Key: String; out AValue: String): Boolean;
310
311Var
312  I : Integer;
313
314begin
315  If Assigned(FValues) then
316    I:=FValues.IndexOf(Key)
317  else
318    I:=-1;
319  Result:=(I<>-1);
320  If Result then
321    AValue:=TStringItem(FValues.Objects[i]).Value
322  else
323    begin
324    Result:=Assigned(FOnGetParam);
325    If Result then
326      FOnGetParam(Self,Key,AValue);
327    end;
328  If Result and Recursive then
329    AValue:=IntParseString(AValue);
330end;
331
332function TTemplateParser.ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;
333begin
334  Result:=Assigned(FOnReplaceTag);
335  If Result then
336    FOnReplaceTag(Self,Key,TagParams,ReplaceWith);
337end;
338
339Function FindDelimiter(SP : PChar; D : TParseDelimiter; MaxLen : Integer) : PChar; Inline;
340
341Var
342  P,P2 : PChar;
343  I,DLen : Integer;
344
345begin
346  Result:=Nil;
347  DLen:=Length(D);
348  Dec(MaxLen,(DLen-1));
349  If MaxLen<=0 then
350   exit;
351  P:=SP;
352  While (Result=Nil) and (P-SP<=MaxLen) do
353    begin
354    While (P-SP<=MaxLen) and (P^<>D[1]) do
355      Inc(P);
356    If ((P-SP)<=MaxLen) then
357      begin
358      Result:=P;
359      P2:=P+1;
360      // Check Other characters
361      I:=2;
362      While (I<=DLen) and (Result<>Nil) do
363        If (P2^=D[i]) then
364          begin
365          inc(i);
366          Inc(p2);
367          end
368        else
369          begin
370          P:=Result;
371          Result:=Nil;
372          end;
373      // Either result<>Nil -> match or result=nil -> no match
374      inc(P);
375      end;
376    end;
377end;
378
379Procedure AddToString(Var S : String; P : PChar; NChars : Integer);inline;
380
381Var
382  SLen : Integer;
383
384begin
385  SLen:=Length(S);
386  SetLength(S,SLen+NChars);
387  Move(P^,S[Slen+1],NChars);
388end;
389
390procedure TTemplateParser.GetTagParams(var TagName:String; var TagParams : TStringList) ;
391var
392  I,SLen:Integer;
393  TS,TM,TE,SP,P : PChar;
394  PName, PValue, TP : String;
395  IsFirst:Boolean;
396begin
397  SLen:=Length(TagName);
398  if SLen=0 then exit;
399
400  IsFirst := true;
401  SP:=PChar(TagName);
402  TP := TagName;
403  P:=SP;
404  while (P-SP<SLen) do
405  begin
406    TS:=FindDelimiter(P,FParamStartDelimiter,SLen-(P-SP));
407    if (TS<>Nil) then
408    begin//Found param start delimiter
409      if IsFirst then
410      begin//Get the real Tag name
411        IsFirst := false;
412        I := 1;
413        while not (P[I] in [#0..' ']) do Inc(I);
414        if i>(TS-SP) then
415          i := TS-SP;
416        SetLength(TP, I);
417        Move(P^, TP[1], I);
418      end;
419      inc(TS, Length(FParamStartDelimiter));
420      I:=TS-P;//index of param name
421      TM:=FindDelimiter(TS,FParamValueSeparator,SLen-I+1);
422      if (TM<>Nil) then
423      begin//Found param value separator
424        I:=TM-TS;//lenght of param name
425        SetLength(PName, I);
426        Move(TS^, PName[1], I);//param name
427        inc(TS, Length(FParamValueSeparator) + I);
428        I := TS - P;//index of param value
429      end;
430
431      TE:=FindDelimiter(TS,FParamEndDelimiter, SLen-I+1);
432      if (TE<>Nil) then
433      begin//Found param end
434        I:=TE-TS;//Param length
435        Setlength(PValue,I);
436        Move(TS^,PValue[1],I);//Param value
437        if TM=nil then
438          TagParams.Add(Trim(PValue))
439        else
440          TagParams.Add(Trim(PName) + '=' + PValue);//Param names cannot contain '='
441        P:=TE+Length(FParamEndDelimiter);
442        TS:=P;
443      end else break;
444    end else break;
445  end;
446  TagName := Trim(TP);
447end;
448
449function TTemplateParser.ParseString(Src: String): String;
450begin
451  FParseLevel:=0;
452  Result:=IntParseString(Src);
453end;
454
455function TTemplateParser.IntParseString(Src: String): String;
456
457Var
458  PN,PV,ReplaceWith : String;
459  i,SLen : Integer;
460  TS,TE,SP,P : PChar;
461  TagParams:TStringList;
462begin
463  if FAllowTagParams then
464  begin//template tags with parameters are allowed
465    SLen:=Length(Src);
466    Result:='';
467    If SLen=0 then
468      exit;
469    SP:=PChar(Src);
470    P:=SP;
471    While (P-SP<SLen) do
472      begin
473      TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
474      If (TS=Nil) then
475        begin//Tag Start Delimiter not found
476        TS:=P;
477        P:=SP+SLen;
478        end
479      else
480        begin
481        I:=TS-P;
482        inc(TS,Length(FStartDelimiter));//points to first char of Tag name now
483        TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
484        If (TE=Nil) then
485          begin//Tag End Delimiter not found
486          TS:=P;
487          P:=SP+SLen;
488          end
489        else//Found start and end delimiters for the Tag
490          begin
491          // Add text prior to template tag to result
492          AddToString(Result,P,I);
493          // Retrieve the full template tag (only tag name if no params specified)
494          I:=TE-TS;//full Tag length
495          Setlength(PN,I);
496          Move(TS^,PN[1],I);//full Tag string (only tag name if no params specified)
497          TagParams := TStringList.Create;
498          try
499            TagParams.Sorted := True;
500            GetTagParams(PN, Tagparams);
501            If ReplaceTag(PN,TagParams,ReplaceWith) then
502              Result:=Result+ReplaceWith;
503          finally
504            TagParams.Free;
505          end;
506          P:=TE+Length(FEndDelimiter);
507          TS:=P;
508          end;
509        end
510      end;
511    I:=P-TS;
512    If (I>0) then
513      AddToString(Result,TS,I);
514  end else begin//template tags with parameters are not allowed
515    Inc(FParseLevel);
516    If FParseLevel>FMaxParseDepth then
517      Raise ETemplateParser.CreateFmt(SErrParseDepthExceeded,[FMaxParseDepth]);
518    SLen:=Length(Src); // Minimum
519    Result:='';
520    If SLen=0 then
521      exit;
522//    STLen:=Length(FStartDelimiter);
523    SP:=PChar(Src);
524    P:=SP;
525    While (P-SP<SLen) do
526      begin
527      TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
528      If (TS=Nil) then
529        begin
530        TS:=P;
531        P:=SP+SLen
532        end
533      else
534        begin
535        I:=TS-P;
536        inc(TS,Length(FStartDelimiter));
537        TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
538        If (TE=Nil) then
539          begin
540          TS:=P;
541          P:=SP+SLen;
542          end
543        else
544          begin
545          // Add text prior to template to result
546          AddToString(Result,P,I);
547          // retrieve template name
548          I:=TE-TS;
549          Setlength(PN,I);
550          Move(TS^,PN[1],I);
551          If GetParam(PN,PV) then
552            begin
553            Result:=Result+PV;
554            end;
555          P:=TE+Length(FEndDelimiter);
556          TS:=P;
557          end;
558        end
559      end;
560    I:=P-TS;
561    If (I>0) then
562      AddToString(Result,TS,I);
563  end;
564end;
565
566function TTemplateParser.ParseStream(Src: TStream; Dest: TStream): Integer;
567
568Var
569  SS : TStringStream;
570  S,R : String;
571
572begin
573  SS:=TStringStream.Create('');
574  Try
575    SS.CopyFrom(Src,0);
576    S:=SS.DataString;
577  Finally
578    SS.Free;
579  end;
580  R:=ParseString(S);
581  Result:=Length(R);
582  If (Result>0) then
583    Dest.Write(R[1],Result);
584end;
585
586procedure TTemplateParser.ParseStrings(Src: TStrings; Dest: TStrings);
587
588Var
589  I : Integer;
590
591begin
592  For I:=0 to Src.Count-1 do
593    Dest.Add(ParseString(Src[i]));
594end;
595
596procedure TTemplateParser.ParseFiles(const Src, Dest: String);
597
598Var
599  Fin,Fout : TFileStream;
600
601begin
602  Fin:=TFileStream.Create(Src,fmOpenRead or fmShareDenyWrite);
603  try
604    Fout:=TFileStream.Create(Dest,fmCreate);
605    try
606      ParseStream(Fin,Fout);
607    finally
608      Fout.Free;
609    end;
610  finally
611    Fin.Free;
612  end;
613end;
614
615{ TFPCustomTemplate }
616
617procedure TFPCustomTemplate.GetParam(Sender: TObject; const ParamName: String; out AValue: String);
618
619begin
620  If Assigned(FOnGetParam) then
621   FOnGetParam(Self,ParamName,AValue);
622end;
623
624procedure TFPCustomTemplate.ReplaceTag(Sender: TObject; const TagName: String; TagParams:TStringList; Out AValue: String);
625
626begin
627  If Assigned(FOnReplaceTag) then
628  begin
629    FOnReplaceTag(Self,TagName,TagParams,AValue);
630  end;
631end;
632
633function TFPCustomTemplate.CreateParser: TTemplateParser;
634
635begin
636  Result:=TTemplateParser.Create;
637  Result.FParseLevel := 0;
638  If (FStartDelimiter<>'') then
639    Result.StartDelimiter:=FStartDelimiter;
640  If (FEndDelimiter<>'') then
641    Result.EndDelimiter:=FEndDelimiter;
642  If (FParamStartDelimiter<>'') then
643    Result.ParamStartDelimiter:=FParamStartDelimiter;
644  If (FParamEndDelimiter<>'') then
645    Result.ParamEndDelimiter:=FParamEndDelimiter;
646  If (FParamValueSeparator<>'') then
647    Result.ParamValueSeparator:=FParamValueSeparator;
648  Result.OnGetParam:=@GetParam;
649  Result.OnReplaceTag:=@ReplaceTag;
650  Result.AllowTagParams:=FAllowTagParams;
651end;
652
653function TFPCustomTemplate.HasContent: Boolean;
654
655begin
656  Result:=(FTemplate<>'') or (FFileName<>'');
657end;
658
659function TFPCustomTemplate.GetContent: String;
660
661Var
662  P : TTemplateParser;
663  S : TStringStream;
664  F : TFileStream;
665
666begin
667  F:=Nil;
668  S:=Nil;
669  If HasContent then
670    begin
671    if (FFileName<>'') then
672      begin
673      F:=TFileStream.Create(FFileName,fmOpenRead);
674      S:=TStringStream.Create('');
675      end;
676    Try
677      P:=CreateParser;
678      Try
679        If (F<>Nil) then
680          begin
681          P.ParseStream(F,S);
682          Result:=S.DataString;
683          end
684        else
685          Result:=P.IntParseString(FTemplate);
686      Finally
687        P.Free;
688      end;
689    Finally
690      F.Free;
691      S.Free;
692    end;
693    end;
694end;
695
696procedure TFPCustomTemplate.Assign(Source: TPersistent);
697
698Var
699  T : TFPCustomTemplate;
700
701begin
702  If Source is TFPCustomTemplate then
703    begin
704    T:=Source as TFPCustomTemplate;
705    FEndDelimiter:=T.EndDelimiter;
706    FStartDelimiter:=T.StartDelimiter;
707    FParamEndDelimiter:=T.ParamEndDelimiter;
708    FParamStartDelimiter:=T.ParamStartDelimiter;
709    FParamValueSeparator:=T.ParamValueSeparator;
710    FFileName:=T.FileName;
711    FTemplate:=T.Template;
712    FOnGetParam:=T.OnGetParam;
713    FOnReplaceTag:=T.OnReplaceTag;
714    FAllowTagParams := T.AllowTagParams;
715    end
716  else
717    inherited Assign(Source);
718end;
719
720end.
721
722