/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. 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 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////// // // pfLocalizationDataMgr - singleton class for managing the // localization XML data tree // ////////////////////////////////////////////////////////////////////// #include "hsTypes.h" #include "hsUtils.h" #include "../plResMgr/plLocalization.h" #include "../plFile/hsFiles.h" #include "../plFile/plEncryptedStream.h" #include "../plStatusLog/plStatusLog.h" #include "pfLocalizedString.h" #include "pfLocalizationMgr.h" #include "pfLocalizationDataMgr.h" #include "expat.h" #include #if HS_BUILD_FOR_MAC #include #endif // Add ..\..\..\..\..\StaticSDKs\XPlatform\expat-1.95.7\StaticLibs\Win32 to your // lib include path if you include this file. #pragma comment(lib, "libexpatwMT.lib") ////////////////////////////////////////////////////////////////////// // // LocalizationXMLFile - a basic class for storing all the // localization data grabbed from a single XML // file // ////////////////////////////////////////////////////////////////////// class LocalizationXMLFile { public: //replace friend by static because of conflits with subtitleXMLFile static void XMLCALL StartTag(void *userData, const XML_Char *element, const XML_Char **attributes); static void XMLCALL EndTag(void *userData, const XML_Char *element); static void XMLCALL HandleData(void *userData, const XML_Char *data, int stringLength); friend class LocalizationDatabase; // first wstring is language, second is data typedef std::map element; // the wstring is the element name typedef std::map set; // the wstring is the set name typedef std::map age; // the wstring is the age name typedef std::map ageMap; protected: std::wstring fLastError; std::string fFilename; XML_Parser fParser; struct tagInfo { std::wstring fTag; std::map fAttributes; }; std::stack fTagStack; int fSkipDepth; // if we need to skip a block, this is the depth we need to skip to bool fIgnoreContents; // are we ignoring the contents between tags? std::wstring fCurrentAge, fCurrentSet, fCurrentElement, fCurrentTranslation; ageMap fData; void IHandleLocalizationsTag(const tagInfo & parentTag, const tagInfo & thisTag); void IHandleAgeTag(const tagInfo & parentTag, const tagInfo & thisTag); void IHandleSetTag(const tagInfo & parentTag, const tagInfo & thisTag); void IHandleElementTag(const tagInfo & parentTag, const tagInfo & thisTag); void IHandleTranslationTag(const tagInfo & parentTag, const tagInfo & thisTag); public: LocalizationXMLFile() : fLastError(L""), fFilename("") {} bool Parse(const std::string & fileName); // returns false on failure void AddError(const std::wstring & errorText); std::wstring GetLastError() {return fLastError;} }; // A few small helper structs // I am setting these up so the header file can use this data without having to put // the LocalizationXMLFile class into the header file struct LocElementInfo { LocalizationXMLFile::element fElement; }; struct LocSetInfo { LocalizationXMLFile::set fSet; }; struct LocAgeInfo { LocalizationXMLFile::age fAge; }; ////////////////////////////////////////////////////////////////////// // Memory functions ////////////////////////////////////////////////////////////////////// static void * XMLCALL XmlMalloc (size_t size) { return ALLOC(size); } static void * XMLCALL XmlRealloc (void * ptr, size_t size) { return REALLOC(ptr, size); } static void XMLCALL XmlFree (void * ptr) { FREE(ptr); } XML_Memory_Handling_Suite gHeapAllocator = { XmlMalloc, XmlRealloc, XmlFree }; ////////////////////////////////////////////////////////////////////// //// XML Parsing functions /////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// //metmet remove static void XMLCALL LocalizationXMLFile::StartTag(void *userData, const XML_Char *element, const XML_Char **attributes) { #if !HS_BUILD_FOR_MAC std::wstring wElement = element; #else // jfim wchar_t buf[2048], buf2[2048]; BX_Char16ToWchar(element, buf); std::wstring wElement = buf; // jfim: element; #endif LocalizationXMLFile *file = (LocalizationXMLFile*)userData; std::map wAttributes; for (int i = 0; attributes[i]; i += 2) #if !HS_BUILD_FOR_MAC wAttributes[attributes[i]] = attributes[i+1]; #else { // jfim BX_Char16ToWchar(attributes[i], buf); BX_Char16ToWchar(attributes[i+1], buf2); wAttributes[buf] = buf2; } #endif LocalizationXMLFile::tagInfo parentTag; if (!file->fTagStack.empty()) parentTag = file->fTagStack.top(); LocalizationXMLFile::tagInfo newTag; newTag.fTag = wElement; newTag.fAttributes = wAttributes; file->fTagStack.push(newTag); if (file->fSkipDepth != -1) // we're currently skipping return; // now we handle this tag if (wElement == L"localizations") file->IHandleLocalizationsTag(parentTag, newTag); else if (wElement == L"age") file->IHandleAgeTag(parentTag, newTag); else if (wElement == L"set") file->IHandleSetTag(parentTag, newTag); else if (wElement == L"element") file->IHandleElementTag(parentTag, newTag); else if (wElement == L"translation") file->IHandleTranslationTag(parentTag, newTag); else file->AddError(L"Unknown tag " + wElement + L" found"); } //metmet remove static and include the function inside LocalizationXMLFile void XMLCALL LocalizationXMLFile::EndTag(void *userData, const XML_Char *element) { #if !HS_BUILD_FOR_MAC std::wstring wElement = element; #else // jfim wchar_t buf[2048], buf2[2048]; BX_Char16ToWchar(element, buf); std::wstring wElement = buf; // jfim: element; #endif LocalizationXMLFile *file = (LocalizationXMLFile*)userData; if (file->fSkipDepth != -1) // we're currently skipping { // check to see if we are done with the block we wanted skipped if (file->fTagStack.size() == file->fSkipDepth) file->fSkipDepth = -1; // we're done skipping } if (wElement == L"age") // we left the age block file->fCurrentAge = L""; else if (wElement == L"set") // we left the set block file->fCurrentSet = L""; else if (wElement == L"element") // we left the element block file->fCurrentElement = L""; else if (wElement == L"translation") // we left the translation block { file->fIgnoreContents = true; file->fCurrentTranslation = L""; } file->fTagStack.pop(); } //metmet remove static and include the function inside LocalizationXMLFile void XMLCALL LocalizationXMLFile::HandleData(void *userData, const XML_Char *data, int stringLength) { LocalizationXMLFile *file = (LocalizationXMLFile*)userData; if (file->fIgnoreContents) return; // we're ignoring data, so just return if (file->fSkipDepth != -1) // we're currently skipping return; // This gets all data between tags, including indentation and newlines // so we'll have to ignore data when we aren't expecting it (not in a translation tag) std::wstring wData = L""; for (int i = 0; i < stringLength; i++) wData += data[i]; // we must be in a translation tag since that's the only tag that doesn't ignore the contents file->fData[file->fCurrentAge][file->fCurrentSet][file->fCurrentElement][file->fCurrentTranslation] += wData; } ////////////////////////////////////////////////////////////////////// //// LocalizationXMLFile Functions /////////////////////////////////// ////////////////////////////////////////////////////////////////////// #define FILEBUFFERSIZE 8192 //// IHandleSubtitlesTag() /////////////////////////////////////////// void LocalizationXMLFile::IHandleLocalizationsTag(const LocalizationXMLFile::tagInfo & parentTag, const LocalizationXMLFile::tagInfo & thisTag) { if (parentTag.fTag != L"") // we only allow tags at root level { AddError(L"localizations tag only allowed at root level"); return; } } //// IHandleAgeTag() ///////////////////////////////////////////////// void LocalizationXMLFile::IHandleAgeTag(const LocalizationXMLFile::tagInfo & parentTag, const LocalizationXMLFile::tagInfo & thisTag) { // it has to be inside the subtitles tag if (parentTag.fTag != L"localizations") { AddError(L"age tag can only be directly inside a localizations tag"); return; } // we have to have a name attribute if (thisTag.fAttributes.find(L"name") == thisTag.fAttributes.end()) { AddError(L"age tag is missing the name attribute"); return; } fCurrentAge = thisTag.fAttributes.find(L"name")->second; } //// IHandleSetTag() ///////////////////////////////////////////////// void LocalizationXMLFile::IHandleSetTag(const LocalizationXMLFile::tagInfo & parentTag, const LocalizationXMLFile::tagInfo & thisTag) { // it has to be inside the age tag if (parentTag.fTag != L"age") { AddError(L"set tag can only be directly inside a age tag"); return; } // we have to have a name attribute if (thisTag.fAttributes.find(L"name") == thisTag.fAttributes.end()) { AddError(L"set tag is missing the name attribute"); return; } fCurrentSet = thisTag.fAttributes.find(L"name")->second; } //// IHandleElementTag() ///////////////////////////////////////////// void LocalizationXMLFile::IHandleElementTag(const LocalizationXMLFile::tagInfo & parentTag, const LocalizationXMLFile::tagInfo & thisTag) { // it has to be inside the element tag if (parentTag.fTag != L"set") { AddError(L"element tag can only be directly inside a set tag"); return; } // we have to have a name attribute if (thisTag.fAttributes.find(L"name") == thisTag.fAttributes.end()) { AddError(L"element tag is missing the name attribute"); return; } fCurrentElement = thisTag.fAttributes.find(L"name")->second; } //// IHandleTranslationTag() ///////////////////////////////////////// void LocalizationXMLFile::IHandleTranslationTag(const LocalizationXMLFile::tagInfo & parentTag, const LocalizationXMLFile::tagInfo & thisTag) { // it has to be inside the element tag if (parentTag.fTag != L"element") { AddError(L"translation tag can only be directly inside a element tag"); return; } // we have to have a language attribute if (thisTag.fAttributes.find(L"language") == thisTag.fAttributes.end()) { AddError(L"translation tag is missing the language attribute"); return; } fIgnoreContents = false; // we now want contents between tags fCurrentTranslation = thisTag.fAttributes.find(L"language")->second; } //// Parse() ///////////////////////////////////////////////////////// bool LocalizationXMLFile::Parse(const std::string & fileName) { fFilename = fileName; fLastError = L""; while (!fTagStack.empty()) fTagStack.pop(); fCurrentAge = L""; fCurrentSet = L""; fCurrentElement = L""; fCurrentTranslation = L""; fIgnoreContents = true; fSkipDepth = -1; char Buff[FILEBUFFERSIZE]; fParser = XML_ParserCreate_MM(NULL, &gHeapAllocator, NULL); if (!fParser) { fLastError = L"ERROR: Couldn't allocate memory for parser"; return false; } XML_SetElementHandler(fParser, StartTag, EndTag); XML_SetCharacterDataHandler(fParser, HandleData); XML_SetUserData(fParser, (void*)this); hsStream *xmlStream = plEncryptedStream::OpenEncryptedFile(fileName.c_str(), false); if (!xmlStream) { wchar_t *wFilename = hsStringToWString(fileName.c_str()); fLastError += L"ERROR: Can't open file stream for "; fLastError += wFilename; fLastError += L"\n"; delete [] wFilename; return false; } for (;;) { int done; size_t len; len = xmlStream->Read(FILEBUFFERSIZE, Buff); done = xmlStream->AtEnd(); if (XML_Parse(fParser, Buff, (int)len, done) == XML_STATUS_ERROR) { wchar_t lineNumber[256]; _itow(XML_GetCurrentLineNumber(fParser), lineNumber, 10); fLastError += L"ERROR: Parse error at line "; fLastError += lineNumber; fLastError += L": "; #if !HS_BUILD_FOR_MAC fLastError += XML_ErrorString(XML_GetErrorCode(fParser)); #else // jfim wchar_t buf[2048]; BX_Utf8ToWchar(XML_ErrorString(XML_GetErrorCode(fParser)), buf); fLastError += buf; #endif fLastError += L"\n"; XML_ParserFree(fParser); fParser = nil; xmlStream->Close(); delete xmlStream; return false; } if (fLastError != L"") // some error occurred in the parser { XML_ParserFree(fParser); fParser = nil; xmlStream->Close(); delete xmlStream; return false; } if (done) break; } XML_ParserFree(fParser); fParser = nil; xmlStream->Close(); delete xmlStream; return true; } //// AddError() ////////////////////////////////////////////////////// void LocalizationXMLFile::AddError(const std::wstring & errorText) { wchar_t lineNumber[256]; _itow(XML_GetCurrentLineNumber(fParser), lineNumber, 10); fLastError += L"ERROR (line "; fLastError += lineNumber; fLastError += L"): " + errorText + L"\n"; fSkipDepth = fTagStack.size(); // skip this block return; } ////////////////////////////////////////////////////////////////////// // // LocalizationDatabase - a basic class for storing all the subtitle // data grabbed from a XML directory (handles // the merging and final validation of the data) // ////////////////////////////////////////////////////////////////////// class LocalizationDatabase { protected: std::string fDirectory; // the directory we're supposed to parse std::wstring fErrorString; // total sum of all errors encountered (also has warnings and status messages) std::vector fFiles; // the various XML files in that directory LocalizationXMLFile::ageMap fData; LocalizationXMLFile::element IMergeElementData(LocalizationXMLFile::element firstElement, LocalizationXMLFile::element secondElement, const std::wstring & fileName, const std::wstring & path); LocalizationXMLFile::set IMergeSetData(LocalizationXMLFile::set firstSet, LocalizationXMLFile::set secondSet, const std::wstring & fileName, const std::wstring & path); LocalizationXMLFile::age IMergeAgeData(LocalizationXMLFile::age firstAge, LocalizationXMLFile::age secondAge, const std::wstring & fileName, const std::wstring & path); void IMergeData(); // merge all localization data in the files void IVerifyElement(const std::wstring &ageName, const std::wstring &setName, LocalizationXMLFile::set::iterator& curElement); void IVerifySet(const std::wstring &ageName, const std::wstring &setName); void IVerifyAge(const std::wstring &ageName); void IVerifyData(); // verify the localization data once it has been merged in public: LocalizationDatabase() {} void Parse(const std::string & directory); void ResetOutput() {fErrorString = L"";} std::wstring GetOutput() {return fErrorString;} LocalizationXMLFile::ageMap GetData() {return fData;} }; ////////////////////////////////////////////////////////////////////// //// LocalizationDatabase Functions ////////////////////////////////// ////////////////////////////////////////////////////////////////////// //// IMergeElementData /////////////////////////////////////////////// LocalizationXMLFile::element LocalizationDatabase::IMergeElementData(LocalizationXMLFile::element firstElement, LocalizationXMLFile::element secondElement, const std::wstring & fileName, const std::wstring & path) { // copy the data over, alerting the user to any duplicate translations LocalizationXMLFile::element::iterator curTranslation; for (curTranslation = secondElement.begin(); curTranslation != secondElement.end(); curTranslation++) { if (firstElement.find(curTranslation->first) != firstElement.end()) fErrorString += L"Duplicate " + curTranslation->first + L" translation for " + path + L" found in file " + fileName + L" Ignoring second translation\n"; else firstElement[curTranslation->first] = curTranslation->second; } return firstElement; } //// IMergeSetData /////////////////////////////////////////////////// LocalizationXMLFile::set LocalizationDatabase::IMergeSetData(LocalizationXMLFile::set firstSet, LocalizationXMLFile::set secondSet, const std::wstring & fileName, const std::wstring & path) { // Merge all the elements LocalizationXMLFile::set::iterator curElement; for (curElement = secondSet.begin(); curElement != secondSet.end(); curElement++) { // if the element doesn't exist in the current set, add it if (firstSet.find(curElement->first) == firstSet.end()) firstSet[curElement->first] = curElement->second; else // merge the element in firstSet[curElement->first] = IMergeElementData(firstSet[curElement->first], curElement->second, fileName, path + L"." + curElement->first); } return firstSet; } //// IMergeAgeData /////////////////////////////////////////////////// LocalizationXMLFile::age LocalizationDatabase::IMergeAgeData(LocalizationXMLFile::age firstAge, LocalizationXMLFile::age secondAge, const std::wstring & fileName, const std::wstring & path) { // Merge all the sets LocalizationXMLFile::age::iterator curSet; for (curSet = secondAge.begin(); curSet != secondAge.end(); curSet++) { // if the set doesn't exist in the current age, just add it if (firstAge.find(curSet->first) == firstAge.end()) firstAge[curSet->first] = curSet->second; else // merge the data in firstAge[curSet->first] = IMergeSetData(firstAge[curSet->first], curSet->second, fileName, path + L"." + curSet->first); } return firstAge; } //// IMergeData() //////////////////////////////////////////////////// void LocalizationDatabase::IMergeData() { for (int i = 0; i < fFiles.size(); i++) { std::wstring wFilename; wchar_t *buff = hsStringToWString(fFiles[i].fFilename.c_str()); wFilename = buff; delete [] buff; LocalizationXMLFile::ageMap fileData = fFiles[i].fData; LocalizationXMLFile::ageMap::iterator curAge; for (curAge = fileData.begin(); curAge != fileData.end(); curAge++) { // if the age doesn't exist in the current merged database, just add it with no more checking if (fData.find(curAge->first) == fData.end()) fData[curAge->first] = curAge->second; else // otherwise, merge the data in fData[curAge->first] = IMergeAgeData(fData[curAge->first], curAge->second, wFilename, curAge->first); } } } //// IVerifyElement() //////////////////////////////////////////////// void LocalizationDatabase::IVerifyElement(const std::wstring &ageName, const std::wstring &setName, LocalizationXMLFile::set::iterator& curElement) { std::vector languageNames; std::wstring defaultLanguage; int numLocales = plLocalization::GetNumLocales(); for (int curLocale = 0; curLocale <= numLocales; curLocale++) { char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); languageNames.push_back(wName); delete [] wName; } defaultLanguage = languageNames[0]; std::wstring elementName = curElement->first; LocalizationXMLFile::element& theElement = curElement->second; LocalizationXMLFile::element::iterator curTranslation; for (curTranslation = theElement.begin(); curTranslation != theElement.end(); curTranslation++) { // Make sure this language exists! bool languageExists = false; for (int i = 0; i < languageNames.size(); i++) { if (languageNames[i] == curTranslation->first) { languageExists = true; break; } } if (!languageExists) { fErrorString += L"ERROR: The language " + curTranslation->first + L" used by " + ageName + L"." + setName + L"."; fErrorString += elementName + L" is not supported, discarding translation\n"; #if !HS_BUILD_FOR_MAC curTranslation = theElement.erase(curTranslation); #else // jfim: I thought std::map::erase returned void? theElement.erase(curTranslation); #endif curTranslation--; // because this will be incremented on the next run through the loop continue; } } LocalizationXMLFile::set& theSet = fData[ageName][setName]; if (theElement.find(defaultLanguage) == theElement.end()) { fErrorString += L"ERROR: Default language " + defaultLanguage + L" is missing from the translations in element "; fErrorString += ageName + L"." + setName + L"." + elementName + L", deleting element\n"; #if !HS_BUILD_FOR_MAC curElement = theSet.erase(curElement); #else // jfim: I thought std::map::erase returned void? theSet.erase(curElement); #endif curElement--; return; } for (int i = 1; i < languageNames.size(); i++) { if (theElement.find(languageNames[i]) == theElement.end()) { fErrorString += L"WARNING: Language " + languageNames[i] + L" is missing from the translations in element "; fErrorString += ageName + L"." + setName + L"." + elementName + L", you'll want to get translations for that!\n"; } } } //// IVerifySet() //////////////////////////////////////////////////// void LocalizationDatabase::IVerifySet(const std::wstring &ageName, const std::wstring &setName) { LocalizationXMLFile::set& theSet = fData[ageName][setName]; LocalizationXMLFile::set::iterator curElement; for (curElement = theSet.begin(); curElement != theSet.end(); curElement++) IVerifyElement(ageName, setName, curElement); } //// IVerifyAge() //////////////////////////////////////////////////// void LocalizationDatabase::IVerifyAge(const std::wstring &ageName) { LocalizationXMLFile::age& theAge = fData[ageName]; LocalizationXMLFile::age::iterator curSet; for (curSet = theAge.begin(); curSet != theAge.end(); curSet++) IVerifySet(ageName, curSet->first); } //// IVerifyData() /////////////////////////////////////////////////// void LocalizationDatabase::IVerifyData() { LocalizationXMLFile::ageMap::iterator curAge; for (curAge = fData.begin(); curAge != fData.end(); curAge++) IVerifyAge(curAge->first); } //// Parse() ///////////////////////////////////////////////////////// void LocalizationDatabase::Parse(const std::string & directory) { fDirectory = directory; fFiles.clear(); fErrorString = L""; char filename[255]; hsFolderIterator xmlFolder((directory+PATH_SEPARATOR_STR).c_str()); while(xmlFolder.NextFileSuffix(".loc")) { xmlFolder.GetPathAndName(filename); wchar_t *buff = hsStringToWString(filename); std::wstring wFilename = buff; delete [] buff; LocalizationXMLFile newFile; bool retVal = newFile.Parse(filename); if (!retVal) fErrorString += L"Errors in file " + wFilename + L":\n" + newFile.GetLastError() + L"\n"; fFiles.push_back(newFile); fErrorString += L"File " + wFilename + L" parsed and added to database\n"; } IMergeData(); IVerifyData(); return; } ////////////////////////////////////////////////////////////////////// //// pf3PartMap Functions //////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// //// ISplitString() ////////////////////////////////////////////////// template void pfLocalizationDataMgr::pf3PartMap::ISplitString(std::wstring key, std::wstring &age, std::wstring &set, std::wstring &name) { std::wstring::size_type periodLoc = key.find(L"."); age = key.substr(0, periodLoc); if (periodLoc >= key.length()) return; // don't get set or name if there isn't any period key = key.substr(periodLoc + 1, key.length()); periodLoc = key.find(L"."); set = key.substr(0, periodLoc); if (periodLoc >= key.length()) return; // don't get name if there isn't another period name = key.substr(periodLoc + 1, key.length()); } //// exists() //////////////////////////////////////////////////////// template bool pfLocalizationDataMgr::pf3PartMap::exists(const std::wstring & key) { std::wstring age, set, name; ISplitString(key, age, set, name); if (age == L"" || set == L"" || name == L"") // if any are missing, it's invalid, so we don't have it return false; // now check individually if (fData.find(age) == fData.end()) // age doesn't exist return false; if (fData[age].find(set) == fData[age].end()) // set doesn't exist return false; if (fData[age][set].find(name) == fData[age][set].end()) // name doesn't exist return false; // we passed all the tests, return true! return true; } //// setExists() ///////////////////////////////////////////////////// template bool pfLocalizationDataMgr::pf3PartMap::setExists(const std::wstring & key) { std::wstring age, set, name; ISplitString(key, age, set, name); if (age == L"" || set == L"") // if any are missing, it's invalid, so we don't have it (ignoring name) return false; // now check individually if (fData.find(age) == fData.end()) // age doesn't exist return false; if (fData[age].find(set) == fData[age].end()) // set doesn't exist return false; // we passed all the tests, return true! return true; } //// erase() ///////////////////////////////////////////////////////// template void pfLocalizationDataMgr::pf3PartMap::erase(const std::wstring & key) { std::wstring age, set, name; ISplitString(key, age, set, name); if (age == L"" || set == L"" || name == L"") // if any are missing, it's invalid, so we don't delete it return; // now check individually if (fData.find(age) == fData.end()) // age doesn't exist return; if (fData[age].find(set) == fData[age].end()) // set doesn't exist return; if (fData[age][set].find(name) == fData[age][set].end()) // name doesn't exist return; // ok, so now we want to nuke it! fData[age][set].erase(name); if (fData[age][set].size() == 0) // is the set now empty? fData[age].erase(set); // nuke it! if (fData[age].size() == 0) // is the age now empty? fData.erase(age); // nuke it! } //// operator[]() //////////////////////////////////////////////////// template mapT &pfLocalizationDataMgr::pf3PartMap::operator[](const std::wstring &key) { std::wstring age, set, name; ISplitString(key, age, set, name); return fData[age][set][name]; } //// getAgeList() //////////////////////////////////////////////////// template std::vector pfLocalizationDataMgr::pf3PartMap::getAgeList() { std::vector retVal; ThreePartMap::iterator curAge; for (curAge = fData.begin(); curAge != fData.end(); curAge++) retVal.push_back(curAge->first); return retVal; } //// getSetList() //////////////////////////////////////////////////// template std::vector pfLocalizationDataMgr::pf3PartMap::getSetList(const std::wstring & age) { std::vector retVal; std::map >::iterator curSet; if (fData.find(age) == fData.end()) return retVal; // return an empty list, the age doesn't exist for (curSet = fData[age].begin(); curSet != fData[age].end(); curSet++) retVal.push_back(curSet->first); return retVal; } //// getNameList() /////////////////////////////////////////////////// template std::vector pfLocalizationDataMgr::pf3PartMap::getNameList(const std::wstring & age, const std::wstring & set) { std::vector retVal; std::map::iterator curName; if (fData.find(age) == fData.end()) return retVal; // return an empty list, the age doesn't exist if (fData[age].find(set) == fData[age].end()) return retVal; // return an empty list, the set doesn't exist for (curName = fData[age][set].begin(); curName != fData[age][set].end(); curName++) retVal.push_back(curName->first); return retVal; } ////////////////////////////////////////////////////////////////////// //// pfLocalizationDataMgr Functions ///////////////////////////////// ////////////////////////////////////////////////////////////////////// pfLocalizationDataMgr *pfLocalizationDataMgr::fInstance = nil; plStatusLog *pfLocalizationDataMgr::fLog = nil; // output logfile //// Constructor/Destructor ////////////////////////////////////////// pfLocalizationDataMgr::pfLocalizationDataMgr(const std::string & path) { hsAssert(!fInstance, "Tried to create the localization data manager more than once!"); fInstance = this; fDataPath = path; fDatabase = nil; } pfLocalizationDataMgr::~pfLocalizationDataMgr() { fInstance = nil; if (fDatabase) { delete fDatabase; fDatabase = nil; } } //// ICreateLocalizedElement ///////////////////////////////////////// pfLocalizationDataMgr::localizedElement pfLocalizationDataMgr::ICreateLocalizedElement() { int numLocales = plLocalization::GetNumLocales(); pfLocalizationDataMgr::localizedElement retVal; for (int curLocale = 0; curLocale <= numLocales; curLocale++) { char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); retVal[wName] = L""; delete [] wName; } return retVal; } //// IGetCurrentLanguageName ///////////////////////////////////////// std::wstring pfLocalizationDataMgr::IGetCurrentLanguageName() { std::wstring retVal; char *name = plLocalization::GetLanguageName(plLocalization::GetLanguage()); wchar_t *wName = hsStringToWString(name); retVal = wName; delete [] wName; return retVal; } //// IGetAllLanguageNames //////////////////////////////////////////// std::vector pfLocalizationDataMgr::IGetAllLanguageNames() { int numLocales = plLocalization::GetNumLocales(); std::vector retVal; for (int curLocale = 0; curLocale <= numLocales; curLocale++) { char *name = plLocalization::GetLanguageName((plLocalization::Language)curLocale); wchar_t *wName = hsStringToWString(name); retVal.push_back(wName); delete [] wName; } return retVal; } //// IConvertSubtitle //////////////////////////////////////////////// void pfLocalizationDataMgr::IConvertElement(LocElementInfo *elementInfo, const std::wstring & curPath) { pfLocalizationDataMgr::localizedElement newElement; Int16 numArgs = -1; LocalizationXMLFile::element::iterator curTranslation; for (curTranslation = elementInfo->fElement.begin(); curTranslation != elementInfo->fElement.end(); curTranslation++) { newElement[curTranslation->first].FromXML(curTranslation->second); UInt16 argCount = newElement[curTranslation->first].GetArgumentCount(); if (numArgs == -1) // just started numArgs = argCount; else if (argCount != numArgs) { std::wstring errorStr = L"WARNING: Argument number mismatch in element " + curPath; char* cErrorStr = hsWStringToString(errorStr.c_str()); fLog->AddLine(cErrorStr); delete [] cErrorStr; } } fLocalizedElements[curPath] = newElement; } //// IConvertSet ///////////////////////////////////////////////////// void pfLocalizationDataMgr::IConvertSet(LocSetInfo *setInfo, const std::wstring & curPath) { LocalizationXMLFile::set::iterator curElement; for (curElement = setInfo->fSet.begin(); curElement != setInfo->fSet.end(); curElement++) { LocElementInfo elementInfo; elementInfo.fElement = curElement->second; IConvertElement(&elementInfo, curPath + L"." + curElement->first); } } //// IConvertAge ///////////////////////////////////////////////////// void pfLocalizationDataMgr::IConvertAge(LocAgeInfo *ageInfo, const std::wstring & curPath) { LocalizationXMLFile::age::iterator curSet; for (curSet = ageInfo->fAge.begin(); curSet != ageInfo->fAge.end(); curSet++) { LocSetInfo setInfo; setInfo.fSet = curSet->second; IConvertSet(&setInfo, curPath + L"." + curSet->first); } } //// IConvertToByteStream //////////////////////////////////////////// char *pfLocalizationDataMgr::IConvertToByteStream(const std::wstring & data, UInt32 &len) { len = data.length() * 2 + 2; // each wchar_t is two chars and add two bytes for the header char *retVal = TRACKED_NEW char[len]; // we don't add an extra byte for the 0 because the parser doesn't need it char lowbyte = 0, highbyte = 0; retVal[0] = (char)0xFF; // insert FFFE for little-endian UTF-16 (big-endian would be FEFF) retVal[1] = (char)0xFE; int curByteStreamPos = 2; for (int curLoc = 0; curLoc < data.length(); curLoc++) { wchar_t curChar = data[curLoc]; lowbyte = (char)(curChar & 0x00FF); highbyte = (char)((curChar & 0xFF00) >> 8); // since the data is AABBCCDD, we need to put in in our byte stream as BBAADDCC // (so it kinda looks backward because we're storing this as little-endian) retVal[curByteStreamPos + 1] = highbyte; retVal[curByteStreamPos] = lowbyte; curByteStreamPos += 2; } return retVal; } //// IWriteText ////////////////////////////////////////////////////// void pfLocalizationDataMgr::IWriteText(const std::string & filename, const std::wstring & ageName, const std::wstring & languageName) { bool weWroteData = false; // did we actually write any data of consequence? bool setEmpty = true; // we will try to pretty print it all so it's easy to read for the devs std::wstring fileData = L"\n"; // stores the xml we are going to write to the file (UTF-16 format) fileData += L"\n"; fileData += L"\t\n"; std::vector setNames = GetSetList(ageName); for (int curSet = 0; curSet < setNames.size(); curSet++) { setEmpty = true; // so far, this set is empty std::wstring setCode = L""; setCode += L"\t\t\n"; std::vector elementNames = GetElementList(ageName, setNames[curSet]); for (int curElement = 0; curElement < elementNames.size(); curElement++) { setCode += L"\t\t\t\n"; std::wstring key = ageName + L"." + setNames[curSet] + L"." + elementNames[curElement]; if (fLocalizedElements[key].find(languageName) != fLocalizedElements[key].end()) { std::wstring key = ageName + L"." + setNames[curSet] + L"." + elementNames[curElement]; weWroteData = true; setEmpty = false; setCode += L"\t\t\t\t"; setCode += fLocalizedElements[key][languageName].ToXML(); setCode += L"\n"; } setCode += L"\t\t\t\n"; } setCode += L"\t\t\n"; if (!setEmpty) fileData += setCode; } fileData += L"\t\n"; fileData += L"\n"; if (weWroteData) { // now spit the results out to the file UInt32 numBytes; char *byteStream = IConvertToByteStream(fileData, numBytes); hsStream *xmlStream = plEncryptedStream::OpenEncryptedFileWrite(filename.c_str()); xmlStream->Write(numBytes, byteStream); xmlStream->Close(); delete xmlStream; delete [] byteStream; } } //// Initialize ////////////////////////////////////////////////////// void pfLocalizationDataMgr::Initialize(const std::string & path) { if (fInstance) return; fInstance = TRACKED_NEW pfLocalizationDataMgr(path); fLog = plStatusLogMgr::GetInstance().CreateStatusLog(30, "LocalizationDataMgr.log", plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kTimestamp); fInstance->SetupData(); } //// Shutdown //////////////////////////////////////////////////////// void pfLocalizationDataMgr::Shutdown() { if ( fLog != nil ) { delete fLog; fLog = nil; } if (fInstance) { delete fInstance; fInstance = nil; } } //// SetupData /////////////////////////////////////////////////////// void pfLocalizationDataMgr::SetupData() { if (fDatabase) delete fDatabase; fDatabase = TRACKED_NEW LocalizationDatabase(); fDatabase->Parse(fDataPath); char *temp = hsWStringToString(fDatabase->GetOutput().c_str()); fLog->AddLine(temp); delete [] temp; fLog->AddLine("File reading complete, converting to native data format"); // and now we read all the data out of the database and convert it to our native formats // transfer subtitle data LocalizationXMLFile::ageMap data = fDatabase->GetData(); LocalizationXMLFile::ageMap::iterator curAge; for (curAge = data.begin(); curAge != data.end(); curAge++) { LocAgeInfo ageInfo; ageInfo.fAge = curAge->second; IConvertAge(&ageInfo, curAge->first); } OutputTreeToLog(); } //// GetElement ////////////////////////////////////////////////////// pfLocalizedString pfLocalizationDataMgr::GetElement(const std::wstring & name) { pfLocalizedString retVal; // if this returns before we initialize it, it will be empty, indicating failure if (!fLocalizedElements.exists(name)) // does the requested element exist? return retVal; // nope, so return failure std::wstring languageName = IGetCurrentLanguageName(); if (fLocalizedElements[name].find(languageName) == fLocalizedElements[name].end()) // current language isn't specified { languageName = L"English"; // force to english if (fLocalizedElements[name].find(languageName) == fLocalizedElements[name].end()) // make sure english exists return retVal; // language doesn't exist } retVal = fLocalizedElements[name][languageName]; return retVal; } //// GetSpecificElement ////////////////////////////////////////////// pfLocalizedString pfLocalizationDataMgr::GetSpecificElement(const std::wstring & name, const std::wstring & language) { pfLocalizedString retVal; // if this returns before we initialize it, it will have an ID of 0, indicating failure if (!fLocalizedElements.exists(name)) // does the requested subtitle exist? return retVal; // nope, so return failure if (fLocalizedElements[name].find(language) == fLocalizedElements[name].end()) return retVal; // language doesn't exist retVal = fLocalizedElements[name][language]; return retVal; } //// GetAgeList ////////////////////////////////////////////////////// std::vector pfLocalizationDataMgr::GetAgeList() { return fLocalizedElements.getAgeList(); } //// GetSetList ////////////////////////////////////////////////////// std::vector pfLocalizationDataMgr::GetSetList(const std::wstring & ageName) { return fLocalizedElements.getSetList(ageName); } //// GetElementList ////////////////////////////////////////////////// std::vector pfLocalizationDataMgr::GetElementList(const std::wstring & ageName, const std::wstring & setName) { return fLocalizedElements.getNameList(ageName, setName); } //// GetLanguages //////////////////////////////////////////////////// std::vector pfLocalizationDataMgr::GetLanguages(const std::wstring & ageName, const std::wstring & setName, const std::wstring & elementName) { std::vector retVal; std::wstring key = ageName + L"." + setName + L"." + elementName; if (fLocalizedElements.exists(key)) { // age, set, and element exists localizedElement elem = fLocalizedElements[key]; localizedElement::iterator curLanguage; for (curLanguage = elem.begin(); curLanguage != elem.end(); curLanguage++) { std::wstring language = curLanguage->first; if (language != L"") // somehow blank language names sneak in... so don't return them retVal.push_back(curLanguage->first); } } return retVal; } //// GetElementXMLData /////////////////////////////////////////////// std::wstring pfLocalizationDataMgr::GetElementXMLData(const std::wstring & name, const std::wstring & languageName) { std::wstring retVal = L""; if (fLocalizedElements.exists(name)) { if (fLocalizedElements[name].find(languageName) != fLocalizedElements[name].end()) retVal = fLocalizedElements[name][languageName].ToXML(); } return retVal; } //// GetElementPlainTextData ///////////////////////////////////////// std::wstring pfLocalizationDataMgr::GetElementPlainTextData(const std::wstring & name, const std::wstring & languageName) { std::wstring retVal = L""; if (fLocalizedElements.exists(name)) { if (fLocalizedElements[name].find(languageName) != fLocalizedElements[name].end()) retVal = (std::wstring)fLocalizedElements[name][languageName]; } return retVal; } //// SetElementXMLData /////////////////////////////////////////////// bool pfLocalizationDataMgr::SetElementXMLData(const std::wstring & name, const std::wstring & languageName, const std::wstring & xmlData) { if (!fLocalizedElements.exists(name)) return false; // doesn't exist fLocalizedElements[name][languageName].FromXML(xmlData); return true; } //// SetElementPlainTextData ///////////////////////////////////////// bool pfLocalizationDataMgr::SetElementPlainTextData(const std::wstring & name, const std::wstring & languageName, const std::wstring & plainText) { if (!fLocalizedElements.exists(name)) return false; // doesn't exist fLocalizedElements[name][languageName] = plainText; return true; } //// AddLocalization ///////////////////////////////////////////////// bool pfLocalizationDataMgr::AddLocalization(const std::wstring & name, const std::wstring & newLanguage) { if (!fLocalizedElements.exists(name)) return false; // doesn't exist // copy the english over so it can be localized fLocalizedElements[name][newLanguage] = fLocalizedElements[name][L"English"]; return true; } //// AddElement ////////////////////////////////////////////////////// bool pfLocalizationDataMgr::AddElement(const std::wstring & name) { if (fLocalizedElements.exists(name)) return false; // already exists pfLocalizedString newElement; fLocalizedElements[name][L"English"] = newElement; return true; } //// DeleteLocalization ////////////////////////////////////////////// bool pfLocalizationDataMgr::DeleteLocalization(const std::wstring & name, const std::wstring & languageName) { if (!fLocalizedElements.exists(name)) return false; // doesn't exist if (fLocalizedElements[name].find(languageName) == fLocalizedElements[name].end()) return false; // doesn't exist fLocalizedElements[name].erase(languageName); return true; } //// DeleteElement /////////////////////////////////////////////////// bool pfLocalizationDataMgr::DeleteElement(const std::wstring & name) { if (!fLocalizedElements.exists(name)) return false; // doesn't exist // delete it! fLocalizedElements.erase(name); return true; } //// WriteDatabaseToDisk ///////////////////////////////////////////// void pfLocalizationDataMgr::WriteDatabaseToDisk(const std::string & path) { // first, write the styles and panel settings to styles.sub std::vector ageNames = GetAgeList(); std::vector languageNames = IGetAllLanguageNames(); for (int curAge = 0; curAge < ageNames.size(); curAge++) { for (int curLanguage = 0; curLanguage < languageNames.size(); curLanguage++) { std::string cAgeName, cLanguageName; char *temp = hsWStringToString(ageNames[curAge].c_str()); cAgeName = temp; delete [] temp; temp = hsWStringToString(languageNames[curLanguage].c_str()); cLanguageName = temp; delete [] temp; IWriteText(path + "/" + cAgeName + cLanguageName + ".loc", ageNames[curAge], languageNames[curLanguage]); } } } //// OutputTreeToLog ///////////////////////////////////////////////// void pfLocalizationDataMgr::OutputTreeToLog() { std::vector ages = GetAgeList(); fLog->AddLine("\n"); fLog->AddLine("Localization tree:\n"); for (int i = 0; i < ages.size(); i++) { char *ageName = hsWStringToString(ages[i].c_str()); std::string temp = ageName; delete [] ageName; temp = "\t" + temp + "\n"; fLog->AddLine(temp.c_str()); std::vector sets = GetSetList(ages[i]); for (int j = 0; j < sets.size(); j++) { char *setName = hsWStringToString(sets[j].c_str()); std::string temp = setName; delete [] setName; temp = "\t\t" + temp + "\n"; fLog->AddLine(temp.c_str()); std::vector names = GetElementList(ages[i], sets[j]); for (int k = 0; k < names.size(); k++) { char *elemName = hsWStringToString(names[k].c_str()); std::string temp = elemName; delete [] elemName; temp = "\t\t\t" + temp + "\n"; fLog->AddLine(temp.c_str()); } } } }