123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- //
- // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
- // Copyright (c) 2021-2023 Alexander Grund
- //
- // Distributed under the Boost Software License, Version 1.0.
- // https://www.boost.org/LICENSE_1_0.txt
- #ifndef BOOST_LOCALE_FORMAT_HPP_INCLUDED
- #define BOOST_LOCALE_FORMAT_HPP_INCLUDED
- #include <boost/locale/formatting.hpp>
- #include <boost/locale/hold_ptr.hpp>
- #include <boost/locale/message.hpp>
- #include <sstream>
- #include <stdexcept>
- #include <string>
- #include <vector>
- #ifdef BOOST_MSVC
- # pragma warning(push)
- # pragma warning(disable : 4275 4251 4231 4660)
- #endif
- namespace boost { namespace locale {
- /// \defgroup format Format
- ///
- /// This module provides printf like functionality integrated into iostreams and suitable for localization
- ///
- /// @{
- /// \cond INTERNAL
- namespace detail {
- template<typename CharType>
- struct formattible {
- typedef std::basic_ostream<CharType> stream_type;
- typedef void (*writer_type)(stream_type& output, const void* ptr);
- formattible() noexcept : pointer_(nullptr), writer_(&formattible::void_write) {}
- formattible(const formattible&) noexcept = default;
- formattible(formattible&&) noexcept = default;
- formattible& operator=(const formattible&) noexcept = default;
- formattible& operator=(formattible&&) noexcept = default;
- template<typename Type>
- explicit formattible(const Type& value) noexcept
- {
- pointer_ = static_cast<const void*>(&value);
- writer_ = &write<Type>;
- }
- friend stream_type& operator<<(stream_type& out, const formattible& fmt)
- {
- fmt.writer_(out, fmt.pointer_);
- return out;
- }
- private:
- static void void_write(stream_type& output, const void* /*ptr*/)
- {
- CharType empty_string[1] = {0};
- output << empty_string;
- }
- template<typename Type>
- static void write(stream_type& output, const void* ptr)
- {
- output << *static_cast<const Type*>(ptr);
- }
- const void* pointer_;
- writer_type writer_;
- }; // formattible
- class BOOST_LOCALE_DECL format_parser {
- public:
- format_parser(std::ios_base& ios, void*, void (*imbuer)(void*, const std::locale&));
- ~format_parser();
- format_parser(const format_parser&) = delete;
- format_parser& operator=(const format_parser&) = delete;
- unsigned get_position();
- void set_one_flag(const std::string& key, const std::string& value);
- template<typename CharType>
- void set_flag_with_str(const std::string& key, const std::basic_string<CharType>& value)
- {
- if(key == "ftime" || key == "strftime") {
- as::strftime(ios_);
- ios_info::get(ios_).date_time_pattern(value);
- }
- }
- void restore();
- private:
- void imbue(const std::locale&);
- std::ios_base& ios_;
- struct data;
- hold_ptr<data> d;
- };
- } // namespace detail
- /// \endcond
- /// \brief a printf like class that allows type-safe and locale aware message formatting
- ///
- /// This class creates a formatted message similar to printf or boost::format and receives
- /// formatted entries via operator %.
- ///
- /// For example
- /// \code
- /// std::cout << format("Hello {1}, you are {2} years old") % name % age << std::endl;
- /// \endcode
- ///
- /// Formatting is enclosed between curly brackets \c { \c } and defined by a comma separated list of flags in the
- /// format key[=value] value may also be text included between single quotes \c ' that is used for special purposes
- /// where inclusion of non-ASCII text is allowed
- ///
- /// Including of literal \c { and \c } is possible by specifying double brackets \c {{ and \c }} accordingly.
- ///
- ///
- /// For example:
- ///
- /// \code
- /// std::cout << format("The height of water at {1,time} is {2,num=fixed,precision=3}") % time % height;
- /// \endcode
- ///
- /// The special key -- a number without a value defines the position of an input parameter.
- /// List of keys:
- /// - \c [0-9]+ -- digits, the index of a formatted parameter -- mandatory key.
- /// - \c num or \c number -- format a number. Optional values are:
- /// - \c hex -- display hexadecimal number
- /// - \c oct -- display in octal format
- /// - \c sci or \c scientific -- display in scientific format
- /// - \c fix or \c fixed -- display in fixed format
- /// .
- /// For example \c number=sci
- /// - \c cur or \c currency -- format currency. Optional values are:
- ///
- /// - \c iso -- display using ISO currency symbol.
- /// - \c nat or \c national -- display using national currency symbol.
- /// .
- /// - \c per or \c percent -- format percent value.
- /// - \c date, \c time , \c datetime or \c dt -- format date, time or date and time. Optional values are:
- /// - \c s or \c short -- display in short format
- /// - \c m or \c medium -- display in medium format.
- /// - \c l or \c long -- display in long format.
- /// - \c f or \c full -- display in full format.
- /// .
- /// - \c ftime with string (quoted) parameter -- display as with \c strftime see, \c as::ftime manipulator
- /// - \c spell or \c spellout -- spell the number.
- /// - \c ord or \c ordinal -- format ordinal number (1st, 2nd... etc)
- /// - \c left or \c < -- align to left.
- /// - \c right or \c > -- align to right.
- /// - \c width or \c w -- set field width (requires parameter).
- /// - \c precision or \c p -- set precision (requires parameter).
- /// - \c locale -- with parameter -- switch locale for current operation. This command generates locale
- /// with formatting facets giving more fine grained control of formatting. For example:
- /// \code
- /// std::cout << format("Today {1,date} ({1,date,locale=he_IL.UTF-8@calendar=hebrew,date} Hebrew Date)") % date;
- /// \endcode
- /// - \c timezone or \c tz -- the name of the timezone to display the time in. For example:\n
- /// \code
- /// std::cout << format("Time is: Local {1,time}, ({1,time,tz=EET} Eastern European Time)") % date;
- /// \endcode
- /// - \c local - display the time in local time
- /// - \c gmt - display the time in UTC time scale
- /// \code
- /// std::cout << format("Local time is: {1,time,local}, universal time is {1,time,gmt}") % time;
- /// \endcode
- ///
- ///
- /// Invalid formatting strings are silently ignored.
- /// This protects against a translator crashing the program in an unexpected location.
- template<typename CharType>
- class basic_format {
- int throw_if_params_bound() const;
- public:
- typedef CharType char_type; ///< Underlying character type
- typedef basic_message<char_type> message_type; ///< The translation message type
- /// \cond INTERNAL
- typedef detail::formattible<CharType> formattible_type;
- /// \endcond
- typedef std::basic_string<CharType> string_type; ///< string type for this type of character
- typedef std::basic_ostream<CharType> stream_type; ///< output stream type for this type of character
- /// Create a format class for \a format_string
- basic_format(const string_type& format_string) : format_(format_string), translate_(false), parameters_count_(0)
- {}
- /// Create a format class using message \a trans. The message if translated first according
- /// to the rules of the target locale and then interpreted as a format string
- basic_format(const message_type& trans) : message_(trans), translate_(true), parameters_count_(0) {}
- /// Non-copyable
- basic_format(const basic_format& other) = delete;
- void operator=(const basic_format& other) = delete;
- /// Moveable
- basic_format(basic_format&& other) :
- message_((other.throw_if_params_bound(), std::move(other.message_))), format_(std::move(other.format_)),
- translate_(other.translate_), parameters_count_(0)
- {}
- basic_format& operator=(basic_format&& other)
- {
- other.throw_if_params_bound();
- message_ = std::move(other.message_);
- format_ = std::move(other.format_);
- translate_ = other.translate_;
- parameters_count_ = 0;
- ext_params_.clear();
- return *this;
- }
- /// Add new parameter to the format list. The object should be a type
- /// with defined expression out << object where \c out is \c std::basic_ostream.
- ///
- /// A reference to the object is stored, so do not store the format object longer
- /// than the lifetime of the parameter.
- /// It is advisable to directly print the result:
- /// \code
- /// basic_format<char> fmt("{0}");
- /// fmt % (5 + 2); // INVALID: Dangling reference
- /// int i = 42;
- /// return fmt % i; // INVALID: Dangling reference
- /// std::cout << fmt % (5 + 2); // OK, print immediately
- /// return (fmt % (5 + 2)).str(); // OK, convert immediately to string
- /// \endcode
- template<typename Formattible>
- basic_format& operator%(const Formattible& object)
- {
- add(formattible_type(object));
- return *this;
- }
- /// Format a string using a locale \a loc
- string_type str(const std::locale& loc = std::locale()) const
- {
- std::basic_ostringstream<CharType> buffer;
- buffer.imbue(loc);
- write(buffer);
- return buffer.str();
- }
- /// write a formatted string to output stream \a out using out's locale
- void write(stream_type& out) const
- {
- string_type format;
- if(translate_)
- format = message_.str(out.getloc(), ios_info::get(out).domain_id());
- else
- format = format_;
- format_output(out, format);
- }
- private:
- class format_guard {
- public:
- format_guard(detail::format_parser& fmt) : fmt_(fmt), restored_(false) {}
- void restore()
- {
- if(restored_)
- return;
- fmt_.restore();
- restored_ = true;
- }
- ~format_guard()
- {
- // clang-format off
- try { restore(); } catch(...) {}
- // clang-format on
- }
- private:
- detail::format_parser& fmt_;
- bool restored_;
- };
- void format_output(stream_type& out, const string_type& sformat) const
- {
- constexpr char_type obrk = '{';
- constexpr char_type cbrk = '}';
- constexpr char_type eq = '=';
- constexpr char_type comma = ',';
- constexpr char_type quote = '\'';
- const size_t size = sformat.size();
- const CharType* format = sformat.c_str();
- for(size_t pos = 0; format[pos];) {
- if(format[pos] != obrk) {
- if(format[pos] == cbrk && format[pos + 1] == cbrk) {
- // Escaped closing brace
- out << cbrk;
- pos += 2;
- } else {
- out << format[pos];
- pos++;
- }
- continue;
- }
- pos++;
- if(format[pos] == obrk) {
- // Escaped opening brace
- out << obrk;
- pos++;
- continue;
- }
- detail::format_parser fmt(out, static_cast<void*>(&out), &basic_format::imbue_locale);
- format_guard guard(fmt);
- while(pos < size) {
- std::string key;
- std::string svalue;
- string_type value;
- bool use_svalue = true;
- for(char_type c = format[pos]; !(c == 0 || c == comma || c == eq || c == cbrk); c = format[++pos]) {
- key += static_cast<char>(c);
- }
- if(format[pos] == eq) {
- pos++;
- if(format[pos] == quote) {
- pos++;
- use_svalue = false;
- while(format[pos]) {
- if(format[pos] == quote) {
- if(format[pos + 1] == quote) {
- value += quote;
- pos += 2;
- } else {
- pos++;
- break;
- }
- } else {
- value += format[pos];
- pos++;
- }
- }
- } else {
- char_type c;
- while((c = format[pos]) != 0 && c != comma && c != cbrk) {
- svalue += static_cast<char>(c);
- pos++;
- }
- }
- }
- if(use_svalue)
- fmt.set_one_flag(key, svalue);
- else
- fmt.set_flag_with_str(key, value);
- if(format[pos] == comma)
- pos++;
- else {
- if(format[pos] == cbrk) {
- unsigned position = fmt.get_position();
- out << get(position);
- pos++;
- }
- break;
- }
- }
- }
- }
- void add(const formattible_type& param)
- {
- if(parameters_count_ >= base_params_)
- ext_params_.push_back(param);
- else
- parameters_[parameters_count_] = param;
- parameters_count_++;
- }
- formattible_type get(unsigned id) const
- {
- if(id >= parameters_count_)
- return formattible_type();
- else if(id >= base_params_)
- return ext_params_[id - base_params_];
- else
- return parameters_[id];
- }
- static void imbue_locale(void* ptr, const std::locale& l) { static_cast<stream_type*>(ptr)->imbue(l); }
- static constexpr unsigned base_params_ = 8;
- message_type message_;
- string_type format_;
- bool translate_;
- formattible_type parameters_[base_params_];
- unsigned parameters_count_;
- std::vector<formattible_type> ext_params_;
- };
- /// Write formatted message to stream.
- ///
- /// This operator actually causes actual text formatting. It uses the locale of \a out stream
- template<typename CharType>
- std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const basic_format<CharType>& fmt)
- {
- fmt.write(out);
- return out;
- }
- /// Definition of char based format
- typedef basic_format<char> format;
- /// Definition of wchar_t based format
- typedef basic_format<wchar_t> wformat;
- #ifndef BOOST_LOCALE_NO_CXX20_STRING8
- /// Definition of char8_t based format
- typedef basic_format<char8_t> u8format;
- #endif
- #ifdef BOOST_LOCALE_ENABLE_CHAR16_T
- /// Definition of char16_t based format
- typedef basic_format<char16_t> u16format;
- #endif
- #ifdef BOOST_LOCALE_ENABLE_CHAR32_T
- /// Definition of char32_t based format
- typedef basic_format<char32_t> u32format;
- #endif
- template<typename CharType>
- int basic_format<CharType>::throw_if_params_bound() const
- {
- if(parameters_count_)
- throw std::invalid_argument("Can't move a basic_format with bound parameters");
- return 0;
- }
- /// @}
- }} // namespace boost::locale
- #ifdef BOOST_MSVC
- # pragma warning(pop)
- #endif
- /// \example hello.cpp
- ///
- /// Basic example of using various functions provided by this library
- ///
- /// \example whello.cpp
- ///
- /// Basic example of using various functions with wide strings provided by this library
- #endif
|