mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
implement fractional number format, continue implementing tests for builtin formats
This commit is contained in:
parent
4aeab7bdfa
commit
5147a282af
|
@ -474,9 +474,29 @@ void number_format_parser::finalize()
|
||||||
bool exponent = false;
|
bool exponent = false;
|
||||||
std::size_t exponent_index = 0;
|
std::size_t exponent_index = 0;
|
||||||
|
|
||||||
|
bool fraction = false;
|
||||||
|
std::size_t fraction_denominator_index = 0;
|
||||||
|
std::size_t fraction_numerator_index = 0;
|
||||||
|
|
||||||
|
bool seconds = false;
|
||||||
|
bool fractional_seconds = false;
|
||||||
|
std::size_t seconds_index = 0;
|
||||||
|
|
||||||
for (std::size_t i = 0; i < code.parts.size(); ++i)
|
for (std::size_t i = 0; i < code.parts.size(); ++i)
|
||||||
{
|
{
|
||||||
const auto &part = code.parts[i];
|
const auto &part = code.parts[i];
|
||||||
|
|
||||||
|
if (i > 0
|
||||||
|
&& i + 1 < code.parts.size()
|
||||||
|
&& part.type == template_part::template_type::text
|
||||||
|
&& part.string == "/"
|
||||||
|
&& code.parts[i - 1].placeholders.type == format_placeholders::placeholders_type::integer_part
|
||||||
|
&& code.parts[i + 1].placeholders.type == format_placeholders::placeholders_type::integer_part)
|
||||||
|
{
|
||||||
|
fraction = true;
|
||||||
|
fraction_numerator_index = i - 1;
|
||||||
|
fraction_denominator_index = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (part.placeholders.type == format_placeholders::placeholders_type::integer_part)
|
if (part.placeholders.type == format_placeholders::placeholders_type::integer_part)
|
||||||
{
|
{
|
||||||
|
@ -498,6 +518,18 @@ void number_format_parser::finalize()
|
||||||
{
|
{
|
||||||
percentage = true;
|
percentage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part.type == template_part::template_type::second
|
||||||
|
|| part.type == template_part::template_type::second_leading_zero)
|
||||||
|
{
|
||||||
|
seconds = true;
|
||||||
|
seconds_index = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds && part.placeholders.type == format_placeholders::placeholders_type::fractional_part)
|
||||||
|
{
|
||||||
|
fractional_seconds = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (part.type == template_part::template_type::month_number
|
if (part.type == template_part::template_type::month_number
|
||||||
|| part.type == template_part::template_type::month_number_leading_zero)
|
|| part.type == template_part::template_type::month_number_leading_zero)
|
||||||
|
@ -547,7 +579,8 @@ void number_format_parser::finalize()
|
||||||
|
|
||||||
if (integer_part && !fractional_part)
|
if (integer_part && !fractional_part)
|
||||||
{
|
{
|
||||||
code.parts[integer_part_index].placeholders.type = format_placeholders::placeholders_type::integer_only;
|
code.parts[integer_part_index].placeholders.type =
|
||||||
|
format_placeholders::placeholders_type::integer_only;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integer_part && fractional_part && percentage)
|
if (integer_part && fractional_part && percentage)
|
||||||
|
@ -568,6 +601,36 @@ void number_format_parser::finalize()
|
||||||
code.parts[i].placeholders.scientific = true;
|
code.parts[i].placeholders.scientific = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fraction)
|
||||||
|
{
|
||||||
|
code.parts[fraction_numerator_index].placeholders.type =
|
||||||
|
format_placeholders::placeholders_type::fraction_numerator;
|
||||||
|
code.parts[fraction_denominator_index].placeholders.type =
|
||||||
|
format_placeholders::placeholders_type::fraction_denominator;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < code.parts.size(); ++i)
|
||||||
|
{
|
||||||
|
if (code.parts[i].placeholders.type ==
|
||||||
|
format_placeholders::placeholders_type::integer_part)
|
||||||
|
{
|
||||||
|
code.parts[i].placeholders.type =
|
||||||
|
format_placeholders::placeholders_type::fraction_integer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fractional_seconds)
|
||||||
|
{
|
||||||
|
if (code.parts[seconds_index].type == template_part::template_type::second)
|
||||||
|
{
|
||||||
|
code.parts[seconds_index].type = template_part::template_type::second_fractional;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
code.parts[seconds_index].type = template_part::template_type::second_leading_zero_fractional;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate();
|
validate();
|
||||||
|
@ -1042,7 +1105,12 @@ std::string number_formatter::format_text(const std::string &text)
|
||||||
{
|
{
|
||||||
if (format_.size() < 4)
|
if (format_.size() < 4)
|
||||||
{
|
{
|
||||||
return format_text(format_[0], text);
|
format_code temp;
|
||||||
|
template_part temp_part;
|
||||||
|
temp_part.type = template_part::template_type::general;
|
||||||
|
temp_part.placeholders.type = format_placeholders::placeholders_type::general;
|
||||||
|
temp.parts.push_back(temp_part);
|
||||||
|
return format_text(temp, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return format_text(format_[3], text);
|
return format_text(format_[3], text);
|
||||||
|
@ -1075,7 +1143,8 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, lo
|
||||||
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
|
||||||
|| p.type == format_placeholders::placeholders_type::integer_part)
|
|| p.type == format_placeholders::placeholders_type::integer_part
|
||||||
|
|| p.type == format_placeholders::placeholders_type::fraction_integer)
|
||||||
{
|
{
|
||||||
if (p.scientific)
|
if (p.scientific)
|
||||||
{
|
{
|
||||||
|
@ -1200,6 +1269,45 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, lo
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string number_formatter::fill_fraction_placeholders(const format_placeholders &numerator,
|
||||||
|
const format_placeholders &denominator, long double number, bool improper)
|
||||||
|
{
|
||||||
|
auto fractional_part = number - static_cast<int>(number);
|
||||||
|
auto original_fractional_part = fractional_part;
|
||||||
|
fractional_part *= 10;
|
||||||
|
|
||||||
|
while (std::abs(fractional_part - static_cast<int>(fractional_part)) > 0.000001
|
||||||
|
&& std::abs(fractional_part - static_cast<int>(fractional_part)) < 0.999999)
|
||||||
|
{
|
||||||
|
fractional_part *= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
fractional_part = static_cast<int>(fractional_part);
|
||||||
|
auto denominator_digits = denominator.num_zeros + denominator.num_optionals + denominator.num_spaces;
|
||||||
|
// auto denominator_digits = static_cast<std::size_t>(std::ceil(std::log10(fractional_part)));
|
||||||
|
|
||||||
|
auto lower = static_cast<int>(std::pow(10, denominator_digits - 1));
|
||||||
|
auto upper = static_cast<int>(std::pow(10, denominator_digits));
|
||||||
|
auto best_denominator = lower;
|
||||||
|
auto best_difference = 1000.0L;
|
||||||
|
|
||||||
|
for (int i = lower; i < upper; ++i)
|
||||||
|
{
|
||||||
|
auto numerator_full = original_fractional_part * i;
|
||||||
|
auto numerator_rounded = static_cast<int>(std::round(numerator_full));
|
||||||
|
auto difference = std::fabs(original_fractional_part - (numerator_rounded / static_cast<long double>(i)));
|
||||||
|
|
||||||
|
if (difference < best_difference)
|
||||||
|
{
|
||||||
|
best_difference = difference;
|
||||||
|
best_denominator = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto numerator_rounded = static_cast<int>(std::round(original_fractional_part * best_denominator));
|
||||||
|
return std::to_string(numerator_rounded) + "/" + std::to_string(best_denominator);
|
||||||
|
}
|
||||||
|
|
||||||
std::string number_formatter::format_number(const format_code &format, long double number)
|
std::string number_formatter::format_number(const format_code &format, long double number)
|
||||||
{
|
{
|
||||||
static const std::vector<std::string> *month_names =
|
static const std::vector<std::string> *month_names =
|
||||||
|
@ -1224,19 +1332,44 @@ std::string number_formatter::format_number(const format_code &format, long doub
|
||||||
if (number < 0)
|
if (number < 0)
|
||||||
{
|
{
|
||||||
result.push_back('-');
|
result.push_back('-');
|
||||||
|
|
||||||
|
if (format.is_datetime)
|
||||||
|
{
|
||||||
|
return std::string(11, '#');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
number = std::fabs(number);
|
number = std::fabs(number);
|
||||||
|
|
||||||
xlnt::datetime dt(0, 0, 0);
|
xlnt::datetime dt(0, 1, 0);
|
||||||
|
std::size_t hour = 0;
|
||||||
|
|
||||||
if (format.is_datetime)
|
if (format.is_datetime)
|
||||||
{
|
{
|
||||||
dt = xlnt::datetime::from_number(number, calendar_);
|
if (number != 0)
|
||||||
}
|
{
|
||||||
|
dt = xlnt::datetime::from_number(number, calendar_);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &part : format.parts)
|
hour = dt.hour;
|
||||||
|
|
||||||
|
if (format.twelve_hour)
|
||||||
|
{
|
||||||
|
hour %= 12;
|
||||||
|
|
||||||
|
if (hour == 0)
|
||||||
|
{
|
||||||
|
hour = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool improper_fraction = true;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < format.parts.size(); ++i)
|
||||||
{
|
{
|
||||||
|
const auto &part = format.parts[i];
|
||||||
|
|
||||||
switch (part.type)
|
switch (part.type)
|
||||||
{
|
{
|
||||||
case template_part::template_type::space:
|
case template_part::template_type::space:
|
||||||
|
@ -1247,7 +1380,28 @@ std::string number_formatter::format_number(const format_code &format, long doub
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::general:
|
case template_part::template_type::general:
|
||||||
{
|
{
|
||||||
result.append(fill_placeholders(part.placeholders, number));
|
if (part.placeholders.type == format_placeholders::placeholders_type::fraction_integer)
|
||||||
|
{
|
||||||
|
improper_fraction = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.placeholders.type == format_placeholders::placeholders_type::fraction_numerator)
|
||||||
|
{
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
if (number == 0)
|
||||||
|
{
|
||||||
|
result.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(fill_fraction_placeholders(part.placeholders, format.parts[i].placeholders, number, improper_fraction));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.append(fill_placeholders(part.placeholders, number));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case template_part::template_type::day_number:
|
case template_part::template_type::day_number:
|
||||||
|
@ -1290,15 +1444,15 @@ std::string number_formatter::format_number(const format_code &format, long doub
|
||||||
result.append(std::to_string(dt.year));
|
result.append(std::to_string(dt.year));
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::hour:
|
case template_part::template_type::hour:
|
||||||
result.append(std::to_string(format.twelve_hour ? dt.hour % 12 : dt.hour));
|
result.append(std::to_string(hour));
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::hour_leading_zero:
|
case template_part::template_type::hour_leading_zero:
|
||||||
if (format.twelve_hour ? dt.hour % 12 : dt.hour < 10)
|
if (hour < 10)
|
||||||
{
|
{
|
||||||
result.push_back('0');
|
result.push_back('0');
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(std::to_string(format.twelve_hour ? dt.hour % 12 : dt.hour));
|
result.append(std::to_string(hour));
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::minute:
|
case template_part::template_type::minute:
|
||||||
result.append(std::to_string(dt.minute));
|
result.append(std::to_string(dt.minute));
|
||||||
|
@ -1312,9 +1466,20 @@ std::string number_formatter::format_number(const format_code &format, long doub
|
||||||
result.append(std::to_string(dt.minute));
|
result.append(std::to_string(dt.minute));
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::second:
|
case template_part::template_type::second:
|
||||||
|
result.append(std::to_string(dt.second + (dt.microsecond > 500000 ? 1 : 0)));
|
||||||
|
break;
|
||||||
|
case template_part::template_type::second_fractional:
|
||||||
result.append(std::to_string(dt.second));
|
result.append(std::to_string(dt.second));
|
||||||
break;
|
break;
|
||||||
case template_part::template_type::second_leading_zero:
|
case template_part::template_type::second_leading_zero:
|
||||||
|
if ((dt.second + (dt.microsecond > 500000 ? 1 : 0)) < 10)
|
||||||
|
{
|
||||||
|
result.push_back('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(std::to_string(dt.second + (dt.microsecond > 500000 ? 1 : 0)));
|
||||||
|
break;
|
||||||
|
case template_part::template_type::second_leading_zero_fractional:
|
||||||
if (dt.second < 10)
|
if (dt.second < 10)
|
||||||
{
|
{
|
||||||
result.push_back('0');
|
result.push_back('0');
|
||||||
|
|
|
@ -303,7 +303,9 @@ struct template_part
|
||||||
minute,
|
minute,
|
||||||
minute_leading_zero,
|
minute_leading_zero,
|
||||||
second,
|
second,
|
||||||
|
second_fractional,
|
||||||
second_leading_zero,
|
second_leading_zero,
|
||||||
|
second_leading_zero_fractional,
|
||||||
am_pm,
|
am_pm,
|
||||||
a_p,
|
a_p,
|
||||||
elapsed_hours,
|
elapsed_hours,
|
||||||
|
@ -358,6 +360,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string fill_placeholders(const format_placeholders &p, long double number);
|
std::string fill_placeholders(const format_placeholders &p, long double number);
|
||||||
|
std::string fill_fraction_placeholders(const format_placeholders &numerator,
|
||||||
|
const format_placeholders &denominator, long double number, bool improper);
|
||||||
std::string format_number(const format_code &format, 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);
|
std::string format_text(const format_code &format, const std::string &text);
|
||||||
|
|
||||||
|
|
|
@ -450,67 +450,67 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// # ?/?
|
// # ?/?
|
||||||
void _test_builtin_format_12()
|
void test_builtin_format_12()
|
||||||
{
|
{
|
||||||
format_and_test(xlnt::number_format::from_builtin_id(12), {{"42503 1/8", "-42503 1/8", "0", "text"}});
|
format_and_test(xlnt::number_format::from_builtin_id(12), {{"42503 1/8", "-42503 1/8", "0", "text"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// # ??/??
|
// # ??/??
|
||||||
void _test_builtin_format_13()
|
void test_builtin_format_13()
|
||||||
{
|
{
|
||||||
format_and_test(xlnt::number_format::from_builtin_id(13), {{"42503 10/81", "-42503 10/81", "0", "text"}});
|
format_and_test(xlnt::number_format::from_builtin_id(13), {{"42503 10/81", "-42503 10/81", "0", "text"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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::from_builtin_id(14), {{"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::from_builtin_id(15), {{"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::from_builtin_id(16), {{"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::from_builtin_id(17), {{"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::from_builtin_id(18), {{"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::from_builtin_id(19), {{"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::from_builtin_id(20), {{"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::from_builtin_id(21), {{"2:57:42", "###########", "0:00:00", "text"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// m/d/yy h:mm
|
// m/d/yy h:mm
|
||||||
void _test_builtin_format_22()
|
void test_builtin_format_22()
|
||||||
{
|
{
|
||||||
format_and_test(xlnt::number_format::from_builtin_id(22), {{"5/13/16 2:57", "###########", "1/0/00 0:00", "text"}});
|
format_and_test(xlnt::number_format::from_builtin_id(22), {{"5/13/16 2:57", "###########", "1/0/00 0:00", "text"}});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user