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