/*==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 . 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 ) { 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 ); }