Skip to content

Instantly share code, notes, and snippets.

@JJL772
Created April 27, 2020 18:12
Show Gist options
  • Save JJL772/e36e368f14b9a3e804ff5347cdf03ade to your computer and use it in GitHub Desktop.
Save JJL772/e36e368f14b9a3e804ff5347cdf03ade to your computer and use it in GitHub Desktop.
/**
*
* Key Values parser
*
*/
#include "keyvalues.h"
#include <memory.h>
#include <stdlib.h>
#include <ctype.h>
#include <stack>
#include <stdio.h>
#include <errno.h>
KeyValues::KeyValues(const char* name) :
pCallback(NULL)
{
this->name = strdup(name);
}
KeyValues::KeyValues() :
pCallback(NULL)
{
}
KeyValues::~KeyValues()
{
if(this->name) free(name);
/* Free the keys */
for(auto key : this->keys)
{
if(key.key) free(key.key);
if(key.value) free(key.value);
}
/* Free child sections */
for(auto section : this->child_sections)
delete section;
}
void KeyValues::ParseFile(const char *file, bool use_escape_codes)
{
FILE* fs = fopen(file, "r");
if(!fs)
{
this->good = false;
return;
}
/* Get file size */
fseek(fs, 0, SEEK_END);
long int size = ftell(fs);
/* Read the entire file */
char* buffer = static_cast<char*>(malloc(size + 1));
fseek(fs, 0, SEEK_SET);
fread(buffer, size, 1, fs);
fclose(fs);
buffer[size] = 0;
this->ParseString(buffer, use_escape_codes);
/* Free the allocated buffer */
free(buffer);
}
void KeyValues::ParseString(const char* string, bool escape)
{
int nline = 0, nchar = 0, bracket_level = 0;
bool inquote = false, incomment = false, parsed_key = false;
char buf[512];
int bufpos = 0;
size_t nlen = strlen(string);
KeyValues* RootKV = this;
KeyValues* CurrentKV = this;
key_t CurrentKey;
std::stack<KeyValues*> SectionStack;
SectionStack.push(this);
this->good = true;
for(int i = 0; i < nlen; i++, nchar++)
{
char c = string[i];
char pc = 0, nc = 0;
if(i > 0) pc = string[i-1];
if(i < nlen-1) nc = string[i+1];
if(c == '\n')
{
/* Check for errors */
if(inquote) this->ReportError(nline, nchar, EError::MISSING_QUOTE);
/* Save any tokens we might have at the end of the line */
if(bufpos > 0)
{
buf[bufpos] = 0;
if(parsed_key)
{
parsed_key = false;
CurrentKey.value = strdup(buf);
CurrentKV->keys.push_back(CurrentKey);
CurrentKey.key = CurrentKey.value = NULL;
}
else
{
CurrentKey.key = strdup(buf);
parsed_key = true;
}
bufpos = 0;
}
incomment = false;
inquote = false;
nline++;
nchar = 0;
continue;
}
if(c == '/' && (pc == '/' || nc == '/') && !inquote)
{
incomment = true;
continue;
}
if(incomment) continue;
/* Handle exit/enter quote */
if(c == '"')
{
if(inquote)
{
inquote = false;
buf[bufpos] = 0;
if(parsed_key)
{
parsed_key = false;
CurrentKey.value = strdup(buf);
CurrentKV->keys.push_back(CurrentKey);
CurrentKey.key = CurrentKey.value = NULL;
}
else
{
CurrentKey.key = strdup(buf);
parsed_key = true;
}
bufpos = 0;
}
else
{
inquote = true;
}
continue;
}
/* Enter scope */
if(!inquote && c == '{')
{
KeyValues* pKV;
if(parsed_key)
{
pKV = new KeyValues(CurrentKey.key);
free(CurrentKey.key);
CurrentKey.key = 0;
}
else if(bufpos > 0)
{
buf[bufpos] = 0;
bufpos = 0;
pKV = new KeyValues(buf);
}
/* In the event that buf is empty and we've not yet parsed a key, issue an error about an unnamed section */
else
{
pKV = new KeyValues();
this->ReportError(nline, nchar, EError::UNNAMED_SECTION);
}
parsed_key = false;
CurrentKV->child_sections.push_back(pKV);
CurrentKV = pKV;
SectionStack.push(pKV);
bracket_level++;
continue;
}
/* Exit scope */
else if(!inquote && c == '}')
{
SectionStack.pop();
CurrentKV = SectionStack.top();
bracket_level--;
continue;
}
/* Eat anything that isn't space into the buffer */
if(!isspace(c) || (inquote))
{
buf[bufpos++] = c;
continue;
}
/* Finally, handle cases where we've encountered a space and we are not in a quote */
if(isspace(c) && !inquote && bufpos > 0)
{
inquote = false;
buf[bufpos] = 0;
if(parsed_key)
{
parsed_key = false;
CurrentKey.value = strdup(buf);
CurrentKV->keys.push_back(CurrentKey);
CurrentKey.key = CurrentKey.value = NULL;
}
else
{
CurrentKey.key = strdup(buf);
parsed_key = true;
}
bufpos = 0;
}
}
/* Verify that the parsing completed fine */
if(inquote) this->ReportError(-1, 0, EError::MISSING_QUOTE);
if(bracket_level > 0) this->ReportError(-1, 0, EError::UNTERMINATED_SECTION);
}
void KeyValues::ReportError(int line, int _char, EError err)
{
if(pCallback) pCallback(line, _char, err);
this->good = false;
}
void KeyValues::DumpToStream(FILE* fs)
{
this->DumpToStreamInternal(fs, 0);
}
void KeyValues::DumpToStreamInternal(FILE* fs, int indent)
{
/* Ensure indent < 128 or fail */
if(!fs || indent >= 127) return;
char buf[128];
for(int i = 0; i < indent; i++) buf[i] = '\t';
buf[indent] = 0;
if(this->name)
{
fprintf(fs, "%s\"%s\"\n%s{\n", buf, this->name, buf);
buf[indent] = '\t';
buf[indent+1] = 0;
}
for(auto key : this->keys)
fprintf(fs, "%s\"%s\" \"%s\"\n", buf, key.key, key.value);
for(auto section : this->child_sections)
{
section->DumpToStreamInternal(fs, indent+1);
}
buf[indent] = 0;
if(this->name) fprintf(fs, "%s}\n", buf);
}
bool KeyValues::GetBool(const char* key, bool _default)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(_key.key, key) == 0)
{
bool ok = false;
bool b = _key.ReadBool(ok);
if(ok) return b;
return _default;
}
}
return _default;
}
int KeyValues::GetInt(const char* key, int _default)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(_key.key, key) == 0)
{
bool ok = false;
int i = (int)_key.ReadInt(ok);
if(ok) return i;
return _default;
}
}
return _default;
}
float KeyValues::GetFloat(const char* key, float _default)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(_key.key, key) == 0)
{
bool ok = false;
float f = (float)_key.ReadFloat(ok);
if(ok) return f;
return _default;
}
}
return _default;
}
double KeyValues::GetDouble(const char* key, double _default)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(_key.key, key) == 0)
{
bool ok = false;
double f = _key.ReadFloat(ok);
if(ok) return f;
return _default;
}
}
return _default;
}
const char* KeyValues::GetString(const char* key)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(key, _key.key) == 0)
return _key.value;
}
return NULL;
}
bool KeyValues::HasKey(const char* key)
{
for(auto _key : this->keys)
{
if(_key.key && strcmp(key, _key.key) == 0) return true;
}
return false;
}
bool KeyValues::key_t::ReadBool(bool& ok)
{
ok = true;
if(this->cached == ELastCached::BOOL)
{
return this->cachedv.bval;
}
/* If the value is not cached, parse it from a string */
if(!this->value)
{
ok = false;
return false;
}
/* For bool, check if we've got TRUE or FALSE */
if(strcasecmp(this->value, "true") == 0 || strcmp(this->value, "1") == 0)
{
this->cachedv.bval = true;
this->cached = ELastCached::BOOL;
return true;
}
else if(strcasecmp(this->value, "false") == 0 || strcmp(this->value, "0") == 0)
{
this->cachedv.bval = false;
this->cached = ELastCached::BOOL;
return false;
}
ok = false;
return false;
}
long int KeyValues::key_t::ReadInt(bool& ok)
{
ok = true;
if(this->cached == ELastCached::INT)
{
return this->cachedv.ival;
}
/* Check that value is not null */
if(!this->value)
{
ok = false;
return 0;
}
long int v = strtol(this->value, NULL, 10);
if(errno == 0)
{
this->cached = ELastCached ::INT;
this->cachedv.ival = v;
return v;
}
ok = false;
return 0;
}
double KeyValues::key_t::ReadFloat(bool& ok)
{
ok = true;
if(this->cached == ELastCached::FLOAT)
{
return this->cachedv.fval;
}
/* Check if value is null */
if(!this->value)
{
ok = false;
return 0.0f;
}
double f = strtod(this->value, NULL);
if(errno == 0)
{
this->cached = ELastCached::FLOAT;
this->cachedv.fval = f;
return f;
}
ok = false;
return 0.0f;
}
KeyValues* KeyValues::GetChild(const char *name)
{
for(auto _child : this->child_sections)
{
if(_child->name && strcmp(name, _child->name) == 0)
{
return _child;
}
}
return nullptr;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment