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.
419 lines
12 KiB
419 lines
12 KiB
//////////////////////////////////////////////////////////////////// |
|
// SML.cpp |
|
// |
|
// Copyright 2007 cDc@seacave |
|
// Distributed under the Boost Software License, Version 1.0 |
|
// (See http://www.boost.org/LICENSE_1_0.txt) |
|
|
|
#include "Common.h" |
|
#include "SML.h" |
|
|
|
using namespace SEACAVE; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
#define SML_AUTOVALUES_OFF 0 |
|
#define SML_AUTOVALUES_ON 1 |
|
#ifndef SML_AUTOVALUES |
|
#define SML_AUTOVALUES SML_AUTOVALUES_ON |
|
#endif |
|
|
|
#define SML_SECTIONOPENBRACKET _T("{") |
|
#define SML_SECTIONCLOSEBRACKET _T("}") |
|
#define SML_NAMEOPENBRACKET _T("[") |
|
#define SML_NAMECLOSEBRACKET _T("]") |
|
#define SML_NAMETOKEN _T("=") |
|
#define SML_VALUETOKEN _T("\n") |
|
#define SML_INDENT _T("\t") |
|
|
|
#define SML_TOKEN_SECTIONOPENBRACKET SML_SECTIONOPENBRACKET[0] |
|
#define SML_TOKEN_SECTIONCLOSEBRACKET SML_SECTIONCLOSEBRACKET[0] |
|
#define SML_TOKEN_NAMEOPENBRACKET SML_NAMEOPENBRACKET[0] |
|
#define SML_TOKEN_NAMECLOSEBRACKET SML_NAMECLOSEBRACKET[0] |
|
#define SML_TOKEN_NAMETOKEN SML_NAMETOKEN[0] |
|
#define SML_TOKEN_VALUETOKEN SML_VALUETOKEN[0] |
|
#define SML_TOKEN_INDENT SML_INDENT[0] |
|
#define SML_TOKEN_IGNORECHARS _T("\n\r\t ") |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
/*-----------------------------------------------------------* |
|
* SML class implementation * |
|
*-----------------------------------------------------------*/ |
|
|
|
/** |
|
* Constructor |
|
*/ |
|
SML::SML(const String& name) |
|
: |
|
m_strName(name), |
|
m_fncInitItem(NULL), |
|
m_fncSaveItem(NULL), |
|
m_fncReleaseItem(NULL), |
|
m_fncItemData(NULL) |
|
{ |
|
} |
|
|
|
/** |
|
* Destructor |
|
*/ |
|
SML::~SML() |
|
{ |
|
Release(); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
/** |
|
* Released all used memory for this nod and its children |
|
*/ |
|
void SML::Release() |
|
{ |
|
m_arrChildren.ReleaseDelete(); |
|
if (m_fncReleaseItem != NULL) { |
|
for (SMLITEMMAP::iterator it=GetBegin(); it!=GetEnd(); ++it) |
|
m_fncReleaseItem(it->second, m_fncItemData); |
|
} |
|
SMLITEMMAP::Release(); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Load the values from a file; |
|
* all children are generated if they do not exist |
|
*/ |
|
bool SML::Load(const String& fileName) |
|
{ |
|
File oStream(fileName, File::READ, File::OPEN); |
|
if (!oStream.isOpen()) |
|
return false; |
|
return Load(oStream); |
|
} |
|
bool SML::Load(ISTREAM& oStream) |
|
{ |
|
// create the section token filter and mem-file |
|
MemFile memFile; |
|
TokenIStream filter(&oStream); |
|
filter.setTrimTokens(SML_TOKEN_IGNORECHARS); |
|
// and parse section |
|
return ParseSection(filter, memFile); |
|
} |
|
bool SML::ParseSection(TokenIStream& filter, MemFile& memFile) |
|
{ |
|
while (true) { |
|
// find first section start (NameOpenBracket) |
|
filter.setToken(SML_TOKEN_NAMEOPENBRACKET); |
|
filter.readLine(memFile); |
|
|
|
// parse values before the new section or end of section |
|
const size_f_t posMemFile = memFile.getPos(); |
|
MemFile valuesMemFile; |
|
TokenIStream sectionFilter(&memFile, SML_TOKEN_SECTIONCLOSEBRACKET); |
|
const size_f_t lenValues = sectionFilter.readLine(valuesMemFile); |
|
if (ParseValues(valuesMemFile) == false) |
|
return false; // Parse Error: invalid values |
|
ASSERT(valuesMemFile.getSize() == 0); |
|
|
|
// if end of section found, return |
|
if (!sectionFilter.isEOS()) { |
|
// if a name open bracket was found before the EOS, |
|
// then restore it for the parent next ParseSection() |
|
memFile.setPos(posMemFile+(lenValues+1)*sizeof(TCHAR)); |
|
if (!filter.isEOS()) |
|
filter.restoreToken(); |
|
break; |
|
} |
|
else { |
|
ASSERT(memFile.getSize()-posMemFile == lenValues*(size_f_t)sizeof(TCHAR)); |
|
ASSERT(posMemFile == 0 || memFile.getSize()-posMemFile == memFile.getSizeLeft() || memFile.getSizeLeft() == 0); |
|
memFile.setSize(0); |
|
} |
|
|
|
// if no more data, return |
|
if (filter.isEOS()) |
|
break; |
|
|
|
// parse child section name |
|
filter.setToken(SML_TOKEN_NAMECLOSEBRACKET); |
|
const size_t lenName = filter.readLine(memFile); |
|
ASSERT(!filter.isEOS()); |
|
if (lenName == 0) |
|
return false; // Parse Error: invalid section name |
|
const String strChildName((LPCTSTR)memFile.getData(), lenName); |
|
memFile.growSize(-((size_f_t)(lenName*sizeof(TCHAR)))); |
|
ASSERT(memFile.getSize() == 0); |
|
// create the child with the given name |
|
const IDX idxChild = CreateChildUnique(strChildName); |
|
LPSML const pChild = m_arrChildren[idxChild]; |
|
pChild->SetFncItem(m_fncInitItem, m_fncSaveItem, m_fncReleaseItem, m_fncItemData); |
|
|
|
// parse child section |
|
filter.setToken(SML_TOKEN_SECTIONOPENBRACKET); |
|
filter.readLine(memFile); |
|
filter.trimBackLine(memFile); |
|
ASSERT(memFile.getSize() == 0); |
|
if (pChild->ParseSection(filter, memFile) == false) |
|
return false; |
|
} |
|
return true; |
|
} |
|
bool SML::ParseValues(MemFile& valuesMemFile) |
|
{ |
|
if (valuesMemFile.getSize() == 0) |
|
return true; |
|
// loop through all name/value pairs |
|
MemFile memLine; |
|
TokenIStream filterLine(&valuesMemFile); |
|
filterLine.setTrimTokens(SML_TOKEN_IGNORECHARS); |
|
filterLine.setToken(SML_TOKEN_VALUETOKEN); |
|
MemFile memValue; |
|
TokenIStream filterValue(&memLine); |
|
filterValue.setTrimTokens(SML_TOKEN_IGNORECHARS); |
|
filterValue.setToken(SML_TOKEN_NAMETOKEN); |
|
do { |
|
// parse value name and value |
|
filterLine.readLine(memLine); |
|
filterLine.trimBackLine(memLine); |
|
filterLine.trimFrontLine(memLine); |
|
if (memLine.isEOF()) |
|
continue; // empty line, return |
|
// parse value name |
|
const size_t lenNameValueReal = (size_t)memLine.getSizeLeft(); |
|
const size_t lenName = filterValue.readLine(memValue); |
|
#if SML_AUTOVALUES == SML_AUTOVALUES_ON |
|
String szName; |
|
if (filterValue.trimBackLine(memValue) == lenName || lenNameValueReal == lenName) { |
|
// no name found, auto generate the name |
|
szName = _T("Item") + String::ToString(size()); |
|
} else { |
|
// read the name |
|
szName = (LPCTSTR)memValue.getData(); |
|
memValue.setSize(0); |
|
} |
|
#else |
|
filterValue.trimBackLine(memValue); |
|
String szName = (LPCTSTR)memValue.getData(); |
|
ASSERT(!filterValue.isEOS() && !szName.IsEmpty()); |
|
if (filterValue.isEOS() || szName.IsEmpty()) { |
|
memValue.setSize(0); |
|
memLine.setSize(0); |
|
filterValue.setPos(0); |
|
continue; // Parse Error: invalid syntax ('=' not found) |
|
} |
|
ASSERT(szName.size() == memValue.getSizeLeft()); |
|
memValue.setSize(0); |
|
#endif |
|
SMLVALUE& val = operator[](szName); |
|
// parse value |
|
filterValue.read(memValue); |
|
LPCTSTR szValue = filterValue.trimFrontLine(memValue); |
|
val.val = szValue; |
|
ASSERT((size_f_t)_tcslen(szValue) == memValue.getSizeLeft()); |
|
memValue.setSize(0); |
|
memLine.setSize(0); |
|
filterValue.setPos(0); |
|
} while (!filterLine.isEOS()); |
|
// all file processed, safe to reset it to 0 |
|
valuesMemFile.setSize(0); |
|
return true; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Write to a file the values of this node and its children. |
|
* Set to false the second parameter in order not to save the empty children. |
|
*/ |
|
bool SML::Save(const String& fileName, SAVEFLAG flags) const |
|
{ |
|
File oStream(fileName, File::WRITE, File::CREATE | File::TRUNCATE); |
|
if (!oStream.isOpen()) |
|
return false; |
|
return Save(oStream, flags); |
|
} |
|
bool SML::Save(OSTREAM& oStream, SAVEFLAG flags) const |
|
{ |
|
// save all values and all children |
|
return SaveIntern(oStream, _T(""), flags); |
|
} |
|
bool SML::SaveBracketize(OSTREAM& oStream, const String& strIndent, SAVEFLAG flags) const |
|
{ |
|
// save its name |
|
oStream.print(_T("%s" SML_NAMEOPENBRACKET "%s" SML_NAMECLOSEBRACKET "\n"), strIndent.c_str(), m_strName.c_str()); |
|
oStream.print(_T("%s" SML_SECTIONOPENBRACKET "\n"), strIndent.c_str()); |
|
// save all values and all children |
|
SaveIntern(oStream, strIndent+SML_TOKEN_INDENT, flags); |
|
// close bracket |
|
oStream.print(_T("%s" SML_SECTIONCLOSEBRACKET "\n"), strIndent.c_str()); |
|
return true; |
|
} |
|
bool SML::SaveIntern(OSTREAM& oStream, const String& strIndent, SAVEFLAG flags) const |
|
{ |
|
const Flags& flgs = (const Flags&)flags; |
|
if (flgs.isAnySet(SORT | SORTINV)) { |
|
// sort values alphabetically and save them |
|
StringArr lines(0, GetSize()); |
|
for (SMLITEMMAP::const_iterator item=GetBegin(); item!=GetEnd(); ++item) |
|
if (!m_fncSaveItem || m_fncSaveItem((*item).second, m_fncItemData)) |
|
lines.InsertSort(String::FormatString(_T("%s%s " SML_NAMETOKEN " %s" SML_VALUETOKEN), strIndent.c_str(), (*item).first.c_str(), (*item).second.val.c_str()), |
|
(flgs.isAnySet(SORT) ? String::CompareAlphabetically : String::CompareAlphabeticallyInv)); |
|
FOREACH(l, lines) |
|
oStream.print(lines[l]); |
|
} else { |
|
// save all values directly |
|
for (SMLITEMMAP::const_iterator item=GetBegin(); item!=GetEnd(); ++item) |
|
if (!m_fncSaveItem || m_fncSaveItem((*item).second, m_fncItemData)) |
|
oStream.print(_T("%s%s " SML_NAMETOKEN " %s" SML_VALUETOKEN), strIndent.c_str(), (*item).first.c_str(), (*item).second.val.c_str()); |
|
} |
|
// save now all children |
|
bool bFirst = IsEmpty(); |
|
FOREACH(i, m_arrChildren) { |
|
const SML* pSML = m_arrChildren[i]; |
|
// skip empty children |
|
if (!flgs.isSet(SAVEEMPTY) && pSML->IsEmpty() && pSML->m_arrChildren.IsEmpty()) |
|
continue; |
|
// insert a new line to separate from the above section (only for visual aspect) |
|
if (bFirst) |
|
bFirst = false; |
|
else |
|
oStream.print(_T("\n")); |
|
// save child |
|
pSML->SaveBracketize(oStream, strIndent, flags); |
|
} |
|
return true; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Create and insert a new child |
|
*/ |
|
IDX SML::CreateChild(const String& strChildName) |
|
{ |
|
return InsertChild(new SML(strChildName)); |
|
} |
|
IDX SML::CreateChildUnique(const String& strChildName) |
|
{ |
|
return InsertChildUnique(new SML(strChildName)); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Insert a new child |
|
*/ |
|
IDX SML::InsertChild(const LPSML pSML) |
|
{ |
|
return m_arrChildren.InsertSort(pSML, SML::Compare); |
|
} |
|
IDX SML::InsertChildUnique(const LPSML pSML) |
|
{ |
|
const std::pair<IDX,bool> res(m_arrChildren.InsertSortUnique(pSML, SML::Compare)); |
|
if (res.second) |
|
delete pSML; |
|
return res.first; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Remove an existing child |
|
*/ |
|
void SML::RemoveChild(IDX idx) |
|
{ |
|
m_arrChildren.RemoveAtMove(idx); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
/** |
|
* Remove and destroy an existing child |
|
*/ |
|
void SML::DestroyChild(IDX idx) |
|
{ |
|
delete m_arrChildren[idx]; |
|
RemoveChild(idx); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Retrieve an child by its name; NO_INDEX if unexistent |
|
*/ |
|
IDX SML::GetChild(const String& name) const |
|
{ |
|
return m_arrChildren.FindFirst(&name, SML::CompareName); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Retrieve an item; NULL if unexistent |
|
*/ |
|
const SMLVALUE* SML::GetValue(const String& key) const |
|
{ |
|
const_iterator it = Find(key); |
|
if (it == GetEnd()) |
|
return NULL; |
|
return &(it->second); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Insert or retrieve an item; initialize it if necessary |
|
*/ |
|
SMLVALUE& SML::GetValue(const String& key) |
|
{ |
|
bool bExisted; |
|
SMLVALUE& val = (*Insert(key, bExisted)).second; |
|
if (!bExisted && m_fncInitItem != NULL) |
|
m_fncInitItem(key, val, m_fncItemData); |
|
return val; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Retrieve an unnamed item by index |
|
*/ |
|
const SMLVALUE& SML::GetValue(IDX idx) const |
|
{ |
|
ASSERT(idx < this->size()); |
|
const String szName(_T("Item") + String::ToString(idx)); |
|
return this->at(szName); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Reset items' init and release functions |
|
*/ |
|
void SML::SetFncItem(TFncInitItem fncInit, TFncSaveItem fncSave, TFncReleaseItem fncRelease, void* data) |
|
{ |
|
m_fncInitItem = fncInit; |
|
m_fncSaveItem = fncSave; |
|
m_fncReleaseItem = fncRelease; |
|
m_fncItemData = data; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/** |
|
* Compare two SML nodes by name |
|
*/ |
|
int STCALL SML::Compare(const void* l, const void* r) |
|
{ |
|
return _tcscmp((*((const SML**)l))->GetName(), (*((const SML**)r))->GetName()); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
/** |
|
* Compare a name and a SML node name |
|
*/ |
|
int STCALL SML::CompareName(const void* l, const void* r) |
|
{ |
|
return _tcscmp((*((const SML**)l))->GetName(), ((const String*)r)->c_str()); |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|