Componentes.Terceros.FastRe.../internal/4.2/1/Source/frxXMLSerializer.pas
2007-11-18 19:40:07 +00:00

806 lines
20 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport v4.0 }
{ XML serializer }
{ }
{ Copyright (c) 1998-2007 }
{ by Alexander Tzyganenko, }
{ Fast Reports Inc. }
{ }
{******************************************}
unit frxXMLSerializer;
interface
{$I frx.inc}
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
TypInfo, frxXML, frxClass
{$IFDEF Delphi6}
, Variants
{$ENDIF};
type
TfrxGetAncestorEvent = procedure(const ComponentName: String;
var Ancestor: TPersistent) of object;
{ TfrxXMLSerializer is the XML analogue of the Delphi component streaming -
TReader and TWriter }
TfrxXMLSerializer = class(TObject)
private
FErrors: TStringList;
FFixups: TList;
FOwner: TfrxComponent;
FReader: TReader;
FReaderStream: TMemoryStream;
FSerializeDefaultValues: Boolean;
FStream: TStream;
FOnGetAncestor: TfrxGetAncestorEvent;
procedure AddFixup(Obj: TPersistent; p: PPropInfo; Value: String);
procedure ClearFixups;
procedure FixupReferences;
public
constructor Create(Stream: TStream);
destructor Destroy; override;
function ObjToXML(Obj: TPersistent; const Add: String = ''; Ancestor: TPersistent = nil): String;
function ReadComponent(Root: TfrxComponent): TfrxComponent;
function ReadComponentStr(Root: TfrxComponent; s: String; DontFixup: Boolean = False): TfrxComponent;
function WriteComponentStr(c: TfrxComponent): String;
procedure ReadRootComponent(Root: TfrxComponent; XMLItem: TfrxXMLItem = nil);
procedure ReadPersistentStr(Root: TComponent; Obj: TPersistent; const s: String);
procedure WriteComponent(c: TfrxComponent);
procedure WriteRootComponent(Root: TfrxComponent; SaveChildren: Boolean = True;
XMLItem: TfrxXMLItem = nil);
procedure XMLToObj(const s: String; Obj: TPersistent);
property Errors: TStringList read FErrors;
property Owner: TfrxComponent read FOwner write FOwner;
property Stream: TStream read FStream;
property SerializeDefaultValues: Boolean read FSerializeDefaultValues
write FSerializeDefaultValues;
property OnGetAncestor: TfrxGetAncestorEvent read FOnGetAncestor write FOnGetAncestor;
end;
implementation
uses frxUtils, frxRes, frxUnicodeUtils;
type
TfrxFixupItem = class(TObject)
public
Obj: TPersistent;
PropInfo: PPropInfo;
Value: String;
end;
THackComponent = class(TComponent);
THackPersistent = class(TPersistent);
THackReader = class(TReader);
{ TfrxXMLSerializer }
constructor TfrxXMLSerializer.Create(Stream: TStream);
begin
FErrors := TStringList.Create;
FErrors.Sorted := True;
FErrors.Duplicates := dupIgnore;
FFixups := TList.Create;
FStream := Stream;
FReaderStream := TMemoryStream.Create;
FReader := TReader.Create(FReaderStream, 4096);
end;
destructor TfrxXMLSerializer.Destroy;
begin
FErrors.Free;
FReader.Free;
FReaderStream.Free;
ClearFixups;
FFixups.Free;
inherited;
end;
procedure TfrxXMLSerializer.ClearFixups;
begin
while FFixups.Count > 0 do
begin
TfrxFixupItem(FFixups[0]).Free;
FFixups.Delete(0);
end;
end;
procedure TfrxXMLSerializer.AddFixup(Obj: TPersistent; p: PPropInfo;
Value: String);
var
Item: TfrxFixupItem;
begin
Item := TfrxFixupItem.Create;
Item.Obj := Obj;
Item.PropInfo := p;
Item.Value := Value;
FFixups.Add(Item);
end;
procedure TfrxXMLSerializer.FixupReferences;
var
i: Integer;
Item: TfrxFixupItem;
Ref: TObject;
begin
for i := 0 to FFixups.Count - 1 do
begin
Item := FFixups[i];
Ref := nil;
if FOwner <> nil then
Ref := FOwner.FindObject(Item.Value);
if Ref = nil then
Ref := frxFindComponent(FOwner, Item.Value);
if Ref <> nil then
SetOrdProp(Item.Obj, Item.PropInfo, Integer(Ref));
end;
FReader.FixupReferences;
FReader.EndReferences;
ClearFixups;
end;
procedure TfrxXMLSerializer.XMLToObj(const s: String; Obj: TPersistent);
var
i, j, start, len, code: Integer;
i1, start1, len1: Integer;
Name, Value: String;
Obj1: TPersistent;
p: PPropInfo;
ps, ps1: PChar;
procedure DoNonPublishedProps;
begin
FReaderStream.Clear;
frxStringToStream(Value, FReaderStream);
FReaderStream.Position := 0;
FReader.Position := 0;
try
while FReader.Position < FReaderStream.Size do
THackReader(FReader).ReadProperty(Obj1);
except
end;
end;
begin
{ speed optimized code. affects the speed of loading prepared page in the preview }
len := Length(s);
i := 1;
ps := PChar(s) - 1;
while i < len do
begin
j := i;
len1 := len;
ps1 := ps;
while (j < len1) and (ps1[j] = ' ') do
Inc(j);
start := j;
while (j < len1) and (ps1[j] <> '=') do
Inc(j);
i := j;
if i < len then
begin
j := i - 1;
while (j > 0) and (ps1[j] = ' ') do
Dec(j);
Name := Copy(s, start, j - start + 1);
if Name = '' then break;
j := i;
len1 := len;
while (j < len1) and (ps1[j] <> '"') do
Inc(j);
start := j + 1;
Inc(j);
while (j < len1) and (ps1[j] <> '"') do
Inc(j);
i := j;
Value := Copy(s, start, i - start);
Inc(i);
Obj1 := Obj;
{ check multiple properties }
len1 := Length(Name);
start1 := 1;
i1 := 1;
while (i1 < len1) and (Name[i1] <> '.') do
Inc(i1);
if i1 < len1 then
begin
while i1 < len1 do
begin
p := GetPropInfo(Obj1.ClassInfo, Copy(Name, start1, i1 - start1));
if p = nil then
break;
Obj1 := TPersistent(GetOrdProp(Obj1, p));
start1 := i1 + 1;
Inc(i1);
while (i1 < len1) and (Name[i1] <> '.') do
Inc(i1);
end;
Name := Copy(Name, start1, MaxInt);
end;
try
if Length(Name) = 1 then
begin
{ special properties }
case Name[1] of
'x':
begin
TfrxCustomMemoView(Obj1).Text := frxXMLToStr(Value);
continue;
end;
'u':
begin
TfrxCustomMemoView(Obj1).Text := Utf8Decode(frxXMLToStr(Value));
continue;
end;
'l':
begin
TfrxComponent(Obj1).Left := frxStrToFloat(Value);
continue;
end;
't':
begin
TfrxComponent(Obj1).Top := frxStrToFloat(Value);
continue;
end;
'w':
begin
TfrxComponent(Obj1).Width := frxStrToFloat(Value);
continue;
end;
'h':
begin
TfrxComponent(Obj1).Height := frxStrToFloat(Value);
continue;
end;
end;
end
else
begin
if Name = 'Text' then
begin
if Obj1 is TStrings then
begin
TStrings(Obj1).Text := frxXMLToStr(Value);
continue;
end
else if Obj1 is TWideStrings then
begin
TWideStrings(Obj1).Text := frxXMLToStr(Value);
continue;
end
else if Obj1 is TfrxCustomMemoView then
begin
TfrxCustomMemoView(Obj1).Text := Utf8Decode(frxXMLToStr(Value));
continue;
end
end
else if Name = 'PropData' then
begin
DoNonPublishedProps;
continue;
end
else if (Obj1 is TfrxReport) and (Name = 'Name') then
continue;
end;
p := GetPropInfo(Obj1.ClassInfo, Name);
if (p <> nil) and (p.SetProc <> nil) then
case p.PropType^.Kind of
tkInteger, tkSet, tkChar, tkWChar:
SetOrdProp(Obj1, p, StrToInt(Value));
tkEnumeration:
begin
Val(Value, j, code);
if code = 0 then
SetOrdProp(Obj1, p, j) else
SetOrdProp(Obj1, p, GetEnumValue(p.PropType^, Value));
end;
tkFloat:
SetFloatProp(Obj1, p, frxStrToFloat(Value));
tkString, tkLString, tkWString:
SetStrProp(Obj1, p, frxXMLToStr(Value));
tkClass:
AddFixup(Obj1, p, Value);
tkVariant:
SetVariantProp(Obj1, p, frxXMLToStr(Value));
end;
except
on E: Exception do
FErrors.Add(E.Message);
end;
end;
end;
end;
function TfrxXMLSerializer.ObjToXML(Obj: TPersistent; const Add: String = '';
Ancestor: TPersistent = nil): String;
var
TypeInfo: PTypeInfo;
PropCount: Integer;
PropList: PPropList;
i: Integer;
s: String;
ws: WideString;
Flag: Boolean;
procedure DoOrdProp;
var
Value: Integer;
function IsDefault: Boolean;
begin
if Ancestor <> nil then
Result := Value = GetOrdProp(Ancestor, PropList[i])
else
Result := Value = PropList[i].Default;
end;
begin
Value := GetOrdProp(Obj, PropList[i]);
if not IsDefault or FSerializeDefaultValues then
if PropList[i].PropType^.Kind = tkEnumeration then
s := GetEnumName(PropList[i].PropType^, Value)
else
s := IntToStr(Value);
end;
procedure DoFloatProp;
var
Value: Extended;
function IsDefault: Boolean;
begin
if Ancestor <> nil then
Result := Abs(Value - GetFloatProp(Ancestor, PropList[i])) < 1e-6
else
Result := False;
end;
begin
Value := GetFloatProp(Obj, PropList[i]);
// commented out due to bug with tfrxmemoview.linespacing=0
if not IsDefault or FSerializeDefaultValues then
s := FloatToStr(Value);
end;
procedure DoStrProp;
var
Value: String;
function IsDefault: Boolean;
begin
if Ancestor <> nil then
Result := Value = GetStrProp(Ancestor, PropList[i])
else
Result := Value = '';
end;
begin
Value := GetStrProp(Obj, PropList[i]);
if not IsDefault or FSerializeDefaultValues then
s := frxStrToXML(Value);
end;
procedure DoVariantProp;
var
Value: Variant;
function IsDefault: Boolean;
begin
if Ancestor <> nil then
Result := Value = GetVariantProp(Ancestor, PropList[i])
else
Result := False;
end;
begin
Value := GetVariantProp(Obj, PropList[i]);
if not IsDefault or FSerializeDefaultValues then
s := frxStrToXML(VarToStr(Value));
end;
procedure DoClassProp;
var
FClass: TClass;
FComp, FAncComp: TComponent;
FObj, FAncObj: TPersistent;
begin
FClass := GetTypeData(PropList[i].PropType^).ClassType;
if FClass.InheritsFrom(TComponent) then
begin
FComp := TComponent(GetOrdProp(Obj, PropList[i]));
if Ancestor <> nil then
FAncComp := TComponent(GetOrdProp(Ancestor, PropList[i]))
else
FAncComp := nil;
if Ancestor <> nil then
begin
if (FComp = nil) and (FAncComp = nil) then Exit;
if (FComp <> nil) and (FAncComp <> nil) then
if CompareText(FComp.Name, FAncComp.Name) = 0 then Exit;
if (FComp = nil) and (FAncComp <> nil) then
begin
s := 'nil';
Exit;
end;
end;
if FComp <> nil then
s := frxGetFullName(FOwner, FComp);
end
else if FClass.InheritsFrom(TPersistent) then
begin
FObj := TPersistent(GetOrdProp(Obj, PropList[i]));
if Ancestor <> nil then
FAncObj := TPersistent(GetOrdProp(Ancestor, PropList[i]))
else
FAncObj := nil;
if FObj is TStrings then
begin
if Ancestor <> nil then
if TStrings(FObj).Text = TStrings(FAncObj).Text then
Exit;
s := TStrings(FObj).Text;
if (Length(s) >= 2) and
(s[Length(s) - 1] = #13) and (s[Length(s)] = #10) then
Delete(s, Length(s) - 1, 2);
s := ' ' + Add + PropList[i].Name + '.Text="' +
frxStrToXML(s) + '"';
end
else if FObj is TWideStrings then
begin
// skip, handle separately
end
else
s := ObjToXML(FObj, Add + PropList[i].Name + '.', FAncObj);
Flag := True;
end;
end;
procedure DoNonPublishedProps;
var
wr: TWriter;
ms, AncMs: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
wr := TWriter.Create(ms, 4096);
wr.Root := FOwner;
try
THackPersistent(Obj).DefineProperties(wr);
finally
wr.Free;
end;
if ms.Size > 0 then
begin
if Ancestor <> nil then
begin
AncMs := TMemoryStream.Create;
try
wr := TWriter.Create(AncMs, 4096);
wr.Root := FOwner;
try
THackPersistent(Ancestor).DefineProperties(wr);
finally
wr.Free;
end;
if frxStreamCRC32(ms) = frxStreamCRC32(AncMs) then
Exit;
finally
AncMs.Free;
end;
end;
s := frxStreamToString(ms);
Result := Result + ' ' + Add + 'PropData="' + s + '"';
end;
finally
ms.Free;
end;
end;
begin
Result := '';
TypeInfo := Obj.ClassInfo;
PropCount := GetTypeData(TypeInfo).PropCount;
GetMem(PropList, PropCount * SizeOf(PPropInfo));
GetPropInfos(TypeInfo, PropList);
try
if Obj is TfrxComponent then
begin
TfrxComponent(Obj).IsWriting := True;
if (Ancestor = nil) and Assigned(FOnGetAncestor) then
FOnGetAncestor(TfrxComponent(Obj).Name, Ancestor);
end;
for i := 0 to PropCount - 1 do
begin
s := '';
Flag := False;
if IsStoredProp(Obj, PropList[i]) then
case PropList[i].PropType^.Kind of
tkInteger, tkSet, tkChar, tkWChar, tkEnumeration:
DoOrdProp;
tkFloat:
DoFloatProp;
tkString, tkLString, tkWString:
DoStrProp;
tkClass:
DoClassProp;
tkVariant:
DoVariantProp;
end;
if s <> '' then
if Flag then
Result := Result + s
else
Result := Result + ' ' + Add + PropList[i].Name + '="' + s + '"';
end;
if Obj is TfrxCustomMemoView then
if (Ancestor = nil) or
(TfrxCustomMemoView(Obj).Text <> TfrxCustomMemoView(Ancestor).Text) then
begin
ws := TfrxCustomMemoView(Obj).Text;
if (Length(ws) >= 2) and
(ws[Length(ws) - 1] = #13) and (ws[Length(ws)] = #10) then
Delete(ws, Length(ws) - 1, 2);
Result := Result + ' Text="' + frxStrToXML(Utf8Encode(ws)) + '"';
end;
DoNonPublishedProps;
finally
if Obj is TfrxComponent then
TfrxComponent(Obj).IsWriting := False;
FreeMem(PropList, PropCount * SizeOf(PPropInfo));
end;
end;
procedure TfrxXMLSerializer.ReadRootComponent(Root: TfrxComponent;
XMLItem: TfrxXMLItem = nil);
var
XMLDoc: TfrxXMLDocument;
CompList: TList;
procedure DoRead(Item: TfrxXMLItem; Owner: TfrxComponent);
var
i: Integer;
c: TfrxComponent;
IsAncestor: Boolean;
begin
IsAncestor := CompareText(Item.Name, 'inherited') = 0;
if not IsAncestor then
try
FindClass(Item.Name);
except
FErrors.Add(frxResources.Get('xrCantFindClass') + ' ' + Item.Name);
Exit;
end;
if Owner <> nil then
begin
c := FOwner.FindComponent(Item.Prop['Name']) as TfrxComponent;
if not IsAncestor and (c = nil) then
begin
c := TfrxComponent(FindClass(Item.Name).NewInstance);
c.Create(Owner);
end;
end
else
c := Root;
if c <> nil then
begin
c.IsLoading := True;
XMLToObj(Item.Text, c);
CompList.Add(c);
for i := 0 to Item.Count - 1 do
DoRead(Item[i], c);
end;
end;
procedure DoLoaded;
var
i: Integer;
c: TfrxComponent;
begin
for i := 0 to CompList.Count - 1 do
begin
c := CompList[i];
c.IsLoading := False;
if not (c is TfrxReport) then
THackComponent(c).Loaded;
end;
end;
begin
if Owner = nil then
Owner := Root;
XMLDoc := nil;
CompList := TList.Create;
if XMLItem = nil then
begin
XMLDoc := TfrxXMLDocument.Create;
XMLItem := XMLDoc.Root;
XMLDoc.LoadFromStream(FStream);
end;
FReader.Root := FOwner;
FReader.BeginReferences;
try
DoRead(XMLItem, nil);
FixupReferences;
DoLoaded;
finally
if XMLDoc <> nil then
XMLDoc.Free;
CompList.Free;
end;
end;
procedure TfrxXMLSerializer.WriteRootComponent(Root: TfrxComponent;
SaveChildren: Boolean = True; XMLItem: TfrxXMLItem = nil);
var
XMLDoc: TfrxXMLDocument;
procedure DoWrite(Item: TfrxXMLItem; ARoot: TfrxComponent);
var
i: Integer;
begin
if ARoot.IsAncestor then
Item.Name := 'inherited'
else
Item.Name := ARoot.ClassName;
if ARoot = Root then
Item.Text := ObjToXML(ARoot)
else
Item.Text := 'Name="' + ARoot.Name + '"' + ObjToXML(ARoot);
if SaveChildren then
for i := 0 to ARoot.Objects.Count - 1 do
DoWrite(Item.Add, ARoot.Objects[i]);
end;
begin
if Owner = nil then
Owner := Root;
XMLDoc := nil;
if XMLItem = nil then
begin
XMLDoc := TfrxXMLDocument.Create;
XMLItem := XMLDoc.Root;
XMLDoc.AutoIndent := True;
end;
try
DoWrite(XMLItem, Root);
if XMLDoc <> nil then
XMLDoc.SaveToStream(FStream);
finally
if XMLDoc <> nil then
XMLDoc.Free;
end;
end;
function TfrxXMLSerializer.ReadComponent(Root: TfrxComponent): TfrxComponent;
var
rd: TfrxXMLReader;
RootItem: TfrxXMLItem;
begin
rd := TfrxXMLReader.Create(FStream);
RootItem := TfrxXMLItem.Create;
try
rd.ReadRootItem(RootItem, False);
Result := ReadComponentStr(Root, RootItem.Name + ' ' + RootItem.Text);
finally
rd.Free;
RootItem.Free;
end;
end;
procedure TfrxXMLSerializer.WriteComponent(c: TfrxComponent);
var
s: String;
begin
s := '<' + WriteComponentStr(c) + '/>';
FStream.Write(s[1], Length(s));
end;
function TfrxXMLSerializer.ReadComponentStr(Root: TfrxComponent;
s: String; DontFixup: Boolean = False): TfrxComponent;
var
n: Integer;
s1: String;
begin
Owner := Root;
if Trim(s) = '' then
Result := nil
else
begin
n := Pos(' ', s);
s1 := Copy(s, n + 1, MaxInt);
Delete(s, n, MaxInt);
Result := TfrxComponent(FindClass(s).NewInstance);
Result.Create(Root);
FReader.Root := Root;
FReader.BeginReferences;
try
Result.IsLoading := True;
XMLToObj(s1, Result);
finally
if DontFixup then
begin
FReader.EndReferences;
ClearFixups;
end
else
FixupReferences;
Result.IsLoading := False;
if not (Result is TfrxReport) then
THackComponent(Result).Loaded;
end;
end;
end;
function TfrxXMLSerializer.WriteComponentStr(c: TfrxComponent): String;
begin
Result := c.ClassName + ObjToXML(c);
end;
procedure TfrxXMLSerializer.ReadPersistentStr(Root: TComponent;
Obj: TPersistent; const s: String);
begin
FReader.Root := Root;
FReader.BeginReferences;
XMLToObj(s, Obj);
FixupReferences;
end;
end.
//862fd5d6aa1a637203d9b08a3c0bcfb0