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 jtkClass) 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.