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