diff --git a/ConfigCore.cpp b/ConfigCore.cpp index 8b4d3ad..78bc116 100644 --- a/ConfigCore.cpp +++ b/ConfigCore.cpp @@ -13,13 +13,26 @@ #include #include #include -#include //--------------------------------------------------------------------------- CConfigCore::CConfigCore() { + // Parameter tree FirstParam = NULL; + + // File Operation + InputFile = NULL; + BufLen = 500; + Buffer = NULL; + BufEnd = NULL; + + // Parsing operation + BufPos = NULL; + LineMark = NULL; + LineNo = 0; + Error = false; + ErrorText[0] = 0; } //--------------------------------------------------------------------------- @@ -82,21 +95,23 @@ bool CConfigCore::DeleteAll() bool CConfigCore::SetParam( TConfigParam * Param, EJSONtype Type, const char * Value, const int Len ) { // Clear previous value - if (Param->Value != NULL) + if (Param->Value != NULL) { free( Param->Value ); + } // Set type Param->Type = Type; // Set new value - if (Value == NULL) { + if (Type == jtNull) { Param->Len = 0; Param->Value = NULL; } else { Param->Len = (Len == -1)? strlen(Value) : Len; - Param->Value = (char *)calloc( 1, sizeof(Param->Len+1) ); + Param->Value = (char *)malloc( sizeof(Param->Len+1) ); memcpy( Param->Value, Value, Param->Len+1 ); + Param->Value[Param->Len] = 0; } return true; @@ -153,6 +168,17 @@ bool CConfigCore::SetParamBool( const char * Name, const bool Value ) } //--------------------------------------------------------------------------- +bool CConfigCore::SetParamNull( const char * Name ) +{ + TConfigParam * Param = NULL; + + // Create new param if it doesn't exist + Param = FindCreateParam( Name ); + SetParam( Param, jtNull, NULL, -1 ); + return false; +} +//--------------------------------------------------------------------------- + const EJSONtype CConfigCore::GetParamType( const char * Name ) { TConfigParam * Param; @@ -161,7 +187,7 @@ const EJSONtype CConfigCore::GetParamType( const char * Name ) return Param->Type; } else { - return jtNone; + return jtNull; } } //--------------------------------------------------------------------------- @@ -170,7 +196,7 @@ const char * CConfigCore::GetParamStr( const char * Name, const char * Default ) { TConfigParam * Param; - if ((Param = GetParam( Name ))) { + if ((Param = GetParam( Name )) && (Param->Type == jtString)) { return Param->Value; } else { @@ -183,7 +209,7 @@ const char * CConfigCore::GetParamStr( const char * Name, int &Len, const char * { TConfigParam * Param; - if ((Param = GetParam( Name ))) { + if ((Param = GetParam( Name )) && (Param->Type == jtString)) { Len = Param->Len; return Param->Value; } @@ -198,7 +224,7 @@ const long CConfigCore::GetParamInt( const char * Name, long Default ) { TConfigParam * Param; - if ((Param = GetParam( Name ))) { + if ((Param = GetParam( Name )) && (Param->Type == jtInt)) { return strtol( Param->Value, NULL, 10 ); } else { @@ -211,7 +237,7 @@ const double CConfigCore::GetParamFloat( const char * Name, double Default ) { TConfigParam * Param; - if ((Param = GetParam( Name ))) { + if ((Param = GetParam( Name )) && (Param->Type == jtFloat)) { return strtod( Param->Value, NULL ); } else { @@ -224,7 +250,7 @@ const bool CConfigCore::GetParamBool( const char * Name, bool Default ) { TConfigParam * Param; - if ((Param = GetParam( Name ))) { + if ((Param = GetParam( Name )) && (Param->Type == jtBool)) { return ((!strcasecmp( Param->Value, "true" ))? true : false ); } else { @@ -233,21 +259,9 @@ const bool CConfigCore::GetParamBool( const char * Name, bool Default ) } //--------------------------------------------------------------------------- -bool CConfigCore::LoadFile( const char * FilePath ) +bool CConfigCore::LoadFile( const char * FilePath, int pBufLen ) { - FILE * InputFile = NULL; - char InputStr[500]; - char * StrPos; - char * Mark = NULL; - char * EndMark = NULL; - char * LineMark = NULL; - int LineNo = 1; - char ParamName[50] = ""; - long IntVal = 0; - double FloatVal = 0.0; - bool Error = false; - char ErrorText[50] = ""; - int BufCount = 0; + TConfigParam * Object = NULL; // Validate if (!FilePath || !FilePath[0]) @@ -257,167 +271,343 @@ bool CConfigCore::LoadFile( const char * FilePath ) if (!(InputFile = fopen( FilePath, "r" ))) return false; - while (!Error && (BufCount = fread( InputStr, 1, 500, InputFile ))) + // 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 + DeleteAll(); + Object = FindCreateParam( "root" ); + + // Parse Root Object + SkipWhiteSpace(); + if (!ParseObject( Object )) { + 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 + FreeBuffer(); + return true; +} +//--------------------------------------------------------------------------- + +bool CConfigCore::CreateBuffer( int pBufLen ) +{ + // Validate + if (!pBufLen) + return false; + + // Create buffer + BufLen = pBufLen; + Buffer = (char*)malloc( BufLen+1 ); + + // Reset markers + BufEnd = Buffer; + BufPos = Buffer; + LineMark = Buffer; + + return true; +} +//--------------------------------------------------------------------------- + +bool CConfigCore::FillBuffer() +{ + int BufCount = 0; + + // Read from file + BufCount = fread( Buffer, 1, 500, InputFile ); + if (BufCount < 1) { + return false; + } + + // Update Markers + BufEnd += BufCount; + BufPos = Buffer; + LineMark = Buffer; + + return true; +} +//--------------------------------------------------------------------------- + +void CConfigCore::FreeBuffer() +{ + // Destroy buffer + if (Buffer) { + free( Buffer ); + Buffer = NULL; + } + BufLen = 0; +} +//--------------------------------------------------------------------------- + +bool CConfigCore::ParseObject( TConfigParam * Object ) +{ + TConfigParam * Param = NULL; + char * ParamName = NULL; + int Len = 0; + + // Clear Values + Error = false; + + // Check for start of Object + if (*BufPos != '{') { + return false; + } + BufPos++; + + while (true) { - // Read Param Name - StrPos = InputStr; - LineMark = StrPos-1; - - // Process string - while (!Error && (StrPos < InputStr + BufCount)) - { - // Skip white space - while (isspace(*StrPos)) { - if (*StrPos == '\n') { - LineMark = StrPos; - LineNo++; - } - StrPos++; - } - if (*StrPos == 0) { - break; - } - - // Check for opening quote - if (*StrPos != '"') { + // Look for Param Name + SkipWhiteSpace(); + if (*BufPos == 0) { + Error = true; + sprintf( ErrorText, "Expect quoted key name on line %d:%ld", LineNo, BufPos-LineMark ); + } + else if (!ParseString( &ParamName, &Len )) { + if (!Error) { Error = true; - sprintf( ErrorText, "Expect opening '\"' for param name on line %d [%ld]", LineNo, StrPos-LineMark ); - break; + 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; + } - // Mark start of param name - StrPos++; - Mark = StrPos; + // Create parameter + Param = FindCreateParam( ParamName ); - // Check for closing quote - if (!(StrPos = strchr( StrPos, '"' ))) { - Error = true; - sprintf( ErrorText, "Expect closing '\"' for param name on line %d [%ld]", LineNo, Mark-LineMark ); - break; - } + // Check for delimiter + SkipWhiteSpace(); + if (*BufPos != ':') { + Error = true; + sprintf( ErrorText, "Expected ':' delimiter on line %d:%ld", LineNo, BufPos-LineMark ); + return false; + } + BufPos++; - // Validate size of Param name - if (StrPos - Mark == 0) { - Error = true; - sprintf( ErrorText, "Empty parameter name on line %d [%ld]", LineNo, StrPos-LineMark ); - break; - } + // Get Value + SkipWhiteSpace(); + if (!ParseObject( Param ) && + !Error && + !ParseString( &Param->Value, &Param->Len, &Param->Type ) && + !Error && + !ParsePrimitive( &Param->Value, &Param->Len, &Param->Type )) { + return false; + } - // Copy Parameter name - memcpy( ParamName, Mark, (StrPos-Mark) ); - ParamName[(StrPos-Mark)] = 0; - StrPos++; + // Check if more parameters to follow + SkipWhiteSpace(); + if (*BufPos != ',') { + // No more parameters + break; + } + BufPos++; + } - // Skip white space - while (isspace(*StrPos)) { - if (*StrPos == '\n') { - LineMark = StrPos; - LineNo++; - } - StrPos++; - } + // 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++; - // Check for delimiter - if (*StrPos != ':') { - Error = true; - sprintf( ErrorText, "Expected ':' delimiter on line %d [%ld]", LineNo, StrPos-LineMark ); - break; - } - StrPos++; + // success + return true; +} +//--------------------------------------------------------------------------- - // Skip white space - while (isspace(*StrPos)) { - if (*StrPos == '\n') { - LineMark = StrPos; - LineNo++; - } - StrPos++; - } +bool CConfigCore::ParseString( char ** Value, int *pLen, EJSONtype *pType ) +{ + char * Mark; + char * EndMark; + int Len; - // Mark start value - Mark = StrPos; + // Clear value + Error = false; + *Value = NULL; + Len = 0; - // Get Value - if (*StrPos == '"') - { - // Quoted values - // Check for closing quote - StrPos++; - if (!(StrPos = strchr( StrPos, '"' ))) { - Error = true; - sprintf( ErrorText, "Expect closing '\"' for param value on line %d [%ld]", LineNo, Mark-LineMark ); - break; - } + // Check for opening quote + if (*BufPos != '"') { + return false; + } - // Create new string Parameter - SetParamStr( ParamName, Mark+1, StrPos-Mark-2 ); - StrPos++; - } - else - { - // Primitive Values - // Get end of value - while ((*StrPos != 0) && !isspace(*StrPos) && (*StrPos != ',')) { - StrPos++; - } + // Mark start of quote + Mark = BufPos; + BufPos++; - // Analyze value - if (StrPos - Mark == 0) { - Error = true; - sprintf( ErrorText, "Missing param value on line %d [%ld]", LineNo, Mark-LineMark ); - break; - } - else if ((StrPos-Mark == 4) && !strncasecmp( Mark, "true", 4 )) { - // Create new boolean Parameter - SetParamBool( ParamName, true ); - } - else if ((StrPos-Mark == 5) && !strncasecmp( Mark, "false", 4 )) { - // Create new boolean Parameter - SetParamBool( ParamName, false ); - } - else { - // Try conversion to int - IntVal = strtol( Mark, &EndMark, 10 ); - if (EndMark == StrPos) { - // Create new integer Parameter - SetParamInt( ParamName, IntVal ); - } - else { - // Try conversion to float - FloatVal = strtod( Mark, &EndMark ); - if (EndMark == StrPos) { - // Create new float Parameter - SetParamFloat( ParamName, FloatVal ); - } - else { - Error = true; - sprintf( ErrorText, "Invalid primitive param value on line %d [%ld]", LineNo, Mark-LineMark ); - break; - } + // 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; } - - // Skip white space - while (isspace(*StrPos)) { - if (*StrPos == '\n') { - LineMark = StrPos; - LineNo++; - } - StrPos++; + else if (strchr( "bfnrt/\\\"", *(BufPos+1) )) { + BufPos += 2; } - - // Check if more parameters to follow - if ((*StrPos != ',') && (*StrPos != 0)) { + else { Error = true; - sprintf( ErrorText, "Parameter separator ',' expected on line %d [%ld]", LineNo, StrPos-LineMark ); - break; + sprintf( ErrorText, "Invalid escape sequence on line %d:%ld", LineNo, Mark-LineMark ); + return false; } - StrPos++; + } + else { + Error = true; + sprintf( ErrorText, "Un-escaped special character in string on line %d:%ld", LineNo, BufPos-LineMark ); + return false; } } - return ((Error)? false : true); + // Validate size of Param 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 + if (pType) + *pType = jtString; + if (pLen) + *pLen = Len; + + // Success + return true; +} +//--------------------------------------------------------------------------- + +bool CConfigCore::ParsePrimitive( char ** Value, int * pLen, EJSONtype * pType ) +{ + char * Mark; + char * EndMark; + int Len; + + // Clear value + Error = false; + *Value = NULL; + Len = 0; + + // 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 )) { + if (pType) + *pType = jtNull; + if (pLen) + *pLen = Len; + *Value = (char*)malloc( 5 ); + strcpy( *Value, "null" ); + } + else if ((Len == 4) && !strncasecmp( Mark, "true", 4 )) { + if (pType) + *pType = jtBool; + if (pLen) + *pLen = Len; + *Value = (char*)malloc( 5 ); + strcpy( *Value, "true" ); + } + else if ((Len == 5) && !strncasecmp( Mark, "false", 5 )) { + if (pType) + *pType = jtBool; + if (pLen) + *pLen = Len; + *Value = (char*)malloc( 6 ); + strcpy( *Value, "false" ); + } + else { + // Try conversion to int + strtol( Mark, &EndMark, 10 ); + if (EndMark == BufPos) { + if (pType) + *pType = jtInt; + if (pLen) + *pLen = Len; + *Value = (char*)malloc( Len+1 ); + memcpy( *Value, Mark, Len ); + *Value[Len] = 0; + } + else { + // Try conversion to float + strtod( Mark, &EndMark ); + if (EndMark == BufPos) { + if (pType) + *pType = jtFloat; + if (pLen) + *pLen = Len; + *Value = (char*)malloc( Len+1 ); + memcpy( *Value, Mark, Len ); + *Value[Len] = 0; + } + else { + Error = true; + sprintf( ErrorText, "Invalid primitive param value on line %d:%ld", LineNo, Mark-LineMark ); + return false; + } + } + } + + // Success + return true; } //--------------------------------------------------------------------------- @@ -452,6 +642,10 @@ bool CConfigCore::SaveFile( const char * FilePath, const int ValueTab ) switch (Param->Type) { + case jtNull : + fprintf( OutputFile, "%s,\n", "null" ); + break; + case jtBool : case jtInt : case jtFloat : @@ -462,7 +656,12 @@ bool CConfigCore::SaveFile( const char * FilePath, const int ValueTab ) fprintf( OutputFile, "\"%s\",\n", Param->Value ); break; - default : + case jtArray : + fprintf( OutputFile, "[],\n" ); + break; + + case jtObject : + fprintf( OutputFile, "{},\n" ); break; } } diff --git a/ConfigCore.h b/ConfigCore.h index 27fc100..b20b060 100644 --- a/ConfigCore.h +++ b/ConfigCore.h @@ -14,10 +14,12 @@ // Standard C/C++ Libraries #include #include +#include +#include //--------------------------------------------------------------------------- -typedef enum { jtNone = 0, jtBool = 1, jtInt = 2, jtFloat = 3, jtString = 4, jtArray = 5, jtObject = 6 } EJSONtype; +typedef enum { jtNull = 0, jtBool = 1, jtInt = 2, jtFloat = 3, jtString = 4, jtArray = 5, jtObject = 6 } EJSONtype; //--------------------------------------------------------------------------- @@ -41,6 +43,40 @@ class CConfigCore private: TConfigParam * FirstParam; + // File operation + FILE * InputFile; + char * Buffer; + int BufLen; + + // Parsing operation + char * BufEnd; + + char * BufPos; + char * LineMark; + int LineNo; + + bool Error; + char ErrorText[100]; + + // File Buffer operation + bool CreateBuffer( int pBufLen ); + bool FillBuffer(); + void FreeBuffer(); + + // Parsing functions + inline void SkipWhiteSpace() { + while (isspace(*BufPos)) { + if (*BufPos == '\n') { + LineMark = BufPos; + LineNo++; + } + BufPos++; + } + } + bool ParseObject( TConfigParam * Object ); + bool ParseString( char ** Value, int * pLen = NULL, EJSONtype * pType = NULL ); + bool ParsePrimitive( char ** Value, int * pLen = NULL, EJSONtype * pType = NULL ); + // Find Param inline TConfigParam * GetParam( const char * Name ) { TConfigParam * Param = FirstParam; @@ -74,10 +110,11 @@ public: CConfigCore(); ~CConfigCore(); - bool SetParamStr( const char * Name, const char * Value = NULL, const int Len = -1 ); + bool SetParamStr( const char * Name, const char * Value = NULL, const int Len = -1 ); // Use Len param if Value contains NULL values bool SetParamInt( const char * Name, const long Value ); bool SetParamFloat( const char * Name, const double Value ); bool SetParamBool( const char * Name, const bool Value ); + bool SetParamNull( const char * Name ); const EJSONtype GetParamType( const char * Name ); @@ -90,8 +127,10 @@ public: bool DeleteParam( const char * Name ); bool DeleteAll(); - bool LoadFile( const char * FilePath ); + bool LoadFile( const char * FilePath, int pBufLen = 500 ); bool SaveFile( const char * FilePath, const int ValueTab = 20 ); + + const char * GetError() { return ((Error)? ErrorText : "Success"); }; }; #endif /* REDACORE_CONFIGCORE_H_ */