2010-05-06 00:08:41 +00:00
|
|
|
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
/*
|
|
|
|
NOTES:
|
|
|
|
|
|
|
|
Strings are limited to 256 characters - this is OK for the basic uses of the system - may require review later...
|
|
|
|
- Note that for many apps use of a string vector in place of a string will solve the 255 limit.
|
|
|
|
|
|
|
|
=> deprecated: the test checking the string length has been disabled, which allows string to exceed a 256 char length
|
|
|
|
*/
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// incudes
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "stdpch.h"
|
|
|
|
#include "nel/misc/file.h"
|
|
|
|
#include "nel/misc/path.h"
|
|
|
|
#include "nel/misc/sstring.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "persistent_data.h"
|
|
|
|
#include "persistent_data_tree.h"
|
|
|
|
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
|
|
#include <io.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// namespaces
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
using namespace NLMISC;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
CPdrTokenRegistry *CPdrTokenRegistry::_Instance = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// globals
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CPersistentDataRecord::CArg CPersistentDataRecord::TempArg;
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// basics...
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ctor
|
|
|
|
CPersistentDataRecord::CPersistentDataRecord(const std::string& tokenFamily)
|
|
|
|
{
|
|
|
|
// setup the token family
|
|
|
|
_TokenFamily=tokenFamily;
|
|
|
|
|
|
|
|
// clear write data/ properties
|
|
|
|
clear();
|
|
|
|
|
|
|
|
// clear read data/ properties
|
|
|
|
rewind();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// set of accessors for storing data in a CPersistentDataRecord
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CPersistentDataRecord::clear()
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordClear);
|
|
|
|
|
|
|
|
// clear persistent data buffers
|
|
|
|
_ArgTable.clear();
|
|
|
|
_TokenTable.clear();
|
|
|
|
|
|
|
|
// setup the string table from the token faimly's string table
|
|
|
|
_StringTable= CPdrTokenRegistry::getInstance()->getStringTable(_TokenFamily);
|
|
|
|
|
|
|
|
// clear working variables and buffers
|
|
|
|
_WritingStructStack.clear();
|
|
|
|
_LookupTbls.clear();
|
|
|
|
|
|
|
|
// make sure read pointers don't point past end of data
|
|
|
|
rewind();
|
|
|
|
|
|
|
|
// slot '0' in string table is reserved
|
|
|
|
addString("BAD_STRING");
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 CPersistentDataRecord::addString(const string& name)
|
|
|
|
{
|
|
|
|
// H_AUTO(CPersistentDataRecordAddString);
|
|
|
|
|
|
|
|
// store the length of the input string for speed of access (just to help the optimiser do its job)
|
2010-05-13 20:45:24 +00:00
|
|
|
uint32 len= (uint32)name.size();
|
2010-05-06 00:08:41 +00:00
|
|
|
|
|
|
|
//Disabled to allow >=256 char strings.
|
|
|
|
//DROP_IF(len>=256,"Attempt to add a string of > 256 characters to the string table",return 0);
|
|
|
|
|
|
|
|
// depending on the string length choose a well suited algorithm for performing a fast search of the string table
|
|
|
|
switch(len)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
if (_StringTable[i].empty())
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
{
|
|
|
|
char c0= name[0]; // first and only char of name
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && s[0]==c0)
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
uint16 c01= *(uint16*)&name[0]; // first and only 2 chars of name
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint16*)&s[0]==c01) )
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
{
|
|
|
|
uint16 c01= *(uint16*)&name[0]; // first 2 chars of name
|
|
|
|
char c2= name[2]; // third and final char of name
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint16*)&s[0]==c01) && s[2]==c2)
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
{
|
|
|
|
uint32 c0123= *(uint32*)&name[0]; // first and only 4 chars of name
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint32*)&s[0]==c0123) )
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
case 6:
|
|
|
|
case 7:
|
|
|
|
case 8:
|
|
|
|
{
|
|
|
|
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
|
|
|
|
uint32 endOffs=len-4; // offset to last 4 characters of the name
|
|
|
|
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with first 4 chars)
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast))
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 9:
|
|
|
|
case 10:
|
|
|
|
case 11:
|
|
|
|
case 12:
|
|
|
|
{
|
|
|
|
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
|
|
|
|
uint32 cMid= *(uint32*)&name[4]; // middle 4 chars of name (touch first 4 chars)
|
|
|
|
uint32 endOffs=len-4; // offset to last 4 characters of the name
|
|
|
|
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with middle 4 chars)
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast) && (*(uint32*)&s[4]==cMid))
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 13:
|
|
|
|
case 14:
|
|
|
|
case 15:
|
|
|
|
case 16:
|
|
|
|
{
|
|
|
|
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
|
|
|
|
uint32 cSecond= *(uint32*)&name[4]; // second 4 chars of name (touch first 4 chars)
|
|
|
|
uint32 cThird= *(uint32*)&name[8]; // third 4 chars of name (touch second 4 chars)
|
|
|
|
uint32 endOffs=len-4; // offset to last 4 characters of the name
|
|
|
|
uint32 cLast= *(uint32*)&name[endOffs]; // last 4 chars of name (touch or overlap with third 4 chars)
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)_StringTable.size();i--;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
if (s.size()==len && (*(uint32*)&s[0]==cFirst) && (*(uint32*)&s[endOffs]==cLast)
|
|
|
|
&& (*(uint32*)&s[4]==cSecond) && (*(uint32*)&s[8]==cThird) )
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
uint32 cFirst= *(uint32*)&name[0]; // first 4 chars of name
|
|
|
|
uint32 endOffs=len-4; // offset to last 4 characters of the name
|
|
|
|
uint32* nameEnd= (uint32*)&name[endOffs]; // pointer to the last 4 chars of name
|
|
|
|
uint32 cLast= *nameEnd; // last 4 chars of name (touch or overlap with middle 4 chars)
|
|
|
|
|
|
|
|
// run through the existing strings looking for a match
|
|
|
|
for (uint32 i=0;i<_StringTable.size();++i)
|
|
|
|
{
|
|
|
|
// store a ref to the next string in the string table just to help optimiser do its job
|
|
|
|
const string &s= _StringTable[i];
|
|
|
|
|
|
|
|
// if string lengths or first r last dwords don't match then abort compare
|
|
|
|
if (s.size()!=len || (*(uint32*)&s[0]!=cFirst) || (*(uint32*)&s[endOffs]!=cLast) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
uint32* sIt= (uint32*)&s[4]; // iterator for 's' - init to point to second dword of string
|
|
|
|
uint32* nameIt= (uint32*)&name[4]; // iterator for 'name'
|
|
|
|
|
|
|
|
// run through the strings comparing 4 bytes at a time
|
|
|
|
while (*sIt==*nameIt)
|
|
|
|
{
|
|
|
|
++sIt;
|
|
|
|
++nameIt;
|
|
|
|
if (nameIt>=nameEnd)
|
|
|
|
{
|
|
|
|
return (uint16)i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// no match found so add this string to the string table and return its index
|
|
|
|
{
|
|
|
|
// H_AUTO(CPersistentDataRecordAddString_NoMatchFound);
|
|
|
|
|
2010-05-13 20:45:24 +00:00
|
|
|
uint16 result= (uint16)_StringTable.size();
|
2010-05-06 00:08:41 +00:00
|
|
|
_StringTable.push_back(name);
|
|
|
|
BOMB_IF(result==(uint16)~0u,"No more room in string table!!!",_StringTable.pop_back());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const NLMISC::CSString& CPersistentDataRecord::lookupString(uint32 idx) const
|
|
|
|
{
|
|
|
|
// note that the string table size is never less than 1 as entry 0 is pre-set with the 'invalid string' value
|
|
|
|
BOMB_IF(idx>=_StringTable.size(),"Attempting to access past end of string table",return lookupString(0));
|
|
|
|
return _StringTable[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
const NLMISC::CSString& CPersistentDataRecord::getTokenFamily() const
|
|
|
|
{
|
|
|
|
return _TokenFamily;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// set of accessors for retrieving data from a CPersistentDataRecord
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CPersistentDataRecord::rewind()
|
|
|
|
{
|
|
|
|
_ArgOffset=0;
|
|
|
|
_TokenOffset=0;
|
|
|
|
_ReadingStructStack.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CPersistentDataRecord::skipStruct()
|
|
|
|
{
|
|
|
|
DROP_IF(!isStartOfStruct(), "Attempting to skip a struct whereas next token is not a struct", return);
|
|
|
|
skipData();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CPersistentDataRecord::skipData()
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordSkipData);
|
|
|
|
|
|
|
|
// if this is a structure then skip the whole thing
|
|
|
|
std::vector<uint16> stack;
|
|
|
|
stack.reserve(16);
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (isStartOfStruct())
|
|
|
|
{
|
|
|
|
stack.push_back(peekNextToken());
|
|
|
|
popStructBegin(stack.back());
|
|
|
|
}
|
|
|
|
else if (isEndOfStruct())
|
|
|
|
{
|
|
|
|
popStructEnd(stack.back());
|
|
|
|
if (!stack.empty())
|
|
|
|
stack.pop_back();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
popNextArg(peekNextToken());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!stack.empty() && !isEndOfData());
|
|
|
|
}
|
|
|
|
|
|
|
|
CPDRLookupTbl* CPersistentDataRecord::getLookupTbl(uint32 id) const
|
|
|
|
{
|
|
|
|
return (id>=_LookupTbls.size())? NULL: _LookupTbls[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
void CPersistentDataRecord::setLookupTbl(uint32 id, CPDRLookupTbl* tbl)
|
|
|
|
{
|
|
|
|
// if the container is too small for the id then grow it
|
|
|
|
if (id>=_LookupTbls.size())
|
|
|
|
{
|
|
|
|
// make sure the lookup table id is valid
|
|
|
|
nlassert(id<CPDRLookupTbl::getNumLookupTableClasses());
|
|
|
|
// grow the container
|
|
|
|
_LookupTbls.resize(CPDRLookupTbl::getNumLookupTableClasses(),NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we don't already have a lookup table allocated for this slot
|
|
|
|
nlassert(_LookupTbls[id]==NULL);
|
|
|
|
|
|
|
|
// store away the new lookup table
|
|
|
|
_LookupTbls[id]= tbl;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::operator==(const CPersistentDataRecord& other) const
|
|
|
|
{
|
|
|
|
#define RESTORE_STATE_VARS {\
|
|
|
|
_ArgOffset=oldArgOffset; _TokenOffset=oldTokenOffset; _ReadingStructStack=oldRSS;\
|
|
|
|
other._ArgOffset=otherOldArgOffset; other._TokenOffset=otherOldTokenOffset; other._ReadingStructStack=otherOldRSS;\
|
|
|
|
}
|
|
|
|
#define RETURN_FALSE { RESTORE_STATE_VARS return false; }
|
|
|
|
#define RETURN_TRUE { RESTORE_STATE_VARS return true; }
|
|
|
|
|
|
|
|
// record the old values of the state variables (to be restored on exit)
|
|
|
|
uint32 oldArgOffset(_ArgOffset);
|
|
|
|
uint32 oldTokenOffset(_TokenOffset);
|
|
|
|
TReadingStructStack oldRSS(_ReadingStructStack);
|
|
|
|
|
|
|
|
uint32 otherOldArgOffset(other._ArgOffset);
|
|
|
|
uint32 otherOldTokenOffset(other._TokenOffset);
|
|
|
|
TReadingStructStack otherOldRSS(other._ReadingStructStack);
|
|
|
|
|
|
|
|
// reset state variables - this is equivalent to rewind()
|
|
|
|
_ArgOffset =0;
|
|
|
|
_TokenOffset =0;
|
|
|
|
_ReadingStructStack.clear();
|
|
|
|
|
|
|
|
other._ArgOffset =0;
|
|
|
|
other._TokenOffset =0;
|
|
|
|
other._ReadingStructStack.clear();
|
|
|
|
|
|
|
|
// iterate over the tokens in our PDRs comparing them as we go...
|
|
|
|
while ( !isEndOfData() && !other.isEndOfData() )
|
|
|
|
{
|
|
|
|
// make sure basic type info for next token matches on both sides
|
|
|
|
if ( isStartOfStruct() != other.isStartOfStruct() )
|
|
|
|
RETURN_FALSE
|
|
|
|
if ( isEndOfStruct() != other.isEndOfStruct() )
|
|
|
|
RETURN_FALSE
|
|
|
|
if ( isTokenWithNoData() != other.isTokenWithNoData() )
|
|
|
|
RETURN_FALSE
|
|
|
|
if ( peekNextTokenName() != other.peekNextTokenName() )
|
|
|
|
RETURN_FALSE
|
|
|
|
|
|
|
|
// deal with the token
|
|
|
|
if (isStartOfStruct())
|
|
|
|
{
|
|
|
|
// skip start of struct token
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->popStructBegin(peekNextToken());
|
|
|
|
const_cast<CPersistentDataRecord&>(other).popStructBegin(other.peekNextToken());
|
|
|
|
}
|
|
|
|
else if (isEndOfStruct())
|
|
|
|
{
|
|
|
|
// skip end of struct token
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->popStructEnd(peekNextToken());
|
|
|
|
const_cast<CPersistentDataRecord&>(other).popStructEnd(other.peekNextToken());
|
|
|
|
}
|
|
|
|
else if (isTokenWithNoData())
|
|
|
|
{
|
|
|
|
// skip token with no data
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->pop(peekNextToken());
|
|
|
|
const_cast<CPersistentDataRecord&>(other).pop(other.peekNextToken());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// get value for token and convert to text for comparison
|
|
|
|
// - this allows us to compare sint32(123) with uint8(123) etc correctly
|
|
|
|
CSString thisValue;
|
|
|
|
CSString otherValue;
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->pop(peekNextToken(),thisValue);
|
|
|
|
const_cast<CPersistentDataRecord&>(other).pop(other.peekNextToken(),otherValue);
|
|
|
|
if (thisValue!=otherValue)
|
|
|
|
RETURN_FALSE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we're at the end of both data buffers
|
|
|
|
if ( !isEndOfData() || !other.isEndOfData() )
|
|
|
|
RETURN_FALSE
|
|
|
|
|
|
|
|
// all of the failure tests passed so we can conclude that our structures match
|
|
|
|
RETURN_TRUE
|
|
|
|
|
|
|
|
#undef RETURN_TRUE
|
|
|
|
#undef RETURN_FALSE
|
|
|
|
#undef RESTORE_STATE_VARS
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// debug methods for retrieving info from pdr records
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
NLMISC::CSString CPersistentDataRecord::getInfo() const
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordGetInfo);
|
|
|
|
return NLMISC::toString("TotalSize=%u TokenCount=%u DataCount=%u StringCount=%u StringSize=%u ValueCount=%u",
|
|
|
|
totalDataSize(),_TokenTable.size(),_ArgTable.size(),_StringTable.size(),stringDataSize(),getNumValues());
|
|
|
|
}
|
|
|
|
|
|
|
|
NLMISC::CSString CPersistentDataRecord::getInfoAsCSV() const
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordGetInfoAsCSV);
|
|
|
|
return NLMISC::toString("%u,%u,%u,%u,%u,%u",
|
|
|
|
totalDataSize(),_TokenTable.size(),_ArgTable.size(),_StringTable.size(),stringDataSize(),getNumValues());
|
|
|
|
}
|
|
|
|
|
|
|
|
const NLMISC::CSString& CPersistentDataRecord::getCSVHeaderLine()
|
|
|
|
{
|
|
|
|
static NLMISC::CSString headerLine="TotalSize,TokenCount,DataCount,StringCount,StringSize,ValueCount";
|
|
|
|
return headerLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CPersistentDataRecord::getNumValues() const
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordGetNumValues);
|
|
|
|
|
|
|
|
// setup a counter variable to buil our result
|
|
|
|
uint32 result=0;
|
|
|
|
|
|
|
|
// record the old values of the state variables (to be restored on exit)
|
|
|
|
uint32 oldArgOffset(_ArgOffset);
|
|
|
|
uint32 oldTokenOffset(_TokenOffset);
|
|
|
|
TReadingStructStack oldRSS(_ReadingStructStack);
|
|
|
|
|
|
|
|
// reset state variables - this is equivalent to rewind()
|
|
|
|
_ArgOffset =0;
|
|
|
|
_TokenOffset =0;
|
|
|
|
_ReadingStructStack.clear();
|
|
|
|
|
|
|
|
// iterate over the tokens in our PDRs comparing them as we go...
|
|
|
|
while ( !isEndOfData() )
|
|
|
|
{
|
|
|
|
uint16 nextToken= peekNextToken();
|
|
|
|
CArg::TType nextTokenType= peekNextTokenType();
|
|
|
|
|
|
|
|
// deal with the token
|
|
|
|
if (nextTokenType==CArg::STRUCT_BEGIN) // isStartOfStruct()
|
|
|
|
{
|
|
|
|
// skip start of struct token
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->popStructBegin(nextToken);
|
|
|
|
}
|
|
|
|
else if (nextTokenType==CArg::STRUCT_END) // isEndOfStruct()
|
|
|
|
{
|
|
|
|
// skip end of struct token
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->popStructEnd(nextToken);
|
|
|
|
}
|
|
|
|
else if (nextTokenType==CArg::FLAG) // isTokenWithNoData()
|
|
|
|
{
|
|
|
|
// skip token with no data
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->pop(nextToken);
|
|
|
|
++result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// get the next value and discard it immediately
|
|
|
|
const_cast<CPersistentDataRecord*>(this)->popNextArg(nextToken);
|
|
|
|
++result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// restore the original values of teh state variables
|
|
|
|
_ArgOffset=oldArgOffset;
|
|
|
|
_TokenOffset=oldTokenOffset;
|
|
|
|
_ReadingStructStack=oldRSS;
|
|
|
|
|
|
|
|
// return the number of values that we found
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// set of accessors for storing a data record to various destinations
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// return the buffer size required to store this record
|
|
|
|
uint32 CPersistentDataRecord::totalDataSize() const
|
|
|
|
{
|
|
|
|
uint32 result=0;
|
|
|
|
result+= sizeof(uint32); // sizeof 'version number' variable
|
|
|
|
result+= sizeof(uint32); // sizeof 'data buffer size' variable
|
|
|
|
result+= sizeof(uint32); // sizeof 'number of tokens in the token table' variable
|
|
|
|
result+= sizeof(uint32); // sizeof 'number of args in the arg table' variable
|
|
|
|
result+= sizeof(uint32); // sizeof 'number of strings in the string table' variable
|
|
|
|
result+= sizeof(uint32); // sizeof 'string table data size' variable
|
2010-05-13 20:45:24 +00:00
|
|
|
result+= (uint32)_TokenTable.size()*sizeof(TToken); // sizeof the token data
|
|
|
|
result+= (uint32)_ArgTable.size()*sizeof(_ArgTable[0]); // size of the args data
|
2010-05-06 00:08:41 +00:00
|
|
|
result+= stringDataSize(); // the data size for the strings in the string table
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return the buffer size required to store this record
|
|
|
|
uint32 CPersistentDataRecord::stringDataSize() const
|
|
|
|
{
|
|
|
|
uint32 result=0;
|
|
|
|
for (uint32 i=0;i<_StringTable.size();++i)
|
2010-05-13 20:45:24 +00:00
|
|
|
result+=(uint32)_StringTable[i].size()+1; // the data size for the strings in the string table
|
2010-05-06 00:08:41 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::toStream(NLMISC::IStream& dest)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToStream);
|
|
|
|
|
|
|
|
#define WRITE(type,what) { type v= (type)(what); dest.serial(v); }
|
2010-05-13 20:45:24 +00:00
|
|
|
#define WRITE_BUFF(type,what) dest.serialBuffer( (uint8*)&what[0], sizeof(type) * (uint)what.size() )
|
2010-05-06 00:08:41 +00:00
|
|
|
|
|
|
|
// mark the amount of data in output stream before we start adding pdr contents
|
|
|
|
uint32 dataStart= dest.getPos();
|
|
|
|
|
|
|
|
// write the header block
|
|
|
|
WRITE(uint32,0);
|
|
|
|
WRITE(uint32,totalDataSize());
|
|
|
|
WRITE(uint32,_TokenTable.size());
|
|
|
|
WRITE(uint32,_ArgTable.size());
|
|
|
|
WRITE(uint32,_StringTable.size());
|
|
|
|
WRITE(uint32,stringDataSize());
|
|
|
|
|
|
|
|
// write the tokens
|
|
|
|
WRITE_BUFF(TToken,_TokenTable);
|
|
|
|
|
|
|
|
// write the arguments
|
|
|
|
WRITE_BUFF(uint32,_ArgTable);
|
|
|
|
|
|
|
|
// mark the amount of data in output stream before we start adding string table
|
|
|
|
uint32 stringTableStart= dest.getPos();
|
|
|
|
|
|
|
|
// write the string table data
|
|
|
|
for (uint32 i=0;i<_StringTable.size();++i)
|
|
|
|
{
|
|
|
|
WRITE_BUFF(char,_StringTable[i]);
|
|
|
|
WRITE(char,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the info written to the header corresponds to the reality of data written to file
|
|
|
|
BOMB_IF(dest.getPos()- stringTableStart!= stringDataSize(), "Error writing pdr string table to output stream", return false);
|
|
|
|
BOMB_IF(dest.getPos()- dataStart!= totalDataSize(), "Error writing pdr to output stream", return false);
|
|
|
|
|
|
|
|
#undef WRITE
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::toBuffer(char *dest,uint32 bufferSize)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToBuffer);
|
|
|
|
|
|
|
|
BOMB_IF(bufferSize<totalDataSize(),"Buffer too small to write data to",return false);
|
|
|
|
|
|
|
|
uint32 offset=0;
|
|
|
|
#define WRITE(type,what) { BOMB_IF(offset+sizeof(type)>bufferSize,"Buffer overflow!",return false); *(type*)&dest[offset]= what; offset+=sizeof(type); }
|
|
|
|
|
|
|
|
// write the header block
|
|
|
|
WRITE(uint32,0);
|
|
|
|
WRITE(uint32,totalDataSize());
|
2010-05-13 20:45:24 +00:00
|
|
|
WRITE(uint32,(uint32)_TokenTable.size());
|
|
|
|
WRITE(uint32,(uint32)_ArgTable.size());
|
|
|
|
WRITE(uint32,(uint32)_StringTable.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
WRITE(uint32,stringDataSize());
|
|
|
|
|
|
|
|
// write the tokens
|
|
|
|
for (uint32 i=0;i<_TokenTable.size();++i)
|
|
|
|
WRITE(TToken,_TokenTable[i]);
|
|
|
|
|
|
|
|
// write the arguments
|
|
|
|
for (uint32 i=0;i<_ArgTable.size();++i)
|
|
|
|
WRITE(uint32,_ArgTable[i]);
|
|
|
|
|
|
|
|
// write the string table data
|
|
|
|
for (uint32 i=0;i<_StringTable.size();++i)
|
|
|
|
{
|
|
|
|
for (uint32 j=0;j<_StringTable[i].size();++j)
|
|
|
|
WRITE(char,_StringTable[i][j]);
|
|
|
|
WRITE(char,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef WRITE
|
|
|
|
|
|
|
|
BOMB_IF(offset!=totalDataSize(),"Buffer size calculation doesn't match with data written",return false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::toString(std::string& result,TStringFormat stringFormat)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToString);
|
|
|
|
|
|
|
|
switch (stringFormat)
|
|
|
|
{
|
|
|
|
case XML_STRING: return toXML(result);
|
|
|
|
case LINES_STRING: return toLines(reinterpret_cast<CSString&>(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
BOMB("Invalid string format",return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::toXML(std::string& result)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToXMLString);
|
|
|
|
|
|
|
|
// clear out the result string before we begin
|
|
|
|
result.clear();
|
|
|
|
|
|
|
|
// build the text buffer
|
|
|
|
rewind();
|
|
|
|
while (!isEndOfData())
|
|
|
|
{
|
|
|
|
if (isStartOfStruct())
|
|
|
|
{
|
|
|
|
// start of a structure block...
|
|
|
|
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
|
|
|
|
result+= "<";
|
|
|
|
result+= lookupString(peekNextToken());
|
|
|
|
result+= ">\n";
|
|
|
|
popStructBegin(peekNextToken());
|
|
|
|
}
|
|
|
|
else if (isEndOfStruct())
|
|
|
|
{
|
|
|
|
// end of a structure block...
|
|
|
|
for (uint32 i=0;i<=_ReadingStructStack.size()-1;++i) result+='\t';
|
|
|
|
result+= "</";
|
|
|
|
result+= lookupString(peekNextToken());
|
|
|
|
result+= ">\n";
|
|
|
|
popStructEnd(peekNextToken());
|
|
|
|
}
|
|
|
|
else if (isTokenWithNoData())
|
|
|
|
{
|
|
|
|
// a standard property without value
|
|
|
|
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
|
|
|
|
result+= "<";
|
|
|
|
result+= lookupString(peekNextToken());
|
|
|
|
result+= " ";
|
|
|
|
result+= "type=\"";
|
|
|
|
result+= CArg::Flag().typeName();
|
|
|
|
result+= "\" value=\"";
|
|
|
|
result+= "1";
|
|
|
|
result+= "\"/>\n";
|
|
|
|
pop(peekNextToken());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// a standard property with value
|
|
|
|
string token= lookupString(peekNextToken());
|
|
|
|
string argType= peekNextArg().typeName();
|
|
|
|
|
|
|
|
CSString argTxt;
|
|
|
|
pop(peekNextToken(),argTxt);
|
|
|
|
|
|
|
|
for (uint32 i=0;i<=_ReadingStructStack.size();++i) result+='\t';
|
|
|
|
result+= "<";
|
|
|
|
result+= token;
|
|
|
|
result+= " ";
|
|
|
|
result+= "type=\"";
|
|
|
|
result+= argType;
|
|
|
|
result+= "\" value=\"";
|
|
|
|
result+= argTxt.encodeXML(true);
|
|
|
|
result+= "\"/>\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result="<xml>\n"+result+"</xml>\n";
|
|
|
|
|
|
|
|
// rewind the read pointer 'cos it's at the end of file
|
|
|
|
rewind();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::toLines(std::string& result)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToLinesString);
|
|
|
|
|
|
|
|
// setup a persistent data tree and have it scan our input buffer
|
|
|
|
rewind();
|
|
|
|
CPersistentDataTree pdt;
|
|
|
|
return pdt.readFromPdr(*this) && pdt.writeToBuffer(reinterpret_cast<NLMISC::CSString&>(result));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::writeToBinFile(const char* fileName)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToBinFile);
|
|
|
|
|
|
|
|
// build the buffer
|
|
|
|
uint32 bufSize= totalDataSize();
|
|
|
|
vector<char> buffer;
|
|
|
|
buffer.resize(bufSize);
|
|
|
|
toBuffer(&buffer[0],bufSize);
|
|
|
|
|
|
|
|
// write the buffer to a file
|
|
|
|
COFile f;
|
|
|
|
bool open = f.open(fileName);
|
|
|
|
DROP_IF(!open,NLMISC::toString("Failed to open output file %s",fileName).c_str(),return false);
|
|
|
|
|
|
|
|
// write the binary data to file
|
|
|
|
try
|
|
|
|
{
|
|
|
|
f.serialBuffer((uint8*)&buffer[0],bufSize);
|
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
DROP(NLMISC::toString("Failed to write output file: %s",fileName),return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// rewind the read pointer 'cos it's at the end of file
|
|
|
|
rewind();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::writeToTxtFile(const char* fileName,TStringFormat stringFormat)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToTxtFile);
|
|
|
|
|
|
|
|
// build the output text buffer
|
|
|
|
string s;
|
|
|
|
toString(s,stringFormat);
|
|
|
|
|
|
|
|
// write the text buffer to a file
|
|
|
|
COFile f;
|
|
|
|
bool open = f.open(fileName);
|
|
|
|
DROP_IF(!open,NLMISC::toString("Failed to open output file %s",fileName).c_str(),return false);
|
|
|
|
|
|
|
|
// write the binary data to file
|
|
|
|
try
|
|
|
|
{
|
2010-05-13 20:45:24 +00:00
|
|
|
f.serialBuffer((uint8*)&s[0],(uint)s.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
DROP(NLMISC::toString("Failed to write output file: %s",fileName),return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// rewind the read pointer 'cos it's at the end of file
|
|
|
|
rewind();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::writeToFile(const char* fileName,TFileFormat fileFormat)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordWriteToFile);
|
|
|
|
|
|
|
|
switch(fileFormat)
|
|
|
|
{
|
|
|
|
case BINARY_FILE:
|
|
|
|
binary_file:
|
|
|
|
nlinfo("saving binary file: %s",fileName);
|
|
|
|
return writeToBinFile(fileName);
|
|
|
|
|
|
|
|
case XML_FILE:
|
|
|
|
xml_file:
|
|
|
|
nlinfo("saving xml file: %s",fileName);
|
|
|
|
return writeToTxtFile(fileName,XML_STRING);
|
|
|
|
|
|
|
|
case LINES_FILE:
|
|
|
|
lines_file:
|
|
|
|
nlinfo("saving line-based txt file: %s",fileName);
|
|
|
|
return writeToTxtFile(fileName,LINES_STRING);
|
|
|
|
|
|
|
|
case ANY_FILE:
|
|
|
|
{
|
|
|
|
if (CSString(fileName).right(4)==".xml") goto xml_file;
|
|
|
|
if (CSString(fileName).right(4)==".txt") goto lines_file;
|
|
|
|
goto binary_file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
BOMB("Bad file type supplied to writeToFile() - file not saved: "<<fileName,return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
// set of accessors for retrieving a data record from various sources
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::fromBuffer(const char *src,uint32 bufferSize)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromBuffer);
|
|
|
|
|
|
|
|
// the second dword of a bin buffer contains the buffer length so use it to check whether
|
|
|
|
// this buffer looks like a correct binary buffer.
|
|
|
|
bool isValidBinary=(bufferSize>24 && *(uint32*)&src[4]==bufferSize);
|
|
|
|
|
|
|
|
// ensure that the data is binary... otherwise try to consider it as text
|
|
|
|
DROP_IF(!isValidBinary,"Failed to parse buffer due to invalid header",return false);
|
|
|
|
|
|
|
|
// make sure the persistent data record is cleared out before we fill it with data
|
|
|
|
clear();
|
|
|
|
// Must clear the string table, because read from file (and clear() init it with token family)
|
|
|
|
_StringTable.clear();
|
|
|
|
|
|
|
|
uint32 offset=0;
|
|
|
|
#define READ(type,what) { DROP_IF(offset+sizeof(type)>bufferSize,"PDR ERROR: Buffer overflow reading: " #type " " #what, clear(); return false); what=*(const type*)&src[offset]; offset+=sizeof(type); }
|
|
|
|
#define READBUF(type,count,what) { DROP_IF(offset+sizeof(type)*count>bufferSize,"PDR ERROR: Buffer overflow reading buffer: " #what, clear(); return false); if(count>0) { memcpy(what,&src[offset],sizeof(type)*count); offset+=sizeof(type)*count; } }
|
|
|
|
|
|
|
|
// READ the header block
|
|
|
|
uint32 version; READ(uint32,version);
|
|
|
|
uint32 totalSize; READ(uint32,totalSize);
|
|
|
|
uint32 tokenCount; READ(uint32,tokenCount);
|
|
|
|
uint32 argCount; READ(uint32,argCount);
|
|
|
|
uint32 stringCount; READ(uint32,stringCount);
|
|
|
|
uint32 stringsSize; READ(uint32,stringsSize);
|
|
|
|
|
|
|
|
DROP_IF(version>0,"PDR ERROR: Wrong file format version!",clear();return false);
|
|
|
|
DROP_IF(totalSize!=bufferSize,"PDR ERROR: Invalid source data",clear();return false);
|
|
|
|
DROP_IF(totalSize!=offset+tokenCount*sizeof(TToken)+argCount*sizeof(uint32)+stringsSize,"PDR ERROR: Invalid source data",clear();return false);
|
|
|
|
|
|
|
|
// READ the tokens
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromBufferTokenTable)
|
|
|
|
_TokenTable.resize(tokenCount);
|
|
|
|
READBUF(TToken,tokenCount,&_TokenTable[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// READ the arguments
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromBufferArgTable)
|
|
|
|
_ArgTable.resize(argCount);
|
|
|
|
READBUF(uint32,argCount,&_ArgTable[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// READ the string table data
|
|
|
|
_StringTable.resize(stringCount);
|
|
|
|
DROP_IF( (stringsSize==0) != (stringCount==0) , "PDR ERROR: Invalid string table parameters", clear(); return false);
|
|
|
|
|
|
|
|
if (stringCount!=0)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromBufferStringTable)
|
|
|
|
TStringTable::iterator stringTableIt= _StringTable.begin();
|
|
|
|
TStringTable::iterator stringTableEnd= _StringTable.end();
|
|
|
|
const char* stringDataIt= (const char*)&src[offset];
|
|
|
|
const char* stringDataEnd= (const char*)&src[offset+stringsSize];
|
|
|
|
|
|
|
|
DROP_IF(stringDataEnd[-1]!=0,"PDR ERROR: Last string table entry isn't zero terminated", clear(); return false);
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// prepare to push a new string into the string table
|
|
|
|
NLMISC::CSString& theTableEntry= *stringTableIt;
|
|
|
|
++stringTableIt;
|
|
|
|
|
|
|
|
// copy out the string
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromBufferStringCopy)
|
|
|
|
theTableEntry= stringDataIt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the string start marker
|
|
|
|
stringDataIt+= theTableEntry.size()+1;
|
|
|
|
|
|
|
|
// make sure we haven't run out of string data or string slots
|
|
|
|
DROP_IF( (stringTableIt!=stringTableEnd) != (stringDataIt!=stringDataEnd), "PDR ERROR: Invalid string table", clear(); return false);
|
|
|
|
}
|
|
|
|
while (stringDataIt!=stringDataEnd);
|
|
|
|
|
|
|
|
DROP_IF(stringTableIt!=stringTableEnd,"PDR ERROR: Too few strings found in string table",clear(); return false);
|
|
|
|
offset+= stringsSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef READBUF
|
|
|
|
#undef READ
|
|
|
|
|
|
|
|
BOMB_IF(offset!=totalSize,"Buffer size calculation doesn't match with data written",return false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string calculateLineAndColumn(const CSString& buff,uint32 index)
|
|
|
|
{
|
|
|
|
uint32 line=1;
|
|
|
|
uint32 col=1;
|
|
|
|
for (uint32 i=0;i<index;++i)
|
|
|
|
{
|
|
|
|
switch(buff[i])
|
|
|
|
{
|
|
|
|
case '\r': break; // ignore cr
|
|
|
|
case '\n': ++line; col=0; break; // treat lf
|
|
|
|
case '\t': col=(col+4)&~3; break; // assume 4 point tab
|
|
|
|
default: ++col; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NLMISC::toString("line %u: col %u: ",line,col);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::fromXML(const std::string& s)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromXML);
|
|
|
|
|
|
|
|
// we need to treat our input data as a CSString - we static cast because CSStrings are not supposed
|
|
|
|
// to contain any data of their own - they are just a fonctionality wrapper round a std::string
|
|
|
|
nlctassert(sizeof(string)==sizeof(CSString));
|
|
|
|
const CSString& buff= static_cast<const CSString&>(s);
|
2010-05-13 20:45:24 +00:00
|
|
|
uint32 len=(uint32)s.size();
|
2010-05-06 00:08:41 +00:00
|
|
|
|
|
|
|
// make sure the persistent data record is cleared out before we fill it with data
|
|
|
|
clear();
|
|
|
|
// Must clear the string table, because read from file (and clear() init it with token family)
|
|
|
|
_StringTable.clear();
|
|
|
|
|
|
|
|
// we have a buffer of xml-like blocks so we're going to start by chunking it up (from the back)
|
|
|
|
vector<CSString> clauses;
|
|
|
|
bool clauseOpen=false;
|
|
|
|
uint32 clauseEnd;
|
|
|
|
for (uint32 i=len;i--;)
|
|
|
|
{
|
|
|
|
switch(buff[i])
|
|
|
|
{
|
|
|
|
case '\n': case ' ': case '\t': case '\r': case 26: break;
|
|
|
|
|
|
|
|
case '>':
|
|
|
|
DROP_IF(clauseOpen==true,calculateLineAndColumn(buff,i)+"Found 2 '>'s with no matching '<'",return false);
|
|
|
|
clauseOpen=true;
|
|
|
|
clauseEnd=i;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '<':
|
|
|
|
DROP_IF(clauseOpen==false,calculateLineAndColumn(buff,i)+"Found '<' with no matching '>'",return false);
|
|
|
|
clauses.push_back(buff.substr(i+1,clauseEnd-i-1));
|
|
|
|
clauseOpen=false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DROP_IF((uint8)(buff[i])<32,calculateLineAndColumn(buff,i)+NLMISC::toString("Invalid (non-ascii text) character encountered: %i",buff[i]),return false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DROP_IF(clauses.size()<2||clauses[0]!="/xml"||clauses.back()!="xml","Invalid data file - didn't find <xml>..</xml> structure",return false)
|
|
|
|
// run through the set of clauses to add them to the data block...
|
2010-05-13 20:45:24 +00:00
|
|
|
for (uint32 i=(uint32)clauses.size()-1;--i;)
|
2010-05-06 00:08:41 +00:00
|
|
|
{
|
|
|
|
// clauses are of four types: <...>=struct_begin </..>=struct_end, <../>=prop, <!..>=comment
|
|
|
|
if (clauses[i].left(1)=="!" || clauses[i].left(1)=="?")
|
|
|
|
{
|
|
|
|
// comment
|
|
|
|
}
|
|
|
|
else if (clauses[i].left(1)=="/")
|
|
|
|
{
|
|
|
|
// struct end
|
|
|
|
CSString ss= clauses[i].leftCrop(1);
|
|
|
|
pushStructEnd(addString(ss));
|
|
|
|
}
|
|
|
|
else if (clauses[i].right(1)=="/")
|
|
|
|
{
|
|
|
|
// prop
|
|
|
|
CSString s= clauses[i].rightCrop(1);
|
|
|
|
CSString token= s.firstWord(true).strip();
|
|
|
|
CSString keyword0= s.strtok("=").strip();
|
|
|
|
CSString value0= s.firstWordOrWords(true,false,false);
|
|
|
|
s=s.strip();
|
|
|
|
CSString keyword1= s.strtok("=").strip();
|
|
|
|
CSString value1= s.firstWordOrWords(true,false,false);
|
|
|
|
if (keyword0=="value" && keyword1=="type")
|
|
|
|
{
|
|
|
|
swap(value0,value1);
|
|
|
|
swap(keyword0,keyword1);
|
|
|
|
}
|
|
|
|
DROP_IF(keyword0!="type" || keyword1!="value","Expecting 'type' and 'value' in property - but not found",continue);
|
|
|
|
CArg arg(value0,value1.decodeXML(),*this);
|
|
|
|
push(addString(token),arg);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// struct begin
|
|
|
|
pushStructBegin(addString(clauses[i]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::fromLines(const std::string& inputBuffer)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromLines);
|
|
|
|
|
|
|
|
// make sure the persistent data record is cleared out before we fill it with data
|
|
|
|
clear();
|
|
|
|
// Must clear the string table, because read from file (and clear() init it with token family)
|
|
|
|
_StringTable.clear();
|
|
|
|
|
|
|
|
// setup a persistent data tree and have it scan our input buffer
|
|
|
|
CPersistentDataTree pdt;
|
|
|
|
return pdt.readFromBuffer(reinterpret_cast<const NLMISC::CSString&>(inputBuffer)) && pdt.writeToPdr(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::fromString(const std::string& s)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordFromString);
|
|
|
|
|
|
|
|
// start by skipping any blank characters at the start of file
|
|
|
|
uint32 i;
|
|
|
|
for (i=0;i<s.size() && CSString::isWhiteSpace(s[i]);++i)
|
|
|
|
{}
|
|
|
|
|
|
|
|
// make sure there are non blank characters in the string
|
|
|
|
DROP_IF(i==s.size(),"string is empty",return false);
|
|
|
|
|
|
|
|
// determine whether we have an xml string or a txt string
|
|
|
|
// we assume that all xml files begin with a '<' character
|
|
|
|
if (s[i]=='<')
|
|
|
|
return fromXML(s);
|
|
|
|
else
|
|
|
|
return fromLines(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::fromStream(NLMISC::IStream& stream, uint32 size)
|
|
|
|
{
|
|
|
|
H_AUTO(pdrFromStream)
|
|
|
|
|
|
|
|
// setup a string as a buffer to hold the stream data
|
|
|
|
CSString buff;
|
|
|
|
buff.resize(size);
|
|
|
|
|
|
|
|
// read the file data
|
|
|
|
try
|
|
|
|
{
|
2010-05-13 20:45:24 +00:00
|
|
|
stream.serialBuffer((uint8*)&buff[0],(uint)buff.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
DROP(NLMISC::toString("Failed to read stream input"),return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// the second dword of a bin buffer contains the buffer length so use it to check whether
|
|
|
|
// this buffer looks like a correct binary buffer.
|
|
|
|
bool isBinary=(size>8 && *(uint32*)&buff[4]==size);
|
|
|
|
if (isBinary) return fromBuffer(&buff[0],size);
|
|
|
|
|
|
|
|
// it's not a valid binary file so see whether it looks like a valid text file
|
|
|
|
DROP_IF(!buff.isValidText(),"File is binary but 'file size' header entry doesn't match true file size",return false);
|
|
|
|
|
|
|
|
// parse the data as text...
|
|
|
|
return fromString(buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
// read from a CMemStream (maybe either binary or text data)
|
|
|
|
bool CPersistentDataRecord::fromBuffer(NLMISC::IStream& stream)
|
|
|
|
{
|
|
|
|
H_AUTO(pdrFromBuffer)
|
|
|
|
|
|
|
|
// try with a CMemStream
|
|
|
|
CMemStream *memStream = dynamic_cast<CMemStream*>(&stream);
|
|
|
|
if (memStream != NULL)
|
|
|
|
{
|
|
|
|
return fromBuffer((const char *)(memStream->buffer()+memStream->getPos()), memStream->length()-memStream->getPos());
|
|
|
|
}
|
|
|
|
// try with a IFile
|
|
|
|
NLMISC::CIFile *fileStream = dynamic_cast<NLMISC::CIFile*>(&stream);
|
|
|
|
if (fileStream != NULL)
|
|
|
|
{
|
|
|
|
return fromStream(*fileStream, fileStream->getFileSize());
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::readFromFile(const char* fileName)
|
|
|
|
{
|
|
|
|
H_AUTO(pdrReadFromFile)
|
|
|
|
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
|
|
|
|
|
|
// open the file
|
|
|
|
FILE* inf= fopen(fileName,"rb");
|
|
|
|
DROP_IF( inf==NULL, "Failed to open input file "<<fileName, return false);
|
|
|
|
|
|
|
|
// get the file size
|
|
|
|
uint32 length= filelength(fileno(inf));
|
|
|
|
|
|
|
|
// allocate a buffer
|
|
|
|
CSString buffer;
|
|
|
|
buffer.resize(length);
|
|
|
|
|
|
|
|
// read the data
|
2010-05-13 20:45:24 +00:00
|
|
|
uint32 blocksRead= (uint32)fread(&buffer[0],length,1,inf);
|
2010-05-06 00:08:41 +00:00
|
|
|
fclose(inf);
|
|
|
|
DROP_IF( blocksRead!=1, "Failed to read data from file "<<fileName, return false);
|
|
|
|
|
|
|
|
// test whether our data buffer is binary
|
|
|
|
bool isBinary=(length>8 && *(uint32*)&buffer[4]==length);
|
|
|
|
if (isBinary)
|
|
|
|
{
|
|
|
|
return fromBuffer(&buffer[0],length);
|
|
|
|
}
|
|
|
|
|
|
|
|
// it's not a valid binary file so see whether it looks like a valid text file
|
|
|
|
DROP_IF(!buffer.isValidText(),"File is binary but 'file size' header entry doesn't match true file size",return false);
|
|
|
|
|
|
|
|
// parse the data as text...
|
|
|
|
return fromString(buffer);
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
// open the file
|
|
|
|
CIFile f;
|
|
|
|
bool open = f.open(fileName);
|
|
|
|
DROP_IF( !open, "Failed to open input file "<<fileName, return false);
|
|
|
|
|
|
|
|
// get the file size
|
|
|
|
uint32 len= f.getFileSize();
|
|
|
|
|
|
|
|
bool result= fromStream(f, len);
|
|
|
|
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::readFromBinFile(const char* fileName)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordReadFromBinFile);
|
|
|
|
|
|
|
|
// open the file
|
|
|
|
CIFile f;
|
|
|
|
bool open = f.open(fileName);
|
|
|
|
DROP_IF(!open,NLMISC::toString("Failed to open input file %s",fileName).c_str(),return false)
|
|
|
|
|
|
|
|
// get the file size
|
|
|
|
uint32 len=CFile::getFileSize(fileName);
|
|
|
|
|
|
|
|
// setup a string as a buffer to hold the file data
|
|
|
|
string s;
|
|
|
|
s.resize(len);
|
|
|
|
|
|
|
|
// read the file data
|
|
|
|
try
|
|
|
|
{
|
2010-05-13 20:45:24 +00:00
|
|
|
f.serialBuffer((uint8*)&s[0],(uint)s.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
DROP(NLMISC::toString("Failed to read input file: %s",fileName),return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse the buffer contents to re-generate the data
|
2010-05-13 20:45:24 +00:00
|
|
|
bool result= fromBuffer(&s[0],(uint32)s.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CPersistentDataRecord::readFromTxtFile(const char* fileName)
|
|
|
|
{
|
|
|
|
H_AUTO(CPersistentDataRecordReadFromTxtFile);
|
|
|
|
|
|
|
|
// open the file
|
|
|
|
CIFile f;
|
|
|
|
bool open = f.open(fileName);
|
|
|
|
DROP_IF(!open,NLMISC::toString("Failed to open input file %s",fileName).c_str(),return false)
|
|
|
|
|
|
|
|
// get the file size
|
|
|
|
uint32 len=CFile::getFileSize(fileName);
|
|
|
|
|
|
|
|
// setup a string as a buffer to hold the file data
|
|
|
|
CSString buff;
|
|
|
|
buff.resize(len);
|
|
|
|
|
|
|
|
// read the file data
|
|
|
|
try
|
|
|
|
{
|
2010-05-13 20:45:24 +00:00
|
|
|
f.serialBuffer((uint8*)&buff[0],(uint)buff.size());
|
2010-05-06 00:08:41 +00:00
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
DROP(NLMISC::toString("Failed to read input file: %s",fileName),return false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse the buffer contents to re-generate the data
|
|
|
|
bool result= fromString(buff);
|
|
|
|
DROP_IF( !result, "Failed to parse input file "<<fileName, return false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// methods & data CPDRLookupTbl
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
uint32 CPDRLookupTbl::_NextLookupTblClassId;
|
|
|
|
|
|
|
|
CPDRLookupTbl::TEnumValue CPDRLookupTbl::operator[](uint32 idx) const
|
|
|
|
{
|
|
|
|
BOMB_IF(idx>=_TheTbl.size(),"ERROR: Failed to retrieve entry from PDR lookup table (idx is out of bounds) - pdr must be corrupt",return -1);
|
|
|
|
return _TheTbl[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CPDRLookupTbl::getNumLookupTableClasses()
|
|
|
|
{
|
|
|
|
return _NextLookupTblClassId;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// methods CPdrTokenRegistry
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CPdrTokenRegistry* CPdrTokenRegistry::getInstance()
|
|
|
|
{
|
|
|
|
// first time the method is called instantiate the singleton object
|
|
|
|
if (_Instance==NULL)
|
|
|
|
_Instance= new CPdrTokenRegistry;
|
|
|
|
|
|
|
|
// return the pointer to our singleton
|
|
|
|
return _Instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CPdrTokenRegistry::releaseInstance()
|
|
|
|
{
|
|
|
|
if( _Instance )
|
|
|
|
delete _Instance;
|
|
|
|
_Instance = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint16 CPdrTokenRegistry::addToken(const std::string& family,const std::string& token)
|
|
|
|
{
|
|
|
|
// get the map entry correponding to 'family' (or create a new one if need be)
|
|
|
|
CPersistentDataRecord::TStringTable& stringTable= _Registry[family];
|
|
|
|
|
|
|
|
// look for an existing match in the string table
|
|
|
|
for (uint16 i=0;i<stringTable.size();++i)
|
|
|
|
{
|
|
|
|
if (stringTable[i]==token)
|
|
|
|
return i;
|
|
|
|
BOMB_IF(i>=8191,"Failed to add more then 8192 static token to a pdr string table",return 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// append new entry to the string table and return the new index
|
|
|
|
stringTable.push_back(token);
|
2010-05-13 20:45:24 +00:00
|
|
|
return (uint16)stringTable.size()-1;
|
2010-05-06 00:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const CPersistentDataRecord::TStringTable& CPdrTokenRegistry::getStringTable(const std::string& family)
|
|
|
|
{
|
|
|
|
// return the map entry correponding to 'family' (creating a new one if need be)
|
|
|
|
return _Registry[family];
|
|
|
|
}
|
|
|
|
|
|
|
|
CPdrTokenRegistry::CPdrTokenRegistry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|