{
*****************************************************************************
* *
* This file is part of the iPhone Laz Extension *
* *
* 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. *
* *
*****************************************************************************
}
unit PlistFile;
{$mode delphi}{$h+}
interface
uses
Classes, SysUtils, DOM, XMLRead{$ifdef darwin},process{$endif};
type
TPlistType = (ltString, ltArray, ltDict, ltData, ltDate, ltBoolean, ltNumber);
{ TPListValue }
TPListValue = class(TObject)
private
fType : TPlistType;
protected
function GetValueIndex(const aname: string; force: Boolean): Integer;
public
str : WideString;
binary : array of byte;
date : TDateTime;
bool : Boolean;
number : Double;
count : Integer;
items : array of TPListValue;
names : array of string;
constructor Create(AType: TPlistType);
destructor Destroy; override;
function AddValue: Integer;
function FindValue(const nm: string): Integer;
procedure Delete(idx: Integer);
procedure Clear;
property ValueType: TPListType read fType;
end;
{ TPListFile }
TPListFile = class(TObject)
protected
public
root : TPListValue;
constructor Create;
destructor Destroy; override;
end;
function LoadFromXML(const fn: string; plist: TPListFile): Boolean; overload;
function LoadFromXML(doc: TXMLDocument; plist: TPListFile): Boolean; overload;
function WriteXML(const plist: TPlistFile): string;
function SaveToXMLFile(plist: TPlistFile; const fn: string): Boolean;
procedure DebugPlistFile(const fl: TPListFile);
function LoadFromFile(const fn: string; plist: TPListFile): Boolean;
function GetStr(dict: TPListValue; const AName: string): string; overload;
function GetStr(pl: TPListFile; const AName: string): string; overload; inline;
procedure SetStr(vl: TPListValue; const AValue: WideString); overload;
procedure SetStr(dict: TPListValue; const AName: string; const AValue: WideString); overload;
procedure SetStr(arr: TPListValue; idx: Integer; const AValue: WideString); overload;
procedure SetStr(pl: TPListFile; const AName: string; const AValue: WideString); overload; inline;
procedure SetBool(dict: TPListvalue; const AName: string; AValue: Boolean); overload;
procedure SetBool(pl: TPListFile; const AName: string; AValue: Boolean); overload; inline;
function AddItem(vl: TPListValue; const name: string; tp: TPListType = ltString): Integer; overload;
function AddItem(vl: TPListValue; tp: TPListType = ltString ): Integer; overload;
procedure AddStr(arr: TPListValue; const AValue: WideString);
function SetArr(vl: TPListValue; const AName: string): TPListValue; overload;
function SetArr(pl: TPListFile; const AName: string): TPListValue; overload; inline;
implementation
function AddItem(vl: TPListValue; const name: string; tp: TPListType = ltString): Integer; overload;
begin
Result:=AddItem(vl, tp);
if vl.ValueType=ltDict then
vl.names[Result]:=name;
end;
function AddItem(vl: TPListValue; tp: TPListType = ltString ): Integer; overload;
var
idx : integer;
begin
if not Assigned(vl) then begin
Result:=-1;
Exit;
end;
idx:=vl.AddValue;
if not Assigned(vl.items[idx]) then
vl.items[idx]:=TPListValue.Create(tp);
Result:=idx;
end;
procedure DebugValue(kv: TPListValue; const prefix: string );
var
i : integer;
begin
for i:=0 to kv.count-1 do begin
if kv.fType=ltDict then
writeln(prefix,kv.names[i],' (',kv.items[i].ValueType,')');
case kv.items[i].fType of
ltString: writeln(prefix+' ', kv.items[i].str);
ltBoolean: writeln(prefix+' ', kv.items[i].bool);
ltDict: begin
writeln;
DebugValue(kv.items[i],prefix+' ');
end;
ltArray: begin
//writeln;
DebugValue(kv.items[i],prefix+' ');
end;
end;
end;
end;
procedure DebugPlistFile(const fl: TPListFile);
begin
DebugValue(fl.root,'');
end;
function LoadFromFile(const fn: string; plist: TPListFile): Boolean;
var
st : TFileStream;
buf : string[5];
res : integer;
{$ifdef darwin}
xs : string;
m : TStringStream;
doc : TXMLDocument;
{$endif}
begin
//todo: detect plist type and convert is necessary
st:=TFileSTream.Create(fn, fmOpenRead or fmShareDenyNone);
try
SetLength(buf, 5);
res:=st.Read(buf[1], 5);
finally
st.Free;
end;
if (res=5) and (buf=''+LineEnding+
''+LineEnding+
'';
const
EncText = ['<', '>', '&'];
amp = '&';
lt = '<';
gt = '>';
function XMLEncodeText(const v: WideString): string;
var
i : integer;
j : Integer;
k : integer;
b : string;
rp : string;
begin
Result:='';
b:=UTF8Encode(v);
j:=1;
for i:=1 to length(b) do begin
if b[i] in EncText then begin
if length(Result)=0 then begin
SetLength(Result, length(b)*5);
k:=1;
end;
Move(b[j], Result[k], i-j);
inc(k, i-j);
case b[i] of
'<': rp:=lt;
'>': rp:=gt;
'&': rp:=amp;
end;
j:=i+1;
Move(rp[1], Result[k], length(rp));
inc(k, length(rp));
end;
end;
if (Result='') and (b<>'') then
Result:=b
else begin
if b='' then
Result:=''
else begin
if j','');
var
i : integer;
begin
case v.ValueType of
ltBoolean: dst.Add(pfx+boolTag[v.bool]);
ltString: dst.Add(pfx+''+XMLEncodeText(v.str)+'');
ltDict: begin
dst.Add(pfx+'');
for i:=0 to v.count-1 do begin
dst.Add(XMLPFX+''+XMLEncodeText(UTF8Decode(v.names[i]))+'');
WriteXMLValue(v.items[i], dst, pfx+XMLPFX);
end;
dst.Add(pfx+'');
end;
ltArray: begin
dst.Add(pfx+'');
for i:=0 to v.count-1 do
WriteXMLValue(v.items[i], dst, pfx+XMLPFX);
dst.Add(pfx+'');
end;
end;
end;
function WriteXML(const plist: TPlistFile): string;
var
st: TSTringList;
begin
st:=TSTringList.Create;
try
st.Add(PlistXMLPrefix);
WriteXMLValue(plist.root, st, '');
st.Add('');
Result:=st.Text;
finally
st.Free;
end;
end;
function SaveToXMLFile(plist: TPlistFile; const fn: string): Boolean;
var
fs : TFileStream;
s : string;
begin
if not Assigned(plist) then begin
Result:=false;
Exit;
end;
try
fs:=TfileStream.Create(fn, fmCreate);
try
s:=WriteXML(plist);
if length(s)>0 then
fs.Write(s[1], length(s));
Result:=true;
finally
fs.Free;
end;
except
Result:=false;
end;
end;
function LoadFromXML(const fn: string; plist: TPListFile): Boolean; overload;
var
doc : TXMLDocument;
begin
ReadXMLFile(doc, fn);
Result:=LoadFromXML(doc, plist);
doc.Free;
end;
function ReadValByNode(valnode: TDomNode): TPListValue; forward;
function NodeNameToPListType(const nd: string; var pl: TPlistType) : Boolean;
begin
Result:=true;
if nd='string' then pl:=ltString
else if nd ='array' then pl:=ltArray
else if (nd ='fasle') or (nd = 'true') then pl:=ltBoolean
else if (nd = 'dict') then pl:=ltDict
//TPlistType = (ltData, ltDate, ltNumber);
else Result:=false;
end;
procedure ReadArrVal(parent: TDomNode; kv: TPListValue);
var
idx : Integer;
nd : TDomNode;
begin
if not Assigned(parent) then Exit;
nd:=parent.FirstChild;
while Assigned(nd) do begin
idx:=kv.AddValue;
kv.items[idx]:=ReadValByNode(nd);
nd:=nd.NextSibling;
end;
end;
procedure ReadKeyVal(parent: TDomNode; kv: TPListValue);
var
nd : TDOMNode;
idx : integer;
begin
if not Assigned(parent) then Exit;
nd:=parent.FirstChild;
while Assigned(nd) do begin
if nd.NodeName='key' then begin
idx:=kv.AddValue;
kv.names[idx]:=UTF8Encode(nd.TextContent);
nd:=nd.NextSibling;
if Assigned(nd) then begin
kv.items[idx]:=ReadValByNode(nd);
nd:=nd.NextSibling;
end;
end else
nd:=nd.NextSibling;
end;
end;
function ReadValByNode(valnode: TDomNode): TPListValue;
var
tp : TPlistType;
begin
Result:=nil;
if not Assigned(valnode) then Exit;
if not NodeNameToPListType( UTF8Encode(valnode.NodeName), tp) then Exit;
Result:=TPListValue.Create(tp);
case tp of
ltBoolean: Result.bool:=(valnode.NodeName='true'); // false is false
ltString: Result.str:=valnode.TextContent;
ltArray: ReadArrVal(valnode, Result);
ltDict: ReadKeyVal(valnode, Result);
end;
end;
function LoadFromXML(doc: TXMLDocument; plist: TPListFile): Boolean; overload;
var
root : TDOMNode;
nd : TDOMNode;
r : TPListValue;
begin
Result:=false;
root:=doc.FirstChild; //('plist');
if not Assigned(root) then Exit;
while Assigned(root) do begin
if (root.NodeType = ELEMENT_NODE) and (root.NodeName = 'plist') then
Break;
root:=root.NextSibling;
end;
if not Assigned(root) then Exit;
nd:=root.FirstChild;
r:=plist.root;
plist.root:=ReadValByNode(nd);
if Assigned(plist.root) then r.Free;
Result:=true;
end;
function TPListValue.GetValueIndex(const aname: string; force: Boolean): Integer;
var
idx: integer;
begin
idx:=FindValue(aname);
if (idx<0) and (force) then begin
idx:=AddValue;
if not Assigned(Items[idx]) then
items[idx]:=TPListValue.Create(ltString);
names[idx]:=aname;
end;
Result:=idx;
end;
constructor TPListFile.Create;
begin
inherited Create;
root:=TPListValue.Create(ltDict);
end;
destructor TPListFile.Destroy;
begin
root.Free;
inherited Destroy;
end;
function GetStr(dict: TPListValue; const AName: string): string;
var
i : integer;
begin
if not Assigned(dict) or (dict.ValueType<>ltDict) then begin
Result:='';
Exit;
end;
for i:=0 to dict.count-1 do
if dict.names[i]=AName then begin
Result:=UTF8Encode(dict.items[i].str);
Exit;
end;
Result:='';
end;
procedure SetStr(vl: TPListValue; const AValue: WideString);
begin
if not Assigned(vl) then Exit;
vl.str:=AValue;
vl.fType:=ltString;
end;
procedure SetStr(dict: TPListValue; const AName: string; const AValue: WideString);
var
idx: integer;
begin
idx:=dict.GetValueIndex(AName, true);
SetStr(dict.items[idx], Avalue);
end;
procedure SetBool(dict: TPListvalue; const AName: string; AValue: Boolean); overload;
var
idx: integer;
begin
idx:=dict.GetValueIndex(AName, true);
dict.items[idx].bool:=AValue;
dict.items[idx].fType:=ltBoolean;
end;
procedure SetStr(pl: TPListFile; const AName: string; const AValue: WideString); overload; inline;
begin
SetStr(pl.root, AName, AValue);
end;
procedure SetBool(pl: TPListFile; const AName: string; AValue: Boolean); overload; inline;
begin
SetBool(pl.root, AName, AValue);
end;
procedure SetStr(arr: TPListValue; idx: Integer; const AValue: WideString); overload;
begin
if not Assigned(arr) or (arr.ValueType<>ltArray) or (idx<0) or (idx>arr.count) then Exit;
if idx=arr.count then
AddStr(arr, Avalue)
else begin
if not Assigned(arr.items[idx]) then arr.items[idx]:=TPListValue.Create(ltString);
SetStr(arr.items[idx], AValue);
end;
end;
procedure AddStr(arr: TPListValue; const AValue: WideString);
var
idx: integer;
begin
idx:=AddItem(arr, ltString);
arr.items[idx].str:=AValue;
end;
function GetStr(pl: TPListFile; const AName: string): string; overload; inline;
begin
Result:=GetStr(pl.root, AName);
end;
function SetArr(vl: TPListValue; const AName: string): TPListValue; overload;
var
idx: integer;
begin
idx:=vl.GetValueIndex(AName, true);
Result:=vl.items[idx];
Result.fType:=ltArray;
end;
function SetArr(pl: TPListFile; const AName: string): TPListValue; overload; inline;
begin
Result := SetArr(pl.root, AName);
end;
{ TPListValue }
constructor TPListValue.Create(AType: TPlistType);
begin
inherited Create;
fType:=AType;
end;
destructor TPListValue.Destroy;
begin
Clear;
inherited Destroy;
end;
function TPListValue.AddValue: Integer;
begin
if not (fType in [ltArray, ltDict]) then begin
Result:=0;
Exit;
end;
Result:=count;
if count=length(items) then begin
if count=0 then SetLength(items, 4)
else SetLength(items, length(items)*2);
if fType=ltDict then SetLength(names, length(items));
end;
inc(count);
end;
function TPListValue.FindValue(const nm: string): Integer;
var
i : integer;
begin
for i:=0 to count-1 do
if names[i]=nm then begin
Result:=i;
Exit;
end;
Result:=-1;
end;
procedure TPListValue.Delete(idx: Integer);
begin
if (idx<0) or (idx>=count) then Exit;
items[idx].Free;
if ValueType=ltDict then names[idx]:='';
if idx