CmStringTable.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #include "CmStringTable.h"
  2. #include "CmException.h"
  3. namespace CamelotFramework
  4. {
  5. const Language StringTable::DEFAULT_LANGUAGE = Language::EnglishUS;
  6. LocalizedStringData::LocalizedStringData()
  7. :commonData(nullptr), parameterOffsets(nullptr), numParameters(0)
  8. {
  9. }
  10. LocalizedStringData::~LocalizedStringData()
  11. {
  12. if(parameterOffsets != nullptr)
  13. cm_deleteN(parameterOffsets, numParameters);
  14. }
  15. WString LocalizedStringData::concatenateString(WString* parameters, UINT32 numParameterValues) const
  16. {
  17. WStringStream fullString;
  18. UINT32 prevIdx = 0;
  19. // A safeguard in case translated strings have different number of parameters
  20. UINT32 actualNumParameters = std::min(numParameterValues, numParameters);
  21. if(parameters != nullptr)
  22. {
  23. for(UINT32 i = 0; i < actualNumParameters; i++)
  24. {
  25. fullString<<string.substr(prevIdx, parameterOffsets[i].location - prevIdx);
  26. fullString<<parameters[parameterOffsets[i].paramIdx];
  27. prevIdx = parameterOffsets[i].location;
  28. }
  29. }
  30. fullString<<string.substr(prevIdx, string.size() - prevIdx);
  31. return fullString.str();
  32. }
  33. void LocalizedStringData::updateString(const WString& _string)
  34. {
  35. if(parameterOffsets != nullptr)
  36. cm_deleteN(parameterOffsets, numParameters);
  37. Vector<ParamOffset>::type paramOffsets;
  38. INT32 lastBracket = -1;
  39. WStringStream bracketChars;
  40. WStringStream cleanString;
  41. bool escaped = false;
  42. UINT32 numRemovedChars = 0;
  43. for(UINT32 i = 0; i < (UINT32)_string.size(); i++)
  44. {
  45. if(_string[i] == '\\' && !escaped)
  46. {
  47. numRemovedChars++;
  48. escaped = true;
  49. continue;
  50. }
  51. if(lastBracket == -1)
  52. {
  53. // If current char is non-escaped opening bracket start parameter definition
  54. if(_string[i] == '{' && !escaped)
  55. lastBracket = i;
  56. else
  57. cleanString<<_string[i];
  58. }
  59. else
  60. {
  61. if(isdigit(_string[i]))
  62. bracketChars<<_string[i];
  63. else
  64. {
  65. // If current char is non-escaped closing bracket end parameter definition
  66. UINT32 numParamChars = (UINT32)bracketChars.tellp();
  67. if(_string[i] == '}' && numParamChars > 0 && !escaped)
  68. {
  69. numRemovedChars += numParamChars + 2; // +2 for open and closed brackets
  70. UINT32 paramIdx = parseUnsignedInt(bracketChars.str());
  71. paramOffsets.push_back(ParamOffset(paramIdx, i + 1 - numRemovedChars));
  72. }
  73. else
  74. {
  75. // Last bracket wasn't really a parameter
  76. for(UINT32 j = lastBracket; j <= i; j++)
  77. cleanString<<_string[j];
  78. }
  79. lastBracket = -1;
  80. bracketChars.clear();
  81. }
  82. }
  83. escaped = false;
  84. }
  85. string = cleanString.str();
  86. numParameters = (UINT32)paramOffsets.size();
  87. // Try to find out of order param offsets and fix them
  88. std::sort(begin(paramOffsets), end(paramOffsets),
  89. [&] (const ParamOffset& a, const ParamOffset& b) { return a.paramIdx < b.paramIdx; } );
  90. if(paramOffsets.size() > 0)
  91. {
  92. UINT32 sequentialIdx = 0;
  93. UINT32 lastParamIdx = paramOffsets[0].paramIdx;
  94. for(UINT32 i = 0; i < numParameters; i++)
  95. {
  96. if(paramOffsets[i].paramIdx == lastParamIdx)
  97. {
  98. paramOffsets[i].paramIdx = sequentialIdx;
  99. continue;
  100. }
  101. lastParamIdx = paramOffsets[i].paramIdx;
  102. sequentialIdx++;
  103. paramOffsets[i].paramIdx = sequentialIdx;
  104. }
  105. }
  106. // Re-sort based on location since we find that more useful at runtime
  107. std::sort(begin(paramOffsets), end(paramOffsets),
  108. [&] (const ParamOffset& a, const ParamOffset& b) { return a.location < b.location; } );
  109. parameterOffsets = cm_newN<ParamOffset>(numParameters);
  110. for(UINT32 i = 0; i < numParameters; i++)
  111. parameterOffsets[i] = paramOffsets[i];
  112. }
  113. StringTable::StringTable()
  114. :mActiveLanguageData(nullptr), mDefaultLanguageData(nullptr), mAllLanguages(nullptr)
  115. {
  116. mAllLanguages = cm_newN<LanguageData>((UINT32)Language::Count);
  117. mDefaultLanguageData = &(mAllLanguages[(UINT32)DEFAULT_LANGUAGE]);
  118. mActiveLanguageData = mDefaultLanguageData;
  119. mActiveLanguage = DEFAULT_LANGUAGE;
  120. }
  121. StringTable::~StringTable()
  122. {
  123. for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
  124. {
  125. for(auto& iter : mAllLanguages[i].strings)
  126. cm_delete(iter.second);
  127. }
  128. cm_deleteN(mAllLanguages, (UINT32)Language::Count);
  129. for(auto& common : mCommonData)
  130. cm_delete(common.second);
  131. }
  132. void StringTable::setActiveLanguage(Language language)
  133. {
  134. if(language == mActiveLanguage)
  135. return;
  136. mActiveLanguageData = &(mAllLanguages[(UINT32)language]);
  137. mActiveLanguage = language;
  138. notifyAllStringsChanged();
  139. }
  140. void StringTable::setString(const WString& identifier, Language language, const WString& string)
  141. {
  142. LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]);
  143. auto iterFind = curLanguage->strings.find(identifier);
  144. LocalizedStringData* stringData;
  145. if(iterFind == curLanguage->strings.end())
  146. {
  147. auto iterFindCommon = mCommonData.find(identifier);
  148. LocalizedStringData::Common* common = nullptr;
  149. if(iterFindCommon == mCommonData.end())
  150. {
  151. common = cm_new<LocalizedStringData::Common>();
  152. common->identifier = identifier;
  153. mCommonData[identifier] = common;
  154. }
  155. else
  156. common = iterFindCommon->second;
  157. stringData = cm_new<LocalizedStringData>();
  158. curLanguage->strings[identifier] = stringData;
  159. stringData->commonData = common;
  160. }
  161. else
  162. {
  163. stringData = iterFind->second;
  164. }
  165. stringData->updateString(string);
  166. if(mActiveLanguage == language)
  167. {
  168. if(!stringData->commonData->onStringDataModified.empty())
  169. stringData->commonData->onStringDataModified();
  170. }
  171. }
  172. void StringTable::removeString(const WString& identifier)
  173. {
  174. // Order of operations is very important here, in case a string that is in use
  175. // is removed. In that case we want the string to be marked as modified and it should
  176. // call getStringData which will generate a new entry for the string.
  177. LocalizedStringData* stringData = nullptr;
  178. for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
  179. {
  180. auto findIter = mAllLanguages[i].strings.find(identifier);
  181. if(findIter != mAllLanguages[i].strings.end())
  182. {
  183. if(mActiveLanguage == (Language)i)
  184. stringData = findIter->second;
  185. else
  186. cm_delete(findIter->second);
  187. mAllLanguages[i].strings.erase(findIter);
  188. }
  189. }
  190. auto findIterCommon = mCommonData.find(identifier);
  191. LocalizedStringData::Common* common = nullptr;
  192. if(findIterCommon != mCommonData.end())
  193. {
  194. common = findIterCommon->second;
  195. mCommonData.erase(findIterCommon);
  196. if(!common->onStringDataModified.empty())
  197. common->onStringDataModified();
  198. }
  199. if(stringData != nullptr)
  200. cm_delete(stringData);
  201. if(common != nullptr)
  202. cm_delete(common);
  203. }
  204. LocalizedStringData& StringTable::getStringData(const WString& identifier, bool insertIfNonExisting)
  205. {
  206. return getStringData(identifier, mActiveLanguage, insertIfNonExisting);
  207. }
  208. LocalizedStringData& StringTable::getStringData(const WString& identifier, Language language, bool insertIfNonExisting)
  209. {
  210. LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]);
  211. auto iterFind = curLanguage->strings.find(identifier);
  212. if(iterFind != curLanguage->strings.end())
  213. return *iterFind->second;
  214. auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
  215. if(defaultIterFind != mDefaultLanguageData->strings.end())
  216. return *defaultIterFind->second;
  217. if(insertIfNonExisting)
  218. {
  219. setString(identifier, DEFAULT_LANGUAGE, identifier);
  220. auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
  221. if(defaultIterFind != mDefaultLanguageData->strings.end())
  222. return *defaultIterFind->second;
  223. }
  224. CM_EXCEPT(InvalidParametersException, "There is no string data for the provided identifier.");
  225. }
  226. void StringTable::notifyAllStringsChanged()
  227. {
  228. for(auto& iter : mCommonData)
  229. {
  230. if(!iter.second->onStringDataModified.empty())
  231. iter.second->onStringDataModified();
  232. }
  233. }
  234. }