123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137 |
- // Filename:- genericparser2.cpp
- // leave this at the top for PCH reasons...
- #include "common_headers.h"
- #ifdef _JK2EXE
- #include "../qcommon/qcommon.h"
- #else
- #include "g_headers.h"
- #endif
- //#define _EXE
- #define MAX_TOKEN_SIZE 1024
- static char token[MAX_TOKEN_SIZE];
- static char *GetToken(char **text, bool allowLineBreaks, bool readUntilEOL = false)
- {
- char *pointer = *text;
- int length = 0;
- int c = 0;
- bool foundLineBreak;
- token[0] = 0;
- if (!pointer)
- {
- return token;
- }
- while(1)
- {
- foundLineBreak = false;
- while(1)
- {
- c = *pointer;
- if (c > ' ')
- {
- break;
- }
- if (!c)
- {
- *text = 0;
- return token;
- }
- if (c == '\n')
- {
- foundLineBreak = true;
- }
- pointer++;
- }
- if (foundLineBreak && !allowLineBreaks)
- {
- *text = pointer;
- return token;
- }
- c = *pointer;
- // skip single line comment
- if (c == '/' && pointer[1] == '/')
- {
- pointer += 2;
- while (*pointer && *pointer != '\n')
- {
- pointer++;
- }
- }
- // skip multi line comments
- else if (c == '/' && pointer[1] == '*')
- {
- pointer += 2;
- while (*pointer && (*pointer != '*' || pointer[1] != '/'))
- {
- pointer++;
- }
- if (*pointer)
- {
- pointer += 2;
- }
- }
- else
- { // found the start of a token
- break;
- }
- }
- if (c == '\"')
- { // handle a string
- pointer++;
- while (1)
- {
- c = *pointer++;
- if (c == '\"')
- {
- // token[length++] = c;
- break;
- }
- else if (!c)
- {
- break;
- }
- else if (length < MAX_TOKEN_SIZE)
- {
- token[length++] = c;
- }
- }
- }
- else if (readUntilEOL)
- {
- // absorb all characters until EOL
- while(c != '\n' && c != '\r')
- {
- if (c == '/' && ((*(pointer+1)) == '/' || (*(pointer+1)) == '*'))
- {
- break;
- }
- if (length < MAX_TOKEN_SIZE)
- {
- token[length++] = c;
- }
- pointer++;
- c = *pointer;
- }
- // remove trailing white space
- while(length && token[length-1] < ' ')
- {
- length--;
- }
- }
- else
- {
- while(c > ' ')
- {
- if (length < MAX_TOKEN_SIZE)
- {
- token[length++] = c;
- }
- pointer++;
- c = *pointer;
- }
- }
- if (token[0] == '\"')
- { // remove start quote
- length--;
- memmove(token, token+1, length);
- if (length && token[length-1] == '\"')
- { // remove end quote
- length--;
- }
- }
- if (length >= MAX_TOKEN_SIZE)
- {
- length = 0;
- }
- token[length] = 0;
- *text = (char *)pointer;
- return token;
- }
- CTextPool::CTextPool(int initSize) :
- mNext(0),
- mSize(initSize),
- mUsed(0)
- {
- #ifdef _EXE
- // mPool = (char *)Z_Malloc(mSize, TAG_GP2);
- mPool = (char *)Z_Malloc(mSize, TAG_TEXTPOOL, qtrue);
- #else
- mPool = (char *)trap_Z_Malloc(mSize, TAG_GP2);
- #endif
- }
- CTextPool::~CTextPool(void)
- {
- #ifdef _EXE
- Z_Free(mPool);
- #else
- trap_Z_Free(mPool);
- #endif
- }
- char *CTextPool::AllocText(char *text, bool addNULL, CTextPool **poolPtr)
- {
- int length = strlen(text) + (addNULL ? 1 : 0);
- if (mUsed + length + 1> mSize)
- { // extra 1 to put a null on the end
- if (poolPtr)
- {
- (*poolPtr)->SetNext(new CTextPool(mSize));
- *poolPtr = (*poolPtr)->GetNext();
- return (*poolPtr)->AllocText(text, addNULL);
- }
- return 0;
- }
- strcpy(mPool + mUsed, text);
- mUsed += length;
- mPool[mUsed] = 0;
- return mPool + mUsed - length;
- }
- void CleanTextPool(CTextPool *pool)
- {
- CTextPool *next;
- while(pool)
- {
- next = pool->GetNext();
- delete pool;
- pool = next;
- }
- }
- CGPObject::CGPObject(const char *initName) :
- mName(initName),
- mNext(0),
- mInOrderNext(0),
- mInOrderPrevious(0)
- {
- }
- bool CGPObject::WriteText(CTextPool **textPool, const char *text)
- {
- if (strchr(text, ' ') || !text[0])
- {
- (*textPool)->AllocText("\"", false, textPool);
- (*textPool)->AllocText((char *)text, false, textPool);
- (*textPool)->AllocText("\"", false, textPool);
- }
- else
- {
- (*textPool)->AllocText((char *)text, false, textPool);
- }
- return true;
- }
-
-
-
-
-
- CGPValue::CGPValue(const char *initName, const char *initValue) :
- CGPObject(initName),
- mList(0)
- {
- if (initValue)
- {
- AddValue(initValue);
- }
- }
- CGPValue::~CGPValue(void)
- {
- CGPObject *next;
- while(mList)
- {
- next = mList->GetNext();
- delete mList;
- mList = next;
- }
- }
- CGPValue *CGPValue::Duplicate(CTextPool **textPool)
- {
- CGPValue *newValue;
- CGPObject *iterator;
- char *name;
- if (textPool)
- {
- name = (*textPool)->AllocText((char *)mName, true, textPool);
- }
- else
- {
- name = (char *)mName;
- }
- newValue = new CGPValue(name);
- iterator = mList;
- while(iterator)
- {
- if (textPool)
- {
- name = (*textPool)->AllocText((char *)iterator->GetName(), true, textPool);
- }
- else
- {
- name = (char *)iterator->GetName();
- }
- newValue->AddValue(name);
- iterator = iterator->GetNext();
- }
- return newValue;
- }
- bool CGPValue::IsList(void)
- {
- if (!mList || !mList->GetNext())
- {
- return false;
- }
- return true;
- }
- const char *CGPValue::GetTopValue(void)
- {
- if (mList)
- {
- return mList->GetName();
- }
- return 0;
- }
- void CGPValue::AddValue(const char *newValue, CTextPool **textPool)
- {
- if (textPool)
- {
- newValue = (*textPool)->AllocText((char *)newValue, true, textPool);
- }
- if (mList == 0)
- {
- mList = new CGPObject(newValue);
- mList->SetInOrderNext(mList);
- }
- else
- {
- mList->GetInOrderNext()->SetNext(new CGPObject(newValue));
- mList->SetInOrderNext(mList->GetInOrderNext()->GetNext());
- }
- }
- bool CGPValue::Parse(char **dataPtr, CTextPool **textPool)
- {
- char *token;
- char *value;
- while(1)
- {
- token = GetToken(dataPtr, true, true);
- if (!token[0])
- { // end of data - error!
- return false;
- }
- else if (strcmpi(token, "]") == 0)
- { // ending brace for this list
- break;
- }
- value = (*textPool)->AllocText(token, true, textPool);
- AddValue(value);
- }
- return true;
- }
- bool CGPValue::Write(CTextPool **textPool, int depth)
- {
- int i;
- CGPObject *next;
- if (!mList)
- {
- return true;
- }
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- WriteText(textPool, mName);
- if (!mList->GetNext())
- {
- (*textPool)->AllocText("\t\t", false, textPool);
- mList->WriteText(textPool, mList->GetName());
- (*textPool)->AllocText("\r\n", false, textPool);
- }
- else
- {
- (*textPool)->AllocText("\r\n", false, textPool);
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- (*textPool)->AllocText("[\r\n", false, textPool);
- next = mList;
- while(next)
- {
- for(i=0;i<depth+1;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- mList->WriteText(textPool, next->GetName());
- (*textPool)->AllocText("\r\n", false, textPool);
- next = next->GetNext();
- }
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- (*textPool)->AllocText("]\r\n", false, textPool);
- }
- return true;
- }
- CGPGroup::CGPGroup(const char *initName, CGPGroup *initParent) :
- CGPObject(initName),
- mPairs(0),
- mInOrderPairs(0),
- mCurrentPair(0),
- mSubGroups(0),
- mInOrderSubGroups(0),
- mCurrentSubGroup(0),
- mParent(initParent),
- mWriteable(false)
- {
- }
- CGPGroup::~CGPGroup(void)
- {
- Clean();
- }
- int CGPGroup::GetNumSubGroups(void)
- {
- int count;
- CGPGroup *group;
- count = 0;
- group = mSubGroups;
- while(group)
- {
- count++;
- group = (CGPGroup *)group->GetNext();
- }
- return(count);
- }
- int CGPGroup::GetNumPairs(void)
- {
- int count;
- CGPValue *pair;
- count = 0;
- pair = mPairs;
- while(pair)
- {
- count++;
- pair = (CGPValue *)pair->GetNext();
- }
- return(count);
- }
- void CGPGroup::Clean(void)
- {
- while(mPairs)
- {
- mCurrentPair = (CGPValue *)mPairs->GetNext();
- delete mPairs;
- mPairs = mCurrentPair;
- }
- while(mSubGroups)
- {
- mCurrentSubGroup = (CGPGroup *)mSubGroups->GetNext();
- delete mSubGroups;
- mSubGroups = mCurrentSubGroup;
- }
- mPairs = mInOrderPairs = mCurrentPair = 0;
- mSubGroups = mInOrderSubGroups = mCurrentSubGroup = 0;
- mParent = 0;
- mWriteable = false;
- }
- CGPGroup *CGPGroup::Duplicate(CTextPool **textPool, CGPGroup *initParent)
- {
- CGPGroup *newGroup, *subSub, *newSub;
- CGPValue *newPair, *subPair;
- char *name;
- if (textPool)
- {
- name = (*textPool)->AllocText((char *)mName, true, textPool);
- }
- else
- {
- name = (char *)mName;
- }
- newGroup = new CGPGroup(name);
- subSub = mSubGroups;
- while(subSub)
- {
- newSub = subSub->Duplicate(textPool, newGroup);
- newGroup->AddGroup(newSub);
- subSub = (CGPGroup *)subSub->GetNext();
- }
- subPair = mPairs;
- while(subPair)
- {
- newPair = subPair->Duplicate(textPool);
- newGroup->AddPair(newPair);
- subPair = (CGPValue *)subPair->GetNext();
- }
- return newGroup;
- }
- void CGPGroup::SortObject(CGPObject *object, CGPObject **unsortedList, CGPObject **sortedList,
- CGPObject **lastObject)
- {
- CGPObject *test, *last;
- if (!*unsortedList)
- {
- *unsortedList = *sortedList = object;
- }
- else
- {
- (*lastObject)->SetNext(object);
- test = *sortedList;
- last = 0;
- while(test)
- {
- if (strcmpi(object->GetName(), test->GetName()) < 0)
- {
- break;
- }
- last = test;
- test = test->GetInOrderNext();
- }
- if (test)
- {
- test->SetInOrderPrevious(object);
- object->SetInOrderNext(test);
- }
- if (last)
- {
- last->SetInOrderNext(object);
- object->SetInOrderPrevious(last);
- }
- else
- {
- *sortedList = object;
- }
- }
- *lastObject = object;
- }
- CGPValue *CGPGroup::AddPair(const char *name, const char *value, CTextPool **textPool)
- {
- CGPValue *newPair;
- if (textPool)
- {
- name = (*textPool)->AllocText((char *)name, true, textPool);
- if (value)
- {
- value = (*textPool)->AllocText((char *)value, true, textPool);
- }
- }
- newPair = new CGPValue(name, value);
- AddPair(newPair);
- return newPair;
- }
- void CGPGroup::AddPair(CGPValue *NewPair)
- {
- SortObject(NewPair, (CGPObject **)&mPairs, (CGPObject **)&mInOrderPairs,
- (CGPObject **)&mCurrentPair);
- }
- CGPGroup *CGPGroup::AddGroup(const char *name, CTextPool **textPool)
- {
- CGPGroup *newGroup;
- if (textPool)
- {
- name = (*textPool)->AllocText((char *)name, true, textPool);
- }
- newGroup = new CGPGroup(name);
- AddGroup(newGroup);
- return newGroup;
- }
-
- void CGPGroup::AddGroup(CGPGroup *NewGroup)
- {
- SortObject(NewGroup, (CGPObject **)&mSubGroups, (CGPObject **)&mInOrderSubGroups,
- (CGPObject **)&mCurrentSubGroup);
- }
-
- CGPGroup *CGPGroup::FindSubGroup(const char *name)
- {
- CGPGroup *group;
- group = mSubGroups;
- while(group)
- {
- if(!stricmp(name, group->GetName()))
- {
- return(group);
- }
- group = (CGPGroup *)group->GetNext();
- }
- return(NULL);
- }
- bool CGPGroup::Parse(char **dataPtr, CTextPool **textPool)
- {
- char *token;
- char lastToken[MAX_TOKEN_SIZE];
- CGPGroup *newSubGroup;
- CGPValue *newPair;
- while(1)
- {
- token = GetToken(dataPtr, true);
- if (!token[0])
- { // end of data - error!
- if (mParent)
- {
- return false;
- }
- else
- {
- break;
- }
- }
- else if (strcmpi(token, "}") == 0)
- { // ending brace for this group
- break;
- }
- strcpy(lastToken, token);
- // read ahead to see what we are doing
- token = GetToken(dataPtr, true, true);
- if (strcmpi(token, "{") == 0)
- { // new sub group
- newSubGroup = AddGroup(lastToken, textPool);
- newSubGroup->SetWriteable(mWriteable);
- if (!newSubGroup->Parse(dataPtr, textPool))
- {
- return false;
- }
- }
- else if (strcmpi(token, "[") == 0)
- { // new pair list
- newPair = AddPair(lastToken, 0, textPool);
- if (!newPair->Parse(dataPtr, textPool))
- {
- return false;
- }
- }
- else
- { // new pair
- AddPair(lastToken, token, textPool);
- }
- }
- return true;
- }
- bool CGPGroup::Write(CTextPool **textPool, int depth)
- {
- int i;
- CGPValue *mPair = mPairs;
- CGPGroup *mSubGroup = mSubGroups;
- if (depth >= 0)
- {
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- WriteText(textPool, mName);
- (*textPool)->AllocText("\r\n", false, textPool);
-
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- (*textPool)->AllocText("{\r\n", false, textPool);
- }
- while(mPair)
- {
- mPair->Write(textPool, depth+1);
- mPair = (CGPValue *)mPair->GetNext();
- }
- while(mSubGroup)
- {
- mSubGroup->Write(textPool, depth+1);
- mSubGroup = (CGPGroup *)mSubGroup->GetNext();
- }
- if (depth >= 0)
- {
- for(i=0;i<depth;i++)
- {
- (*textPool)->AllocText("\t", false, textPool);
- }
- (*textPool)->AllocText("}\r\n", false, textPool);
- }
- return true;
- }
- CGPValue *CGPGroup::FindPair(const char *key)
- {
- CGPValue *pair = mPairs;
- while(pair)
- {
- if (strcmpi(pair->GetName(), key) == 0)
- {
- return pair;
- }
- pair = pair->GetNext();
- }
- return 0;
- }
- const char *CGPGroup::FindPairValue(const char *key, const char *defaultVal)
- {
- CGPValue *pair = FindPair(key);
- if (pair)
- {
- return pair->GetTopValue();
- }
- return defaultVal;
- }
- CGenericParser2::CGenericParser2(void) :
- mTextPool(0),
- mWriteable(false)
- {
- }
- CGenericParser2::~CGenericParser2(void)
- {
- Clean();
- }
- bool CGenericParser2::Parse(char **dataPtr, bool cleanFirst, bool writeable)
- {
- CTextPool *topPool;
- #ifdef _XBOX
- // Parsers are temporary structures. They exist mainly at load time.
- extern void Z_SetNewDeleteTemporary(bool bTemp);
- Z_SetNewDeleteTemporary(true);
- #endif
- if (cleanFirst)
- {
- Clean();
- }
- if (!mTextPool)
- {
- mTextPool = new CTextPool;
- }
- SetWriteable(writeable);
- mTopLevel.SetWriteable(writeable);
- topPool = mTextPool;
- bool ret = mTopLevel.Parse(dataPtr, &topPool);
- #ifdef _XBOX
- Z_SetNewDeleteTemporary(false);
- #endif
- return ret;
- }
- void CGenericParser2::Clean(void)
- {
- mTopLevel.Clean();
- CleanTextPool(mTextPool);
- mTextPool = 0;
- }
- bool CGenericParser2::Write(CTextPool *textPool)
- {
- return mTopLevel.Write(&textPool, -1);
- }
- // The following groups of routines are used for a C interface into GP2.
- // C++ users should just use the objects as normally and not call these routines below
- //
- // CGenericParser2 (void *) routines
- TGenericParser2 GP_Parse(char **dataPtr, bool cleanFirst, bool writeable)
- {
- CGenericParser2 *parse;
- parse = new CGenericParser2;
- if (parse->Parse(dataPtr, cleanFirst, writeable))
- {
- return parse;
- }
- delete parse;
- return 0;
- }
- void GP_Clean(TGenericParser2 GP2)
- {
- if (!GP2)
- {
- return;
- }
- ((CGenericParser2 *)GP2)->Clean();
- }
- void GP_Delete(TGenericParser2 *GP2)
- {
- if (!GP2 || !(*GP2))
- {
- return;
- }
- delete ((CGenericParser2 *)(*GP2));
- (*GP2) = 0;
- }
- TGPGroup GP_GetBaseParseGroup(TGenericParser2 GP2)
- {
- if (!GP2)
- {
- return 0;
- }
-
- return ((CGenericParser2 *)GP2)->GetBaseParseGroup();
- }
- // CGPGroup (void *) routines
- const char *GPG_GetName(TGPGroup GPG)
- {
- if (!GPG)
- {
- return "";
- }
- return ((CGPGroup *)GPG)->GetName();
- }
- TGPGroup GPG_GetNext(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetNext();
- }
- TGPGroup GPG_GetInOrderNext(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetInOrderNext();
- }
- TGPGroup GPG_GetInOrderPrevious(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetInOrderPrevious();
- }
- TGPGroup GPG_GetPairs(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetPairs();
- }
- TGPGroup GPG_GetInOrderPairs(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetInOrderPairs();
- }
- TGPGroup GPG_GetSubGroups(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetSubGroups();
- }
- TGPGroup GPG_GetInOrderSubGroups(TGPGroup GPG)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->GetInOrderSubGroups();
- }
- TGPGroup GPG_FindSubGroup(TGPGroup GPG, const char *name)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->FindSubGroup(name);
- }
- TGPValue GPG_FindPair(TGPGroup GPG, const char *key)
- {
- if (!GPG)
- {
- return 0;
- }
- return ((CGPGroup *)GPG)->FindPair(key);
- }
- const char *GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal)
- {
- if (!GPG)
- {
- return defaultVal;
- }
- return ((CGPGroup *)GPG)->FindPairValue(key, defaultVal);
- }
- // CGPValue (void *) routines
- const char *GPV_GetName(TGPValue GPV)
- {
- if (!GPV)
- {
- return "";
- }
- return ((CGPValue *)GPV)->GetName();
- }
- TGPValue GPV_GetNext(TGPValue GPV)
- {
- if (!GPV)
- {
- return 0;
- }
- return ((CGPValue *)GPV)->GetNext();
- }
- TGPValue GPV_GetInOrderNext(TGPValue GPV)
- {
- if (!GPV)
- {
- return 0;
- }
- return ((CGPValue *)GPV)->GetInOrderNext();
- }
- TGPValue GPV_GetInOrderPrevious(TGPValue GPV)
- {
- if (!GPV)
- {
- return 0;
- }
- return ((CGPValue *)GPV)->GetInOrderPrevious();
- }
- bool GPV_IsList(TGPValue GPV)
- {
- if (!GPV)
- {
- return 0;
- }
- return ((CGPValue *)GPV)->IsList();
- }
- const char *GPV_GetTopValue(TGPValue GPV)
- {
- if (!GPV)
- {
- return "";
- }
- return ((CGPValue *)GPV)->GetTopValue();
- }
- TGPValue GPV_GetList(TGPValue GPV)
- {
- if (!GPV)
- {
- return 0;
- }
- return ((CGPValue *)GPV)->GetList();
- }
- //////////////////// eof /////////////////////
|