// Ryzom - MMORPG Framework // 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 . /* 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 #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) uint32 len= name.size(); //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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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 for (uint32 i=_StringTable.size();i--;) { 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); uint16 result= _StringTable.size(); _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 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(this)->popStructBegin(peekNextToken()); const_cast(other).popStructBegin(other.peekNextToken()); } else if (isEndOfStruct()) { // skip end of struct token const_cast(this)->popStructEnd(peekNextToken()); const_cast(other).popStructEnd(other.peekNextToken()); } else if (isTokenWithNoData()) { // skip token with no data const_cast(this)->pop(peekNextToken()); const_cast(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(this)->pop(peekNextToken(),thisValue); const_cast(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(this)->popStructBegin(nextToken); } else if (nextTokenType==CArg::STRUCT_END) // isEndOfStruct() { // skip end of struct token const_cast(this)->popStructEnd(nextToken); } else if (nextTokenType==CArg::FLAG) // isTokenWithNoData() { // skip token with no data const_cast(this)->pop(nextToken); ++result; } else { // get the next value and discard it immediately const_cast(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 result+= _TokenTable.size()*sizeof(TToken); // sizeof the token data result+= _ArgTable.size()*sizeof(_ArgTable[0]); // size of the args data 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) result+=_StringTable[i].size()+1; // the data size for the strings in the string table return result; } bool CPersistentDataRecord::toStream(NLMISC::IStream& dest) { H_AUTO(CPersistentDataRecordWriteToStream); #define WRITE(type,what) { type v= (type)(what); dest.serial(v); } #define WRITE_BUFF(type,what) dest.serialBuffer( (uint8*)&what[0], sizeof(type) * what.size() ) // 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(bufferSizebufferSize,"Buffer overflow!",return false); *(type*)&dest[offset]= what; offset+=sizeof(type); } // 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 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(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+= "\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="\n"+result+"\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(result)); } bool CPersistentDataRecord::writeToBinFile(const char* fileName) { H_AUTO(CPersistentDataRecordWriteToBinFile); // build the buffer uint32 bufSize= totalDataSize(); vector 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 { f.serialBuffer((uint8*)&s[0],s.size()); } 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: "<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(s); uint32 len=s.size(); // 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 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 .. structure",return false) // run through the set of clauses to add them to the data block... for (uint32 i=clauses.size()-1;--i;) { // 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(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;i8 && *(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(&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(&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 "<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 "<=_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=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); return stringTable.size()-1; } 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() { }