diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 064844ac..ba5e6d21 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -61,10 +61,6 @@ class cell public: static const std::unordered_map ErrorCodes; - static std::string check_string(const std::string &value); - static std::string check_numeric(const std::string &value); - static std::string check_error(const std::string &value); - cell(); cell(worksheet ws, const cell_reference &reference); cell(worksheet ws, const cell_reference &reference, const value &initial_value); diff --git a/source/cell.cpp b/source/cell.cpp index 509cd55a..5fdf41cb 100644 --- a/source/cell.cpp +++ b/source/cell.cpp @@ -161,49 +161,134 @@ void cell::set_value(const value &v) d_->value_ = v; } +// return s after checking encoding, size, and illegal characters +std::string check_string(std::string s) +{ + if (s.size() == 0) + { + return s; + } + + // check encoding? + + if (s.size() > 32767) + { + s = s.substr(0, 32767); // max string length in Excel + } + + for (unsigned char c : s) + { + if (c <= 8 || c == 11 || c == 12 || (c >= 14 && c <= 31)) + { + throw "illegal characters"; + } + } + + return s; +} + +std::pair cast_numeric(const std::string &s) +{ + const char *str = s.c_str(); + char *str_end = nullptr; + auto result = std::strtold(str, &str_end); + if (str_end != str + s.size()) return{ false, 0 }; + return{ true, result }; +} + +std::pair cast_percentage(const std::string &s) +{ + if (s.back() == '%') + { + auto number = cast_numeric(s.substr(0, s.size() - 1)); + + if (number.first) + { + return{ true, number.second / 100 }; + } + } + + return { false, 0 }; +} + +std::pair cast_time(const std::string &s) +{ + time result; + + try + { + auto last_colon = s.find_last_of(':'); + if (last_colon == std::string::npos) return { false, result }; + double seconds = std::stod(s.substr(last_colon + 1)); + result.second = static_cast(seconds); + result.microsecond = static_cast((seconds - static_cast(result.second)) * 1e6); + + auto first_colon = s.find_first_of(':'); + + if (first_colon == last_colon) + { + auto decimal_pos = s.find('.'); + if (decimal_pos != std::string::npos) + { + result.minute = std::stoi(s.substr(0, first_colon)); + } + else + { + result.hour = std::stoi(s.substr(0, first_colon)); + result.minute = result.second; + result.second = 0; + } + } + else + { + result.hour = std::stoi(s.substr(0, first_colon)); + result.minute = std::stoi(s.substr(first_colon + 1, last_colon - first_colon - 1)); + } + } + catch (std::invalid_argument) + { + return{ false, result }; + } + + return { true, result }; +} + void cell::set_value(const std::string &s) { - if(!get_parent().get_parent().get_guess_types()) - { - d_->is_date_ = false; - d_->value_ = value(s); - } - else - { - d_->is_date_ = false; + d_->is_date_ = false; + auto temp = check_string(s); + d_->value_ = value(temp); - switch(data_type_for_value(s)) - { - case value::type::numeric: - if(s.find(':') != std::string::npos) - { - d_->is_date_ = true; - d_->value_ = value(time(s).to_number()); - } - else if(s.back() == '%') - { - d_->value_ = value(std::stod(s.substr(0, s.length() - 1)) / 100); - get_style().get_number_format().set_format_code(xlnt::number_format::format::percentage); - } - else - { - d_->value_ = value(std::stod(s)); - } - break; - case value::type::boolean: - d_->value_ = value(s == "TRUE" || s == "true"); - break; - case value::type::error: - d_->value_ = value(s); - break; - case value::type::string: - d_->value_ = value(s); - break; - case value::type::null: - d_->value_ = value::null(); - break; - default: throw data_type_exception(); - } + if (temp.size() > 1 && temp.front() == '=') + { + d_->formula_ = temp; + } + + if(get_parent().get_parent().get_guess_types()) + { + auto percentage = cast_percentage(s); + if (percentage.first) + { + d_->value_ = value(percentage.second); + get_style().get_number_format().set_format_code(xlnt::number_format::format::percentage); + } + else + { + auto time = cast_time(s); + if (time.first) + { + set_value(time.second); + } + + else + { + auto numeric = cast_numeric(s); + if (numeric.first) + { + d_->value_ = value(numeric.second); + } + } + } } } diff --git a/tests/test_cell.hpp b/tests/test_cell.hpp index 100812b0..52e30796 100644 --- a/tests/test_cell.hpp +++ b/tests/test_cell.hpp @@ -9,6 +9,49 @@ class test_cell : public CxxTest::TestSuite { public: + void test_infer_numeric() + { + wb.set_guess_types(true); + xlnt::worksheet ws = wb.create_sheet(); + xlnt::cell cell(ws, "A1"); + + cell.set_value("4.2"); + TS_ASSERT(cell.get_value() == 4.2); + + cell.set_value("-42.000"); + TS_ASSERT(cell.get_value() == -42); + + cell.set_value("0"); + TS_ASSERT(cell.get_value() == 0); + + cell.set_value("0.9999"); + TS_ASSERT(cell.get_value() == 0.9999); + + cell.set_value("99E-02"); + TS_ASSERT(cell.get_value() == 0.99); + + cell.set_value("4"); + TS_ASSERT(cell.get_value() == 4); + + cell.set_value("-1E3"); + TS_ASSERT(cell.get_value() == -1000); + + cell.set_value("2e+2"); + TS_ASSERT(cell.get_value() == 200); + + cell.set_value("3.1%"); + TS_ASSERT(cell.get_value() == 0.031); + + cell.set_value("03:40:16"); + TS_ASSERT(cell.get_value() == xlnt::time(3, 40, 16)); + + cell.set_value("03:40"); + TS_ASSERT(cell.get_value() == xlnt::time(3, 40)); + + cell.set_value("30:33.865633336"); + TS_ASSERT(cell.get_value() == xlnt::time(0, 30, 33, 865633)); + } + void test_coordinates() { xlnt::cell_reference coord("ZF46"); @@ -120,50 +163,6 @@ public: TS_ASSERT(cell.get_value().is(xlnt::value::type::null)); } - void test_numeric() - { - xlnt::workbook wb_guess_types; - wb.set_guess_types(true); - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - - cell.set_value(42); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("4.2"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("-42.000"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("0"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value(0); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value(0.0001); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("0.9999"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("99E-02"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value(1e1); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("4"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value("-1E3"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - - cell.set_value(4); - TS_ASSERT(cell.get_value().is(xlnt::value::type::numeric)); - } - void test_string() { xlnt::worksheet ws = wb.create_sheet(); @@ -203,14 +202,6 @@ public: TS_ASSERT(cell.get_value().is(xlnt::value::type::boolean)); } - void test_leading_zero() - { - xlnt::worksheet ws = wb.create_sheet(); - xlnt::cell cell(ws, "A1"); - cell.set_value("0800"); - TS_ASSERT(cell.get_value().is(xlnt::value::type::string)); - } - void test_error_codes() { xlnt::worksheet ws = wb.create_sheet();