Files
redAcore/JSONparseCore.cpp
Charl Wentzel d8f8d6851c Important Update:
- JSONparseCore:
  - Bug fix: Always shows "No content after root" error
2019-11-04 13:15:04 +02:00

1075 lines
27 KiB
C++

/*
* JSONparseCore.cpp
*
* Created on: 5 Mar 2017
* Author: wentzelc
*/
// Standard C/C++ Libraries
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
// redA Libraries
#include "JSONparseCore.h"
//---------------------------------------------------------------------------
CJSONparse::CJSONparse()
{
SetBase( NULL );
}
//---------------------------------------------------------------------------
CJSONparse::CJSONparse( CDataMember * pDataTree )
{
if (!SetBase( pDataTree ))
DataTree = NULL;
}
//---------------------------------------------------------------------------
CJSONparse::~CJSONparse()
{
// Destroy buffer
if (Buffer) {
delete Buffer;
}
}
//---------------------------------------------------------------------------
bool CJSONparse::SetBase( CDataMember * Object )
{
// Validate
if (Object && !Object->isObject() && !Object->isNull())
return false;
// Set
DataTree = Object;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::WriteToString( const char * BasePath, char * TargetStr, int &MaxLen, const int Indent )
{
CDataMember * Member;
// Validate
if (!DataTree) {
Error = true;
sprintf( ErrorText, "No Data Tree set" );
MaxLen = 0;
return false;
}
// Prepare Output
if (!TargetStr) {
Error = true;
sprintf( ErrorText, "Could not write to String" );
MaxLen = 0;
return false;
}
OutputStr = TargetStr;
OutputMaxLen = MaxLen;
OutputPos = 0;
Print = JSONstrOutput;
// Get Root object
if (!(Member = DataTree->GetChild( BasePath ))) {
Error = true;
sprintf( ErrorText, "Invalid root object path" );
MaxLen = 0;
return false;
}
// Print to file
if (!PrintObject( Member, Indent ) ||
(Print( this, "\n", 1 ) < 0)) {
MaxLen = 0;
return false;
}
MaxLen = OutputPos;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::WriteToScreen( const char * BasePath, const int Indent )
{
// Print to screen
WriteToHandle( BasePath, STDOUT_FILENO, Indent );
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::WriteToFile( const char * BasePath, const char * Path, const char * FileName, const int Indent )
{
char FilePath[250] = "";
int PathLen = 0;
// Validate
if (!FileName) {
Error = true;
sprintf( ErrorText, "No File name specified" );
return false;
}
// Build file name
if (Path && *Path) {
strcpy( FilePath, Path );
PathLen = strlen( FilePath );
if (FilePath[PathLen] != '/') {
FilePath[PathLen++] = '/';
}
}
strcpy( &FilePath[PathLen], FileName );
// Read file
return WriteToFile( BasePath, FilePath, Indent );
}
//---------------------------------------------------------------------------
bool CJSONparse::WriteToFile( const char * BasePath, const char * FilePath, const int Indent )
{
int Handle = -1;
// Clear Error
Error = false;
// Validate
if (!FilePath || !FilePath[0]) {
Error = true;
sprintf( ErrorText, "No File path specified" );
return false;
}
// Open file
if ((Handle = open( FilePath, O_CREAT|O_WRONLY|O_TRUNC, 0660 )) < 0) {
Error = true;
sprintf( ErrorText, "Could not open file - [%d] %s", errno, strerror(errno) );
return false;
}
// Save to file
WriteToHandle( BasePath, Handle, Indent );
// Close file
close( Handle );
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::WriteToHandle( const char * BasePath, const int Handle, const int Indent )
{
CDataMember * Member;
// Validate
if (!DataTree) {
Error = true;
sprintf( ErrorText, "No Data Tree set" );
return false;
}
// Open file
if (Handle < 0) {
Error = true;
sprintf( ErrorText, "Could not write to invalid handle" );
return false;
}
OutputHandle = Handle;
Print = JSONfileOutput;
// Get Root object
if (!(Member = DataTree->GetChild( BasePath ))) {
Error = true;
sprintf( ErrorText, "Invalid root object path" );
OutputHandle = -1;
return false;
}
// Print to file
if (!PrintObject( Member, Indent ) ||
(Print( this, "\n", 1 ) < 0)) {
OutputHandle = -1;
return false;
}
OutputHandle = -1;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::ReadFromFile( const char * BasePath, const char * Path, const char * FileName )
{
char FilePath[250] = "";
int PathLen = 0;
// Validate
if (!FileName) {
Error = true;
sprintf( ErrorText, "No File name specified" );
return false;
}
// Build file name
if (Path && *Path) {
strcpy( FilePath, Path );
PathLen = strlen( FilePath );
if (FilePath[PathLen] != '/') {
FilePath[PathLen++] = '/';
}
}
strcpy( &FilePath[PathLen], FileName );
// Read file
return ReadFromFile( BasePath, FilePath );
}
//---------------------------------------------------------------------------
bool CJSONparse::ReadFromFile( const char * BasePath, const char * FilePath )
{
int Handle = -1;
bool result = false;
// Clear Error
Error = false;
// Validate
if (!FilePath || !FilePath[0]) {
Error = true;
sprintf( ErrorText, "No File path specified" );
return false;
}
// Open file
if ((Handle = open( FilePath, O_RDONLY )) < 0) {
Error = true;
sprintf( ErrorText, "Could not open file - [%d] %s", errno, strerror(errno) );
return false;
}
// Continuously refill buffer while loading
result = ReadFromHandle( BasePath, Handle, true );
// Close File
close( Handle );
return result;
}
//---------------------------------------------------------------------------
bool CJSONparse::ReadFromHandle( const char * BasePath, int Handle, bool pRefillBuffer )
{
bool result = false;
// Clear Error
Error = false;
// Validate
if (Handle < 0) {
Error = true;
sprintf( ErrorText, "Cannot read from invalid handle" );
return false;
}
InputHandle = Handle;
// Load Buffer
CreateBuffer( 1000 );
if (!FillBuffer()) {
Error = true;
sprintf( ErrorText, "Could not read from file" );
FreeBuffer();
return false;
}
// Continuously refill buffer while loading
RefillBuffer = pRefillBuffer;
result = ReadFromBuffer( BasePath );
RefillBuffer = false;
// Destroy buffer
FreeBuffer();
InputHandle = -1;
return result;
}
//---------------------------------------------------------------------------
bool CJSONparse::ReadFromString( const char * BasePath, const char * InString, const int Len )
{
bool result = false;
// Clear Error
Error = false;
// Load Buffer
CreateBuffer( Len );
Buffer->Push( true, InString, Len );
// Continuously refill buffer while loading
result = ReadFromBuffer( BasePath );
RefillBuffer = false;
// Destroy buffer
FreeBuffer();
InputHandle = -1;
return result;
}
//---------------------------------------------------------------------------
bool CJSONparse::ReadFromBuffer( const char * BasePath )
{
CDataMember * BaseMember = NULL;
// Validate
if (!DataTree) {
Error = true;
sprintf( ErrorText, "No Data Tree set" );
return false;
}
if (!Buffer) {
Error = true;
sprintf( ErrorText, "No Data Buffer defined" );
return false;
}
// Clear Error
Error = false;
// Get/Create Root object
if (!(BaseMember = DataTree->GetChild( BasePath, true ))) {
Error = true;
sprintf( ErrorText, "Invalid root object path" );
return false;
}
// Delete existing object contents
BaseMember->Clear();
// Position Counters
LineNo = 1;
CharNo = 0;
// Parse Root Object
SkipWhiteSpace();
if (!ParseObject( BaseMember ) && !Error && !ParseArray( BaseMember )) {
if (!Error) {
Error = true;
CharNo += BufPos-Mark;
sprintf( ErrorText, "First entry in file must be an Object or Array on line %d:%d", LineNo, CharNo );
}
FreeBuffer();
return false;
}
// Ensure remainder of file is empty
SkipWhiteSpace();
if (Error) {
return false;
}
else if (*BufPos != 0) {
Error = true;
CharNo += BufPos-Mark;
sprintf( ErrorText, "No content expected after Root object on line %d:%d", LineNo, CharNo );
FreeBuffer();
return false;
}
// Success
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::CreateBuffer( int pBufLen )
{
// Validate
if (!pBufLen)
return false;
// Create buffer
Buffer = new CShiftBuffer( pBufLen );
// Reset markers
Buffer->PeekDirect( &BufPos, 0 );
Mark = BufPos;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::FillBuffer()
{
char * BufStart;
int PosShift = BufPos - Mark;
// Remove all bytes up to Mark
Buffer->PeekDirect( &BufStart, 0 );
Buffer->Clear( Mark-BufStart );
// Read from file
Buffer->ReadFromFD( InputHandle );
// Reset Mark to start of buffer
Mark = BufStart;
BufPos = Mark + PosShift;
return true;
}
//---------------------------------------------------------------------------
void CJSONparse::FreeBuffer()
{
// Destroy buffer
if (Buffer) {
delete Buffer;
Buffer = NULL;
// Update Markers
BufPos = NULL;
Mark = NULL;
}
}
//---------------------------------------------------------------------------
void CJSONparse::SkipWhiteSpace()
{
while (true)
{
// Append buffer if required
if (!*BufPos && RefillBuffer)
FillBuffer();
// Skip whitespace
if (!isspace(*BufPos)) {
CharNo += BufPos-Mark;
Mark = BufPos;
break;
}
if (*BufPos == '\n') {
Mark = BufPos;
LineNo++;
CharNo = 0;
}
BufPos++;
}
}
//---------------------------------------------------------------------------
bool CJSONparse::ParseString( char ** Value, int &Len )
{
char * EndMark;
char * ValuePos = NULL;
char HexVal[5] = "";
// Check for opening quote
if (*BufPos != '"') {
return false;
}
// Clear values
*Value = NULL;
Len = 0;
// Mark start of quote
BufPos++;
// Check for closing quote
while (true)
{
// Check for special characters
if ((EndMark = strpbrk( BufPos, "\"\\\n\t\b\f\n\r" ))) { /*"\"/\\\n\t\b\f\n\r" (forward slash included)*/
BufPos = EndMark;
} else if (RefillBuffer) {
FillBuffer();
continue;
}
if (*BufPos == '"') {
// End of string found
BufPos++;
break;
}
else if (!*BufPos) {
Error = true;
sprintf( ErrorText, "Expect closing '\"' for string on line %d:%d", LineNo, CharNo+1 );
return false;
}
else if (*BufPos == '\\') {
if (!*(BufPos+1) && RefillBuffer) {
if (FillBuffer()) {
continue;
}
}
if (*(BufPos+1) == 'u') {
for (EndMark = BufPos+2; EndMark < BufPos+6; EndMark++) {
if (!*EndMark && RefillBuffer) {
FillBuffer();
continue;
}
if (!isxdigit( *EndMark )) {
Error = true;
CharNo += BufPos-EndMark;
sprintf( ErrorText, "Expect 4-digit hex value for escape value on line %d:%d", LineNo, CharNo );
return false;
}
}
BufPos += 6;
Len -= 5;
}
else if (strchr( "bfnrt/\\\"", *(BufPos+1) )) {
BufPos += 2;
Len -= 1;
}
else {
Error = true;
CharNo += BufPos-Mark;
sprintf( ErrorText, "Invalid escape sequence on line %d:%d", LineNo, CharNo );
return false;
}
}
else {
Error = true;
CharNo += BufPos-Mark;
sprintf( ErrorText, "Un-escaped special character in string on line %d:%d", LineNo, CharNo );
return false;
}
}
// Create Return value pointer
Len += BufPos-Mark-2;
*Value = (char*)malloc( Len+1 );
ValuePos = *Value;
// Convert value
BufPos = Mark+1;
while ((EndMark = strpbrk( BufPos, "\"\\" )))
{
// Copy portion
memcpy( ValuePos, BufPos, (EndMark-BufPos) );
ValuePos += (EndMark-BufPos);
BufPos = EndMark;
if (*BufPos == '"') {
break;
}
else if (*BufPos== '\\') {
if (*(BufPos+1) == 'u') {
strncpy( HexVal, BufPos+2, 4 );
*ValuePos = (char)strtol( HexVal, NULL, 16 );
ValuePos++;
BufPos += 6;
}
else {
switch (*(BufPos+1)) {
case 'b': *ValuePos = '\b'; break;
case 'f': *ValuePos = '\f'; break;
case 'n': *ValuePos = '\n'; break;
case 'r': *ValuePos = '\r'; break;
case 't': *ValuePos = '\t'; break;
case '/': *ValuePos = '/'; break;
case '\\': *ValuePos = '\\'; break;
case '"': *ValuePos = '"'; break;
}
ValuePos++;
BufPos +=2;
}
}
}
*ValuePos = 0;
BufPos++;
// Success
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::ParseObject( CDataMember * Object )
{
CDataMember * Member = NULL;
char * MemberName = NULL;
int Len = 0;
// Check for start of Object
if (*BufPos != '{') {
return false;
}
BufPos++;
// Set Type
Object->SetValue( jtObject );
while (true)
{
// Evaluate key name
SkipWhiteSpace();
if (*BufPos == '}') {
// End of Object
break;
}
else if (!ParseString( &MemberName, Len )) {
if (!Error) {
Error = true;
sprintf( ErrorText, "Expect quoted key name on line %d:%d", LineNo, CharNo );
}
return false;
}
else if (!Len) {
Error = true;
sprintf( ErrorText, "Empty parameter name on line %d:%d", LineNo, CharNo );
return false;
}
// Check if Member exists
Member = Object->GetChild( MemberName, true );
// Check for delimiter
SkipWhiteSpace();
if (*BufPos != ':') {
Error = true;
sprintf( ErrorText, "Expected ':' delimiter on line %d:%d", LineNo, CharNo );
return false;
}
BufPos++;
// Get Value
SkipWhiteSpace();
if (!ParseObject( Member ) && !Error && !ParseArray( Member ) && !Error && !ParseString( Member ) && !Error && !ParsePrimitive( Member ) ) {}
if (Error) {
// Destroy member
Object->DeleteCh( MemberName );
return false;
}
// Free Name
free( MemberName );
// 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:%d", LineNo, CharNo );
return false;
}
BufPos++;
// success
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::ParseArray( CDataMember * Array )
{
CDataMember ** Member;
// Check for start of Object
if (*BufPos != '[') {
return false;
}
BufPos++;
// Set Type
Array->SetValue( jtArray );
Member = &(Array->FirstChild);
while (true)
{
// Look for Member Name
SkipWhiteSpace();
if (*BufPos == ']') {
break;
}
// Add new element
*Member = new CDataMember( Array );
// Get Value
SkipWhiteSpace();
if (!ParseObject( *Member ) && !Error && !ParseArray( *Member ) && !Error && !ParseString( *Member ) && !Error && !ParsePrimitive( *Member ) ) {}
if (Error) {
delete *Member;
return false;
}
else {
Member = &((*Member)->NextPeer);
}
// 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:%d", LineNo, CharNo );
return false;
}
BufPos++;
// success
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::ParseString( CDataMember * Member )
{
char * Value = NULL;
int Len = 0;
// Try to parse
if (!ParseString( &Value, Len )) {
return false;
}
// Set string
Member->SetValuePtr( jtString, Value, Len );
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::ParsePrimitive( CDataMember * Member )
{
char * Value = NULL;
int Len = 0;
char * EndMark;
// Get end of value
while (true) {
if (!*BufPos && RefillBuffer)
FillBuffer();
if (!*BufPos || isspace(*BufPos) || (*BufPos == ',') || (*BufPos == '}') || (*BufPos == ']'))
break;
BufPos++;
}
// Check length of value
Len = BufPos - Mark;
if (!Len) {
Error = true;
sprintf( ErrorText, "Missing param value on line %d:%d", LineNo, CharNo );
return false;
}
// Check for primitive values
if ((Len == 4) && !strncasecmp( Mark, "null", 4 )) {
Member->SetValue( jtNull );
}
else if ((Len == 4) && !strncasecmp( Mark, "true", 4 )) {
Member->SetValue( jtBool, "1" );
}
else if ((Len == 5) && !strncasecmp( Mark, "false", 5 )) {
Member->SetValue( jtBool, "0" );
}
else {
// Try conversion to int
strtol( Mark, &EndMark, 10 );
if (EndMark == BufPos) {
Value = (char*)malloc( Len+1 );
memcpy( Value, Mark, Len );
Value[Len] = 0;
Member->SetValuePtr( 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;
Member->SetValuePtr( jtFloat, Value, Len );
}
else {
Error = true;
sprintf( ErrorText, "Invalid primitive param value on line %d:%d", LineNo, CharNo );
return false;
}
}
}
// Success
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::PrintString( char * String, int Len )
{
int BytesWritten = 0;
char TempBuf[7] = "";
// Start quote
if (Print( this, "\"", 1 ) < 0)
return false;
// Content
BufPos = String;
while (true)
{
// Scan for special chars
Mark = BufPos;
while ((*BufPos >= 32) && (*BufPos <= 126) && (*BufPos != '\\') && /*(*BufPos != '/') &&*/ (*BufPos != '"'))
BufPos++;
// Print Portion
if (Print( this, Mark, (BufPos-Mark) ) < 0)
return false;
// Handle special chars
if (BufPos-String >= Len) {
break;
}
else {
switch (*BufPos) {
case '\b': BytesWritten = Print( this, "\\b", 2 ); break;
case '\f': BytesWritten = Print( this, "\\f", 2 ); break;
case '\n': BytesWritten = Print( this, "\\n", 2 ); break;
case '\r': BytesWritten = Print( this, "\\r", 2 ); break;
case '\t': BytesWritten = Print( this, "\\t", 2 ); break;
case '/': BytesWritten = Print( this, "\\/", 2 ); break;
case '\\': BytesWritten = Print( this, "\\\\", 2 ); break;
case '"': BytesWritten = Print( this, "\\\"", 2 ); break;
default:
BytesWritten = sprintf( TempBuf, "\\u%04X", (unsigned char)*BufPos );
Print( this, TempBuf, 6 );
break;
}
if (BytesWritten < 0)
return false;
BufPos++;
}
}
// End Quote
if (Print( this, "\"", 1 ) < 0)
return false;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::PrintObject( CDataMember * Object, const int Indent )
{
CDataMember * Member;
bool First = true;
bool Last = false;
int Count = 0;
// Opening brace
if (Print( this, "{", 1 ) < 0)
return false;
// Check if empty
if (Object->Len == 0) {
if (Print( this, "}", 1 ) < 0)
return false;
return true;
}
// Extend spacer
if (Indent) {
memset( &Spacer[SpacerLen], ' ', 2 );
SpacerLen += 2;
Spacer[SpacerLen] = 0;
}
// Save parameters
for (Member = Object->FirstChild; Member != NULL; (Member = Member->NextPeer))
{
// Whitespace around first bracket
if (Indent) {
if (First) {
First = false;
if (Print( this, "\n", 1 ) < 0)
return false;
}
if (Print( this, Spacer, SpacerLen ) < 0)
return false;
}
// Print key name
if (!PrintString( Member->Name, strlen(Member->Name) ))
return false;
if (Indent) {
if (Print( this, " : ", 3 ) < 0)
return false;
} else {
if (Print( this, ":", 1 ) < 0)
return false;
}
// Print value
Last = (++Count >= Object->Len);
switch (Member->Type)
{
case jtNull :
if (Print( this, "null", 4 ) < 0)
return false;
break;
case jtBool :
if (!strcmp( Member->Value, "0" )) {
if (Print( this, "false", 5 ) < 0)
return false;
}
else {
if (Print( this, "true", 4 ) < 0)
return false;
}
break;
case jtInt :
case jtFloat :
if (Print( this, Member->Value, Member->Len ) < 0)
return false;
break;
case jtString :
if (!PrintString( Member->Value, Member->Len ))
return false;
break;
case jtArray :
if (!PrintArray( Member, Indent ))
return false;
break;
case jtObject :
if (!PrintObject( Member, Indent ))
return false;
break;
}
if (!Last) {
if (Print( this, ",", 1 ) < 0)
return false;
}
if (Indent) {
if (Print( this, "\n", 1 ) < 0)
return false;
}
}
// Shorten spacer
if (Indent) {
SpacerLen -= 2;
Spacer[SpacerLen] = 0;
}
// Closing brace
if (Indent) {
if (Print( this, Spacer, SpacerLen ) < 0)
return false;
}
if (Print( this, "}", 1 ) < 0)
return false;
return true;
}
//---------------------------------------------------------------------------
bool CJSONparse::PrintArray( CDataMember * Array, const int Indent )
{
CDataMember * Member;
bool First = true;
bool Last = false;
int Count = 0;
// Opening brace
if (Print( this, "[", 1 ) < 0)
return false;
// Check if empty
if (Array->Len == 0) {
if (Print( this, "]", 1 ) < 0)
return false;
return true;
}
// Extend spacer
if (Indent) {
memset( &Spacer[SpacerLen], ' ', 2 );
SpacerLen += 2;
Spacer[SpacerLen] = 0;
}
// Save parameters
for (Member = Array->FirstChild; Member != NULL; (Member = Member->NextPeer))
{
// Whitespace around brace
if (Indent) {
if (First) {
First = false;
if (Print( this, "\n", 1 ) < 0)
return false;
}
if (Print( this, Spacer, SpacerLen ) < 0)
return false;
}
Last = (++Count >= Array->Len);
switch (Member->Type)
{
case jtNull :
if (Print( this, "null", 4 ) < 0)
return false;
break;
case jtBool :
case jtInt :
case jtFloat :
if (Print( this, Member->Value, Member->Len ) < 0)
return false;
break;
case jtString :
if (!PrintString( Member->Value, Member->Len ))
return false;
break;
case jtArray :
if (!PrintArray( Member, Indent ))
return false;
break;
case jtObject :
if (!PrintObject( Member, Indent ))
return false;
break;
}
if (!Last) {
if (Print( this, ",", 1 ) < 0)
return false;
}
if (Indent) {
if (Print( this, "\n", 1 ) < 0)
return false;
}
}
// Shorten spacer
if (Indent) {
SpacerLen -= 2;
Spacer[SpacerLen] = 0;
}
// Closing brace
if (Indent) {
if (Print( this, Spacer, SpacerLen ) < 0)
return false;
}
if (Print( this, "]", 1 ) < 0)
return false;
return true;
}
//---------------------------------------------------------------------------