fpc/packages/fcl-json/src/jsonconf.pp
michael 970188907e * Fix bug ID #35113
git-svn-id: trunk@41473 -
2019-02-25 21:04:39 +00:00

850 lines
21 KiB
ObjectPascal

{
This file is part of the Free Component Library
Implementation of TJSONConfig class
Copyright (c) 2007 Michael Van Canneyt michael@freepascal.org
See the file COPYING.FPC, 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.
**********************************************************************}
{
TJSONConfig enables applications to use JSON files for storing their
configuration data
}
{$IFDEF FPC}
{$MODE objfpc}
{$H+}
{$ENDIF}
unit jsonConf;
interface
uses
SysUtils, Classes, fpjson, jsonscanner, jsonparser;
Const
DefaultJSONOptions = [joUTF8,joComments];
type
EJSONConfigError = class(Exception);
(* ********************************************************************
"APath" is the path and name of a value: A JSON configuration file
is hierachical. "/" is the path delimiter, the part after the last
"/" is the name of the value. The path components will be mapped
to nested JSON objects, with the name equal to the part. In practice
this means that "/my/path/value" will be written as:
{
"my" : {
"path" : {
"value" : Value
}
}
}
******************************************************************** *)
{ TJSONConfig }
TJSONConfig = class(TComponent)
private
FFilename: String;
FFormatIndentSize: Integer;
FFormatoptions: TFormatOptions;
FFormatted: Boolean;
FJSONOptions: TJSONOptions;
FKey: TJSONObject;
procedure DoSetFilename(const AFilename: String; ForceReload: Boolean);
procedure SetFilename(const AFilename: String);
procedure SetJSONOptions(AValue: TJSONOptions);
Function StripSlash(Const P : UnicodeString) : UnicodeString;
protected
FJSON: TJSONObject;
FModified: Boolean;
Procedure LoadFromFile(Const AFileName : String);
Procedure LoadFromStream(S : TStream); virtual;
procedure Loaded; override;
function FindNodeForValue(const APath: UnicodeString; aExpectedType: TJSONDataClass; out AParent: TJSONObject; out ElName: UnicodeString): TJSONData;
function FindPath(Const APath: UnicodeString; AllowCreate : Boolean) : TJSONObject;
function FindObject(Const APath: UnicodeString; AllowCreate : Boolean) : TJSONObject;
function FindObject(Const APath: UnicodeString; AllowCreate : Boolean;Out ElName : UnicodeString) : TJSONObject;
function FindElement(Const APath: UnicodeString; CreateParent : Boolean; AllowObject : Boolean = False) : TJSONData;
function FindElement(Const APath: UnicodeString; CreateParent : Boolean; out AParent : TJSONObject; Out ElName : UnicodeString; AllowObject : Boolean = False) : TJSONData;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
Procedure Reload;
procedure Clear;
procedure Flush; // Writes the JSON file
procedure OpenKey(const aPath: UnicodeString; AllowCreate : Boolean);
procedure CloseKey;
procedure ResetKey;
Procedure EnumSubKeys(Const APath : UnicodeString; List : TStrings);
Procedure EnumValues(Const APath : UnicodeString; List : TStrings);
function GetValue(const APath: UnicodeString; const ADefault: UnicodeString): UnicodeString; overload;
function GetValue(const APath: RawByteString; const ADefault: RawByteString): UnicodeString; overload;
function GetValue(const APath: UnicodeString; ADefault: Integer): Integer; overload;
function GetValue(const APath: RawByteString; ADefault: Integer): Integer; overload;
function GetValue(const APath: UnicodeString; ADefault: Int64): Int64; overload;
function GetValue(const APath: RawByteString; ADefault: Int64): Int64; overload;
function GetValue(const APath: UnicodeString; ADefault: Boolean): Boolean; overload;
function GetValue(const APath: RawByteString; ADefault: Boolean): Boolean; overload;
function GetValue(const APath: UnicodeString; ADefault: Double): Double; overload;
function GetValue(const APath: RawByteString; ADefault: Double): Double; overload;
Function GetValue(const APath: UnicodeString; AValue: TStrings; Const ADefault: String) : Boolean; overload;
Function GetValue(const APath: RawByteString; AValue: TStrings; Const ADefault: String) : Boolean; overload;
Function GetValue(const APath: UnicodeString; AValue: TStrings; Const ADefault: TStrings): Boolean; overload;
procedure SetValue(const APath: UnicodeString; const AValue: UnicodeString); overload;
procedure SetValue(const APath: RawByteString; const AValue: RawByteString); overload;
procedure SetValue(const APath: UnicodeString; AValue: Integer); overload;
procedure SetValue(const APath: UnicodeString; AValue: Int64); overload;
procedure SetValue(const APath: UnicodeString; AValue: Boolean); overload;
procedure SetValue(const APath: UnicodeString; AValue: Double); overload;
procedure SetValue(const APath: UnicodeString; AValue: TStrings; AsObject : Boolean = False); overload;
procedure SetDeleteValue(const APath: UnicodeString; const AValue, DefValue: UnicodeString); overload;
procedure SetDeleteValue(const APath: UnicodeString; AValue, DefValue: Integer); overload;
procedure SetDeleteValue(const APath: UnicodeString; AValue, DefValue: Int64); overload;
procedure SetDeleteValue(const APath: UnicodeString; AValue, DefValue: Boolean); overload;
procedure DeletePath(const APath: UnicodeString);
procedure DeleteValue(const APath: UnicodeString);
property Modified: Boolean read FModified;
published
Property Filename: String read FFilename write SetFilename;
Property Formatted : Boolean Read FFormatted Write FFormatted;
Property FormatOptions : TFormatOptions Read FFormatoptions Write FFormatOptions Default DefaultFormat;
Property FormatIndentsize : Integer Read FFormatIndentSize Write FFormatIndentSize Default DefaultIndentSize;
Property JSONOptions : TJSONOptions Read FJSONOptions Write SetJSONOptions Default DefaultJSONOptions;
end;
// ===================================================================
implementation
Resourcestring
SErrInvalidJSONFile = '"%s" is not a valid JSON configuration file.';
SErrCouldNotOpenKey = 'Could not open key "%s".';
SErrCannotNotReplaceKey = 'A (sub)key with name "%s" already exists.';
constructor TJSONConfig.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FJSON:=TJSONObject.Create;
FKey:=FJSON;
FFormatOptions:=DefaultFormat;
FFormatIndentsize:=DefaultIndentSize;
FJSONOptions:=DefaultJSONOptions;
end;
destructor TJSONConfig.Destroy;
begin
if Assigned(FJSON) then
begin
Flush;
FreeANdNil(FJSON);
end;
inherited Destroy;
end;
procedure TJSONConfig.Clear;
begin
FJSON.Clear;
FKey:=FJSON;
end;
procedure TJSONConfig.Flush;
Var
F : TFileStream;
S : TJSONStringType;
begin
if Modified then
begin
F:=TFileStream.Create(FileName,fmCreate);
Try
if Formatted then
S:=FJSON.FormatJSON(Formatoptions,FormatIndentSize)
else
S:=FJSON.AsJSON;
if S>'' then
F.WriteBuffer(S[1],Length(S));
Finally
F.Free;
end;
FModified := False;
end;
end;
function TJSONConfig.FindObject(const APath: UnicodeString; AllowCreate: Boolean
): TJSONObject;
Var
Dummy : UnicodeString;
begin
Result:=FindObject(APath,AllowCreate,Dummy);
end;
function TJSONConfig.FindObject(const APath: UnicodeString; AllowCreate: Boolean;
out ElName: UnicodeString): TJSONObject;
Var
S,El : UnicodeString;
P,I : Integer;
T : TJSonObject;
begin
// Writeln('Looking for : ', APath);
S:=APath;
If Pos('/',S)=1 then
Result:=FJSON
else
Result:=FKey;
Repeat
P:=Pos('/',S);
If (P<>0) then
begin
// Only real paths, ignore double slash
If (P<>1) then
begin
El:=Copy(S,1,P-1);
If (Result.Count=0) then
I:=-1
else
I:=Result.IndexOfName(UTF8Encode(El));
If (I=-1) then
// No element with this name.
begin
If AllowCreate then
begin
// Create new node.
T:=Result;
Result:=TJSonObject.Create;
T.Add(UTF8Encode(El),Result);
end
else
Result:=Nil
end
else
// Node found, check if it is an object
begin
if (Result.Items[i].JSONtype=jtObject) then
Result:=Result.Objects[UTF8Encode(el)]
else
begin
// Writeln(el,' type wrong');
If AllowCreate then
begin
// Writeln('Creating ',el);
Result.Delete(I);
T:=Result;
Result:=TJSonObject.Create;
T.Add(UTF8Encode(El),Result);
end
else
Result:=Nil
end;
end;
end;
Delete(S,1,P);
end;
Until (P=0) or (Result=Nil);
ElName:=S;
end;
function TJSONConfig.FindElement(const APath: UnicodeString; CreateParent: Boolean; AllowObject : Boolean = False): TJSONData;
Var
O : TJSONObject;
ElName : UnicodeString;
begin
Result:=FindElement(APath,CreateParent,O,ElName,AllowObject);
end;
function TJSONConfig.FindElement(const APath: UnicodeString;
CreateParent: Boolean; out AParent: TJSONObject; out ElName: UnicodeString;
AllowObject : Boolean = False): TJSONData;
Var
I : Integer;
begin
Result:=Nil;
Aparent:=FindObject(APath,CreateParent,ElName);
If Assigned(Aparent) then
begin
// Writeln('Found parent, looking for element:',elName);
I:=AParent.IndexOfName(UTF8Encode(ElName));
// Writeln('Element index is',I);
If (I<>-1) And ((AParent.items[I].JSONType<>jtObject) or AllowObject) then
Result:=AParent.Items[i];
end;
// Writeln('Find ',aPath,' in "',FJSON.AsJSOn,'" : ',Elname,' : ',Result<>NIl);
end;
function TJSONConfig.GetValue(const APath: RawByteString; const ADefault: RawByteString): UnicodeString;
begin
Result:=GetValue(UTF8Decode(aPath),UTF8Decode(ADefault));
end;
function TJSONConfig.GetValue(const APath: UnicodeString; const ADefault: UnicodeString): UnicodeString;
var
El : TJSONData;
begin
El:=FindElement(StripSlash(APath),False);
If Assigned(El) then
Result:=El.AsUnicodeString
else
Result:=ADefault;
end;
function TJSONConfig.GetValue(const APath: RawByteString; ADefault: Integer): Integer;
begin
Result:=GetValue(UTF8Decode(aPath),ADefault);
end;
function TJSONConfig.GetValue(const APath: UnicodeString; ADefault: Integer): Integer;
var
El : TJSONData;
begin
El:=FindElement(StripSlash(APath),False);
If Not Assigned(el) then
Result:=ADefault
else if (el is TJSONNumber) then
Result:=El.AsInteger
else
Result:=StrToIntDef(El.AsString,ADefault);
end;
function TJSONConfig.GetValue(const APath: RawByteString; ADefault: Int64): Int64;
begin
Result:=GetValue(UTF8Decode(aPath),ADefault);
end;
function TJSONConfig.GetValue(const APath: UnicodeString; ADefault: Int64): Int64;
var
El : TJSONData;
begin
El:=FindElement(StripSlash(APath),False);
If Not Assigned(el) then
Result:=ADefault
else if (el is TJSONNumber) then
Result:=El.AsInt64
else
Result:=StrToInt64Def(El.AsString,ADefault);
end;
function TJSONConfig.GetValue(const APath: RawByteString; ADefault: Boolean): Boolean;
begin
Result:=GetValue(UTF8Decode(aPath),ADefault);
end;
function TJSONConfig.GetValue(const APath: UnicodeString; ADefault: Boolean): Boolean;
var
El : TJSONData;
begin
El:=FindElement(StripSlash(APath),False);
If Not Assigned(el) then
Result:=ADefault
else if (el is TJSONBoolean) then
Result:=El.AsBoolean
else
Result:=StrToBoolDef(El.AsString,ADefault);
end;
function TJSONConfig.GetValue(const APath: RawByteString; ADefault: Double): Double;
begin
Result:=GetValue(UTF8Decode(aPath),ADefault);
end;
function TJSONConfig.GetValue(const APath: UnicodeString; ADefault: Double): Double;
var
El : TJSONData;
begin
El:=FindElement(StripSlash(APath),False);
If Not Assigned(el) then
Result:=ADefault
else if (el is TJSONNumber) then
Result:=El.AsFloat
else
Result:=StrToFloatDef(El.AsString,ADefault);
end;
function TJSONConfig.GetValue(const APath: RawByteString; AValue: TStrings;
const ADefault: String): Boolean;
begin
Result:=GetValue(UTF8Decode(aPath),AValue, ADefault);
end;
function TJSONConfig.GetValue(const APath: UnicodeString; AValue: TStrings;
const ADefault: String): Boolean;
var
El : TJSONData;
D : TJSONEnum;
begin
AValue.Clear;
El:=FindElement(StripSlash(APath),False,True);
Result:=Assigned(el);
If Not Result then
begin
AValue.Text:=ADefault;
exit;
end;
Case El.JSONType of
jtArray:
For D in El do
if D.Value.JSONType in ActualValueJSONTypes then
AValue.Add(D.Value.AsString);
jtObject:
For D in El do
if D.Value.JSONType in ActualValueJSONTypes then
AValue.Add(D.Key+'='+D.Value.AsString);
else
AValue.Text:=EL.AsString
end;
end;
function TJSONConfig.GetValue(const APath: UnicodeString; AValue: TStrings;
const ADefault: TStrings): Boolean;
begin
Result:=GetValue(APath,AValue,'');
If Not Result then
AValue.Assign(ADefault);
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; const AValue: UnicodeString);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
begin
El:=FindNodeForValue(aPath,TJSONString,O,elName);
If Not Assigned(el) then
begin
El:=TJSONString.Create(AValue);
O.Add(UTF8Encode(ElName),El);
end
else
El.AsUnicodeString:=AValue;
FModified:=True;
end;
procedure TJSONConfig.SetValue(const APath: RawByteString;
const AValue: RawByteString);
begin
SetValue(UTF8Decode(APath),UTF8Decode(AValue));
end;
procedure TJSONConfig.SetDeleteValue(const APath: UnicodeString; const AValue, DefValue: UnicodeString);
begin
if AValue = DefValue then
DeleteValue(APath)
else
SetValue(APath, AValue);
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; AValue: Integer);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
begin
El:=FindNodeForValue(aPath,TJSONIntegerNumber,O,elName);
If Not Assigned(el) then
begin
El:=TJSONIntegerNumber.Create(AValue);
O.Add(UTF8Encode(ElName),El);
end
else
El.AsInteger:=AValue;
FModified:=True;
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; AValue: Int64);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
begin
El:=FindNodeForValue(aPath,TJSONInt64Number,O,elName);
If Not Assigned(el) then
begin
El:=TJSONInt64Number.Create(AValue);
O.Add(UTF8Encode(ElName),El);
end
else
El.AsInt64:=AValue;
FModified:=True;
end;
procedure TJSONConfig.SetDeleteValue(const APath: UnicodeString; AValue,
DefValue: Integer);
begin
if AValue = DefValue then
DeleteValue(APath)
else
SetValue(APath, AValue);
end;
procedure TJSONConfig.SetDeleteValue(const APath: UnicodeString; AValue,
DefValue: Int64);
begin
if AValue = DefValue then
DeleteValue(APath)
else
SetValue(APath, AValue);
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; AValue: Boolean);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
begin
El:=FindNodeForValue(aPath,TJSONBoolean,O,elName);
If Not Assigned(el) then
begin
El:=TJSONBoolean.Create(AValue);
O.Add(UTF8Encode(ElName),El);
end
else
El.AsBoolean:=AValue;
FModified:=True;
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; AValue: Double);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
begin
El:=FindNodeForValue(aPath,TJSONFloatNumber,O,elName);
If Not Assigned(el) then
begin
El:=TJSONFloatNumber.Create(AValue);
O.Add(UTF8Encode(ElName),El);
end
else
El.AsFloat:=AValue;
FModified:=True;
end;
procedure TJSONConfig.SetValue(const APath: UnicodeString; AValue: TStrings; AsObject : Boolean = False);
var
El : TJSONData;
ElName : UnicodeString;
O : TJSONObject;
I : integer;
A : TJSONArray;
N,V : String;
begin
if AsObject then
El:=FindNodeForValue(aPath,TJSONObject,O,elName)
else
El:=FindNodeForValue(aPath,TJSONArray,O,elName);
If Not Assigned(el) then
begin
if AsObject then
El:=TJSONObject.Create
else
El:=TJSONArray.Create;
O.Add(UTF8Encode(ElName),El);
end;
if Not AsObject then
begin
A:=El as TJSONArray;
A.Clear;
For N in Avalue do
A.Add(N);
end
else
begin
O:=El as TJSONObject;
For I:=0 to AValue.Count-1 do
begin
AValue.GetNameValue(I,N,V);
O.Add(N,V);
end;
end;
FModified:=True;
end;
procedure TJSONConfig.SetDeleteValue(const APath: UnicodeString; AValue,
DefValue: Boolean);
begin
if AValue = DefValue then
DeleteValue(APath)
else
SetValue(APath,AValue);
end;
procedure TJSONConfig.DeletePath(const APath: UnicodeString);
Var
P : UnicodeString;
L : integer;
Node : TJSONObject;
ElName : UnicodeString;
begin
P:=StripSlash(APath);
L:=Length(P);
If (L>0) then
begin
Node := FindObject(P,False,ElName);
If Assigned(Node) then
begin
L:=Node.IndexOfName(UTF8Encode(ElName));
If (L<>-1) then
Node.Delete(L);
end;
end;
FModified:=True;
end;
procedure TJSONConfig.DeleteValue(const APath: UnicodeString);
begin
DeletePath(APath);
end;
procedure TJSONConfig.Reload;
begin
if Length(Filename) > 0 then
DoSetFilename(Filename,True);
end;
procedure TJSONConfig.Loaded;
begin
inherited Loaded;
Reload;
end;
function TJSONConfig.FindNodeForValue(const APath: UnicodeString; aExpectedType : TJSONDataClass; out AParent: TJSONObject; out ElName: UnicodeString): TJSONData;
var
I : Integer;
begin
Result:=FindElement(StripSlash(APath),True,aParent,ElName,True);
if Assigned(Result) and Not Result.InheritsFrom(aExpectedType) then
begin
I:=aParent.IndexOfName(UTF8Encode(elName));
aParent.Delete(i);
Result:=Nil;
end;
end;
function TJSONConfig.FindPath(const APath: UnicodeString; AllowCreate: Boolean
): TJSONObject;
Var
P : UnicodeString;
L : Integer;
begin
P:=APath;
L:=Length(P);
If (L=0) or (P[L]<>'/') then
P:=P+'/';
Result:=FindObject(P,AllowCreate);
end;
procedure TJSONConfig.DoSetFilename(const AFilename: String; ForceReload: Boolean);
begin
if (not ForceReload) and (FFilename = AFilename) then
exit;
FFilename := AFilename;
if csLoading in ComponentState then
exit;
Flush;
If Not FileExists(AFileName) then
Clear
else
LoadFromFile(AFileName);
end;
procedure TJSONConfig.SetFilename(const AFilename: String);
begin
DoSetFilename(AFilename, False);
end;
procedure TJSONConfig.SetJSONOptions(AValue: TJSONOptions);
begin
if FJSONOptions=AValue then Exit;
FJSONOptions:=AValue;
if csLoading in ComponentState then
exit;
if (FFileName<>'') then
Reload;
end;
function TJSONConfig.StripSlash(const P: UnicodeString): UnicodeString;
Var
L : Integer;
begin
L:=Length(P);
If (L>0) and (P[l]='/') then
Result:=Copy(P,1,L-1)
else
Result:=P;
end;
procedure TJSONConfig.LoadFromFile(const AFileName: String);
Var
F : TFileStream;
begin
F:=TFileStream.Create(AFileName,fmopenRead or fmShareDenyWrite);
try
LoadFromStream(F);
finally
F.Free;
end;
end;
procedure TJSONConfig.LoadFromStream(S: TStream);
Var
P : TJSONParser;
J : TJSONData;
begin
P:=TJSONParser.Create(S,FJSONOptions);
try
J:=P.Parse;
If (J is TJSONObject) then
begin
FreeAndNil(FJSON);
FJSON:=J as TJSONObject;
FKey:=FJSON;
end
else
begin
FreeAndNil(J);
Raise EJSONConfigError.CreateFmt(SErrInvalidJSONFile,[FileName]);
end;
finally
P.Free;
end;
end;
procedure TJSONConfig.CloseKey;
begin
ResetKey;
end;
procedure TJSONConfig.OpenKey(const aPath: UnicodeString; AllowCreate: Boolean);
Var
P : UnicodeString;
L : Integer;
begin
P:=APath;
L:=Length(P);
If (L=0) then
FKey:=FJSON
else
begin
if (P[L]<>'/') then
P:=P+'/';
FKey:=FindObject(P,AllowCreate);
If (FKey=Nil) Then
Raise EJSONConfigError.CreateFmt(SErrCouldNotOpenKey,[APath]);
end;
end;
procedure TJSONConfig.ResetKey;
begin
FKey:=FJSON;
end;
procedure TJSONConfig.EnumSubKeys(const APath: UnicodeString; List: TStrings);
Var
AKey : TJSONObject;
I : Integer;
begin
AKey:=FindPath(APath,False);
If Assigned(AKey) then
begin
For I:=0 to AKey.Count-1 do
If AKey.Items[i] is TJSONObject then
List.Add(AKey.Names[i]);
end;
end;
procedure TJSONConfig.EnumValues(const APath: UnicodeString; List: TStrings);
Var
AKey : TJSONObject;
I : Integer;
begin
AKey:=FindPath(APath,False);
If Assigned(AKey) then
begin
For I:=0 to AKey.Count-1 do
If Not (AKey.Items[i] is TJSONObject) then
List.Add(AKey.Names[i]);
end;
end;
end.