
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5045 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1052 lines
28 KiB
ObjectPascal
1052 lines
28 KiB
ObjectPascal
unit pbxcontainer;
|
|
(*-------------------------------------------------------------------------------
|
|
* by Dmitry Boyarintsev - Oct 2014 *
|
|
* *
|
|
* license: free for use, but please leave a note to the origin of the library *
|
|
* *
|
|
* PBXcontainer unit is a library to read/write the pbx formatter file as a *
|
|
* whole The file structure is made to keep the reference in a complex objects *
|
|
* struture. Hierarchial trees, lists, cycles and so on. *
|
|
* *
|
|
* It's achieved by giving a full list of objects. With each object having an *
|
|
* "id" assigned. Later in the description, the reference is specifeid by the *
|
|
* object's id. *
|
|
* *
|
|
* Currently PBXContainer would read and try to build a structure based of *
|
|
* Object Pascal RTTI. (Not sure if Delphi compatible) *
|
|
* Following rules are used *
|
|
* read/write objects are going to join the list of objects (for the futher *
|
|
* reference) *
|
|
* read-only objects should be allocated in the constructor of the parent *
|
|
* they're "inlined" in the file *
|
|
* for array of objects TPBXObjectsList must be used *
|
|
* for array of strings TPBXStringArray must be used *
|
|
* for key-value set use TPBXKeyValue class *
|
|
* string and integer properties are supported... anything else? *
|
|
* booleans are (always) written as 0 or 1 to the file *
|
|
* *
|
|
* *
|
|
* todo: add more documentions *
|
|
* *
|
|
* todo: memoty allocation and release. ObjC is using ref-counted structure. *
|
|
* do the same? similar? *
|
|
* *
|
|
* Class naming convention. *
|
|
* Any "utility" class should follow Object Pascal naming convention and start *
|
|
* with "T" (i.e. TPBXValue) *
|
|
* Any class that's expected to map an actual class (objective-c?), stored *
|
|
* in .pbx file, should go without "T" prefix. The name of the class *
|
|
* should match the one loaded/saved to the PBX file. *
|
|
* This is necessary for serializing purposes *
|
|
* Any mapped class must inherit from the base PBXObject. *
|
|
* *
|
|
* TPBXUnkObject inherites from PBXObject, but since it has an utility role *
|
|
* it falls under Object Pascal naming convention. It also stores ObjC *
|
|
* class name as a property (which is serialized as a classname) *
|
|
* *
|
|
-------------------------------------------------------------------------------*)
|
|
|
|
interface
|
|
|
|
{$ifdef fpc}{$mode delphi}{$endif}
|
|
|
|
uses
|
|
Classes, SysUtils, typinfo, pbxfile, contnrs;
|
|
|
|
type
|
|
TPBXValue = class;
|
|
{ TPBXUnkProperty }
|
|
|
|
TPBXUnkProperty = class(TObject)
|
|
private
|
|
fname: string;
|
|
public
|
|
value : TPBXValue;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
property name : string read fname;
|
|
end;
|
|
|
|
{ PBXObject }
|
|
|
|
PBXObject = class(TObject)
|
|
private
|
|
_id : string;
|
|
_fheaderComment : string;
|
|
_funkProp : TFPHashObjectList;
|
|
function GetHasUnkProp: Boolean;
|
|
protected
|
|
// collects the name of string properties that should be written out
|
|
// even if their values is an empty string.
|
|
// if property value is an empty string, it would not be written to the file
|
|
class procedure _WriteEmpty(propnames: TStrings); virtual;
|
|
function GetUnkProp: TFPHashObjectList;
|
|
public
|
|
property __id: string read _id;
|
|
property _headerComment: string read _fheaderComment write _fheaderComment;
|
|
constructor Create; virtual;
|
|
destructor Destroy; override;
|
|
function GetIsaName: string; virtual;
|
|
//todo: unknown properties are read as "strings"
|
|
// however, some strings are actually object references
|
|
// these references must be resolved (as actual objects!)
|
|
function _addUnkProp(const aname: string): TPBXUnkProperty;
|
|
property _unkProp: TFPHashObjectList read GetUnkProp;
|
|
property _hasUnkProp: Boolean read GetHasUnkProp;
|
|
end;
|
|
PBXObjectClass = class of PBXObject;
|
|
|
|
TPBXObjectsList = class(TObjectList);
|
|
TPBXStringArray = class(TStringList);
|
|
|
|
TPBXValueType = (vtString, vtArrayOfStr, vtKeyVal);
|
|
|
|
TPBXKeyValue = class;
|
|
|
|
{ TPBXValue }
|
|
|
|
TPBXValue = class(TObject)
|
|
public
|
|
valType : TPBXValueType;
|
|
str : string;
|
|
arr : TPBXStringArray;
|
|
keyval : TPBXKeyValue;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
{ TPBXKeyValue }
|
|
|
|
TPBXKeyValue = class(TFPHashObjectList)
|
|
protected
|
|
function AddVal(const name: string; atype: TPBXValueType): TPBXValue;
|
|
function GetValStr(const name: string): string;
|
|
procedure AddValStr(const name, avalue: string);
|
|
public
|
|
function AddStr(const name: string; const avalue: string = ''): TPBXValue;
|
|
function AddStrArray(const name: string): TPBXValue;
|
|
function AddKeyVal(const name: string): TPBXValue;
|
|
// it must be "public, not published"
|
|
property Str[const name: string]: string read GetValStr write AddValStr;
|
|
end;
|
|
|
|
TPBXFileInfo = record
|
|
archiveVersion : string;
|
|
objectVersion : string;
|
|
rootObject : PBXObject;
|
|
end;
|
|
|
|
{ TPBXReref }
|
|
|
|
TPBXReref = class(TObject)
|
|
instance : TObject;
|
|
propname : string;
|
|
_id : string;
|
|
constructor Create(ainstance: TObject; const apropname, aref: string);
|
|
end;
|
|
|
|
{ TPBXContainer }
|
|
|
|
TObjHashList = TFPHashObjectList;
|
|
|
|
{ TPBXUnkClass }
|
|
|
|
TPBXUnkClass = class(PBXObject)
|
|
private
|
|
fisa : string;
|
|
public
|
|
constructor CreateWithName(const AISA: string);
|
|
constructor Create; override;
|
|
function GetIsaName: string; override;
|
|
property _isa: string read fisa write fisa;
|
|
end;
|
|
|
|
TPBXContainer = class(TObject)
|
|
protected
|
|
procedure ReadObjects(p: TPBXParser; objs: TObjHashList);
|
|
function AllocObject(const nm: string): PBXObject;
|
|
public
|
|
function ReadFile(s: TStream; var AFileInfo: TPBXFileInfo): Boolean;
|
|
end;
|
|
|
|
procedure TestContainer(const buf: string);
|
|
|
|
procedure PBXRegisterClass(aclass: PBXObjectClass);
|
|
function PBXFindClass(const aclassname: string): PBXObjectClass;
|
|
|
|
function PBXReadObjectsListRef(p: TPBXParser; obj: PBXObject; propName: string; refs: TList): Boolean;
|
|
function PBXReadStringArray(p: TPBXParser; arr: TPBXStringArray): Boolean;
|
|
function PBXReadKeyValue(p: TPBXParser; kv: TPBXKeyValue): Boolean;
|
|
function PBXReadClass(p: TPBXParser; obj: PBXObject; refs: TList): Boolean;
|
|
procedure PBXReref(objs: TObjHashList; refs: TList);
|
|
|
|
function PBXWriteContainer(const FileInfo: TPBXFileInfo; AssignRef: Boolean = true): string;
|
|
procedure PBXWriteObjArray( w: TPBXWriter; list: TPBXObjectsList );
|
|
procedure PBXWriteStrArray( w: TPBXWriter; list: TPBXStringArray );
|
|
procedure PBXWriteKeyValue( w: TPBXWriter; kv: TPBXKeyValue );
|
|
procedure PBXWriteObj(pbx: PBXObject; w: TPBXWriter; WriteEmpty: TStrings);
|
|
|
|
{ assigns reference IDs to each object in the list. "ID" is 24 charactewr long hex-string }
|
|
procedure PBXAssignRef(list: TList);
|
|
|
|
{ Returns the list of objects that should populate the "objects" section of pbx file }
|
|
procedure PBXGatherObjects(obj: TObject; srz: TList);
|
|
|
|
procedure PBXKeyValsCopy(src, dst: TPBXKeyValue);
|
|
procedure PBXValueCopy(src, dst: TPBXValue);
|
|
procedure PBXStringArrayCopy(src, dst: TPBXStringArray);
|
|
|
|
implementation
|
|
|
|
var
|
|
pbxClassList : TStringList;
|
|
|
|
function PBXReadKeyValue(p: TPBXParser; kv: TPBXKeyValue): Boolean;
|
|
var
|
|
et : TPBXEntity;
|
|
v : TPBXValue;
|
|
begin
|
|
et:=p.FetchNextEntity;
|
|
while et<>etCloseObject do begin
|
|
case et of
|
|
etValue: kv.AddStr(p.Name, p.Value);
|
|
etOpenArray: begin
|
|
v:=kv.AddStrArray(p.Name);
|
|
PBXReadStringArray(p, v.arr);
|
|
end;
|
|
etOpenObject: begin
|
|
v:=kv.AddKeyVal(p.Name);
|
|
PBXReadKeyValue(p, v.keyval);
|
|
end;
|
|
else
|
|
Result:=false;
|
|
Exit;
|
|
end;
|
|
et:=p.FetchNextEntity;
|
|
end;
|
|
Result:=True;
|
|
end;
|
|
|
|
procedure PBXStringArrayCopy(src, dst: TPBXStringArray);
|
|
begin
|
|
if not Assigned(src) or not Assigned(dst) then Exit;
|
|
src.Assign(dst);
|
|
end;
|
|
|
|
procedure PBXValueCopy(src, dst: TPBXValue);
|
|
begin
|
|
if not Assigned(src) or not Assigned(dst) then Exit;
|
|
dst.valType:=src.valType;
|
|
case dst.valType of
|
|
vtString: dst.str:=src.str;
|
|
vtArrayOfStr: begin
|
|
if not Assigned(dst.arr) then dst.arr:=TPBXStringArray.Create;
|
|
PBXStringArrayCopy(src.arr, dst.arr);
|
|
end;
|
|
vtKeyVal: begin
|
|
if not Assigned(dst.keyval) then dst.keyval:=TPBXKeyValue.Create(true);
|
|
PBXKeyValsCopy(src.keyval, dst.keyval);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure PBXKeyValsCopy(src, dst: TPBXKeyValue);
|
|
var
|
|
svl : TPBXValue;
|
|
nm : string;
|
|
i : Integer;
|
|
dvl : TPBXValue;
|
|
begin
|
|
if not Assigned(src) or not Assigned(dst) then Exit;
|
|
for i:=0 to src.Count-1 do begin
|
|
nm:=src.NameOfIndex(i);
|
|
svl:=TPBXValue(src.Items[i]);
|
|
dvl:=dst.AddVal(nm, svl.valType);
|
|
PBXValueCopy(svl, dvl);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TestContainer(const buf: string);
|
|
var
|
|
c : TPBXContainer;
|
|
st : TStringStream;
|
|
info : TPBXFileInfo;
|
|
begin
|
|
c:= TPBXContainer.Create;
|
|
st := TStringStream.Create(buf);
|
|
try
|
|
c.ReadFile(st, info);
|
|
writeln('arch ver: ',info.archiveVersion);
|
|
writeln(' obj ver: ',info.objectVersion);
|
|
writeln('root obj: ', PtrUInt( info.rootObject ));
|
|
finally
|
|
st.Free;
|
|
c.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure PBXRegisterClass(aclass: PBXObjectClass);
|
|
begin
|
|
pbxClassList.AddObject(aclass.ClassName, TObject(aclass));
|
|
end;
|
|
|
|
function PBXFindClass(const aclassname: string): PBXObjectClass;
|
|
var
|
|
i : integer;
|
|
begin
|
|
i:=pbxClassList.IndexOf(aclassname);
|
|
if i<0 then Result:=nil
|
|
else Result:=PBXObjectClass(pbxClassList.Objects[i]);
|
|
|
|
end;
|
|
|
|
{ TPBXUnkProperty }
|
|
|
|
constructor TPBXUnkProperty.Create;
|
|
begin
|
|
inherited Create;
|
|
value:=TPBXValue.Create;
|
|
end;
|
|
|
|
destructor TPBXUnkProperty.Destroy;
|
|
begin
|
|
value.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TPBXUnkClass }
|
|
|
|
constructor TPBXUnkClass.CreateWithName(const AISA: string);
|
|
begin
|
|
fisa:=AISA;
|
|
Create;
|
|
end;
|
|
|
|
constructor TPBXUnkClass.Create;
|
|
begin
|
|
inherited Create;
|
|
end;
|
|
|
|
function TPBXUnkClass.GetIsaName: string;
|
|
begin
|
|
Result:=fisa;
|
|
end;
|
|
|
|
{ TPBXValue }
|
|
|
|
destructor TPBXValue.Destroy;
|
|
begin
|
|
arr.Free;
|
|
keyval.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TPBXKeyValue }
|
|
|
|
procedure TPBXKeyValue.AddValStr(const name, AValue: string);
|
|
begin
|
|
AddStr(name, Avalue);
|
|
end;
|
|
|
|
function TPBXKeyValue.AddVal(const name: string; atype: TPBXValueType): TPBXValue;
|
|
begin
|
|
Result:=TPBXValue.Create;
|
|
Result.valType:=atype;
|
|
case atype of
|
|
vtKeyVal: Result.keyval:=TPBXKeyValue.Create(true);
|
|
vtArrayOfStr: Result.arr:=TPBXStringArray.Create;
|
|
end;
|
|
Add(name, Result);
|
|
end;
|
|
|
|
function TPBXKeyValue.GetValStr(const name: string): string;
|
|
var
|
|
vl : TPBXValue;
|
|
begin
|
|
vl:=TPBXValue(Self.Find(name));
|
|
if not Assigned(vl)
|
|
then Result:=''
|
|
else Result:=vl.str;
|
|
end;
|
|
|
|
function TPBXKeyValue.AddStr(const name: string; const avalue: string): TPBXValue;
|
|
begin
|
|
Result:=AddVal(name, vtString);
|
|
Result.str:=avalue;
|
|
end;
|
|
|
|
function TPBXKeyValue.AddStrArray(const name: string): TPBXValue;
|
|
begin
|
|
Result:=AddVal(name, vtArrayOfStr);
|
|
end;
|
|
|
|
function TPBXKeyValue.AddKeyVal(const name: string): TPBXValue;
|
|
begin
|
|
Result:=AddVal(name, vtKeyVal);
|
|
end;
|
|
|
|
|
|
{ TPBXReref }
|
|
|
|
constructor TPBXReref.Create(ainstance: TObject; const apropname, aref: string);
|
|
begin
|
|
inherited Create;
|
|
instance := ainstance;
|
|
propname := apropname;
|
|
_id := aref;
|
|
end;
|
|
|
|
{ TPBXObject }
|
|
|
|
function PBXObject.GetHasUnkProp: Boolean;
|
|
begin
|
|
Result:=Assigned(_funkProp) and (_funkProp.Count>0);
|
|
end;
|
|
|
|
class procedure PBXObject._WriteEmpty(propnames: TStrings);
|
|
begin
|
|
|
|
end;
|
|
|
|
function PBXObject.GetUnkProp: TFPHashObjectList;
|
|
begin
|
|
if not Assigned(_funkProp) then _funkProp:=TFPHashObjectList.Create(true);
|
|
Result:=_funkProp;
|
|
end;
|
|
|
|
constructor PBXObject.Create;
|
|
begin
|
|
end;
|
|
|
|
destructor PBXObject.Destroy;
|
|
begin
|
|
_funkProp.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function PBXObject.GetIsaName: string;
|
|
begin
|
|
Result:=ClassName;
|
|
end;
|
|
|
|
function PBXObject._addUnkProp(const aname: string): TPBXUnkProperty;
|
|
begin
|
|
Result:=TPBXUnkProperty.Create;
|
|
Result.fname:=aname;
|
|
_unkProp.Add(aname, Result);
|
|
end;
|
|
|
|
{ TPBXContainer }
|
|
|
|
procedure PBXReref(objs: TObjHashList; refs: TList);
|
|
var
|
|
i : integer;
|
|
refobj : TObject;
|
|
r : TPBXReref;
|
|
prp : PPropInfo;
|
|
pcls : TObject;
|
|
begin
|
|
for i:=0 to refs.Count-1 do begin
|
|
r := TPBXReref(refs[i]);
|
|
refobj:=objs.Find(r._id);
|
|
if Assigned(refobj) then begin
|
|
prp:=GetPropInfo(r.instance, r.propname);
|
|
if prp^.PropType^.Kind=tkClass then begin
|
|
pcls:=GetObjectProp(r.instance, r.propname);
|
|
if pcls is TPBXObjectsList then begin
|
|
TPBXObjectsList(pcls).Add(refobj);
|
|
end else begin
|
|
//writeln('setting prop: ', r.propname,' ');
|
|
SetObjectProp(r.instance, r.propname, refobj);
|
|
end;
|
|
end;
|
|
end;
|
|
//else writeln('no object found! ', r._id);
|
|
end;
|
|
end;
|
|
|
|
procedure TPBXContainer.ReadObjects(p: TPBXParser; objs: TObjHashList);
|
|
var
|
|
tk : TPBXEntity;
|
|
id : string;
|
|
cls : string;
|
|
obj : PBXObject;
|
|
i : Integer;
|
|
refs : TList;
|
|
cmt : string;
|
|
begin
|
|
tk:=p.FetchNextEntity;
|
|
refs:=TList.Create;
|
|
try
|
|
while not (tk in [etError, etCloseObject]) do begin
|
|
if tk=etOpenObject then begin
|
|
id:=p.Name;
|
|
cmt:=p.LastComment;
|
|
cls:='';
|
|
p.FetchNextEntity;
|
|
if (p.CurEntity = etValue) and (p.Name = 'isa') then begin
|
|
cls:=p.Value;
|
|
obj:=AllocObject(cls);
|
|
if not Assigned(obj) then
|
|
obj:=TPBXUnkClass.CreateWithName(cls);
|
|
if Assigned(obj) then begin
|
|
obj._headerComment:=cmt;
|
|
obj._id:=id;
|
|
PBXReadClass(p, obj, refs);
|
|
objs.Add(id, obj);
|
|
end else begin
|
|
PBXParserSkipLevel(p);
|
|
end;
|
|
|
|
end else
|
|
PBXParserSkipLevel(p);
|
|
end;
|
|
tk:=p.FetchNextEntity;
|
|
end;
|
|
|
|
PBXReref(objs, refs);
|
|
|
|
finally
|
|
for i:=0 to refs.Count-1 do TObject(refs[i]).Free;
|
|
refs.Free;
|
|
end;
|
|
end;
|
|
|
|
function TPBXContainer.AllocObject(const nm: string): PBXObject;
|
|
var
|
|
cls : PBXObjectClass;
|
|
begin
|
|
cls:=PBXFindClass(nm);
|
|
if not Assigned(cls) then Result:=nil
|
|
else Result:=cls.Create;
|
|
end;
|
|
|
|
function TPBXContainer.ReadFile(s: TStream; var AFileInfo: TPBXFileInfo): Boolean;
|
|
var
|
|
p : TPBXParser;
|
|
buf : string;
|
|
tk : TPBXEntity;
|
|
root : string;
|
|
objs : TObjHashList;
|
|
rt : TObject;
|
|
begin
|
|
Result:=false;
|
|
AFileInfo.archiveVersion:='';
|
|
AFileInfo.objectVersion:='';
|
|
AFileInfo.rootObject:=nil;
|
|
|
|
if not Assigned(s) then Exit;
|
|
SetLength(buf, s.Size);
|
|
s.Read(buf[1], length(buf));
|
|
|
|
objs:=TObjHashList.Create(False);
|
|
p:=TPBXParser.Create;
|
|
try
|
|
p.scanner.SetBuf(buf);
|
|
if p.FetchNextEntity <> etOpenObject then Exit;
|
|
|
|
|
|
tk:=p.FetchNextEntity;
|
|
while not (tk in [etEOF,etError]) do begin
|
|
if tk = etValue then begin
|
|
if p.Name='archiveVersion' then AFileInfo.archiveVersion:=p.Value
|
|
else if p.Name='objectVersion' then AFileInfo.objectVersion:=p.Value
|
|
else if p.Name='rootObject' then root:=p.Value;
|
|
end else if (tk=etOpenObject) and (p.Name = 'objects') then begin
|
|
ReadObjects(p, objs);
|
|
end;
|
|
tk:=p.FetchNextEntity;
|
|
end;
|
|
|
|
if tk=etError then begin
|
|
{for i:=0 to objs.Count-1 do
|
|
TObject(objs[i]).Free;}
|
|
Result:=false;
|
|
Exit;
|
|
end;
|
|
|
|
rt:=objs.Find(root);
|
|
|
|
if Assigned(rt) and (rt is PBXObject) then
|
|
AFileInfo.rootObject:=PBXObject(rt);
|
|
Result:=true;
|
|
finally
|
|
objs.Free;
|
|
p.Free;
|
|
end;
|
|
end;
|
|
|
|
function PBXReadObjectsListRef(p: TPBXParser; obj: PBXObject; propName: string; refs: TList): Boolean;
|
|
begin
|
|
Result:=true;
|
|
p.FetchNextEntity;
|
|
while not (p.CurEntity in [etCloseArray, etEOF, etError]) do begin
|
|
if p.CurEntity <> etValue then begin
|
|
Result:=false;
|
|
Exit;
|
|
end;
|
|
if p.Value<>'' then
|
|
refs.Add ( TPBXReref.Create( obj, propName, p.Value ));
|
|
p.FetchNextEntity;
|
|
end;
|
|
end;
|
|
|
|
function PBXReadStringArray(p: TPBXParser; arr: TPBXStringArray): Boolean;
|
|
begin
|
|
Result:=true;
|
|
p.FetchNextEntity;
|
|
while not (p.CurEntity in [etCloseArray, etEOF, etError]) do begin
|
|
if p.CurEntity <> etValue then begin
|
|
Result:=false;
|
|
Exit;
|
|
end;
|
|
arr.Add(p.Value);
|
|
p.FetchNextEntity;
|
|
end;
|
|
end;
|
|
|
|
function GuessProperty(p: TPBXParser; uprop: TPBXUnkProperty): Boolean;
|
|
begin
|
|
case p.CurEntity of
|
|
etValue: begin
|
|
// assuming string. Object Ref will be resolved later!
|
|
uprop.value.str:=p.Value;
|
|
uprop.value.valType:=vtString;
|
|
Result:=true;
|
|
end;
|
|
etOpenArray: begin
|
|
// assuming array of strings. Array of Objects will be resolved later!
|
|
uprop.value.arr:=TPBXStringArray.Create;
|
|
uprop.value.valType:=vtArrayOfStr;
|
|
Result:=PBXReadStringArray(p, uprop.value.arr);
|
|
end;
|
|
etOpenObject: begin
|
|
uprop.value.keyval:=TPBXKeyValue.Create;
|
|
uprop.value.valType:=vtKeyVal;
|
|
Result:=PBXReadKeyValue(p, uprop.value.keyval);
|
|
end;
|
|
else
|
|
Result:=false;
|
|
end;
|
|
end;
|
|
|
|
function PBXReadClass(p: TPBXParser; obj: PBXObject; refs: TList): Boolean;
|
|
var
|
|
tk : TPBXEntity;
|
|
lvl : Integer;
|
|
prp : PPropInfo;
|
|
pobj : TObject;
|
|
pk : TTypeKind;
|
|
uprop : TPBXUnkProperty;
|
|
begin
|
|
lvl:=p.Level;
|
|
tk:=p.FetchNextEntity;
|
|
while p.Level>=lvl {tk<>tkCurlyBraceClose} do begin
|
|
prp:=GetPropInfo(obj, p.Name);
|
|
if Assigned(prp) then begin
|
|
pk:=prp^.PropType^.Kind;
|
|
if pk=tkClass then
|
|
pobj:=GetObjectProp(obj, prp)
|
|
else
|
|
pobj:=nil;
|
|
|
|
if tk=etValue then begin
|
|
|
|
case pk of
|
|
tkClass: begin
|
|
//writeln('ref for: ',p.Name,' to ', p.Value);
|
|
refs.Add( TPBXReref.Create(obj, p.Name, p.Value))
|
|
end;
|
|
tkInteger, tkInt64, tkQWord: begin
|
|
SetInt64Prop(obj, p.Name, StrToIntDef(p.Value, GetInt64Prop(obj, p.Name)) );
|
|
end;
|
|
tkBool: begin
|
|
SetOrdProp(obj, p.Name, StrToIntDef(p.Value, GetInt64Prop(obj, p.Name)) );
|
|
end;
|
|
else
|
|
SetStrProp(obj, p.Name, p.Value);
|
|
end;
|
|
end else begin
|
|
{write( p.CurEntity,' ',p.Name,' ',PtrUInt(pobj));
|
|
if Assigned(pobj) then write(' ', pobj.ClassName);
|
|
writeln;}
|
|
if (pobj is TPBXObjectsList) and (tk = etOpenArray) then begin
|
|
Result:=PBXReadObjectsListRef(p, obj, p.Name, refs);
|
|
if not Result then Exit;
|
|
end else if (pobj is TPBXStringArray) and (tk = etOpenArray) then begin
|
|
Result:=PBXReadStringArray(p, TPBXStringArray(pobj) );
|
|
if not Result then Exit;
|
|
end else if (pobj is TPBXKeyValue) and (tk = etOpenObject) then begin
|
|
Result:=PBXReadKeyValue(p, TPBXKeyValue(pobj) );
|
|
if not Result then Exit;
|
|
end else
|
|
// array of object
|
|
PBXParserSkipLevel(p);
|
|
end;
|
|
end else begin
|
|
//writeln(obj.ClassName, ': unkown property: ', p.Name);
|
|
uprop:=obj._addUnkProp(p.Name);
|
|
GuessProperty(p,uprop);
|
|
{if tk <> etValue then
|
|
PBXParserSkipLevel(p);}
|
|
end;
|
|
|
|
tk:=p.FetchNextEntity;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure PBXGatherObjects(obj: TObject; srz: TList);
|
|
var
|
|
plist : PPropList;
|
|
cnt : Integer;
|
|
i : Integer;
|
|
j : Integer;
|
|
k : Integer;
|
|
arr : TPBXObjectsList;
|
|
ch : TObject;
|
|
ach : TObject;
|
|
kind : TTypeKind;
|
|
const
|
|
FlagGet = 3; // 1 + 2 //ptField = 0;
|
|
FlagSet = 12; // 4 + 8 , 16 + 32 //ptStatic = 1;
|
|
FlagSP = 16 + 32; //ptVirtual = 2;
|
|
FlagIdx = 64; //ptConst = 3;
|
|
begin
|
|
if (not Assigned(obj)) or (not Assigned(srz)) then Exit;
|
|
|
|
srz.Add(obj);
|
|
j:=0;
|
|
while j<srz.Count do begin
|
|
obj:=TObject(srz[j]);
|
|
|
|
plist:=nil;
|
|
cnt:=GetPropList(obj, plist);
|
|
if Assigned(plist) then begin
|
|
for i:=0 to cnt-1 do begin
|
|
kind := plist^[i]^.PropType^.Kind;
|
|
if (kind<>tkClass) then Continue;
|
|
|
|
ch:=GetObjectProp(obj, plist^[i] );
|
|
if not Assigned(ch) then Continue;
|
|
|
|
if (plist^[i]^.PropProcs and FlagSet <> FlagSet) then begin
|
|
if srz.IndexOf(ch)<0 then
|
|
srz.Add ( ch );
|
|
end else if ch is TPBXObjectsList then begin
|
|
|
|
arr:=TPBXObjectsList(ch);
|
|
for k:=0 to arr.Count-1 do begin
|
|
ach:=arr[k];
|
|
if srz.IndexOf(ach)<0 then srz.Add(ach);
|
|
end;
|
|
end;
|
|
end;
|
|
Freemem(plist);
|
|
end;
|
|
inc(j);
|
|
end;
|
|
end;
|
|
|
|
function GetNextID(hash: TFPHashObjectList; var id: Int64): string;
|
|
begin
|
|
repeat
|
|
Result:=IntToHex(id, 24);
|
|
inc(id);
|
|
until not Assigned(hash.Find(Result));
|
|
end;
|
|
|
|
|
|
procedure PBXAssignRef(list: TList);
|
|
var
|
|
i : Integer;
|
|
p : PBXObject;
|
|
id: Int64;
|
|
usedid: TFPHashObjectList;
|
|
begin
|
|
if not Assigned(list) then Exit;
|
|
usedid:=TFPHashObjectList.Create(false);
|
|
try
|
|
for i:=0 to list.Count-1 do begin
|
|
p:=PBXObject(list[i]);
|
|
if p._id<>'' then usedid.Add(p._id, p);;
|
|
end;
|
|
|
|
|
|
id:=2; // root! :)
|
|
for i:=0 to list.Count-1 do begin
|
|
p:=PBXObject(list[i]);
|
|
if not Assigned(p) then Continue;
|
|
if (p._id='') then begin
|
|
p._id:=GetNextID(usedid, id);
|
|
end;
|
|
end;
|
|
finally
|
|
usedid.Free;
|
|
end;
|
|
// 0AFA6EA519F60EFD004C8FD9
|
|
// 123456789012345678901234
|
|
end;
|
|
|
|
procedure PBXWriteStrArray( w: TPBXWriter; list: TPBXStringArray );
|
|
var
|
|
i : Integer;
|
|
begin
|
|
w.OpenBlock('(');
|
|
for i:=0 to list.Count-1 do
|
|
w.WriteArrValue(list.Strings[i]);
|
|
w.CloseBlock(')');
|
|
end;
|
|
|
|
|
|
procedure PBXWriteObjArray( w: TPBXWriter; list: TPBXObjectsList );
|
|
var
|
|
i : Integer;
|
|
pbx : PBXObject;
|
|
begin
|
|
for i:=0 to list.Count-1 do begin
|
|
pbx:=PBXObject(list[i]);
|
|
w.WriteArrValue(pbx._id, pbx._headerComment);
|
|
end;
|
|
end;
|
|
|
|
procedure PBXWriteKeyValue( w: TPBXWriter; kv: TPBXKeyValue );
|
|
var
|
|
i : Integer;
|
|
v : TPBXValue;
|
|
nm : string;
|
|
begin
|
|
w.OpenBlock( '{' );
|
|
for i:=0 to kv.Count-1 do begin
|
|
v:=TPBXValue(kv.Items[i]);
|
|
nm:=kv.NameOfIndex(i);
|
|
w.WriteName(nm);
|
|
case v.valType of
|
|
vtString: w.WriteValue(v.str);
|
|
vtArrayOfStr: PBXWriteStrArray(w, v.arr);
|
|
vtKeyVal: PBXWriteKeyValue(w, v.keyval);
|
|
end;
|
|
end;
|
|
w.CloseBlock( '}' );
|
|
end;
|
|
|
|
type
|
|
|
|
{ TWriteProp }
|
|
|
|
TWriteProp = class(TObject)
|
|
propIdx : integer;
|
|
unk : TPBXUnkProperty;
|
|
constructor Create(aidx: Integer); overload;
|
|
constructor Create(aunk: TPBXUnkProperty); overload;
|
|
end;
|
|
|
|
procedure PBXWriteObj(pbx: PBXObject; w: TPBXWriter; WriteEmpty: TStrings);
|
|
var
|
|
p : PPropList;
|
|
cnt : Integer;
|
|
i,j : Integer;
|
|
isMan : Boolean;
|
|
vl : string;
|
|
sobj : TObject;
|
|
nm : string;
|
|
vcmt : string;
|
|
isstr : Boolean;
|
|
|
|
names : TStringList; // used for sorting.
|
|
// todo: find a better way for sort by names
|
|
wp : TWriteProp;
|
|
up : TPBXUnkProperty;
|
|
begin
|
|
|
|
w.WriteName(pbx._id, pbx._headerComment);
|
|
|
|
isMan:=(pbx.GetIsaName='PBXFileReference') or (pbx.GetIsaName='PBXBuildFile');
|
|
if isMan then w.ManualLineBreak:=true;
|
|
|
|
w.OpenBlock('{');
|
|
w.WriteNamedValue('isa', pbx.GetIsaName);
|
|
|
|
p:=nil;
|
|
cnt:=GetPropList(pbx, p);
|
|
|
|
//todo: I don't like this sorting at all!
|
|
// but it appears to be the most common available
|
|
names:=TStringList.Create;
|
|
try
|
|
names.OwnsObjects:=true;
|
|
|
|
for i:=0 to cnt-1 do begin
|
|
wp:=TWriteProp.Create(i);
|
|
names.AddObject(p^[i].Name, wp);
|
|
//names.AddObject(p^[i].Name, TObject(PtrInt(i)));
|
|
end;
|
|
//for i:=0 to
|
|
if pbx._hasUnkProp then begin
|
|
for i:=0 to pbx._unkProp.Count-1 do begin
|
|
up:=TPBXUnkProperty(pbx._unkProp[i]);
|
|
wp:=TWriteProp.Create(up);
|
|
names.AddObject(up.name, wp);
|
|
end;
|
|
end;
|
|
|
|
names.Sort;
|
|
|
|
for j:=0 to names.Count-1 do begin
|
|
//i:=Integer(PtrInt(names.Objects[j]));
|
|
|
|
vl:='';
|
|
vcmt:='';
|
|
isstr:=false;
|
|
sobj:=nil;
|
|
|
|
wp:=TWriteProp(names.Objects[j]);
|
|
if not Assigned(wp.unk) then begin
|
|
i:=wp.propIdx;
|
|
nm:=p^[i].Name;
|
|
if p^[i].PropType.Kind=tkClass then begin
|
|
sobj:=GetObjectProp(pbx, p^[i])
|
|
end else begin
|
|
if p^[i].PropType.Kind in [tkAString, tkString] then begin
|
|
vl:=GetStrProp(pbx,p^[i]);
|
|
isstr:=(vl<>'') or (WriteEmpty.indexOf(nm)>=0);
|
|
end else if p^[i].PropType.Kind in [tkInteger, tkInt64, tkQWord] then begin
|
|
vl:=IntToStr(GetInt64Prop(pbx, p^[i]));
|
|
isstr:=(vl<>'') or (WriteEmpty.indexOf(nm)>=0);
|
|
end else if p^[i].PropType.Kind = tkBool then begin
|
|
isstr:=PtrUInt(p^[i].Default)<>PtrUInt(p^[i].GetProc);
|
|
if isstr then vl:=IntToStr(GetOrdProp(pbx, p^[i]));
|
|
end;
|
|
end;
|
|
end else begin
|
|
nm:=wp.unk.name;
|
|
case wp.unk.value.valType of
|
|
vtArrayOfStr: sobj:=wp.unk.value.arr;
|
|
vtKeyVal: sobj:=wp.unk.value.keyval;
|
|
else
|
|
isstr:=true;
|
|
vl:=wp.unk.value.str;
|
|
end;
|
|
end;
|
|
|
|
if Assigned(sobj) then begin
|
|
if sobj is PBXObject then begin
|
|
vl:=PBXObject(sobj)._id;
|
|
vcmt:=PBXObject(sobj)._headerComment;
|
|
isstr:=vl<>'';
|
|
end else if sobj is TPBXObjectsList then begin
|
|
w.WriteName(nm); w.OpenBlock('(');
|
|
PBXWriteObjArray( w, TPBXObjectsList(sobj) );
|
|
w.CloseBlock(')');
|
|
end else if sobj is TPBXStringArray then begin
|
|
w.WriteName(nm);
|
|
PBXWriteStrArray( w, TPBXStringArray(sobj) );
|
|
end else if sobj is TPBXKeyValue then begin
|
|
w.WriteName(nm);
|
|
PBXWriteKeyValue(w, TPBXKeyValue(sobj));
|
|
end;
|
|
end;
|
|
|
|
if isstr then begin
|
|
w.WriteName(nm);
|
|
w.WriteValue(vl,vcmt);
|
|
end;
|
|
|
|
end;
|
|
|
|
if isMan then w.ManualLineBreak:=false;
|
|
w.CloseBlock('}');
|
|
finally
|
|
names.Free;
|
|
if Assigned(p) then Freemem(p);
|
|
end;
|
|
end;
|
|
|
|
function PBXWriteContainer(const FileInfo: TPBXFileInfo; AssignRef: Boolean = true): string;
|
|
var
|
|
lst : TList;
|
|
st : TStringList;
|
|
i : Integer;
|
|
w : TPBXWriter;
|
|
sc : string;
|
|
pbx : PBXObject;
|
|
emp : TStringList;
|
|
begin
|
|
lst:=TList.Create;
|
|
st:=TStringList.Create;
|
|
emp:=TStringList.Create;
|
|
try
|
|
PBXGatherObjects(fileInfo.rootObject, lst);
|
|
if AssignRef then PBXAssignRef(lst);
|
|
|
|
for i:=0 to lst.Count-1 do begin
|
|
st.AddObject( PBXObject(lst[i]).GetIsaName+' '+PBXObject(lst[i])._id, PBXObject(lst[i]));
|
|
end;
|
|
st.Sort;
|
|
|
|
w:=TPBXWriter.Create;
|
|
try
|
|
sc:='';
|
|
w.WriteRaw('// !$*UTF8*$!');
|
|
w.WriteLineBreak;
|
|
w.OpenBlock('{');
|
|
w.WriteNamedValue('archiveVersion', FileInfo.archiveVersion);
|
|
w.WriteName('classes'); w.OpenBlock('{'); w.CloseBlock('}');
|
|
w.WriteNamedValue('objectVersion', FileInfo.objectVersion);
|
|
w.WriteName('objects'); w.OpenBlock('{');
|
|
for i:=0 to st.Count-1 do begin
|
|
pbx:=PBXObject(st.Objects[i]);
|
|
if sc<>pbx.GetIsaName then begin
|
|
if sc<>'' then begin
|
|
w.WriteLineComment('End '+sc+' section');
|
|
end;
|
|
sc:=pbx.GetIsaName;
|
|
w.WriteLineBreak();
|
|
w.WriteLineComment('Begin '+sc+' section');
|
|
emp.Clear;
|
|
pbx._WriteEmpty(emp);
|
|
end;
|
|
PBXWriteObj(pbx, w, emp);
|
|
end;
|
|
|
|
if sc<>'' then w.WriteLineComment('End '+sc+' section');
|
|
w.CloseBlock('}');
|
|
|
|
w.WriteNamedValue('rootObject', FileInfo.rootObject._id, FileInfo.rootObject._headerComment);
|
|
w.CloseBlock('}');
|
|
Result:=w.Buffer;
|
|
finally
|
|
w.Free;
|
|
end;
|
|
|
|
finally
|
|
st.Free;
|
|
lst.Free;
|
|
emp.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TWriteProp }
|
|
|
|
constructor TWriteProp.Create(aidx: Integer);
|
|
begin
|
|
inherited Create;
|
|
propIdx:=aidx;
|
|
end;
|
|
|
|
constructor TWriteProp.Create(aunk: TPBXUnkProperty);
|
|
begin
|
|
inherited Create;
|
|
unk:=aunk;
|
|
end;
|
|
|
|
initialization
|
|
pbxClassList := TStringList.Create;
|
|
|
|
finalization
|
|
pbxClassList.Free;
|
|
|
|
|
|
|
|
end.
|