mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-06 06:32:40 +02:00
503 lines
14 KiB
ObjectPascal
503 lines
14 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.modifiedLGPL.txt, 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. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Component serialisation drivers for pascal.
|
|
|
|
Works:
|
|
- simple properties: integer, strings, events, ...
|
|
- nested components (e.g. the child controls of a form)
|
|
- class properties (e.g. TControl.Font)
|
|
|
|
ToDo:
|
|
- TCollection needs a typecast to the item class
|
|
- variants
|
|
- widestrings need special encoding conversions, but the driver does not
|
|
know, that a widestring is assigned
|
|
- what to do with DefineProperties?
|
|
- the 'with' can conflict
|
|
- circle dependencies:
|
|
Edit1:=TEdit.Create(Form1);
|
|
Edit1.AnchorSide[akLeft].Control:=Label1;
|
|
Label1:=TLabel.Create(Form1);
|
|
Label1.AnchorSide[akTop].Control:=Edit1;
|
|
- a reader
|
|
}
|
|
|
|
unit ComponentStreamPas;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc, typinfo;
|
|
|
|
type
|
|
|
|
TPASObjectWriterStackElType = (
|
|
elUnknown,
|
|
elComponent,
|
|
elPropertyList,
|
|
elProperty,
|
|
elChildrenList,
|
|
elCollection,
|
|
elCollectionItem
|
|
);
|
|
|
|
TPASObjectWriterStackEl = class
|
|
public
|
|
ElementName, ElementClass: string;
|
|
ElemType: TPASObjectWriterStackElType;
|
|
end;
|
|
|
|
{ TPASObjectWriter }
|
|
|
|
TPASObjectWriter = class(TAbstractObjectWriter)
|
|
private
|
|
FStream: TStream;
|
|
FStack: TFPList; // stack of TPASObjectWriterStackEl
|
|
StackEl: TPASObjectWriterStackEl;
|
|
procedure StackPush(const ElementName: string;
|
|
ElementType: TPASObjectWriterStackElType);
|
|
procedure StackPop;
|
|
procedure WriteIndent;
|
|
procedure WriteAssignment(PropertyName, Value: string);
|
|
procedure WritePropertyAssignment(Value: string);
|
|
procedure WriteCreateComponent(CompName, CompClass, CompOwner: string);
|
|
procedure WriteWithDoBegin(Expr: string);
|
|
procedure WriteEnd;
|
|
|
|
function StringToConstant(s: string): string;
|
|
public
|
|
constructor Create(AStream: TStream);
|
|
|
|
{ Begin/End markers. Those ones who don't have an end indicator, use
|
|
"EndList", after the occurrence named in the comment. Note that this
|
|
only counts for "EndList" calls on the same level; each BeginXXX call
|
|
increases the current level. }
|
|
procedure BeginCollection; override;{ Ends with the next "EndList" }
|
|
procedure BeginComponent(Component: TComponent; Flags: TFilerFlags;
|
|
ChildPos: Integer); override;{ Ends after the second "EndList" }
|
|
procedure BeginList; override;
|
|
procedure EndList; override;
|
|
procedure BeginProperty(const PropName: String); override;
|
|
procedure EndProperty; override;
|
|
|
|
procedure WriteBinary(const Buffer; Count: Longint); override;
|
|
procedure WriteBoolean(Value: Boolean); override;
|
|
// procedure WriteChar(Value: Char);
|
|
procedure WriteFloat(const Value: Extended); override;
|
|
procedure WriteSingle(const Value: Single); override;
|
|
procedure WriteCurrency(const Value: Currency); override;
|
|
procedure WriteDate(const Value: TDateTime); override;
|
|
procedure WriteIdent(const Ident: string); override;
|
|
procedure WriteInteger(Value: Int64); override;
|
|
procedure WriteMethodName(const Name: String); override;
|
|
procedure WriteSet(Value: LongInt; SetType: Pointer); override;
|
|
procedure WriteString(const Value: String); override;
|
|
procedure WriteWideString(const Value: WideString); override;
|
|
procedure WriteUInt64(Value: QWord); override;
|
|
procedure WriteUnicodeString(const Value: UnicodeString); override;
|
|
procedure WriteVariant(const VarValue: Variant); override;
|
|
procedure Write(const Buffer; Count: Longint); override;
|
|
public
|
|
property Stream: TStream read FStream;
|
|
end;
|
|
TPASObjectWriterClass = class of TPASObjectWriter;
|
|
|
|
procedure WriteComponentToPasStream(AComponent: TComponent; AStream: TStream);
|
|
|
|
implementation
|
|
|
|
procedure WriteComponentToPasStream(AComponent: TComponent; AStream: TStream);
|
|
var
|
|
Driver: TPASObjectWriter;
|
|
Writer: TWriter;
|
|
begin
|
|
Driver:=nil;
|
|
Writer:=nil;
|
|
try
|
|
Driver:=TPASObjectWriter.Create(AStream);
|
|
Writer:=TWriter.Create(Driver);
|
|
Writer.WriteDescendent(AComponent,nil);
|
|
finally
|
|
Writer.Free;
|
|
Driver.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.StackPush(const ElementName: string;
|
|
ElementType: TPASObjectWriterStackElType);
|
|
begin
|
|
if Assigned(FStack) then
|
|
begin
|
|
// append to stack
|
|
FStack.Add(StackEl);
|
|
end else
|
|
begin
|
|
// start stack
|
|
FStack := TFPList.Create;
|
|
end;
|
|
// create element
|
|
StackEl := TPASObjectWriterStackEl.Create;
|
|
StackEl.ElementName:=ElementName;
|
|
StackEl.ElemType:=ElementType;
|
|
DebugLn('TPASObjectWriter.StackPush Element="',ElementName,'" FStack.Count=',dbgs(FStack.Count));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.StackPop;
|
|
begin
|
|
DebugLn('TPASObjectWriter.StackPop ',dbgs(FStack.Count),' ',StackEl.ElementName);
|
|
if FStack=nil then
|
|
raise Exception.Create('TPASObjectWriter.StackPop stack empty');
|
|
StackEl.Free;
|
|
if FStack.Count > 0 then
|
|
begin
|
|
StackEl := TPASObjectWriterStackEl(FStack[FStack.Count - 1]);
|
|
FStack.Delete(FStack.Count - 1);
|
|
end else
|
|
begin
|
|
FStack.Free;
|
|
FStack := nil;
|
|
StackEl := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteIndent;
|
|
const
|
|
Indent: PChar = ' ';
|
|
var
|
|
i: Integer;
|
|
Item: TPASObjectWriterStackEl;
|
|
begin
|
|
if StackEl<>nil then begin
|
|
if StackEl.ElemType in [elComponent,elCollection,elCollectionItem] then
|
|
Stream.Write(Indent^,2);
|
|
end;
|
|
if FStack<>nil then begin
|
|
for i:=0 to FStack.Count-1 do begin
|
|
Item:=TPASObjectWriterStackEl(FStack[i]);
|
|
if Item.ElemType in [elComponent,elCollection,elCollectionItem] then
|
|
Stream.Write(Indent^,2);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteAssignment(PropertyName, Value: string);
|
|
var
|
|
s: String;
|
|
begin
|
|
WriteIndent;
|
|
s:=PropertyName+' := '+Value+';'+LineEnding;
|
|
Stream.Write(s[1],length(s));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WritePropertyAssignment(Value: string);
|
|
begin
|
|
if (StackEl=nil) or (StackEl.ElemType<>elProperty) then
|
|
raise Exception.Create('TPASObjectWriter.WritePropertyAssignment not in property');
|
|
WriteAssignment(StackEl.ElementName,Value);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteCreateComponent(CompName, CompClass,
|
|
CompOwner: string);
|
|
var
|
|
s: String;
|
|
begin
|
|
WriteIndent;
|
|
s:='if '+CompName+'=nil then '+CompName+' := '+CompClass+'.Create('+CompOwner+');'+LineEnding;
|
|
Stream.Write(s[1],length(s));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteWithDoBegin(Expr: string);
|
|
var
|
|
s: String;
|
|
begin
|
|
WriteIndent;
|
|
s:='with '+Expr+' do begin'+LineEnding;
|
|
Stream.Write(s[1],length(s));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteEnd;
|
|
const
|
|
EndTxt: String = 'end;'+LineEnding;
|
|
begin
|
|
WriteIndent;
|
|
Stream.Write(EndTxt[1],length(EndTxt));
|
|
end;
|
|
|
|
function TPASObjectWriter.StringToConstant(s: string): string;
|
|
var
|
|
i: Integer;
|
|
InString: Boolean;
|
|
begin
|
|
InString:=false;
|
|
for i:=1 to length(s) do begin
|
|
case s[i] of
|
|
#0..#31,#127..#255:
|
|
begin
|
|
if InString then
|
|
Result:=Result+'''';
|
|
InString:=false;
|
|
Result:=Result+'#'+IntToStr(ord(s[i]));
|
|
end;
|
|
else
|
|
if not InString then
|
|
Result:=Result+'''';
|
|
InString:=true;
|
|
Result:=Result+s[i];
|
|
end;
|
|
end;
|
|
if InString then Result:=Result+'''';
|
|
end;
|
|
|
|
constructor TPASObjectWriter.Create(AStream: TStream);
|
|
begin
|
|
inherited Create;
|
|
FStream:=AStream;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.BeginCollection;
|
|
begin
|
|
debugln(['TPASObjectWriter.BeginCollection']);
|
|
if StackEl.ElemType<>elProperty then
|
|
raise Exception.Create('TPASObjectWriter.BeginCollection not supported');
|
|
WriteWithDoBegin(StackEl.ElementName);
|
|
StackPush('collection',elCollection);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.BeginComponent(Component: TComponent; Flags: TFilerFlags;
|
|
ChildPos: Integer);
|
|
// TWriter expects to push two elements on the stack, which are popped by
|
|
// two EndList calls.
|
|
var
|
|
i: Integer;
|
|
Item: TPASObjectWriterStackEl;
|
|
begin
|
|
if (Component.Name='') or (not IsValidIdent(Component.Name)) then
|
|
raise Exception.Create('TPASObjectWriter.BeginComponent not pascal identifier');
|
|
if (FStack<>nil) and (FStack.Count>0) then begin
|
|
// auto create child components
|
|
for i:=0 to FStack.Count-1 do begin
|
|
Item:=TPASObjectWriterStackEl(FStack[i]);
|
|
if Item.ElemType=elComponent then begin
|
|
if Item.ElementName<>'' then
|
|
WriteCreateComponent(Component.Name,Component.ClassName,Item.ElementName);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
// enclose in "with" to create nicer code
|
|
WriteWithDoBegin(Component.Name);
|
|
|
|
StackPush(Component.Name,elComponent);
|
|
StackEl.ElementClass := Component.ClassName;
|
|
StackPush('properties',elPropertyList);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.BeginList;
|
|
begin
|
|
debugln(['TPASObjectWriter.BeginList ']);
|
|
if (StackEl<>nil) and (StackEl.ElemType=elCollection) then begin
|
|
// create collection item
|
|
WriteWithDoBegin('Add');
|
|
StackPush('collectionlist',elCollectionItem);
|
|
end else
|
|
raise Exception.Create('TPASObjectWriter.BeginList not supported');
|
|
end;
|
|
|
|
procedure TPASObjectWriter.EndList;
|
|
begin
|
|
if StackEl.ElemType = elPropertyList then begin
|
|
// end the property list and start the children list
|
|
StackPop;
|
|
StackPush('children',elChildrenList);
|
|
end else if StackEl.ElemType = elChildrenList then begin
|
|
// end the children list and the component
|
|
StackPop; // end children
|
|
StackPop; // end component
|
|
WriteEnd;
|
|
end else if StackEl.ElemType in [elCollection,elCollectionItem] then begin
|
|
StackPop;
|
|
WriteEnd;
|
|
end else
|
|
StackPop;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.BeginProperty(const PropName: String);
|
|
begin
|
|
DebugLn('TPASObjectWriter.BeginProperty "',PropName,'"');
|
|
StackPush(PropName,elProperty);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.EndProperty;
|
|
begin
|
|
StackPop;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteBinary(const Buffer; Count: Longint);
|
|
var
|
|
s: string;
|
|
begin
|
|
SetLength(s,Count);
|
|
if s<>'' then
|
|
System.Move(Buffer,s[1],length(s));
|
|
raise Exception.Create('TPASObjectWriter.WriteBinary not supported');
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteBoolean(Value: Boolean);
|
|
begin
|
|
if Value then
|
|
WritePropertyAssignment('true')
|
|
else
|
|
WritePropertyAssignment('false');
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteFloat(const Value: Extended);
|
|
begin
|
|
WritePropertyAssignment(FloatToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteSingle(const Value: Single);
|
|
begin
|
|
WritePropertyAssignment(FloatToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteCurrency(const Value: Currency);
|
|
begin
|
|
WritePropertyAssignment(FloatToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteDate(const Value: TDateTime);
|
|
begin
|
|
WritePropertyAssignment(FloatToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteIdent(const Ident: string);
|
|
begin
|
|
WritePropertyAssignment(Ident);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteInteger(Value: Int64);
|
|
begin
|
|
WritePropertyAssignment(IntToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteMethodName(const Name: String);
|
|
begin
|
|
WritePropertyAssignment('@'+Name);
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteSet(Value: LongInt; SetType: Pointer);
|
|
var
|
|
i: Integer;
|
|
Mask: LongInt;
|
|
s: String;
|
|
begin
|
|
Mask := 1;
|
|
s:='';
|
|
for i := 0 to 31 do begin
|
|
if (Value and Mask) <> 0 then begin
|
|
if s<>'' then s:=s+',';
|
|
s:=s+GetEnumName(PTypeInfo(SetType), i);
|
|
end;
|
|
Mask := Mask shl 1;
|
|
end;
|
|
WritePropertyAssignment('['+s+']');
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteString(const Value: String);
|
|
begin
|
|
WritePropertyAssignment(StringToConstant(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteWideString(const Value: WideString);
|
|
// save widestrings as utf8
|
|
begin
|
|
WritePropertyAssignment('System.UTF8Decode('+StringToConstant(System.UTF8Encode(Value))+')');
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteUInt64(Value: QWord);
|
|
begin
|
|
WritePropertyAssignment(IntToStr(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteUnicodeString(const Value: UnicodeString);
|
|
// save unicodestrings as utf8
|
|
begin
|
|
WritePropertyAssignment(StringToConstant(Value));
|
|
end;
|
|
|
|
procedure TPASObjectWriter.WriteVariant(const VarValue: Variant);
|
|
begin
|
|
case tvardata(VarValue).vtype of
|
|
varEmpty:
|
|
begin
|
|
WritePropertyAssignment('nil'); // ToDo
|
|
end;
|
|
varNull:
|
|
begin
|
|
WritePropertyAssignment('nil'); // ToDo
|
|
end;
|
|
{ all integer sizes must be split for big endian systems }
|
|
varShortInt,varSmallInt,varInteger,varInt64:
|
|
begin
|
|
WriteInteger(VarValue);
|
|
end;
|
|
varQWord:
|
|
begin
|
|
WriteUInt64(VarValue);
|
|
end;
|
|
varBoolean:
|
|
begin
|
|
WriteBoolean(VarValue);
|
|
end;
|
|
varCurrency:
|
|
begin
|
|
WriteCurrency(VarValue);
|
|
end;
|
|
varSingle:
|
|
begin
|
|
WriteSingle(VarValue);
|
|
end;
|
|
varDouble:
|
|
begin
|
|
WriteFloat(VarValue);
|
|
end;
|
|
varDate:
|
|
begin
|
|
WriteDate(VarValue);
|
|
end;
|
|
varString:
|
|
begin
|
|
WriteString(VarValue);
|
|
end;
|
|
varOleStr:
|
|
begin
|
|
WriteWideString(VarValue);
|
|
end;
|
|
else
|
|
raise EWriteError.CreateFmt('Unsupported property variant type %d', [Ord(tvardata(VarValue).vtype)]);
|
|
end;
|
|
end;
|
|
|
|
procedure TPASObjectWriter.Write(const Buffer; Count: Longint);
|
|
begin
|
|
// there can be arbitrary lots of Write calls
|
|
raise Exception.Create('TPASObjectWriter.Write not supported');
|
|
end;
|
|
|
|
end.
|
|
|