format.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. //
  2. // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
  3. // Copyright (c) 2021-2023 Alexander Grund
  4. //
  5. // Distributed under the Boost Software License, Version 1.0.
  6. // https://www.boost.org/LICENSE_1_0.txt
  7. #ifndef BOOST_LOCALE_FORMAT_HPP_INCLUDED
  8. #define BOOST_LOCALE_FORMAT_HPP_INCLUDED
  9. #include <boost/locale/formatting.hpp>
  10. #include <boost/locale/hold_ptr.hpp>
  11. #include <boost/locale/message.hpp>
  12. #include <sstream>
  13. #include <stdexcept>
  14. #include <string>
  15. #include <vector>
  16. #ifdef BOOST_MSVC
  17. # pragma warning(push)
  18. # pragma warning(disable : 4275 4251 4231 4660)
  19. #endif
  20. namespace boost { namespace locale {
  21. /// \defgroup format Format
  22. ///
  23. /// This module provides printf like functionality integrated into iostreams and suitable for localization
  24. ///
  25. /// @{
  26. /// \cond INTERNAL
  27. namespace detail {
  28. template<typename CharType>
  29. struct formattible {
  30. typedef std::basic_ostream<CharType> stream_type;
  31. typedef void (*writer_type)(stream_type& output, const void* ptr);
  32. formattible() noexcept : pointer_(nullptr), writer_(&formattible::void_write) {}
  33. formattible(const formattible&) noexcept = default;
  34. formattible(formattible&&) noexcept = default;
  35. formattible& operator=(const formattible&) noexcept = default;
  36. formattible& operator=(formattible&&) noexcept = default;
  37. template<typename Type>
  38. explicit formattible(const Type& value) noexcept
  39. {
  40. pointer_ = static_cast<const void*>(&value);
  41. writer_ = &write<Type>;
  42. }
  43. friend stream_type& operator<<(stream_type& out, const formattible& fmt)
  44. {
  45. fmt.writer_(out, fmt.pointer_);
  46. return out;
  47. }
  48. private:
  49. static void void_write(stream_type& output, const void* /*ptr*/)
  50. {
  51. CharType empty_string[1] = {0};
  52. output << empty_string;
  53. }
  54. template<typename Type>
  55. static void write(stream_type& output, const void* ptr)
  56. {
  57. output << *static_cast<const Type*>(ptr);
  58. }
  59. const void* pointer_;
  60. writer_type writer_;
  61. }; // formattible
  62. class BOOST_LOCALE_DECL format_parser {
  63. public:
  64. format_parser(std::ios_base& ios, void*, void (*imbuer)(void*, const std::locale&));
  65. ~format_parser();
  66. format_parser(const format_parser&) = delete;
  67. format_parser& operator=(const format_parser&) = delete;
  68. unsigned get_position();
  69. void set_one_flag(const std::string& key, const std::string& value);
  70. template<typename CharType>
  71. void set_flag_with_str(const std::string& key, const std::basic_string<CharType>& value)
  72. {
  73. if(key == "ftime" || key == "strftime") {
  74. as::strftime(ios_);
  75. ios_info::get(ios_).date_time_pattern(value);
  76. }
  77. }
  78. void restore();
  79. private:
  80. void imbue(const std::locale&);
  81. std::ios_base& ios_;
  82. struct data;
  83. hold_ptr<data> d;
  84. };
  85. } // namespace detail
  86. /// \endcond
  87. /// \brief a printf like class that allows type-safe and locale aware message formatting
  88. ///
  89. /// This class creates a formatted message similar to printf or boost::format and receives
  90. /// formatted entries via operator %.
  91. ///
  92. /// For example
  93. /// \code
  94. /// std::cout << format("Hello {1}, you are {2} years old") % name % age << std::endl;
  95. /// \endcode
  96. ///
  97. /// Formatting is enclosed between curly brackets \c { \c } and defined by a comma separated list of flags in the
  98. /// format key[=value] value may also be text included between single quotes \c ' that is used for special purposes
  99. /// where inclusion of non-ASCII text is allowed
  100. ///
  101. /// Including of literal \c { and \c } is possible by specifying double brackets \c {{ and \c }} accordingly.
  102. ///
  103. ///
  104. /// For example:
  105. ///
  106. /// \code
  107. /// std::cout << format("The height of water at {1,time} is {2,num=fixed,precision=3}") % time % height;
  108. /// \endcode
  109. ///
  110. /// The special key -- a number without a value defines the position of an input parameter.
  111. /// List of keys:
  112. /// - \c [0-9]+ -- digits, the index of a formatted parameter -- mandatory key.
  113. /// - \c num or \c number -- format a number. Optional values are:
  114. /// - \c hex -- display hexadecimal number
  115. /// - \c oct -- display in octal format
  116. /// - \c sci or \c scientific -- display in scientific format
  117. /// - \c fix or \c fixed -- display in fixed format
  118. /// .
  119. /// For example \c number=sci
  120. /// - \c cur or \c currency -- format currency. Optional values are:
  121. ///
  122. /// - \c iso -- display using ISO currency symbol.
  123. /// - \c nat or \c national -- display using national currency symbol.
  124. /// .
  125. /// - \c per or \c percent -- format percent value.
  126. /// - \c date, \c time , \c datetime or \c dt -- format date, time or date and time. Optional values are:
  127. /// - \c s or \c short -- display in short format
  128. /// - \c m or \c medium -- display in medium format.
  129. /// - \c l or \c long -- display in long format.
  130. /// - \c f or \c full -- display in full format.
  131. /// .
  132. /// - \c ftime with string (quoted) parameter -- display as with \c strftime see, \c as::ftime manipulator
  133. /// - \c spell or \c spellout -- spell the number.
  134. /// - \c ord or \c ordinal -- format ordinal number (1st, 2nd... etc)
  135. /// - \c left or \c < -- align to left.
  136. /// - \c right or \c > -- align to right.
  137. /// - \c width or \c w -- set field width (requires parameter).
  138. /// - \c precision or \c p -- set precision (requires parameter).
  139. /// - \c locale -- with parameter -- switch locale for current operation. This command generates locale
  140. /// with formatting facets giving more fine grained control of formatting. For example:
  141. /// \code
  142. /// std::cout << format("Today {1,date} ({1,date,locale=he_IL.UTF-8@calendar=hebrew,date} Hebrew Date)") % date;
  143. /// \endcode
  144. /// - \c timezone or \c tz -- the name of the timezone to display the time in. For example:\n
  145. /// \code
  146. /// std::cout << format("Time is: Local {1,time}, ({1,time,tz=EET} Eastern European Time)") % date;
  147. /// \endcode
  148. /// - \c local - display the time in local time
  149. /// - \c gmt - display the time in UTC time scale
  150. /// \code
  151. /// std::cout << format("Local time is: {1,time,local}, universal time is {1,time,gmt}") % time;
  152. /// \endcode
  153. ///
  154. ///
  155. /// Invalid formatting strings are silently ignored.
  156. /// This protects against a translator crashing the program in an unexpected location.
  157. template<typename CharType>
  158. class basic_format {
  159. int throw_if_params_bound() const;
  160. public:
  161. typedef CharType char_type; ///< Underlying character type
  162. typedef basic_message<char_type> message_type; ///< The translation message type
  163. /// \cond INTERNAL
  164. typedef detail::formattible<CharType> formattible_type;
  165. /// \endcond
  166. typedef std::basic_string<CharType> string_type; ///< string type for this type of character
  167. typedef std::basic_ostream<CharType> stream_type; ///< output stream type for this type of character
  168. /// Create a format class for \a format_string
  169. basic_format(const string_type& format_string) : format_(format_string), translate_(false), parameters_count_(0)
  170. {}
  171. /// Create a format class using message \a trans. The message if translated first according
  172. /// to the rules of the target locale and then interpreted as a format string
  173. basic_format(const message_type& trans) : message_(trans), translate_(true), parameters_count_(0) {}
  174. /// Non-copyable
  175. basic_format(const basic_format& other) = delete;
  176. void operator=(const basic_format& other) = delete;
  177. /// Moveable
  178. basic_format(basic_format&& other) :
  179. message_((other.throw_if_params_bound(), std::move(other.message_))), format_(std::move(other.format_)),
  180. translate_(other.translate_), parameters_count_(0)
  181. {}
  182. basic_format& operator=(basic_format&& other)
  183. {
  184. other.throw_if_params_bound();
  185. message_ = std::move(other.message_);
  186. format_ = std::move(other.format_);
  187. translate_ = other.translate_;
  188. parameters_count_ = 0;
  189. ext_params_.clear();
  190. return *this;
  191. }
  192. /// Add new parameter to the format list. The object should be a type
  193. /// with defined expression out << object where \c out is \c std::basic_ostream.
  194. ///
  195. /// A reference to the object is stored, so do not store the format object longer
  196. /// than the lifetime of the parameter.
  197. /// It is advisable to directly print the result:
  198. /// \code
  199. /// basic_format<char> fmt("{0}");
  200. /// fmt % (5 + 2); // INVALID: Dangling reference
  201. /// int i = 42;
  202. /// return fmt % i; // INVALID: Dangling reference
  203. /// std::cout << fmt % (5 + 2); // OK, print immediately
  204. /// return (fmt % (5 + 2)).str(); // OK, convert immediately to string
  205. /// \endcode
  206. template<typename Formattible>
  207. basic_format& operator%(const Formattible& object)
  208. {
  209. add(formattible_type(object));
  210. return *this;
  211. }
  212. /// Format a string using a locale \a loc
  213. string_type str(const std::locale& loc = std::locale()) const
  214. {
  215. std::basic_ostringstream<CharType> buffer;
  216. buffer.imbue(loc);
  217. write(buffer);
  218. return buffer.str();
  219. }
  220. /// write a formatted string to output stream \a out using out's locale
  221. void write(stream_type& out) const
  222. {
  223. string_type format;
  224. if(translate_)
  225. format = message_.str(out.getloc(), ios_info::get(out).domain_id());
  226. else
  227. format = format_;
  228. format_output(out, format);
  229. }
  230. private:
  231. class format_guard {
  232. public:
  233. format_guard(detail::format_parser& fmt) : fmt_(fmt), restored_(false) {}
  234. void restore()
  235. {
  236. if(restored_)
  237. return;
  238. fmt_.restore();
  239. restored_ = true;
  240. }
  241. ~format_guard()
  242. {
  243. // clang-format off
  244. try { restore(); } catch(...) {}
  245. // clang-format on
  246. }
  247. private:
  248. detail::format_parser& fmt_;
  249. bool restored_;
  250. };
  251. void format_output(stream_type& out, const string_type& sformat) const
  252. {
  253. constexpr char_type obrk = '{';
  254. constexpr char_type cbrk = '}';
  255. constexpr char_type eq = '=';
  256. constexpr char_type comma = ',';
  257. constexpr char_type quote = '\'';
  258. const size_t size = sformat.size();
  259. const CharType* format = sformat.c_str();
  260. for(size_t pos = 0; format[pos];) {
  261. if(format[pos] != obrk) {
  262. if(format[pos] == cbrk && format[pos + 1] == cbrk) {
  263. // Escaped closing brace
  264. out << cbrk;
  265. pos += 2;
  266. } else {
  267. out << format[pos];
  268. pos++;
  269. }
  270. continue;
  271. }
  272. pos++;
  273. if(format[pos] == obrk) {
  274. // Escaped opening brace
  275. out << obrk;
  276. pos++;
  277. continue;
  278. }
  279. detail::format_parser fmt(out, static_cast<void*>(&out), &basic_format::imbue_locale);
  280. format_guard guard(fmt);
  281. while(pos < size) {
  282. std::string key;
  283. std::string svalue;
  284. string_type value;
  285. bool use_svalue = true;
  286. for(char_type c = format[pos]; !(c == 0 || c == comma || c == eq || c == cbrk); c = format[++pos]) {
  287. key += static_cast<char>(c);
  288. }
  289. if(format[pos] == eq) {
  290. pos++;
  291. if(format[pos] == quote) {
  292. pos++;
  293. use_svalue = false;
  294. while(format[pos]) {
  295. if(format[pos] == quote) {
  296. if(format[pos + 1] == quote) {
  297. value += quote;
  298. pos += 2;
  299. } else {
  300. pos++;
  301. break;
  302. }
  303. } else {
  304. value += format[pos];
  305. pos++;
  306. }
  307. }
  308. } else {
  309. char_type c;
  310. while((c = format[pos]) != 0 && c != comma && c != cbrk) {
  311. svalue += static_cast<char>(c);
  312. pos++;
  313. }
  314. }
  315. }
  316. if(use_svalue)
  317. fmt.set_one_flag(key, svalue);
  318. else
  319. fmt.set_flag_with_str(key, value);
  320. if(format[pos] == comma)
  321. pos++;
  322. else {
  323. if(format[pos] == cbrk) {
  324. unsigned position = fmt.get_position();
  325. out << get(position);
  326. pos++;
  327. }
  328. break;
  329. }
  330. }
  331. }
  332. }
  333. void add(const formattible_type& param)
  334. {
  335. if(parameters_count_ >= base_params_)
  336. ext_params_.push_back(param);
  337. else
  338. parameters_[parameters_count_] = param;
  339. parameters_count_++;
  340. }
  341. formattible_type get(unsigned id) const
  342. {
  343. if(id >= parameters_count_)
  344. return formattible_type();
  345. else if(id >= base_params_)
  346. return ext_params_[id - base_params_];
  347. else
  348. return parameters_[id];
  349. }
  350. static void imbue_locale(void* ptr, const std::locale& l) { static_cast<stream_type*>(ptr)->imbue(l); }
  351. static constexpr unsigned base_params_ = 8;
  352. message_type message_;
  353. string_type format_;
  354. bool translate_;
  355. formattible_type parameters_[base_params_];
  356. unsigned parameters_count_;
  357. std::vector<formattible_type> ext_params_;
  358. };
  359. /// Write formatted message to stream.
  360. ///
  361. /// This operator actually causes actual text formatting. It uses the locale of \a out stream
  362. template<typename CharType>
  363. std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const basic_format<CharType>& fmt)
  364. {
  365. fmt.write(out);
  366. return out;
  367. }
  368. /// Definition of char based format
  369. typedef basic_format<char> format;
  370. /// Definition of wchar_t based format
  371. typedef basic_format<wchar_t> wformat;
  372. #ifndef BOOST_LOCALE_NO_CXX20_STRING8
  373. /// Definition of char8_t based format
  374. typedef basic_format<char8_t> u8format;
  375. #endif
  376. #ifdef BOOST_LOCALE_ENABLE_CHAR16_T
  377. /// Definition of char16_t based format
  378. typedef basic_format<char16_t> u16format;
  379. #endif
  380. #ifdef BOOST_LOCALE_ENABLE_CHAR32_T
  381. /// Definition of char32_t based format
  382. typedef basic_format<char32_t> u32format;
  383. #endif
  384. template<typename CharType>
  385. int basic_format<CharType>::throw_if_params_bound() const
  386. {
  387. if(parameters_count_)
  388. throw std::invalid_argument("Can't move a basic_format with bound parameters");
  389. return 0;
  390. }
  391. /// @}
  392. }} // namespace boost::locale
  393. #ifdef BOOST_MSVC
  394. # pragma warning(pop)
  395. #endif
  396. /// \example hello.cpp
  397. ///
  398. /// Basic example of using various functions provided by this library
  399. ///
  400. /// \example whello.cpp
  401. ///
  402. /// Basic example of using various functions with wide strings provided by this library
  403. #endif