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
24namespace 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

source code of boost/libs/locale/include/boost/locale/format.hpp