test some number formatting edge cases

This commit is contained in:
Thomas Fussell 2016-07-11 18:10:57 -04:00
parent 8970bfaffb
commit 053508e8b7
2 changed files with 186 additions and 18 deletions

View File

@ -159,7 +159,6 @@ bool format_condition::satisfied_by(long double number) const
return number < value; return number < value;
case condition_type::not_equal: case condition_type::not_equal:
return number != value; return number != value;
case condition_type::none:
default: default:
return false; return false;
} }
@ -198,9 +197,6 @@ void number_format_parser::parse()
break; break;
case number_format_token::token_type::bad:
throw std::runtime_error("bad format");
case number_format_token::token_type::color: case number_format_token::token_type::color:
if (section.color != format_color::none if (section.color != format_color::none
|| section.condition.type != format_condition::condition_type::none || section.condition.type != format_condition::condition_type::none
@ -716,6 +712,12 @@ number_format_token number_format_parser::parse_next_token()
break; break;
case '*':
token.type = number_format_token::token_type::fill;
token.string.push_back(format_string_[position_++]);
break;
case '0': case '0':
case '#': case '#':
case '?': case '?':
@ -783,10 +785,18 @@ number_format_token number_format_parser::parse_next_token()
while (end != std::string::npos && format_string_[end - 1] == '\\') while (end != std::string::npos && format_string_[end - 1] == '\\')
{ {
token.string.append(format_string_.substr(start, end - start - 1));
token.string.push_back('"');
position_ = end + 1;
start = position_;
end = format_string_.find('"', position_); end = format_string_.find('"', position_);
} }
token.string = format_string_.substr(start, end - start); if (end != start)
{
token.string.append(format_string_.substr(start, end - start));
}
position_ = end + 1; position_ = end + 1;
break; break;
@ -910,9 +920,9 @@ format_placeholders number_format_parser::parse_placeholders(const std::string &
if (!comma_indices.empty()) if (!comma_indices.empty())
{ {
std::size_t i = placeholders_string.size(); std::size_t i = placeholders_string.size() - 1;
while (i == comma_indices.back()) while (!comma_indices.empty() && i == comma_indices.back())
{ {
++p.thousands_scale; ++p.thousands_scale;
--i; --i;
@ -1140,6 +1150,11 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, lo
number *= 100; number *= 100;
} }
if (p.thousands_scale > 0)
{
number /= std::pow(1000, p.thousands_scale);
}
auto integer_part = static_cast<int>(number); auto integer_part = static_cast<int>(number);
if (p.type == format_placeholders::placeholders_type::integer_only if (p.type == format_placeholders::placeholders_type::integer_only
@ -1365,6 +1380,9 @@ std::string number_formatter::format_number(const format_code &format, long doub
} }
bool improper_fraction = true; bool improper_fraction = true;
std::size_t fill_index = 0;
bool fill = false;
std::string fill_character;
for (std::size_t i = 0; i < format.parts.size(); ++i) for (std::size_t i = 0; i < format.parts.size(); ++i)
{ {
@ -1378,6 +1396,11 @@ std::string number_formatter::format_number(const format_code &format, long doub
case template_part::template_type::text: case template_part::template_type::text:
result.append(part.string); result.append(part.string);
break; break;
case template_part::template_type::fill:
fill = true;
fill_index = result.size();
fill_character = part.string;
break;
case template_part::template_type::general: case template_part::template_type::general:
{ {
if (part.placeholders.type == format_placeholders::placeholders_type::fraction_integer) if (part.placeholders.type == format_placeholders::placeholders_type::fraction_integer)
@ -1504,6 +1527,28 @@ std::string number_formatter::format_number(const format_code &format, long doub
} }
} }
const std::size_t width = 11;
if (fill && result.size() < width)
{
auto remaining = width - result.size();
std::string fill_string(remaining, fill_character.front());
// A UTF-8 character could be multiple bytes
if (fill_character.size() > 1)
{
fill_string.clear();
for (std::size_t i = 0; i < remaining; ++i)
{
fill_string.append(fill_character);
}
}
result = result.substr(0, fill_index) + fill_string + result.substr(fill_index);
}
return result; return result;
} }

View File

@ -22,6 +22,14 @@ public:
nf.set_format_string("\"any\"General"); nf.set_format_string("\"any\"General");
formatted = nf.format(-3.14, xlnt::calendar::windows_1900); formatted = nf.format(-3.14, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "-any3.14"); TS_ASSERT_EQUALS(formatted, "-any3.14");
nf.set_format_string("\"positive\"General;\"negative\"General;\"zero\"General");
formatted = nf.format(3.14, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "positive3.14");
formatted = nf.format(-3.14, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "negative3.14");
formatted = nf.format(0, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "zero0");
} }
void test_simple_date() void test_simple_date()
@ -272,6 +280,14 @@ public:
formatted = nf.format(2, xlnt::calendar::windows_1900); formatted = nf.format(2, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "third2"); TS_ASSERT_EQUALS(formatted, "third2");
nf.set_format_string("[>=5]\"first\"General");
formatted = nf.format(4, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "###########");
nf.set_format_string("[>=5]\"first\"General;[>=4]\"second\"General");
formatted = nf.format(3, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "###########");
nf.set_format_string("[<1]\"first\"General;[<5]\"second\"General;\"third\"General"); nf.set_format_string("[<1]\"first\"General;[<5]\"second\"General;\"third\"General");
formatted = nf.format(0, xlnt::calendar::windows_1900); formatted = nf.format(0, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "first0"); TS_ASSERT_EQUALS(formatted, "first0");
@ -307,6 +323,36 @@ public:
TS_ASSERT_EQUALS(formatted, "third3"); TS_ASSERT_EQUALS(formatted, "third3");
formatted = nf.format(0, xlnt::calendar::windows_1900); formatted = nf.format(0, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "third0"); TS_ASSERT_EQUALS(formatted, "third0");
nf.set_format_string("[<>1]\"first\"General;[<>2]\"second\"General");
formatted = nf.format(2, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "first2");
formatted = nf.format(1, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "second1");
}
void test_space()
{
xlnt::number_format nf;
nf.set_format_string("_(General_)");
auto formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, " 6 ");
}
void test_fill()
{
xlnt::number_format nf;
nf.set_format_string("*-General");
auto formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "----------6");
nf.set_format_string("General*-");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6----------");
nf.set_format_string("\\a*-\\b");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "a---------b");
} }
void test_locale_currency() void test_locale_currency()
@ -353,6 +399,65 @@ public:
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);
} }
void test_escaped_quote_string()
{
xlnt::number_format nf;
nf.set_format_string("\"\\\"\"General");
auto formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "\"6");
}
void test_thousands_scale()
{
xlnt::number_format nf;
nf.set_format_string("#,");
auto formatted = nf.format(61234, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "61");
}
void test_colors()
{
xlnt::number_format nf;
nf.set_format_string("[Black]#");
auto formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Black]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Blue]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Green]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Red]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Cyan]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Magenta]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Yellow]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
nf.set_format_string("[Color15]#");
formatted = nf.format(6, xlnt::calendar::windows_1900);
TS_ASSERT_EQUALS(formatted, "6");
}
void test_bad_format() void test_bad_format()
{ {
xlnt::number_format nf; xlnt::number_format nf;
@ -362,6 +467,24 @@ public:
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);
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("[Redd]");
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
nf.set_format_string("[$1]#");
TS_ASSERT_THROWS(nf.format(1.2, xlnt::calendar::windows_1900), std::runtime_error);
nf.set_format_string("Gee");
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);
} }
void format_and_test(const xlnt::number_format &nf, const std::array<std::string, 4> &expect) void format_and_test(const xlnt::number_format &nf, const std::array<std::string, 4> &expect)
@ -404,7 +527,7 @@ public:
// #,##0.00 // #,##0.00
void test_builtin_format_4() void test_builtin_format_4()
{ {
format_and_test(xlnt::number_format::from_builtin_id(4), {{"42,503.12", "-42,503.12", "0.00", "text"}}); format_and_test(xlnt::number_format::number_comma_separated1(), {{"42,503.12", "-42,503.12", "0.00", "text"}});
} }
// #,##0;-#,##0 // #,##0;-#,##0
@ -464,49 +587,49 @@ public:
// mm-dd-yy // mm-dd-yy
void test_builtin_format_14() void test_builtin_format_14()
{ {
format_and_test(xlnt::number_format::from_builtin_id(14), {{"05-13-16", "###########", "01-00-00", "text"}}); format_and_test(xlnt::number_format::date_xlsx14(), {{"05-13-16", "###########", "01-00-00", "text"}});
} }
// d-mmm-yy // d-mmm-yy
void test_builtin_format_15() void test_builtin_format_15()
{ {
format_and_test(xlnt::number_format::from_builtin_id(15), {{"13-May-16", "###########", "0-Jan-00", "text"}}); format_and_test(xlnt::number_format::date_xlsx15(), {{"13-May-16", "###########", "0-Jan-00", "text"}});
} }
// d-mmm // d-mmm
void test_builtin_format_16() void test_builtin_format_16()
{ {
format_and_test(xlnt::number_format::from_builtin_id(16), {{"13-May", "###########", "0-Jan", "text"}}); format_and_test(xlnt::number_format::date_xlsx16(), {{"13-May", "###########", "0-Jan", "text"}});
} }
// mmm-yy // mmm-yy
void test_builtin_format_17() void test_builtin_format_17()
{ {
format_and_test(xlnt::number_format::from_builtin_id(17), {{"May-16", "###########", "Jan-00", "text"}}); format_and_test(xlnt::number_format::date_xlsx17(), {{"May-16", "###########", "Jan-00", "text"}});
} }
// h:mm AM/PM // h:mm AM/PM
void test_builtin_format_18() void test_builtin_format_18()
{ {
format_and_test(xlnt::number_format::from_builtin_id(18), {{"2:57 AM", "###########", "12:00 AM", "text"}}); format_and_test(xlnt::number_format::date_time1(), {{"2:57 AM", "###########", "12:00 AM", "text"}});
} }
// h:mm:ss AM/PM // h:mm:ss AM/PM
void test_builtin_format_19() void test_builtin_format_19()
{ {
format_and_test(xlnt::number_format::from_builtin_id(19), {{"2:57:42 AM", "###########", "12:00:00 AM", "text"}}); format_and_test(xlnt::number_format::date_time2(), {{"2:57:42 AM", "###########", "12:00:00 AM", "text"}});
} }
// h:mm // h:mm
void test_builtin_format_20() void test_builtin_format_20()
{ {
format_and_test(xlnt::number_format::from_builtin_id(20), {{"2:57", "###########", "0:00", "text"}}); format_and_test(xlnt::number_format::date_time3(), {{"2:57", "###########", "0:00", "text"}});
} }
// h:mm:ss // h:mm:ss
void test_builtin_format_21() void test_builtin_format_21()
{ {
format_and_test(xlnt::number_format::from_builtin_id(21), {{"2:57:42", "###########", "0:00:00", "text"}}); format_and_test(xlnt::number_format::date_time4(), {{"2:57:42", "###########", "0:00:00", "text"}});
} }
// m/d/yy h:mm // m/d/yy h:mm