/******************************************************************************** Copyright (C) 2001-2012 Hugh Bailey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ********************************************************************************/ #include "XT.h" /*======================================================== XElement =========================================================*/ XElement::~XElement() { DWORD i; for(i=0; istrData; return def; } int XElement::GetInt(CTSTR lpName, int def) const { assert(lpName); XDataItem *item = GetDataItem(lpName); if(item) { CTSTR lpValue = item->strData; if( (*LPWORD(lpValue) == 'x0') || (*LPWORD(lpValue) == 'X0') ) { return tstring_base_to_uint(lpValue+2, NULL, 16); } else if(scmpi(lpValue, TEXT("true")) == 0) return 1; else if(scmpi(lpValue, TEXT("false")) == 0) return 0; else return tstring_base_to_uint(lpValue, NULL, 0); } return def; } float XElement::GetFloat(CTSTR lpName, float def) const { assert(lpName); XDataItem *item = GetDataItem(lpName); if(item) return (float)tstof(item->strData); return def; } void XElement::GetStringList(CTSTR lpName, StringList &stringList) const { assert(lpName); stringList.Clear(); for(DWORD i=0; iIsData()) continue; XDataItem *item = static_cast(SubItems[i]); if(item->strName.CompareI(lpName)) stringList << item->strData; } } void XElement::GetIntList(CTSTR lpName, List &IntList) const { assert(lpName); IntList.Clear(); for(DWORD i=0; iIsData()) continue; XDataItem *item = static_cast(SubItems[i]); if(item->strName.CompareI(lpName)) { CTSTR lpValue = item->strData; if( (*LPWORD(lpValue) == 'x0') || (*LPWORD(lpValue) == 'X0') ) { IntList << tstring_base_to_uint(lpValue+2, NULL, 16); } else if(scmpi(lpValue, TEXT("true")) == 0) IntList << 1; else if(scmpi(lpValue, TEXT("false")) == 0) IntList << 0; else IntList << tstring_base_to_uint(lpValue, NULL, 0); } } } void XElement::SetString(CTSTR lpName, CTSTR lpString) { assert(lpName); if(!lpString) lpString = TEXT(""); XDataItem *item = GetDataItem(lpName); if(item) { item->strData = lpString; return; } SubItems << new XDataItem(lpName, lpString); } void XElement::SetInt(CTSTR lpName, int number) { assert(lpName); String intStr = IntString(number); XDataItem *item = GetDataItem(lpName); if(item) { item->strData = intStr; return; } SubItems << new XDataItem(lpName, intStr); } void XElement::SetFloat(CTSTR lpName, float number) { assert(lpName); String floatStr = FloatString(number); XDataItem *item = GetDataItem(lpName); if(item) { item->strData = floatStr; return; } SubItems << new XDataItem(lpName, floatStr); } void XElement::SetHex(CTSTR lpName, DWORD hex) { assert(lpName); String hexStr; hexStr << TEXT("0x") << IntString(hex, 16); XDataItem *item = GetDataItem(lpName); if(item) { item->strData = hexStr; return; } SubItems << new XDataItem(lpName, hexStr); } void XElement::SetStringList(CTSTR lpName, List &stringList) { RemoveItem(lpName); AddStringList(lpName, stringList); } void XElement::SetStringList(CTSTR lpName, StringList &stringList) { RemoveItem(lpName); AddStringList(lpName, stringList); } void XElement::SetIntList(CTSTR lpName, List &IntList) { RemoveItem(lpName); AddIntList(lpName, IntList); } void XElement::SetFloatList(CTSTR lpName, List &FloatList) { RemoveItem(lpName); AddFloatList(lpName, FloatList); } void XElement::SetHexList(CTSTR lpName, List &HexList) { RemoveItem(lpName); AddHexList(lpName, HexList); } void XElement::AddString(CTSTR lpName, TSTR lpString) { assert(lpName); if(!lpString) lpString = TEXT(""); SubItems << new XDataItem(lpName, lpString); } void XElement::AddInt(CTSTR lpName, int number) { assert(lpName); SubItems << new XDataItem(lpName, IntString(number)); } void XElement::AddFloat(CTSTR lpName, float number) { assert(lpName); SubItems << new XDataItem(lpName, FloatString(number)); } void XElement::AddHex(CTSTR lpName, DWORD hex) { assert(lpName); String hexStr; hexStr << TEXT("0x") << IntString(hex, 16); SubItems << new XDataItem(lpName, hexStr); } void XElement::AddStringList(CTSTR lpName, List &stringList) { assert(lpName); for(DWORD i=0; i &IntList) { assert(lpName); for(DWORD i=0; i &FloatList) { assert(lpName); for(DWORD i=0; i &HexList) { assert(lpName); for(DWORD i=0; iIsData()) continue; if(static_cast(SubItems[i])->strName.CompareI(lpName)) { delete SubItems[i]; SubItems.Remove(i--); } } } else { for(DWORD i=0; iIsData()) continue; delete SubItems[i]; SubItems.Remove(i--); } } } //--------------------------- XElement* XElement::GetElement(CTSTR lpName) const { if (!lpName) return NULL; for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(element->strName.CompareI(lpName)) return element; } return NULL; } XElement* XElement::GetElementByID(DWORD elementID) const { int id=0; for(DWORD i=0; iIsElement()) continue; if(id == elementID) return static_cast(SubItems[i]); ++id; } return NULL; } XElement* XElement::GetElementByItem(CTSTR lpName, CTSTR lpItemName, CTSTR lpItemValue) const { assert(lpItemName); assert(lpItemValue); if(lpName) { for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(element->strName.CompareI(lpName)) { if(scmpi(element->GetString(lpItemName), lpItemValue) == 0) return element; } } } else { for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(scmpi(element->GetString(lpItemName), lpItemValue) == 0) return element; } } return NULL; } XElement* XElement::CreateElement(CTSTR lpName) { assert(lpName); XElement *newElement = new XElement(file, this, lpName); SubItems << newElement; return newElement; } XElement* XElement::NewElementCopy(XElement* element, bool bSelfAsRoot) { XElement *newElement = NULL; if (bSelfAsRoot) { newElement = this; } else { newElement = new XElement(this->file, this, element->strName); } for(DWORD i=0; i < element->SubItems.Num(); i++) { XBaseItem *sub = element->SubItems[i]; if (sub->GetType() == XConfig_Data) { XDataItem *subdata = static_cast(sub); newElement->SubItems << new XDataItem(subdata->strName, subdata->strData); } else { newElement->SubItems << newElement->NewElementCopy( static_cast(sub), false ); } } return newElement; } XElement* XElement::CopyElement(XElement* element, CTSTR lpNewName) { assert(lpNewName); assert(element); XElement *newElement = new XElement(file, this, lpNewName); newElement->NewElementCopy(element, true); SubItems << newElement; return newElement; } XElement* XElement::InsertElement(UINT pos, CTSTR lpName) { assert(lpName); XElement *newElement = new XElement(file, this, lpName); if(pos > SubItems.Num()) pos = SubItems.Num(); SubItems.Insert(pos, newElement); return newElement; } void XElement::GetElementList(CTSTR lpName, List &Elements) const { Elements.Clear(); for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(!lpName || element->strName.CompareI(lpName)) Elements << element; } } void XElement::RemoveElement(XElement *element) { assert(element); for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(element->strName.CompareI(lpName)) { delete element; SubItems.Remove(i--); } } } XDataItem* XElement::GetDataItem(CTSTR lpName) const { for(DWORD i=0; iIsData()) continue; XDataItem *data = static_cast(SubItems[i]); if(data->strName.CompareI(lpName)) return data; } return NULL; } XDataItem* XElement::GetDataItemByID(DWORD itemID) const { int id=0; for(DWORD i=0; iIsData()) continue; if(id == itemID) return static_cast(SubItems[i]); ++id; } return NULL; } XBaseItem* XElement::GetBaseItem(CTSTR lpName) const { for(DWORD i=0; istrName.CompareI(lpName)) return SubItems[i]; } return NULL; } XBaseItem* XElement::GetBaseItemByID(DWORD itemID) const { if(itemID >= SubItems.Num()) return NULL; return SubItems[itemID]; } DWORD XElement::NumElements(CTSTR lpName) { int num=0; for(DWORD i=0; iIsElement()) continue; XElement *element = static_cast(SubItems[i]); if(!lpName || element->strName.CompareI(lpName)) ++num; } return num; } DWORD XElement::NumDataItems(CTSTR lpName) { int num=0; for(DWORD i=0; iIsData()) continue; XDataItem *data = static_cast(SubItems[i]); if(!lpName || data->strName.CompareI(lpName)) ++num; } return num; } DWORD XElement::NumBaseItems(CTSTR lpName) { int num=0; for(DWORD i=0; i(SubItems[i]); if(!lpName || data->strName.CompareI(lpName)) ++num; } return num; } void XElement::MoveUp() { UINT lastElement = INVALID; for(UINT i=0; iSubItems.Num(); i++) { XBaseItem *baseItem = parent->SubItems[i]; if(baseItem->GetType() == XConfig_Element) { if(baseItem == this) { if(lastElement != INVALID) parent->SubItems.SwapValues(lastElement, i); break; } else lastElement = i; } } } void XElement::MoveDown() { UINT lastElement = INVALID; for(int i=int(parent->SubItems.Num()-1); i>=0; i--) { XBaseItem *baseItem = parent->SubItems[(UINT)i]; if(baseItem->GetType() == XConfig_Element) { if(baseItem == this) { if(lastElement != INVALID) parent->SubItems.SwapValues(lastElement, (UINT)i); break; } else lastElement = (UINT)i; } } } void XElement::MoveToTop() { XElement *thisItem = this; parent->SubItems.RemoveItem(thisItem); parent->SubItems.Insert(0, thisItem); } void XElement::MoveToBottom() { XElement *thisItem = this; parent->SubItems.RemoveItem(thisItem); parent->SubItems.Add(thisItem); } bool XElement::Import(CTSTR lpFile) { XFile importFile; if(!importFile.Open(lpFile, XFILE_READ, XFILE_OPENEXISTING)) return false; String strFileData; importFile.ReadFileToString(strFileData); TSTR lpFileData = strFileData; file->ReadFileData2(this, 0, lpFileData, false); return true; } bool XElement::Export(CTSTR lpFile) { XFile exportFile; if(!exportFile.Open(lpFile, XFILE_WRITE, XFILE_CREATEALWAYS)) return false; file->WriteFileItem(exportFile, 0, this); return true; } /*======================================================== XConfig =========================================================*/ String XConfig::ConvertToTextString(String &string) { String stringOut = string; stringOut.FindReplace(TEXT("\\"), TEXT("\\\\")); stringOut.FindReplace(TEXT("\r"), TEXT("\\r")); stringOut.FindReplace(TEXT("\n"), TEXT("\\n")); stringOut.FindReplace(TEXT("\""), TEXT("\\\"")); stringOut.FindReplace(TEXT("\t"), TEXT("\\t")); return String() << TEXT("\"") << stringOut << TEXT("\""); } String XConfig::ProcessString(TSTR &lpTemp) { TSTR lpStart = lpTemp; BOOL bFoundEnd = FALSE; BOOL bPreviousWasEscaped = FALSE; while(*++lpTemp) { if (*lpTemp == '\\' && lpTemp[-1] == '\\') { bPreviousWasEscaped = TRUE; continue; } if(*lpTemp == '"' && (bPreviousWasEscaped || lpTemp[-1] != '\\')) { bFoundEnd = TRUE; break; } bPreviousWasEscaped = FALSE; } if(!bFoundEnd) return String(); ++lpTemp; TCHAR backupChar = *lpTemp; *lpTemp = 0; String string = lpStart; *lpTemp = backupChar; if (string.Length() == 2) return String(); String stringOut = string.Mid(1, string.Length()-1); if (stringOut.IsEmpty()) return String(); TSTR lpStringOut = stringOut; while(*lpStringOut != 0 && (lpStringOut = schr(lpStringOut, '\\')) != 0) { switch(lpStringOut[1]) { case 0: *lpStringOut = 0; break; case '"': *lpStringOut = '"'; scpy(lpStringOut+1, lpStringOut+2); break; case 't': *lpStringOut = '\t'; scpy(lpStringOut+1, lpStringOut+2); break; case 'r': *lpStringOut = '\r'; scpy(lpStringOut+1, lpStringOut+2); break; case 'n': *lpStringOut = '\n'; scpy(lpStringOut+1, lpStringOut+2); break; case '/': *lpStringOut = '/'; scpy(lpStringOut+1, lpStringOut+2); break; case '\\': scpy(lpStringOut+1, lpStringOut+2); break; } lpStringOut++; } stringOut.SetLength(slen(stringOut)); return stringOut; } bool XConfig::ReadFileData(XElement *curElement, int level, TSTR &lpTemp) { return false; } static inline bool GetNextLine(TSTR &lpTemp, bool isJSON) { while (*lpTemp) { if (isJSON) { if (*lpTemp == ',' || *lpTemp == '}') return true; } else if (*lpTemp == '\n') return true; lpTemp++; } return false; } bool XConfig::ReadFileData2(XElement *curElement, int level, TSTR &lpTemp, bool isJSON) { while(*lpTemp) { if(*lpTemp == '}') return level != 0; if(*lpTemp == '{') //unnamed object, usually only happens at the start of the file, ignore { ++lpTemp; if(!ReadFileData2(curElement, level+1, lpTemp, true)) return false; } else if(*lpTemp != ' ' && *lpTemp != L' ' && *lpTemp != '\t' && *lpTemp != '\r' && *lpTemp != '\n' && *lpTemp != ',') { String strName; if(*lpTemp == '"') strName = ProcessString(lpTemp); else { TSTR lpDataStart = lpTemp; lpTemp = schr(lpTemp, ':'); if(!lpTemp) return false; *lpTemp = 0; strName = lpDataStart; *lpTemp = ':'; strName.KillSpaces(); } //--------------------------- lpTemp = schr(lpTemp, ':'); if(!lpTemp) return false; ++lpTemp; while( *lpTemp == ' ' || *lpTemp == L' ' || *lpTemp == '\t' ) { ++lpTemp; } //--------------------------- if(*lpTemp == '{') //element { ++lpTemp; XElement *newElement = curElement->CreateElement(strName); if (!ReadFileData2(newElement, level + 1, lpTemp, isJSON)) return false; } else //item { String data; if(*lpTemp == '"') data = ProcessString(lpTemp); else { TSTR lpDataStart = lpTemp; if (!GetNextLine(lpTemp, isJSON)) return false; if(lpTemp[-1] == '\r') --lpTemp; if(lpTemp != lpDataStart) { TCHAR oldChar = *lpTemp; *lpTemp = 0; data = lpDataStart; *lpTemp = oldChar; data.KillSpaces(); } } if (!GetNextLine(lpTemp, isJSON) && curElement != RootElement) return false; curElement->SubItems << new XDataItem(strName, data); } } // A ++lpTemp above can step off the end of the string causing // the condition on while to go a bit crazy. // Making sure we preserve the end of string. if (*lpTemp != 0) { ++lpTemp; } } return (curElement == RootElement); } void XConfig::WriteFileItem(XFile &file, int indent, XBaseItem *baseItem) { int j; if(baseItem->IsData()) { XDataItem *item = static_cast(baseItem); String strItem; for(j=0; jstrName.IsValid() && ( item->strName[0] == ' ' || item->strName[0] == '\t' || item->strName[0] == '{' || item->strName[item->strName.Length()-1] == ' ' || item->strName[item->strName.Length()-1] == '\t' || schr(item->strName, '\n') || schr(item->strName, '"') || schr(item->strName, ':') )) { strItem << ConvertToTextString(item->strName); } else strItem << item->strName; strItem << TEXT(" : "); if( item->strData.IsValid() && ( item->strData[0] == ' ' || item->strData[0] == '\t' || item->strData[0] == '{' || item->strData[item->strData.Length()-1] == ' ' || item->strData[item->strData.Length()-1] == '\t' || schr(item->strData, '\n') || schr(item->strData, '"') || schr(item->strData, ':') )) { strItem << ConvertToTextString(item->strData); } else strItem << item->strData; strItem << TEXT("\r\n"); file.WriteAsUTF8(strItem); } else if(baseItem->IsElement()) { XElement *element = static_cast(baseItem); String strElement; for(j=0; jstrName.IsValid() && ( element->strName[0] == ' ' || element->strName[0] == '\t' || element->strName[0] == '{' || element->strName[element->strName.Length()-1] == ' ' || element->strName[element->strName.Length()-1] == '\t' || schr(element->strName, '\n') || schr(element->strName, '"') || schr(element->strName, ':') )) { strElement << ConvertToTextString(element->strName); } else strElement << element->strName; strElement << TEXT(" : {\r\n"); file.WriteAsUTF8(strElement); strElement.Clear(); WriteFileData(file, indent+1, element); for(j=0; jSubItems.Num(); i++) { XBaseItem *baseItem = curElement->SubItems[i]; WriteFileItem(file, indent, baseItem); } } // Basically the same as Open (and in fact Open could/should call ParseString to do its thing) // But ParseString allows chunks of JSON type strings to be parse into the XConfig structure. bool XConfig::ParseString(const String& config) { String safe_copy = config; TSTR lpTemp = safe_copy; RootElement = new XElement(this, NULL, TEXT("Root")); if(!ReadFileData2(RootElement, 0, lpTemp, true)) { for(DWORD i=0; iSubItems.Num(); i++) delete RootElement->SubItems[i]; CrashError(TEXT("Error parsing X string '%s'"), config.Array()); Close(false); } return true; } bool XConfig::Open(CTSTR lpFile) { if(RootElement) { if(strFileName.CompareI(lpFile)) return true; Close(); } //------------------------------------- XFile file; if(!file.Open(lpFile, XFILE_READ, XFILE_OPENALWAYS)) return false; RootElement = new XElement(this, NULL, TEXT("Root")); strFileName = lpFile; DWORD dwFileSize = (DWORD)file.GetFileSize(); LPSTR lpFileDataUTF8 = (LPSTR)Allocate(dwFileSize+1); zero(lpFileDataUTF8, dwFileSize+1); file.Read(lpFileDataUTF8, dwFileSize); TSTR lpFileData = utf8_createTstr(lpFileDataUTF8); Free(lpFileDataUTF8); //------------------------------------- // remove comments /*TSTR lpComment, lpEndComment; while(lpComment = sstr(lpFileData, TEXT("/*"))) {*/ //lpEndComment = sstr(lpFileData, TEXT("*/")); /*assert(lpEndComment); assert(lpComment < lpEndComment); if(!lpEndComment || (lpComment > lpEndComment)) { file.Close(); Close(false); Free(lpFileData); CrashError(TEXT("Error parsing X file '%s'"), strFileName.Array()); } mcpy(lpComment, lpEndComment+3, slen(lpEndComment+3)+1); }*/ //------------------------------------- TSTR lpTemp = lpFileData; if(!ReadFileData2(RootElement, 0, lpTemp, false)) { for(DWORD i=0; iSubItems.Num(); i++) delete RootElement->SubItems[i]; CrashError(TEXT("Error parsing X file '%s'"), strFileName.Array()); Free(lpFileData); Close(false); file.Close(); } Free(lpFileData); file.Close(); return true; } void XConfig::Close(bool bSave) { if(bSave) Save(); delete RootElement; RootElement = NULL; strFileName.Clear(); } void XConfig::Save() { if(RootElement) { XFile file; file.Open(strFileName, XFILE_WRITE, XFILE_CREATEALWAYS); WriteFileData(file, 0, RootElement); file.Close(); } }