459 lines
17 KiB
C++
459 lines
17 KiB
C++
|
//
|
||
|
// 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
|