diff options
Diffstat (limited to 'src/libixion/name_resolver_test.cpp')
-rw-r--r-- | src/libixion/name_resolver_test.cpp | 1131 |
1 files changed, 1131 insertions, 0 deletions
diff --git a/src/libixion/name_resolver_test.cpp b/src/libixion/name_resolver_test.cpp new file mode 100644 index 0000000..82cea0f --- /dev/null +++ b/src/libixion/name_resolver_test.cpp @@ -0,0 +1,1131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "test_global.hpp" // This must be the first header to be included. + +#include <ixion/model_context.hpp> +#include <ixion/formula_name_resolver.hpp> +#include <ixion/address.hpp> +#include <ixion/table.hpp> + +#include <string> + +using namespace ixion; + +namespace { + +struct ref_name_entry +{ + const char* name; + bool sheet_name; +}; + +void test_calc_a1() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("Three"); + cxt.append_sheet("A B C"); // name with space + auto resolver = formula_name_resolver::get(formula_name_resolver_t::calc_a1, &cxt); + assert(resolver); + + { + // Parse single cell addresses. + ref_name_entry names[] = + { + { "A1", false }, + { "$A1", false }, + { "A$1", false }, + { "$A$1", false }, + { "Z1", false }, + { "AA23", false }, + { "AB23", false }, + { "$AB23", false }, + { "AB$23", false }, + { "$AB$23", false }, + { "BA1", false }, + { "AAA2", false }, + { "ABA1", false }, + { "BAA1", false }, + { "XFD1048576", false }, + { "One.A1", true }, + { "One.XFD1048576", true }, + { "Two.B10", true }, + { "Two.$B10", true }, + { "Two.B$10", true }, + { "Two.$B$10", true }, + { "Three.CFD234", true }, + { "$Three.CFD234", true }, + { "'A B C'.Z12", true }, + { "$'A B C'.Z12", true }, + { 0, false } + }; + + for (size_t i = 0; names[i].name; ++i) + { + const char* p = names[i].name; + std::string name_a1(p); + std::cout << "single cell address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_a1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + + { + // Parse range addresses. + + ref_name_entry names[] = + { + { "A1:B2", false }, + { "$D10:G$24", false }, + { "One.C$1:Z$400", true }, + { "Two.$C1:$Z400", true }, + { "Three.$C1:Z$400", true }, + { "$Three.$C1:Z$400", true }, + { "'A B C'.$C4:$Z256", true }, + { "$'A B C'.$C4:$Z256", true }, + { "One.C4:Three.Z100", true }, + { "One.C4:$Three.Z100", true }, + { "$One.C4:Three.Z100", true }, + { "$One.C4:$Three.Z100", true }, + { 0, false }, + }; + + for (sheet_t sheet = 0; sheet <= 3; ++sheet) + { + for (size_t i = 0; names[i].name; ++i) + { + abs_address_t pos{sheet, 0, 0}; + std::string name_a1(names[i].name); + std::cout << "range address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, pos); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_a1 << std::endl; + assert(false); + } + + std::string test_name = resolver->get_name( + std::get<range_t>(res.value), pos, names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from range: (pos: " << pos << "; name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + } + + struct { + const char* name; sheet_t sheet1; row_t row1; col_t col1; sheet_t sheet2; row_t row2; col_t col2; + } range_tests[] = { + { "A1:B2", 0, 0, 0, 0, 1, 1 }, + { "D10:G24", 0, 9, 3, 0, 23, 6 }, + { "One.C1:Z400", 0, 0, 2, 0, 399, 25 }, + { "Two.C1:Z400", 1, 0, 2, 1, 399, 25 }, + { "Three.C1:Z400", 2, 0, 2, 2, 399, 25 }, + { 0, 0, 0, 0, 0, 0, 0 } + }; + + for (size_t i = 0; range_tests[i].name; ++i) + { + std::string name_a1(range_tests[i].name); + std::cout << "range address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + auto range = std::get<range_t>(res.value); + assert(res.type == formula_name_t::range_reference); + assert(range.first.sheet == range_tests[i].sheet1); + assert(range.first.row == range_tests[i].row1); + assert(range.first.column == range_tests[i].col1); + assert(range.last.sheet == range_tests[i].sheet2); + assert(range.last.row == range_tests[i].row2); + assert(range.last.column == range_tests[i].col2); + } + + { + formula_name_t res = resolver->resolve("B1", abs_address_t(0,1,1)); + auto addr = std::get<address_t>(res.value); + assert(res.type == formula_name_t::cell_reference); + assert(addr.sheet == 0); + assert(addr.row == -1); + assert(addr.column == 0); + } + + { + formula_name_t res = resolver->resolve("B2:B4", abs_address_t(0,0,3)); + auto range = std::get<range_t>(res.value); + assert(res.type == formula_name_t::range_reference); + assert(range.first.sheet == 0); + assert(range.first.row == 1); + assert(range.first.column == -2); + assert(range.last.sheet == 0); + assert(range.last.row == 3); + assert(range.last.column == -2); + } + + { + formula_name_t res = resolver->resolve("Three.B2", abs_address_t(2,0,0)); + auto addr = std::get<address_t>(res.value); + assert(res.type == formula_name_t::cell_reference); + assert(!addr.abs_sheet); + assert(!addr.abs_row); + assert(!addr.abs_column); + assert(addr.sheet == 0); + assert(addr.row == 1); + assert(addr.column == 1); + } + + { + abs_address_t pos(2, 0, 0); + std::string name("One.B2:Three.C4"); + formula_name_t res = resolver->resolve(name, pos); + auto range = std::get<range_t>(res.value); + assert(res.type == formula_name_t::range_reference); + assert(!range.first.abs_sheet); + assert(!range.first.abs_row); + assert(!range.first.abs_column); + assert(range.first.sheet == -2); + assert(range.first.row == 1); + assert(range.first.column == 1); + assert(!range.last.abs_sheet); + assert(!range.last.abs_row); + assert(!range.last.abs_column); + assert(range.last.sheet == 0); + assert(range.last.row == 3); + assert(range.last.column == 2); + + std::string s = resolver->get_name(range, pos, true); + assert(s == name); + } +} + +void test_excel_a1() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("Three"); + cxt.append_sheet("A B C"); // name with space + cxt.append_sheet("A'B"); // name with quote + cxt.append_sheet("C'''D"); // name with multiple consecutive quotes + cxt.append_sheet("\"Quoted\""); // name with double quotes + auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt); + assert(resolver); + + { + // Parse single cell addresses. + ref_name_entry names[] = + { + { "A1", false }, + { "$A1", false }, + { "A$1", false }, + { "$A$1", false }, + { "Z1", false }, + { "AA23", false }, + { "AB23", false }, + { "$AB23", false }, + { "AB$23", false }, + { "$AB$23", false }, + { "BA1", false }, + { "AAA2", false }, + { "ABA1", false }, + { "BAA1", false }, + { "XFD1048576", false }, + { "One!A1", true }, + { "One!XFD1048576", true }, + { "Two!B10", true }, + { "Two!$B10", true }, + { "Two!B$10", true }, + { "Two!$B$10", true }, + { "Three!CFD234", true }, + { "'A B C'!Z12", true }, + { "'A''B'!Z12", true }, + { "'C''''''D'!BB23", true }, + { "'\"Quoted\"'!A2", true }, + { 0, false } + }; + + for (size_t i = 0; names[i].name; ++i) + { + const char* p = names[i].name; + std::string name_a1(p); + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_a1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + + { + // Parse range addresses. + + ref_name_entry names[] = + { + { "A1:B2", false }, + { "$D10:G$24", false }, + { "One!C$1:Z$400", true }, + { "Two!$C1:$Z400", true }, + { "Three!$C1:Z$400", true }, + { "'A B C'!$C4:$Z256", true }, + { 0, false }, + }; + + for (size_t i = 0; names[i].name; ++i) + { + const char* p = names[i].name; + std::string name_a1(p); + std::cout << "range address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_a1 << std::endl; + assert(false); + } + + range_t range = std::get<range_t>(res.value); + std::string test_name = resolver->get_name(range, abs_address_t(), names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + + // Parse range addresses. + struct { + const char* name; sheet_t sheet1; row_t row1; col_t col1; sheet_t sheet2; row_t row2; col_t col2; + } range_tests[] = { + { "A1:B2", 0, 0, 0, 0, 1, 1 }, + { "D10:G24", 0, 9, 3, 0, 23, 6 }, + { "One!C1:Z400", 0, 0, 2, 0, 399, 25 }, + { "Two!C1:Z400", 1, 0, 2, 1, 399, 25 }, + { "Three!C1:Z400", 2, 0, 2, 2, 399, 25 }, + { 0, 0, 0, 0, 0, 0, 0 } + }; + + for (size_t i = 0; range_tests[i].name; ++i) + { + std::string name_a1(range_tests[i].name); + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + auto range = std::get<range_t>(res.value); + assert(res.type == formula_name_t::range_reference); + assert(range.first.sheet == range_tests[i].sheet1); + assert(range.first.row == range_tests[i].row1); + assert(range.first.column == range_tests[i].col1); + assert(range.last.sheet == range_tests[i].sheet2); + assert(range.last.row == range_tests[i].row2); + assert(range.last.column == range_tests[i].col2); + } + + { + formula_name_t res = resolver->resolve("B1", abs_address_t(0,1,1)); + auto addr = std::get<address_t>(res.value); + assert(res.type == formula_name_t::cell_reference); + assert(addr.sheet == 0); + assert(addr.row == -1); + assert(addr.column == 0); + } + + { + formula_name_t res = resolver->resolve("B2:B4", abs_address_t(0,0,3)); + auto range = std::get<range_t>(res.value); + assert(res.type == formula_name_t::range_reference); + assert(range.first.sheet == 0); + assert(range.first.row == 1); + assert(range.first.column == -2); + assert(range.last.sheet == 0); + assert(range.last.row == 3); + assert(range.last.column == -2); + } + + // Parse name without row index. + struct { + const char* name; formula_name_t::name_type type; + } name_tests[] = { + { "H:H", formula_name_t::range_reference }, + { "ABC", formula_name_t::named_expression }, + { "H", formula_name_t::named_expression }, + { "MAX", formula_name_t::function }, + { 0, formula_name_t::invalid } + }; + + for (size_t i = 0; name_tests[i].name; ++i) + { + std::string name_a1(name_tests[i].name); + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + assert(res.type == name_tests[i].type); + } + + std::string_view invalid_names[] = { + "NotExists!A1", // non-existing sheet name + "A B C!B2", // sheet name with space not being quoted + "A''B!D10", // sheet name with quote must be quoted + "\"Quoted\"!E12", // sheet name with double quotes must be quoted + }; + + for (const auto& name: invalid_names) + { + std::cout << "invalid name: " << name << std::endl; + formula_name_t res = resolver->resolve(name, abs_address_t()); + std::cout << "parsed name: " << res.to_string() << std::endl; + assert(res.type == formula_name_t::invalid); + } +} + +void test_excel_a1_multisheet() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("Three"); + cxt.append_sheet("A B C"); // name with space + cxt.append_sheet("A'B"); // name with quote + cxt.append_sheet("C'D''E"); // name with two quotes + cxt.append_sheet("\"Quoted\""); // name with double quotes + + auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt); + assert(resolver); + + struct check_type + { + std::string_view name; + address_t address1; + address_t address2; + }; + + // NB: sheet positions are always absolute in Excel. + const check_type checks[] = { + { "One:Two!C4", {0, 3, 2, true, false, false}, {1, 3, 2, true, false, false} }, + { "One:Three!A1:B2", {0, 0, 0, true, false, false}, {2, 1, 1, true, false, false} }, + { "'Two:A''B'!$F$10", {1, 9, 5, true, true, true}, {4, 9, 5, true, true, true} }, + { "'A''B:C''D''''E'!B$2:D$10", {4, 1, 1, true, true, false}, {5, 9, 3, true, true, false} }, + { "'Three:\"Quoted\"'!B10", {2, 9, 1, true, false, false}, {6, 9, 1, true, false, false} }, + }; + + for (const auto& c : checks) + { + std::cout << "name: " << c.name << std::endl; + formula_name_t res = resolver->resolve(c.name, abs_address_t()); + assert(res.type == formula_name_t::name_type::range_reference); + + auto v = std::get<range_t>(res.value); + assert(v.first == c.address1); + assert(v.last == c.address2); + } +} + +void test_named_expression() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("Sheet"); + + const std::vector<formula_name_resolver_t> resolver_types = { + formula_name_resolver_t::excel_a1, + formula_name_resolver_t::excel_r1c1, + // TODO : add more resolver types. + }; + + const std::vector<std::string> names = { + "MyRange", + "MyRange2", + }; + + for (formula_name_resolver_t rt : resolver_types) + { + std::cout << "formula resolver type: " << int(rt) << std::endl; // TODO : map the enum value to std::string name. + + auto resolver = formula_name_resolver::get(rt, &cxt); + assert(resolver); + + for (const std::string& name : names) + { + std::cout << "parsing '" << name << "'..." << std::endl; + formula_name_t res = resolver->resolve(name, abs_address_t(0,0,0)); + assert(res.type == formula_name_t::name_type::named_expression); + } + } +} + +void test_table_excel_a1() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("Sheet"); + string_id_t s_table1 = cxt.append_string("Table1"); + string_id_t s_table2 = cxt.append_string("Table2"); + string_id_t s_cat = cxt.append_string("Category"); + string_id_t s_val = cxt.append_string("Value"); + + // Make sure these work correctly before proceeding further with the test. + assert(s_table1 == cxt.get_identifier_from_string("Table1")); + assert(s_table2 == cxt.get_identifier_from_string("Table2")); + assert(s_cat == cxt.get_identifier_from_string("Category")); + assert(s_val == cxt.get_identifier_from_string("Value")); + + auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_a1, &cxt); + assert(resolver); + + struct { + std::string_view exp; + sheet_t sheet; + row_t row; + col_t col; + string_id_t table_name; + string_id_t column_first; + string_id_t column_last; + table_areas_t areas; + } tests[] = { + { "[Value]", 0, 9, 2, empty_string_id, s_val, empty_string_id, table_area_data }, + { "Table1[Category]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_data }, + { "Table1[Value]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_data }, + { "Table1[[#Headers],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_headers }, + { "Table1[[#Headers],[#Data],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_headers | table_area_data }, + { "Table1[[#All],[Category]]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_all }, + { "Table1[[#Totals],[Category]]", 0, 9, 2, s_table1, s_cat, empty_string_id, table_area_totals }, + { "Table1[[#Data],[#Totals],[Value]]", 0, 9, 2, s_table1, s_val, empty_string_id, table_area_data | table_area_totals }, + { "Table1[#All]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_all }, + { "Table1[#Headers]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_headers }, + { "Table1[#Data]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_data }, + { "Table1[#Totals]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_totals }, + { "Table1[[#Headers],[#Data]]", 0, 9, 2, s_table1, empty_string_id, empty_string_id, table_area_headers | table_area_data }, + { "Table1[[#Totals],[Category]:[Value]]", 0, 9, 2, s_table1, s_cat, s_val, table_area_totals }, + { "Table1[[#Data],[#Totals],[Category]:[Value]]", 0, 9, 2, s_table1, s_cat, s_val, table_area_data | table_area_totals }, + }; + + for (size_t i = 0, n = std::size(tests); i < n; ++i) + { + std::cout << "* table reference: " << tests[i].exp << std::endl; + abs_address_t pos(tests[i].sheet, tests[i].row, tests[i].col); + formula_name_t res = resolver->resolve(tests[i].exp, pos); + if (res.type != formula_name_t::table_reference) + assert(!"table reference expected."); + + auto table = std::get<formula_name_t::table_type>(res.value); + string_id_t table_name = cxt.get_identifier_from_string(table.name); + string_id_t column_first = cxt.get_identifier_from_string(table.column_first); + string_id_t column_last = cxt.get_identifier_from_string(table.column_last); + assert(table_name == tests[i].table_name); + assert(column_first == tests[i].column_first); + assert(column_last == tests[i].column_last); + assert(table.areas == tests[i].areas); + + // Make sure we get the same name back. + table_t tb; + tb.name = table_name; + tb.column_first = column_first; + tb.column_last = column_last; + tb.areas = table.areas; + std::string original(tests[i].exp); + std::string returned = resolver->get_name(tb); + std::cout << " original: " << original << std::endl; + std::cout << " returned: " << returned << std::endl; + assert(original == returned); + } +} + +void test_excel_r1c1() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("A B C"); // name with space + cxt.append_sheet("80's Music"); + + auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_r1c1, &cxt); + assert(resolver); + + // Parse single cell addresses for round-tripping. + ref_name_entry single_ref_names[] = + { + { "R2", false }, + { "R[3]", false }, + { "R[-10]", false }, + { "C2", false }, + { "C[3]", false }, + { "C[-10]", false }, + { "R1C1", false }, + { "R[1]C2", false }, + { "R2C[-2]", false }, + { "R1C", false }, + { "R[-3]C", false }, + { "RC2", false }, + { "One!R10C", true }, + { "Two!C[-2]", true }, + { "'A B C'!R100", true }, + { 0, false } + }; + + for (size_t i = 0; single_ref_names[i].name; ++i) + { + const char* p = single_ref_names[i].name; + std::string name_r1c1(p); + std::cout << "Parsing " << name_r1c1 << std::endl; + formula_name_t res = resolver->resolve(name_r1c1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_r1c1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), single_ref_names[i].sheet_name); + + if (name_r1c1 != test_name) + { + std::cerr << "failed to compile name from address: (name expected: " + << name_r1c1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + // These are supposed to be all invalid or named expression. + const char* invalid_address[] = { + "F", + "RR", + "RC", + "R", + "C", + "R0C1", + "R[-2]C-1", + "R1C2:", + "R:", + "R2:", + "R[-3]:", + "C:", + "C3:", + "C[-4]:", + 0 + }; + + for (size_t i = 0; invalid_address[i]; ++i) + { + const char* p = invalid_address[i]; + std::string name_r1c1(p); + formula_name_t res = resolver->resolve(name_r1c1, abs_address_t()); + if (res.type != formula_name_t::invalid && res.type != formula_name_t::named_expression) + { + std::cerr << "address " << name_r1c1 << " is expected to be invalid." << std::endl; + assert(false); + } + } + + // These are supposed to be all valid. + const char* valid_address[] = { + "r1c2", + "r[-2]", + "c10", + 0 + }; + + for (size_t i = 0; valid_address[i]; ++i) + { + const char* p = valid_address[i]; + std::string name_r1c1(p); + formula_name_t res = resolver->resolve(name_r1c1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "address " << name_r1c1 << " is expected to be valid." << std::endl; + assert(false); + } + } + + // Parse range addresses. + struct { + const char* name; + sheet_t sheet1; + row_t row1; + col_t col1; + sheet_t sheet2; + row_t row2; + col_t col2; + bool abs_sheet1; + bool abs_row1; + bool abs_col1; + bool abs_sheet2; + bool abs_row2; + bool abs_col2; + } range_tests[] = { + { "R1C1:R2C2", 0, 0, 0, 0, 1, 1, true, true, true, true, true, true }, + { "R[-3]C[2]:R[1]C[4]", 0, -3, 2, 0, 1, 4, true, false, false, true, false, false }, + { "R2:R4", 0, 1, column_unset, 0, 3, column_unset, true, true, false, true, true, false }, + { "R[2]:R[4]", 0, 2, column_unset, 0, 4, column_unset, true, false, false, true, false, false }, + { "C3:C6", 0, row_unset, 2, 0, row_unset, 5, true, false, true, true, false, true }, + { "C[3]:C[6]", 0, row_unset, 3, 0, row_unset, 6, true, false, false, true, false, false }, + { "Two!R2C2:R2C[100]", 1, 1, 1, 1, 1, 100, true, true, true, true, true, false }, + { "'A B C'!R[2]:R[4]", 2, 2, column_unset, 2, 4, column_unset, true, false, false, true, false, false }, + { 0, 0, 0, 0, 0, 0, 0, false, false, false, false, false, false } + }; + + for (size_t i = 0; range_tests[i].name; ++i) + { + std::string name_r1c1(range_tests[i].name); + std::cout << "Parsing " << name_r1c1 << std::endl; + formula_name_t res = resolver->resolve(name_r1c1, abs_address_t()); + auto range = std::get<range_t>(res.value); + + assert(res.type == formula_name_t::range_reference); + assert(range.first.sheet == range_tests[i].sheet1); + assert(range.first.row == range_tests[i].row1); + assert(range.first.column == range_tests[i].col1); + assert(range.first.abs_sheet == range_tests[i].abs_sheet1); + if (range.first.row != row_unset) + // When row is unset, whether it's relative or absolute is not relevant. + assert(range.first.abs_row == range_tests[i].abs_row1); + if (range.first.column != column_unset) + // Same with unset column. + assert(range.first.abs_column == range_tests[i].abs_col1); + + assert(range.last.sheet == range_tests[i].sheet2); + assert(range.last.row == range_tests[i].row2); + assert(range.last.column == range_tests[i].col2); + assert(range.last.abs_sheet == range_tests[i].abs_sheet2); + if (range.last.row != row_unset) + assert(range.last.abs_row == range_tests[i].abs_row2); + if (range.last.column != column_unset) + assert(range.last.abs_column == range_tests[i].abs_col2); + } + + ref_name_entry range_ref_names[] = + { + { "R2C2:R3C3", false }, + { "R[-3]C2:R[-1]C3", false }, + { "R[-5]C:R[-1]C", false }, + { "'A B C'!R2:R4", true }, + { "'80''s Music'!C[2]:C[4]", true }, + { 0, false }, + }; + + for (size_t i = 0; range_ref_names[i].name; ++i) + { + const char* p = range_ref_names[i].name; + std::string name_r1c1(p); + std::cout << "Parsing " << name_r1c1 << std::endl; + formula_name_t res = resolver->resolve(name_r1c1, abs_address_t()); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_r1c1 << std::endl; + assert(false); + } + + auto range = std::get<range_t>(res.value); + std::string test_name = resolver->get_name(range, abs_address_t(), range_ref_names[i].sheet_name); + + if (name_r1c1 != test_name) + { + std::cerr << "failed to compile name from range: (name expected: " << name_r1c1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + struct { + col_t col; + std::string name; + } colnames[] = { + { 0, "1" }, + { 1, "2" }, + { 10, "11" }, + { 123, "124" }, + }; + + for (size_t i = 0, n = std::size(colnames); i < n; ++i) + { + std::string colname = resolver->get_column_name(colnames[i].col); + if (colname != colnames[i].name) + { + std::cerr << "column name: expected='" << colnames[i].name << "', actual='" << colname << "'" << std::endl; + assert(false); + } + } +} + +void test_excel_r1c1_multisheet() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("Three"); + cxt.append_sheet("A B C"); // name with space + cxt.append_sheet("A'B"); // name with quote + cxt.append_sheet("C'D''E"); // name with two quotes + cxt.append_sheet("\"Quoted\""); // name with double quotes + + auto resolver = formula_name_resolver::get(formula_name_resolver_t::excel_r1c1, &cxt); + assert(resolver); + + struct check_type + { + std::string_view name; + address_t address1; + address_t address2; + }; + + // NB: sheet positions are always absolute in Excel. + const check_type checks[] = { + { "One:Two!R[3]C[2]", {0, 3, 2, true, false, false}, {1, 3, 2, true, false, false} }, + { "One:Three!RC:R[1]C[1]", {0, 0, 0, true, false, false}, {2, 1, 1, true, false, false} }, + { "'Two:A''B'!R10C6", {1, 9, 5, true, true, true}, {4, 9, 5, true, true, true} }, + { "'A''B:C''D''''E'!R2C[1]:R10C[3]", {4, 1, 1, true, true, false}, {5, 9, 3, true, true, false} }, + { "'Three:\"Quoted\"'!R[9]C[1]", {2, 9, 1, true, false, false}, {6, 9, 1, true, false, false} }, + }; + + for (const auto& c : checks) + { + std::cout << "name: " << c.name << std::endl; + formula_name_t res = resolver->resolve(c.name, abs_address_t()); + assert(res.type == formula_name_t::name_type::range_reference); + + auto v = std::get<range_t>(res.value); + assert(v.first == c.address1); + assert(v.last == c.address2); + } +} + +void test_odff() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("A B C"); // name with space + cxt.append_sheet("80's Music"); + + auto resolver = formula_name_resolver::get(formula_name_resolver_t::odff, &cxt); + assert(resolver); + + // Parse single cell addresses. + ref_name_entry single_ref_names[] = + { + { "[.A1]", false }, + { "[.$A1]", false }, + { "[.A$1]", false }, + { "[.$A$1]", false }, + { 0, false } + }; + + for (size_t i = 0; single_ref_names[i].name; ++i) + { + const char* p = single_ref_names[i].name; + std::string name_a1(p); + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_a1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), single_ref_names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + // Parse cell range addresses. + ref_name_entry range_ref_names[] = + { + { "[.A1:.A3]", false }, + { "[.$B5:.$D10]", false }, + { 0, false } + }; + + for (size_t i = 0; range_ref_names[i].name; ++i) + { + const char* p = range_ref_names[i].name; + std::string name_a1(p); + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_a1 << std::endl; + assert(false); + } + + auto range = std::get<range_t>(res.value); + std::string test_name = resolver->get_name(range, abs_address_t(), range_ref_names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + // single cell addresses with sheet names + ref_name_entry addr_with_sheet_names[] = + { + { "[One.$B$1]", true }, + { "[$One.$B$1]", true }, + { "[Two.$B$2]", true }, + { "[$Two.$B$4]", true }, + { "['A B C'.$B$4]", true }, + { "[$'A B C'.$B$4]", true }, + { 0, false }, + }; + + for (size_t i = 0; addr_with_sheet_names[i].name; ++i) + { + const char* p = addr_with_sheet_names[i].name; + std::string name_a1(p); + + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_a1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), addr_with_sheet_names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from cell address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + // range addresses with sheet names + ref_name_entry ref_with_sheet_names[] = + { + { "[One.$B$1:.$B$30]", true }, + { "[$One.$B$1:.$B$30]", true }, + { "[Two.$B$2:.$D30]", true }, + { "[$Two.$B$4:.F$35]", true }, + { "['A B C'.$B$4:.F$35]", true }, + { "[$'A B C'.$B$4:.F$35]", true }, + { "[$One.B$4:$Two.F35]", true }, + { "[$One.B$4:'A B C'.F35]", true }, + { "[One.B$4:$'A B C'.F35]", true }, + { 0, false }, + }; + + for (size_t i = 0; ref_with_sheet_names[i].name; ++i) + { + const char* p = ref_with_sheet_names[i].name; + std::string name_a1(p); + + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_a1 << std::endl; + assert(false); + } + + auto range = std::get<range_t>(res.value); + std::string test_name = resolver->get_name(range, abs_address_t(), ref_with_sheet_names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from range: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + + { + std::string name = "[.H2:.I2]"; + abs_address_t pos(2, 1, 9); + formula_name_t res = resolver->resolve(name, pos); + assert(res.type == formula_name_t::range_reference); + abs_range_t range = std::get<range_t>(res.value).to_abs(pos); + abs_range_t range_expected(abs_address_t(pos.sheet, 1, 7), 1, 2); + assert(range == range_expected); + } + + { + std::string name = "[Two.$B$2:.$B$10]"; + abs_address_t pos(3, 2, 1); + formula_name_t res = resolver->resolve(name, pos); + assert(res.type == formula_name_t::range_reference); + abs_range_t range = std::get<range_t>(res.value).to_abs(pos); + abs_range_t range_expected(abs_address_t(1, 1, 1), 9, 1); + assert(range == range_expected); + } + + { + std::string name = "MyRange"; + abs_address_t pos(3, 2, 1); + formula_name_t res = resolver->resolve(name, pos); + assert(res.type == formula_name_t::named_expression); + } +} + +void test_odf_cra() +{ + IXION_TEST_FUNC_SCOPE; + + model_context cxt; + cxt.append_sheet("One"); + cxt.append_sheet("Two"); + cxt.append_sheet("Three"); + cxt.append_sheet("A B C"); // name with space + auto resolver = formula_name_resolver::get(formula_name_resolver_t::odf_cra, &cxt); + assert(resolver); + + { + // Parse single cell addresses. + ref_name_entry names[] = + { + { ".A1", false }, + { ".$A1", false }, + { ".A$1", false }, + { ".$A$1", false }, + { ".Z1", false }, + { ".AA23", false }, + { ".AB23", false }, + { ".$AB23", false }, + { ".AB$23", false }, + { ".$AB$23", false }, + { ".BA1", false }, + { ".AAA2", false }, + { ".ABA1", false }, + { ".BAA1", false }, + { ".XFD1048576", false }, + { "One.A1", true }, + { "One.XFD1048576", true }, + { "Two.B10", true }, + { "Two.$B10", true }, + { "Two.B$10", true }, + { "Two.$B$10", true }, + { "Three.CFD234", true }, + { "$Three.CFD234", true }, + { "'A B C'.Z12", true }, + { "$'A B C'.Z12", true }, + { 0, false } + }; + + for (size_t i = 0; names[i].name; ++i) + { + const char* p = names[i].name; + std::string name_a1(p); + std::cout << "single cell address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, abs_address_t()); + if (res.type != formula_name_t::cell_reference) + { + std::cerr << "failed to resolve cell address: " << name_a1 << std::endl; + assert(false); + } + + address_t addr = std::get<address_t>(res.value); + std::string test_name = resolver->get_name(addr, abs_address_t(), names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from address: (name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + + { + // Parse range addresses. + + ref_name_entry names[] = + { + { ".A1:.B2", false }, + { ".$D10:.G$24", false }, + { "One.C$1:.Z$400", true }, + { "Two.$C1:.$Z400", true }, + { "Three.$C1:.Z$400", true }, + { "$Three.$C1:.Z$400", true }, + { "'A B C'.$C4:.$Z256", true }, + { "$'A B C'.$C4:.$Z256", true }, + { "One.C4:Three.Z100", true }, + { "One.C4:$Three.Z100", true }, + { "$One.C4:Three.Z100", true }, + { "$One.C4:$Three.Z100", true }, + { 0, false }, + }; + + for (sheet_t sheet = 0; sheet <= 3; ++sheet) + { + for (size_t i = 0; names[i].name; ++i) + { + abs_address_t pos{sheet, 0, 0}; + std::string name_a1(names[i].name); + std::cout << "range address: " << name_a1 << std::endl; + formula_name_t res = resolver->resolve(name_a1, pos); + if (res.type != formula_name_t::range_reference) + { + std::cerr << "failed to resolve range address: " << name_a1 << std::endl; + assert(false); + } + + auto range = std::get<range_t>(res.value); + std::string test_name = resolver->get_name(range, pos, names[i].sheet_name); + + if (name_a1 != test_name) + { + std::cerr << "failed to compile name from range: (pos: " << pos << "; name expected: " << name_a1 << "; actual name created: " << test_name << ")" << std::endl; + assert(false); + } + } + } + } +} + +} // anonymous namespace + +int main() +{ + test_calc_a1(); + test_excel_a1(); + test_excel_a1_multisheet(); + test_named_expression(); + test_table_excel_a1(); + test_excel_r1c1(); + test_excel_r1c1_multisheet(); + test_odff(); + test_odf_cra(); + + return EXIT_SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |