
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5045 8e941d3f-bd1b-0410-a28a-d453659cc2b4
597 lines
14 KiB
ObjectPascal
597 lines
14 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
* *
|
|
* 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='<?xml') then begin
|
|
Result:=LoadFromXML(fn, plist)
|
|
end else begin
|
|
{$ifdef darwin}
|
|
// the utility is not available anywhere else but OSX
|
|
if not RunCommand('plutil', ['-convert','xml1','-o' ,'-', fn], xs) then begin
|
|
Result:=false;
|
|
Exit;
|
|
end;
|
|
m:=TStringStream.Create(xs);
|
|
try
|
|
ReadXMLFile(doc, m);
|
|
Result:=LoadFromXML(doc, plist);
|
|
doc.Free;
|
|
finally
|
|
m.Free;
|
|
end;
|
|
{$else}
|
|
Result:=false;
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
const
|
|
PlistXMLPrefix=
|
|
'<?xml version="1.0" encoding="UTF-8"?>'+LineEnding+
|
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'+LineEnding+
|
|
'<plist version="1.0">';
|
|
|
|
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<length(b) then begin
|
|
i:=length(b)+1;
|
|
Move(b[j], Result[k], i-j);
|
|
inc(k, i-j);
|
|
end;
|
|
SetLength(Result, k-1);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
XMLPFX = #9;
|
|
|
|
procedure WriteXMLValue(v: TPListValue; dst: TStrings; const pfx: string);
|
|
const
|
|
boolTag : array [boolean] of string = ('<false/>','<true/>');
|
|
var
|
|
i : integer;
|
|
begin
|
|
case v.ValueType of
|
|
ltBoolean: dst.Add(pfx+boolTag[v.bool]);
|
|
ltString: dst.Add(pfx+'<string>'+XMLEncodeText(v.str)+'</string>');
|
|
ltDict: begin
|
|
dst.Add(pfx+'<dict>');
|
|
for i:=0 to v.count-1 do begin
|
|
dst.Add(XMLPFX+'<key>'+XMLEncodeText(UTF8Decode(v.names[i]))+'</key>');
|
|
WriteXMLValue(v.items[i], dst, pfx+XMLPFX);
|
|
end;
|
|
dst.Add(pfx+'</dict>');
|
|
end;
|
|
ltArray: begin
|
|
dst.Add(pfx+'<array>');
|
|
for i:=0 to v.count-1 do
|
|
WriteXMLValue(v.items[i], dst, pfx+XMLPFX);
|
|
dst.Add(pfx+'</array>');
|
|
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('</plist>');
|
|
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<count-1 then begin
|
|
Move(items[idx+1], items[idx], (count-idx)*sizeof(TPListValue));
|
|
if ValueType=ltDict then begin
|
|
Move(names[idx+1], names[idx], (count-idx)*sizeof(PtrUInt));
|
|
names[count-1]:='';
|
|
end;
|
|
end;
|
|
dec(count);
|
|
items[count]:=nil;
|
|
end;
|
|
|
|
procedure TPListValue.Clear;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i:=0 to length(items)-1 do
|
|
if Assigned(items[i]) then
|
|
items[i].Free;
|
|
count:=0;
|
|
SetLength(names, 0);
|
|
SetLength(items, 0);
|
|
end;
|
|
|
|
end.
|
|
|