You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

771 lines
20 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnIni/Private/pnIniCore.cpp
*
***/
#include "../Pch.h"
#pragma hdrstop
/*****************************************************************************
*
* Internal functions
*
***/
//===========================================================================
static wchar * TrimWhitespace (wchar * name) {
while (isspace((char) *name))
++name;
for (wchar * term = name; *term; ++term) {
if (isspace((char) *term)) {
*term = 0;
break;
}
}
return name;
}
/****************************************************************************
*
* IniValue
*
***/
//===========================================================================
IniValue::IniValue (IniKey * key, unsigned lineNum)
: fKey(key)
, fLineNum(lineNum)
{
fIndex = key->fValues.Add(this);
}
//===========================================================================
IniValue::~IniValue () {
wchar ** cur = fArgs.Ptr();
wchar ** end = fArgs.Term();
for (; cur < end; ++cur)
FREE(*cur);
}
//===========================================================================
static void AddValueString (
IniValue * value,
const wchar src[]
) {
unsigned chars = StrLen(src) + 1;
wchar * dst = ALLOCA(wchar, chars);
StrTokenize(&src, dst, chars, L" \t\r\n\"");
value->fArgs.Add(StrDup(dst));
}
/****************************************************************************
*
* IniKey
*
***/
//===========================================================================
IniKey::IniKey (IniSection * section, const wchar name[])
: fSection(section)
{
StrCopy(fName, name, (unsigned) -1);
fSection->fKeys.Add(this);
}
//===========================================================================
IniKey::~IniKey () {
IniValue ** cur = fValues.Ptr();
IniValue ** end = fValues.Term();
for (; cur < end; ++cur)
DEL(*cur);
}
//===========================================================================
inline unsigned IniKey::GetHash () const {
return StrHashI(fName);
}
//===========================================================================
inline bool IniKey::operator== (const CHashKeyStrPtrI & rhs) const {
return !StrCmpI(fName, rhs.GetString());
}
//===========================================================================
static IniValue * AddKeyValue (
IniSection * section,
wchar * string,
unsigned lineNum
) {
string = TrimWhitespace(string);
// Find or create the key
IniKey * key = section->fKeys.Find(string);
if (!key) {
key = new(ALLOC(
sizeof(*key) - sizeof(key->fName) + StrBytes(string)
)) IniKey(section, string);
}
// Add a new value holder for the key
return NEW(IniValue)(key, lineNum);
}
/****************************************************************************
*
* IniSection
*
***/
//===========================================================================
IniSection::IniSection (const wchar name[]) {
StrCopy(fName, name, (unsigned) -1);
}
//===========================================================================
IniSection::~IniSection () {
fKeys.Clear();
}
//===========================================================================
inline unsigned IniSection::GetHash () const {
return StrHashI(fName);
}
//===========================================================================
inline bool IniSection::operator== (const CHashKeyStrPtrI & rhs) const {
return !StrCmpI(fName, rhs.GetString());
}
//===========================================================================
static IniSection * AddSection (
Ini * ini,
wchar * string
) {
// Find or create the section
IniSection * section = ini->fSections.Find(string);
if (!section) {
section = new(ALLOC(
sizeof(*section) - sizeof(section->fName) + StrBytes(string)
)) IniSection(string);
ini->fSections.Add(section);
}
return section;
}
/****************************************************************************
*
* Ini
*
***/
//===========================================================================
Ini::~Ini () {
fSections.Clear();
}
/****************************************************************************
*
* ParseBuffer
*
***/
//===========================================================================
static void ParseBuffer (
Ini * ini,
const wchar buffer[]
) {
const wchar SECTION_OPEN_CHAR = '[';
const wchar SECTION_CLOSE_CHAR = ']';
const wchar EQUIVALENCE_CHAR = '=';
const wchar VALUE_SEPARATOR = ',';
const wchar COMMENT_CHAR = ';';
const wchar QUOTE_CHAR = '\"';
const wchar NEWLINE = '\n';
enum {
STATE_BEGIN,
STATE_NEWLINE,
STATE_SECTION,
STATE_STRIP_TRAILING,
STATE_KEY,
STATE_VALUE,
} state = STATE_BEGIN;
IniSection * section = nil;
IniValue * value = nil;
const wchar * start = nil;
bool valInQuotes = false;
wchar dst[512];
dst[0] = 0;
for (unsigned lineNum = 1;; ++buffer) {
// Get next character
unsigned chr = *buffer;
if (!chr)
break;
if (chr == '\r')
continue;
if (chr == '\n')
++lineNum;
if (chr == '\t')
chr = ' ';
switch (state) {
case STATE_BEGIN:
ASSERT(chr == UNICODE_BOM);
state = STATE_NEWLINE;
break;
case STATE_NEWLINE:
if (chr == NEWLINE)
break;
if (chr == ' ')
break;
if (chr == SECTION_OPEN_CHAR) {
start = buffer + 1;
state = STATE_SECTION;
}
else if (chr == COMMENT_CHAR) {
state = STATE_STRIP_TRAILING;
}
else {
start = buffer;
state = STATE_KEY;
}
break;
case STATE_SECTION:
if (chr == NEWLINE) {
state = STATE_NEWLINE;
break;
}
if (chr == SECTION_CLOSE_CHAR) {
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst)));
section = AddSection(ini, dst);
state = STATE_STRIP_TRAILING;
}
break;
case STATE_STRIP_TRAILING:
if (chr == NEWLINE)
state = STATE_NEWLINE;
break;
case STATE_KEY:
if (chr == NEWLINE) {
state = STATE_NEWLINE;
break;
}
if (chr != EQUIVALENCE_CHAR)
break;
if (!section) {
state = STATE_STRIP_TRAILING;
break;
}
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst)));
value = AddKeyValue(section, dst, lineNum);
start = buffer + 1;
state = STATE_VALUE;
valInQuotes = false;
break;
case STATE_VALUE:
if (chr == QUOTE_CHAR)
valInQuotes = !valInQuotes;
if ((valInQuotes || chr != VALUE_SEPARATOR) && (chr != NEWLINE))
break;
if (!value) {
state = chr == NEWLINE ? STATE_NEWLINE : STATE_STRIP_TRAILING;
break;
}
if (valInQuotes) {
state = chr == NEWLINE ? STATE_NEWLINE : STATE_STRIP_TRAILING;
break;
}
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst)));
AddValueString(value, dst);
if (chr == VALUE_SEPARATOR)
start = buffer + 1;
else
state = STATE_NEWLINE;
break;
}
}
// cleanup current value
if (state == STATE_VALUE) {
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst)));
AddValueString(value, dst);
}
}
/****************************************************************************
*
* ParseFile
*
***/
//===========================================================================
static void IniFileNotifyProc (
AsyncFile ,
EAsyncNotifyFile ,
AsyncNotifyFile * ,
void **
) {
}
//===========================================================================
static bool ParseFile (
Ini * ini,
const wchar fileName[]
) {
// Open file
qword fileSize;
qword fileLastWriteTime;
EFileError error;
AsyncFile file = AsyncFileOpen(
fileName,
IniFileNotifyProc,
&error,
kAsyncFileReadAccess,
kAsyncFileModeOpenExisting,
kAsyncFileShareRead,
nil,
&fileSize,
&fileLastWriteTime
);
if (!file)
return false;
bool result;
if (fileSize > 256 * 1024) {
result = false;
}
else if (!fileSize) {
result = true;
}
else {
// Read entire file into memory and NULL terminate wchar
byte * buffer = (byte *) ALLOC((unsigned) fileSize + sizeof(wchar));
AsyncFileRead(file, 0, buffer, (unsigned) fileSize, kAsyncFileRwSync, nil);
* (wchar *) &buffer[fileSize] = 0;
// Convert to unicode if necessary
if (* (wchar *) buffer != UNICODE_BOM) {
byte * src = buffer;
// Allocate two extra spaces for UNICODE_BOM and terminator
unsigned newBufferSize = ((unsigned) fileSize + 2) * sizeof(wchar);
// Allocate new buffer
wchar * dst = (wchar *) ALLOC(newBufferSize);
// If it's UTF-8 file,convert to Unicode
if (StrCmpI((char *)buffer, UTF8_BOM, StrLen(UTF8_BOM)) == 0) {
// StrUtf8ToUnicode will convert UTF8_BOM to UNICODE_BOM
StrUtf8ToUnicode(dst, (char *)src, newBufferSize);
}
else {
// do simple conversion to Unicode
dst[0] = UNICODE_BOM;
for (unsigned index = 0;; ++index) {
if (0 == (dst[index + 1] = src[index]))
break;
}
}
FREE(src);
buffer = (byte *) dst;
}
ParseBuffer(ini, (const wchar *) buffer);
FREE(buffer);
result = true;
}
AsyncFileClose(file, kAsyncFileDontTruncate);
return result;
}
/*****************************************************************************
*
* Exports
*
***/
//===========================================================================
Ini * IniOpen (
const wchar fileName[]
) {
Ini * ini = NEW(Ini);
if (!ParseFile(ini, fileName)) {
IniClose(ini);
return nil;
}
return ini;
}
//===========================================================================
void IniClose (Ini * ini) {
DEL(ini);
}
//===========================================================================
const IniSection * IniGetFirstSection (
const Ini * ini,
wchar * name,
unsigned chars
) {
if (chars)
*name = 0;
if (!ini)
return nil;
const IniSection * section = ini->fSections.Head();
if (section)
StrCopy(name, section->fName, chars);
return section;
}
//===========================================================================
const IniSection * IniGetNextSection (
const IniSection * section,
wchar * name,
unsigned chars
) {
if (chars)
*name = 0;
if (!section)
return nil;
section = section->fLink.Next();
if (section)
StrCopy(name, section->fName, chars);
return section;
}
//===========================================================================
const IniSection * IniGetSection (
const Ini * ini,
const wchar name[]
) {
if (!ini)
return nil;
return ini->fSections.Find(
CHashKeyStrPtrI(name)
);
}
//===========================================================================
const IniKey * IniGetFirstKey (
const IniSection * section,
wchar * name,
unsigned chars
) {
if (chars)
*name = 0;
if (!section)
return nil;
const IniKey * key = section->fKeys.Head();
if (key)
StrCopy(name, key->fName, chars);
return key;
}
//============================================================================
const IniKey * IniGetFirstKey (
const Ini * ini,
const wchar sectionName[],
wchar * name,
unsigned chars
) {
if (const IniSection * section = IniGetSection(ini, sectionName))
return IniGetFirstKey(section, name, chars);
return nil;
}
//===========================================================================
const IniKey * IniGetNextKey (
const IniKey * key,
wchar * name,
unsigned chars
) {
if (chars)
*name = 0;
if (!key)
return nil;
key = key->fLink.Next();
if (key)
StrCopy(name, key->fName, chars);
return key;
}
//===========================================================================
const IniKey * IniGetKey (
const IniSection * section,
const wchar name[]
) {
if (!section)
return nil;
return section->fKeys.Find(
CHashKeyStrPtrI(name)
);
}
//===========================================================================
const IniValue * IniGetFirstValue (
const IniKey * key,
unsigned * lineNum
) {
if (lineNum)
*lineNum = 0;
const IniValue * value = nil;
for (;;) {
if (!key)
break;
value = key->fValues[0];
if (lineNum)
*lineNum = value->fLineNum;
break;
}
return value;
}
//===========================================================================
const IniValue * IniGetFirstValue (
const IniSection * section,
const wchar keyName[],
unsigned * lineNum
) {
const IniValue * value = nil;
if (lineNum)
*lineNum = 0;
for (;;) {
if (!section)
break;
const IniKey * key = section->fKeys.Find(
CHashKeyStrPtrI(keyName)
);
value = IniGetFirstValue(key, lineNum);
break;
}
return value;
}
//===========================================================================
const IniValue * IniGetFirstValue (
const Ini * ini,
const wchar sectionName[],
const wchar keyName[],
unsigned * lineNum
) {
const IniValue * value = nil;
if (lineNum)
*lineNum = 0;
for (;;) {
if (!ini)
break;
const IniSection * section = ini->fSections.Find(
CHashKeyStrPtrI(sectionName)
);
value = IniGetFirstValue(section, keyName, lineNum);
break;
}
return value;
}
//===========================================================================
const IniValue * IniGetNextValue (
const IniValue * value,
unsigned * lineNum
) {
if (lineNum)
*lineNum = 0;
const IniKey * key = value->fKey;
if (value->fIndex + 1 < key->fValues.Count()) {
value = key->fValues[value->fIndex + 1];
if (lineNum)
*lineNum = value->fLineNum;
}
else {
value = nil;
}
return value;
}
//===========================================================================
bool IniGetUnsigned (
const IniValue * value,
unsigned * result,
unsigned index,
unsigned defaultValue
) {
ASSERT(result);
for (;;) {
if (!value)
break;
wchar str[32];
if (!IniGetString(value, str, arrsize(str), index, nil))
break;
if (!str[0])
break;
*result = StrToUnsigned(str, nil, 0);
return true;
}
*result = defaultValue;
return false;
}
//===========================================================================
bool IniGetString (
const IniValue * value,
wchar * result,
unsigned resultChars,
unsigned index,
const wchar defaultValue[]
) {
ASSERT(result);
bool found = value && index < value->fArgs.Count();
if (found)
StrCopy(result, value->fArgs[index], resultChars);
else if (defaultValue)
StrCopy(result, defaultValue, resultChars);
return found;
}
//===========================================================================
bool IniGetUuid (
const IniValue * value,
Uuid * uuid,
unsigned index,
const Uuid & defaultValue
) {
wchar str[128];
if (IniGetString(value, str, arrsize(str), index, nil))
return GuidFromString(str, uuid);
else
*uuid = defaultValue;
return false;
}
//===========================================================================
unsigned IniGetBoundedValue (
const IniValue * value,
const wchar section[],
const wchar key[],
unsigned index,
unsigned minVal,
unsigned maxVal,
unsigned defVal
) {
ref(key);
ref(section);
if (!value)
return defVal;
unsigned result;
IniGetUnsigned(value, &result, index, defVal);
if ((result < minVal) || (result > maxVal)) {
result = defVal;
}
return result;
}
//===========================================================================
unsigned IniGetBoundedValue (
const Ini * ini,
const wchar section[],
const wchar key[],
unsigned index,
unsigned minVal,
unsigned maxVal,
unsigned defVal
) {
unsigned lineNum;
const IniValue * value = IniGetFirstValue(
ini,
section,
key,
&lineNum
);
return IniGetBoundedValue(
value,
section,
key,
index,
minVal,
maxVal,
defVal
);
}