mirror of https://gitlab.com/basile.b/dexed.git
383 lines
9.4 KiB
Puppet
383 lines
9.4 KiB
Puppet
{
|
|
This file is part of the Free Component Library
|
|
|
|
JSON source parser
|
|
Copyright (c) 2007 by 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.
|
|
|
|
**********************************************************************}
|
|
{$mode objfpc}
|
|
{$h+}
|
|
unit jsonparser;
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fpJSON, jsonscanner;
|
|
|
|
Type
|
|
|
|
{ TJSONParser }
|
|
|
|
TJSONParser = Class(TObject)
|
|
Private
|
|
FScanner : TJSONScanner;
|
|
function GetO(AIndex: TJSONOption): Boolean;
|
|
function GetOptions: TJSONOptions;
|
|
function ParseNumber: TJSONNumber;
|
|
procedure SetO(AIndex: TJSONOption; AValue: Boolean);
|
|
procedure SetOptions(AValue: TJSONOptions);
|
|
Protected
|
|
procedure DoError(const Msg: String);
|
|
function DoParse(AtCurrent,AllowEOF: Boolean): TJSONData;
|
|
function GetNextToken: TJSONToken;
|
|
function CurrentTokenString: String;
|
|
function CurrentToken: TJSONToken;
|
|
function ParseArray: TJSONArray;
|
|
function ParseObject: TJSONObject;
|
|
Property Scanner : TJSONScanner read FScanner;
|
|
Public
|
|
function Parse: TJSONData;
|
|
Constructor Create(Source : TStream; AUseUTF8 : Boolean = True); overload;deprecated 'use options form instead';
|
|
Constructor Create(Source : TJSONStringType; AUseUTF8 : Boolean = True); overload;deprecated 'use options form instead';
|
|
constructor Create(Source: TStream; AOptions: TJSONOptions); overload;
|
|
constructor Create(const Source: String; AOptions: TJSONOptions); overload;
|
|
destructor Destroy();override;
|
|
// Use strict JSON: " for strings, object members are strings, not identifiers
|
|
Property Strict : Boolean Index joStrict Read GetO Write SetO ; deprecated 'use options instead';
|
|
// if set to TRUE, then strings will be converted to UTF8 ansistrings, not system codepage ansistrings.
|
|
Property UseUTF8 : Boolean index joUTF8 Read GetO Write SetO; deprecated 'Use options instead';
|
|
// Parsing options
|
|
Property Options : TJSONOptions Read GetOptions Write SetOptions;
|
|
end;
|
|
|
|
EJSONParser = Class(EParserError);
|
|
|
|
implementation
|
|
|
|
Resourcestring
|
|
SErrUnexpectedEOF = 'Unexpected EOF encountered.';
|
|
SErrUnexpectedToken = 'Unexpected token (%s) encountered.';
|
|
SErrExpectedColon = 'Expected colon (:), got token "%s".';
|
|
SErrUnexpectedComma = 'Invalid comma encountered.';
|
|
SErrEmptyElement = 'Empty element encountered.';
|
|
SErrExpectedElementName = 'Expected element name, got token "%s"';
|
|
SExpectedCommaorBraceClose = 'Expected , or ], got token "%s".';
|
|
SErrInvalidNumber = 'Number is not an integer or real number: %s';
|
|
SErrNoScanner = 'No scanner. No source specified ?';
|
|
|
|
{ TJSONParser }
|
|
|
|
procedure DefJSONParserHandler(AStream: TStream; const AUseUTF8: Boolean; out
|
|
Data: TJSONData);
|
|
|
|
Var
|
|
P : TJSONParser;
|
|
|
|
begin
|
|
Data:=Nil;
|
|
P:=TJSONParser.Create(AStream,[joUTF8]);
|
|
try
|
|
Data:=P.Parse;
|
|
finally
|
|
P.Free;
|
|
end;
|
|
end;
|
|
|
|
function TJSONParser.Parse: TJSONData;
|
|
|
|
begin
|
|
if (FScanner=Nil) then
|
|
DoError(SErrNoScanner);
|
|
Result:=DoParse(False,True);
|
|
end;
|
|
|
|
{
|
|
Consume next token and convert to JSON data structure.
|
|
If AtCurrent is true, the current token is used. If false,
|
|
a token is gotten from the scanner.
|
|
If AllowEOF is false, encountering a tkEOF will result in an exception.
|
|
}
|
|
|
|
function TJSONParser.CurrentToken: TJSONToken;
|
|
|
|
begin
|
|
Result:=FScanner.CurToken;
|
|
end;
|
|
|
|
function TJSONParser.CurrentTokenString: String;
|
|
|
|
begin
|
|
If CurrentToken in [tkString,tkIdentifier,tkNumber,tkComment] then
|
|
Result:=FScanner.CurTokenString
|
|
else
|
|
Result:=TokenInfos[CurrentToken];
|
|
end;
|
|
|
|
function TJSONParser.DoParse(AtCurrent, AllowEOF: Boolean): TJSONData;
|
|
|
|
var
|
|
T : TJSONToken;
|
|
|
|
begin
|
|
Result:=nil;
|
|
try
|
|
If not AtCurrent then
|
|
T:=GetNextToken
|
|
else
|
|
T:=FScanner.CurToken;
|
|
Case T of
|
|
tkEof : If Not AllowEof then
|
|
DoError(SErrUnexpectedEOF);
|
|
tkNull : Result:=CreateJSON;
|
|
tkTrue,
|
|
tkFalse : Result:=CreateJSON(t=tkTrue);
|
|
tkString : if joUTF8 in Options then
|
|
Result:=CreateJSON(UTF8Decode(CurrentTokenString))
|
|
else
|
|
Result:=CreateJSON(CurrentTokenString);
|
|
tkCurlyBraceOpen : Result:=ParseObject;
|
|
tkCurlyBraceClose : DoError(SErrUnexpectedToken);
|
|
tkSQuaredBraceOpen : Result:=ParseArray;
|
|
tkSQuaredBraceClose : DoError(SErrUnexpectedToken);
|
|
tkNumber : Result:=ParseNumber;
|
|
tkComma : DoError(SErrUnexpectedToken);
|
|
end;
|
|
except
|
|
FreeAndNil(Result);
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
|
|
// Creates the correct JSON number type, based on the current token.
|
|
function TJSONParser.ParseNumber: TJSONNumber;
|
|
|
|
Var
|
|
I : Integer;
|
|
I64 : Int64;
|
|
QW : QWord;
|
|
F : TJSONFloat;
|
|
S : String;
|
|
|
|
begin
|
|
S:=CurrentTokenString;
|
|
I:=0;
|
|
if TryStrToQWord(S,QW) then
|
|
begin
|
|
if QW>qword(high(Int64)) then
|
|
Result:=CreateJSON(QW)
|
|
else
|
|
if QW>MaxInt then
|
|
begin
|
|
I64 := QW;
|
|
Result:=CreateJSON(I64);
|
|
end
|
|
else
|
|
begin
|
|
I := QW;
|
|
Result:=CreateJSON(I);
|
|
end
|
|
end
|
|
else
|
|
begin
|
|
If TryStrToInt64(S,I64) then
|
|
if (I64>Maxint) or (I64<-MaxInt) then
|
|
Result:=CreateJSON(I64)
|
|
Else
|
|
begin
|
|
I:=I64;
|
|
Result:=CreateJSON(I);
|
|
end
|
|
else
|
|
begin
|
|
I:=0;
|
|
Val(S,F,I);
|
|
If (I<>0) then
|
|
DoError(SErrInvalidNumber);
|
|
Result:=CreateJSON(F);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
function TJSONParser.GetO(AIndex: TJSONOption): Boolean;
|
|
begin
|
|
Result:=AIndex in Options;
|
|
end;
|
|
|
|
function TJSONParser.GetOptions: TJSONOptions;
|
|
begin
|
|
Result:=FScanner.Options
|
|
end;
|
|
|
|
procedure TJSONParser.SetO(AIndex: TJSONOption; AValue: Boolean);
|
|
begin
|
|
if aValue then
|
|
FScanner.Options:=FScanner.Options+[AINdex]
|
|
else
|
|
FScanner.Options:=FScanner.Options-[AINdex]
|
|
end;
|
|
|
|
procedure TJSONParser.SetOptions(AValue: TJSONOptions);
|
|
begin
|
|
FScanner.Options:=AValue;
|
|
end;
|
|
|
|
|
|
// Current token is {, on exit current token is }
|
|
function TJSONParser.ParseObject: TJSONObject;
|
|
|
|
Var
|
|
T : TJSONtoken;
|
|
E : TJSONData;
|
|
N : String;
|
|
LastComma : Boolean;
|
|
|
|
begin
|
|
LastComma:=False;
|
|
Result:=CreateJSONObject([]);
|
|
Try
|
|
T:=GetNextToken;
|
|
While T<>tkCurlyBraceClose do
|
|
begin
|
|
If (T<>tkString) and (T<>tkIdentifier) then
|
|
DoError(SErrExpectedElementName);
|
|
N:=CurrentTokenString;
|
|
T:=GetNextToken;
|
|
If (T<>tkColon) then
|
|
DoError(SErrExpectedColon);
|
|
E:=DoParse(False,False);
|
|
Result.Add(N,E);
|
|
T:=GetNextToken;
|
|
If Not (T in [tkComma,tkCurlyBraceClose]) then
|
|
DoError(SExpectedCommaorBraceClose);
|
|
If T=tkComma then
|
|
begin
|
|
T:=GetNextToken;
|
|
LastComma:=(t=tkCurlyBraceClose);
|
|
end;
|
|
end;
|
|
If LastComma and ((joStrict in Options) or not (joIgnoreTrailingComma in Options)) then // Test for ,} case
|
|
DoError(SErrUnExpectedToken);
|
|
Except
|
|
FreeAndNil(Result);
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
// Current token is [, on exit current token is ]
|
|
function TJSONParser.ParseArray: TJSONArray;
|
|
|
|
Var
|
|
T : TJSONtoken;
|
|
E : TJSONData;
|
|
LastComma : Boolean;
|
|
S : TJSONOPTions;
|
|
begin
|
|
Result:=CreateJSONArray([]);
|
|
LastComma:=False;
|
|
Try
|
|
Repeat
|
|
T:=GetNextToken;
|
|
If (T<>tkSquaredBraceClose) then
|
|
begin
|
|
E:=DoParse(True,False);
|
|
If (E<>Nil) then
|
|
Result.Add(E)
|
|
else if (Result.Count>0) then
|
|
DoError(SErrEmptyElement);
|
|
T:=GetNextToken;
|
|
If Not (T in [tkComma,tkSquaredBraceClose]) then
|
|
DoError(SExpectedCommaorBraceClose);
|
|
LastComma:=(t=TkComma);
|
|
end;
|
|
Until (T=tkSquaredBraceClose);
|
|
S:=Options;
|
|
If LastComma and ((joStrict in S) or not (joIgnoreTrailingComma in S)) then // Test for ,] case
|
|
DoError(SErrUnExpectedToken);
|
|
Except
|
|
FreeAndNil(Result);
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
// Get next token, discarding whitespace
|
|
function TJSONParser.GetNextToken: TJSONToken;
|
|
|
|
begin
|
|
Repeat
|
|
Result:=FScanner.FetchToken;
|
|
Until (Not (Result in [tkComment,tkWhiteSpace]));
|
|
end;
|
|
|
|
procedure TJSONParser.DoError(const Msg: String);
|
|
|
|
Var
|
|
S : String;
|
|
|
|
begin
|
|
S:=Format(Msg,[CurrentTokenString]);
|
|
S:=Format('Error at line %d, Pos %d:',[FScanner.CurRow,FSCanner.CurColumn])+S;
|
|
Raise EJSONParser.Create(S);
|
|
end;
|
|
|
|
constructor TJSONParser.Create(Source: TStream; AUseUTF8 : Boolean = True);
|
|
begin
|
|
Inherited Create;
|
|
FScanner:=TJSONScanner.Create(Source,[joUTF8]);
|
|
if AUseUTF8 then
|
|
Options:=Options + [joUTF8];
|
|
end;
|
|
|
|
constructor TJSONParser.Create(Source: TJSONStringType; AUseUTF8 : Boolean = True);
|
|
begin
|
|
Inherited Create;
|
|
FScanner:=TJSONScanner.Create(Source,[joUTF8]);
|
|
if AUseUTF8 then
|
|
Options:=Options + [joUTF8];
|
|
end;
|
|
|
|
constructor TJSONParser.Create(Source: TStream; AOptions: TJSONOptions);
|
|
begin
|
|
FScanner:=TJSONScanner.Create(Source,AOptions);
|
|
end;
|
|
|
|
constructor TJSONParser.Create(const Source: String; AOptions: TJSONOptions);
|
|
begin
|
|
FScanner:=TJSONScanner.Create(Source,AOptions);
|
|
end;
|
|
|
|
destructor TJSONParser.Destroy();
|
|
begin
|
|
FreeAndNil(FScanner);
|
|
inherited Destroy();
|
|
end;
|
|
|
|
Procedure InitJSONHandler;
|
|
|
|
begin
|
|
if GetJSONParserHandler=Nil then
|
|
SetJSONParserHandler(@DefJSONParserHandler);
|
|
end;
|
|
|
|
Procedure DoneJSONHandler;
|
|
|
|
begin
|
|
if GetJSONParserHandler=@DefJSONParserHandler then
|
|
SetJSONParserHandler(Nil);
|
|
end;
|
|
|
|
initialization
|
|
InitJSONHandler;
|
|
finalization
|
|
DoneJSONHandler;
|
|
end.
|
|
|