dexed/etc/fcl-json/src/jsonparser.pp

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.