- Bug fix: BufferCore - set correct auto buffer size
- Change Get/Set methods to use BaseReference and full path instead of
ParentPath and MemberName
- Add Create param to all Get methods (create if not found)
- Implement arrays & parsing of arrays
- Set CJSONparse as friend class to DataTree (access protected methods)
- Print JSON to screen
680 lines
17 KiB
C++
680 lines
17 KiB
C++
/*
|
|
* JSONparseCore.cpp
|
|
*
|
|
* Created on: 5 Mar 2017
|
|
* Author: wentzelc
|
|
*/
|
|
|
|
// redA Libraries
|
|
#include "JSONparseCore.h"
|
|
|
|
// Standard C/C++ Libraries
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
CJSONparse::CJSONparse( CDataTree * pDataTree )
|
|
{
|
|
// Object tree
|
|
DataTree = pDataTree;
|
|
|
|
// File Operation
|
|
InputHandle = -1;
|
|
OutputHandle = -1;
|
|
|
|
Buffer = NULL;
|
|
BufEnd = NULL;
|
|
Level = 0;
|
|
|
|
// Parsing operation
|
|
BufPos = NULL;
|
|
LineMark = NULL;
|
|
LineNo = 0;
|
|
|
|
// Printing operation
|
|
Spacer[0] = 0;
|
|
SpacerLen = 0;
|
|
|
|
// Error reporting
|
|
Error = false;
|
|
ErrorText[0] = 0;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
CJSONparse::~CJSONparse()
|
|
{
|
|
// Destroy buffer
|
|
if (Buffer) {
|
|
delete Buffer;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::LoadFromFile( const char * FilePath, int pBufLen )
|
|
{
|
|
TDataMember * RootObject = NULL;
|
|
|
|
// Clear Error
|
|
Error = false;
|
|
|
|
// Validate
|
|
if (!FilePath || !FilePath[0]) {
|
|
Error = true;
|
|
sprintf( ErrorText, "No File path specified" );
|
|
return false;
|
|
}
|
|
|
|
// Open file
|
|
if (!(InputHandle = open( FilePath, O_RDONLY ))) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Could not open file" );
|
|
return false;
|
|
}
|
|
|
|
// Load Buffer
|
|
CreateBuffer( pBufLen );
|
|
if (!FillBuffer()) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Could not read from file" );
|
|
FreeBuffer();
|
|
return false;
|
|
}
|
|
|
|
// Reset values
|
|
LineNo = 1;
|
|
Error = false;
|
|
|
|
// Create Root object
|
|
DataTree->DeleteAll();
|
|
RootObject = DataTree->GetRootMember();
|
|
Level = 0;
|
|
|
|
// Parse Root Object
|
|
SkipWhiteSpace();
|
|
if (!ParseObject( RootObject )) {
|
|
if (!Error) {
|
|
Error = true;
|
|
sprintf( ErrorText, "First entry in file must be an Object on line %d:%ld", LineNo, BufPos-LineMark );
|
|
}
|
|
FreeBuffer();
|
|
return false;
|
|
}
|
|
|
|
// Ensure remainder of file is empty
|
|
SkipWhiteSpace();
|
|
if (*BufPos != 0) {
|
|
Error = true;
|
|
sprintf( ErrorText, "No content expected after Root object on line %d:%ld", LineNo, BufPos-LineMark );
|
|
FreeBuffer();
|
|
return false;
|
|
}
|
|
|
|
// Success
|
|
close( InputHandle );
|
|
InputHandle = -1;
|
|
FreeBuffer();
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::CreateBuffer( int pBufLen )
|
|
{
|
|
// Validate
|
|
if (!pBufLen)
|
|
return false;
|
|
|
|
// Create buffer
|
|
Buffer = new CShiftBuffer( pBufLen );
|
|
|
|
// Reset markers
|
|
Buffer->PeekDirect( &BufPos, 0 );
|
|
BufEnd = BufPos;
|
|
LineMark = BufPos;
|
|
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::FillBuffer()
|
|
{
|
|
// Read from file
|
|
Buffer->ReadFromFD( InputHandle );
|
|
|
|
// Update Markers
|
|
Buffer->PeekDirect( &BufEnd, Buffer->Len() );
|
|
Buffer->PeekDirect( &BufPos, 0 );
|
|
LineMark = BufPos;
|
|
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CJSONparse::FreeBuffer()
|
|
{
|
|
// Destroy buffer
|
|
if (Buffer) {
|
|
delete Buffer;
|
|
Buffer = NULL;
|
|
|
|
// Update Markers
|
|
BufEnd = NULL;
|
|
BufPos = NULL;
|
|
LineMark = NULL;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::ParseString( char ** Value, int &pLen )
|
|
{
|
|
char * Mark;
|
|
char * EndMark;
|
|
int Len;
|
|
|
|
// Check for opening quote
|
|
if (*BufPos != '"') {
|
|
return false;
|
|
}
|
|
|
|
// Clear values
|
|
*Value = NULL;
|
|
Len = 0;
|
|
|
|
// Mark start of quote
|
|
Mark = BufPos;
|
|
BufPos++;
|
|
|
|
// Check for closing quote
|
|
while ((BufPos = strpbrk( BufPos, "\"/\\\n\t\b\f\n\r" )))
|
|
{
|
|
if (*BufPos == '"') {
|
|
// End of string found
|
|
BufPos++;
|
|
break;
|
|
}
|
|
else if (!*BufPos) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Expect closing '\"' for string on line %d:%ld", LineNo, Mark-LineMark );
|
|
return false;
|
|
}
|
|
else if (*BufPos == '\\') {
|
|
if (*(BufPos+1) == 'u') {
|
|
for (EndMark = BufPos+2; EndMark < BufPos+6; EndMark++) {
|
|
if (!isxdigit( *EndMark )) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Expect 4-digit hex value for escape value on line %d:%ld", LineNo, Mark-LineMark );
|
|
return false;
|
|
}
|
|
}
|
|
BufPos += 6;
|
|
}
|
|
else if (strchr( "bfnrt/\\\"", *(BufPos+1) )) {
|
|
BufPos += 2;
|
|
}
|
|
else {
|
|
Error = true;
|
|
sprintf( ErrorText, "Invalid escape sequence on line %d:%ld", LineNo, Mark-LineMark );
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
Error = true;
|
|
sprintf( ErrorText, "Un-escaped special character in string on line %d:%ld", LineNo, BufPos-LineMark );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Validate size of name
|
|
Len = BufPos-Mark-2;
|
|
|
|
// Create Return value pointer
|
|
*Value = (char*)malloc( Len+1 );
|
|
memcpy( *Value, Mark+1, Len );
|
|
(*Value)[Len] = 0;
|
|
|
|
// Set other parameters
|
|
pLen = Len;
|
|
|
|
// Success
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::ParseObject( TDataMember * Object )
|
|
{
|
|
TDataMember * Member = NULL;
|
|
char * MemberName = NULL;
|
|
int Len = 0;
|
|
|
|
// Check for start of Object
|
|
if (*BufPos != '{') {
|
|
return false;
|
|
}
|
|
BufPos++;
|
|
|
|
// Set Type
|
|
Level++;
|
|
DataTree->SetValue( Object, jtObject, NULL, -1 );
|
|
|
|
while (true)
|
|
{
|
|
// Look for Member Name
|
|
SkipWhiteSpace();
|
|
if (*BufPos == '}') {
|
|
break;
|
|
}
|
|
else if (!ParseString( &MemberName, Len )) {
|
|
if (!Error) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Expect quoted key name on line %d:%ld", LineNo, BufPos-LineMark );
|
|
}
|
|
return false;
|
|
}
|
|
else if (!Len) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Empty parameter name on line %d:%ld", LineNo, BufPos-LineMark );
|
|
return false;
|
|
}
|
|
|
|
// Check if Member exists
|
|
Member = DataTree->GetMember( Object, MemberName, true );
|
|
|
|
// Check for delimiter
|
|
SkipWhiteSpace();
|
|
if (*BufPos != ':') {
|
|
Error = true;
|
|
sprintf( ErrorText, "Expected ':' delimiter on line %d:%ld", LineNo, BufPos-LineMark );
|
|
return false;
|
|
}
|
|
BufPos++;
|
|
|
|
// Get Value
|
|
SkipWhiteSpace();
|
|
if (!ParseObject( Member ) &&
|
|
!Error &&
|
|
!ParseArray( Member ) &&
|
|
!Error &&
|
|
!ParseString( Member ) &&
|
|
!Error &&
|
|
!ParsePrimitive( Member ))
|
|
{
|
|
DataTree->Delete( Object, MemberName );
|
|
return false;
|
|
}
|
|
|
|
// Check if more parameters to follow
|
|
SkipWhiteSpace();
|
|
if (*BufPos != ',') {
|
|
// No more parameters
|
|
break;
|
|
}
|
|
BufPos++;
|
|
}
|
|
|
|
// Expect end of object
|
|
if (*BufPos != '}') {
|
|
Error = true;
|
|
sprintf( ErrorText, "Closing brace for object '}' expected on line %d:%ld", LineNo, BufPos-LineMark );
|
|
return false;
|
|
}
|
|
BufPos++;
|
|
Level--;
|
|
|
|
// success
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::ParseArray( TDataMember * Array )
|
|
{
|
|
TDataMember * Member = NULL;
|
|
|
|
// Check for start of Object
|
|
if (*BufPos != '[') {
|
|
return false;
|
|
}
|
|
BufPos++;
|
|
|
|
// Set Type
|
|
Level++;
|
|
DataTree->SetValue( Array, jtArray, NULL, -1 );
|
|
|
|
while (true)
|
|
{
|
|
// Look for Member Name
|
|
SkipWhiteSpace();
|
|
if (*BufPos == ']') {
|
|
break;
|
|
}
|
|
|
|
// Add new element
|
|
Member = DataTree->CreateMember( NULL );
|
|
|
|
// Get Value
|
|
SkipWhiteSpace();
|
|
if (!ParseObject( Member ) &&
|
|
!Error &&
|
|
!ParseArray( Member ) &&
|
|
!Error &&
|
|
!ParseString( Member ) &&
|
|
!Error &&
|
|
!ParsePrimitive( Member ))
|
|
{
|
|
DataTree->DestroyMember( &Member );
|
|
return false;
|
|
}
|
|
else {
|
|
DataTree->AddMember( Array, Member );
|
|
}
|
|
|
|
// Check if more parameters to follow
|
|
SkipWhiteSpace();
|
|
if (*BufPos != ',') {
|
|
// No more parameters
|
|
break;
|
|
}
|
|
BufPos++;
|
|
}
|
|
|
|
// Expect end of object
|
|
if (*BufPos != ']') {
|
|
Error = true;
|
|
sprintf( ErrorText, "Closing brace for array ']' expected on line %d:%ld", LineNo, BufPos-LineMark );
|
|
return false;
|
|
}
|
|
BufPos++;
|
|
Level--;
|
|
|
|
// success
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::ParseString( TDataMember * Member )
|
|
{
|
|
char * Value = NULL;
|
|
int Len = 0;
|
|
|
|
// Try to parse
|
|
if (!ParseString( &Value, Len )) {
|
|
return false;
|
|
}
|
|
|
|
// Set string
|
|
DataTree->SetValuePtr( Member, jtString, Value, Len );
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
bool CJSONparse::ParsePrimitive( TDataMember * Member )
|
|
{
|
|
char * Value = NULL;
|
|
int Len = 0;
|
|
char * Mark;
|
|
char * EndMark;
|
|
|
|
// Mark start of value
|
|
Mark = BufPos;
|
|
|
|
// Get end of value
|
|
while ((*BufPos != 0) && !isspace(*BufPos) && (*BufPos != ',')) {
|
|
BufPos++;
|
|
}
|
|
|
|
// Check length of value
|
|
Len = BufPos - Mark;
|
|
if (!Len) {
|
|
Error = true;
|
|
sprintf( ErrorText, "Missing param value on line %d:%ld", LineNo, Mark-LineMark );
|
|
return false;
|
|
}
|
|
|
|
// Check for primitive values
|
|
if ((Len == 4) && !strncasecmp( Mark, "null", 4 )) {
|
|
DataTree->SetValuePtr( Member, jtNull, NULL, -1 );
|
|
}
|
|
else if ((Len == 4) && !strncasecmp( Mark, "true", 4 )) {
|
|
DataTree->SetValue( Member, jtBool, "1", 1 );
|
|
}
|
|
else if ((Len == 5) && !strncasecmp( Mark, "false", 5 )) {
|
|
DataTree->SetValue( Member, jtBool, "0", 1 );
|
|
}
|
|
else {
|
|
// Try conversion to int
|
|
strtol( Mark, &EndMark, 10 );
|
|
if (EndMark == BufPos) {
|
|
Value = (char*)malloc( Len+1 );
|
|
memcpy( Value, Mark, Len );
|
|
Value[Len] = 0;
|
|
DataTree->SetValuePtr( Member, jtInt, Value, Len );
|
|
}
|
|
else {
|
|
// Try conversion to float
|
|
strtod( Mark, &EndMark );
|
|
if (EndMark == BufPos) {
|
|
Value = (char*)malloc( Len+1 );
|
|
memcpy( Value, Mark, Len );
|
|
Value[Len] = 0;
|
|
DataTree->SetValuePtr( Member, jtFloat, Value, Len );
|
|
}
|
|
else {
|
|
Error = true;
|
|
sprintf( ErrorText, "Invalid primitive param value on line %d:%ld", LineNo, Mark-LineMark );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::PrintToScreen( const int Indent )
|
|
{
|
|
TDataMember * RootObject;
|
|
|
|
// Set to StdOut
|
|
OutputHandle = 1;
|
|
|
|
// Save Root object
|
|
Level = 0;
|
|
RootObject = DataTree->GetRootMember();
|
|
PrintObject( RootObject, Indent );
|
|
|
|
// Close file
|
|
close( OutputHandle );
|
|
OutputHandle = -1;
|
|
return false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::SaveToFile( const char * FilePath, const int Indent )
|
|
{
|
|
TDataMember * RootObject;
|
|
|
|
// Validate
|
|
if (!FilePath || !FilePath[0])
|
|
return false;
|
|
|
|
// Open file
|
|
if (!(OutputHandle = open( FilePath, O_WRONLY )))
|
|
return false;
|
|
|
|
// Save Root object
|
|
Level = 0;
|
|
RootObject = DataTree->GetRootMember();
|
|
PrintObject( RootObject, Indent );
|
|
|
|
// Close file
|
|
close( OutputHandle );
|
|
OutputHandle = -1;
|
|
return false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::PrintObject( TDataMember * Object, const int Indent )
|
|
{
|
|
TDataMember * Member;
|
|
bool First = true;
|
|
bool Last = false;
|
|
int Count = 0;
|
|
|
|
// Opening brace
|
|
write( OutputHandle, "{", 1 );
|
|
Level++;
|
|
|
|
// Extend spacer
|
|
memset( &Spacer[SpacerLen], ' ', 2 );
|
|
SpacerLen += 2;
|
|
Spacer[SpacerLen] = 0;
|
|
|
|
// Save parameters
|
|
for (Member = Object->FirstChild; Member != NULL; (Member = Member->Next))
|
|
{
|
|
// Write parameter name
|
|
if (First) {
|
|
First = false;
|
|
if (Object->Name) {
|
|
write( OutputHandle, "\n", 1 );
|
|
write( OutputHandle, Spacer, SpacerLen );
|
|
}
|
|
else {
|
|
write( OutputHandle, " ", 1 );
|
|
}
|
|
}
|
|
else {
|
|
write( OutputHandle, Spacer, SpacerLen );
|
|
}
|
|
|
|
// Print key name
|
|
write( OutputHandle, "\"", 1 );
|
|
write( OutputHandle, Member->Name, strlen(Member->Name) );
|
|
write( OutputHandle, "\" : ", 4 );
|
|
|
|
// Print value
|
|
Last = (++Count >= Object->Len);
|
|
switch (Member->Type)
|
|
{
|
|
case jtNull :
|
|
write( OutputHandle, "null", 4 );
|
|
break;
|
|
|
|
case jtBool :
|
|
case jtInt :
|
|
case jtFloat :
|
|
write( OutputHandle, Member->Value, Member->Len );
|
|
break;
|
|
|
|
case jtString :
|
|
write( OutputHandle, "\"", 1 );
|
|
write( OutputHandle, Member->Value, Member->Len );
|
|
write( OutputHandle, "\"", 1 );
|
|
break;
|
|
|
|
case jtArray :
|
|
PrintArray( Member, Indent );
|
|
break;
|
|
|
|
case jtObject :
|
|
PrintObject( Member, Indent );
|
|
break;
|
|
}
|
|
if (!Last) {
|
|
write( OutputHandle, ",", 1 );
|
|
}
|
|
write( OutputHandle, "\n", 1 );
|
|
}
|
|
|
|
// Shorten spacer
|
|
SpacerLen -= 2;
|
|
Spacer[SpacerLen] = 0;
|
|
|
|
// Closing brace
|
|
Level--;
|
|
if (Object->Len) {
|
|
write( OutputHandle, Spacer, SpacerLen );
|
|
}
|
|
write( OutputHandle, "}", 1 );
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CJSONparse::PrintArray( TDataMember * Array, const int Indent )
|
|
{
|
|
TDataMember * Member;
|
|
bool First = true;
|
|
bool Last = false;
|
|
int Count = 0;
|
|
|
|
// Opening brace
|
|
write( OutputHandle, "[", 1 );
|
|
Level++;
|
|
|
|
// Extend spacer
|
|
memset( &Spacer[SpacerLen], ' ', 2 );
|
|
SpacerLen += 2;
|
|
Spacer[SpacerLen] = 0;
|
|
|
|
// Save parameters
|
|
for (Member = Array->FirstChild; Member != NULL; (Member = Member->Next))
|
|
{
|
|
// Write parameter name
|
|
if (First) {
|
|
First = false;
|
|
write( OutputHandle, "\n", 1 );
|
|
}
|
|
write( OutputHandle, Spacer, SpacerLen );
|
|
|
|
Last = (++Count >= Array->Len);
|
|
switch (Member->Type)
|
|
{
|
|
case jtNull :
|
|
write( OutputHandle, "null", 4 );
|
|
break;
|
|
|
|
case jtBool :
|
|
case jtInt :
|
|
case jtFloat :
|
|
write( OutputHandle, Member->Value, Member->Len );
|
|
break;
|
|
|
|
case jtString :
|
|
write( OutputHandle, "\"", 1 );
|
|
write( OutputHandle, Member->Value, Member->Len );
|
|
write( OutputHandle, "\"", 1 );
|
|
break;
|
|
|
|
case jtArray :
|
|
PrintArray( Member, Indent );
|
|
break;
|
|
|
|
case jtObject :
|
|
PrintObject( Member, Indent );
|
|
break;
|
|
}
|
|
if (!Last) {
|
|
write( OutputHandle, ",", 1 );
|
|
}
|
|
write( OutputHandle, "\n", 1 );
|
|
}
|
|
|
|
// Shorten spacer
|
|
SpacerLen -= 2;
|
|
Spacer[SpacerLen] = 0;
|
|
|
|
// Closing brace
|
|
Level--;
|
|
if (Array->Len) {
|
|
write( OutputHandle, Spacer, SpacerLen );
|
|
}
|
|
write( OutputHandle, "]", 1 );
|
|
return true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|