From 14cb4e88a4b39d7abc14977bc26a3c5a1b7b02c9 Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Mon, 12 May 2014 19:59:33 -0400 Subject: [PATCH] fixed some more tests --- source/tests/DumpTestSuite.h | 123 ++++---- source/tests/IterTestSuite.h | 76 +++-- source/tests/MetaTestSuite.h | 14 +- source/tests/TemporaryDirectory.h | 31 ++ source/tests/TemporaryFile.h | 31 ++ source/tests/WorksheetTestSuite.h | 4 +- source/tests/WriteTestSuite.h | 4 +- source/xlnt.cpp | 167 ++++++++++- source/xlnt.h | 464 ++++++++++++++++++++++++++++-- 9 files changed, 774 insertions(+), 140 deletions(-) create mode 100644 source/tests/TemporaryDirectory.h create mode 100644 source/tests/TemporaryFile.h diff --git a/source/tests/DumpTestSuite.h b/source/tests/DumpTestSuite.h index 6fbe2043..76457d0d 100644 --- a/source/tests/DumpTestSuite.h +++ b/source/tests/DumpTestSuite.h @@ -3,6 +3,7 @@ #include #include +#include "TemporaryFile.h" #include "../xlnt.h" class DumpTestSuite : public CxxTest::TestSuite @@ -10,37 +11,41 @@ class DumpTestSuite : public CxxTest::TestSuite public: DumpTestSuite() { - + } - void _get_test_filename() - { - NamedTemporaryFile test_file("w", "xlnt.", ".xlsx", false); - test_file.close(); - return test_file.name; - } - - _COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in range(1, 18279)); + //_COL_CONVERSION_CACHE = dict((get_column_letter(i), i) for i in range(1, 18279)); void test_dump_sheet_title() { - test_filename = _get_test_filename(); - wb = Workbook(optimized_write = True); - ws = wb.create_sheet(title = "Test1"); - wb.save(test_filename); - wb2 = load_workbook(test_filename, True); + xlnt::workbook wb; + wb.optimized_write(true); + auto ws = wb.create_sheet("Test1"); + wb.save(temp_file.GetFilename()); + xlnt::workbook wb2; + wb2.load(temp_file.GetFilename()); ws = wb2.get_sheet_by_name("Test1"); - TS_ASSERT_EQUALS("Test1", ws.title); + TS_ASSERT_EQUALS("Test1", ws.get_title()); } void test_dump_sheet() { - test_filename = _get_test_filename(); - wb = Workbook(optimized_write = True); - ws = wb.create_sheet(); - letters = [get_column_letter(x + 1) for x in range(20)]; - expected_rows = []; - for(auto row : range(20)) + auto test_filename = temp_file.GetFilename(); + + xlnt::workbook wb; + wb.optimized_write(true); + auto ws = wb.create_sheet(); + + std::vector letters; + + for(int i = 0; i < 20; i++) + { + letters.push_back(xlnt::cell::get_column_letter(i + 1)); + } + + std::vector expected_rows; + + for(int row = 0; row < 20; row++) { expected_rows.append(["%s%d" % (letter, row + 1) for letter in letters]); for(auto row in range(20)) @@ -80,41 +85,43 @@ public: void test_table_builder() { - sb = StringTableBuilder(); + StringTableBuilder sb; - result = {"a":0, "b" : 1, "c" : 2, "d" : 3}; + std::unordered_map result = {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}}; - for(auto letter in sorted(result.keys())) + for(auto pair : result) + { + for(int i = 0; i < 5; i++) { - for x in range(5) + sb.add(pair.first); + + auto table = sb.get_table(); + + try { - sb.add(letter) + result_items = result.items(); + } - table = dict(sb.get_table()) - - try - { - result_items = result.items() - } - - for key, idx in result_items - { - TS_ASSERT_EQUALS(idx, table[key]) - } + for key, idx in result_items + { + TS_ASSERT_EQUALS(idx, table[key]) } } + } } void test_open_too_many_files() { - test_filename = _get_test_filename(); - wb = Workbook(optimized_write = True); + auto test_filename = temp_file.GetFilename(); - for i in range(200) over 200 worksheets should raise an OSError("too many open files") + xlnt::workbook wb; + wb.optimized_write(true); + + for(int i = 0; i < 200; i++) // over 200 worksheets should raise an OSError("too many open files") { wb.create_sheet(); wb.save(test_filename); - os.remove(test_filename); + unlink(test_filename.c_str()); } } @@ -130,29 +137,37 @@ public: void test_dump_twice() { - test_filename = _get_test_filename(); + auto test_filename = temp_file.GetFilename(); - wb = Workbook(optimized_write = True); - ws = wb.create_sheet(); - ws.append(["hello"]); + xlnt::workbook wb; + wb.optimized_write(true); + auto ws = wb.create_sheet(); + + std::vector to_append = {"hello"}; + ws.append(to_append); wb.save(test_filename); - os.remove(test_filename); - + unlink(test_filename.c_str()); wb.save(test_filename); } void test_append_after_save() { - test_filename = _get_test_filename(); + xlnt::workbook wb; + wb.optimized_write(true); + auto ws = wb.create_sheet(); - wb = Workbook(optimized_write = True); - ws = wb.create_sheet(); - ws.append(["hello"]); + std::vector to_append = {"hello"}; + ws.append(to_append); - wb.save(test_filename); - os.remove(test_filename); + { + TemporaryFile temp2; + wb.save(temp2.GetFilename()); + } - ws.append(["hello"]); + ws.append(to_append); } + +private: + TemporaryFile temp_file; }; diff --git a/source/tests/IterTestSuite.h b/source/tests/IterTestSuite.h index ea796263..4d0035e0 100644 --- a/source/tests/IterTestSuite.h +++ b/source/tests/IterTestSuite.h @@ -13,17 +13,13 @@ public: } - void test_1() - { - - } - void test_get_dimensions() { - expected = ["A1:G5", "D1:K30", "D2:D2", "A1:C1"]; + auto expected = {"A1:G5", "D1:K30", "D2:D2", "A1:C1"}; wb = _open_wb(); - for i, sheetn in enumerate(wb.get_sheet_names()) + + for(i, sheetn : enumerate(wb.get_sheet_names())) { ws = wb.get_sheet_by_name(name = sheetn); TS_ASSERT_EQUALS(ws._dimensions, expected[i]); @@ -32,68 +28,68 @@ public: void test_read_fast_integrated() { - sheet_name = "Sheet1 - Text" + std::string sheet_name = "Sheet1 - Text"; - expected = [["This is cell A1 in Sheet 1", None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, None], - [None, None, None, None, None, None, "This is cell G5"], ] + std::vector> expected = {{"This is cell A1 in Sheet 1", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "This is cell G5"}}; - wb = load_workbook(filename = workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = sheet_name) + wb = load_workbook(filename = workbook_name, use_iterators = True); + ws = wb.get_sheet_by_name(name = sheet_name); - for row, expected_row in zip(ws.iter_rows(), expected) : - - row_values = [x.internal_value for x in row] - - TS_ASSERT_EQUALS(row_values, expected_row) + for(row, expected_row : zip(ws.iter_rows(), expected) + { + row_values = [x.internal_value for x in row]; + TS_ASSERT_EQUALS(row_values, expected_row); + } } void test_get_boundaries_range() { - TS_ASSERT_EQUALS(get_range_boundaries("C1:C4"), (3, 1, 3, 4)) + TS_ASSERT_EQUALS(get_range_boundaries("C1:C4"), (3, 1, 3, 4)); } void test_get_boundaries_one() { - TS_ASSERT_EQUALS(get_range_boundaries("C1"), (3, 1, 4, 1)) + TS_ASSERT_EQUALS(get_range_boundaries("C1"), (3, 1, 4, 1)); } void test_read_single_cell_range() { - wb = load_workbook(filename = workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = sheet_name) + wb = load_workbook(filename = workbook_name, use_iterators = True); + ws = wb.get_sheet_by_name(name = sheet_name); - TS_ASSERT_EQUALS("This is cell A1 in Sheet 1", list(ws.iter_rows("A1"))[0][0].internal_value) + TS_ASSERT_EQUALS("This is cell A1 in Sheet 1", list(ws.iter_rows("A1"))[0][0].internal_value); } void test_read_fast_integrated2() { - sheet_name = "Sheet2 - Numbers" + sheet_name = "Sheet2 - Numbers"; - expected = [[x + 1] for x in range(30)] + expected = [[x + 1] for x in range(30)]; - query_range = "D1:E30" + query_range = "D1:E30"; - wb = load_workbook(filename = workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = sheet_name) + wb = load_workbook(filename = workbook_name, use_iterators = True); + ws = wb.get_sheet_by_name(name = sheet_name); - for row, expected_row in zip(ws.iter_rows(query_range), expected) : - - row_values = [x.internal_value for x in row] - - TS_ASSERT_EQUALS(row_values, expected_row) + for(row, expected_row : zip(ws.iter_rows(query_range), expected)) + { + row_values = [x.internal_value for x in row]; + TS_ASSERT_EQUALS(row_values, expected_row); + } } void test_read_single_cell_date() { - sheet_name = "Sheet4 - Dates" + sheet_name = "Sheet4 - Dates"; - wb = load_workbook(filename = workbook_name, use_iterators = True) - ws = wb.get_sheet_by_name(name = sheet_name) + wb = load_workbook(filename = workbook_name, use_iterators = True); + ws = wb.get_sheet_by_name(name = sheet_name); - TS_ASSERT_EQUALS(datetime.datetime(1973, 5, 20), list(ws.iter_rows("A1"))[0][0].internal_value) - TS_ASSERT_EQUALS(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows("C1"))[0][0].internal_value) + TS_ASSERT_EQUALS(datetime.datetime(1973, 5, 20), list(ws.iter_rows("A1"))[0][0].internal_value); + TS_ASSERT_EQUALS(datetime.datetime(1973, 5, 20, 9, 15, 2), list(ws.iter_rows("C1"))[0][0].internal_value); } }; diff --git a/source/tests/MetaTestSuite.h b/source/tests/MetaTestSuite.h index cc185abc..01d552cd 100644 --- a/source/tests/MetaTestSuite.h +++ b/source/tests/MetaTestSuite.h @@ -15,20 +15,20 @@ public: void test_write_content_types() { - wb = Workbook(); + xlnt::workbook wb; wb.create_sheet(); wb.create_sheet(); - content = write_content_types(wb); - reference_file = os.path.join(DATADIR, "writer", "expected", - "[Content_Types].xml"); + auto content = xlnt::workbook::write_content_types(wb); + std::string reference_file = DATADIR + "/writer/expected/[Content_Types].xml"; assert_equals_file_content(reference_file, content); } void test_write_root_rels() { - wb = Workbook(); - content = write_root_rels(wb); - reference_file = os.path.join(DATADIR, "writer", "expected", ".rels"); + xlnt::workbook wb; + wb.create_sheet(); + auto content = xlnt::workbook::write_root_rels(wb); + std::string reference_file = DATADIR + "/writer/expected/.rels"; assert_equals_file_content(reference_file, content); } }; diff --git a/source/tests/TemporaryDirectory.h b/source/tests/TemporaryDirectory.h new file mode 100644 index 00000000..94b19920 --- /dev/null +++ b/source/tests/TemporaryDirectory.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class TemporaryDirectory +{ +public: + static std::string CreateTemporaryFilename() + { + std::array buffer; + tmpnam(buffer.data()); + return std::string(buffer.begin(), buffer.end()); + } + + TemporaryDirectory() : filename_(CreateTemporaryFilename()) + { + + } + + ~TemporaryDirectory() + { + remove(filename_.c_str()); + } + + std::string GetFilename() const { return filename_; } + +private: + const std::string filename_; +}; diff --git a/source/tests/TemporaryFile.h b/source/tests/TemporaryFile.h new file mode 100644 index 00000000..eab45b16 --- /dev/null +++ b/source/tests/TemporaryFile.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class TemporaryFile +{ +public: + static std::string CreateTemporaryFilename() + { + std::array buffer; + tmpnam(buffer.data()); + return std::string(buffer.begin(), buffer.end()); + } + + TemporaryFile() : filename_(CreateTemporaryFilename()) + { + + } + + ~TemporaryFile() + { + remove(filename_.c_str()); + } + + std::string GetFilename() const { return filename_; } + +private: + const std::string filename_; +}; \ No newline at end of file diff --git a/source/tests/WorksheetTestSuite.h b/source/tests/WorksheetTestSuite.h index 88a02ea6..bdf29c7f 100644 --- a/source/tests/WorksheetTestSuite.h +++ b/source/tests/WorksheetTestSuite.h @@ -34,7 +34,9 @@ public: void test_set_bad_title() { - Worksheet(wb, "X" * 50); + std::string title(50, 'X'); + xlnt::workbook wb; + wb.create_sheet(title); } void test_set_bad_title_character() diff --git a/source/tests/WriteTestSuite.h b/source/tests/WriteTestSuite.h index 3f8bf544..508db271 100644 --- a/source/tests/WriteTestSuite.h +++ b/source/tests/WriteTestSuite.h @@ -4,6 +4,7 @@ #include #include "../xlnt.h" +#include "TemporaryDirectory.h" class WriteTestSuite : public CxxTest::TestSuite { @@ -15,12 +16,11 @@ public: void test_write_empty_workbook() { - make_tmpdir(); + TemporaryDirectory temp_dir; wb = Workbook(); dest_filename = os.path.join(TMPDIR, "empty_book.xlsx"); save_workbook(wb, dest_filename); assert os.path.isfile(dest_filename); - clean_tmpdir(); } void test_write_virtual_workbook() diff --git a/source/xlnt.cpp b/source/xlnt.cpp index d0bbf533..8d67a7b2 100644 --- a/source/xlnt.cpp +++ b/source/xlnt.cpp @@ -6,6 +6,7 @@ #include #include "xlnt.h" +#include "../third-party/pugixml/src/pugixml.hpp" namespace xlnt { @@ -971,6 +972,8 @@ struct cell_struct bool bool_value; }; + + std::string error_value; tm date_value; std::string string_value; std::string formula_value; @@ -1015,12 +1018,27 @@ cell::cell(cell_struct *root) : root_(root) cell::type cell::data_type_for_value(const std::string &value) { + if(value[0] == '=') + { + return type::formula; + } + return type::null; } void cell::set_explicit_value(const std::string &value, type data_type) { - + root_->type = data_type; + switch(data_type) + { + case type::formula: root_->formula_value = value; return; + case type::date: root_->date_value.tm_hour = std::stoi(value); return; + case type::error: root_->error_value = value; return; + case type::boolean: root_->bool_value = value == "true"; return; + case type::null: return; + case type::numeric: root_->numeric_value = std::stod(value); return; + case type::string: root_->string_value = value; return; + } } bool cell::bind_value() @@ -1620,17 +1638,17 @@ void worksheet::unmerge_cells(int start_row, int start_column, int end_row, int root_->unmerge_cells(start_row, start_column, end_row, end_column); } -void worksheet::append(const std::vector &cells) +void worksheet::append(const std::vector &cells) { root_->append(cells); } -void worksheet::append(const std::unordered_map &cells) +void worksheet::append(const std::unordered_map &cells) { root_->append(cells); } -void worksheet::append(const std::unordered_map &cells) +void worksheet::append(const std::unordered_map &cells) { root_->append(cells); } @@ -1676,11 +1694,146 @@ cell worksheet::operator[](const std::string &address) return cell(address); } -workbook::workbook() : active_worksheet_(nullptr) +std::string workbook::write_content_types(workbook &wb) +{ + std::set seen; + + pugi::xml_node root; + + if(wb.has_vba_archive()) + { + root = fromstring(wb.get_vba_archive().read(ARC_CONTENT_TYPES)); + + for(auto elem : root.findall("{" + CONTYPES_NS + "}Override")) + { + seen.insert(elem.attrib["PartName"]); + } + } + else + { + root = Element("{" + CONTYPES_NS + "}Types"); + + for(auto content_type : static_content_types_config) + { + if(setting_type == "Override") + { + tag = "{" + CONTYPES_NS + "}Override"; + attrib = {"PartName": "/" + name}; + } + else + { + tag = "{" + CONTYPES_NS + "}Default"; + attrib = {"Extension": name}; + } + + attrib["ContentType"] = content_type; + SubElement(root, tag, attrib); + } + } + + int drawing_id = 1; + int chart_id = 1; + int comments_id = 1; + int sheet_id = 0; + + for(auto sheet : wb) + { + std::string name = "/xl/worksheets/sheet" + std::to_string(sheet_id) + ".xml"; + + if(seen.find(name) == seen.end()) + { + SubElement(root, "{" + CONTYPES_NS + "}Override", {{"PartName", name}, + {"ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"}}); + } + + if(sheet._charts || sheet._images) + { + name = "/xl/drawings/drawing" + drawing_id + ".xml"; + + if(seen.find(name) == seen.end()) + { + SubElement(root, "{%s}Override" % CONTYPES_NS, {"PartName" : name, + "ContentType" : "application/vnd.openxmlformats-officedocument.drawing+xml"}); + } + + drawing_id += 1; + + for(auto chart : sheet._charts) + { + name = "/xl/charts/chart%d.xml" % chart_id; + + if(seen.find(name) == seen.end()) + { + SubElement(root, "{%s}Override" % CONTYPES_NS, {"PartName" : name, + "ContentType" : "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"}); + } + + chart_id += 1; + + if(chart._shapes) + { + name = "/xl/drawings/drawing%d.xml" % drawing_id; + + if(seen.find(name) == seen.end()) + { + SubElement(root, "{%s}Override" % CONTYPES_NS, {"PartName" : name, + "ContentType" : "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"}); + } + + drawing_id += 1; + } + } + } + if(sheet.get_comment_count() > 0) + { + SubElement(root, "{%s}Override" % CONTYPES_NS, + {"PartName": "/xl/comments%d.xml" % comments_id, + "ContentType" : "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"}); + comments_id += 1; + } + } + + return get_document_content(root); +} + +std::string workbook::write_root_rels(workbook wb) +{ + root = Element("{%s}Relationships" % PKG_REL_NS); + relation_tag = "{%s}Relationship" % PKG_REL_NS; + SubElement(root, relation_tag, {"Id": "rId1", "Target" : ARC_WORKBOOK, + "Type" : "%s/officeDocument" % REL_NS}); + SubElement(root, relation_tag, {"Id": "rId2", "Target" : ARC_CORE, + "Type" : "%s/metadata/core-properties" % PKG_REL_NS}); + SubElement(root, relation_tag, {"Id": "rId3", "Target" : ARC_APP, + "Type" : "%s/extended-properties" % REL_NS}); + if(wb.has_vba_archive()) + { + // See if there was a customUI relation and reuse its id + arc = fromstring(workbook.vba_archive.read(ARC_ROOT_RELS)); + rels = arc.findall(relation_tag); + rId = None; + for(rel in rels) + { + if(rel.get("Target") == ARC_CUSTOM_UI) + { + rId = rel.get("Id"); + break; + } + } + if(rId is not None) + { + SubElement(root, relation_tag, {"Id": rId, "Target" : ARC_CUSTOM_UI, + "Type" : "%s" % CUSTOMUI_NS}); + } + } + + return get_document_content(root); +} + +workbook::workbook() : active_sheet_index_(0) { auto ws = create_sheet(); ws.set_title("Sheet1"); - active_worksheet_ = ws; } worksheet workbook::get_sheet_by_name(const std::string &name) @@ -1695,7 +1848,7 @@ worksheet workbook::get_sheet_by_name(const std::string &name) worksheet workbook::get_active_sheet() { - return active_worksheet_; + return worksheets_[active_sheet_index_]; } worksheet workbook::create_sheet() diff --git a/source/xlnt.h b/source/xlnt.h index 899b93b6..11c8d17e 100644 --- a/source/xlnt.h +++ b/source/xlnt.h @@ -12,17 +12,21 @@ struct tm; namespace xlnt { -struct cell_struct; -struct worksheet_struct; -struct package_impl; - -class style; -class worksheet; -class worksheet; class cell; -class relationship; -class workbook; +class comment; +class drawing; +class named_range; class package; +class relationship; +class style; +class workbook; +class worksheet; + +struct cell_struct; +struct drawing_struct; +struct named_range_struct; +struct package_impl; +struct worksheet_struct; const int MIN_ROW = 0; const int MIN_COLUMN = 0; @@ -192,6 +196,12 @@ enum class target_mode Internal }; +enum class encoding_type +{ + utf8, + latin1 +}; + /// /// Represents a value type that may or may not have an assigned value. /// @@ -798,25 +808,278 @@ struct coordinate int row; }; +/// +/// Alignment options for use in styles. +/// +struct alignment +{ + enum class horizontal_alignment + { + general, + left, + right, + center, + center_continuous, + justify + }; + + enum class vertical_alignment + { + bottom, + top, + center, + justify + }; + + horizontal_alignment horizontal = horizontal_alignment::general; + vertical_alignment vertical = vertical_alignment::bottom; + int text_rotation = 0; + bool wrap_text = false; + bool shrink_to_fit = false; + int indent = 0; +}; + class number_format { public: - void set_format_code(const std::string &format_code) { format_code_ = format_code; } + enum class format + { + general, + text, + number, + number00, + number_comma_separated1, + number_comma_separated2, + percentage, + percentage00, + date_yyyymmdd2, + date_yyyymmdd, + date_ddmmyyyy, + date_dmyslash, + date_dmyminus, + date_dmminus, + date_myminus, + date_xlsx14, + date_xlsx15, + date_xlsx16, + date_xlsx17, + date_xlsx22, + date_datetime, + date_time1, + date_time2, + date_time3, + date_time4, + date_time5, + date_time6, + date_time7, + date_time8, + date_timedelta, + date_yyyymmddslash, + currency_usd_simple, + currency_usd, + currency_eur_simple + }; + + static const std::unordered_map builtin_formats; + + static std::string builtin_format_code(int index); + + static bool is_date_format(const std::string &format); + static bool is_builtin(const std::string &format); + + format get_format_code() const { return format_code_; } + void set_format_code(format format_code) { format_code_ = format_code; } private: - std::string format_code_; + format format_code_ = format::general; + int format_index_ = 0; +}; + +struct color +{ + static const color black; + static const color white; + static const color red; + static const color darkred; + static const color blue; + static const color darkblue; + static const color green; + static const color darkgreen; + static const color yellow; + static const color darkyellow; + + color(int index) + { + this->index = index; + } + + int index; +}; + +class font +{ + enum class underline + { + none, + double_, + double_accounting, + single, + single_accounting + }; + + std::string name = "Calibri"; + int size = 11; + bool bold = false; + bool italic = false; + bool superscript = false; + bool subscript = false; + underline underline = underline::none; + bool strikethrough = false; + color color = color::black; +}; + +class fill +{ +public: + enum class type + { + none, + solid, + gradient_linear, + gradient_path, + pattern_darkdown, + pattern_darkgray, + pattern_darkgrid, + pattern_darkhorizontal, + pattern_darktrellis, + pattern_darkup, + pattern_darkvertical, + pattern_gray0625, + pattern_gray125, + pattern_lightdown, + pattern_lightgray, + pattern_lightgrid, + pattern_lighthorizontal, + pattern_lighttrellis, + pattern_lightup, + pattern_lightvertical, + pattern_mediumgray, + }; + + type type = type::none; + int rotation = 0; + color start_color = color::white; + color end_color = color::black; +}; + +class borders +{ + struct border + { + enum class style + { + none, + dashdot, + dashdotdot, + dashed, + dotted, + double_, + hair, + medium, + mediumdashdot, + mediumdashdotdot, + mediumdashed, + slantdashdot, + thick, + thin + }; + + style style = style::none; + color color = color::black; + }; + + enum class diagonal_direction + { + none, + up, + down, + both + }; + + border left; + border right; + border top; + border bottom; + border diagonal; + diagonal_direction diagonal_direction = diagonal_direction::none; + border all_borders; + border outline; + border inside; + border vertical; + border horizontal; +}; + +class protection +{ +public: + enum class type + { + inherit, + protected_, + unprotected + }; + + type locked; + type hidden; }; class style { public: + style(bool static_ = false) : static_(static_) {} + + style copy() const; + + font get_font() const; + void set_font(font font); + + fill get_fill() const; + void set_fill(fill fill); + + borders get_borders() const; + void set_borders(borders borders); + + alignment get_alignment() const; + void set_alignment(alignment alignment); + number_format &get_number_format() { return number_format_; } const number_format &get_number_format() const { return number_format_; } + void set_number_format(number_format number_format); + + protection get_protection() const; + void set_protection(protection protection); private: + style(const style &rhs); + + bool static_ = false; + font font_; + fill fill_; + borders borders_; + alignment alignment_; number_format number_format_; + protection protection_; }; +/// +/// Describes cell associated properties. +/// +/// +/// Properties of interest include style, type, value, and address. +/// The Cell class is required to know its value and type, display options, +/// and any other features of an Excel cell.Utilities for referencing +/// cells using Excel's 'A1' column/row nomenclature are also provided. +/// class cell { public: @@ -833,15 +1096,46 @@ public: static const std::unordered_map ErrorCodes; + /// + /// Convert a coordinate string like 'B12' to a tuple ('B', 12) + /// static coordinate coordinate_from_string(const std::string &address); - static int column_index_from_string(const std::string &column_string); - static std::string get_column_letter(int column_index); + + /// + /// Convert a coordinate to an absolute coordinate string (B12 -> $B$12) + /// static std::string absolute_coordinate(const std::string &absolute_address); + /// + /// Convert a column letter into a column number (e.g. B -> 2) + /// + /// + /// Excel only supports 1 - 3 letter column names from A->ZZZ, so we + /// restrict our column names to 1 - 3 characters, each in the range A - Z. + /// + static int column_index_from_string(const std::string &column_string); + + /// + /// Convert a column number into a column letter (3 -> 'C') + /// + /// + /// Right shift the column col_idx by 26 to find column letters in reverse + /// order.These numbers are 1 - based, and can be converted to ASCII + /// ordinals by adding 64. + /// + static std::string get_column_letter(int column_index); + + 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 std::string &column, int row); cell(worksheet &ws, const std::string &column, int row, const std::string &initial_value); + encoding_type get_encoding() const; + std::string to_string() const; + void set_explicit_value(const std::string &value, type data_type); type data_type_for_value(const std::string &value); @@ -853,32 +1147,59 @@ public: bool bind_value(bool value); bool bind_value(const tm &value); + std::string get_hyperlink() const; + void set_hyperlink(const std::string &value); + + std::string get_hyperlink_rel_id() const; + + void set_number_format(const std::string &format_code); + + bool has_style() const; + + style &get_style(); + const style &get_style() const; + + type get_data_type() const; + + std::string get_coordinate() const; + std::string get_address() const; + + cell get_offset(int row, int column); + + bool is_date() const; + + std::pair get_anchor() const; + + comment get_comment() const; + void set_comment(comment comment); + + cell &operator=(const cell &rhs); + cell &operator=(bool value); cell &operator=(int value); cell &operator=(double value); cell &operator=(const std::string &value); cell &operator=(const char *value); - cell &operator=(bool value); cell &operator=(const tm &value); + bool operator==(std::nullptr_t) const; + bool operator==(bool comparand) const; + bool operator==(int comparand) const; + bool operator==(double comparand) const; bool operator==(const std::string &comparand) const; bool operator==(const char *comparand) const; bool operator==(const tm &comparand) const; + friend bool operator==(std::nullptr_t, const cell &cell); + friend bool operator==(bool comparand, const cell &cell); + friend bool operator==(int comparand, const cell &cell); + friend bool operator==(double comparand, const cell &cell); friend bool operator==(const std::string &comparand, const cell &cell); friend bool operator==(const char *comparand, const cell &cell); friend bool operator==(const tm &comparand, const cell &cell); - std::string to_string() const; - bool is_date() const; - style &get_style(); - const style &get_style() const; - type get_data_type() const; - private: friend struct worksheet_struct; - cell(cell_struct *root); - cell_struct *root_; }; @@ -951,9 +1272,9 @@ public: void merge_cells(int start_row, int start_column, int end_row, int end_column); void unmerge_cells(const std::string &range_string); void unmerge_cells(int start_row, int start_column, int end_row, int end_column); - void append(const std::vector &cells); - void append(const std::unordered_map &cells); - void append(const std::unordered_map &cells); + void append(const std::vector &cells); + void append(const std::unordered_map &cells); + void append(const std::unordered_map &cells); xlnt::range rows() const; xlnt::range columns() const; bool operator==(const worksheet &other) const; @@ -967,26 +1288,111 @@ private: worksheet_struct *root_; }; +class named_range +{ +public: + named_range(const std::string &name); + +private: + friend class worksheet; + named_range(named_range_struct *root); + named_range_struct *root_; +}; + +class drawing +{ +public: + drawing(); + +private: + friend class worksheet; + drawing(drawing_struct *root); + drawing_struct *root_; +}; + class workbook { public: + static std::string write_content_types(workbook &wb); + static std::string write_root_rels(workbook &wb); + + //constructors workbook(); + + //prevent copy and assignment workbook(const workbook &) = delete; const workbook &operator=(const workbook &) = delete; - worksheet get_sheet_by_name(const std::string &sheet_name); + //named parameters + workbook &optimized_write(bool value); + workbook &encoding(encoding_type value); + workbook &guess_types(bool value); + workbook &data_only(bool value); + + void read_workbook_settings(const std::string &xml_source); + + //getters worksheet get_active_sheet(); + bool get_optimized_write() const { return optimized_write_; } + encoding_type get_encoding() const { return encoding_; } + bool get_guess_types() const { return guess_types_; } + bool get_data_only() const { return data_only_; } + + //create worksheet create_sheet(); worksheet create_sheet(std::size_t index); - std::vector get_sheet_names() const; + worksheet create_sheet(const std::string &title); + worksheet create_sheet(std::size_t index, const std::string &title); + + //add + void add_sheet(worksheet worksheet); + void add_sheet(worksheet worksheet, std::size_t index); + + //remove + void remove_sheet(worksheet worksheet); + + //container operations + worksheet get_sheet_by_name(const std::string &sheet_name); + + bool contains(const std::string &key) const; + + int get_index(worksheet worksheet); + + worksheet operator[](const std::string &name); + std::vector::iterator begin(); std::vector::iterator end(); - worksheet operator[](const std::string &name); + + std::vector::const_iterator begin() const; + std::vector::const_iterator end() const; + + std::vector::const_iterator cbegin() const; + std::vector::const_iterator cend() const; + + std::vector get_sheet_names() const; + + //named ranges + void create_named_range(const std::string &name, worksheet worksheet, const std::string &range_string); + std::vector get_named_ranges(); + void add_named_range(named_range named_range); + void get_named_range(const std::string &name); + void remove_named_range(named_range named_range); + + //serialization void save(const std::string &filename); + void load(const std::string &filename); private: - worksheet active_worksheet_; + bool optimized_write_; + bool optimized_read_; + bool guess_types_; + bool data_only_; + int active_sheet_index_; + encoding_type encoding_; std::vector worksheets_; + std::vector named_ranges_; + std::vector relationships_; + std::vector drawings_; }; } // namespace xlnt