lazarus-ccr/components/systools/source/general/run/stmerge.pas
wp_xxyyzz 543cdf06d9 systools: Rearrange units and packages
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6159 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2018-01-30 16:17:37 +00:00

458 lines
12 KiB
ObjectPascal

(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower SysTools
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{*********************************************************}
{* SysTools: StMerge.pas 4.04 *}
{*********************************************************}
{* SysTools: "Mail Merge" functionality *}
{*********************************************************}
{$IFDEF FPC}
{$mode DELPHI}
{$ENDIF}
//{$include StDefine.inc}
unit StMerge;
interface
uses
{$IFNDEF FPC}
Windows,
{$ENDIF}
SysUtils, Classes;
const
StDefaultTagStart = '<';
StDefaultTagEnd = '>';
StDefaultEscapeChar = '\';
type
TStGotMergeTagEvent = procedure (Sender : TObject; Tag : AnsiString;
var Value : AnsiString; var Discard : Boolean) of object;
TStMergeProgressEvent = procedure (Sender : TObject; Index : Integer; var Abort : Boolean);
TStTextMerge = class
private
FBadTag: AnsiString;
FDefaultTags: TStrings;
FEscapeChar: AnsiChar;
FMergedText : TStrings;
FMergeTags: TStrings;
FTagEnd: AnsiString;
FTagStart: AnsiString;
FTemplate : TStrings;
FOnMergeStart: TNotifyEvent;
FOnMergeDone: TNotifyEvent;
FOnLineStart: TStMergeProgressEvent;
FOnLineDone: TStMergeProgressEvent;
FOnGotMergeTag: TStGotMergeTagEvent;
FOnGotUnknownTag: TStGotMergeTagEvent;
protected {private}
procedure DoGotUnknownTag(Tag: AnsiString; var Value: AnsiString;
var Discard: Boolean);
procedure DoGotMergeTag(Tag : AnsiString; var Value : AnsiString;
var Discard : Boolean);
procedure SetEscapeChar(const Value: AnsiChar);
procedure SetTagEnd(const Value: AnsiString);
procedure SetTagStart(const Value: AnsiString);
public
constructor Create;
destructor Destroy; override;
{ Access and Update Methods }
procedure Merge;
{ Persistence and streaming methods }
{template }
procedure LoadTemplateFromFile(const AFile : TFileName);
procedure LoadTemplateFromStream(AStream : TStream);
procedure SaveTemplateToFile(const AFile : TFileName);
procedure SaveTemplateToStream(AStream : TStream);
{ merge result text }
procedure SaveMergeToFile(const AFile : TFileName);
procedure SaveMergeToStream(AStream : TStream);
{ properties }
property BadTag : AnsiString
read FBadTag write FBadTag;
property DefaultTags : TStrings
read FDefaultTags;
property EscapeChar : AnsiChar
read FEscapeChar write SetEscapeChar;
property MergedText : TStrings
read FMergedText;
property MergeTags : TStrings
read FMergeTags;
property TagEnd : AnsiString
read FTagEnd write SetTagEnd;
property TagStart : AnsiString
read FTagStart write SetTagStart;
property Template : TStrings
read FTemplate;
{ events }
property OnGotMergeTag : TStGotMergeTagEvent
read FOnGotMergeTag write FOnGotMergeTag;
property OnGotUnknownTag : TStGotMergeTagEvent
read FOnGotUnknownTag write FOnGotUnknownTag;
property OnLineDone : TStMergeProgressEvent
read FOnLineDone write FOnLineDone;
property OnLineStart : TStMergeProgressEvent
read FOnLineStart write FOnLineStart;
property OnMergeDone : TNotifyEvent
read FOnMergeDone write FOnMergeDone;
property OnMergeStart : TNotifyEvent
read FOnMergeStart write FOnMergeStart;
end;
implementation
{ TStTextMerge }
constructor TStTextMerge.Create;
begin
inherited Create;
FDefaultTags := TStringList.Create;
FMergeTags := TStringList.Create;
FMergedText := TStringList.Create;
FTemplate := TStringList.Create;
FTagEnd := StDefaultTagEnd;
FTagStart := StDefaultTagStart;
FEscapeChar := StDefaultEscapeChar;
FBadTag := '';
end;
destructor TStTextMerge.Destroy;
begin
FDefaultTags.Free;
FMergeTags.Free;
FMergedText.Free;
FTemplate.Free;
inherited Destroy;
end;
procedure TStTextMerge.DoGotMergeTag(Tag : AnsiString;
var Value : AnsiString; var Discard : Boolean);
begin
if Assigned(FOnGotMergeTag) then
FOnGotMergeTag(self, Tag, Value, Discard);
end;
procedure TStTextMerge.DoGotUnknownTag(Tag : AnsiString;
var Value : AnsiString; var Discard : Boolean);
begin
if Assigned(FOnGotUnknownTag) then
FOnGotUnknownTag(self, Tag, Value, Discard)
else
Value := FBadTag;
end;
procedure TStTextMerge.LoadTemplateFromFile(const AFile: TFileName);
var
FS : TFileStream;
begin
FS := TFileStream.Create(AFile, fmOpenRead or fmShareDenyNone);
try
LoadTemplateFromStream(FS);
finally
FS.Free;
end;
end;
procedure TStTextMerge.LoadTemplateFromStream(AStream: TStream);
begin
FTemplate.Clear;
FTemplate.LoadFromStream(AStream);
end;
procedure TStTextMerge.Merge;
{ merge template with current DataTags }
const
TagIDChars = ['A'..'Z', 'a'..'z', '0'..'9', '_'];
function MatchDelim(Delim : AnsiString; var PC : PAnsiChar) : Boolean;
{ see if current sequence matches specified Tag delimiter }
var
Match : PAnsiChar;
Len : Integer;
begin
{ compare text starting at PC with Tag delimiter }
Len := Length(Delim);
GetMem(Match, Len + 1);
FillChar(Match^, Len + 1, #0);
StrLCopy(Match, PC, Len);
Result := StrPas(Match) = Delim;
if Result then
Inc(PC, Len); {advance past Tag delimiter }
FreeMem(Match, Len + 1);
end;
function GetTag(const Tag: AnsiString; var Discard : Boolean) : AnsiString;
var
IdxMerge, IdxDef : Integer;
TagID : AnsiString;
begin
{ extract TagID from delimiters }
TagID := Copy(Tag, Length(TagStart) + 1, Length(Tag));
TagID := Copy(TagID, 1, Length(TagID) - Length(TagEnd));
{ see if it matches Tag in MergeTags or DefaultTags }
IdxMerge := FMergeTags.IndexOfName(TagID);
IdxDef := FDefaultTags.IndexOfName(TagID);
{ fire events as needed }
if (IdxMerge < 0) and (IdxDef < 0) then begin { no match }
DoGotUnknownTag(TagID, Result, Discard)
end
else begin { found match }
if (IdxMerge > -1) then begin { match in MergeTags }
Result := FMergeTags.Values[TagID];
DoGotMergeTag(TagID, Result, Discard);
end
else { not in MergTags, use Default }
if (IdxDef > -1) then begin
Result := FDefaultTags.Values[TagID];
DoGotMergeTag(TagID, Result, Discard);
end;
end;
end;
procedure ReplaceTags(Idx : Integer);
type
TagSearchStates = (fsCollectingText, fsCollectingTagID);
var
i, Len : Integer;
P, Cur : PAnsiChar;
Buff, NewBuff, TagBuff, DataBuff, TextBuff : AnsiString;
State : TagSearchStates;
FS, FE, Prev : AnsiChar;
{Escaped,} Discard : Boolean;
begin
{ copy current template line }
Buff := FTemplate[Idx];
Len := Length(Buff);
{ output line starts empty }
NewBuff := '';
TagBuff := '';
TextBuff := '';
{ starts of delimiter strings }
FS := FTagStart[1];
FE := FTagEnd[1];
Prev := ' ';
{ point at start of current line }
P := PAnsiChar(Buff);
Cur := P;
{ start looking for Tags }
State := fsCollectingText;
for i := 1 to Len do begin
case State of
{ accumulating non-Tag text }
fsCollectingText: begin
{ matching the start of a Tag? }
if (Cur^ = FS) and (Prev <> EscapeChar) and
MatchDelim(FTagStart, Cur) then
begin
{ dump what we've got }
NewBuff := NewBuff + TextBuff;
TextBuff := '';
{ start accumulating a TagID }
TagBuff := TagStart;
State := fsCollectingTagID;
end
else
if (Cur^ = FS) and (Prev = EscapeChar) and
MatchDelim(FTagStart, Cur) then
begin
{ overwrite escape character }
TextBuff[Length(TextBuff)] := Cur^;
{ go to next character }
Prev := Cur^;
Inc(Cur);
end
else
{ accumulate text }
begin
TextBuff := TextBuff + Cur^;
{ go to next character }
Prev := Cur^;
Inc(Cur);
end;
end;
{ accumulating a possible Tag }
fsCollectingTagID: begin
{ matching the end of a Tag? }
if (Cur^ = FE) and (Prev <> EscapeChar) and
MatchDelim(FTagEnd, Cur) then
begin
{ insert Tag value in place of TagID }
TagBuff := TagBuff + TagEnd;
DataBuff := GetTag(TagBuff, Discard);
if not Discard then
NewBuff := NewBuff + DataBuff;
{ switch back to accumulating non-Tag text }
State := fsCollectingText;
end
else
{ accumulate TagID }
if (Cur^ in TagIDChars) then begin
TagBuff := TagBuff + Cur^;
{ go to next character }
Prev := Cur^;
Inc(Cur);
end
else
{ doesn't look like a TagID; pass it back to text collection logic }
begin
{ turn the "failed Tag" into regular accumulated text }
TextBuff := TagBuff + Cur^;
TagBuff := '';
{ go to next character }
Prev := Cur^;
Inc(Cur);
{ switch back to accumulating non-Tag text }
State := fsCollectingText;
end;
end;
end; {case State}
end; {for}
{ append anything remaining }
if State = fsCollectingText then
NewBuff := NewBuff + TextBuff
else
NewBuff := NewBuff + TagBuff;
{ update merge text with current line }
FMergedText.Add(NewBuff);
end;
var
i : Integer;
Abort : Boolean;
begin
{ notify start of merge }
if Assigned(FOnMergeStart) then
FOnMergeStart(self);
FMergedText.Clear;
Abort := False;
{ iterate Template }
for i := 0 to Pred(FTemplate.Count) do begin
if Assigned(FOnLineStart) then
FOnLineStart(self, i, Abort);
if Abort then Break;
ReplaceTags(i);
if Assigned(FOnLineDone) then
FOnLineDone(self, i, Abort);
if Abort then Break;
end; {for}
{ notify end of merge }
if Assigned(FOnMergeDone) then
FOnMergeDone(self);
end;
procedure TStTextMerge.SaveMergeToFile(const AFile: TFileName);
var
FS : TFileStream;
begin
FS := TFileStream.Create(AFile, fmCreate);
try
SaveMergeToStream(FS);
finally
FS.Free;
end;
end;
procedure TStTextMerge.SaveMergeToStream(AStream: TStream);
begin
FMergedText.SaveToStream(AStream);
end;
procedure TStTextMerge.SaveTemplateToFile(const AFile: TFileName);
var
FS : TFileStream;
begin
FS := TFileStream.Create(AFile, fmCreate);
try
SaveTemplateToStream(FS);
finally
FS.Free;
end;
end;
procedure TStTextMerge.SaveTemplateToStream(AStream: TStream);
begin
FTemplate.SaveToStream(AStream);
end;
procedure TStTextMerge.SetEscapeChar(const Value: AnsiChar);
begin
FEscapeChar := Value;
end;
procedure TStTextMerge.SetTagEnd(const Value: AnsiString);
begin
FTagEnd := Value;
end;
procedure TStTextMerge.SetTagStart(const Value: AnsiString);
begin
FTagStart := Value;
end;
end.