BsStringFormat.h 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. namespace BansheeEngine
  2. {
  3. /**
  4. * @brief Helper class used for string formatting operations.
  5. */
  6. class StringFormat
  7. {
  8. private:
  9. /**
  10. * @brief Data structure used during string formatting. It holds
  11. * information about parameter identifiers to replace with
  12. * actual parameters.
  13. */
  14. struct FormatParamRange
  15. {
  16. FormatParamRange() { }
  17. FormatParamRange(UINT32 start, UINT32 identifierSize, UINT32 paramIdx)
  18. :start(start), identifierSize(identifierSize), paramIdx(paramIdx)
  19. { }
  20. UINT32 start;
  21. UINT32 identifierSize;
  22. UINT32 paramIdx;
  23. };
  24. /**
  25. * @brief Structure that holds value of a parameter during string
  26. * formatting.
  27. */
  28. template<class T>
  29. struct ParamData
  30. {
  31. T* buffer;
  32. UINT32 size;
  33. };
  34. public:
  35. /**
  36. * @brief Formats the provided string by replacing the identifiers with the provided parameters.
  37. * The identifiers are represented like "{0}, {1}" in the source string, where the number
  38. * represents the position of the parameter that will be used for replacing the identifier.
  39. *
  40. * @note You may use "\" to escape identifier brackets.
  41. * Maximum identifier number is 19 (for a total of 20 unique identifiers. e.g. {20} won't be recognized as an identifier).
  42. * Total number of parameters that can be referenced is 200.
  43. */
  44. template<class T, class... Args>
  45. static BasicString<T> format(const T* source, Args&& ...args)
  46. {
  47. UINT32 strLength = getLength(source);
  48. ParamData<T> parameters[MAX_PARAMS];
  49. memset(parameters, 0, sizeof(parameters));
  50. getParams(parameters, 0U, std::forward<Args>(args)...);
  51. T bracketChars[MAX_IDENTIFIER_SIZE + 1];
  52. UINT32 bracketWriteIdx = 0;
  53. FormatParamRange paramRanges[MAX_PARAM_REFERENCES];
  54. memset(paramRanges, 0, sizeof(paramRanges));
  55. UINT32 paramRangeWriteIdx = 0;
  56. // Determine parameter positions
  57. INT32 lastBracket = -1;
  58. bool escaped = false;
  59. UINT32 charWriteIdx = 0;
  60. for (UINT32 i = 0; i < strLength; i++)
  61. {
  62. if (source[i] == '\\' && !escaped && paramRangeWriteIdx < MAX_PARAM_REFERENCES)
  63. {
  64. escaped = true;
  65. paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, 1, (UINT32)-1);
  66. continue;
  67. }
  68. if (lastBracket == -1)
  69. {
  70. // If current char is non-escaped opening bracket start parameter definition
  71. if (source[i] == '{' && !escaped)
  72. lastBracket = i;
  73. else
  74. charWriteIdx++;
  75. }
  76. else
  77. {
  78. if (isdigit(source[i]) && bracketWriteIdx < MAX_IDENTIFIER_SIZE)
  79. bracketChars[bracketWriteIdx++] = source[i];
  80. else
  81. {
  82. // If current char is non-escaped closing bracket end parameter definition
  83. UINT32 numParamChars = bracketWriteIdx;
  84. bool processedBracket = false;
  85. if (source[i] == '}' && numParamChars > 0 && !escaped)
  86. {
  87. bracketChars[bracketWriteIdx] = '\0';
  88. UINT32 paramIdx = strToInt(bracketChars);
  89. if (paramIdx < MAX_PARAMS && paramRangeWriteIdx < MAX_PARAM_REFERENCES) // Check if exceeded maximum parameter limit
  90. {
  91. paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, numParamChars + 2, paramIdx);
  92. charWriteIdx += parameters[paramIdx].size;
  93. processedBracket = true;
  94. }
  95. }
  96. if (!processedBracket)
  97. {
  98. // Last bracket wasn't really a parameter
  99. for (UINT32 j = lastBracket; j <= i; j++)
  100. charWriteIdx++;
  101. }
  102. lastBracket = -1;
  103. bracketWriteIdx = 0;
  104. }
  105. }
  106. escaped = false;
  107. }
  108. // Copy the clean string into output buffer
  109. UINT32 finalStringSize = charWriteIdx;
  110. T* outputBuffer = (T*)bs_alloc(finalStringSize * sizeof(T));
  111. UINT32 copySourceIdx = 0;
  112. UINT32 copyDestIdx = 0;
  113. for (UINT32 i = 0; i < paramRangeWriteIdx; i++)
  114. {
  115. const FormatParamRange& rangeInfo = paramRanges[i];
  116. UINT32 copySize = rangeInfo.start - copyDestIdx;
  117. memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, copySize * sizeof(T));
  118. copySourceIdx += copySize + rangeInfo.identifierSize;
  119. copyDestIdx += copySize;
  120. if (rangeInfo.paramIdx == (UINT32)-1)
  121. continue;
  122. UINT32 paramSize = parameters[rangeInfo.paramIdx].size;
  123. memcpy(outputBuffer + copyDestIdx, parameters[rangeInfo.paramIdx].buffer, paramSize * sizeof(T));
  124. copyDestIdx += paramSize;
  125. }
  126. memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, (finalStringSize - copyDestIdx) * sizeof(T));
  127. BasicString<T> outputStr(outputBuffer, finalStringSize);
  128. bs_free(outputBuffer);
  129. for (UINT32 i = 0; i < MAX_PARAMS; i++)
  130. {
  131. if (parameters[i].buffer != nullptr)
  132. bs_free(parameters[i].buffer);
  133. }
  134. return outputStr;
  135. }
  136. private:
  137. /**
  138. * @brief Set of methods that can be specialized so we have a generalized way for retrieving length
  139. * of strings of different types.
  140. */
  141. static UINT32 getLength(const char* source) { return (UINT32)strlen(source); }
  142. /**
  143. * @brief Set of methods that can be specialized so we have a generalized way for retrieving length
  144. * of strings of different types.
  145. */
  146. static UINT32 getLength(const wchar_t* source) { return (UINT32)wcslen(source); }
  147. /**
  148. * @brief Parses the string and returns an integer value extracted from string characters.
  149. */
  150. static UINT32 strToInt(const char* buffer)
  151. {
  152. return (UINT32)strtoul(buffer, nullptr, 10);
  153. }
  154. /**
  155. * @brief Parses the string and returns an integer value extracted from string characters.
  156. */
  157. static UINT32 strToInt(const wchar_t* buffer)
  158. {
  159. return (UINT32)wcstoul(buffer, nullptr, 10);
  160. }
  161. /**
  162. * @brief Helper method for converting any data type to a narrow string.
  163. */
  164. template<class T> static std::string toString(const T& param) { return std::to_string(param); }
  165. /**
  166. * @brief Helper method that "converts" a narrow string to a narrow string (simply a pass through)
  167. */
  168. template<> static std::string toString<std::string>(const std::string& param) { return param; }
  169. /**
  170. * @brief Helper method that converts a Banshee narrow string to a standard narrow string.
  171. */
  172. template<> static std::string toString<String>(const String& param)
  173. {
  174. return std::string(param.c_str());
  175. }
  176. /**
  177. * @brief Helper method for converting any data type to a wide string.
  178. */
  179. template<class T> static std::wstring toWString(const T& param) { return std::to_wstring(param); }
  180. /**
  181. * @brief Helper method that "converts" a wide string to a wide string (simply a pass through)
  182. */
  183. template<> static std::wstring toWString<std::wstring>(const std::wstring& param) { return param; }
  184. /**
  185. * @brief Helper method that converts a Banshee wide string to a standard wide string.
  186. */
  187. template<> static std::wstring toWString<WString>(const WString& param)
  188. {
  189. return std::wstring(param.c_str());
  190. }
  191. /**
  192. * @brief Converts all the provided parameters into string representations and populates the provided
  193. * "parameter" array.
  194. */
  195. template<class P, class... Args>
  196. static void getParams(ParamData<char>* parameters, UINT32 idx, P&& param, Args&& ...args)
  197. {
  198. if (idx >= MAX_PARAMS)
  199. return;
  200. std::basic_string<char> sourceParam = toString(param);
  201. parameters[idx].buffer = (char*)bs_alloc(sourceParam.size() * sizeof(char));
  202. parameters[idx].size = (UINT32)sourceParam.size();
  203. sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
  204. getParams(parameters, idx + 1, std::forward<Args>(args)...);
  205. }
  206. /**
  207. * @brief Converts all the provided parameters into string representations and populates the provided
  208. * "parameter" array.
  209. */
  210. template<class P, class... Args>
  211. static void getParams(ParamData<wchar_t>* parameters, UINT32 idx, P&& param, Args&& ...args)
  212. {
  213. if (idx >= MAX_PARAMS)
  214. return;
  215. std::basic_string<wchar_t> sourceParam = toWString(param);
  216. parameters[idx].buffer = (wchar_t*)bs_alloc(sourceParam.size() * sizeof(wchar_t));
  217. parameters[idx].size = (UINT32)sourceParam.size();
  218. sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
  219. getParams(parameters, idx + 1, std::forward<Args>(args)...);
  220. }
  221. /**
  222. * @brief Helper method for parameter size calculation. Used as a stopping point in template recursion.
  223. */
  224. static void getParams(ParamData<char>* parameters, UINT32 idx)
  225. {
  226. // Do nothing
  227. }
  228. /**
  229. * @brief Helper method for parameter size calculation. Used as a stopping point in template recursion.
  230. */
  231. static void getParams(ParamData<wchar_t>* parameters, UINT32 idx)
  232. {
  233. // Do nothing
  234. }
  235. static const UINT32 MAX_PARAMS = 20;
  236. static const UINT32 MAX_IDENTIFIER_SIZE = 2;
  237. static const UINT32 MAX_PARAM_REFERENCES = 200;
  238. };
  239. }