BsStringFormat.h 9.8 KB

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