BsStringTable.cpp 8.9 KB

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