summaryrefslogtreecommitdiffstats
path: root/src/libixion/name_resolver_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libixion/name_resolver_test.cpp')
-rw-r--r--src/libixion/name_resolver_test.cpp1131
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: */