BsStringTable.cpp 8.5 KB

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