849 lines
26 KiB
C++
849 lines
26 KiB
C++
// 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/>.
|
|
|
|
/*
|
|
|
|
NOTE: The file format for storing trees to disk is line based. It follows the following rules:
|
|
|
|
NOTE too that this file format is intended as an alternative for storage of PDR data and that the PDR
|
|
format doesn't differentiate between single values and array elements
|
|
|
|
- Leading spaces are ignored when scanning lines, as are all spaces preceding the '=' or '==' signs
|
|
- Any component of the paths may be quote encapsulated
|
|
- Blank lines + those starting with '//' or with '#' are ignored
|
|
- lines of format <path>'='<value> contain raw values
|
|
- lines of format <path>'=='<value> contain values that will be stripped and unquoted if they were previously quoted
|
|
- lines of format <path> declare the start of a new record for the given path
|
|
- paths take the form: <component>'.'<component>'.'<component>...
|
|
- components take one of the following forms:
|
|
<name>
|
|
<name>'#'<idx>
|
|
<name>':'<map_key>
|
|
<name>'#'<idx>':'<map_key>
|
|
<name>':'<map_key>'#'<idx>
|
|
<name>'#'<idx>':'<map_key>'#'<idx>
|
|
|
|
EXAMPLE:
|
|
# a little example
|
|
# the data format that we are representing looks like this:
|
|
# class ClassA { vector<int> b; int c; string d; }
|
|
# class ClassTheWholeFile { vector<ClassA> a; map<string,vector<string>> m; }
|
|
|
|
// start a first record
|
|
a
|
|
a.b=1
|
|
a.c=2
|
|
a.d== "hello \" world"
|
|
|
|
// create a seond record
|
|
a
|
|
a.b=3
|
|
a.c=4
|
|
a.d=hello world
|
|
|
|
// append a new element to the first record
|
|
a#0.b=6
|
|
|
|
// change an element from the first record
|
|
a#0.b#0=5
|
|
|
|
// add a couple of map entries
|
|
m:abc="def"
|
|
m:bcd=fgh
|
|
|
|
// add a value to the array 'm:bcd'
|
|
m:bcd=ghi
|
|
|
|
*/
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Includes
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "stdpch.h"
|
|
#include "persistent_data_tree.h"
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Namespaces
|
|
//-----------------------------------------------------------------------------
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// handy utility routines
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static bool needsQuotes(const CSString& src)
|
|
{
|
|
for (uint32 i=0;i<src.size();++i)
|
|
{
|
|
char c= src[i];
|
|
if (c<=32 || c=='.' || c==':' || c=='#' || c=='\"' || c=='=')
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static CSString cleanQuotes(CSString src)
|
|
{
|
|
src= src.strip().unquoteIfQuoted();
|
|
return (needsQuotes(src))? src.quote(): src;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// methods CPersistentDataTreeNode
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CPersistentDataTreeNode::CPersistentDataTreeNode(const NLMISC::CSString& name,CPersistentDataTreeNode* parent)
|
|
{
|
|
_Name= name;
|
|
_Parent=NULL;
|
|
_IsMap= false;
|
|
_IsValue= false;
|
|
attachToParent(parent);
|
|
}
|
|
|
|
CPersistentDataTreeNode::CPersistentDataTreeNode(const NLMISC::CSString& name,CPersistentDataTreeNode* parent,uint32 idx)
|
|
{
|
|
_Name= name;
|
|
_Parent=NULL;
|
|
_IsMap= false;
|
|
_IsValue= false;
|
|
attachToParent(parent,idx);
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::attachToParent(CPersistentDataTreeNode* parent)
|
|
{
|
|
return attachToParent(parent,parent==NULL?~0u:(uint32)parent->_Children.size());
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::attachToParent(CPersistentDataTreeNode* parent,uint32 idx)
|
|
{
|
|
// check value of 'idx'
|
|
BOMB_IF(parent!=NULL && idx>1024*1024,"Implausibly high number of children requested ("+NLMISC::toString("%d",idx)+") for persistent data tree node: "+parent->getNodeName(),return false);
|
|
|
|
// check for attachment to previous parent
|
|
BOMB_IF(_Parent!=NULL,"Attempting to attach a persistent data node to parent '"+(parent==NULL?"NULL":parent->getNodeName())+"' when it's already attached to another parent as: "+getNodeName(),return false);
|
|
|
|
// split the name into its component parts
|
|
CSString mapIndex= _Name;
|
|
CSString nameBase= cleanQuotes(mapIndex.splitToSeparator('#',true,false,true,true,false));
|
|
// get rid of any odd spaces floating about the map index
|
|
bool hasExplicitIndex= false;
|
|
if (!mapIndex.empty())
|
|
{
|
|
hasExplicitIndex= true;
|
|
mapIndex= mapIndex.leftCrop(1).strip();
|
|
}
|
|
|
|
// add self to parent's children
|
|
if (parent!=NULL)
|
|
{
|
|
// check parent isn't a value
|
|
BOMB_IF(parent->_IsValue,"Attempting to attach a persistent data node to parent that has a value '"+parent->getNodeName()+"' = "+parent->_Value,return false);
|
|
|
|
if (hasExplicitIndex)
|
|
{
|
|
// there is an explicit '#' number so ensure that it really is followed by a valid number
|
|
sint32 num= mapIndex.atoi();
|
|
if(NLMISC::toString("%u",num)!=mapIndex)
|
|
{
|
|
WARN("WARNING: Strange node name detected after last '#' - truncating and replacing with a number: "+getNodeName());
|
|
hasExplicitIndex=false;
|
|
}
|
|
else
|
|
{
|
|
// if this is the highest '#' number for this root node name then ajust the _NextPoundValue value
|
|
if (num>=parent->_NextPoundValue[nameBase])
|
|
{
|
|
parent->_NextPoundValue[nameBase]= num+1;
|
|
}
|
|
|
|
// ensure that there isn't already a node with the same '#' value attached to the same parent
|
|
DROP_IF(parent->_ChildIndex.find(nameBase+'#'+mapIndex)!=parent->_ChildIndex.end(),"Failed to add child '"+_Name+"' to parent '"+parent->getNodeName()+"' because another child of same name already exists",return false);
|
|
}
|
|
}
|
|
if (!hasExplicitIndex)
|
|
{
|
|
// we don't have an explicit '#' number in the name, so we need to generate one
|
|
uint32 number= parent->_NextPoundValue[nameBase]++;
|
|
mapIndex=NLMISC::toString("%u",number);
|
|
}
|
|
|
|
// construct our cleaned up name from its constituent parts
|
|
_Name= nameBase+'#'+mapIndex;
|
|
|
|
// setup own _Parent property and ensure that there are no trailing spaces round the _Name
|
|
_Parent= parent;
|
|
|
|
_Parent->_ChildIndex[_Name]=this;
|
|
_Parent->_ChildIndex[nameBase]=this;
|
|
|
|
// grow the parent's _Children vector if needed
|
|
if (_Parent->_Children.size()<=idx)
|
|
{
|
|
_Parent->_Children.resize(idx+1);
|
|
}
|
|
|
|
// ensure that there isn't another child already assigned to this parent slot
|
|
BOMB_IF(_Parent->_Children[idx]!=NULL,"Ignoring attempt to add second child to same slot ("+NLMISC::toString("%d",idx)+") in persistent data tree node's children: "+_Parent->getNodeName(),return false);
|
|
|
|
// write own pointer into parent's _Children vector
|
|
_Parent->_Children[idx]= this;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::readFromPdr(CPersistentDataRecord& pdr)
|
|
{
|
|
static uint16 mapKeyToken= (uint16)~0u; pdr.addString("__Key__",mapKeyToken);
|
|
static uint16 mapValToken= (uint16)~0u; pdr.addString("__Val__",mapValToken);
|
|
|
|
while (!pdr.isEndOfData())
|
|
{
|
|
if (pdr.isStartOfStruct())
|
|
{
|
|
// get the token corresponding to this start of block
|
|
uint16 token=pdr.peekNextToken();
|
|
pdr.popStructBegin(token);
|
|
|
|
// create a new sub-node for the block in question
|
|
CSString structName= pdr.lookupString(token);
|
|
if (needsQuotes(structName))
|
|
structName= structName.quote();
|
|
TNodePtr newNode= new CPersistentDataTreeNode(structName,this);
|
|
|
|
// recurse into new block
|
|
bool ok= newNode->readFromPdr(pdr);
|
|
if (!ok || pdr.isEndOfData())
|
|
return false;
|
|
|
|
// pop the end of block token for the block we just finished processing
|
|
DROP_IF(pdr.peekNextToken()!=token,"ERROR: End of "+pdr.lookupString(token)+" block expected but not found at: "+getNodeName(),return false);
|
|
pdr.popStructEnd(token);
|
|
}
|
|
else if (pdr.isEndOfStruct())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
uint16 nextToken=pdr.peekNextToken();
|
|
if (nextToken==mapKeyToken)
|
|
{
|
|
// we've matched a map key so we need to extract both key and related value (or child block)
|
|
|
|
// ensure the node is either a map or struct but not mixed
|
|
if (!flagAsMap()) return false;
|
|
|
|
// extract the map key and ensure that it's followed by a valid __Val__ entry
|
|
CSString mapKey;
|
|
pdr.pop(mapKeyToken,mapKey);
|
|
DROP_IF(pdr.isEndOfData() || pdr.peekNextToken()!=mapValToken,"ERROR: Ignoring map key (__Key__) because __Val__ token expected but not found at: "+getNodeName()+":"+mapKey,continue);
|
|
if (needsQuotes(mapKey))
|
|
mapKey=mapKey.quote();
|
|
|
|
// create the new node for the map entry
|
|
TNodePtr newNode= new CPersistentDataTreeNode(mapKey,this);
|
|
|
|
// see whether this is a struct entry or a simple value
|
|
if (pdr.isStartOfStruct())
|
|
{
|
|
// struct entry - pop the start of struct marker
|
|
pdr.popStructBegin(mapValToken);
|
|
|
|
// recurse into the new block
|
|
bool ok= newNode->readFromPdr(pdr);
|
|
if (!ok || pdr.isEndOfData())
|
|
return false;
|
|
|
|
// pop the end of struct marker
|
|
DROP_IF(pdr.peekNextToken()!=mapValToken,"ERROR: End of __Val__ block expected but not found at: "+getNodeName()+":"+mapKey,return false);
|
|
pdr.popStructEnd(mapValToken);
|
|
}
|
|
else
|
|
{
|
|
pdr.pop(mapValToken,newNode->_Value);
|
|
newNode->_IsValue=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we've got a classic value so create a new node and set it's value
|
|
CSString valName= pdr.lookupString(nextToken);
|
|
if (needsQuotes(valName))
|
|
valName= valName.quote();
|
|
|
|
TNodePtr newNode= new CPersistentDataTreeNode(valName,this);
|
|
pdr.pop(nextToken,newNode->_Value);
|
|
newNode->_IsValue=true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool isFloat(double d,float f)
|
|
{
|
|
return (double(f)==d);
|
|
}
|
|
|
|
static void pdrPushCompactValue(CPersistentDataRecord& pdr,uint16 token,const NLMISC::CSString& value)
|
|
{
|
|
// if this is an empty string or doesn't begin with a valid numeric character then just push it to the pdr and return
|
|
if ( value.empty() || ( ((uint32)(value[0]-'0')>9) && (value[0]!='-') ) )
|
|
{
|
|
// not numeric so try storing as an eid
|
|
NLMISC::CEntityId eid;
|
|
eid.fromString(value.c_str());
|
|
if (eid!=NLMISC::CEntityId::Unknown && eid.toString()==value)
|
|
{
|
|
pdr.push(token,eid);
|
|
return;
|
|
}
|
|
|
|
pdr.push(token,value);
|
|
return;
|
|
}
|
|
|
|
if (value[0]=='-')
|
|
{
|
|
// see if we can treat this value as a signed int
|
|
sint64 si64= value.atosi64();
|
|
if (si64!=0)
|
|
{
|
|
// we can store this value as a signed int so decide how big an int we need
|
|
sint32 si= (sint32)si64;
|
|
if (si==si64) pdr.push(token,si);
|
|
else pdr.push(token,si64);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a signed int so try storing as an unsigned int
|
|
uint64 ui64= value.atoui64();
|
|
if (ui64!=0 || value=="0")
|
|
{
|
|
// we can store this value as a signed int so decide how big an int we need
|
|
uint32 ui= (uint32)ui64;
|
|
if (ui==ui64) pdr.push(token,ui);
|
|
else pdr.push(token,ui64);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// failed to store as an int so try a float
|
|
double d = (double)value.atof();
|
|
if (NLMISC::toString(d)==value)
|
|
{
|
|
float f= (float)d;
|
|
if (isFloat(d,f)) pdr.push(token, f);
|
|
else pdr.push(token, d);
|
|
return;
|
|
}
|
|
|
|
// default to save as a string
|
|
pdr.push(token,value);
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::writeToPdr(CPersistentDataRecord& pdr) const
|
|
{
|
|
// extract the sub-part of the name that precedes the '#' token
|
|
CSString name= _Name.splitToSeparator('#').unquoteIfQuoted();
|
|
|
|
uint16 token= pdr.addString(name);
|
|
|
|
// if this is a map entry then split into map key and value
|
|
if (isMapEntry())
|
|
{
|
|
static uint16 mapKeyToken= (uint16)~0u; pdr.addString("__Key__",mapKeyToken);
|
|
static uint16 mapValToken= (uint16)~0u; pdr.addString("__Val__",mapValToken);
|
|
|
|
// write a value - try to match a reasonably compact format if one exists
|
|
pdrPushCompactValue(pdr,mapKeyToken,name);
|
|
|
|
// setup the valToken for writing value using common code...
|
|
token= mapValToken;
|
|
}
|
|
|
|
if (_IsValue)
|
|
{
|
|
// write a value - try to match a reasonably compact format if one exists
|
|
pdrPushCompactValue(pdr,token,_Value);
|
|
}
|
|
else
|
|
{
|
|
// open a new block in the pdr
|
|
pdr.pushStructBegin(token);
|
|
|
|
// iterate over children recursing...
|
|
bool ok=true;
|
|
for (uint32 i=0;i<_Children.size() && ok;++i)
|
|
{
|
|
BOMB_IF(_Children[i]==NULL,"ERROR: NULL pointer in persistent data tree node children",return false);
|
|
ok= _Children[i]->writeToPdr(pdr);
|
|
}
|
|
|
|
// close the block inthe pdr
|
|
pdr.pushStructEnd(token);
|
|
BOMB_IF(!ok,"Giving up writeToPdr() "+_Name,return false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::writeToBuffer(NLMISC::CSString& buffer) const
|
|
{
|
|
if (_IsValue)
|
|
{
|
|
// write a value
|
|
if (needsQuotes(_Value))
|
|
buffer+= getNodeName()+"=="+_Value.quote()+"\n";
|
|
else
|
|
buffer+= getNodeName()+"="+_Value+"\n";
|
|
}
|
|
else
|
|
{
|
|
if (_Children.empty())
|
|
{
|
|
// write an empty block
|
|
buffer+= getNodeName()+"\n";
|
|
}
|
|
|
|
// iterate over children recursing...
|
|
for (uint32 i=0;i<_Children.size();++i)
|
|
{
|
|
BOMB_IF(_Children[i]==NULL,"ERROR: NULL pointer in persistent data tree node children",return false);
|
|
bool ok= _Children[i]->writeToBuffer(buffer);
|
|
BOMB_IF(!ok,"Giving up writeToBuffer() "+_Name,return false);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const CSString& CPersistentDataTreeNode::getName() const
|
|
{
|
|
return _Name;
|
|
}
|
|
|
|
CSString CPersistentDataTreeNode::getNodeName() const
|
|
{
|
|
// extract the sub-part of the name that precedes the '#' token
|
|
CSString name=_Name.splitToSeparator('#');
|
|
|
|
// if we have no parent then our name is obviously just the 'name' value
|
|
if (_Parent==NULL)
|
|
return name;
|
|
|
|
// check to see whether there are more then one child in our parent that match our base name
|
|
if (_Parent->_NextPoundValue[name]>1)
|
|
{
|
|
// other siblings exist so we need to retain th full name including the '#' segment
|
|
name=_Name;
|
|
}
|
|
|
|
// recurse trhough parents to build the name root
|
|
CSString parentName= _Parent->getNodeName();
|
|
|
|
// return one of name, parentName.name and parentName:name
|
|
if (parentName.empty()) return name;
|
|
if (isMapEntry()) return parentName+":"+name;
|
|
else return parentName+"."+name;
|
|
}
|
|
|
|
void CPersistentDataTreeNode::setValue(const TValue& value)
|
|
{
|
|
DROP_IF(!_Children.empty(),"Ignoring attempt to set value of a node that has children: "+getNodeName(),return);
|
|
_Value= value;
|
|
_IsValue= true;
|
|
}
|
|
|
|
const CPersistentDataTreeNode::TValue& CPersistentDataTreeNode::getValue() const
|
|
{
|
|
WARN_IF(!_IsValue,"Attempting to get value of a node that isn't flagged as being one: "+getNodeName());
|
|
return _Value;
|
|
}
|
|
|
|
const CPersistentDataTreeNode::TValue& CPersistentDataTreeNode::getValue(const CSString& nameList) const
|
|
{
|
|
// get hold of the named node, or NULL if it doesn't exist
|
|
CPersistentDataTreeNode* node= const_cast<CPersistentDataTreeNode*>(this)->getDescendant(nameList,false);
|
|
|
|
// check whether the node exists
|
|
static TValue nullValue;
|
|
// DROP_IF(node==NULL,"Failed to locate node in getValue("+nameList+")",return nullValue);
|
|
if(node==NULL)
|
|
return nullValue;
|
|
|
|
// return the node's value
|
|
return node->getValue();
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::isMapEntry() const
|
|
{
|
|
return (_Parent!=NULL) && (_Parent->_IsMap);
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::flagAsMap()
|
|
{
|
|
DROP_IF(_IsValue,"ERROR: Failed to flag persistent data tree node '"+getNodeName()+"' as a map as it already has a value: "+_Value,return false);
|
|
|
|
if (_IsMap)
|
|
return true;
|
|
|
|
DROP_IF(!_Children.empty(),"ERROR: Failed to flag persistent data tree node '"+getNodeName()+"' as a map as it already has children: ",return false);
|
|
|
|
_IsMap= true;
|
|
return true;
|
|
}
|
|
|
|
const CPersistentDataTreeNode* CPersistentDataTreeNode::getChild(const NLMISC::CSString& name) const
|
|
{
|
|
return const_cast<CPersistentDataTreeNode*>(this)->getChild(name);
|
|
}
|
|
|
|
CPersistentDataTreeNode* CPersistentDataTreeNode::getChild(const NLMISC::CSString& name)
|
|
{
|
|
TChildIndex::iterator it= _ChildIndex.find(name);
|
|
return (it==_ChildIndex.end())? NULL: (*it).second;
|
|
}
|
|
|
|
const CPersistentDataTreeNode::TChildren& CPersistentDataTreeNode::getChildren() const
|
|
{
|
|
WARN_IF(!_Value.empty(),"Attempting to access children of a node that has a value: "+getNodeName());
|
|
return _Children;
|
|
}
|
|
|
|
CPersistentDataTreeNode* CPersistentDataTreeNode::getDescendant(const NLMISC::CSString& nameList,bool createIfNotExist)
|
|
{
|
|
CSString s=nameList.strip();
|
|
|
|
// if the node name is empty then this is the node we're after...
|
|
if (s.empty())
|
|
{
|
|
return this;
|
|
}
|
|
|
|
// extract the next chunk from the name list and get a pointer to the identified node
|
|
CSString nextChunk= s.splitToSeparator('.',true,false,true,true,true).strip();
|
|
|
|
// break the chunk into elements and perform a syntax check
|
|
CVectorSString elements;
|
|
nextChunk.splitByOneOfSeparators("#:",elements,false,true,true,true,false);
|
|
switch (elements.size())
|
|
{
|
|
case 7:
|
|
DROP_IF(elements[1]!='#'||elements[3]!=':'||elements[5]!='#',"SYNTAX ERROR in persistent data tree node name: "+nextChunk,return NULL);
|
|
break;
|
|
case 5:
|
|
DROP_IF(elements[3]!='#'&&elements[3]!=':',"SYNTAX ERROR in persistent data tree node name: "+nextChunk,return NULL);
|
|
DROP_IF(elements[1]==':'&&elements[3]==':',"SYNTAX ERROR in persistent data tree node name: "+nextChunk,return NULL);
|
|
// drop through (no break)
|
|
case 3:
|
|
DROP_IF(elements[1]!='#'&&elements[1]!=':',"SYNTAX ERROR in persistent data tree node name: "+nextChunk,return NULL);
|
|
// drop through (no break)
|
|
case 1:
|
|
break;
|
|
default:
|
|
DROP("SYNTAX ERROR in persistent data tree node name: "+nextChunk,return NULL);
|
|
}
|
|
uint32 elementsToSkip=0;
|
|
|
|
// if this is a map entry then get the parent element
|
|
CPersistentDataTreeNode* container= this;
|
|
if (elements.size()>=5 || (elements.size()>=3 && elements[1]==":"))
|
|
{
|
|
// compose the map name
|
|
CSString mapName= cleanQuotes(elements[0]);
|
|
if (elements[1]=='#')
|
|
{
|
|
mapName+=elements[1]+elements[2];
|
|
elementsToSkip=4;
|
|
}
|
|
else
|
|
{
|
|
elementsToSkip=2;
|
|
}
|
|
|
|
// this is a true map entry so it's composed of 2 parts ... we treat the first part here and drop through to the
|
|
// normal code to treat the second part
|
|
container= getDescendant(mapName.strip(),createIfNotExist);
|
|
if (container==NULL || !container->flagAsMap())
|
|
return NULL;
|
|
}
|
|
|
|
// compose the chunk name
|
|
nextChunk.clear();
|
|
for (uint32 i=elementsToSkip;i<elements.size();++i)
|
|
{
|
|
nextChunk+= ((i&1)==1)? elements[i]: cleanQuotes(elements[i]);
|
|
}
|
|
|
|
CPersistentDataTreeNode* nextNode= container->getChild(nextChunk);
|
|
|
|
// if the node didn't exist...
|
|
if (nextNode==NULL)
|
|
{
|
|
// if we're not allowed to create non-existant nodes then return NULL
|
|
if (createIfNotExist==false)
|
|
return NULL;
|
|
|
|
// create a new node...
|
|
nextNode= new CPersistentDataTreeNode(nextChunk,container);
|
|
}
|
|
|
|
// recurse...
|
|
return nextNode->getDescendant(s,createIfNotExist);
|
|
}
|
|
|
|
bool CPersistentDataTreeNode::operator==(const CPersistentDataTreeNode& other) const
|
|
{
|
|
// make sure the names match
|
|
if (_Name != other._Name)
|
|
return false;
|
|
|
|
// make sure the 'type' booleans match
|
|
if (_IsMap != other._IsMap)
|
|
return false;
|
|
if (_IsValue != other._IsValue)
|
|
return false;
|
|
|
|
// are we a node or a value?
|
|
if (_IsValue)
|
|
{
|
|
// check that values match
|
|
if (_Value != other._Value)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// check that children match
|
|
if (_Children.size() != other._Children.size())
|
|
return false;
|
|
for (uint32 i=0;i<_Children.size();++i)
|
|
{
|
|
// Check whether this child matches the other tree node's equivalent child
|
|
if (!(*_Children[i]==*other._Children[i]))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// methods CPersistentDataTree
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CPersistentDataTree::CPersistentDataTree()
|
|
{
|
|
_Child= new CPersistentDataTreeNode;
|
|
}
|
|
|
|
bool CPersistentDataTree::readFromBuffer(const CSString& buffer)
|
|
{
|
|
// generate a new _Child object and implicitly liberate the previous one
|
|
_Child= new CPersistentDataTreeNode;
|
|
|
|
// convert the buffer to lines
|
|
CVectorSString lines;
|
|
buffer.splitLines(lines);
|
|
|
|
// iterate over the lines...
|
|
for (uint32 i=0;i<lines.size();++i)
|
|
{
|
|
// make sure this isn't a blank or comment line
|
|
CSString line= lines[i].leftStrip();
|
|
if (line.empty()||line[0]=='#'||line.left(2)=="//")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// split the line into key (s0) and value (s1)
|
|
CSString s1=line;
|
|
CSString s0=s1.splitToSeparator('=',true,false,true,true,false).strip();
|
|
s1=s1.leftStrip();
|
|
|
|
// we need to create a new tree node (eg a new struct, array entry or value)
|
|
|
|
// split the key string into its component parts
|
|
CVectorSString components;
|
|
s0.splitBySeparator('.',components);
|
|
|
|
// extract the child name
|
|
CSString childName= components.back();
|
|
|
|
// construct the parent name
|
|
components.pop_back();
|
|
CSString parentName;
|
|
parentName.join(components,'.');
|
|
|
|
// if the child name is a map entry then we have a special case to deal with
|
|
CSString mapName= childName.splitToSeparator(':',true,false,true,true,true);
|
|
if (childName.empty())
|
|
{
|
|
childName=mapName;
|
|
mapName.clear();
|
|
}
|
|
else
|
|
{
|
|
if (!parentName.empty())
|
|
parentName+='.';
|
|
parentName+= mapName;
|
|
}
|
|
|
|
// get hold of a pointer to the parent node, creating new tree structure if need be
|
|
CPersistentDataTreeNode* parent= _Child->getDescendant(parentName,true);
|
|
DROP_IF(parent==NULL,"ERROR Failed to get handle to node in readFromBuffer: "+parentName,return false);
|
|
|
|
// if this is a map entry then try to flag the parent as a map
|
|
if (!mapName.empty())
|
|
{
|
|
if (!parent->flagAsMap())
|
|
return false;
|
|
}
|
|
|
|
// create the new child node and attach it to the parent
|
|
// note that by using a smart ptr (TNodePtr) we avoid memory leakage if attach fails
|
|
CPersistentDataTree::TNodePtr child= new CPersistentDataTreeNode(childName,parent);
|
|
|
|
// if there is a value then add it
|
|
if (!s1.empty())
|
|
{
|
|
// note that we use the 2 parameter version of setValue here and NOT child->setValue because
|
|
// it is possible that the attempt to attach a child failed and that there is another node
|
|
// in the tree that will accept the 'setValue()' instead
|
|
if (s1.left(2)=="==")
|
|
setValue(s0,s1.leftCrop(2).strip().unquoteIfQuoted());
|
|
else /* in this case we have a single "=" and not an "==" */
|
|
setValue(s0,s1.leftCrop(1).leftStrip());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPersistentDataTree::readFromFile(const CSString& fileName)
|
|
{
|
|
// generate a new _Child object and implicitly liberate the previous one
|
|
_Child= new CPersistentDataTreeNode;
|
|
|
|
CSString buffer;
|
|
buffer.readFromFile(fileName);
|
|
DROP_IF(buffer.empty(),"Failed to read input file for persistent data tree: "+fileName,return false);
|
|
return readFromBuffer(buffer);
|
|
}
|
|
|
|
bool CPersistentDataTree::readFromPdr(CPersistentDataRecord& pdr)
|
|
{
|
|
// generate a new _Child object and implicitly liberate the previous one
|
|
_Child= new CPersistentDataTreeNode;
|
|
|
|
bool ok= _Child->readFromPdr(pdr);
|
|
if (!ok || !pdr.isEndOfData())
|
|
{
|
|
// we've had a problem so give up cleanly
|
|
|
|
// delete the child object and create a new empty one
|
|
_Child= new CPersistentDataTreeNode;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPersistentDataTree::writeToBuffer(CSString& buffer) const
|
|
{
|
|
// clear out the buffer before starting
|
|
buffer.clear();
|
|
|
|
// write the tree to the buffer
|
|
bool ok= _Child->writeToBuffer(buffer);
|
|
|
|
// if there was an error then clear out the buffer before returning
|
|
if (!ok)
|
|
buffer.clear();
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool CPersistentDataTree::writeToFile(const CSString& fileName) const
|
|
{
|
|
// create a new text buffer and call writeToBuffer() to do the work
|
|
CSString buffer;
|
|
bool ok= writeToBuffer(buffer);
|
|
|
|
// in the case of an error display a warning and drop throught to write an empty buffer to the output file
|
|
WARN_IF(!ok,"Failed to build output buffer for persistent data tree to write to file: "+fileName);
|
|
|
|
// write the output file
|
|
return buffer.writeToFile(fileName);
|
|
}
|
|
|
|
bool CPersistentDataTree::writeToPdr(CPersistentDataRecord& pdr) const
|
|
{
|
|
// clear out the pdr before starting
|
|
pdr.clear();
|
|
|
|
// iterate over children recursing...
|
|
bool ok=true;
|
|
for (uint32 i=0;i<_Child->getChildren().size() && ok;++i)
|
|
{
|
|
BOMB_IF(_Child->getChildren()[i]==NULL,"ERROR: NULL pointer in persistent data tree node children",return false);
|
|
ok= _Child->getChildren()[i]->writeToPdr(pdr);
|
|
}
|
|
|
|
// if an error occured during write then clear out the result pdr and return false
|
|
if (!ok)
|
|
pdr.clear();
|
|
|
|
return ok;
|
|
}
|
|
|
|
void CPersistentDataTree::setValue(const TValue& nodeName,const CSString& value)
|
|
{
|
|
// get hold of the named node, creating it if need be
|
|
CPersistentDataTreeNode* node= _Child->getDescendant(nodeName,true);
|
|
DROP_IF(node==NULL,"ERROR: Failed get to handle to node in order to set value: "+nodeName,return);
|
|
|
|
// set the node's value
|
|
node->setValue(value);
|
|
}
|
|
|
|
const CPersistentDataTree::TValue& CPersistentDataTree::getValue(const CSString& nodeName) const
|
|
{
|
|
return _Child->getValue(nodeName);
|
|
}
|
|
|
|
CPersistentDataTreeNode* CPersistentDataTree::getNode(const NLMISC::CSString& nodeName)
|
|
{
|
|
return (_Child==NULL)? NULL: _Child->getDescendant(nodeName,false);
|
|
}
|
|
|
|
bool CPersistentDataTree::operator==(const CPersistentDataTree& other) const
|
|
{
|
|
return *_Child == *other._Child;
|
|
}
|