You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1404 lines
47 KiB

/*==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 <http://www.gnu.org/licenses/>.
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 <stack>
#if HS_BUILD_FOR_MAC
#include <bxwchar.h>
#endif
//////////////////////////////////////////////////////////////////////
//
// 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<std::wstring, std::wstring> element;
// the wstring is the element name
typedef std::map<std::wstring, element> set;
// the wstring is the set name
typedef std::map<std::wstring, set> age;
// the wstring is the age name
typedef std::map<std::wstring, age> ageMap;
protected:
std::wstring fLastError;
std::string fFilename;
XML_Parser fParser;
struct tagInfo
{
std::wstring fTag;
std::map<std::wstring, std::wstring> fAttributes;
};
std::stack<tagInfo> 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<std::wstring, std::wstring> 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 <localizations> 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<LocalizationXMLFile> 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<std::wstring> 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<class mapT>
void pfLocalizationDataMgr::pf3PartMap<mapT>::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<class mapT>
bool pfLocalizationDataMgr::pf3PartMap<mapT>::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<class mapT>
bool pfLocalizationDataMgr::pf3PartMap<mapT>::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<class mapT>
void pfLocalizationDataMgr::pf3PartMap<mapT>::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<class mapT>
mapT &pfLocalizationDataMgr::pf3PartMap<mapT>::operator[](const std::wstring &key)
{
std::wstring age, set, name;
ISplitString(key, age, set, name);
return fData[age][set][name];
}
//// getAgeList() ////////////////////////////////////////////////////
template<class mapT>
std::vector<std::wstring> pfLocalizationDataMgr::pf3PartMap<mapT>::getAgeList()
{
std::vector<std::wstring> retVal;
ThreePartMap::iterator curAge;
for (curAge = fData.begin(); curAge != fData.end(); curAge++)
retVal.push_back(curAge->first);
return retVal;
}
//// getSetList() ////////////////////////////////////////////////////
template<class mapT>
std::vector<std::wstring> pfLocalizationDataMgr::pf3PartMap<mapT>::getSetList(const std::wstring & age)
{
std::vector<std::wstring> retVal;
std::map<std::wstring, std::map<std::wstring, mapT> >::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<class mapT>
std::vector<std::wstring> pfLocalizationDataMgr::pf3PartMap<mapT>::getNameList(const std::wstring & age, const std::wstring & set)
{
std::vector<std::wstring> retVal;
std::map<std::wstring, mapT>::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<std::wstring> pfLocalizationDataMgr::IGetAllLanguageNames()
{
int numLocales = plLocalization::GetNumLocales();
std::vector<std::wstring> 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"<?xml version=\"1.0\" encoding=\"utf-16\"?>\n"; // stores the xml we are going to write to the file (UTF-16 format)
fileData += L"<localizations>\n";
fileData += L"\t<age name=\"" + ageName + L"\">\n";
std::vector<std::wstring> 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<set name=\"" + setNames[curSet] + L"\">\n";
std::vector<std::wstring> elementNames = GetElementList(ageName, setNames[curSet]);
for (int curElement = 0; curElement < elementNames.size(); curElement++)
{
setCode += L"\t\t\t<element name=\"" + elementNames[curElement] + L"\">\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<translation language=\"" + languageName + L"\">";
setCode += fLocalizedElements[key][languageName].ToXML();
setCode += L"</translation>\n";
}
setCode += L"\t\t\t</element>\n";
}
setCode += L"\t\t</set>\n";
if (!setEmpty)
fileData += setCode;
}
fileData += L"\t</age>\n";
fileData += L"</localizations>\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<std::wstring> pfLocalizationDataMgr::GetAgeList()
{
return fLocalizedElements.getAgeList();
}
//// GetSetList //////////////////////////////////////////////////////
std::vector<std::wstring> pfLocalizationDataMgr::GetSetList(const std::wstring & ageName)
{
return fLocalizedElements.getSetList(ageName);
}
//// GetElementList //////////////////////////////////////////////////
std::vector<std::wstring> pfLocalizationDataMgr::GetElementList(const std::wstring & ageName, const std::wstring & setName)
{
return fLocalizedElements.getNameList(ageName, setName);
}
//// GetLanguages ////////////////////////////////////////////////////
std::vector<std::wstring> pfLocalizationDataMgr::GetLanguages(const std::wstring & ageName, const std::wstring & setName, const std::wstring & elementName)
{
std::vector<std::wstring> 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<std::wstring> ageNames = GetAgeList();
std::vector<std::wstring> 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<std::wstring> 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<std::wstring> 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<std::wstring> 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());
}
}
}
}