257 lines
5.7 KiB
C++
257 lines
5.7 KiB
C++
#include <ZUtil/ZIniReader.hpp>
|
|
|
|
#include <ZSTL/ZBasicStringAlgo.hpp>
|
|
|
|
bool ZIniReader::Read(const char* data, size_t length)
|
|
{
|
|
/*
|
|
Algorithm:
|
|
1) Read a line, ignore blank lines and comment lines
|
|
2) If line is KVP
|
|
2.1) No section? -> Error
|
|
2.2) Section exists? -> Parse KVP
|
|
2.2.1) Takes form of K = V? -> Add KVP
|
|
2.2.2) Doesn't? -> Error
|
|
3) Else if section
|
|
3.1) If already seen section -> Error
|
|
3.2) Create section hashtable, insert it
|
|
4) Next line
|
|
*/
|
|
|
|
ClearData();
|
|
ErrorMessage.Clear();
|
|
|
|
ZHashMap<ZString, ZString>* currentSection = NULL;
|
|
|
|
size_t i = 0; //Current index into file data
|
|
size_t lineStart = 0; //Start of line
|
|
size_t lineLength;
|
|
uint32_t lineNumber = 1;
|
|
|
|
//Parse loop
|
|
while(i < length)
|
|
{
|
|
char c = data[i];
|
|
i++;
|
|
|
|
//Treat final character as EOL too
|
|
if(i < length)
|
|
{
|
|
//Split on \n or \r
|
|
if(c != 0x0A && c != 0x0D)
|
|
continue;
|
|
else //Found EOL character
|
|
{
|
|
//Line length does NOT include EOL character
|
|
lineLength = i - 1 - lineStart;
|
|
}
|
|
}
|
|
else //Final line, don't remove extra character
|
|
lineLength = i - lineStart;
|
|
|
|
//Line must be non-blank
|
|
if(lineLength > 0 && data[lineStart] != '\n')
|
|
{
|
|
//Get pointer to start of line
|
|
const char* line = &data[lineStart];
|
|
|
|
//Ignore comments
|
|
if(line[0] != ';' && line [0] != '#')
|
|
{
|
|
//Section name
|
|
if(line[0] == '[')
|
|
{
|
|
size_t j=1; //Start after '['
|
|
//Read until closing '] or EOL
|
|
while(j<lineLength && line[j] != ']')
|
|
{
|
|
if(!isalnum(line[j]) && line[j] != '_' && line[j] != ' ')
|
|
{
|
|
SetError("Header must contain alphanumeric, space, or _ only", lineNumber);
|
|
return false;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
//Reached end of line but no terminator
|
|
if(j == lineLength)
|
|
{
|
|
SetError("Missing closing \']\'", lineNumber);
|
|
return false;
|
|
}
|
|
|
|
//Copy section name to ZString
|
|
ZString sectionName(line+1, j-1);
|
|
|
|
//Check for existence
|
|
if(StringSectionMap.ContainsKey(sectionName))
|
|
{
|
|
SetError("Section already exists", lineNumber);
|
|
return false;
|
|
}
|
|
|
|
//Construct new table
|
|
ZHashMap<ZString, ZString>* table = new(std::nothrow) ZHashMap<ZString, ZString>();
|
|
if(table != NULL)
|
|
{
|
|
//Add mapping
|
|
StringSectionMap.Put(sectionName, table);
|
|
Sections.PushBack(table);
|
|
SectionNames.PushBack(sectionName);
|
|
|
|
//Save this as the current section
|
|
currentSection = table;
|
|
}
|
|
else
|
|
{
|
|
SetError("Out of memory", lineNumber);
|
|
return false;
|
|
}
|
|
}
|
|
else if(!isspace(line[0])) //Not a space, not comment, not section -> KVP
|
|
{
|
|
//Did we find a section first?
|
|
if(currentSection != NULL)
|
|
{
|
|
size_t j=0; //length of key
|
|
|
|
//Scan until space, equals sign, or EOL
|
|
while(j<lineLength && line[j] != '=' && !isspace(line[j]))
|
|
{
|
|
if(!isalnum(line[j]) && line[j] != '_')
|
|
{
|
|
SetError("Key must contain alphanumeric or '_' only", lineNumber);
|
|
return false;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
//Didn't find either a space or '='
|
|
if(j == lineLength)
|
|
{
|
|
SetError("Missing '=' after key name", lineNumber);
|
|
return false;
|
|
}
|
|
|
|
//Copy key to ZString
|
|
ZString key(line, j);
|
|
|
|
if(line[j] != '=')
|
|
{
|
|
//Skip remaining whitespace
|
|
while(j<lineLength && isspace(line[j]))
|
|
j++;
|
|
|
|
if(j == lineLength || line[j] != '=')
|
|
{
|
|
SetError("Missing '=' after key name", lineNumber);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Now we definitely point to a '=' character. Ensure there is data after it
|
|
if(j+1 == lineLength)
|
|
{
|
|
SetError("Expected value after '='", lineNumber);
|
|
return false;
|
|
}
|
|
j++;
|
|
|
|
//Skip any whitespace after '='
|
|
while(j<lineLength && isspace(line[j]))
|
|
j++;
|
|
|
|
//Special case: if we hit EOL, then we have an empty value
|
|
if(j == lineLength)
|
|
{
|
|
currentSection->Put(key, "");
|
|
}
|
|
else
|
|
{
|
|
ZString val(&line[j], lineLength-j);
|
|
currentSection->Put(key, val);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetError("Values must be in a section", lineNumber);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
SetError("Invalid line. Must be a section, comment, or value", lineNumber);
|
|
return false;
|
|
}
|
|
}
|
|
} //if(non-blank line)
|
|
|
|
//Check for \r \n pattern
|
|
if(c == '\r') //we expect a \n, but verify
|
|
{
|
|
if(i < length && data[i] == '\n')
|
|
i++;
|
|
}
|
|
|
|
//Start on next line
|
|
lineStart = i;
|
|
lineNumber++;
|
|
}
|
|
|
|
return !Sections.Empty();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
const ZHashMap<ZString, ZString>* ZIniReader::GetSection(const ZString& sectionName)
|
|
{
|
|
ZHashMap<ZString, ZHashMap<ZString, ZString>* >::Iterator it = StringSectionMap.Find(sectionName);
|
|
|
|
if(it.HasCurrent())
|
|
return it.GetValue();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZIniReader::SetError(const char* message, uint32_t line)
|
|
{
|
|
ZStringAlgo::BuildPrintf(ErrorMessage, "Line %u: %s", line, message);
|
|
|
|
//Clear all data
|
|
ClearData();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZIniReader::ClearData()
|
|
{
|
|
for(size_t i = 0; i < Sections.Size(); i++)
|
|
delete Sections[i];
|
|
|
|
Sections.Clear();
|
|
SectionNames.Clear();
|
|
StringSectionMap.Clear();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZIniReader::GetKVTree(ZKVTree& _kvtree)
|
|
{
|
|
for(size_t i = 0; i < Sections.Size(); i++)
|
|
{
|
|
ZHashMap<ZString, ZString>* hmap = Sections[i];
|
|
ZHashMap<ZString, ZString>::Iterator it = hmap->Begin();
|
|
|
|
for(; it != hmap->End(); ++it)
|
|
{
|
|
ZString key = SectionNames[i]+"."+it.GetKey();
|
|
ZString& value = it.GetValue();
|
|
|
|
_kvtree.Put(key, value);
|
|
}
|
|
}
|
|
}
|