mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
finally implement a real number format parser. that was rough...
This commit is contained in:
parent
bdc770d23a
commit
d92ad1ab9c
1214
source/detail/number_formatter.cpp
Normal file
1214
source/detail/number_formatter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
366
source/detail/number_formatter.hpp
Normal file
366
source/detail/number_formatter.hpp
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
// Copyright (c) 2014-2016 Thomas Fussell
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE
|
||||||
|
//
|
||||||
|
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
// @author: see AUTHORS file
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <xlnt/utils/datetime.hpp>
|
||||||
|
|
||||||
|
namespace xlnt {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
enum class bracket_color
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
black,
|
||||||
|
blue,
|
||||||
|
cyan,
|
||||||
|
green,
|
||||||
|
magenta,
|
||||||
|
red,
|
||||||
|
white,
|
||||||
|
yellow,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
color3,
|
||||||
|
color4,
|
||||||
|
color5,
|
||||||
|
color6,
|
||||||
|
color7,
|
||||||
|
color8,
|
||||||
|
color9,
|
||||||
|
color10,
|
||||||
|
color11,
|
||||||
|
color12,
|
||||||
|
color13,
|
||||||
|
color14,
|
||||||
|
color15,
|
||||||
|
color16,
|
||||||
|
color17,
|
||||||
|
color18,
|
||||||
|
color19,
|
||||||
|
color20,
|
||||||
|
color21,
|
||||||
|
color22,
|
||||||
|
color23,
|
||||||
|
color24,
|
||||||
|
color25,
|
||||||
|
color26,
|
||||||
|
color27,
|
||||||
|
color28,
|
||||||
|
color29,
|
||||||
|
color30,
|
||||||
|
color31,
|
||||||
|
color32,
|
||||||
|
color33,
|
||||||
|
color34,
|
||||||
|
color35,
|
||||||
|
color36,
|
||||||
|
color37,
|
||||||
|
color38,
|
||||||
|
color39,
|
||||||
|
color40,
|
||||||
|
color41,
|
||||||
|
color42,
|
||||||
|
color43,
|
||||||
|
color44,
|
||||||
|
color45,
|
||||||
|
color46,
|
||||||
|
color47,
|
||||||
|
color48,
|
||||||
|
color49,
|
||||||
|
color50,
|
||||||
|
color51,
|
||||||
|
color52,
|
||||||
|
color53,
|
||||||
|
color54,
|
||||||
|
color55,
|
||||||
|
color56
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class locale
|
||||||
|
{
|
||||||
|
none = 0,
|
||||||
|
arabic_saudi_arabia = 0x401,
|
||||||
|
bulgarian = 0x402,
|
||||||
|
catalan = 0x403,
|
||||||
|
chinese_taiwan = 0x404,
|
||||||
|
czech = 0x405,
|
||||||
|
danish = 0x406,
|
||||||
|
german_germany = 0x407,
|
||||||
|
greek = 0x408,
|
||||||
|
english_united_states = 0x409,
|
||||||
|
italian_italy = 0x410,
|
||||||
|
japanese = 0x411,
|
||||||
|
korean = 0x412,
|
||||||
|
dutch_netherlands = 0x413,
|
||||||
|
norwegian_bokml = 0x414,
|
||||||
|
polish = 0x415,
|
||||||
|
portuguese_brazil = 0x416,
|
||||||
|
raeto_romance = 0x417,
|
||||||
|
romanian_romania = 0x418,
|
||||||
|
russian = 0x419,
|
||||||
|
urdu = 0x420,
|
||||||
|
indonesian = 0x421,
|
||||||
|
ukrainian = 0x422,
|
||||||
|
belarusian = 0x423,
|
||||||
|
slovenian = 0x424,
|
||||||
|
estonian = 0x425,
|
||||||
|
latvian = 0x426,
|
||||||
|
lithuanian = 0x427,
|
||||||
|
tajik = 0x428,
|
||||||
|
farsi_persian = 0x429,
|
||||||
|
sesotho_sutu = 0x430,
|
||||||
|
tsonga = 0x431,
|
||||||
|
setsuana = 0x432,
|
||||||
|
venda = 0x433,
|
||||||
|
xhosa = 0x434,
|
||||||
|
zulu = 0x435,
|
||||||
|
afrikaans = 0x436,
|
||||||
|
georgian = 0x437,
|
||||||
|
faroese = 0x438,
|
||||||
|
hindi = 0x439,
|
||||||
|
kyrgyz_cyrillic = 0x440,
|
||||||
|
swahili = 0x441,
|
||||||
|
turkmen = 0x442,
|
||||||
|
uzbek_latin = 0x443,
|
||||||
|
tatar = 0x444,
|
||||||
|
bengali_india = 0x445,
|
||||||
|
punjabi = 0x446,
|
||||||
|
gujarati = 0x447,
|
||||||
|
oriya = 0x448,
|
||||||
|
tamil = 0x449,
|
||||||
|
mongolian = 0x450,
|
||||||
|
tibetan = 0x451,
|
||||||
|
welsh = 0x452,
|
||||||
|
khmer = 0x453,
|
||||||
|
lao = 0x454,
|
||||||
|
burmese = 0x455,
|
||||||
|
galician = 0x456,
|
||||||
|
konkani = 0x457,
|
||||||
|
manipuri = 0x458,
|
||||||
|
sindhi = 0x459,
|
||||||
|
kashmiri = 0x460,
|
||||||
|
nepali = 0x461,
|
||||||
|
frisian_netherlands = 0x462,
|
||||||
|
filipino = 0x464,
|
||||||
|
divehi_dhivehi_maldivian = 0x465,
|
||||||
|
edo = 0x466,
|
||||||
|
igbo_nigeria = 0x470,
|
||||||
|
guarani_paraguay = 0x474,
|
||||||
|
latin = 0x476,
|
||||||
|
somali = 0x477,
|
||||||
|
maori = 0x481,
|
||||||
|
arabic_iraq = 0x801,
|
||||||
|
chinese_china = 0x804,
|
||||||
|
german_switzerland = 0x807,
|
||||||
|
english_great_britain = 0x809,
|
||||||
|
italian_switzerland = 0x810,
|
||||||
|
dutch_belgium = 0x813,
|
||||||
|
norwegian_nynorsk = 0x814,
|
||||||
|
portuguese_portugal = 0x816,
|
||||||
|
romanian_moldova = 0x818,
|
||||||
|
russian_moldova = 0x819,
|
||||||
|
uzbek_cyrillic = 0x843,
|
||||||
|
bengali_bangladesh = 0x845,
|
||||||
|
mongolian2 = 0x850,
|
||||||
|
arabic_libya = 0x1001,
|
||||||
|
chinese_singapore = 0x1004,
|
||||||
|
german_luxembourg = 0x1007,
|
||||||
|
english_canada = 0x1009,
|
||||||
|
arabic_algeria = 0x1401,
|
||||||
|
chinese_macau_sar = 0x1404,
|
||||||
|
german_liechtenstein = 0x1407,
|
||||||
|
english_new_zealand = 0x1409,
|
||||||
|
arabic_morocco = 0x1801,
|
||||||
|
english_ireland = 0x1809,
|
||||||
|
arabic_oman = 0x2001,
|
||||||
|
english_jamaica = 0x2009,
|
||||||
|
arabic_yemen = 0x2401,
|
||||||
|
english_caribbean = 0x2409,
|
||||||
|
arabic_syria = 0x2801,
|
||||||
|
english_belize = 0x2809,
|
||||||
|
arabic_lebanon = 0x3001,
|
||||||
|
english_zimbabwe = 0x3009,
|
||||||
|
arabic_kuwait = 0x3401,
|
||||||
|
english_phillippines = 0x3409,
|
||||||
|
arabic_united_arab_emirates = 0x3801,
|
||||||
|
arabic_qatar = 0x4001
|
||||||
|
};
|
||||||
|
|
||||||
|
struct condition
|
||||||
|
{
|
||||||
|
enum class condition_type
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
less_than,
|
||||||
|
less_or_equal,
|
||||||
|
equal,
|
||||||
|
not_equal,
|
||||||
|
greater_than,
|
||||||
|
greater_or_equal
|
||||||
|
} type = condition_type::none;
|
||||||
|
|
||||||
|
long double value = 0;
|
||||||
|
|
||||||
|
bool satisfied_by(long double number) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct placeholders
|
||||||
|
{
|
||||||
|
enum class placeholders_type
|
||||||
|
{
|
||||||
|
bad,
|
||||||
|
general,
|
||||||
|
text,
|
||||||
|
fractional_part,
|
||||||
|
integer_part,
|
||||||
|
fraction_integer,
|
||||||
|
fraction_numerator,
|
||||||
|
fraction_denominator,
|
||||||
|
scientific_significand,
|
||||||
|
scientific_exponent
|
||||||
|
} type = placeholders_type::bad;
|
||||||
|
|
||||||
|
bool use_comma_separator = false;
|
||||||
|
|
||||||
|
std::size_t num_zeros = 0; // 0
|
||||||
|
std::size_t num_optionals = 0; // #
|
||||||
|
std::size_t num_spaces = 0; // ?
|
||||||
|
std::size_t thousands_scale = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct number_format_token
|
||||||
|
{
|
||||||
|
enum class token_type
|
||||||
|
{
|
||||||
|
bad,
|
||||||
|
color,
|
||||||
|
locale,
|
||||||
|
condition,
|
||||||
|
text,
|
||||||
|
fill,
|
||||||
|
space,
|
||||||
|
number,
|
||||||
|
datetime,
|
||||||
|
end_section,
|
||||||
|
end
|
||||||
|
} type = token_type::bad;
|
||||||
|
|
||||||
|
std::string string;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct template_part
|
||||||
|
{
|
||||||
|
enum class template_type
|
||||||
|
{
|
||||||
|
bad,
|
||||||
|
text,
|
||||||
|
fill,
|
||||||
|
space,
|
||||||
|
general,
|
||||||
|
placeholder,
|
||||||
|
month_number,
|
||||||
|
month_number_leading_zero,
|
||||||
|
month_abbreviation,
|
||||||
|
month_name,
|
||||||
|
month_letter,
|
||||||
|
day_number,
|
||||||
|
day_number_leading_zero,
|
||||||
|
day_abbreviation,
|
||||||
|
day_name,
|
||||||
|
year_short,
|
||||||
|
year_long,
|
||||||
|
hour,
|
||||||
|
hour_leading_zero,
|
||||||
|
minute,
|
||||||
|
minute_leading_zero,
|
||||||
|
second,
|
||||||
|
second_leading_zero,
|
||||||
|
am_pm,
|
||||||
|
a_p,
|
||||||
|
elapsed_hours,
|
||||||
|
elapsed_minutes,
|
||||||
|
elapsed_seconds
|
||||||
|
} type = template_type::bad;
|
||||||
|
|
||||||
|
std::string string;
|
||||||
|
placeholders placeholders;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct format_code
|
||||||
|
{
|
||||||
|
bracket_color color = bracket_color::none;
|
||||||
|
locale locale = locale::none;
|
||||||
|
condition condition;
|
||||||
|
bool is_datetime = false;
|
||||||
|
bool is_timedelta = false;
|
||||||
|
bool twelve_hour = false;
|
||||||
|
std::vector<template_part> parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
class number_format_parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
number_format_parser(const std::string &format_string);
|
||||||
|
const std::vector<format_code> &get_result() const;
|
||||||
|
void reset(const std::string &format_string);
|
||||||
|
void parse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void finalize();
|
||||||
|
void validate();
|
||||||
|
|
||||||
|
number_format_token parse_next_token();
|
||||||
|
|
||||||
|
placeholders parse_placeholders(const std::string &placeholders_string);
|
||||||
|
bracket_color color_from_string(const std::string &color);
|
||||||
|
std::pair<locale, std::string> locale_from_string(const std::string &locale_string);
|
||||||
|
|
||||||
|
std::size_t position_ = 0;
|
||||||
|
std::string format_string_;
|
||||||
|
std::vector<format_code> codes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class number_formatter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
number_formatter(const std::string &format_string, xlnt::calendar calendar);
|
||||||
|
std::string format_number(long double number);
|
||||||
|
std::string format_text(const std::string &text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string fill_placeholders(const placeholders &p, long double number);
|
||||||
|
std::string format_number(const format_code &format, long double number);
|
||||||
|
std::string format_text(const format_code &format, const std::string &text);
|
||||||
|
|
||||||
|
number_format_parser parser_;
|
||||||
|
std::vector<format_code> format_;
|
||||||
|
xlnt::calendar calendar_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace xlnt
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2014-2016 Thomas Fussell
|
// Copyright (c) 2014-2016 Thomas Fussell
|
||||||
// Copyright (c) 2010-2015 openpyxl
|
// Copyright (c) 2010-2015 openpyxl
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
@ -30,6 +30,8 @@
|
||||||
#include <xlnt/utils/hash_combine.hpp>
|
#include <xlnt/utils/hash_combine.hpp>
|
||||||
#include <xlnt/styles/number_format.hpp>
|
#include <xlnt/styles/number_format.hpp>
|
||||||
|
|
||||||
|
#include <detail/number_formatter.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const std::unordered_map<std::size_t, std::string> &builtin_formats()
|
const std::unordered_map<std::size_t, std::string> &builtin_formats()
|
||||||
|
@ -61,16 +63,16 @@ const std::unordered_map<std::size_t, std::string> &builtin_formats()
|
||||||
{ 21, "h:mm:ss" },
|
{ 21, "h:mm:ss" },
|
||||||
{ 22, "m/d/yy h:mm" },
|
{ 22, "m/d/yy h:mm" },
|
||||||
|
|
||||||
{ 37, "#,##0_);(#,##0)" },
|
{ 37, "#,##0 ;(#,##0)" },
|
||||||
{ 38, "#,##0_);[Red](#,##0)" },
|
{ 38, "#,##0 ;[Red](#,##0)" },
|
||||||
{ 39, "#,##0.00_);(#,##0.00)" },
|
{ 39, "#,##0.00;(#,##0.00)" },
|
||||||
{ 40, "#,##0.00_);[Red](#,##0.00)" },
|
{ 40, "#,##0.00;[Red](#,##0.00)" },
|
||||||
|
|
||||||
{ 41, "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)" },
|
{ 41, "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)" },
|
||||||
{ 42, "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)" },
|
{ 42, "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)" },
|
||||||
{ 43, "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)" },
|
{ 43, "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)" },
|
||||||
|
|
||||||
{ 44, "_-\"$\"* #,##0.00_-;\\-\"$\"* #,##0.00_-;_-\"$\"* \"-\"??_-;_-@_-" },
|
{ 44, "_-\"$\"* #,##0.00_-;\\-\"$\"* #,##0.00_-;_-\"$\"* \"-\"??_-;_-@_-" },
|
||||||
|
|
||||||
{ 45, "mm:ss" },
|
{ 45, "mm:ss" },
|
||||||
{ 46, "[h]:mm:ss" },
|
{ 46, "[h]:mm:ss" },
|
||||||
{ 47, "mmss.0" },
|
{ 47, "mmss.0" },
|
||||||
|
@ -91,868 +93,6 @@ const std::unordered_map<std::size_t, std::string> &builtin_formats()
|
||||||
return *formats;
|
return *formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class condition_type
|
|
||||||
{
|
|
||||||
less_than,
|
|
||||||
less_or_equal,
|
|
||||||
equal,
|
|
||||||
greater_than,
|
|
||||||
greater_or_equal,
|
|
||||||
invalid
|
|
||||||
};
|
|
||||||
|
|
||||||
struct section
|
|
||||||
{
|
|
||||||
bool has_value = false;
|
|
||||||
std::string value;
|
|
||||||
bool has_color = false;
|
|
||||||
std::string color;
|
|
||||||
bool has_condition = false;
|
|
||||||
condition_type condition = condition_type::invalid;
|
|
||||||
std::string condition_value;
|
|
||||||
bool has_locale = false;
|
|
||||||
std::string locale;
|
|
||||||
|
|
||||||
section &operator=(const section &other)
|
|
||||||
{
|
|
||||||
has_value = other.has_value;
|
|
||||||
value = other.value;
|
|
||||||
has_color = other.has_color;
|
|
||||||
color = other.color;
|
|
||||||
has_condition = other.has_condition;
|
|
||||||
condition = other.condition;
|
|
||||||
condition_value = other.condition_value;
|
|
||||||
has_locale = other.has_locale;
|
|
||||||
locale = other.locale;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct format_sections
|
|
||||||
{
|
|
||||||
section first;
|
|
||||||
section second;
|
|
||||||
section third;
|
|
||||||
section fourth;
|
|
||||||
};
|
|
||||||
|
|
||||||
// copied from named_range.cpp, keep in sync
|
|
||||||
/// <summary>
|
|
||||||
/// Return a vector containing string split at each delim.
|
|
||||||
/// </summary>
|
|
||||||
/// <remark>
|
|
||||||
/// This should maybe be in a utility header so it can be used elsewhere.
|
|
||||||
/// </remarks>
|
|
||||||
std::vector<std::string> split_string(const std::string &string, char delim)
|
|
||||||
{
|
|
||||||
std::vector<std::string> split;
|
|
||||||
std::string::size_type previous_index = 0;
|
|
||||||
auto separator_index = string.find(delim);
|
|
||||||
|
|
||||||
while (separator_index != std::string::npos)
|
|
||||||
{
|
|
||||||
auto part = string.substr(previous_index, separator_index - previous_index);
|
|
||||||
split.push_back(part);
|
|
||||||
|
|
||||||
previous_index = separator_index + 1;
|
|
||||||
separator_index = string.find(delim, previous_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
split.push_back(string.substr(previous_index));
|
|
||||||
|
|
||||||
return split;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> split_string_any(const std::string &string, const std::string &delims)
|
|
||||||
{
|
|
||||||
std::vector<std::string> split;
|
|
||||||
std::string::size_type previous_index = 0;
|
|
||||||
auto separator_index = string.find_first_of(delims);
|
|
||||||
|
|
||||||
while (separator_index != std::string::npos)
|
|
||||||
{
|
|
||||||
auto part = string.substr(previous_index, separator_index - previous_index);
|
|
||||||
|
|
||||||
if (!part.empty())
|
|
||||||
{
|
|
||||||
split.push_back(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
previous_index = separator_index + 1;
|
|
||||||
separator_index = string.find_first_of(delims, previous_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
split.push_back(string.substr(previous_index));
|
|
||||||
|
|
||||||
return split;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_date_format(const std::string &format_string)
|
|
||||||
{
|
|
||||||
auto not_in = format_string.find_first_not_of("/-:, mMyYdDhHsSAP");
|
|
||||||
return not_in == std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_color(const std::string &color)
|
|
||||||
{
|
|
||||||
static const std::vector<std::string> *colors =
|
|
||||||
new std::vector<std::string>(
|
|
||||||
{
|
|
||||||
"Black",
|
|
||||||
"Green",
|
|
||||||
"White",
|
|
||||||
"Blue",
|
|
||||||
"Magenta",
|
|
||||||
"Yellow",
|
|
||||||
"Cyan",
|
|
||||||
"Red"
|
|
||||||
});
|
|
||||||
|
|
||||||
auto compare_color = [&](const std::string &other) {
|
|
||||||
if (color.size() != other.size()) return false;
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < color.size(); i++)
|
|
||||||
{
|
|
||||||
if (std::toupper(color[i]) != std::toupper(other[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return std::find_if(colors->begin(), colors->end(), compare_color) != colors->end();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_condition(const std::string &string, section &s)
|
|
||||||
{
|
|
||||||
s.has_condition = false;
|
|
||||||
s.condition = condition_type::invalid;
|
|
||||||
s.condition_value.clear();
|
|
||||||
|
|
||||||
if (string[0] == '<')
|
|
||||||
{
|
|
||||||
s.has_condition = true;
|
|
||||||
|
|
||||||
if (string[1] == '=')
|
|
||||||
{
|
|
||||||
s.condition = condition_type::less_or_equal;
|
|
||||||
s.condition_value = string.substr(2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s.condition = condition_type::less_than;
|
|
||||||
s.condition_value = string.substr(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (string[0] == '>')
|
|
||||||
{
|
|
||||||
s.has_condition = true;
|
|
||||||
|
|
||||||
if (string[1] == '=')
|
|
||||||
{
|
|
||||||
s.condition = condition_type::greater_or_equal;
|
|
||||||
s.condition_value = string.substr(2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s.condition = condition_type::greater_than;
|
|
||||||
s.condition_value = string.substr(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (string[0] == '=')
|
|
||||||
{
|
|
||||||
s.has_condition = true;
|
|
||||||
s.condition = condition_type::equal;
|
|
||||||
s.condition_value = string.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.has_condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_hex(char c)
|
|
||||||
{
|
|
||||||
return (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9');
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<int, std::string> known_locales()
|
|
||||||
{
|
|
||||||
const std::unordered_map<int, std::string> *all =
|
|
||||||
new std::unordered_map<int, std::string>(
|
|
||||||
{
|
|
||||||
{ 0x401, "Arabic - Saudi Arabia" },
|
|
||||||
{ 0x402, "Bulgarian" },
|
|
||||||
{ 0x403, "Catalan" },
|
|
||||||
{ 0x404, "Chinese - Taiwan" },
|
|
||||||
{ 0x405, "Czech" },
|
|
||||||
{ 0x406, "Danish" },
|
|
||||||
{ 0x407, "German - Germany" },
|
|
||||||
{ 0x408, "Greek" },
|
|
||||||
{ 0x409, "English - United States" },
|
|
||||||
{ 0x410, "Italian - Italy" },
|
|
||||||
{ 0x411, "Japanese" },
|
|
||||||
{ 0x412, "Korean" },
|
|
||||||
{ 0x413, "Dutch - Netherlands" },
|
|
||||||
{ 0x414, "Norwegian - Bokml" },
|
|
||||||
{ 0x415, "Polish" },
|
|
||||||
{ 0x416, "Portuguese - Brazil" },
|
|
||||||
{ 0x417, "Raeto-Romance" },
|
|
||||||
{ 0x418, "Romanian - Romania" },
|
|
||||||
{ 0x419, "Russian" },
|
|
||||||
{ 0x420, "Urdu" },
|
|
||||||
{ 0x421, "Indonesian" },
|
|
||||||
{ 0x422, "Ukrainian" },
|
|
||||||
{ 0x423, "Belarusian" },
|
|
||||||
{ 0x424, "Slovenian" },
|
|
||||||
{ 0x425, "Estonian" },
|
|
||||||
{ 0x426, "Latvian" },
|
|
||||||
{ 0x427, "Lithuanian" },
|
|
||||||
{ 0x428, "Tajik" },
|
|
||||||
{ 0x429, "Farsi - Persian" },
|
|
||||||
{ 0x430, "Sesotho (Sutu)" },
|
|
||||||
{ 0x431, "Tsonga" },
|
|
||||||
{ 0x432, "Setsuana" },
|
|
||||||
{ 0x433, "Venda" },
|
|
||||||
{ 0x434, "Xhosa" },
|
|
||||||
{ 0x435, "Zulu" },
|
|
||||||
{ 0x436, "Afrikaans" },
|
|
||||||
{ 0x437, "Georgian" },
|
|
||||||
{ 0x438, "Faroese" },
|
|
||||||
{ 0x439, "Hindi" },
|
|
||||||
{ 0x440, "Kyrgyz - Cyrillic" },
|
|
||||||
{ 0x441, "Swahili" },
|
|
||||||
{ 0x442, "Turkmen" },
|
|
||||||
{ 0x443, "Uzbek - Latin" },
|
|
||||||
{ 0x444, "Tatar" },
|
|
||||||
{ 0x445, "Bengali - India" },
|
|
||||||
{ 0x446, "Punjabi" },
|
|
||||||
{ 0x447, "Gujarati" },
|
|
||||||
{ 0x448, "Oriya" },
|
|
||||||
{ 0x449, "Tamil" },
|
|
||||||
{ 0x450, "Mongolian" },
|
|
||||||
{ 0x451, "Tibetan" },
|
|
||||||
{ 0x452, "Welsh" },
|
|
||||||
{ 0x453, "Khmer" },
|
|
||||||
{ 0x454, "Lao" },
|
|
||||||
{ 0x455, "Burmese" },
|
|
||||||
{ 0x456, "Galician" },
|
|
||||||
{ 0x457, "Konkani" },
|
|
||||||
{ 0x458, "Manipuri" },
|
|
||||||
{ 0x459, "Sindhi" },
|
|
||||||
{ 0x460, "Kashmiri" },
|
|
||||||
{ 0x461, "Nepali" },
|
|
||||||
{ 0x462, "Frisian - Netherlands" },
|
|
||||||
{ 0x464, "Filipino" },
|
|
||||||
{ 0x465, "Divehi; Dhivehi; Maldivian" },
|
|
||||||
{ 0x466, "Edo" },
|
|
||||||
{ 0x470, "Igbo - Nigeria" },
|
|
||||||
{ 0x474, "Guarani - Paraguay" },
|
|
||||||
{ 0x476, "Latin" },
|
|
||||||
{ 0x477, "Somali" },
|
|
||||||
{ 0x481, "Maori" },
|
|
||||||
{ 0x801, "Arabic - Iraq" },
|
|
||||||
{ 0x804, "Chinese - China" },
|
|
||||||
{ 0x807, "German - Switzerland" },
|
|
||||||
{ 0x809, "English - Great Britain" },
|
|
||||||
{ 0x810, "Italian - Switzerland" },
|
|
||||||
{ 0x813, "Dutch - Belgium" },
|
|
||||||
{ 0x814, "Norwegian - Nynorsk" },
|
|
||||||
{ 0x816, "Portuguese - Portugal" },
|
|
||||||
{ 0x818, "Romanian - Moldova" },
|
|
||||||
{ 0x819, "Russian - Moldova" },
|
|
||||||
{ 0x843, "Uzbek - Cyrillic" },
|
|
||||||
{ 0x845, "Bengali - Bangladesh" },
|
|
||||||
{ 0x850, "Mongolian" },
|
|
||||||
{ 0x1001, "Arabic - Libya" },
|
|
||||||
{ 0x1004, "Chinese - Singapore" },
|
|
||||||
{ 0x1007, "German - Luxembourg" },
|
|
||||||
{ 0x1009, "English - Canada" },
|
|
||||||
{ 0x1401, "Arabic - Algeria" },
|
|
||||||
{ 0x1404, "Chinese - Macau SAR" },
|
|
||||||
{ 0x1407, "German - Liechtenstein" },
|
|
||||||
{ 0x1409, "English - New Zealand" },
|
|
||||||
{ 0x1801, "Arabic - Morocco" },
|
|
||||||
{ 0x1809, "English - Ireland" },
|
|
||||||
{ 0x2001, "Arabic - Oman" },
|
|
||||||
{ 0x2009, "English - Jamaica" },
|
|
||||||
{ 0x2401, "Arabic - Yemen" },
|
|
||||||
{ 0x2409, "English - Caribbean" },
|
|
||||||
{ 0x2801, "Arabic - Syria" },
|
|
||||||
{ 0x2809, "English - Belize" },
|
|
||||||
{ 0x3001, "Arabic - Lebanon" },
|
|
||||||
{ 0x3009, "English - Zimbabwe" },
|
|
||||||
{ 0x3401, "Arabic - Kuwait" },
|
|
||||||
{ 0x3409, "English - Phillippines" },
|
|
||||||
{ 0x3801, "Arabic - United Arab Emirates" },
|
|
||||||
{ 0x4001, "Arabic - Qatar" }
|
|
||||||
});
|
|
||||||
|
|
||||||
return *all;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_locale(const std::string &locale_string)
|
|
||||||
{
|
|
||||||
auto hyphen_index = locale_string.find('-');
|
|
||||||
|
|
||||||
if (hyphen_index == std::string::npos)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string country = locale_string.substr(hyphen_index + 1);
|
|
||||||
|
|
||||||
if (country.empty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto c : country)
|
|
||||||
{
|
|
||||||
if (!is_hex(static_cast<char>(std::toupper(c))))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto index = std::stoi(country, 0, 16);
|
|
||||||
|
|
||||||
auto known_locales_ = known_locales();
|
|
||||||
|
|
||||||
if (known_locales_.find(index) == known_locales_.end())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string beginning = locale_string.substr(0, locale_string.find('-'));
|
|
||||||
|
|
||||||
if (beginning.empty() || beginning[0] != '$')
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beginning.size() == 1)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
beginning = beginning.substr(1);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
section parse_section(const std::string §ion_string)
|
|
||||||
{
|
|
||||||
if (section_string.empty())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("empty format");
|
|
||||||
}
|
|
||||||
|
|
||||||
section s;
|
|
||||||
|
|
||||||
std::string format_part;
|
|
||||||
std::string bracket_part;
|
|
||||||
|
|
||||||
std::vector<std::string> bracket_parts;
|
|
||||||
|
|
||||||
bool in_quotes = false;
|
|
||||||
bool in_brackets = false;
|
|
||||||
|
|
||||||
const std::vector<std::string> bracket_times = { "h", "hh", "m", "mm", "s", "ss" };
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < section_string.size(); i++)
|
|
||||||
{
|
|
||||||
if (!in_quotes && section_string[i] == '"')
|
|
||||||
{
|
|
||||||
format_part.push_back(section_string[i]);
|
|
||||||
in_quotes = true;
|
|
||||||
}
|
|
||||||
else if (in_quotes && section_string[i] == '"')
|
|
||||||
{
|
|
||||||
format_part.push_back(section_string[i]);
|
|
||||||
|
|
||||||
if (i < section_string.size() - 1 && section_string[i + 1] != '"')
|
|
||||||
{
|
|
||||||
in_quotes = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!in_brackets && section_string[i] == '[')
|
|
||||||
{
|
|
||||||
in_brackets = true;
|
|
||||||
|
|
||||||
for (auto bracket_time : bracket_times)
|
|
||||||
{
|
|
||||||
if (i < section_string.size() - bracket_time.size() &&
|
|
||||||
section_string.substr(i + 1, bracket_time.size()) == bracket_time)
|
|
||||||
{
|
|
||||||
in_brackets = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (in_brackets)
|
|
||||||
{
|
|
||||||
if (section_string[i] == ']')
|
|
||||||
{
|
|
||||||
in_brackets = false;
|
|
||||||
|
|
||||||
if (is_valid_color(bracket_part))
|
|
||||||
{
|
|
||||||
if (!s.has_color)
|
|
||||||
{
|
|
||||||
s.color = bracket_part;
|
|
||||||
s.has_color = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw std::runtime_error("two colors");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (is_valid_locale(bracket_part))
|
|
||||||
{
|
|
||||||
if (!s.has_locale)
|
|
||||||
{
|
|
||||||
s.locale = bracket_part;
|
|
||||||
s.has_locale = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw std::runtime_error("two locales");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (s.has_condition || !parse_condition(bracket_part, s))
|
|
||||||
{
|
|
||||||
throw std::runtime_error("invalid bracket format");
|
|
||||||
}
|
|
||||||
|
|
||||||
bracket_part.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bracket_part.push_back(section_string[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
format_part.push_back(section_string[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.value = format_part;
|
|
||||||
s.has_value = true;
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
format_sections parse_format_sections(const std::string &combined)
|
|
||||||
{
|
|
||||||
format_sections result = {};
|
|
||||||
|
|
||||||
auto split = split_string(combined, ';');
|
|
||||||
result.first = parse_section(split[0]);
|
|
||||||
|
|
||||||
if (!result.first.has_condition)
|
|
||||||
{
|
|
||||||
result.second = result.first;
|
|
||||||
result.third = result.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split.size() > 1)
|
|
||||||
{
|
|
||||||
result.second = parse_section(split[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split.size() > 2)
|
|
||||||
{
|
|
||||||
if (result.first.has_condition && !result.second.has_condition)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("first two sections should have conditions");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.third = parse_section(split[2]);
|
|
||||||
|
|
||||||
if (result.third.has_condition)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("third section shouldn't have a condition");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split.size() > 3)
|
|
||||||
{
|
|
||||||
if (result.first.has_condition)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("too many parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.fourth = parse_section(split[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split.size() > 4)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("too many parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_section(long double number, const section &format, xlnt::calendar base_date)
|
|
||||||
{
|
|
||||||
const std::string unquoted = "$+(:^'{<=-/)!&~}> ";
|
|
||||||
std::string format_temp = format.value;
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
if (is_date_format(format.value))
|
|
||||||
{
|
|
||||||
auto lower_string = [](const std::string &s) {
|
|
||||||
std::string lower;
|
|
||||||
lower.resize(s.size());
|
|
||||||
for (std::size_t i = 0; i < s.size(); i++)
|
|
||||||
lower[i] = static_cast<char>(std::tolower(s[i]));
|
|
||||||
return lower;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto value = format.value;
|
|
||||||
bool twelve_hour = false;
|
|
||||||
|
|
||||||
if (format.value.size() > 5 && lower_string(value).substr(format.value.size() - 5) == "am/pm")
|
|
||||||
{
|
|
||||||
twelve_hour = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string date_unquoted = ",-/: ";
|
|
||||||
const std::vector<std::string> dates = { "m", "mm", "mmm", "mmmmm", "mmmmmm", "d", "dd", "ddd", "dddd", "yy",
|
|
||||||
"yyyy", "h", "[h]", "hh", "m", "[m]", "mm", "s", "[s]", "ss" };
|
|
||||||
const std::vector<std::string> MonthNames = { "January", "February", "March",
|
|
||||||
"April", "May", "June", "July", "August", "September", "October", "November", "December" };
|
|
||||||
|
|
||||||
|
|
||||||
auto split = split_string_any(format.value, date_unquoted);
|
|
||||||
std::string::size_type index = 0, prev = 0;
|
|
||||||
auto d = xlnt::datetime::from_number(number, base_date);
|
|
||||||
bool processed_month = false;
|
|
||||||
|
|
||||||
for (auto part : split)
|
|
||||||
{
|
|
||||||
while (format.value.substr(index, part.size()) != part)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
part = lower_string(part);
|
|
||||||
|
|
||||||
auto between = format.value.substr(prev, index - prev);
|
|
||||||
|
|
||||||
if (between == "/" && part == "pm")
|
|
||||||
{
|
|
||||||
index += part.size();
|
|
||||||
prev = index;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(between);
|
|
||||||
|
|
||||||
if (part == "m" && !processed_month)
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.month));
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "mm" && !processed_month)
|
|
||||||
{
|
|
||||||
if (d.month < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.month));
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "mmm" && !processed_month)
|
|
||||||
{
|
|
||||||
result.append(MonthNames.at(static_cast<std::size_t>(d.month - 1)).substr(0, 3));
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "mmmm" && !processed_month)
|
|
||||||
{
|
|
||||||
result.append(MonthNames.at(static_cast<std::size_t>(d.month - 1)));
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "d")
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.day));
|
|
||||||
}
|
|
||||||
else if (part == "dd")
|
|
||||||
{
|
|
||||||
if (d.day < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.day));
|
|
||||||
}
|
|
||||||
else if (part == "yy")
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.year % 1000));
|
|
||||||
}
|
|
||||||
else if (part == "yyyy")
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.year));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (part == "h")
|
|
||||||
{
|
|
||||||
if (twelve_hour)
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.hour % 12));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.hour));
|
|
||||||
}
|
|
||||||
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "hh")
|
|
||||||
{
|
|
||||||
if (twelve_hour)
|
|
||||||
{
|
|
||||||
if (d.hour % 12 < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.hour % 12));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (d.hour < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.hour));
|
|
||||||
}
|
|
||||||
|
|
||||||
processed_month = true;
|
|
||||||
}
|
|
||||||
else if (part == "m")
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.minute));
|
|
||||||
}
|
|
||||||
else if (part == "mm")
|
|
||||||
{
|
|
||||||
if (d.minute < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.minute));
|
|
||||||
}
|
|
||||||
else if (part == "s")
|
|
||||||
{
|
|
||||||
result.append(std::to_string(d.second));
|
|
||||||
}
|
|
||||||
else if (part == "ss")
|
|
||||||
{
|
|
||||||
if (d.second < 10)
|
|
||||||
{
|
|
||||||
result.append("0");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(std::to_string(d.second));
|
|
||||||
}
|
|
||||||
else if (part == "am")
|
|
||||||
{
|
|
||||||
if (d.hour < 12)
|
|
||||||
{
|
|
||||||
result.append("AM");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.append("PM");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index += part.size();
|
|
||||||
prev = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < format.value.size())
|
|
||||||
{
|
|
||||||
result.append(format.value.substr(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (format.value.find_first_of("General") != std::string::npos || format.value == "0")
|
|
||||||
{
|
|
||||||
std::string before_text, end_text;
|
|
||||||
auto open_quote = format.value.find('"');
|
|
||||||
|
|
||||||
if (open_quote != std::string::npos)
|
|
||||||
{
|
|
||||||
auto close_quote = format.value.find('"', open_quote + 1);
|
|
||||||
auto quoted_text = format.value.substr(open_quote, close_quote - open_quote + 1);
|
|
||||||
|
|
||||||
if (open_quote == 0)
|
|
||||||
{
|
|
||||||
before_text = quoted_text.substr(1, quoted_text.size() - 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
end_text = quoted_text.substr(1, quoted_text.size() - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (number < 0)
|
|
||||||
{
|
|
||||||
before_text = std::string("-") + before_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = before_text;
|
|
||||||
|
|
||||||
if (number == static_cast<long long int>(number))
|
|
||||||
{
|
|
||||||
result.append(std::to_string(static_cast<long long int>(std::abs(number))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto number_string = std::to_string(std::abs(number));
|
|
||||||
|
|
||||||
while (number_string.find('.') != std::string::npos && number_string.back() == '0' && number_string.find('.') < number_string.size() - 1)
|
|
||||||
{
|
|
||||||
number_string.erase(number_string.end() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(number_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append(end_text);
|
|
||||||
}
|
|
||||||
else if (format.value.find("#,##0.00") != std::string::npos)
|
|
||||||
{
|
|
||||||
auto index = format.value.find("#,##0.00");
|
|
||||||
auto before_text = format.value.substr(0, index);
|
|
||||||
auto after_text = format.value.substr(index + 8);
|
|
||||||
bool minus_first = before_text.substr(0, 1) == "-";
|
|
||||||
|
|
||||||
if (minus_first)
|
|
||||||
{
|
|
||||||
before_text = before_text.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format.has_locale && format.locale == "$$-1009")
|
|
||||||
{
|
|
||||||
before_text = std::string(minus_first ? "-" : "") + "CA$" + before_text;
|
|
||||||
}
|
|
||||||
else if (format.has_locale && format.locale == "$€-407")
|
|
||||||
{
|
|
||||||
before_text = std::string(minus_first ? "-" : "") + "€" + before_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = before_text + std::to_string(number < 0 ? -number : number) + after_text;
|
|
||||||
|
|
||||||
auto decimal_pos = result.find('.');
|
|
||||||
|
|
||||||
if (decimal_pos != std::string::npos)
|
|
||||||
{
|
|
||||||
result[decimal_pos] = ',';
|
|
||||||
decimal_pos += 3;
|
|
||||||
|
|
||||||
while (decimal_pos < result.size())
|
|
||||||
{
|
|
||||||
result.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_section(const std::string &text, const section &format)
|
|
||||||
{
|
|
||||||
auto arobase_index = format.value.find('@');
|
|
||||||
|
|
||||||
std::string first_part, middle_part, last_part;
|
|
||||||
|
|
||||||
if (arobase_index != std::string::npos)
|
|
||||||
{
|
|
||||||
first_part = format.value.substr(0, arobase_index);
|
|
||||||
middle_part = text;
|
|
||||||
last_part = format.value.substr(arobase_index + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
first_part = format.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto unquote = [](std::string &s) {
|
|
||||||
if (!s.empty())
|
|
||||||
{
|
|
||||||
if (s.front() != '"' || s.back() != '"') return false;
|
|
||||||
s = s.substr(1, s.size() - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!unquote(first_part) || !unquote(last_part))
|
|
||||||
{
|
|
||||||
throw std::runtime_error(std::string("additional text must be enclosed in quotes: ") + format.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return first_part + middle_part + last_part;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_number(long double number, const std::string &format, xlnt::calendar base_date)
|
|
||||||
{
|
|
||||||
auto sections = parse_format_sections(format);
|
|
||||||
|
|
||||||
if (sections.first.has_condition)
|
|
||||||
{
|
|
||||||
auto right_side = std::stod(sections.first.condition_value);
|
|
||||||
|
|
||||||
if ((sections.first.condition == condition_type::equal && number == right_side) ||
|
|
||||||
(sections.first.condition == condition_type::greater_or_equal && number >= right_side) ||
|
|
||||||
(sections.first.condition == condition_type::greater_than && number > right_side) ||
|
|
||||||
(sections.first.condition == condition_type::less_or_equal && number <= right_side) ||
|
|
||||||
(sections.first.condition == condition_type::less_than && number < right_side))
|
|
||||||
{
|
|
||||||
return format_section(number, sections.first, base_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
right_side = std::stod(sections.second.condition_value);
|
|
||||||
|
|
||||||
if ((sections.second.condition == condition_type::equal && number == right_side) ||
|
|
||||||
(sections.second.condition == condition_type::greater_or_equal && number >= right_side) ||
|
|
||||||
(sections.second.condition == condition_type::greater_than && number > right_side) ||
|
|
||||||
(sections.second.condition == condition_type::less_or_equal && number <= right_side) ||
|
|
||||||
(sections.second.condition == condition_type::less_than && number < right_side))
|
|
||||||
{
|
|
||||||
return format_section(number, sections.second, base_date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (number > 0)
|
|
||||||
{
|
|
||||||
return format_section(number, sections.first, base_date);
|
|
||||||
}
|
|
||||||
else if (number < 0)
|
|
||||||
{
|
|
||||||
return format_section(number, sections.second, base_date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// number == 0 or default condition
|
|
||||||
return format_section(number, sections.third, base_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_text(const std::string &text, const std::string &format)
|
|
||||||
{
|
|
||||||
if (format == "General" || format == "@") return text;
|
|
||||||
auto sections = parse_format_sections(format);
|
|
||||||
return format_section(text, sections.fourth);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace xlnt {
|
namespace xlnt {
|
||||||
|
@ -1247,30 +387,37 @@ std::size_t number_format::get_id() const
|
||||||
|
|
||||||
bool number_format::is_date_format() const
|
bool number_format::is_date_format() const
|
||||||
{
|
{
|
||||||
if (format_string_ != "General")
|
detail::number_format_parser p(format_string_);
|
||||||
|
p.parse();
|
||||||
|
auto parsed = p.get_result();
|
||||||
|
|
||||||
|
bool any_datetime = false;
|
||||||
|
bool any_timedelta = false;
|
||||||
|
|
||||||
|
for (const auto §ion : parsed)
|
||||||
{
|
{
|
||||||
try
|
if (section.is_datetime)
|
||||||
{
|
{
|
||||||
auto sections = parse_format_sections(format_string_);
|
any_datetime = true;
|
||||||
return ::is_date_format(sections.first.value);
|
|
||||||
}
|
}
|
||||||
catch (std::exception)
|
|
||||||
|
if (section.is_timedelta)
|
||||||
{
|
{
|
||||||
return false;
|
any_timedelta = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return any_datetime && !any_timedelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string number_format::format(const std::string &text) const
|
std::string number_format::format(const std::string &text) const
|
||||||
{
|
{
|
||||||
return format_text(text, format_string_);
|
return detail::number_formatter(format_string_, calendar::windows_1900).format_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string number_format::format(long double number, calendar base_date) const
|
std::string number_format::format(long double number, calendar base_date) const
|
||||||
{
|
{
|
||||||
return format_number(number, format_string_, base_date);
|
return detail::number_formatter(format_string_, base_date).format_number(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xlnt
|
} // namespace xlnt
|
||||||
|
|
|
@ -16,10 +16,12 @@ public:
|
||||||
nf.set_format_string("\"positive\"General;\"negative\"General");
|
nf.set_format_string("\"positive\"General;\"negative\"General");
|
||||||
auto formatted = nf.format(3.14, xlnt::calendar::windows_1900);
|
auto formatted = nf.format(3.14, xlnt::calendar::windows_1900);
|
||||||
TS_ASSERT_EQUALS(formatted, "positive3.14");
|
TS_ASSERT_EQUALS(formatted, "positive3.14");
|
||||||
|
|
||||||
nf.set_format_string("\"positive\"General;\"negative\"General");
|
|
||||||
formatted = nf.format(-3.14, xlnt::calendar::windows_1900);
|
formatted = nf.format(-3.14, xlnt::calendar::windows_1900);
|
||||||
TS_ASSERT_EQUALS(formatted, "-negative3.14");
|
TS_ASSERT_EQUALS(formatted, "negative3.14");
|
||||||
|
|
||||||
|
nf.set_format_string("\"any\"General");
|
||||||
|
formatted = nf.format(-3.14, xlnt::calendar::windows_1900);
|
||||||
|
TS_ASSERT_EQUALS(formatted, "-any3.14");
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_simple_date()
|
void test_simple_date()
|
||||||
|
@ -311,14 +313,13 @@ public:
|
||||||
{
|
{
|
||||||
xlnt::number_format nf;
|
xlnt::number_format nf;
|
||||||
|
|
||||||
nf.set_format_string("[$€-407]-#,##0.00");
|
nf.set_format_string("[$€-407]#,##0.00");
|
||||||
auto formatted = nf.format(1.2, xlnt::calendar::windows_1900);
|
auto formatted = nf.format(-45000.1, xlnt::calendar::windows_1900);
|
||||||
TS_ASSERT_EQUALS(formatted, "-€1,20");
|
TS_ASSERT_EQUALS(formatted, "-€45,000.10");
|
||||||
|
|
||||||
nf.set_format_string("[$$-1009]#,##0.00");
|
nf.set_format_string("[$$-1009]#,##0.00");
|
||||||
formatted = nf.format(1.2, xlnt::calendar::windows_1900);
|
formatted = nf.format(-45000.1, xlnt::calendar::windows_1900);
|
||||||
TS_ASSERT_EQUALS(formatted, "CA$1,20");
|
TS_ASSERT_EQUALS(formatted, "-$45,000.10");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_bad_country()
|
void test_bad_country()
|
||||||
|
@ -356,21 +357,9 @@ public:
|
||||||
{
|
{
|
||||||
xlnt::number_format nf;
|
xlnt::number_format nf;
|
||||||
|
|
||||||
nf.set_format_string("");
|
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
|
||||||
|
|
||||||
nf.set_format_string(";;;");
|
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
|
||||||
|
|
||||||
nf.set_format_string("[=1]\"first\"General;\"second\"General;\"third\"General");
|
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
|
||||||
|
|
||||||
nf.set_format_string("[=1]\"first\"General;[=2]\"second\"General;[=3]\"third\"General");
|
nf.set_format_string("[=1]\"first\"General;[=2]\"second\"General;[=3]\"third\"General");
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
||||||
|
|
||||||
nf.set_format_string("[=1]\"first\"General;[=2]\"second\"General;\"third\"General;\"fourth\"General");
|
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
|
||||||
|
|
||||||
nf.set_format_string("\"first\"General;\"second\"General;\"third\"General;\"fourth\"General;\"fifth\"General");
|
nf.set_format_string("\"first\"General;\"second\"General;\"third\"General;\"fourth\"General;\"fifth\"General");
|
||||||
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
|
||||||
}
|
}
|
||||||
|
@ -379,7 +368,7 @@ public:
|
||||||
{
|
{
|
||||||
TS_ASSERT_EQUALS(xlnt::number_format::text().format("a"), "a");
|
TS_ASSERT_EQUALS(xlnt::number_format::text().format("a"), "a");
|
||||||
TS_ASSERT_EQUALS(xlnt::number_format::number().format(1, xlnt::calendar::windows_1900), "1");
|
TS_ASSERT_EQUALS(xlnt::number_format::number().format(1, xlnt::calendar::windows_1900), "1");
|
||||||
TS_ASSERT_EQUALS(xlnt::number_format::number_comma_separated1().format(1, xlnt::calendar::windows_1900), "1,00");
|
TS_ASSERT_EQUALS(xlnt::number_format::number_comma_separated1().format(12345.67, xlnt::calendar::windows_1900), "12,345.67");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
auto datetime = xlnt::datetime(2016, 6, 24, 0, 45, 58);
|
auto datetime = xlnt::datetime(2016, 6, 24, 0, 45, 58);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user