diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a4089b8..3487da85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,8 @@ set(ODR_SOURCE_FILES "src/odr/odr.cpp" "src/odr/quantity.cpp" "src/odr/style.cpp" + "src/odr/table_dimension.cpp" + "src/odr/table_position.cpp" "${CMAKE_CURRENT_BINARY_DIR}/src/odr/internal/git_info.cpp" "src/odr/internal/magic.cpp" @@ -99,7 +101,6 @@ set(ODR_SOURCE_FILES "src/odr/internal/cfb/cfb_util.cpp" "src/odr/internal/common/document.cpp" - "src/odr/internal/common/document_element.cpp" "src/odr/internal/common/file.cpp" "src/odr/internal/common/filesystem.cpp" "src/odr/internal/common/image_file.cpp" @@ -107,7 +108,7 @@ set(ODR_SOURCE_FILES "src/odr/internal/common/random.cpp" "src/odr/internal/common/style.cpp" "src/odr/internal/common/table_cursor.cpp" - "src/odr/internal/common/table_position.cpp" + "src/odr/table_position.cpp" "src/odr/internal/common/table_range.cpp" "src/odr/internal/common/temporary_file.cpp" @@ -133,25 +134,24 @@ set(ODR_SOURCE_FILES "src/odr/internal/odf/odf_crypto.cpp" "src/odr/internal/odf/odf_document.cpp" - "src/odr/internal/odf/odf_element.cpp" + "src/odr/internal/odf/odf_element_registry.cpp" "src/odr/internal/odf/odf_file.cpp" "src/odr/internal/odf/odf_manifest.cpp" "src/odr/internal/odf/odf_meta.cpp" "src/odr/internal/odf/odf_parser.cpp" - "src/odr/internal/odf/odf_spreadsheet.cpp" "src/odr/internal/odf/odf_style.cpp" "src/odr/internal/oldms/oldms_file.cpp" "src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp" - "src/odr/internal/ooxml/presentation/ooxml_presentation_element.cpp" + "src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp" "src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp" "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp" - "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.cpp" "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp" + "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp" "src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp" "src/odr/internal/ooxml/text/ooxml_text_document.cpp" - "src/odr/internal/ooxml/text/ooxml_text_element.cpp" + "src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp" "src/odr/internal/ooxml/text/ooxml_text_parser.cpp" "src/odr/internal/ooxml/text/ooxml_text_style.cpp" "src/odr/internal/ooxml/ooxml_crypto.cpp" diff --git a/README.md b/README.md index 683f5967..2804cd9e 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,13 @@ As an alternative to the Conan remote you can also export the package locally vi ## Version Versions and history are tracked on [GitHub](https://github.com/opendocument-app/OpenDocument.core). + +## Testing + +### Running the HTML Comparison Server + +Scripts and Docker images can be found here https://github.com/opendocument-app/compare-html + +```bash +./test/scripts/compare_output_server.sh +``` diff --git a/cli/src/back_translate.cpp b/cli/src/back_translate.cpp index 5e9bd643..eefee80b 100644 --- a/cli/src/back_translate.cpp +++ b/cli/src/back_translate.cpp @@ -4,12 +4,14 @@ #include -#include #include using namespace odr; int main(int, char **argv) { + const std::shared_ptr logger = + Logger::create_stdio("odr-back-translate", LogLevel::verbose); + const std::string input{argv[1]}; const std::string diff_path{argv[2]}; const std::string output{argv[3]}; @@ -17,7 +19,7 @@ int main(int, char **argv) { const DocumentFile document_file{input}; if (document_file.password_encrypted()) { - std::cerr << "encrypted documents are not supported" << std::endl; + ODR_FATAL(*logger, "encrypted documents are not supported"); return 1; } diff --git a/cli/src/meta.cpp b/cli/src/meta.cpp index d6780671..82466604 100644 --- a/cli/src/meta.cpp +++ b/cli/src/meta.cpp @@ -10,21 +10,27 @@ using namespace odr; int main(const int argc, char **argv) { + const std::shared_ptr logger = + Logger::create_stdio("odr-meta", LogLevel::verbose); + const std::string input{argv[1]}; - const bool has_password = argc >= 4; - std::string password; - if (has_password) { + std::optional password; + if (argc >= 3) { password = argv[2]; } DocumentFile document_file{input}; - if (document_file.password_encrypted() && has_password) { + if (document_file.password_encrypted() && !password) { + ODR_FATAL(*logger, "document encrypted but no password given"); + return 2; + } + if (document_file.password_encrypted()) { try { - document_file = document_file.decrypt(password); + document_file = document_file.decrypt(*password); } catch (const WrongPasswordError &) { - std::cerr << "wrong password" << std::endl; + ODR_FATAL(*logger, "wrong password"); return 1; } } diff --git a/cli/src/server.cpp b/cli/src/server.cpp index 2c5b6b3b..18e2b865 100644 --- a/cli/src/server.cpp +++ b/cli/src/server.cpp @@ -1,15 +1,17 @@ +#include #include #include +#include #include #include -#include #include using namespace odr; int main(const int argc, char **argv) { - auto logger = Logger::create_stdio("odr-server", LogLevel::verbose); + const std::shared_ptr logger = + Logger::create_stdio("odr-server", LogLevel::verbose); std::string input{argv[1]}; @@ -48,8 +50,9 @@ int main(const int argc, char **argv) { html_config.editable = true; { - std::string prefix = "one_file"; - HtmlViews views = server.serve_file(decoded_file, prefix, html_config); + const std::string prefix = "file"; + const HtmlViews views = + server.serve_file(decoded_file, prefix, html_config); ODR_INFO(*logger, "hosted decoded file with id: " << prefix); for (const auto &view : views) { ODR_INFO(*logger, @@ -57,6 +60,26 @@ int main(const int argc, char **argv) { } } + if (decoded_file.is_document_file() || decoded_file.is_archive_file()) { + std::optional filesystem; + if (decoded_file.is_document_file()) { + filesystem = decoded_file.as_document_file().document().as_filesystem(); + } else if (decoded_file.is_archive_file()) { + filesystem = decoded_file.as_archive_file().archive().as_filesystem(); + } + + const std::string prefix = "filesystem"; + const HtmlService filesystem_service = html::translate( + filesystem.value(), server_config.cache_path + "/" + prefix, + html_config, logger); + server.connect_service(filesystem_service, prefix); + ODR_INFO(*logger, "hosted filesystem with id: " << prefix); + for (const auto &view : filesystem_service.list_views()) { + ODR_INFO(*logger, + "http://localhost:8080/file/" << prefix << "/" << view.path()); + } + } + server.listen("localhost", 8080); return 0; diff --git a/cli/src/translate.cpp b/cli/src/translate.cpp index b9871078..d294d274 100644 --- a/cli/src/translate.cpp +++ b/cli/src/translate.cpp @@ -3,12 +3,14 @@ #include #include -#include #include using namespace odr; int main(const int argc, char **argv) { + const std::shared_ptr logger = + Logger::create_stdio("odr-translate", LogLevel::verbose); + const std::string input{argv[1]}; const std::string output{argv[2]}; @@ -20,14 +22,14 @@ int main(const int argc, char **argv) { DecodedFile decoded_file{input}; if (decoded_file.password_encrypted() && !password) { - std::cerr << "document encrypted but no password given" << std::endl; + ODR_FATAL(*logger, "document encrypted but no password given"); return 2; } if (decoded_file.password_encrypted()) { try { decoded_file = decoded_file.decrypt(*password); } catch (const WrongPasswordError &) { - std::cerr << "wrong password" << std::endl; + ODR_FATAL(*logger, "wrong password"); return 1; } } diff --git a/src/odr/definitions.hpp b/src/odr/definitions.hpp new file mode 100644 index 00000000..3627eac3 --- /dev/null +++ b/src/odr/definitions.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace odr { + +using ElementIdentifier = std::uint64_t; + +static constexpr ElementIdentifier null_element_id{0}; + +} // namespace odr diff --git a/src/odr/document.cpp b/src/odr/document.cpp index ccd33bc8..23b0b98a 100644 --- a/src/odr/document.cpp +++ b/src/odr/document.cpp @@ -39,7 +39,7 @@ DocumentType Document::document_type() const noexcept { } Element Document::root_element() const { - return {m_impl.get(), m_impl->root_element()}; + return {m_impl->element_adapter(), m_impl->root_element()}; } Filesystem Document::as_filesystem() const { diff --git a/src/odr/document_element.cpp b/src/odr/document_element.cpp index c20046bf..7bf3415d 100644 --- a/src/odr/document_element.cpp +++ b/src/odr/document_element.cpp @@ -2,131 +2,172 @@ #include #include +#include +#include #include -#include namespace odr { Element::Element() = default; -Element::Element(const internal::abstract::Document *document, - internal::abstract::Element *element) - : m_document{document}, m_element{element} {} - -bool Element::operator==(const Element &rhs) const { - return m_element == rhs.m_element; -} - -bool Element::operator!=(const Element &rhs) const { - return m_element != rhs.m_element; -} +Element::Element(const internal::abstract::ElementAdapter *adapter, + const ElementIdentifier identifier) + : m_adapter{adapter}, m_identifier{identifier} {} Element::operator bool() const { return exists_(); } -bool Element::exists_() const { return m_element != nullptr; } +bool Element::exists_() const { + return m_adapter != nullptr && m_identifier != null_element_id; +} ElementType Element::type() const { - return exists_() ? m_element->type(m_document) : ElementType::none; + return exists_() ? m_adapter->element_type(m_identifier) : ElementType::none; } Element Element::parent() const { - return exists_() ? Element(m_document, m_element->parent(m_document)) + return exists_() ? Element(m_adapter, m_adapter->element_parent(m_identifier)) : Element(); } Element Element::first_child() const { - return exists_() ? Element(m_document, m_element->first_child(m_document)) - : Element(); + return exists_() + ? Element(m_adapter, m_adapter->element_first_child(m_identifier)) + : Element(); } Element Element::previous_sibling() const { - return exists_() - ? Element(m_document, m_element->previous_sibling(m_document)) - : Element(); + return exists_() ? Element(m_adapter, + m_adapter->element_previous_sibling(m_identifier)) + : Element(); } Element Element::next_sibling() const { - return exists_() ? Element(m_document, m_element->next_sibling(m_document)) - : Element(); + return exists_() + ? Element(m_adapter, m_adapter->element_next_sibling(m_identifier)) + : Element(); } bool Element::is_editable() const { - return exists_() ? m_element->is_editable(m_document) : false; + return exists_() ? m_adapter->element_is_editable(m_identifier) : false; } -TextRoot Element::as_text_root() const { return {m_document, m_element}; } +TextRoot Element::as_text_root() const { + return {m_adapter, m_identifier, m_adapter->text_root_adapter(m_identifier)}; +} -Slide Element::as_slide() const { return {m_document, m_element}; } +Slide Element::as_slide() const { + return {m_adapter, m_identifier, m_adapter->slide_adapter(m_identifier)}; +} -Sheet Element::as_sheet() const { return {m_document, m_element}; } +Sheet Element::as_sheet() const { + return {m_adapter, m_identifier, m_adapter->sheet_adapter(m_identifier)}; +} -Page Element::as_page() const { return {m_document, m_element}; } +Page Element::as_page() const { + return {m_adapter, m_identifier, m_adapter->page_adapter(m_identifier)}; +} -MasterPage Element::as_master_page() const { return {m_document, m_element}; } +SheetCell Element::as_sheet_cell() const { + return {m_adapter, m_identifier, m_adapter->sheet_cell_adapter(m_identifier)}; +} -LineBreak Element::as_line_break() const { return {m_document, m_element}; } +MasterPage Element::as_master_page() const { + return {m_adapter, m_identifier, + m_adapter->master_page_adapter(m_identifier)}; +} + +LineBreak Element::as_line_break() const { + return {m_adapter, m_identifier, m_adapter->line_break_adapter(m_identifier)}; +} -Paragraph Element::as_paragraph() const { return {m_document, m_element}; } +Paragraph Element::as_paragraph() const { + return {m_adapter, m_identifier, m_adapter->paragraph_adapter(m_identifier)}; +} -Span Element::as_span() const { return {m_document, m_element}; } +Span Element::as_span() const { + return {m_adapter, m_identifier, m_adapter->span_adapter(m_identifier)}; +} -Text Element::as_text() const { return {m_document, m_element}; } +Text Element::as_text() const { + return {m_adapter, m_identifier, m_adapter->text_adapter(m_identifier)}; +} -Link Element::as_link() const { return {m_document, m_element}; } +Link Element::as_link() const { + return {m_adapter, m_identifier, m_adapter->link_adapter(m_identifier)}; +} -Bookmark Element::as_bookmark() const { return {m_document, m_element}; } +Bookmark Element::as_bookmark() const { + return {m_adapter, m_identifier, m_adapter->bookmark_adapter(m_identifier)}; +} -ListItem Element::as_list_item() const { return {m_document, m_element}; } +ListItem Element::as_list_item() const { + return {m_adapter, m_identifier, m_adapter->list_item_adapter(m_identifier)}; +} -Table Element::as_table() const { return {m_document, m_element}; } +Table Element::as_table() const { + return {m_adapter, m_identifier, m_adapter->table_adapter(m_identifier)}; +} -TableColumn Element::as_table_column() const { return {m_document, m_element}; } +TableColumn Element::as_table_column() const { + return {m_adapter, m_identifier, + m_adapter->table_column_adapter(m_identifier)}; +} -TableRow Element::as_table_row() const { return {m_document, m_element}; } +TableRow Element::as_table_row() const { + return {m_adapter, m_identifier, m_adapter->table_row_adapter(m_identifier)}; +} -TableCell Element::as_table_cell() const { return {m_document, m_element}; } +TableCell Element::as_table_cell() const { + return {m_adapter, m_identifier, m_adapter->table_cell_adapter(m_identifier)}; +} -Frame Element::as_frame() const { return {m_document, m_element}; } +Frame Element::as_frame() const { + return {m_adapter, m_identifier, m_adapter->frame_adapter(m_identifier)}; +} -Rect Element::as_rect() const { return {m_document, m_element}; } +Rect Element::as_rect() const { + return {m_adapter, m_identifier, m_adapter->rect_adapter(m_identifier)}; +} -Line Element::as_line() const { return {m_document, m_element}; } +Line Element::as_line() const { + return {m_adapter, m_identifier, m_adapter->line_adapter(m_identifier)}; +} -Circle Element::as_circle() const { return {m_document, m_element}; } +Circle Element::as_circle() const { + return {m_adapter, m_identifier, m_adapter->circle_adapter(m_identifier)}; +} -CustomShape Element::as_custom_shape() const { return {m_document, m_element}; } +CustomShape Element::as_custom_shape() const { + return {m_adapter, m_identifier, + m_adapter->custom_shape_adapter(m_identifier)}; +} -Image Element::as_image() const { return {m_document, m_element}; } +Image Element::as_image() const { + return {m_adapter, m_identifier, m_adapter->image_adapter(m_identifier)}; +} ElementRange Element::children() const { - return {exists_() - ? ElementIterator(m_document, m_element->first_child(m_document)) - : ElementIterator(), + return {exists_() ? ElementIterator(m_adapter, m_adapter->element_first_child( + m_identifier)) + : ElementIterator(), ElementIterator()}; } ElementIterator::ElementIterator() = default; -ElementIterator::ElementIterator(const internal::abstract::Document *document, - internal::abstract::Element *element) - : m_document{document}, m_element{element} {} - -bool ElementIterator::operator==(const ElementIterator &rhs) const { - return m_element == rhs.m_element; -} - -bool ElementIterator::operator!=(const ElementIterator &rhs) const { - return m_element != rhs.m_element; -} +ElementIterator::ElementIterator( + const internal::abstract::ElementAdapter *adapter, + const ElementIdentifier identifier) + : m_adapter{adapter}, m_identifier{identifier} {} ElementIterator::reference ElementIterator::operator*() const { - return {m_document, m_element}; + return {m_adapter, m_identifier}; } ElementIterator &ElementIterator::operator++() { if (exists_()) { - m_element = m_element->next_sibling(m_document); + m_identifier = m_adapter->element_next_sibling(m_identifier); } return *this; } @@ -135,17 +176,19 @@ ElementIterator ElementIterator::operator++(int) { if (!exists_()) { return {}; } - return {m_document, m_element->next_sibling(m_document)}; + return {m_adapter, m_adapter->element_next_sibling(m_identifier)}; } -bool ElementIterator::exists_() const { return m_element != nullptr; } +bool ElementIterator::exists_() const { + return m_adapter != nullptr && m_identifier != null_element_id; +} ElementRange::ElementRange() = default; -ElementRange::ElementRange(const ElementIterator begin) : m_begin{begin} {} +ElementRange::ElementRange(const ElementIterator &begin) : m_begin{begin} {} -ElementRange::ElementRange(const ElementIterator begin, - const ElementIterator end) +ElementRange::ElementRange(const ElementIterator &begin, + const ElementIterator &end) : m_begin{begin}, m_end{end} {} ElementIterator ElementRange::begin() const { return m_begin; } @@ -153,346 +196,373 @@ ElementIterator ElementRange::begin() const { return m_begin; } ElementIterator ElementRange::end() const { return m_end; } PageLayout TextRoot::page_layout() const { - return exists_() ? m_element->page_layout(m_document) : PageLayout(); + return exists_() ? m_adapter2->text_root_page_layout(m_identifier) + : PageLayout(); } MasterPage TextRoot::first_master_page() const { - return exists_() - ? MasterPage(m_document, m_element->first_master_page(m_document)) - : MasterPage(); + if (!exists_()) { + return {}; + } + const ElementIdentifier master_page_id = + m_adapter2->text_root_first_master_page(m_identifier); + return {m_adapter, master_page_id, + m_adapter->master_page_adapter(master_page_id)}; } std::string Slide::name() const { - return exists_() ? m_element->name(m_document) : ""; + return exists_() ? m_adapter2->slide_name(m_identifier) : ""; } PageLayout Slide::page_layout() const { - return exists_() ? m_element->page_layout(m_document) : PageLayout(); + return exists_() ? m_adapter2->slide_page_layout(m_identifier) : PageLayout(); } MasterPage Slide::master_page() const { - return exists_() ? MasterPage(m_document, m_element->master_page(m_document)) - : MasterPage(); + if (!exists_()) { + return {}; + } + const ElementIdentifier master_page_id = + m_adapter2->slide_master_page(m_identifier); + return {m_adapter, master_page_id, + m_adapter->master_page_adapter(master_page_id)}; } std::string Sheet::name() const { - return exists_() ? m_element->name(m_document) : ""; + return exists_() ? m_adapter2->sheet_name(m_identifier) : ""; } TableDimensions Sheet::dimensions() const { - return exists_() ? m_element->dimensions(m_document) : TableDimensions(); + return exists_() ? m_adapter2->sheet_dimensions(m_identifier) + : TableDimensions(); } TableDimensions Sheet::content(const std::optional range) const { - return exists_() ? m_element->content(m_document, range) : TableDimensions(); -} - -SheetColumn Sheet::column(const std::uint32_t column) const { - return exists_() ? SheetColumn(m_document, m_element, column) : SheetColumn(); -} - -SheetRow Sheet::row(const std::uint32_t row) const { - return exists_() ? SheetRow(m_document, m_element, row) : SheetRow(); + return exists_() ? m_adapter2->sheet_content(m_identifier, range) + : TableDimensions(); } SheetCell Sheet::cell(const std::uint32_t column, const std::uint32_t row) const { - return exists_() ? SheetCell(m_document, m_element, column, row, - m_element->cell(m_document, column, row)) - : SheetCell(); + if (!exists_()) { + return {}; + } + const ElementIdentifier cell_id = + m_adapter2->sheet_cell(m_identifier, column, row); + return {m_adapter, cell_id, m_adapter->sheet_cell_adapter(cell_id)}; } ElementRange Sheet::shapes() const { - return exists_() ? ElementRange(ElementIterator( - m_document, m_element->first_shape(m_document))) - : ElementRange(); + if (!exists_()) { + return {}; + } + const ElementIdentifier first_shape_id = + m_adapter2->sheet_first_shape(m_identifier); + return ElementRange(ElementIterator(m_adapter, first_shape_id)); } -SheetColumn::SheetColumn(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, - const std::uint32_t column) - : TypedElement(document, sheet), m_column{column} {} +TableStyle Sheet::style() const { + return exists_() ? m_adapter2->sheet_style(m_identifier) : TableStyle(); +} -TableColumnStyle SheetColumn::style() const { - return exists_() ? m_element->column_style(m_document, m_column) +TableColumnStyle Sheet::column_style(const std::uint32_t column) const { + return exists_() ? m_adapter2->sheet_column_style(m_identifier, column) : TableColumnStyle(); } -SheetRow::SheetRow(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, const std::uint32_t row) - : TypedElement(document, sheet), m_row{row} {} - -TableRowStyle SheetRow::style() const { - return exists_() ? m_element->row_style(m_document, m_row) : TableRowStyle(); +TableRowStyle Sheet::row_style(const std::uint32_t row) const { + return exists_() ? m_adapter2->sheet_row_style(m_identifier, row) + : TableRowStyle(); } -SheetCell::SheetCell(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, - const std::uint32_t column, const std::uint32_t row, - internal::abstract::SheetCell *element) - : TypedElement(document, element), m_sheet{sheet}, m_column{column}, - m_row{row} {} - -Sheet SheetCell::sheet() const { return {m_document, m_sheet}; } - -std::uint32_t SheetCell::column() const { return m_column; } +TableCellStyle Sheet::cell_style(const std::uint32_t column, + const std::uint32_t row) const { + return exists_() ? m_adapter2->sheet_cell_style(m_identifier, column, row) + : TableCellStyle(); +} -std::uint32_t SheetCell::row() const { return m_row; } +TablePosition SheetCell::position() const { + return exists_() ? m_adapter2->sheet_cell_position(m_identifier) + : TablePosition(0, 0); +} bool SheetCell::is_covered() const { - return exists_() ? m_element->is_covered(m_document) : false; + return exists_() && m_adapter2->sheet_cell_is_covered(m_identifier); } TableDimensions SheetCell::span() const { - return exists_() ? m_element->span(m_document) : TableDimensions(1, 1); + return exists_() ? m_adapter2->sheet_cell_span(m_identifier) + : TableDimensions(1, 1); } ValueType SheetCell::value_type() const { - return exists_() ? m_element->value_type(m_document) : ValueType::unknown; -} - -TableCellStyle SheetCell::style() const { - return m_sheet != nullptr ? m_sheet->cell_style(m_document, m_column, m_row) - : TableCellStyle(); + return exists_() ? m_adapter2->sheet_cell_value_type(m_identifier) + : ValueType::unknown; } std::string Page::name() const { - return exists_() ? m_element->name(m_document) : ""; + return exists_() ? m_adapter2->page_name(m_identifier) : ""; } PageLayout Page::page_layout() const { - return exists_() ? m_element->page_layout(m_document) : PageLayout(); + return exists_() ? m_adapter2->page_layout(m_identifier) : PageLayout(); } MasterPage Page::master_page() const { - return exists_() ? MasterPage(m_document, m_element->master_page(m_document)) - : MasterPage(); + if (!exists_()) { + return {}; + } + const ElementIdentifier master_page_id = + m_adapter2->page_master_page(m_identifier); + return {m_adapter, master_page_id, + m_adapter->master_page_adapter(master_page_id)}; } PageLayout MasterPage::page_layout() const { - return exists_() ? m_element->page_layout(m_document) : PageLayout(); + return exists_() ? m_adapter2->master_page_page_layout(m_identifier) + : PageLayout(); } TextStyle LineBreak::style() const { - return exists_() ? m_element->style(m_document) : TextStyle(); + return exists_() ? m_adapter2->line_break_style(m_identifier) : TextStyle(); } ParagraphStyle Paragraph::style() const { - return exists_() ? m_element->style(m_document) : ParagraphStyle(); + return exists_() ? m_adapter2->paragraph_style(m_identifier) + : ParagraphStyle(); } TextStyle Paragraph::text_style() const { - return exists_() ? m_element->text_style(m_document) : TextStyle(); + return exists_() ? m_adapter2->paragraph_text_style(m_identifier) + : TextStyle(); } TextStyle Span::style() const { - return exists_() ? m_element->style(m_document) : TextStyle(); + return exists_() ? m_adapter2->span_style(m_identifier) : TextStyle(); } std::string Text::content() const { - return exists_() ? m_element->content(m_document) : ""; + return exists_() ? m_adapter2->text_content(m_identifier) : ""; } void Text::set_content(const std::string &text) const { - if (exists_()) { - m_element->set_content(m_document, text); + if (!exists_()) { + return; } + m_adapter2->text_set_content(m_identifier, text); } TextStyle Text::style() const { - return exists_() ? m_element->style(m_document) : TextStyle(); + return exists_() ? m_adapter2->text_style(m_identifier) : TextStyle(); } std::string Link::href() const { - return exists_() ? m_element->href(m_document) : ""; + return exists_() ? m_adapter2->link_href(m_identifier) : ""; } std::string Bookmark::name() const { - return exists_() ? m_element->name(m_document) : ""; + return exists_() ? m_adapter2->bookmark_name(m_identifier) : ""; } TextStyle ListItem::style() const { - return exists_() ? m_element->style(m_document) : TextStyle(); + return exists_() ? m_adapter2->list_item_style(m_identifier) : TextStyle(); } TableRow Table::first_row() const { - return exists_() ? TableRow(m_document, m_element->first_row(m_document)) - : TableRow(); + if (!exists_()) { + return {}; + } + const ElementIdentifier row_id = m_adapter2->table_first_row(m_identifier); + return {m_adapter, row_id, m_adapter->table_row_adapter(row_id)}; } TableColumn Table::first_column() const { - return exists_() - ? TableColumn(m_document, m_element->first_column(m_document)) - : TableColumn(); + if (!exists_()) { + return {}; + } + const ElementIdentifier column_id = + m_adapter2->table_first_column(m_identifier); + return {m_adapter, column_id, m_adapter->table_column_adapter(column_id)}; } ElementRange Table::columns() const { - return exists_() ? ElementRange(ElementIterator( - m_document, m_element->first_column(m_document))) - : ElementRange(); + return exists_() + ? ElementRange(ElementIterator( + m_adapter, m_adapter2->table_first_column(m_identifier))) + : ElementRange(); } ElementRange Table::rows() const { return exists_() ? ElementRange(ElementIterator( - m_document, m_element->first_row(m_document))) + m_adapter, m_adapter2->table_first_row(m_identifier))) : ElementRange(); } TableDimensions Table::dimensions() const { - return exists_() ? m_element->dimensions(m_document) : TableDimensions(); + return exists_() ? m_adapter2->table_dimensions(m_identifier) + : TableDimensions(); } TableStyle Table::style() const { - return exists_() ? m_element->style(m_document) : TableStyle(); + return exists_() ? m_adapter2->table_style(m_identifier) : TableStyle(); } TableColumnStyle TableColumn::style() const { - return exists_() ? m_element->style(m_document) : TableColumnStyle(); + return exists_() ? m_adapter2->table_column_style(m_identifier) + : TableColumnStyle(); } TableRowStyle TableRow::style() const { - return exists_() ? m_element->style(m_document) : TableRowStyle(); + return exists_() ? m_adapter2->table_row_style(m_identifier) + : TableRowStyle(); } bool TableCell::is_covered() const { - return exists_() && m_element->is_covered(m_document); + return exists_() && m_adapter2->table_cell_is_covered(m_identifier); } TableDimensions TableCell::span() const { - return exists_() ? m_element->span(m_document) : TableDimensions(); + return exists_() ? m_adapter2->table_cell_span(m_identifier) + : TableDimensions(); } ValueType TableCell::value_type() const { - return exists_() ? m_element->value_type(m_document) : ValueType::string; + return exists_() ? m_adapter2->table_cell_value_type(m_identifier) + : ValueType::string; } TableCellStyle TableCell::style() const { - return exists_() ? m_element->style(m_document) : TableCellStyle(); + return exists_() ? m_adapter2->table_cell_style(m_identifier) + : TableCellStyle(); } AnchorType Frame::anchor_type() const { - return exists_() ? m_element->anchor_type(m_document) + return exists_() ? m_adapter2->frame_anchor_type(m_identifier) : AnchorType::as_char; // TODO default? } std::optional Frame::x() const { - return exists_() ? m_element->x(m_document) : std::optional(); + return exists_() ? m_adapter2->frame_x(m_identifier) + : std::optional(); } std::optional Frame::y() const { - return exists_() ? m_element->y(m_document) : std::optional(); + return exists_() ? m_adapter2->frame_y(m_identifier) + : std::optional(); } std::optional Frame::width() const { - return exists_() ? m_element->width(m_document) + return exists_() ? m_adapter2->frame_width(m_identifier) : std::optional(); } std::optional Frame::height() const { - return exists_() ? m_element->height(m_document) + return exists_() ? m_adapter2->frame_height(m_identifier) : std::optional(); } std::optional Frame::z_index() const { - return exists_() ? m_element->z_index(m_document) + return exists_() ? m_adapter2->frame_z_index(m_identifier) : std::optional(); } GraphicStyle Frame::style() const { - return exists_() ? m_element->style(m_document) : GraphicStyle(); + return exists_() ? m_adapter2->frame_style(m_identifier) : GraphicStyle(); } std::string Rect::x() const { - return exists_() ? m_element->x(m_document) : ""; + return exists_() ? m_adapter2->rect_x(m_identifier) : ""; } std::string Rect::y() const { - return exists_() ? m_element->y(m_document) : ""; + return exists_() ? m_adapter2->rect_y(m_identifier) : ""; } std::string Rect::width() const { - return exists_() ? m_element->width(m_document) : ""; + return exists_() ? m_adapter2->rect_width(m_identifier) : ""; } std::string Rect::height() const { - return exists_() ? m_element->height(m_document) : ""; + return exists_() ? m_adapter2->rect_height(m_identifier) : ""; } GraphicStyle Rect::style() const { - return exists_() ? m_element->style(m_document) : GraphicStyle(); + return exists_() ? m_adapter2->rect_style(m_identifier) : GraphicStyle(); } std::string Line::x1() const { - return exists_() ? m_element->x1(m_document) : ""; + return exists_() ? m_adapter2->line_x1(m_identifier) : ""; } std::string Line::y1() const { - return exists_() ? m_element->y1(m_document) : ""; + return exists_() ? m_adapter2->line_y1(m_identifier) : ""; } std::string Line::x2() const { - return exists_() ? m_element->x2(m_document) : ""; + return exists_() ? m_adapter2->line_x2(m_identifier) : ""; } std::string Line::y2() const { - return exists_() ? m_element->y2(m_document) : ""; + return exists_() ? m_adapter2->line_y2(m_identifier) : ""; } GraphicStyle Line::style() const { - return exists_() ? m_element->style(m_document) : GraphicStyle(); + return exists_() ? m_adapter2->line_style(m_identifier) : GraphicStyle(); } std::string Circle::x() const { - return exists_() ? m_element->x(m_document) : ""; + return exists_() ? m_adapter2->circle_x(m_identifier) : ""; } std::string Circle::y() const { - return exists_() ? m_element->y(m_document) : ""; + return exists_() ? m_adapter2->circle_y(m_identifier) : ""; } std::string Circle::width() const { - return exists_() ? m_element->width(m_document) : ""; + return exists_() ? m_adapter2->circle_width(m_identifier) : ""; } std::string Circle::height() const { - return exists_() ? m_element->height(m_document) : ""; + return exists_() ? m_adapter2->circle_height(m_identifier) : ""; } GraphicStyle Circle::style() const { - return exists_() ? m_element->style(m_document) : GraphicStyle(); + return exists_() ? m_adapter2->circle_style(m_identifier) : GraphicStyle(); } std::optional CustomShape::x() const { - return exists_() ? m_element->x(m_document) : ""; + return exists_() ? m_adapter2->custom_shape_x(m_identifier) : ""; } std::optional CustomShape::y() const { - return exists_() ? m_element->y(m_document) : ""; + return exists_() ? m_adapter2->custom_shape_y(m_identifier) : ""; } std::string CustomShape::width() const { - return exists_() ? m_element->width(m_document) : ""; + return exists_() ? m_adapter2->custom_shape_width(m_identifier) : ""; } std::string CustomShape::height() const { - return exists_() ? m_element->height(m_document) : ""; + return exists_() ? m_adapter2->custom_shape_height(m_identifier) : ""; } GraphicStyle CustomShape::style() const { - return exists_() ? m_element->style(m_document) : GraphicStyle(); + return exists_() ? m_adapter2->custom_shape_style(m_identifier) + : GraphicStyle(); } bool Image::is_internal() const { - return exists_() && m_element->is_internal(m_document); + return exists_() && m_adapter2->image_is_internal(m_identifier); } std::optional Image::file() const { - return exists_() ? m_element->file(m_document) : std::optional(); + return exists_() ? m_adapter2->image_file(m_identifier) + : std::optional(); } std::string Image::href() const { - return exists_() ? m_element->href(m_document) : ""; + return exists_() ? m_adapter2->image_href(m_identifier) : ""; } } // namespace odr diff --git a/src/odr/document_element.hpp b/src/odr/document_element.hpp index e555baec..e5a915eb 100644 --- a/src/odr/document_element.hpp +++ b/src/odr/document_element.hpp @@ -4,37 +4,11 @@ #include #include -namespace odr::internal::abstract { -class Document; - -class Element; -class TextRoot; -class Slide; -class Sheet; -class SheetCell; -class Page; -class MasterPage; -class LineBreak; -class Paragraph; -class Span; -class Text; -class Link; -class Bookmark; -class ListItem; -class Table; -class TableColumn; -class TableRow; -class TableCell; -class Frame; -class Rect; -class Line; -class Circle; -class CustomShape; -class Image; -} // namespace odr::internal::abstract +#include namespace odr { - +class TablePosition; +struct TableDimensions; class File; struct TextStyle; struct ParagraphStyle; @@ -44,7 +18,40 @@ struct TableRowStyle; struct TableCellStyle; struct GraphicStyle; struct PageLayout; -struct TableDimensions; +} // namespace odr + +namespace odr::internal::abstract { +class Document; + +class ElementAdapter; +class TextRootAdapter; +class SlideAdapter; +class PageAdapter; +class SheetAdapter; +class SheetColumnAdapter; +class SheetRowAdapter; +class SheetCellAdapter; +class MasterPageAdapter; +class LineBreakAdapter; +class ParagraphAdapter; +class SpanAdapter; +class TextAdapter; +class LinkAdapter; +class BookmarkAdapter; +class ListItemAdapter; +class TableAdapter; +class TableColumnAdapter; +class TableRowAdapter; +class TableCellAdapter; +class FrameAdapter; +class RectAdapter; +class LineAdapter; +class CircleAdapter; +class CustomShapeAdapter; +class ImageAdapter; +} // namespace odr::internal::abstract + +namespace odr { class Element; class ElementIterator; @@ -86,8 +93,6 @@ enum class ElementType { master_page, - sheet_column, - sheet_row, sheet_cell, text, @@ -136,10 +141,8 @@ enum class ValueType { class Element { public: Element(); - Element(const internal::abstract::Document *, internal::abstract::Element *); - - bool operator==(const Element &rhs) const; - bool operator!=(const Element &rhs) const; + Element(const internal::abstract::ElementAdapter *adapter, + ElementIdentifier identifier); explicit operator bool() const; @@ -158,6 +161,7 @@ class Element { [[nodiscard]] Slide as_slide() const; [[nodiscard]] Sheet as_sheet() const; [[nodiscard]] Page as_page() const; + [[nodiscard]] SheetCell as_sheet_cell() const; [[nodiscard]] MasterPage as_master_page() const; [[nodiscard]] LineBreak as_line_break() const; [[nodiscard]] Paragraph as_paragraph() const; @@ -178,8 +182,12 @@ class Element { [[nodiscard]] Image as_image() const; protected: - const internal::abstract::Document *m_document{nullptr}; - internal::abstract::Element *m_element{nullptr}; + const internal::abstract::ElementAdapter *m_adapter{nullptr}; + ElementIdentifier m_identifier; + + friend bool operator==(const Element &lhs, const Element &rhs) { + return lhs.m_identifier == rhs.m_identifier; + } [[nodiscard]] bool exists_() const; }; @@ -194,11 +202,8 @@ class ElementIterator { using iterator_category = std::forward_iterator_tag; ElementIterator(); - ElementIterator(const internal::abstract::Document *, - internal::abstract::Element *); - - bool operator==(const ElementIterator &rhs) const; - bool operator!=(const ElementIterator &rhs) const; + ElementIterator(const internal::abstract::ElementAdapter *adapter, + ElementIdentifier identifier); reference operator*() const; @@ -206,8 +211,13 @@ class ElementIterator { ElementIterator operator++(int); private: - const internal::abstract::Document *m_document{nullptr}; - internal::abstract::Element *m_element{nullptr}; + const internal::abstract::ElementAdapter *m_adapter{nullptr}; + ElementIdentifier m_identifier{null_element_id}; + + friend bool operator==(const ElementIterator &lhs, + const ElementIterator &rhs) { + return lhs.m_identifier == rhs.m_identifier; + } [[nodiscard]] bool exists_() const; }; @@ -216,8 +226,8 @@ class ElementIterator { class ElementRange { public: ElementRange(); - explicit ElementRange(ElementIterator begin); - ElementRange(ElementIterator begin, ElementIterator end); + explicit ElementRange(const ElementIterator &begin); + ElementRange(const ElementIterator &begin, const ElementIterator &end); [[nodiscard]] ElementIterator begin() const; [[nodiscard]] ElementIterator end() const; @@ -228,26 +238,27 @@ class ElementRange { }; /// @brief Represents a typed element in a document. -template class TypedElement : public Element { +template class ElementBase : public Element { public: - TypedElement() = default; - TypedElement(const internal::abstract::Document *document, T *element) - : Element(document, element), m_element{element} {} - TypedElement(const internal::abstract::Document *document, - internal::abstract::Element *element) - : Element(document, dynamic_cast(element)), - m_element{dynamic_cast(element)} {} + ElementBase() = default; + ElementBase(const internal::abstract::ElementAdapter *adapter, + const ElementIdentifier identifier, const T *adapter2) + : Element(adapter, identifier), m_adapter2{adapter2} {} + + explicit operator bool() const { return exists_(); } protected: - T *m_element; + const T *m_adapter2{nullptr}; - [[nodiscard]] bool exists_() const { return m_element != nullptr; } + [[nodiscard]] bool exists_() const { + return Element::exists_() && m_adapter2 != nullptr; + } }; /// @brief Represents a root element in a document. -class TextRoot final : public TypedElement { +class TextRoot final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] PageLayout page_layout() const; @@ -255,9 +266,9 @@ class TextRoot final : public TypedElement { }; /// @brief Represents a slide element in a document. -class Slide final : public TypedElement { +class Slide final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string name() const; @@ -267,9 +278,9 @@ class Slide final : public TypedElement { }; /// @brief Represents a sheet element in a document. -class Sheet final : public TypedElement { +class Sheet final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string name() const; @@ -277,66 +288,32 @@ class Sheet final : public TypedElement { [[nodiscard]] TableDimensions content(std::optional range) const; - [[nodiscard]] SheetColumn column(std::uint32_t column) const; - [[nodiscard]] SheetRow row(std::uint32_t row) const; [[nodiscard]] SheetCell cell(std::uint32_t column, std::uint32_t row) const; - [[nodiscard]] ElementRange shapes() const; -}; - -/// @brief Represents a sheet column element in a document. -class SheetColumn final : public TypedElement { -public: - SheetColumn() = default; - SheetColumn(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, std::uint32_t column); - [[nodiscard]] TableColumnStyle style() const; - -private: - std::uint32_t m_column{}; -}; - -/// @brief Represents a sheet row element in a document. -class SheetRow final : public TypedElement { -public: - SheetRow() = default; - SheetRow(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, std::uint32_t row); - - [[nodiscard]] TableRowStyle style() const; - -private: - std::uint32_t m_row{}; + [[nodiscard]] TableStyle style() const; + [[nodiscard]] TableColumnStyle column_style(std::uint32_t column) const; + [[nodiscard]] TableRowStyle row_style(std::uint32_t row) const; + [[nodiscard]] TableCellStyle cell_style(std::uint32_t column, + std::uint32_t row) const; }; /// @brief Represents a sheet cell element in a document. -class SheetCell final : public TypedElement { +class SheetCell final + : public ElementBase { public: - SheetCell() = default; - SheetCell(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, std::uint32_t column, - std::uint32_t row, internal::abstract::SheetCell *element); - - [[nodiscard]] Sheet sheet() const; - [[nodiscard]] std::uint32_t column() const; - [[nodiscard]] std::uint32_t row() const; + using ElementBase::ElementBase; + + [[nodiscard]] TablePosition position() const; [[nodiscard]] bool is_covered() const; [[nodiscard]] TableDimensions span() const; [[nodiscard]] ValueType value_type() const; - - [[nodiscard]] TableCellStyle style() const; - -private: - internal::abstract::Sheet *m_sheet{}; - std::uint32_t m_column{}; - std::uint32_t m_row{}; }; /// @brief Represents a page element in a document. -class Page final : public TypedElement { +class Page final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string name() const; @@ -346,42 +323,45 @@ class Page final : public TypedElement { }; /// @brief Represents a master page element in a document. -class MasterPage final : public TypedElement { +class MasterPage final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] PageLayout page_layout() const; }; /// @brief Represents a line break element in a document. -class LineBreak final : public TypedElement { +class LineBreak final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TextStyle style() const; }; /// @brief Represents a paragraph element in a document. -class Paragraph final : public TypedElement { +class Paragraph final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] ParagraphStyle style() const; [[nodiscard]] TextStyle text_style() const; }; /// @brief Represents a span element in a document. -class Span final : public TypedElement { +class Span final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TextStyle style() const; }; /// @brief Represents a text element in a document. -class Text final : public TypedElement { +class Text final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string content() const; void set_content(const std::string &text) const; @@ -390,33 +370,33 @@ class Text final : public TypedElement { }; /// @brief Represents a link element in a document. -class Link final : public TypedElement { +class Link final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string href() const; }; /// @brief Represents a bookmark element in a document. -class Bookmark final : public TypedElement { +class Bookmark final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string name() const; }; /// @brief Represents a list item element in a document. -class ListItem final : public TypedElement { +class ListItem final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TextStyle style() const; }; /// @brief Represents a table element in a document. -class Table final : public TypedElement { +class Table final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TableRow first_row() const; [[nodiscard]] TableColumn first_column() const; @@ -430,25 +410,27 @@ class Table final : public TypedElement { }; /// @brief Represents a table column element in a document. -class TableColumn final : public TypedElement { +class TableColumn final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TableColumnStyle style() const; }; /// @brief Represents a table row element in a document. -class TableRow final : public TypedElement { +class TableRow final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] TableRowStyle style() const; }; /// @brief Represents a table cell element in a document. -class TableCell final : public TypedElement { +class TableCell final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] bool is_covered() const; [[nodiscard]] TableDimensions span() const; @@ -458,9 +440,9 @@ class TableCell final : public TypedElement { }; /// @brief Represents a frame element in a document. -class Frame final : public TypedElement { +class Frame final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] AnchorType anchor_type() const; [[nodiscard]] std::optional x() const; @@ -473,9 +455,9 @@ class Frame final : public TypedElement { }; /// @brief Represents a rectangle element in a document. -class Rect final : public TypedElement { +class Rect final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string x() const; [[nodiscard]] std::string y() const; @@ -486,9 +468,9 @@ class Rect final : public TypedElement { }; /// @brief Represents a line element in a document. -class Line final : public TypedElement { +class Line final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string x1() const; [[nodiscard]] std::string y1() const; @@ -499,9 +481,9 @@ class Line final : public TypedElement { }; /// @brief Represents a circle element in a document. -class Circle final : public TypedElement { +class Circle final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::string x() const; [[nodiscard]] std::string y() const; @@ -512,9 +494,10 @@ class Circle final : public TypedElement { }; /// @brief Represents a custom shape element in a document. -class CustomShape final : public TypedElement { +class CustomShape final + : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] std::optional x() const; [[nodiscard]] std::optional y() const; @@ -525,9 +508,9 @@ class CustomShape final : public TypedElement { }; /// @brief Represents an image element in a document. -class Image final : public TypedElement { +class Image final : public ElementBase { public: - using TypedElement::TypedElement; + using ElementBase::ElementBase; [[nodiscard]] bool is_internal() const; [[nodiscard]] std::optional file() const; diff --git a/src/odr/document_path.cpp b/src/odr/document_path.cpp index 9ef2ffce..7f109f4f 100644 --- a/src/odr/document_path.cpp +++ b/src/odr/document_path.cpp @@ -7,45 +7,100 @@ namespace odr { -template -const std::string &DocumentPath::ComponentTemplate::prefix_string() { - static std::string result(Derived::prefix); +const std::string &DocumentPath::Child::prefix_string() { + static std::string result(prefix); return result; } -template -DocumentPath::ComponentTemplate::ComponentTemplate( - const std::uint32_t number) - : number{number} {} +std::pair +DocumentPath::Child::extract(const Element element) { + if (!element) { + throw std::invalid_argument("element is null"); + } + const Element parent = element.parent(); + if (!parent) { + throw std::invalid_argument("parent not found"); + } + + Element current = element; + std::uint32_t distance = 0; + for (; current.previous_sibling(); current = current.previous_sibling()) { + ++distance; + } + + return {parent, Child(distance)}; +} + +Element DocumentPath::Child::resolve(const Element element, + const Child &child) { + if (!element) { + throw std::invalid_argument("element is null"); + } + if (!element.first_child()) { + throw std::invalid_argument("child not found"); + } + Element result = element.first_child(); + for (std::uint32_t i = 0; i < child.m_number; ++i) { + if (!result.next_sibling()) { + throw std::invalid_argument("sibling not found"); + } + result = result.next_sibling(); + } + return result; +} + +DocumentPath::Child::Child(const std::uint32_t number) : m_number{number} {} + +bool DocumentPath::Child::operator==(const Child &other) const noexcept { + return m_number == other.m_number; +} -template -bool DocumentPath::ComponentTemplate::operator==( - const ComponentTemplate &other) const noexcept { - return number == other.number; +[[nodiscard]] std::string DocumentPath::Child::to_string() const noexcept { + return prefix_string() + ":" + std::to_string(m_number); } -template -bool DocumentPath::ComponentTemplate::operator!=( - const ComponentTemplate &other) const noexcept { - return number != other.number; +const std::string &DocumentPath::Cell::prefix_string() { + static std::string result(prefix); + return result; +} + +std::pair +DocumentPath::Cell::extract(const SheetCell &element) { + if (!element) { + throw std::invalid_argument("element is null"); + } + const Element parent = element.parent(); + if (!parent) { + throw std::invalid_argument("parent not found"); + } + const Sheet sheet = parent.as_sheet(); + if (!sheet) { + throw std::invalid_argument("parent is not a sheet"); + } + const SheetCell cell = element.as_sheet_cell(); + if (!cell) { + throw std::invalid_argument("element is not a sheet cell"); + } + return {sheet, Cell(cell.position())}; } -template -Derived &DocumentPath::ComponentTemplate::operator++() { - ++number; - return *this; +SheetCell DocumentPath::Cell::resolve(const Sheet &element, const Cell &cell) { + if (!element) { + throw std::invalid_argument("element is null"); + } + return element.as_sheet().cell(cell.m_position.column(), + cell.m_position.row()); } -template -Derived &DocumentPath::ComponentTemplate::operator--() { - --number; - return *this; +DocumentPath::Cell::Cell(const TablePosition &position) + : m_position{position} {} + +bool DocumentPath::Cell::operator==(const Cell &other) const noexcept { + return m_position == other.m_position; } -template -std::string -DocumentPath::ComponentTemplate::to_string() const noexcept { - return prefix_string() + ":" + std::to_string(number); +[[nodiscard]] std::string DocumentPath::Cell::to_string() const noexcept { + return prefix_string() + ":" + m_position.to_string(); } DocumentPath::Component @@ -56,16 +111,14 @@ DocumentPath::component_from_string(const std::string &string) { } const std::string prefix = string.substr(0, colon); - const std::uint32_t number = std::stoul(string.substr(colon + 1)); if (prefix == Child::prefix_string()) { + const std::uint32_t number = std::stoul(string.substr(colon + 1)); return Child(number); } - if (prefix == Column::prefix_string()) { - return Column(number); - } - if (prefix == Row::prefix_string()) { - return Row(number); + if (prefix == Cell::prefix_string()) { + const TablePosition position(string.substr(colon + 1)); + return Cell(position); } throw std::invalid_argument("string"); @@ -78,63 +131,44 @@ DocumentPath DocumentPath::extract(const Element element) { DocumentPath DocumentPath::extract(const Element element, const Element root) { std::vector reverse; - for (auto current = element; current != root;) { - const auto parent = current.parent(); - if (!parent) { + for (Element current = element; current != root;) { + if (!current.parent()) { break; } - std::uint32_t distance = 0; - for (; current.previous_sibling(); current = current.previous_sibling()) { - ++distance; - } - - if (current.as_table_column() || current.as_table_cell()) { - reverse.emplace_back(Column(distance)); - } else if (current.as_table_row()) { - reverse.emplace_back(Row(distance)); + if (const SheetCell sheet_cell = current.as_sheet_cell(); sheet_cell) { + const auto [parent, cell] = Cell::extract(sheet_cell); + reverse.emplace_back(cell); + current = static_cast(parent); } else { - reverse.emplace_back(Child(distance)); + const auto [parent, child] = Child::extract(current); + reverse.emplace_back(child); + current = parent; } - - current = parent; } std::ranges::reverse(reverse); return DocumentPath(reverse); } -Element DocumentPath::find(const Element root, const DocumentPath &path) { +Element DocumentPath::resolve(const Element root, const DocumentPath &path) { Element element = root; for (const Component &c : path) { - std::uint32_t number = 0; - if (const auto child = std::get_if(&c)) { - if (!element.first_child()) { - throw std::invalid_argument("child not found"); - } - element = element.first_child(); - number = child->number; - } else if (const auto column = std::get_if(&c)) { - if (!element.as_table_row()) { - throw std::invalid_argument("column not found"); + if (const auto *child = std::get_if(&c); child != nullptr) { + element = Child::resolve(element, *child); + } else if (const auto *cell = std::get_if(&c); cell != nullptr) { + const Sheet sheet = element.as_sheet(); + if (!sheet) { + throw std::invalid_argument("element is not a sheet"); } - element = element.as_table_row().first_child(); - number = column->number; - } else if (const auto row = std::get_if(&c)) { - if (!element.as_table().first_row()) { - throw std::invalid_argument("row not found"); - } - element = Element(element.as_table().first_row()); - number = row->number; + const SheetCell sheet_cell = Cell::resolve(sheet, *cell); + element = static_cast(sheet_cell); } else { throw std::invalid_argument("unknown component"); } - for (std::uint32_t i = 0; i < number; ++i) { - if (!element.next_sibling()) { - throw std::invalid_argument("sibling not found"); - } - element = element.next_sibling(); + if (!element) { + throw std::invalid_argument("element not found"); } } diff --git a/src/odr/document_path.hpp b/src/odr/document_path.hpp index 55eaeb60..3cb33284 100644 --- a/src/odr/document_path.hpp +++ b/src/odr/document_path.hpp @@ -1,56 +1,64 @@ #pragma once +#include + #include #include +#include #include #include namespace odr { class Document; class Element; +class Sheet; +class SheetCell; /// @brief A path to a specific element in a document. class DocumentPath final { public: - template struct ComponentTemplate { + class Child final { + public: + static constexpr std::string_view prefix = "child"; + static const std::string &prefix_string(); + static std::pair extract(Element element); + static Element resolve(Element element, const Child &child); - std::uint32_t number{0}; + explicit Child(std::uint32_t number); - explicit ComponentTemplate(std::uint32_t number); + bool operator==(const Child &other) const noexcept; + [[nodiscard]] std::string to_string() const noexcept; - bool operator==(const ComponentTemplate &other) const noexcept; - bool operator!=(const ComponentTemplate &other) const noexcept; + private: + std::uint32_t m_number{0}; + }; - Derived &operator++(); - Derived &operator--(); + class Cell final { + public: + static constexpr std::string_view prefix = "cell"; - [[nodiscard]] std::string to_string() const noexcept; - }; + static const std::string &prefix_string(); + static std::pair extract(const SheetCell &element); + static SheetCell resolve(const Sheet &element, const Cell &cell); - struct Child final : ComponentTemplate { - static constexpr std::string_view prefix = "child"; - using ComponentTemplate::ComponentTemplate; - }; + explicit Cell(const TablePosition &position); - struct Column final : ComponentTemplate { - static constexpr std::string_view prefix = "column"; - using ComponentTemplate::ComponentTemplate; - }; + bool operator==(const Cell &other) const noexcept; + [[nodiscard]] std::string to_string() const noexcept; - struct Row final : ComponentTemplate { - static constexpr std::string_view prefix = "row"; - using ComponentTemplate::ComponentTemplate; + private: + TablePosition m_position; }; - using Component = std::variant; + using Component = std::variant; using Container = std::vector; using const_iterator = Container::const_iterator; static Component component_from_string(const std::string &string); static DocumentPath extract(Element element); static DocumentPath extract(Element element, Element root); - static Element find(Element root, const DocumentPath &path); + static Element resolve(Element root, const DocumentPath &path); DocumentPath() noexcept; explicit DocumentPath(const Container &components); diff --git a/src/odr/filesystem.cpp b/src/odr/filesystem.cpp index 46ff7ad0..37ee588d 100644 --- a/src/odr/filesystem.cpp +++ b/src/odr/filesystem.cpp @@ -7,10 +7,12 @@ namespace odr { -FileWalker::FileWalker() = default; - FileWalker::FileWalker(std::unique_ptr impl) - : m_impl{std::move(impl)} {} + : m_impl{std::move(impl)} { + if (m_impl == nullptr) { + throw std::invalid_argument("impl must not be null"); + } +} FileWalker::FileWalker(const FileWalker &other) : m_impl{other.m_impl->clone()} {} @@ -29,71 +31,48 @@ FileWalker &FileWalker::operator=(const FileWalker &other) { FileWalker &FileWalker::operator=(FileWalker &&) noexcept = default; -FileWalker::operator bool() const { return m_impl != nullptr; } +bool FileWalker::end() const { return m_impl->end(); } -bool FileWalker::end() const { return m_impl == nullptr || m_impl->end(); } +std::uint32_t FileWalker::depth() const { return m_impl->depth(); } -std::uint32_t FileWalker::depth() const { - return m_impl != nullptr ? m_impl->depth() : 0; -} +std::string FileWalker::path() const { return m_impl->path().string(); } -std::string FileWalker::path() const { - return m_impl != nullptr ? m_impl->path().string() : std::string(""); -} +bool FileWalker::is_file() const { return m_impl->is_file(); } -bool FileWalker::is_file() const { - return m_impl != nullptr && m_impl->is_file(); -} +bool FileWalker::is_directory() const { return m_impl->is_directory(); } -bool FileWalker::is_directory() const { - return m_impl != nullptr && m_impl->is_directory(); -} +void FileWalker::pop() const { m_impl->pop(); } -void FileWalker::pop() const { - if (m_impl != nullptr) { - m_impl->pop(); - } -} +void FileWalker::next() const { m_impl->next(); } -void FileWalker::next() const { - if (m_impl != nullptr) { - m_impl->next(); - } -} - -void FileWalker::flat_next() const { - if (m_impl != nullptr) { - m_impl->flat_next(); - } -} +void FileWalker::flat_next() const { m_impl->flat_next(); } Filesystem::Filesystem( std::shared_ptr impl) - : m_impl{std::move(impl)} {} - -Filesystem::operator bool() const { return m_impl != nullptr; } + : m_impl{std::move(impl)} { + if (m_impl == nullptr) { + throw std::invalid_argument("impl must not be null"); + } +} bool Filesystem::exists(const std::string &path) const { - return m_impl != nullptr && m_impl->exists(internal::AbsPath(path)); + return m_impl->exists(internal::AbsPath(path)); } bool Filesystem::is_file(const std::string &path) const { - return m_impl != nullptr && m_impl->is_file(internal::AbsPath(path)); + return m_impl->is_file(internal::AbsPath(path)); } bool Filesystem::is_directory(const std::string &path) const { - return m_impl != nullptr && m_impl->is_directory(internal::AbsPath(path)); + return m_impl->is_directory(internal::AbsPath(path)); } FileWalker Filesystem::file_walker(const std::string &path) const { - return m_impl != nullptr - ? FileWalker(m_impl->file_walker(internal::AbsPath(path))) - : FileWalker(); + return FileWalker(m_impl->file_walker(internal::AbsPath(path))); } File Filesystem::open(const std::string &path) const { - return m_impl != nullptr ? File(m_impl->open(internal::AbsPath(path))) - : File(); + return File(m_impl->open(internal::AbsPath(path))); } } // namespace odr diff --git a/src/odr/filesystem.hpp b/src/odr/filesystem.hpp index da5b21f5..9fcf82e3 100644 --- a/src/odr/filesystem.hpp +++ b/src/odr/filesystem.hpp @@ -14,7 +14,6 @@ class File; /// @brief FileWalker class class FileWalker { public: - FileWalker(); explicit FileWalker(std::unique_ptr); FileWalker(const FileWalker &); FileWalker(FileWalker &&) noexcept; @@ -22,7 +21,6 @@ class FileWalker { FileWalker &operator=(const FileWalker &); FileWalker &operator=(FileWalker &&) noexcept; - [[nodiscard]] explicit operator bool() const; [[nodiscard]] bool end() const; [[nodiscard]] std::uint32_t depth() const; @@ -43,8 +41,6 @@ class Filesystem { public: explicit Filesystem(std::shared_ptr); - [[nodiscard]] explicit operator bool() const; - [[nodiscard]] bool exists(const std::string &path) const; [[nodiscard]] bool is_file(const std::string &path) const; [[nodiscard]] bool is_directory(const std::string &path) const; diff --git a/src/odr/html.cpp b/src/odr/html.cpp index db68b35a..c01856d9 100644 --- a/src/odr/html.cpp +++ b/src/odr/html.cpp @@ -326,13 +326,21 @@ HtmlService html::translate(const PdfFile &pdf_file, std::move(logger)); } -HtmlService html::translate(const Archive &archive, +HtmlService html::translate(const Filesystem &filesystem, const std::string &cache_path, const HtmlConfig &config, std::shared_ptr logger) { std::filesystem::create_directories(cache_path); - return internal::html::create_filesystem_service( - archive.as_filesystem(), cache_path, config, std::move(logger)); + return internal::html::create_filesystem_service(filesystem, cache_path, + config, std::move(logger)); +} + +HtmlService html::translate(const Archive &archive, + const std::string &cache_path, + const HtmlConfig &config, + std::shared_ptr logger) { + return translate(archive.as_filesystem(), cache_path, config, + std::move(logger)); } HtmlService html::translate(const Document &document, @@ -349,7 +357,7 @@ void html::edit(const Document &document, const char *diff, for (auto json = nlohmann::json::parse(diff); const auto &[key, value] : json["modifiedText"].items()) { auto element = - DocumentPath::find(document.root_element(), DocumentPath(key)); + DocumentPath::resolve(document.root_element(), DocumentPath(key)); if (!element) { throw std::invalid_argument("element with path " + key + " not found"); } diff --git a/src/odr/html.hpp b/src/odr/html.hpp index 452c8dc9..463e437d 100644 --- a/src/odr/html.hpp +++ b/src/odr/html.hpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -19,6 +19,7 @@ class HtmlResource; namespace odr { class Archive; +class Filesystem; struct HtmlPage; class HtmlService; struct HtmlConfig; @@ -259,6 +260,16 @@ HtmlService translate(const PdfFile &pdf_file, const std::string &cache_path, const HtmlConfig &config, std::shared_ptr logger = Logger::create_null()); +/// @brief Translates a filesystem to HTML. +/// +/// @param filesystem Filesystem to translate. +/// @param cache_path Directory path for temporary output. +/// @param config Configuration for the HTML output. +/// @param logger Logger to use for logging. +/// @return HTML output. +HtmlService translate(const Filesystem &filesystem, + const std::string &cache_path, const HtmlConfig &config, + std::shared_ptr logger = Logger::create_null()); /// @brief Translates an archive to HTML. /// /// @param archive Archive to translate. diff --git a/src/odr/http_server.hpp b/src/odr/http_server.hpp index 4a3ea016..0241457c 100644 --- a/src/odr/http_server.hpp +++ b/src/odr/http_server.hpp @@ -18,6 +18,7 @@ class HttpServer { constexpr static auto prefix_pattern = R"(([a-zA-Z0-9_-]+))"; struct Config { + // TODO remove std::string cache_path{"/tmp/odr"}; }; @@ -28,6 +29,7 @@ class HttpServer { void connect_service(HtmlService service, const std::string &prefix) const; + // TODO remove [[nodiscard]] HtmlViews serve_file(const DecodedFile &file, const std::string &prefix, const HtmlConfig &config) const; diff --git a/src/odr/internal/abstract/document.hpp b/src/odr/internal/abstract/document.hpp index fb8e0e4a..9e8ea669 100644 --- a/src/odr/internal/abstract/document.hpp +++ b/src/odr/internal/abstract/document.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include namespace odr { @@ -16,7 +14,7 @@ class Path; namespace odr::internal::abstract { class ReadableFilesystem; -class Element; +class ElementAdapter; class Document { public: @@ -47,7 +45,12 @@ class Document { as_filesystem() const noexcept = 0; /// \return cursor to the root element of the document. - [[nodiscard]] virtual Element *root_element() const = 0; + [[nodiscard]] virtual ElementIdentifier root_element() const = 0; + + // TODO element adapter per id + /// \return the element adapter for this document. + [[nodiscard]] virtual const ElementAdapter *element_adapter() const = 0; + // TODO move other adapters here }; } // namespace odr::internal::abstract diff --git a/src/odr/internal/abstract/document_element.hpp b/src/odr/internal/abstract/document_element.hpp index 477ddad3..f93c821f 100644 --- a/src/odr/internal/abstract/document_element.hpp +++ b/src/odr/internal/abstract/document_element.hpp @@ -2,294 +2,418 @@ #include +#include #include #include -#ifdef _MSC_VER -#pragma warning(disable : 4250) -#endif - namespace odr { -class File; - enum class ElementType; -struct TextStyle; -struct ParagraphStyle; -struct TableStyle; -struct TableColumnStyle; -struct TableRowStyle; -struct TableCellStyle; -struct GraphicStyle; -struct PageLayout; -struct TableDimensions; +class File; } // namespace odr namespace odr::internal::abstract { -class Document; -class MasterPage; - -class Element { +class TextRootAdapter; +class SlideAdapter; +class PageAdapter; +class SheetAdapter; +class SheetColumnAdapter; +class SheetRowAdapter; +class SheetCellAdapter; +class MasterPageAdapter; +class LineBreakAdapter; +class ParagraphAdapter; +class SpanAdapter; +class TextAdapter; +class LinkAdapter; +class BookmarkAdapter; +class ListItemAdapter; +class TableAdapter; +class TableColumnAdapter; +class TableRowAdapter; +class TableCellAdapter; +class FrameAdapter; +class RectAdapter; +class LineAdapter; +class CircleAdapter; +class CustomShapeAdapter; +class ImageAdapter; + +class ElementAdapter { public: - virtual ~Element() = default; + virtual ~ElementAdapter() = default; + + [[nodiscard]] virtual ElementType + element_type(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual ElementIdentifier + element_parent(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ElementIdentifier + element_first_child(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ElementIdentifier + element_last_child(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ElementIdentifier + element_previous_sibling(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ElementIdentifier + element_next_sibling(ElementIdentifier element_id) const = 0; + + // TODO element_is_unique + // TODO element_is_self_locatable + [[nodiscard]] virtual bool + element_is_editable(ElementIdentifier element_id) const = 0; + // TODO element_path_from_parent + // TODO element_path_from_document + + // TODO push to document + [[nodiscard]] virtual const TextRootAdapter * + text_root_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SlideAdapter * + slide_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const PageAdapter * + page_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetAdapter * + sheet_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetCellAdapter * + sheet_cell_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const MasterPageAdapter * + master_page_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LineBreakAdapter * + line_break_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ParagraphAdapter * + paragraph_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SpanAdapter * + span_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TextAdapter * + text_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LinkAdapter * + link_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const BookmarkAdapter * + bookmark_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ListItemAdapter * + list_item_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableAdapter * + table_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableColumnAdapter * + table_column_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableRowAdapter * + table_row_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableCellAdapter * + table_cell_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const FrameAdapter * + frame_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const RectAdapter * + rect_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LineAdapter * + line_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const CircleAdapter * + circle_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const CustomShapeAdapter * + custom_shape_adapter(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ImageAdapter * + image_adapter(ElementIdentifier element_id) const = 0; +}; - [[nodiscard]] virtual ElementType type(const Document *) const = 0; +class TextRootAdapter { +public: + virtual ~TextRootAdapter() = default; - [[nodiscard]] virtual Element *parent(const Document *) const = 0; - [[nodiscard]] virtual Element *first_child(const Document *) const = 0; - [[nodiscard]] virtual Element *last_child(const Document *) const = 0; - [[nodiscard]] virtual Element *previous_sibling(const Document *) const = 0; - [[nodiscard]] virtual Element *next_sibling(const Document *) const = 0; + [[nodiscard]] virtual PageLayout + text_root_page_layout(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual bool is_editable(const Document *document) const = 0; + [[nodiscard]] virtual ElementIdentifier + text_root_first_master_page(ElementIdentifier element_id) const = 0; }; -class TextRoot : public virtual Element { +class SlideAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::root; - } + virtual ~SlideAdapter() = default; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; + [[nodiscard]] virtual PageLayout + slide_page_layout(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *first_master_page(const Document *) const = 0; + [[nodiscard]] virtual ElementIdentifier + slide_master_page(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual std::string + slide_name(ElementIdentifier element_id) const = 0; }; -class Slide : public virtual Element { +class PageAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::slide; - } + virtual ~PageAdapter() = default; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; + [[nodiscard]] virtual PageLayout + page_layout(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *master_page(const Document *) const = 0; + [[nodiscard]] virtual ElementIdentifier + page_master_page(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual std::string name(const Document *) const = 0; + [[nodiscard]] virtual std::string + page_name(ElementIdentifier element_id) const = 0; }; -class Page : public virtual Element { +class SheetAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::page; - } + virtual ~SheetAdapter() = default; + + [[nodiscard]] virtual std::string + sheet_name(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual TableDimensions + sheet_dimensions(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + sheet_content(ElementIdentifier element_id, + std::optional range) const = 0; + + [[nodiscard]] virtual ElementIdentifier + sheet_cell(ElementIdentifier element_id, std::uint32_t column, + std::uint32_t row) const = 0; + [[nodiscard]] virtual ElementIdentifier + sheet_first_shape(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual TableStyle + sheet_style(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableColumnStyle + sheet_column_style(ElementIdentifier element_id, + std::uint32_t column) const = 0; + [[nodiscard]] virtual TableRowStyle + sheet_row_style(ElementIdentifier element_id, std::uint32_t row) const = 0; + [[nodiscard]] virtual TableCellStyle + sheet_cell_style(ElementIdentifier element_id, std::uint32_t column, + std::uint32_t row) const = 0; +}; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; +class SheetCellAdapter { +public: + virtual ~SheetCellAdapter() = default; - [[nodiscard]] virtual Element *master_page(const Document *) const = 0; + [[nodiscard]] virtual TablePosition + sheet_cell_position(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual std::string name(const Document *) const = 0; + [[nodiscard]] virtual bool + sheet_cell_is_covered(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + sheet_cell_span(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ValueType + sheet_cell_value_type(ElementIdentifier element_id) const = 0; }; -class MasterPage : public virtual Element { +class MasterPageAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::master_page; - } + virtual ~MasterPageAdapter() = default; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; + [[nodiscard]] virtual PageLayout + master_page_page_layout(ElementIdentifier element_id) const = 0; }; -class LineBreak : public virtual Element { +class LineBreakAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::line_break; - } + virtual ~LineBreakAdapter() = default; - [[nodiscard]] virtual TextStyle style(const Document *) const = 0; + [[nodiscard]] virtual TextStyle + line_break_style(ElementIdentifier element_id) const = 0; }; -class Paragraph : public virtual Element { +class ParagraphAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::paragraph; - } + virtual ~ParagraphAdapter() = default; - [[nodiscard]] virtual ParagraphStyle style(const Document *) const = 0; - [[nodiscard]] virtual TextStyle text_style(const Document *) const = 0; + [[nodiscard]] virtual ParagraphStyle + paragraph_style(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TextStyle + paragraph_text_style(ElementIdentifier element_id) const = 0; }; -class Span : public virtual Element { +class SpanAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::span; - } + virtual ~SpanAdapter() = default; - [[nodiscard]] virtual TextStyle style(const Document *document) const = 0; + [[nodiscard]] virtual TextStyle + span_style(ElementIdentifier element_id) const = 0; }; -class Text : public virtual Element { +class TextAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::text; - } + virtual ~TextAdapter() = default; - [[nodiscard]] virtual std::string content(const Document *) const = 0; - virtual void set_content(const Document *, const std::string &text) = 0; + [[nodiscard]] virtual std::string + text_content(ElementIdentifier element_id) const = 0; + virtual void text_set_content(ElementIdentifier element_id, + const std::string &text) const = 0; - [[nodiscard]] virtual TextStyle style(const Document *) const = 0; + [[nodiscard]] virtual TextStyle + text_style(ElementIdentifier element_id) const = 0; }; -class Link : public virtual Element { +class LinkAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::link; - } + virtual ~LinkAdapter() = default; - [[nodiscard]] virtual std::string href(const Document *) const = 0; + [[nodiscard]] virtual std::string + link_href(ElementIdentifier element_id) const = 0; }; -class Bookmark : public virtual Element { +class BookmarkAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::bookmark; - } + virtual ~BookmarkAdapter() = default; - [[nodiscard]] virtual std::string name(const Document *) const = 0; + [[nodiscard]] virtual std::string + bookmark_name(ElementIdentifier element_id) const = 0; }; -class ListItem : public virtual Element { +class ListItemAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::list_item; - } + virtual ~ListItemAdapter() = default; - [[nodiscard]] virtual TextStyle style(const Document *) const = 0; + [[nodiscard]] virtual TextStyle + list_item_style(ElementIdentifier element_id) const = 0; }; -class Table : public virtual Element { +class TableAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::table; - } + virtual ~TableAdapter() = default; - [[nodiscard]] virtual TableDimensions dimensions(const Document *) const = 0; + [[nodiscard]] virtual TableDimensions + table_dimensions(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *first_column(const Document *) const = 0; - [[nodiscard]] virtual Element *first_row(const Document *) const = 0; + [[nodiscard]] virtual ElementIdentifier + table_first_column(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ElementIdentifier + table_first_row(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual TableStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableStyle + table_style(ElementIdentifier element_id) const = 0; }; -class TableColumn : public virtual Element { +class TableColumnAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::table_column; - } + virtual ~TableColumnAdapter() = default; - [[nodiscard]] virtual TableColumnStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableColumnStyle + table_column_style(ElementIdentifier element_id) const = 0; }; -class TableRow : public virtual Element { +class TableRowAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::table_row; - } + virtual ~TableRowAdapter() = default; - [[nodiscard]] virtual TableRowStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableRowStyle + table_row_style(ElementIdentifier element_id) const = 0; }; -class TableCell : public virtual Element { +class TableCellAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::table_cell; - } + virtual ~TableCellAdapter() = default; - [[nodiscard]] virtual bool is_covered(const Document *) const = 0; - [[nodiscard]] virtual TableDimensions span(const Document *) const = 0; - [[nodiscard]] virtual ValueType value_type(const Document *) const = 0; + [[nodiscard]] virtual bool + table_cell_is_covered(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + table_cell_span(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ValueType + table_cell_value_type(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual TableCellStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableCellStyle + table_cell_style(ElementIdentifier element_id) const = 0; }; -class Frame : public virtual Element { +class FrameAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::frame; - } + virtual ~FrameAdapter() = default; - [[nodiscard]] virtual AnchorType anchor_type(const Document *) const = 0; + [[nodiscard]] virtual AnchorType + frame_anchor_type(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - x(const Document *) const = 0; + frame_x(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - y(const Document *) const = 0; + frame_y(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - width(const Document *) const = 0; + frame_width(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - height(const Document *) const = 0; + frame_height(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - z_index(const Document *) const = 0; + frame_z_index(ElementIdentifier element_id) const = 0; - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + [[nodiscard]] virtual GraphicStyle + frame_style(ElementIdentifier element_id) const = 0; }; -class Rect : public virtual Element { +class RectAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::rect; - } - - [[nodiscard]] virtual std::string x(const Document *) const = 0; - [[nodiscard]] virtual std::string y(const Document *) const = 0; - [[nodiscard]] virtual std::string width(const Document *) const = 0; - [[nodiscard]] virtual std::string height(const Document *) const = 0; - - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + virtual ~RectAdapter() = default; + + [[nodiscard]] virtual std::string + rect_x(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_y(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_width(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_height(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + rect_style(ElementIdentifier element_id) const = 0; }; -class Line : public virtual Element { +class LineAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::line; - } - - [[nodiscard]] virtual std::string x1(const Document *) const = 0; - [[nodiscard]] virtual std::string y1(const Document *) const = 0; - [[nodiscard]] virtual std::string x2(const Document *) const = 0; - [[nodiscard]] virtual std::string y2(const Document *) const = 0; - - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + virtual ~LineAdapter() = default; + + [[nodiscard]] virtual std::string + line_x1(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_y1(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_x2(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_y2(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + line_style(ElementIdentifier element_id) const = 0; }; -class Circle : public virtual Element { +class CircleAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::circle; - } - - [[nodiscard]] virtual std::string x(const Document *) const = 0; - [[nodiscard]] virtual std::string y(const Document *) const = 0; - [[nodiscard]] virtual std::string width(const Document *) const = 0; - [[nodiscard]] virtual std::string height(const Document *) const = 0; - - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + virtual ~CircleAdapter() = default; + + [[nodiscard]] virtual std::string + circle_x(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_y(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_width(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_height(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + circle_style(ElementIdentifier element_id) const = 0; }; -class CustomShape : public virtual Element { +class CustomShapeAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::custom_shape; - } + virtual ~CustomShapeAdapter() = default; [[nodiscard]] virtual std::optional - x(const Document *) const = 0; + custom_shape_x(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - y(const Document *) const = 0; - [[nodiscard]] virtual std::string width(const Document *) const = 0; - [[nodiscard]] virtual std::string height(const Document *) const = 0; - - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + custom_shape_y(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + custom_shape_width(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + custom_shape_height(ElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + custom_shape_style(ElementIdentifier element_id) const = 0; }; -class Image : public virtual Element { +class ImageAdapter { public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::image; - } + virtual ~ImageAdapter() = default; - [[nodiscard]] virtual bool is_internal(const Document *) const = 0; + [[nodiscard]] virtual bool + image_is_internal(ElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - file(const Document *) const = 0; - [[nodiscard]] virtual std::string href(const Document *) const = 0; + image_file(ElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + image_href(ElementIdentifier element_id) const = 0; }; } // namespace odr::internal::abstract diff --git a/src/odr/internal/abstract/sheet_element.hpp b/src/odr/internal/abstract/sheet_element.hpp deleted file mode 100644 index 8049c945..00000000 --- a/src/odr/internal/abstract/sheet_element.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -namespace odr::internal::abstract { -class SheetCell; - -class Sheet : public virtual Element { -public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::sheet; - } - - [[nodiscard]] virtual std::string name(const Document *) const = 0; - - [[nodiscard]] virtual TableDimensions dimensions(const Document *) const = 0; - [[nodiscard]] virtual TableDimensions - content(const Document *, std::optional range) const = 0; - - [[nodiscard]] virtual SheetCell *cell(const Document *, std::uint32_t column, - std::uint32_t row) const = 0; - - [[nodiscard]] virtual Element *first_shape(const Document *) const = 0; - - [[nodiscard]] virtual TableStyle style(const Document *) const = 0; - [[nodiscard]] virtual TableColumnStyle - column_style(const Document *, std::uint32_t column) const = 0; - [[nodiscard]] virtual TableRowStyle row_style(const Document *, - std::uint32_t row) const = 0; - [[nodiscard]] virtual TableCellStyle cell_style(const Document *, - std::uint32_t column, - std::uint32_t row) const = 0; -}; - -class SheetCell : public virtual Element { -public: - [[nodiscard]] ElementType type(const Document *) const override { - return ElementType::sheet_cell; - } - - [[nodiscard]] virtual bool is_covered(const Document *) const = 0; - [[nodiscard]] virtual TableDimensions span(const Document *) const = 0; - [[nodiscard]] virtual ValueType value_type(const Document *) const = 0; - - [[nodiscard]] virtual TableCellStyle style(const Document *) const = 0; -}; - -} // namespace odr::internal::abstract diff --git a/src/odr/internal/common/document.cpp b/src/odr/internal/common/document.cpp index 8fac6c41..e10ae662 100644 --- a/src/odr/internal/common/document.cpp +++ b/src/odr/internal/common/document.cpp @@ -1,15 +1,17 @@ #include +#include +#include #include -#include - namespace odr::internal { Document::Document(const FileType file_type, const DocumentType document_type, std::shared_ptr files) : m_file_type{file_type}, m_document_type{document_type}, - m_filesystem{std::move(files)} {} + m_files{std::move(files)} {} + +Document::~Document() = default; FileType Document::file_type() const noexcept { return m_file_type; } @@ -19,7 +21,13 @@ DocumentType Document::document_type() const noexcept { std::shared_ptr Document::as_filesystem() const noexcept { - return m_filesystem; + return m_files; +} + +ElementIdentifier Document::root_element() const { return m_root_element; } + +const abstract::ElementAdapter *Document::element_adapter() const { + return m_element_adapter.get(); } } // namespace odr::internal diff --git a/src/odr/internal/common/document.hpp b/src/odr/internal/common/document.hpp index df489a80..0efdcb32 100644 --- a/src/odr/internal/common/document.hpp +++ b/src/odr/internal/common/document.hpp @@ -1,20 +1,21 @@ #pragma once +#include #include -#include +#include namespace odr::internal::abstract { class ReadableFilesystem; } // namespace odr::internal::abstract namespace odr::internal { -class Element; class Document : public abstract::Document { public: Document(FileType file_type, DocumentType document_type, std::shared_ptr files); + ~Document() override; [[nodiscard]] FileType file_type() const noexcept final; [[nodiscard]] DocumentType document_type() const noexcept final; @@ -22,32 +23,19 @@ class Document : public abstract::Document { [[nodiscard]] std::shared_ptr as_filesystem() const noexcept final; + [[nodiscard]] ElementIdentifier root_element() const override; + + [[nodiscard]] const abstract::ElementAdapter * + element_adapter() const override; + protected: FileType m_file_type; DocumentType m_document_type; - std::shared_ptr m_filesystem; + std::shared_ptr m_files; - friend class Element; -}; - -template class TemplateDocument : public Document { -public: - TemplateDocument(FileType file_type, const DocumentType document_type, - std::shared_ptr filesystem) - : Document(file_type, document_type, std::move(filesystem)) {} - - [[nodiscard]] abstract::Element *root_element() const final { - return m_root_element; - } - - void register_element_(std::unique_ptr element) { - m_elements.push_back(std::move(element)); - } - -protected: - std::vector> m_elements; - element_t *m_root_element{}; + ElementIdentifier m_root_element; + std::unique_ptr m_element_adapter; }; } // namespace odr::internal diff --git a/src/odr/internal/common/document_element.cpp b/src/odr/internal/common/document_element.cpp deleted file mode 100644 index 12f18c67..00000000 --- a/src/odr/internal/common/document_element.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include - -namespace odr::internal { - -abstract::Element *Element::parent(const abstract::Document *) const { - return m_parent; -} - -abstract::Element *Element::first_child(const abstract::Document *) const { - return m_first_child; -} - -abstract::Element *Element::last_child(const abstract::Document *) const { - return m_last_child; -} - -abstract::Element *Element::previous_sibling(const abstract::Document *) const { - return m_previous_sibling; -} - -abstract::Element *Element::next_sibling(const abstract::Document *) const { - return m_next_sibling; -} - -void Element::append_child_(Element *element) { - element->m_previous_sibling = m_last_child; - element->m_parent = this; - if (m_last_child == nullptr) { - m_first_child = element; - } else { - m_last_child->m_next_sibling = element; - } - m_last_child = element; -} - -abstract::Element *Table::first_column(const abstract::Document *) const { - return m_first_column; -} - -abstract::Element *Table::first_row(const abstract::Document *) const { - return m_first_child; -} - -void Table::append_column_(Element *element) { - element->m_previous_sibling = m_last_column; - element->m_parent = this; - if (m_last_column == nullptr) { - m_first_column = element; - } else { - m_last_column->m_next_sibling = element; - } - m_last_column = element; -} - -void Table::append_row_(Element *element) { append_child_(element); } - -} // namespace odr::internal diff --git a/src/odr/internal/common/document_element.hpp b/src/odr/internal/common/document_element.hpp deleted file mode 100644 index 2734c330..00000000 --- a/src/odr/internal/common/document_element.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include - -namespace odr::internal::abstract { -class Document; -} // namespace odr::internal::abstract - -namespace odr::internal { - -class Element : public virtual abstract::Element { -public: - [[nodiscard]] abstract::Element * - parent(const abstract::Document *) const override; - [[nodiscard]] abstract::Element * - first_child(const abstract::Document *) const override; - [[nodiscard]] abstract::Element * - last_child(const abstract::Document *) const override; - [[nodiscard]] abstract::Element * - previous_sibling(const abstract::Document *) const override; - [[nodiscard]] abstract::Element * - next_sibling(const abstract::Document *) const override; - - void append_child_(Element *element); - - template - static const document_t &document_(const abstract::Document *document) { - const auto *cast = dynamic_cast(document); - if (cast == nullptr) { - throw std::runtime_error("unknown document type"); - } - return *cast; - } - - Element *m_parent{}; - Element *m_first_child{}; - Element *m_last_child{}; - Element *m_previous_sibling{}; - Element *m_next_sibling{}; -}; - -class Table : public virtual Element, public abstract::Table { -public: - [[nodiscard]] abstract::Element * - first_column(const abstract::Document *) const final; - [[nodiscard]] abstract::Element * - first_row(const abstract::Document *) const final; - - void append_column_(Element *element); - void append_row_(Element *element); - - Element *m_first_column{}; - Element *m_last_column{}; -}; - -} // namespace odr::internal diff --git a/src/odr/internal/common/path.hpp b/src/odr/internal/common/path.hpp index d86bb4e3..c04c1db6 100644 --- a/src/odr/internal/common/path.hpp +++ b/src/odr/internal/common/path.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/src/odr/internal/common/table_cursor.cpp b/src/odr/internal/common/table_cursor.cpp index 61c41a33..87e855cf 100644 --- a/src/odr/internal/common/table_cursor.cpp +++ b/src/odr/internal/common/table_cursor.cpp @@ -7,12 +7,12 @@ namespace odr::internal { TableCursor::TableCursor() noexcept { m_sparse.emplace_back(); } void TableCursor::add_column(const uint32_t repeat) noexcept { - m_col += repeat; + m_column += repeat; } void TableCursor::add_row(const std::uint32_t repeat) noexcept { m_row += repeat; - m_col = 0; + m_column = 0; if (repeat > 1) { // TODO assert trivial m_sparse.clear(); @@ -28,7 +28,7 @@ void TableCursor::add_row(const std::uint32_t repeat) noexcept { void TableCursor::add_cell(const std::uint32_t colspan, const std::uint32_t rowspan, const std::uint32_t repeat) noexcept { - const auto new_next_cols = m_col + colspan * repeat; + const std::uint32_t next_column = m_column + colspan * repeat; // handle rowspan auto it = std::begin(m_sparse); @@ -37,24 +37,26 @@ void TableCursor::add_cell(const std::uint32_t colspan, m_sparse.emplace_back(); } ++it; - it->emplace_back(Range{m_col, new_next_cols}); + it->emplace_back(Range{m_column, next_column}); } - m_col = new_next_cols; + m_column = next_column; handle_rowspan_(); } -TablePosition TableCursor::position() const noexcept { return {m_col, m_row}; } +TablePosition TableCursor::position() const noexcept { + return {m_column, m_row}; +} -std::uint32_t TableCursor::column() const noexcept { return m_col; } +std::uint32_t TableCursor::column() const noexcept { return m_column; } std::uint32_t TableCursor::row() const noexcept { return m_row; } void TableCursor::handle_rowspan_() noexcept { auto &s = m_sparse.front(); auto it = std::begin(s); - while (it != std::end(s) && m_col == it->start) { - m_col = it->end; + while (it != std::end(s) && m_column == it->start) { + m_column = it->end; ++it; } s.erase(std::begin(s), it); diff --git a/src/odr/internal/common/table_cursor.hpp b/src/odr/internal/common/table_cursor.hpp index 3e080abb..9a78d685 100644 --- a/src/odr/internal/common/table_cursor.hpp +++ b/src/odr/internal/common/table_cursor.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -22,11 +22,11 @@ class TableCursor final { private: struct Range { - std::uint32_t start; - std::uint32_t end; + std::uint32_t start{0}; + std::uint32_t end{0}; }; - std::uint32_t m_col{0}; + std::uint32_t m_column{0}; std::uint32_t m_row{0}; std::list> m_sparse; diff --git a/src/odr/internal/common/table_range.hpp b/src/odr/internal/common/table_range.hpp index c49ecddb..06eb0dcc 100644 --- a/src/odr/internal/common/table_range.hpp +++ b/src/odr/internal/common/table_range.hpp @@ -1,8 +1,7 @@ #pragma once -#include +#include -#include #include namespace odr::internal { diff --git a/src/odr/internal/csv/csv_file.cpp b/src/odr/internal/csv/csv_file.cpp index 4d6f9b90..c0f5e33c 100644 --- a/src/odr/internal/csv/csv_file.cpp +++ b/src/odr/internal/csv/csv_file.cpp @@ -2,8 +2,6 @@ #include -#include - namespace odr::internal::csv { CsvFile::CsvFile(std::shared_ptr file) diff --git a/src/odr/internal/html/common.cpp b/src/odr/internal/html/common.cpp index 27e971af..bc7e2103 100644 --- a/src/odr/internal/html/common.cpp +++ b/src/odr/internal/html/common.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include diff --git a/src/odr/internal/html/document.cpp b/src/odr/internal/html/document.cpp index 0dd6a71b..dfe89780 100644 --- a/src/odr/internal/html/document.cpp +++ b/src/odr/internal/html/document.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/src/odr/internal/html/document_element.cpp b/src/odr/internal/html/document_element.cpp index 529ceacb..04aa0754 100644 --- a/src/odr/internal/html/document_element.cpp +++ b/src/odr/internal/html/document_element.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -20,7 +21,8 @@ void html::translate_children(const ElementRange &range, } } -void html::translate_element(const Element element, const WritingState &state) { +void html::translate_element(const Element &element, + const WritingState &state) { if (element.type() == ElementType::text) { translate_text(element, state); } else if (element.type() == ElementType::line_break) { @@ -86,8 +88,8 @@ void html::translate_sheet(const Sheet &sheet, const WritingState &state) { for (std::uint32_t column_index = 0; column_index < end_column; ++column_index) { - SheetColumn table_column = sheet.column(column_index); - TableColumnStyle table_column_style = table_column.style(); + const TableColumnStyle table_column_style = + sheet.column_style(column_index); state.out().write_element_begin( "col", @@ -119,8 +121,7 @@ void html::translate_sheet(const Sheet &sheet, const WritingState &state) { TableCursor cursor; for (std::uint32_t row_index = cursor.row(); row_index < end_row; row_index = cursor.row()) { - SheetRow table_row = sheet.row(row_index); - TableRowStyle table_row_style = table_row.style(); + const TableRowStyle table_row_style = sheet.row_style(row_index); state.out().write_element_begin( "tr", HtmlElementOptions().set_style( @@ -140,7 +141,7 @@ void html::translate_sheet(const Sheet &sheet, const WritingState &state) { for (std::uint32_t column_index = cursor.column(); column_index < end_column; column_index = cursor.column()) { - SheetCell cell = sheet.cell(column_index, row_index); + const SheetCell cell = sheet.cell(column_index, row_index); if (cell.is_covered()) { continue; @@ -148,9 +149,10 @@ void html::translate_sheet(const Sheet &sheet, const WritingState &state) { // TODO looks a bit odd to query the same (col, row) all the time. maybe // there could be a struct to get all the info? - TableCellStyle cell_style = cell.style(); - TableDimensions cell_span = cell.span(); - ValueType cell_value_type = cell.value_type(); + const TableCellStyle cell_style = + sheet.cell_style(column_index, row_index); + const TableDimensions cell_span = cell.span(); + const ValueType cell_value_type = cell.value_type(); state.out().write_element_begin( "td", @@ -229,7 +231,7 @@ void html::translate_master_page(const MasterPage &masterPage, } } -void html::translate_text(const Element element, const WritingState &state) { +void html::translate_text(const Element &element, const WritingState &state) { const Text text = element.as_text(); state.out().write_element_begin( @@ -247,7 +249,7 @@ void html::translate_text(const Element element, const WritingState &state) { state.out().write_element_end("x-s"); } -void html::translate_line_break(const Element element, +void html::translate_line_break(const Element &element, const WritingState &state) { const LineBreak line_break = element.as_line_break(); @@ -259,7 +261,7 @@ void html::translate_line_break(const Element element, state.out().write_element_end("x-s"); } -void html::translate_paragraph(const Element element, +void html::translate_paragraph(const Element &element, const WritingState &state) { const Paragraph paragraph = element.as_paragraph(); @@ -286,7 +288,7 @@ void html::translate_paragraph(const Element element, state.out().write_element_end("x-p"); } -void html::translate_span(const Element element, const WritingState &state) { +void html::translate_span(const Element &element, const WritingState &state) { const Span span = element.as_span(); state.out().write_element_begin( @@ -296,7 +298,7 @@ void html::translate_span(const Element element, const WritingState &state) { state.out().write_element_end("x-s"); } -void html::translate_link(const Element element, const WritingState &state) { +void html::translate_link(const Element &element, const WritingState &state) { const Link link = element.as_link(); state.out().write_element_begin( @@ -306,7 +308,7 @@ void html::translate_link(const Element element, const WritingState &state) { state.out().write_element_end("a"); } -void html::translate_bookmark(const Element element, +void html::translate_bookmark(const Element &element, const WritingState &state) { const Bookmark bookmark = element.as_bookmark(); @@ -316,13 +318,13 @@ void html::translate_bookmark(const Element element, state.out().write_element_end("a"); } -void html::translate_list(const Element element, const WritingState &state) { +void html::translate_list(const Element &element, const WritingState &state) { state.out().write_element_begin("ul"); translate_children(element.children(), state); state.out().write_element_end("ul"); } -void html::translate_list_item(const Element element, +void html::translate_list_item(const Element &element, const WritingState &state) { const ListItem list_item = element.as_list_item(); @@ -333,7 +335,7 @@ void html::translate_list_item(const Element element, state.out().write_element_end("li"); } -void html::translate_table(const Element element, const WritingState &state) { +void html::translate_table(const Element &element, const WritingState &state) { const Table table = element.as_table(); state.out().write_element_begin( @@ -393,7 +395,7 @@ void html::translate_table(const Element element, const WritingState &state) { state.out().write_element_end("table"); } -void html::translate_image(const Element element, const WritingState &state) { +void html::translate_image(const Element &element, const WritingState &state) { const Image image = element.as_image(); odr::HtmlResource resource; @@ -429,7 +431,7 @@ void html::translate_image(const Element element, const WritingState &state) { .set_style("position:absolute;left:0;top:0;width:100%;height:100%")); } -void html::translate_frame(const Element element, const WritingState &state) { +void html::translate_frame(const Element &element, const WritingState &state) { const Frame frame = element.as_frame(); const GraphicStyle style = frame.style(); @@ -440,7 +442,7 @@ void html::translate_frame(const Element element, const WritingState &state) { state.out().write_element_end("div"); } -void html::translate_rect(const Element element, const WritingState &state) { +void html::translate_rect(const Element &element, const WritingState &state) { const Rect rect = element.as_rect(); const GraphicStyle style = rect.style(); @@ -454,7 +456,7 @@ void html::translate_rect(const Element element, const WritingState &state) { state.out().write_element_end("div"); } -void html::translate_line(const Element element, const WritingState &state) { +void html::translate_line(const Element &element, const WritingState &state) { const Line line = element.as_line(); const GraphicStyle style = line.style(); @@ -478,7 +480,7 @@ void html::translate_line(const Element element, const WritingState &state) { state.out().write_element_end("svg"); } -void html::translate_circle(const Element element, const WritingState &state) { +void html::translate_circle(const Element &element, const WritingState &state) { const Circle circle = element.as_circle(); const GraphicStyle style = circle.style(); @@ -493,7 +495,7 @@ void html::translate_circle(const Element element, const WritingState &state) { state.out().write_element_end("div"); } -void html::translate_custom_shape(const Element element, +void html::translate_custom_shape(const Element &element, const WritingState &state) { const CustomShape custom_shape = element.as_custom_shape(); const GraphicStyle style = custom_shape.style(); diff --git a/src/odr/internal/html/document_element.hpp b/src/odr/internal/html/document_element.hpp index 1fd36b9e..f49e251b 100644 --- a/src/odr/internal/html/document_element.hpp +++ b/src/odr/internal/html/document_element.hpp @@ -14,7 +14,7 @@ struct WritingState; class HtmlWriter; void translate_children(const ElementRange &range, const WritingState &state); -void translate_element(Element element, const WritingState &state); +void translate_element(const Element &element, const WritingState &state); void translate_slide(const Slide &slide, const WritingState &state); void translate_sheet(const Sheet &sheet, const WritingState &state); @@ -23,20 +23,20 @@ void translate_page(const Page &page, const WritingState &state); void translate_master_page(const MasterPage &masterPage, const WritingState &state); -void translate_text(Element element, const WritingState &state); -void translate_line_break(Element element, const WritingState &state); -void translate_paragraph(Element element, const WritingState &state); -void translate_span(Element element, const WritingState &state); -void translate_link(Element element, const WritingState &state); -void translate_bookmark(Element element, const WritingState &state); -void translate_list(Element element, const WritingState &state); -void translate_list_item(Element element, const WritingState &state); -void translate_table(Element element, const WritingState &state); -void translate_image(Element element, const WritingState &state); -void translate_frame(Element element, const WritingState &state); -void translate_rect(Element element, const WritingState &state); -void translate_line(Element element, const WritingState &state); -void translate_circle(Element element, const WritingState &state); -void translate_custom_shape(Element element, const WritingState &state); +void translate_text(const Element &element, const WritingState &state); +void translate_line_break(const Element &element, const WritingState &state); +void translate_paragraph(const Element &element, const WritingState &state); +void translate_span(const Element &element, const WritingState &state); +void translate_link(const Element &element, const WritingState &state); +void translate_bookmark(const Element &element, const WritingState &state); +void translate_list(const Element &element, const WritingState &state); +void translate_list_item(const Element &element, const WritingState &state); +void translate_table(const Element &element, const WritingState &state); +void translate_image(const Element &element, const WritingState &state); +void translate_frame(const Element &element, const WritingState &state); +void translate_rect(const Element &element, const WritingState &state); +void translate_line(const Element &element, const WritingState &state); +void translate_circle(const Element &element, const WritingState &state); +void translate_custom_shape(const Element &element, const WritingState &state); } // namespace odr::internal::html diff --git a/src/odr/internal/html/pdf_file.cpp b/src/odr/internal/html/pdf_file.cpp index cb1e3f16..be978ca6 100644 --- a/src/odr/internal/html/pdf_file.cpp +++ b/src/odr/internal/html/pdf_file.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace odr::internal::html { diff --git a/src/odr/internal/json/json_file.cpp b/src/odr/internal/json/json_file.cpp index d5fecd72..f4b287a3 100644 --- a/src/odr/internal/json/json_file.cpp +++ b/src/odr/internal/json/json_file.cpp @@ -2,8 +2,6 @@ #include -#include - namespace odr::internal::json { JsonFile::JsonFile(std::shared_ptr file) diff --git a/src/odr/internal/magic.cpp b/src/odr/internal/magic.cpp index c5df6d6d..17ac2021 100644 --- a/src/odr/internal/magic.cpp +++ b/src/odr/internal/magic.cpp @@ -7,8 +7,6 @@ #include #include -#include - namespace odr::internal { namespace { diff --git a/src/odr/internal/odf/odf_document.cpp b/src/odr/internal/odf/odf_document.cpp index 4f170860..e79254eb 100644 --- a/src/odr/internal/odf/odf_document.cpp +++ b/src/odr/internal/odf/odf_document.cpp @@ -1,34 +1,61 @@ #include +#include #include +#include #include #include +#include +#include #include #include +#include #include #include +#include #include #include +#include + namespace odr::internal::odf { +namespace { +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry); +} + Document::Document(const FileType file_type, const DocumentType document_type, std::shared_ptr files) - : TemplateDocument(file_type, document_type, std::move(files)) { - m_content_xml = util::xml::parse(*m_filesystem, AbsPath("/content.xml")); + : internal::Document(file_type, document_type, std::move(files)) { + m_content_xml = util::xml::parse(*m_files, AbsPath("/content.xml")); - if (m_filesystem->exists(AbsPath("/styles.xml"))) { - m_styles_xml = util::xml::parse(*m_filesystem, AbsPath("/styles.xml")); + if (m_files->exists(AbsPath("/styles.xml"))) { + m_styles_xml = util::xml::parse(*m_files, AbsPath("/styles.xml")); } m_root_element = parse_tree( - *this, + m_element_registry, m_content_xml.document_element().child("office:body").first_child()); m_style_registry = StyleRegistry(*this, m_content_xml.document_element(), m_styles_xml.document_element()); + + m_element_adapter = create_element_adapter(*this, m_element_registry); +} + +ElementRegistry &Document::element_registry() { return m_element_registry; } + +StyleRegistry &Document::style_registry() { return m_style_registry; } + +const ElementRegistry &Document::element_registry() const { + return m_element_registry; +} + +const StyleRegistry &Document::style_registry() const { + return m_style_registry; } bool Document::is_editable() const noexcept { @@ -47,12 +74,12 @@ void Document::save(const Path &path) const { zip::ZipArchive archive; // `mimetype` has to be the first file and uncompressed - if (m_filesystem->is_file(AbsPath("/mimetype"))) { + if (m_files->is_file(AbsPath("/mimetype"))) { archive.insert_file(std::end(archive), RelPath("mimetype"), - m_filesystem->open(AbsPath("/mimetype")), 0); + m_files->open(AbsPath("/mimetype")), 0); } - for (auto walker = m_filesystem->file_walker(AbsPath("/")); !walker->end(); + for (auto walker = m_files->file_walker(AbsPath("/")); !walker->end(); walker->next()) { const AbsPath &abs_path = walker->path(); RelPath rel_path = abs_path.rebase(AbsPath("/")); @@ -74,7 +101,7 @@ void Document::save(const Path &path) const { if (abs_path == Path("/META-INF/manifest.xml")) { // TODO auto manifest = - util::xml::parse(*m_filesystem, AbsPath("/META-INF/manifest.xml")); + util::xml::parse(*m_files, AbsPath("/META-INF/manifest.xml")); for (auto &&node : manifest.select_nodes("//manifest:encryption-data")) { node.node().parent().remove_child(node.node()); @@ -87,8 +114,7 @@ void Document::save(const Path &path) const { continue; } - archive.insert_file(std::end(archive), rel_path, - m_filesystem->open(abs_path)); + archive.insert_file(std::end(archive), rel_path, m_files->open(abs_path)); } std::ofstream ostream = util::file::create(path.string()); @@ -100,4 +126,1078 @@ void Document::save(const Path & /*path*/, const char * /*password*/) const { throw UnsupportedOperation(); } +namespace { + +class ElementAdapter final : public abstract::ElementAdapter, + public abstract::TextRootAdapter, + public abstract::SlideAdapter, + public abstract::PageAdapter, + public abstract::SheetAdapter, + public abstract::SheetCellAdapter, + public abstract::MasterPageAdapter, + public abstract::LineBreakAdapter, + public abstract::ParagraphAdapter, + public abstract::SpanAdapter, + public abstract::TextAdapter, + public abstract::LinkAdapter, + public abstract::BookmarkAdapter, + public abstract::ListItemAdapter, + public abstract::TableAdapter, + public abstract::TableColumnAdapter, + public abstract::TableRowAdapter, + public abstract::TableCellAdapter, + public abstract::FrameAdapter, + public abstract::RectAdapter, + public abstract::LineAdapter, + public abstract::CircleAdapter, + public abstract::CustomShapeAdapter, + public abstract::ImageAdapter { +public: + ElementAdapter(const Document &document, ElementRegistry ®istry) + : m_document(&document), m_registry(®istry) {} + + [[nodiscard]] ElementType + element_type(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->type; + } + return ElementType::none; + } + + [[nodiscard]] ElementIdentifier + element_parent(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->parent_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_first_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->first_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_last_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->last_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_previous_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->previous_sibling_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_next_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->next_sibling_id; + } + return null_element_id; + } + + [[nodiscard]] bool + element_is_editable(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + if (element->parent_id != null_element_id) { + return element_is_editable(element->parent_id); + } + if (element->type == ElementType::sheet_cell) { + const ElementRegistry::SheetCell *sheet_cell = + m_registry->sheet_cell_element(element_id); + return sheet_cell != nullptr && !sheet_cell->is_repeated; + } + return true; + } + return false; + } + + [[nodiscard]] const TextRootAdapter * + text_root_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::root) { + return nullptr; + } + return this; + } + [[nodiscard]] const SlideAdapter * + slide_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::slide) { + return nullptr; + } + return this; + } + [[nodiscard]] const PageAdapter * + page_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::page) { + return nullptr; + } + return this; + } + [[nodiscard]] const SheetAdapter * + sheet_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::sheet) { + return nullptr; + } + return this; + } + [[nodiscard]] const SheetCellAdapter * + sheet_cell_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::sheet_cell) { + return nullptr; + } + return this; + } + [[nodiscard]] const MasterPageAdapter * + master_page_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::page) { + return nullptr; + } + return this; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::line_break) { + return nullptr; + } + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::paragraph) { + return nullptr; + } + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::span) { + return nullptr; + } + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::text) { + return nullptr; + } + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::link) { + return nullptr; + } + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::bookmark) { + return nullptr; + } + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::list_item) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_column) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_row) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_cell) { + return nullptr; + } + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::frame) { + return nullptr; + } + return this; + } + [[nodiscard]] const RectAdapter * + rect_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::rect) { + return nullptr; + } + return this; + } + [[nodiscard]] const LineAdapter * + line_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::line) { + return nullptr; + } + return this; + } + [[nodiscard]] const CircleAdapter * + circle_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::circle) { + return nullptr; + } + return this; + } + [[nodiscard]] const CustomShapeAdapter * + custom_shape_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::custom_shape) { + return nullptr; + } + return this; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::image) { + return nullptr; + } + return this; + } + + [[nodiscard]] PageLayout + text_root_page_layout(const ElementIdentifier element_id) const override { + if (const ElementIdentifier master_page_id = + text_root_first_master_page(element_id); + master_page_id != null_element_id) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ElementIdentifier text_root_first_master_page( + const ElementIdentifier element_id) const override { + (void)element_id; + if (const ElementIdentifier first_master_page_id = + m_document->style_registry().first_master_page(); + first_master_page_id != null_element_id) { + return first_master_page_id; + } + return {}; + } + + [[nodiscard]] PageLayout + slide_page_layout(const ElementIdentifier element_id) const override { + if (const ElementIdentifier master_page_id = slide_master_page(element_id); + master_page_id != null_element_id) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ElementIdentifier + slide_master_page(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute master_page_name_attr = + node.attribute("draw:master-page-name"); + master_page_name_attr) { + return m_document->style_registry().master_page( + master_page_name_attr.value()); + } + return {}; + } + [[nodiscard]] std::string + slide_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("draw:name").value(); + } + + [[nodiscard]] PageLayout + page_layout(const ElementIdentifier element_id) const override { + if (const ElementIdentifier master_page_id = page_master_page(element_id); + master_page_id != null_element_id) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ElementIdentifier + page_master_page(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute master_page_name_attr = + node.attribute("draw:master-page-name"); + master_page_name_attr) { + return m_document->style_registry().master_page( + master_page_name_attr.value()); + } + return {}; + } + [[nodiscard]] std::string + page_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("draw:name").value(); + } + + [[nodiscard]] std::string + sheet_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("table:name").value(); + } + [[nodiscard]] TableDimensions + sheet_dimensions(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(element_id); + sheet_registry != nullptr) { + return sheet_registry->dimensions; + } + return {}; + } + [[nodiscard]] TableDimensions + sheet_content(const ElementIdentifier element_id, + const std::optional range) const override { + const pugi::xml_node node = get_node(element_id); + + TableDimensions result; + + TableCursor cursor; + for (auto row : node.children("table:table-row")) { + const auto rows_repeated = + row.attribute("table:number-rows-repeated").as_uint(1); + cursor.add_row(rows_repeated); + + for (auto cell : row.children("table:table-cell")) { + const auto columns_repeated = + cell.attribute("table:number-columns-repeated").as_uint(1); + const auto colspan = + cell.attribute("table:number-columns-spanned").as_uint(1); + const auto rowspan = + cell.attribute("table:number-rows-spanned").as_uint(1); + cursor.add_cell(colspan, rowspan, columns_repeated); + + const std::uint32_t new_rows = cursor.row(); + if (const std::uint32_t new_cols = + std::max(result.columns, cursor.column()); + cell.first_child() && range && new_rows < range->rows && + new_cols < range->columns) { + result.rows = new_rows; + result.columns = new_cols; + } + } + } + + return result; + } + [[nodiscard]] ElementIdentifier + sheet_cell(const ElementIdentifier element_id, const std::uint32_t column, + const std::uint32_t row) const override { + if (const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(element_id); + sheet_registry != nullptr) { + if (const ElementRegistry::Sheet::Cell *sheet_cell = + sheet_registry->cell(column, row); + sheet_cell != nullptr) { + return sheet_cell->element_id; + } + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + sheet_first_shape(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(element_id); + sheet_registry != nullptr) { + return sheet_registry->first_shape_id; + } + return null_element_id; + } + [[nodiscard]] TableStyle + sheet_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + [[nodiscard]] TableColumnStyle + sheet_column_style(const ElementIdentifier element_id, + const std::uint32_t column) const override { + if (const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(element_id); + sheet_registry != nullptr) { + if (const pugi::xml_node column_node = + sheet_registry->column_node(column); + column_node) { + if (const pugi::xml_attribute attr = + column_node.attribute("table:style-name")) { + if (const Style *style = + m_document->style_registry().style(attr.value()); + style != nullptr) { + return style->resolved().table_column_style; + } + } + } + } + return {}; + } + [[nodiscard]] TableRowStyle + sheet_row_style(const ElementIdentifier element_id, + const std::uint32_t row) const override { + if (const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(element_id); + sheet_registry != nullptr) { + if (const pugi::xml_node column_node = sheet_registry->row_node(row); + column_node) { + if (const pugi::xml_attribute attr = + column_node.attribute("table:style-name")) { + if (const Style *style = + m_document->style_registry().style(attr.value()); + style != nullptr) { + return style->resolved().table_row_style; + } + } + } + } + return {}; + } + [[nodiscard]] TableCellStyle + sheet_cell_style(const ElementIdentifier element_id, + const std::uint32_t column, + const std::uint32_t row) const override { + const ElementIdentifier cell_id = sheet_cell(element_id, column, row); + return get_partial_cell_style(element_id, cell_id, {column, row}) + .table_cell_style; + } + + [[nodiscard]] TablePosition + sheet_cell_position(const ElementIdentifier element_id) const override { + if (const ElementRegistry::SheetCell *sheet_cell = + m_registry->sheet_cell_element(element_id); + sheet_cell != nullptr) { + return sheet_cell->position; + } + return {}; + } + [[nodiscard]] bool + sheet_cell_is_covered(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return std::strcmp(node.name(), "table:covered-table-cell") == 0; + } + [[nodiscard]] TableDimensions + sheet_cell_span(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return {node.attribute("table:number-rows-spanned").as_uint(1), + node.attribute("table:number-columns-spanned").as_uint(1)}; + } + [[nodiscard]] ValueType + sheet_cell_value_type(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const char *value_type = node.attribute("office:value-type").value(); + std::strcmp("float", value_type) == 0) { + return ValueType::float_number; + } + return ValueType::string; + } + + [[nodiscard]] PageLayout + master_page_page_layout(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = + node.attribute("style:page-layout-name")) { + return m_document->style_registry().page_layout(attribute.value()); + } + return {}; + } + + [[nodiscard]] TextStyle + line_break_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle + paragraph_text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ElementIdentifier element_id) const override { + const ElementRegistry::Text *text_element = + m_registry->text_element(element_id); + if (text_element == nullptr) { + return ""; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + std::string result; + for (pugi::xml_node node = first; node != last.next_sibling(); + node = node.next_sibling()) { + result += get_text(node); + } + return result; + } + void text_set_content(const ElementIdentifier element_id, + const std::string &text) const override { + ElementRegistry::Element *element = m_registry->element(element_id); + ElementRegistry::Text *text_element = m_registry->text_element(element_id); + if (element == nullptr || text_element == nullptr) { + return; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + pugi::xml_node parent = first.parent(); + const pugi::xml_node old_first = first; + const pugi::xml_node old_last = last; + pugi::xml_node new_first = old_first; + pugi::xml_node new_last = last; + + const auto insert_pcdata = [&] { + const pugi::xml_node new_node = parent.insert_child_before( + pugi::xml_node_type::node_pcdata, old_first); + if (new_first == old_first) { + new_first = new_node; + } + new_last = new_node; + return new_node; + }; + const auto insert_node = [&](const char *node) { + const pugi::xml_node new_node = + parent.insert_child_before(node, old_first); + if (new_first == old_first) { + new_first = new_node; + } + new_last = new_node; + return new_node; + }; + + for (const util::xml::StringToken &token : util::xml::tokenize_text(text)) { + switch (token.type) { + case util::xml::StringToken::Type::none: + break; + case util::xml::StringToken::Type::string: { + auto text_node = insert_pcdata(); + text_node.text().set(token.string.c_str()); + } break; + case util::xml::StringToken::Type::spaces: { + auto space_node = insert_node("text:s"); + space_node.prepend_attribute("text:c").set_value(token.string.size()); + } break; + case util::xml::StringToken::Type::tabs: { + for (std::size_t i = 0; i < token.string.size(); ++i) { + insert_node("text:tab"); + } + } break; + } + } + + element->node = new_first; + text_element->last = new_last; + + for (pugi::xml_node node = old_first; node != old_last.next_sibling();) { + const pugi::xml_node next = node.next_sibling(); + parent.remove_child(node); + node = next; + } + } + [[nodiscard]] TextStyle + text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("xlink:href").value(); + } + + [[nodiscard]] std::string + bookmark_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("text:name").value(); + } + + [[nodiscard]] TextStyle + list_item_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TableDimensions + table_dimensions(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + + TableDimensions result; + TableCursor cursor; + + for (auto column : node.children("table:table-column")) { + const auto columns_repeated = + column.attribute("table:number-columns-repeated").as_uint(1); + cursor.add_column(columns_repeated); + } + + result.columns = cursor.column(); + cursor = {}; + + for (auto row : node.children("table:table-row")) { + const auto rows_repeated = + row.attribute("table:number-rows-repeated").as_uint(1); + cursor.add_row(rows_repeated); + } + + result.rows = cursor.row(); + + return result; + } + [[nodiscard]] ElementIdentifier + table_first_column(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Table *table_registry = + m_registry->table_element(element_id); + table_registry != nullptr) { + return table_registry->first_column_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + table_first_row(const ElementIdentifier element_id) const override { + return element_first_child(element_id); + } + [[nodiscard]] TableStyle + table_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + + [[nodiscard]] TableColumnStyle + table_column_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_column_style; + } + + [[nodiscard]] TableRowStyle + table_row_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_row_style; + } + + [[nodiscard]] bool + table_cell_is_covered(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return std::strcmp(node.name(), "table:covered-table-cell") == 0; + } + [[nodiscard]] TableDimensions + table_cell_span(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return {node.attribute("table:number-rows-spanned").as_uint(1), + node.attribute("table:number-columns-spanned").as_uint(1)}; + } + [[nodiscard]] ValueType + table_cell_value_type(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const char *value_type = node.attribute("office:value-type").value(); + std::strcmp("float", value_type) == 0) { + return ValueType::float_number; + } + return ValueType::string; + } + [[nodiscard]] TableCellStyle + table_cell_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_cell_style; + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + + const char *anchor_type = node.attribute("text:anchor-type").value(); + if (std::strcmp("as-char", anchor_type) == 0) { + return AnchorType::as_char; + } + if (std::strcmp("char", anchor_type) == 0) { + return AnchorType::at_char; + } + if (std::strcmp("paragraph", anchor_type) == 0) { + return AnchorType::at_paragraph; + } + if (std::strcmp("page", anchor_type) == 0) { + return AnchorType::at_page; + } + return AnchorType::at_page; + } + [[nodiscard]] std::optional + frame_x(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:x")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::optional + frame_y(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:y")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::optional + frame_width(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:width")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::optional + frame_height(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:height")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::optional + frame_z_index(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("draw:z-index")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + rect_x(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:x").value(); + } + [[nodiscard]] std::string + rect_y(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:y").value(); + } + [[nodiscard]] std::string + rect_width(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:width").value(); + } + [[nodiscard]] std::string + rect_height(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:height").value(); + } + [[nodiscard]] GraphicStyle + rect_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + line_x1(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:x1").value(); + } + [[nodiscard]] std::string + line_y1(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:y1").value(); + } + [[nodiscard]] std::string + line_x2(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:x2").value(); + } + [[nodiscard]] std::string + line_y2(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:y2").value(); + } + [[nodiscard]] GraphicStyle + line_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + circle_x(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:x").value(); + } + [[nodiscard]] std::string + circle_y(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:y").value(); + } + [[nodiscard]] std::string + circle_width(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:width").value(); + } + [[nodiscard]] std::string + circle_height(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:height").value(); + } + [[nodiscard]] GraphicStyle + circle_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::optional + custom_shape_x(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:x")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::optional + custom_shape_y(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:y")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] std::string + custom_shape_width(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:width").value(); + } + [[nodiscard]] std::string + custom_shape_height(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:height").value(); + } + [[nodiscard]] GraphicStyle + custom_shape_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] bool + image_is_internal(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return false; + } + try { + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return m_document->as_filesystem()->is_file(path); + } catch (...) { + } + return false; + } + [[nodiscard]] std::optional + image_file(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return std::nullopt; + } + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return File(m_document->as_filesystem()->open(path)); + } + [[nodiscard]] std::string + image_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("xlink:href").value(); + } + +private: + const Document *m_document{nullptr}; + ElementRegistry *m_registry{nullptr}; + + [[nodiscard]] pugi::xml_node + get_node(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->node; + } + return {}; + } + + [[nodiscard]] static std::string get_text(const pugi::xml_node node) { + if (node.type() == pugi::node_pcdata) { + return node.value(); + } + + const std::string name = node.name(); + if (name == "text:s") { + const std::size_t count = node.attribute("text:c").as_uint(1); + return std::string(count, ' '); + } + if (name == "text:tab") { + return "\t"; + } + return ""; + } + + [[nodiscard]] const char * + get_style_name(const ElementIdentifier element_id) const { + const pugi::xml_node node = get_node(element_id); + for (pugi::xml_attribute attribute : node.attributes()) { + if (util::string::ends_with(attribute.name(), ":style-name")) { + return attribute.value(); + } + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_partial_style(const ElementIdentifier element_id) const { + if (const ElementRegistry::SheetCell *cell_registry = + m_registry->sheet_cell_element(element_id); + cell_registry != nullptr) { + return get_partial_cell_style(element_parent(element_id), element_id, + cell_registry->position); + } + if (const char *style_name = get_style_name(element_id); + style_name != nullptr) { + if (const Style *style = m_document->style_registry().style(style_name)) { + return style->resolved(); + } + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ElementIdentifier element_id) const { + const ElementIdentifier parent_id = element_parent(element_id); + if (parent_id == null_element_id) { + return get_partial_style(element_id); + } + ResolvedStyle base = get_intermediate_style(parent_id); + base.override(get_partial_style(element_id)); + return base; + } + + ResolvedStyle get_partial_cell_style(const ElementIdentifier sheet_id, + const ElementIdentifier cell_id, + const TablePosition &position) const { + const char *style_name = nullptr; + + if (const pugi::xml_attribute attr = + get_node(cell_id).attribute("table:style-name")) { + style_name = attr.value(); + } + + const auto [column, row] = position.pair(); + const ElementRegistry::Sheet *sheet_registry = + m_registry->sheet_element(sheet_id); + if (sheet_registry == nullptr) { + return {}; + } + + if (style_name == nullptr) { + const pugi::xml_node cell_node = sheet_registry->cell_node(column, row); + if (const pugi::xml_attribute attr = + cell_node.attribute("table:style-name")) { + style_name = attr.value(); + } + } + + if (style_name == nullptr) { + const pugi::xml_node row_node = sheet_registry->row_node(row); + if (const pugi::xml_attribute attr = + row_node.attribute("table:default-cell-style-name")) { + style_name = attr.value(); + } + } + if (style_name == nullptr) { + const pugi::xml_node column_node = sheet_registry->column_node(column); + if (const pugi::xml_attribute attr = + column_node.attribute("table:default-cell-style-name")) { + style_name = attr.value(); + } + } + + if (style_name != nullptr) { + if (const Style *style = m_document->style_registry().style(style_name); + style != nullptr) { + return style->resolved(); + } + } + + return {}; + } +}; + +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry) { + return std::make_unique(document, registry); +} + +} // namespace + } // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_document.hpp b/src/odr/internal/odf/odf_document.hpp index 37ac2041..cc172a14 100644 --- a/src/odr/internal/odf/odf_document.hpp +++ b/src/odr/internal/odf/odf_document.hpp @@ -1,38 +1,38 @@ #pragma once -#include - #include -#include -#include +#include #include -#include +#include -namespace odr::internal::abstract { -class ReadableFilesystem; -} // namespace odr::internal::abstract +#include namespace odr::internal::odf { -class Document final : public TemplateDocument { +class Document final : public internal::Document { public: Document(FileType file_type, DocumentType document_type, std::shared_ptr files); + ElementRegistry &element_registry(); + StyleRegistry &style_registry(); + + [[nodiscard]] const ElementRegistry &element_registry() const; + [[nodiscard]] const StyleRegistry &style_registry() const; + [[nodiscard]] bool is_editable() const noexcept override; [[nodiscard]] bool is_savable(bool encrypted) const noexcept override; void save(const Path &path) const override; void save(const Path &path, const char *password) const override; -protected: +private: pugi::xml_document m_content_xml; pugi::xml_document m_styles_xml; + ElementRegistry m_element_registry; StyleRegistry m_style_registry; - - friend class Element; }; } // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_element.cpp b/src/odr/internal/odf/odf_element.cpp deleted file mode 100644 index 734a4a5c..00000000 --- a/src/odr/internal/odf/odf_element.cpp +++ /dev/null @@ -1,491 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace odr::internal::odf { - -namespace { - -const char *default_style_name(const pugi::xml_node node) { - for (pugi::xml_attribute attribute : node.attributes()) { - if (util::string::ends_with(attribute.name(), ":style-name")) { - return attribute.value(); - } - } - return {}; -} - -} // namespace - -Element::Element(const pugi::xml_node node) : m_node{node} { - if (!node) { - // TODO log error - throw std::runtime_error("node not set"); - } -} - -ResolvedStyle Element::partial_style(const abstract::Document *document) const { - if (const char *style_name = style_name_(document)) { - if (const Style *style = style_(document)->style(style_name)) { - return style->resolved(); - } - } - return {}; -} - -ResolvedStyle -Element::intermediate_style(const abstract::Document *document) const { - abstract::Element *parent = this->parent(document); - if (parent == nullptr) { - return partial_style(document); - } - ResolvedStyle base = - dynamic_cast(parent)->intermediate_style(document); - base.override(partial_style(document)); - return base; -} - -bool Element::is_editable(const abstract::Document *document) const { - if (m_parent == nullptr) { - return document_(document)->is_editable(); - } - return m_parent->is_editable(document); -} - -const char *Element::style_name_(const abstract::Document *) const { - return default_style_name(m_node); -} - -const Document *Element::document_(const abstract::Document *document) { - return dynamic_cast(document); -} - -const StyleRegistry *Element::style_(const abstract::Document *document) { - return &dynamic_cast(document)->m_style_registry; -} - -ElementType Root::type(const abstract::Document *) const { - return ElementType::root; -} - -ElementType TextRoot::type(const abstract::Document *) const { - return ElementType::root; -} - -PageLayout TextRoot::page_layout(const abstract::Document *document) const { - if (const auto *master_page = - dynamic_cast(this->first_master_page(document))) { - return master_page->page_layout(document); - } - return {}; -} - -abstract::Element * -TextRoot::first_master_page(const abstract::Document *document) const { - if (MasterPage *first_master_page = style_(document)->first_master_page()) { - return first_master_page; - } - return {}; -} - -PageLayout MasterPage::page_layout(const abstract::Document *document) const { - if (const pugi::xml_attribute attribute = - m_node.attribute("style:page-layout-name")) { - return style_(document)->page_layout(attribute.value()); - } - return {}; -} - -PageLayout Slide::page_layout(const abstract::Document *document) const { - if (const auto *master_page = - dynamic_cast(this->master_page(document))) { - return master_page->page_layout(document); - } - return {}; -} - -abstract::Element * -Slide::master_page(const abstract::Document *document) const { - if (const pugi::xml_attribute master_page_name_attr = - m_node.attribute("draw:master-page-name")) { - return style_(document)->master_page(master_page_name_attr.value()); - } - return {}; -} - -std::string Slide::name(const abstract::Document *) const { - return m_node.attribute("draw:name").value(); -} - -PageLayout Page::page_layout(const abstract::Document *document) const { - if (const auto *master_page = - dynamic_cast(this->master_page(document))) { - return master_page->page_layout(document); - } - return {}; -} - -abstract::Element *Page::master_page(const abstract::Document *document) const { - if (const pugi::xml_attribute master_page_name_attr = - m_node.attribute("draw:master-page-name")) { - return style_(document)->master_page(master_page_name_attr.value()); - } - return {}; -} - -std::string Page::name(const abstract::Document *) const { - return m_node.attribute("draw:name").value(); -} - -TextStyle LineBreak::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -ParagraphStyle Paragraph::style(const abstract::Document *document) const { - return intermediate_style(document).paragraph_style; -} - -TextStyle Paragraph::text_style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -TextStyle Span::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -Text::Text(const pugi::xml_node node) : Text(node, node) {} - -Text::Text(const pugi::xml_node first, const pugi::xml_node last) - : Element(first), m_last{last} { - if (!last) { - // TODO log error - throw std::runtime_error("last not set"); - } -} - -std::string Text::content(const abstract::Document *) const { - std::string result; - for (pugi::xml_node node = m_node; node != m_last.next_sibling(); - node = node.next_sibling()) { - result += text_(node); - } - return result; -} - -void Text::set_content(const abstract::Document *, const std::string &text) { - pugi::xml_node parent = m_node.parent(); - const pugi::xml_node old_first = m_node; - const pugi::xml_node old_last = m_last; - pugi::xml_node new_first = old_first; - pugi::xml_node new_last = m_last; - - const auto insert_pcdata = [&] { - const pugi::xml_node new_node = - parent.insert_child_before(pugi::xml_node_type::node_pcdata, old_first); - if (new_first == old_first) { - new_first = new_node; - } - new_last = new_node; - return new_node; - }; - const auto insert_node = [&](const char *node) { - const pugi::xml_node new_node = parent.insert_child_before(node, old_first); - if (new_first == old_first) { - new_first = new_node; - } - new_last = new_node; - return new_node; - }; - - for (const util::xml::StringToken &token : util::xml::tokenize_text(text)) { - switch (token.type) { - case util::xml::StringToken::Type::none: - break; - case util::xml::StringToken::Type::string: { - auto text_node = insert_pcdata(); - text_node.text().set(token.string.c_str()); - } break; - case util::xml::StringToken::Type::spaces: { - auto space_node = insert_node("text:s"); - space_node.prepend_attribute("text:c").set_value(token.string.size()); - } break; - case util::xml::StringToken::Type::tabs: { - for (std::size_t i = 0; i < token.string.size(); ++i) { - insert_node("text:tab"); - } - } break; - } - } - - m_node = new_first; - m_last = new_last; - - for (pugi::xml_node node = old_first; node != old_last.next_sibling();) { - const pugi::xml_node next = node.next_sibling(); - parent.remove_child(node); - node = next; - } -} - -TextStyle Text::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -std::string Text::text_(const pugi::xml_node node) { - if (node.type() == pugi::node_pcdata) { - return node.value(); - } - - const std::string name = node.name(); - if (name == "text:s") { - const std::size_t count = node.attribute("text:c").as_uint(1); - return std::string(count, ' '); - } - if (name == "text:tab") { - return "\t"; - } - return ""; -} - -std::string Link::href(const abstract::Document *) const { - return m_node.attribute("xlink:href").value(); -} - -std::string Bookmark::name(const abstract::Document *) const { - return m_node.attribute("text:name").value(); -} - -TextStyle ListItem::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -TableStyle Table::style(const abstract::Document *document) const { - return partial_style(document).table_style; -} - -TableDimensions Table::dimensions(const abstract::Document *) const { - TableDimensions result; - TableCursor cursor; - - for (auto column : m_node.children("table:table-column")) { - const auto columns_repeated = - column.attribute("table:number-columns-repeated").as_uint(1); - cursor.add_column(columns_repeated); - } - - result.columns = cursor.column(); - cursor = {}; - - for (auto row : m_node.children("table:table-row")) { - const auto rows_repeated = - row.attribute("table:number-rows-repeated").as_uint(1); - cursor.add_row(rows_repeated); - } - - result.rows = cursor.row(); - - return result; -} - -TableColumnStyle TableColumn::style(const abstract::Document *document) const { - return partial_style(document).table_column_style; -} - -TableRowStyle TableRow::style(const abstract::Document *document) const { - return partial_style(document).table_row_style; -} - -bool TableCell::is_covered(const abstract::Document *) const { - return std::strcmp(m_node.name(), "table:covered-table-cell") == 0; -} - -TableDimensions TableCell::span(const abstract::Document *) const { - return {m_node.attribute("table:number-rows-spanned").as_uint(1), - m_node.attribute("table:number-columns-spanned").as_uint(1)}; -} - -ValueType TableCell::value_type(const abstract::Document *) const { - if (const char *value_type = m_node.attribute("office:value-type").value(); - std::strcmp("float", value_type) == 0) { - return ValueType::float_number; - } - return ValueType::string; -} - -TableCellStyle TableCell::style(const abstract::Document *document) const { - return partial_style(document).table_cell_style; -} - -AnchorType Frame::anchor_type(const abstract::Document *) const { - const char *anchor_type = m_node.attribute("text:anchor-type").value(); - if (std::strcmp("as-char", anchor_type) == 0) { - return AnchorType::as_char; - } - if (std::strcmp("char", anchor_type) == 0) { - return AnchorType::at_char; - } - if (std::strcmp("paragraph", anchor_type) == 0) { - return AnchorType::at_paragraph; - } - if (std::strcmp("page", anchor_type) == 0) { - return AnchorType::at_page; - } - return AnchorType::at_page; -} - -std::optional Frame::x(const abstract::Document *) const { - if (const pugi::xml_attribute attribute = m_node.attribute("svg:x")) { - return attribute.value(); - } - return {}; -} - -std::optional Frame::y(const abstract::Document *) const { - if (const pugi::xml_attribute attribute = m_node.attribute("svg:y")) { - return attribute.value(); - } - return {}; -} - -std::optional Frame::width(const abstract::Document *) const { - if (const pugi::xml_attribute attribute = m_node.attribute("svg:width")) { - return attribute.value(); - } - return {}; -} - -std::optional Frame::height(const abstract::Document *) const { - if (const pugi::xml_attribute attribute = m_node.attribute("svg:height")) { - return attribute.value(); - } - return {}; -} - -std::optional Frame::z_index(const abstract::Document *) const { - if (const pugi::xml_attribute attribute = m_node.attribute("draw:z-index")) { - return attribute.value(); - } - return {}; -} - -GraphicStyle Frame::style(const abstract::Document *document) const { - return intermediate_style(document).graphic_style; -} - -std::string Rect::x(const abstract::Document *) const { - return m_node.attribute("svg:x").value(); -} - -std::string Rect::y(const abstract::Document *) const { - return m_node.attribute("svg:y").value(); -} - -std::string Rect::width(const abstract::Document *) const { - return m_node.attribute("svg:width").value(); -} - -std::string Rect::height(const abstract::Document *) const { - return m_node.attribute("svg:height").value(); -} - -GraphicStyle Rect::style(const abstract::Document *document) const { - return intermediate_style(document).graphic_style; -} - -std::string Line::x1(const abstract::Document *) const { - return m_node.attribute("svg:x1").value(); -} - -std::string Line::y1(const abstract::Document *) const { - return m_node.attribute("svg:y1").value(); -} - -std::string Line::x2(const abstract::Document *) const { - return m_node.attribute("svg:x2").value(); -} - -std::string Line::y2(const abstract::Document *) const { - return m_node.attribute("svg:y2").value(); -} - -GraphicStyle Line::style(const abstract::Document *document) const { - return intermediate_style(document).graphic_style; -} - -std::string Circle::x(const abstract::Document *) const { - return m_node.attribute("svg:x").value(); -} - -std::string Circle::y(const abstract::Document *) const { - return m_node.attribute("svg:y").value(); -} - -std::string Circle::width(const abstract::Document *) const { - return m_node.attribute("svg:width").value(); -} - -std::string Circle::height(const abstract::Document *) const { - return m_node.attribute("svg:height").value(); -} - -GraphicStyle Circle::style(const abstract::Document *document) const { - return intermediate_style(document).graphic_style; -} - -std::optional CustomShape::x(const abstract::Document *) const { - return m_node.attribute("svg:x").value(); -} - -std::optional CustomShape::y(const abstract::Document *) const { - return m_node.attribute("svg:y").value(); -} - -std::string CustomShape::width(const abstract::Document *) const { - return m_node.attribute("svg:width").value(); -} - -std::string CustomShape::height(const abstract::Document *) const { - return m_node.attribute("svg:height").value(); -} - -GraphicStyle CustomShape::style(const abstract::Document *document) const { - return intermediate_style(document).graphic_style; -} - -bool Image::is_internal(const abstract::Document *document) const { - const Document *doc = document_(document); - if (!doc || !doc->as_filesystem()) { - return false; - } - try { - const AbsPath path = Path(href(document)).make_absolute(); - return doc->as_filesystem()->is_file(path); - } catch (...) { - } - return false; -} - -std::optional Image::file(const abstract::Document *document) const { - const Document *doc = document_(document); - if (!doc || !is_internal(document)) { - return {}; - } - const AbsPath path = Path(href(document)).make_absolute(); - return File(doc->as_filesystem()->open(path)); -} - -std::string Image::href(const abstract::Document *) const { - return m_node.attribute("xlink:href").value(); -} - -} // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_element.hpp b/src/odr/internal/odf/odf_element.hpp deleted file mode 100644 index 26deff1e..00000000 --- a/src/odr/internal/odf/odf_element.hpp +++ /dev/null @@ -1,289 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace odr::internal::odf { -class Document; -class StyleRegistry; - -class Element : public virtual internal::Element { -public: - explicit Element(pugi::xml_node); - - virtual ResolvedStyle partial_style(const abstract::Document *) const; - virtual ResolvedStyle intermediate_style(const abstract::Document *) const; - - bool is_editable(const abstract::Document *document) const override; - - pugi::xml_node m_node; - -protected: - virtual const char *style_name_(const abstract::Document *) const; - - static const Document *document_(const abstract::Document *); - static const StyleRegistry *style_(const abstract::Document *); -}; - -template -class DefaultElement final : public Element { -public: - explicit DefaultElement(const pugi::xml_node node) : Element(node) {} - - [[nodiscard]] ElementType type(const abstract::Document *) const override { - return element_type; - } -}; - -class Root : public Element { -public: - using Element::Element; - - [[nodiscard]] ElementType type(const abstract::Document *) const override; -}; - -class TextRoot final : public Root, public abstract::TextRoot { -public: - using Root::Root; - - [[nodiscard]] ElementType type(const abstract::Document *) const override; - - [[nodiscard]] PageLayout - page_layout(const abstract::Document *) const override; - - [[nodiscard]] abstract::Element * - first_master_page(const abstract::Document *) const override; -}; - -class PresentationRoot final : public Root { -public: - using Root::Root; -}; - -class DrawingRoot final : public Root { -public: - using Root::Root; -}; - -class MasterPage final : public Element, public abstract::MasterPage { -public: - using Element::Element; - - [[nodiscard]] PageLayout - page_layout(const abstract::Document *) const override; -}; - -class Slide final : public Element, public abstract::Slide { -public: - using Element::Element; - - [[nodiscard]] PageLayout - page_layout(const abstract::Document *) const override; - - [[nodiscard]] abstract::Element * - master_page(const abstract::Document *) const override; - - [[nodiscard]] std::string name(const abstract::Document *) const override; -}; - -class Page final : public Element, public abstract::Page { -public: - using Element::Element; - - [[nodiscard]] PageLayout - page_layout(const abstract::Document *) const override; - - [[nodiscard]] abstract::Element * - master_page(const abstract::Document *) const override; - - [[nodiscard]] std::string name(const abstract::Document *) const override; -}; - -class LineBreak final : public Element, public abstract::LineBreak { -public: - using Element::Element; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Paragraph final : public Element, public abstract::Paragraph { -public: - using Element::Element; - - [[nodiscard]] ParagraphStyle style(const abstract::Document *) const override; - - [[nodiscard]] TextStyle text_style(const abstract::Document *) const override; -}; - -class Span final : public Element, public abstract::Span { -public: - using Element::Element; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Text final : public Element, public abstract::Text { -public: - explicit Text(pugi::xml_node); - Text(pugi::xml_node first, pugi::xml_node last); - - [[nodiscard]] std::string content(const abstract::Document *) const override; - - void set_content(const abstract::Document *, - const std::string &text) override; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; - -private: - pugi::xml_node m_last; - - static std::string text_(pugi::xml_node); -}; - -class Link final : public Element, public abstract::Link { -public: - using Element::Element; - - [[nodiscard]] std::string href(const abstract::Document *) const override; -}; - -class Bookmark final : public Element, public abstract::Bookmark { -public: - using Element::Element; - - [[nodiscard]] std::string name(const abstract::Document *) const override; -}; - -class ListItem final : public Element, public abstract::ListItem { -public: - using Element::Element; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Table final : public Element, public internal::Table { -public: - using Element::Element; - - [[nodiscard]] TableStyle style(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions - dimensions(const abstract::Document *) const override; -}; - -class TableColumn final : public Element, public abstract::TableColumn { -public: - using Element::Element; - - [[nodiscard]] TableColumnStyle - style(const abstract::Document *) const override; -}; - -class TableRow final : public Element, public abstract::TableRow { -public: - using Element::Element; - - [[nodiscard]] TableRowStyle style(const abstract::Document *) const override; -}; - -class TableCell final : public Element, public abstract::TableCell { -public: - using Element::Element; - - [[nodiscard]] bool is_covered(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions span(const abstract::Document *) const override; - - [[nodiscard]] ValueType value_type(const abstract::Document *) const override; - - [[nodiscard]] TableCellStyle style(const abstract::Document *) const override; -}; - -class Frame final : public Element, public abstract::Frame { -public: - using Element::Element; - - [[nodiscard]] AnchorType - anchor_type(const abstract::Document *) const override; - - [[nodiscard]] std::optional - x(const abstract::Document *) const override; - [[nodiscard]] std::optional - y(const abstract::Document *) const override; - [[nodiscard]] std::optional - width(const abstract::Document *) const override; - [[nodiscard]] std::optional - height(const abstract::Document *) const override; - - [[nodiscard]] std::optional - z_index(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; -}; - -class Rect final : public Element, public abstract::Rect { -public: - using Element::Element; - - [[nodiscard]] std::string x(const abstract::Document *) const override; - [[nodiscard]] std::string y(const abstract::Document *) const override; - [[nodiscard]] std::string width(const abstract::Document *) const override; - [[nodiscard]] std::string height(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; -}; - -class Line final : public Element, public abstract::Line { -public: - using Element::Element; - - [[nodiscard]] std::string x1(const abstract::Document *) const override; - [[nodiscard]] std::string y1(const abstract::Document *) const override; - [[nodiscard]] std::string x2(const abstract::Document *) const override; - [[nodiscard]] std::string y2(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; -}; - -class Circle final : public Element, public abstract::Circle { -public: - using Element::Element; - - [[nodiscard]] std::string x(const abstract::Document *) const override; - [[nodiscard]] std::string y(const abstract::Document *) const override; - [[nodiscard]] std::string width(const abstract::Document *) const override; - [[nodiscard]] std::string height(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; -}; - -class CustomShape final : public Element, public abstract::CustomShape { -public: - using Element::Element; - - [[nodiscard]] std::optional - x(const abstract::Document *) const override; - [[nodiscard]] std::optional - y(const abstract::Document *) const override; - [[nodiscard]] std::string width(const abstract::Document *) const override; - [[nodiscard]] std::string height(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; -}; - -class Image final : public Element, public abstract::Image { -public: - using Element::Element; - - [[nodiscard]] bool is_internal(const abstract::Document *) const override; - - [[nodiscard]] std::optional - file(const abstract::Document *) const override; - - [[nodiscard]] std::string href(const abstract::Document *) const override; -}; - -} // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_element_registry.cpp b/src/odr/internal/odf/odf_element_registry.cpp new file mode 100644 index 00000000..74d5b3b2 --- /dev/null +++ b/src/odr/internal/odf/odf_element_registry.cpp @@ -0,0 +1,363 @@ +#include + +#include + +#include + +namespace odr::internal::odf { + +void ElementRegistry::clear() noexcept { + m_elements.clear(); + m_tables.clear(); + m_texts.clear(); + m_sheets.clear(); +} + +[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { + return m_elements.size(); +} + +std::tuple +ElementRegistry::create_element(const ElementType type, + const pugi::xml_node node) { + Element &element = m_elements.emplace_back(); + ElementIdentifier element_id = m_elements.size(); + element.type = type; + element.node = node; + return {element_id, element}; +} + +std::tuple +ElementRegistry::create_text_element(const pugi::xml_node first_node, + const pugi::xml_node last_node) { + const auto &[element_id, element] = + create_element(ElementType::text, first_node); + auto [it, success] = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_table_element(const pugi::xml_node node) { + const auto &[element_id, element] = create_element(ElementType::table, node); + auto [it, success] = m_tables.emplace(element_id, Table{}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_sheet_element(const pugi::xml_node node) { + const auto &[element_id, element] = create_element(ElementType::sheet, node); + auto [it, success] = m_sheets.emplace(element_id, Sheet{}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_sheet_cell_element(const pugi::xml_node node, + const TablePosition &position, + bool is_repeated) { + const auto &[element_id, element] = + create_element(ElementType::sheet_cell, node); + auto [it, success] = m_sheet_cells.emplace( + element_id, SheetCell{.position = position, .is_repeated = is_repeated}); + return {element_id, element, it->second}; +} + +ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) { + check_element_id(id); + return m_elements.at(id - 1); +} + +ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) { + check_table_id(id); + return m_tables.at(id); +} + +ElementRegistry::Sheet & +ElementRegistry::sheet_element_at(const ElementIdentifier id) { + check_sheet_id(id); + return m_sheets.at(id); +} + +const ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id - 1); +} + +const ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) const { + check_table_id(id); + return m_tables.at(id); +} + +const ElementRegistry::Sheet & +ElementRegistry::sheet_element_at(const ElementIdentifier id) const { + check_sheet_id(id); + return m_sheets.at(id); +} + +ElementRegistry::Element *ElementRegistry::element(const ElementIdentifier id) { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Element * +ElementRegistry::element(const ElementIdentifier id) const { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +const ElementRegistry::Table * +ElementRegistry::table_element(const ElementIdentifier id) const { + if (const auto it = m_tables.find(id); it != m_tables.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) const { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet * +ElementRegistry::sheet_element(const ElementIdentifier id) const { + if (const auto it = m_sheets.find(id); it != m_sheets.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::SheetCell * +ElementRegistry::sheet_cell_element(const ElementIdentifier id) const { + if (const auto it = m_sheet_cells.find(id); it != m_sheet_cells.end()) { + return &it->second; + } + return nullptr; +} + +void ElementRegistry::append_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + if (element_at(child_id).parent_id != null_element_id) { + throw std::invalid_argument( + "DocumentElementRegistry::append_child: child already has a parent"); + } + + const ElementIdentifier previous_sibling_id = + element_at(parent_id).last_child_id; + + element_at(child_id).parent_id = parent_id; + element_at(child_id).previous_sibling_id = previous_sibling_id; + + if (element_at(parent_id).first_child_id == null_element_id) { + element_at(parent_id).first_child_id = child_id; + } else { + element_at(previous_sibling_id).next_sibling_id = child_id; + } + element_at(parent_id).last_child_id = child_id; +} + +void ElementRegistry::append_column(const ElementIdentifier table_id, + const ElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + if (element_at(column_id).parent_id != null_element_id) { + throw std::invalid_argument( + "DocumentElementRegistry::append_column: child already has a parent"); + } + + const ElementIdentifier previous_sibling_id = + table_element_at(table_id).last_column_id; + + element_at(column_id).parent_id = table_id; + element_at(column_id).previous_sibling_id = previous_sibling_id; + + if (table_element_at(table_id).first_column_id == null_element_id) { + table_element_at(table_id).first_column_id = column_id; + } else { + element_at(previous_sibling_id).next_sibling_id = column_id; + } + table_element_at(table_id).last_column_id = column_id; +} + +void ElementRegistry::append_shape(const ElementIdentifier sheet_id, + const ElementIdentifier shape_id) { + check_sheet_id(sheet_id); + check_element_id(shape_id); + if (element_at(shape_id).parent_id != null_element_id) { + throw std::invalid_argument( + "DocumentElementRegistry::append_shape: child already has a parent"); + } + + const ElementIdentifier previous_sibling_id = + sheet_element_at(sheet_id).last_shape_id; + + element_at(shape_id).parent_id = sheet_id; + element_at(shape_id).previous_sibling_id = previous_sibling_id; + + if (sheet_element_at(sheet_id).first_shape_id == null_element_id) { + sheet_element_at(sheet_id).first_shape_id = shape_id; + } else { + element_at(previous_sibling_id).next_sibling_id = shape_id; + } + sheet_element_at(sheet_id).last_shape_id = shape_id; +} + +void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, + const ElementIdentifier cell_id) { + check_sheet_id(sheet_id); + check_element_id(cell_id); + if (element_at(cell_id).parent_id != null_element_id) { + throw std::invalid_argument("DocumentElementRegistry::append_sheet_cell: " + "child already has a parent"); + } + + element_at(cell_id).parent_id = sheet_id; +} + +void ElementRegistry::check_element_id(const ElementIdentifier id) const { + if (id == null_element_id) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_cell_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_sheet_cells.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::Sheet::register_column(const std::uint32_t column, + const std::uint32_t repeated, + const pugi::xml_node element) { + columns[column + repeated] = {.node = element}; +} + +void ElementRegistry::Sheet::register_row(const std::uint32_t row, + const std::uint32_t repeated, + const pugi::xml_node element) { + rows[row + repeated].node = element; +} + +void ElementRegistry::Sheet::register_cell(const std::uint32_t column, + const std::uint32_t row, + const std::uint32_t columns_repeated, + const std::uint32_t rows_repeated, + const pugi::xml_node element, + const ElementIdentifier element_id) { + Cell &cell = rows[row + rows_repeated].cells[column + columns_repeated]; + cell.node = element; + cell.element_id = element_id; +} + +const ElementRegistry::Sheet::Column * +ElementRegistry::Sheet::column(const std::uint32_t column) const { + if (const auto it = util::map::lookup_greater_than(columns, column); + it != std::end(columns)) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet::Row * +ElementRegistry::Sheet::row(const std::uint32_t row) const { + if (const auto it = util::map::lookup_greater_than(rows, row); + it != std::end(rows)) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet::Cell * +ElementRegistry::Sheet::cell(const std::uint32_t column, + const std::uint32_t row) const { + if (const Row *row_entry = this->row(row); row_entry != nullptr) { + const auto &cells = row_entry->cells; + if (const auto cell_it = util::map::lookup_greater_than(cells, column); + cell_it != std::end(cells)) { + return &cell_it->second; + } + } + return nullptr; +} + +[[nodiscard]] pugi::xml_node +ElementRegistry::Sheet::column_node(const std::uint32_t column) const { + if (const Column *column_entry = this->column(column); + column_entry != nullptr) { + return column_entry->node; + } + return {}; +} + +[[nodiscard]] pugi::xml_node +ElementRegistry::Sheet::row_node(const std::uint32_t row) const { + if (const Row *row_entry = this->row(row); row_entry != nullptr) { + return row_entry->node; + } + return {}; +} + +[[nodiscard]] pugi::xml_node +ElementRegistry::Sheet::cell_node(const std::uint32_t column, + const std::uint32_t row) const { + if (const Cell *cell_entry = this->cell(column, row); cell_entry != nullptr) { + return cell_entry->node; + } + return {}; +} + +} // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_element_registry.hpp b/src/odr/internal/odf/odf_element_registry.hpp new file mode 100644 index 00000000..a3e52940 --- /dev/null +++ b/src/odr/internal/odf/odf_element_registry.hpp @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::odf { + +class ElementRegistry final { +public: + struct Element final { + ElementIdentifier parent_id{null_element_id}; + ElementIdentifier first_child_id{null_element_id}; + ElementIdentifier last_child_id{null_element_id}; + ElementIdentifier previous_sibling_id{null_element_id}; + ElementIdentifier next_sibling_id{null_element_id}; + ElementType type{ElementType::none}; + pugi::xml_node node; + }; + + struct Table final { + ElementIdentifier first_column_id{null_element_id}; + ElementIdentifier last_column_id{null_element_id}; + }; + + struct Text final { + pugi::xml_node last; + }; + + struct Sheet final { + struct Column final { + pugi::xml_node node; + }; + + struct Cell final { + pugi::xml_node node; + ElementIdentifier element_id{null_element_id}; + }; + + struct Row final { + pugi::xml_node node; + std::map cells; + }; + + TableDimensions dimensions; + + std::map columns; + std::map rows; + + ElementIdentifier first_shape_id{null_element_id}; + ElementIdentifier last_shape_id{null_element_id}; + + void register_column(std::uint32_t column, std::uint32_t repeated, + pugi::xml_node element); + void register_row(std::uint32_t row, std::uint32_t repeated, + pugi::xml_node element); + void register_cell(std::uint32_t column, std::uint32_t row, + std::uint32_t columns_repeated, + std::uint32_t rows_repeated, pugi::xml_node element, + ElementIdentifier element_id); + + [[nodiscard]] const Column *column(std::uint32_t column) const; + [[nodiscard]] const Row *row(std::uint32_t row) const; + [[nodiscard]] const Cell *cell(std::uint32_t column, + std::uint32_t row) const; + + [[nodiscard]] pugi::xml_node column_node(std::uint32_t column) const; + [[nodiscard]] pugi::xml_node row_node(std::uint32_t row) const; + [[nodiscard]] pugi::xml_node cell_node(std::uint32_t column, + std::uint32_t row) const; + }; + + struct SheetCell final { + TablePosition position; + bool is_repeated{false}; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + std::tuple create_element(ElementType type, + pugi::xml_node node); + std::tuple + create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); + std::tuple + create_table_element(pugi::xml_node node); + std::tuple + create_sheet_element(pugi::xml_node node); + std::tuple + create_sheet_cell_element(pugi::xml_node node, const TablePosition &position, + bool is_repeated); + + [[nodiscard]] Element &element_at(ElementIdentifier id); + [[nodiscard]] Table &table_element_at(ElementIdentifier id); + [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); + + [[nodiscard]] const Element &element_at(ElementIdentifier id) const; + [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; + [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; + + [[nodiscard]] Element *element(ElementIdentifier id); + [[nodiscard]] Text *text_element(ElementIdentifier id); + + [[nodiscard]] const Element *element(ElementIdentifier id) const; + [[nodiscard]] const Text *text_element(ElementIdentifier id) const; + [[nodiscard]] const Table *table_element(ElementIdentifier id) const; + [[nodiscard]] const Sheet *sheet_element(ElementIdentifier id) const; + [[nodiscard]] const SheetCell *sheet_cell_element(ElementIdentifier id) const; + + void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + void append_column(ElementIdentifier table_id, ElementIdentifier column_id); + void append_shape(ElementIdentifier sheet_id, ElementIdentifier shape_id); + void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); + +private: + std::vector m_elements; + std::unordered_map m_texts; + std::unordered_map m_tables; + std::unordered_map m_sheets; + std::unordered_map m_sheet_cells; + + void check_element_id(ElementIdentifier id) const; + void check_table_id(ElementIdentifier id) const; + void check_text_id(ElementIdentifier id) const; + void check_sheet_id(ElementIdentifier id) const; + void check_sheet_cell_id(ElementIdentifier id) const; +}; + +} // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_parser.cpp b/src/odr/internal/odf/odf_parser.cpp index bbffb659..56e5b2db 100644 --- a/src/odr/internal/odf/odf_parser.cpp +++ b/src/odr/internal/odf/odf_parser.cpp @@ -1,14 +1,55 @@ #include -#include -#include +#include +#include #include +#include + namespace odr::internal::odf { namespace { +using TreeParser = std::function( + ElementRegistry ®istry, pugi::xml_node node)>; +using ChildrenParser = + std::function; + +std::tuple +parse_any_element_tree(ElementRegistry ®istry, pugi::xml_node node); + +void parse_any_element_children(ElementRegistry ®istry, + const ElementIdentifier parent_id, + const pugi::xml_node node) { + for (pugi::xml_node child_node = node.first_child(); child_node;) { + if (auto [child_id, next_sibling] = + parse_any_element_tree(registry, child_node); + child_id == null_element_id) { + child_node = child_node.next_sibling(); + } else { + registry.append_child(parent_id, child_id); + child_node = next_sibling; + } + } +} + +std::tuple +parse_element_tree(ElementRegistry ®istry, const ElementType type, + const pugi::xml_node node, + const ChildrenParser &children_parser) { + if (!node) { + return {null_element_id, pugi::xml_node()}; + } + + const auto &[element_id, _] = registry.create_element(type, node); + + children_parser(registry, element_id, node); + + return {element_id, node.next_sibling()}; +} + bool is_text_node(const pugi::xml_node node) { if (!node) { return false; @@ -29,188 +70,320 @@ bool is_text_node(const pugi::xml_node node) { return false; } -} // namespace - -} // namespace odr::internal::odf - -namespace odr::internal { - -odf::Element *odf::parse_tree(Document &document, const pugi::xml_node node) { - auto [root, _] = parse_any_element_tree(document, node); - return root; -} - -void odf::parse_element_children(Document &document, Element *element, - const pugi::xml_node node) { - for (auto child_node = node.first_child(); child_node;) { - if (auto [child, next_sibling] = - parse_any_element_tree(document, child_node); - child == nullptr) { - child_node = child_node.next_sibling(); - } else { - element->append_child_(child); - child_node = next_sibling; - } +std::tuple +parse_text_element(ElementRegistry ®istry, const pugi::xml_node first) { + if (!first) { + return {null_element_id, pugi::xml_node()}; } -} -void odf::parse_element_children(Document &document, PresentationRoot *element, - const pugi::xml_node node) { - for (const pugi::xml_node child_node : node.children("draw:page")) { - auto [child, _] = parse_element_tree(document, child_node); - element->append_child_(child); + pugi::xml_node last = first; + for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { } -} -void odf::parse_element_children(Document &document, SpreadsheetRoot *element, - const pugi::xml_node node) { - for (const pugi::xml_node child_node : node.children("table:table")) { - auto [child, _] = parse_element_tree(document, child_node); - element->append_child_(child); - } -} + const auto &[element_id, _, __] = registry.create_text_element(first, last); -void odf::parse_element_children(Document &document, DrawingRoot *element, - const pugi::xml_node node) { - for (const pugi::xml_node child_node : node.children("draw:page")) { - auto [child, _] = parse_element_tree(document, child_node); - element->append_child_(child); - } + return {element_id, last.next_sibling()}; } -template <> -std::tuple -odf::parse_element_tree(Document &document, pugi::xml_node node) { +std::tuple +parse_table_row(ElementRegistry ®istry, const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - pugi::xml_node last = node; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { - } + const auto &[element_id, _] = + registry.create_element(ElementType::table_row, node); - auto element_unique = std::make_unique(node, last); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + for (const pugi::xml_node cell_node : node.children()) { + // TODO log warning if repeated + auto [cell, _] = parse_any_element_tree(registry, cell_node); + registry.append_child(element_id, cell); + } - return std::make_tuple(element, last.next_sibling()); + return {element_id, node.next_sibling()}; } -template <> -std::tuple -odf::parse_element_tree(Document &document, pugi::xml_node node) { +std::tuple +parse_table(ElementRegistry ®istry, const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto table_unique = std::make_unique(node); - auto table = table_unique.get(); - document.register_element_(std::move(table_unique)); + const auto &[element_id, _, table] = registry.create_table_element(node); // TODO inflate table first? - for (auto column_node : node.children("table:table-column")) { - for (std::uint32_t i = 0; - i < column_node.attribute("table:number-columns-repeated").as_uint(1); - ++i) { + for (const pugi::xml_node column_node : node.children("table:table-column")) { + const std::uint32_t repeat = + column_node.attribute("table:number-columns-repeated").as_uint(1); + for (std::uint32_t i = 0; i < repeat; ++i) { // TODO mark as repeated - auto [column, _] = parse_element_tree(document, column_node); - table->append_column_(column); + auto [column_id, _] = parse_any_element_tree(registry, column_node); + registry.append_column(element_id, column_id); } } for (const pugi::xml_node row_node : node.children("table:table-row")) { // TODO log warning if repeated - auto [row, _] = parse_element_tree(document, row_node); - table->append_row_(row); + auto [row_id, _] = parse_any_element_tree(registry, row_node); + registry.append_child(element_id, row_id); } - return std::make_tuple(table, node.next_sibling()); + return {element_id, node.next_sibling()}; } -template <> -std::tuple -odf::parse_element_tree(Document &document, - const pugi::xml_node node) { +std::tuple +parse_sheet(ElementRegistry ®istry, const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto table_row_unique = std::make_unique(node); - auto table_row = table_row_unique.get(); - document.register_element_(std::move(table_row_unique)); + const auto &[element_id, _, sheet] = registry.create_sheet_element(node); - for (const pugi::xml_node cell_node : node.children()) { - // TODO log warning if repeated - auto [cell, _] = parse_any_element_tree(document, cell_node); - table_row->append_child_(cell); + TableCursor cursor; + + for (const pugi::xml_node column_node : node.children("table:table-column")) { + const std::uint32_t columns_repeated = + column_node.attribute("table:number-columns-repeated").as_uint(1); + + sheet.register_column(cursor.column(), columns_repeated, column_node); + + cursor.add_column(columns_repeated); + } + + sheet.dimensions.columns = cursor.column(); + cursor = {}; + + for (const pugi::xml_node row_node : node.children("table:table-row")) { + const std::uint32_t rows_repeated = + row_node.attribute("table:number-rows-repeated").as_uint(1); + + sheet.register_row(cursor.row(), rows_repeated, row_node); + + // TODO covered cells + bool row_empty = true; + for (const pugi::xml_node cell_node : + row_node.children("table:table-cell")) { + const std::uint32_t colspan = + cell_node.attribute("table:number-columns-spanned").as_uint(1); + const std::uint32_t rowspan = + cell_node.attribute("table:number-rows-spanned").as_uint(1); + + bool cell_empty = true; + if (cell_node.first_child()) { + cell_empty = false; + } + if (colspan > 1 || rowspan > 1) { + cell_empty = false; + } + + if (!cell_empty) { + row_empty = false; + break; + } + } + + if (row_empty) { + sheet.register_row(cursor.row(), rows_repeated, row_node); + + // TODO covered cells + for (const pugi::xml_node cell_node : + row_node.children("table:table-cell")) { + const std::uint32_t columns_repeated = + cell_node.attribute("table:number-columns-repeated").as_uint(1); + const std::uint32_t colspan = + cell_node.attribute("table:number-columns-spanned").as_uint(1); + const std::uint32_t rowspan = + cell_node.attribute("table:number-rows-spanned").as_uint(1); + + sheet.register_cell(cursor.column(), cursor.row(), columns_repeated, + rows_repeated, cell_node, null_element_id); + + cursor.add_cell(colspan, rowspan, columns_repeated); + } + + cursor.add_row(rows_repeated); + continue; + } + + for (std::uint32_t row_repeat = 0; row_repeat < rows_repeated; + ++row_repeat) { + sheet.register_row(cursor.row(), 1, row_node); + + // TODO covered cells + for (const pugi::xml_node cell_node : + row_node.children("table:table-cell")) { + const std::uint32_t columns_repeated = + cell_node.attribute("table:number-columns-repeated").as_uint(1); + const std::uint32_t colspan = + cell_node.attribute("table:number-columns-spanned").as_uint(1); + const std::uint32_t rowspan = + cell_node.attribute("table:number-rows-spanned").as_uint(1); + const bool is_repeated = columns_repeated > 1 || rows_repeated > 1; + + bool cell_empty = true; + if (cell_node.first_child()) { + cell_empty = false; + } + if (colspan > 1 || rowspan > 1) { + cell_empty = false; + } + + if (cell_empty) { + sheet.register_cell(cursor.column(), cursor.row(), columns_repeated, + 1, cell_node, null_element_id); + + cursor.add_cell(colspan, rowspan, columns_repeated); + continue; + } + + for (std::uint32_t column_repeat = 0; column_repeat < columns_repeated; + ++column_repeat) { + const auto &[cell_id, _, __] = registry.create_sheet_cell_element( + cell_node, cursor.position(), is_repeated); + registry.append_sheet_cell(element_id, cell_id); + sheet.register_cell(cursor.column(), cursor.row(), 1, 1, cell_node, + cell_id); + parse_any_element_children(registry, cell_id, cell_node); + + cursor.add_cell(colspan, rowspan, 1); + } + } + + cursor.add_row(1); + } + } + + sheet.dimensions.rows = cursor.row(); + + for (const pugi::xml_node shape_node : + node.child("table:shapes").children()) { + if (auto [shape_id, _] = parse_any_element_tree(registry, shape_node); + shape_id != null_element_id) { + registry.append_shape(element_id, shape_id); + } } - return std::make_tuple(table_row, node.next_sibling()); + return {element_id, node.next_sibling()}; } -std::tuple -odf::parse_any_element_tree(Document &document, const pugi::xml_node node) { - using Parser = std::function( - Document & document, pugi::xml_node node)>; - - using List = DefaultElement; - using Group = DefaultElement; - using PageBreak = DefaultElement; - - static std::unordered_map parser_table{ - {"office:text", parse_element_tree}, - {"office:presentation", parse_element_tree}, - {"office:spreadsheet", parse_element_tree}, - {"office:drawing", parse_element_tree}, - {"text:p", parse_element_tree}, - {"text:h", parse_element_tree}, - {"text:span", parse_element_tree}, - {"text:s", parse_element_tree}, - {"text:tab", parse_element_tree}, - {"text:line-break", parse_element_tree}, - {"text:a", parse_element_tree}, - {"text:bookmark", parse_element_tree}, - {"text:bookmark-start", parse_element_tree}, - {"text:list", parse_element_tree}, - {"text:list-header", parse_element_tree}, - {"text:list-item", parse_element_tree}, - {"text:index-title", parse_element_tree}, - {"text:table-of-content", parse_element_tree}, - {"text:illustration-index", parse_element_tree}, - {"text:index-body", parse_element_tree}, - {"text:soft-page-break", parse_element_tree}, - {"text:date", parse_element_tree}, - {"text:time", parse_element_tree}, - {"text:section", parse_element_tree}, - //{"text:page-number", parse_element_tree}, - //{"text:page-continuation", parse_element_tree}, - {"table:table", parse_element_tree
}, - {"table:table-column", parse_element_tree}, - {"table:table-row", parse_element_tree}, - {"table:table-cell", parse_element_tree}, - {"table:covered-table-cell", parse_element_tree}, - {"draw:frame", parse_element_tree}, - {"draw:image", parse_element_tree}, - {"draw:rect", parse_element_tree}, - {"draw:line", parse_element_tree}, - {"draw:circle", parse_element_tree}, - {"draw:custom-shape", parse_element_tree}, - {"draw:text-box", parse_element_tree}, - {"draw:g", parse_element_tree}, - {"draw:a", parse_element_tree}, - {"style:master-page", parse_element_tree}}; +void parse_presentation_children(ElementRegistry ®istry, + const ElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : node.children("draw:page")) { + auto [child_id, _] = parse_element_tree( + registry, ElementType::slide, child_node, parse_any_element_children); + registry.append_child(root_id, child_id); + } +} + +void parse_spreadsheet_children(ElementRegistry ®istry, + const ElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : node.children("table:table")) { + auto [child_id, _] = parse_sheet(registry, child_node); + registry.append_child(root_id, child_id); + } +} + +void parse_drawing_children(ElementRegistry ®istry, + const ElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : node.children("draw:page")) { + auto [child_id, _] = parse_element_tree( + registry, ElementType::page, child_node, parse_any_element_children); + registry.append_child(root_id, child_id); + } +} + +std::tuple +parse_any_element_tree(ElementRegistry ®istry, const pugi::xml_node node) { + const auto create_default_tree_parser = + [](const ElementType type, + const ChildrenParser &children_parser = parse_any_element_children) { + return [type, children_parser](ElementRegistry &r, + const pugi::xml_node n) { + return parse_element_tree(r, type, n, children_parser); + }; + }; + + static std::unordered_map parser_table{ + {"office:text", create_default_tree_parser(ElementType::root)}, + {"office:presentation", + create_default_tree_parser(ElementType::root, + parse_presentation_children)}, + {"office:spreadsheet", + create_default_tree_parser(ElementType::root, + parse_spreadsheet_children)}, + {"office:drawing", + create_default_tree_parser(ElementType::root, parse_drawing_children)}, + {"text:p", create_default_tree_parser(ElementType::paragraph)}, + {"text:h", create_default_tree_parser(ElementType::paragraph)}, + {"text:span", create_default_tree_parser(ElementType::span)}, + {"text:s", parse_text_element}, + {"text:tab", parse_text_element}, + {"text:line-break", create_default_tree_parser(ElementType::line_break)}, + {"text:a", create_default_tree_parser(ElementType::link)}, + {"text:bookmark", create_default_tree_parser(ElementType::bookmark)}, + {"text:bookmark-start", + create_default_tree_parser(ElementType::bookmark)}, + {"text:list", create_default_tree_parser(ElementType::list)}, + {"text:list-header", create_default_tree_parser(ElementType::list_item)}, + {"text:list-item", create_default_tree_parser(ElementType::list_item)}, + {"text:index-title", create_default_tree_parser(ElementType::group)}, + {"text:table-of-content", create_default_tree_parser(ElementType::group)}, + {"text:illustration-index", + create_default_tree_parser(ElementType::group)}, + {"text:index-body", create_default_tree_parser(ElementType::group)}, + {"text:soft-page-break", + create_default_tree_parser(ElementType::page_break)}, + {"text:date", create_default_tree_parser(ElementType::group)}, + {"text:time", create_default_tree_parser(ElementType::group)}, + {"text:section", create_default_tree_parser(ElementType::group)}, + //{"text:page-number", create_tree_parser(ElementType::group)}, + //{"text:page-continuation", create_tree_parser(ElementType::group)}, + {"table:table", parse_table}, + {"table:table-column", + create_default_tree_parser(ElementType::table_column)}, + {"table:table-row", parse_table_row}, + {"table:table-cell", create_default_tree_parser(ElementType::table_cell)}, + {"table:covered-table-cell", + create_default_tree_parser(ElementType::table_cell)}, + {"draw:frame", create_default_tree_parser(ElementType::frame)}, + {"draw:image", create_default_tree_parser(ElementType::image)}, + {"draw:rect", create_default_tree_parser(ElementType::rect)}, + {"draw:line", create_default_tree_parser(ElementType::line)}, + {"draw:circle", create_default_tree_parser(ElementType::circle)}, + {"draw:custom-shape", + create_default_tree_parser(ElementType::custom_shape)}, + {"draw:text-box", create_default_tree_parser(ElementType::group)}, + {"draw:g", create_default_tree_parser(ElementType::frame)}, + {"draw:a", create_default_tree_parser(ElementType::link)}, + {"style:master-page", + create_default_tree_parser(ElementType::master_page)}}; if (node.type() == pugi::xml_node_type::node_pcdata) { - return parse_element_tree(document, node); + return parse_text_element(registry, node); } if (const auto constructor_it = parser_table.find(node.name()); constructor_it != std::end(parser_table)) { - return constructor_it->second(document, node); + return constructor_it->second(registry, node); } - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; +} + +} // namespace + +} // namespace odr::internal::odf + +namespace odr::internal { + +ElementIdentifier odf::parse_tree(ElementRegistry ®istry, + const pugi::xml_node node) { + auto [root, _] = parse_any_element_tree(registry, node); + return root; } } // namespace odr::internal diff --git a/src/odr/internal/odf/odf_parser.hpp b/src/odr/internal/odf/odf_parser.hpp index cb3aef78..41c6244c 100644 --- a/src/odr/internal/odf/odf_parser.hpp +++ b/src/odr/internal/odf/odf_parser.hpp @@ -1,58 +1,14 @@ #pragma once -#include +#include -#include -#include -#include +namespace pugi { +class xml_node; +} // namespace pugi namespace odr::internal::odf { -class Element; -class PresentationRoot; -class SpreadsheetRoot; -class DrawingRoot; -class Text; -class Table; -class TableRow; +class ElementRegistry; -Element *parse_tree(Document &document, pugi::xml_node node); - -template -std::tuple -parse_element_tree(Document &document, pugi::xml_node node, args_t &&...args) { - if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); - } - - auto element_unique = - std::make_unique(node, std::forward(args)...); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); - - parse_element_children(document, element, node); - - return std::make_tuple(element, node.next_sibling()); -} -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node); -template <> -std::tuple
-parse_element_tree
(Document &document, pugi::xml_node node); -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node); - -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node); - -void parse_element_children(Document &document, Element *element, - pugi::xml_node node); -void parse_element_children(Document &document, PresentationRoot *element, - pugi::xml_node node); -void parse_element_children(Document &document, SpreadsheetRoot *element, - pugi::xml_node node); -void parse_element_children(Document &document, DrawingRoot *element, - pugi::xml_node node); +ElementIdentifier parse_tree(ElementRegistry ®istry, pugi::xml_node node); } // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_spreadsheet.cpp b/src/odr/internal/odf/odf_spreadsheet.cpp deleted file mode 100644 index 168c5280..00000000 --- a/src/odr/internal/odf/odf_spreadsheet.cpp +++ /dev/null @@ -1,368 +0,0 @@ -#include - -#include -#include - -#include -#include - -namespace odr::internal::odf { - -void SheetIndex::init_column(const std::uint32_t column, - const std::uint32_t repeated, - const pugi::xml_node element) { - columns[column + repeated] = element; -} - -void SheetIndex::init_row(const std::uint32_t row, const std::uint32_t repeated, - const pugi::xml_node element) { - rows[row + repeated].row = element; -} - -void SheetIndex::init_cell(const std::uint32_t column, const std::uint32_t row, - const std::uint32_t columns_repeated, - const std::uint32_t rows_repeated, - const pugi::xml_node element) { - rows[row + rows_repeated].cells[column + columns_repeated] = element; -} - -pugi::xml_node SheetIndex::column(const std::uint32_t column) const { - if (const auto it = util::map::lookup_greater_than(columns, column); - it != std::end(columns)) { - return it->second; - } - return {}; -} - -pugi::xml_node SheetIndex::row(const std::uint32_t row) const { - if (const auto it = util::map::lookup_greater_than(rows, row); - it != std::end(rows)) { - return it->second.row; - } - return {}; -} - -pugi::xml_node SheetIndex::cell(const std::uint32_t column, - const std::uint32_t row) const { - if (const auto row_it = util::map::lookup_greater_than(rows, row); - row_it != std::end(rows)) { - const auto &cells = row_it->second.cells; - if (const auto cell_it = util::map::lookup_greater_than(cells, column); - cell_it != std::end(cells)) { - return cell_it->second; - } - } - return {}; -} - -class SheetCell final : public Element, public abstract::SheetCell { -public: - SheetCell(const pugi::xml_node node, const std::uint32_t column, - const std::uint32_t row, const bool is_repeated) - : Element(node), m_column{column}, m_row{row}, - m_is_repeated{is_repeated} {} - - [[nodiscard]] bool is_covered(const abstract::Document *) const override { - return std::strcmp(m_node.name(), "table:covered-table-cell") == 0; - } - - [[nodiscard]] TableDimensions - span(const abstract::Document *) const override { - return {m_node.attribute("table:number-rows-spanned").as_uint(1), - m_node.attribute("table:number-columns-spanned").as_uint(1)}; - } - - [[nodiscard]] ValueType - value_type(const abstract::Document *) const override { - if (const char *value_type = m_node.attribute("office:value-type").value(); - std::strcmp("float", value_type) == 0) { - return ValueType::float_number; - } - return ValueType::string; - } - - ResolvedStyle - partial_style(const abstract::Document *document) const override { - const auto *sheet = dynamic_cast(parent(document)); - return sheet->cell_style_(document, m_column, m_row); - } - - ResolvedStyle - intermediate_style(const abstract::Document *document) const override { - return partial_style(document); - } - - [[nodiscard]] TableCellStyle - style(const abstract::Document *document) const override { - return intermediate_style(document).table_cell_style; - } - - bool is_editable(const abstract::Document *) const override { - return !m_is_repeated; - } - -private: - std::uint32_t m_column{}; - std::uint32_t m_row{}; - bool m_is_repeated{}; -}; - -std::string Sheet::name(const abstract::Document *) const { - return m_node.attribute("table:name").value(); -} - -TableDimensions Sheet::dimensions(const abstract::Document *) const { - return m_index.dimensions; -} - -TableDimensions -Sheet::content(const abstract::Document *, - const std::optional range) const { - TableDimensions result; - - TableCursor cursor; - for (auto row : m_node.children("table:table-row")) { - const auto rows_repeated = - row.attribute("table:number-rows-repeated").as_uint(1); - cursor.add_row(rows_repeated); - - for (auto cell : row.children("table:table-cell")) { - const auto columns_repeated = - cell.attribute("table:number-columns-repeated").as_uint(1); - const auto colspan = - cell.attribute("table:number-columns-spanned").as_uint(1); - const auto rowspan = - cell.attribute("table:number-rows-spanned").as_uint(1); - cursor.add_cell(colspan, rowspan, columns_repeated); - - const std::uint32_t new_rows = cursor.row(); - if (const std::uint32_t new_cols = - std::max(result.columns, cursor.column()); - cell.first_child() && range && new_rows < range->rows && - new_cols < range->columns) { - result.rows = new_rows; - result.columns = new_cols; - } - } - } - - return result; -} - -ResolvedStyle Sheet::cell_style_(const abstract::Document *document, - const std::uint32_t column, - const std::uint32_t row) const { - const char *style_name = nullptr; - - const pugi::xml_node cell_node = m_index.cell(column, row); - if (const pugi::xml_attribute attr = - cell_node.attribute("table:style-name")) { - style_name = attr.value(); - } - - if (style_name == nullptr) { - const pugi::xml_node row_node = m_index.row(row); - if (const pugi::xml_attribute attr = - row_node.attribute("table:default-cell-style-name")) { - style_name = attr.value(); - } - } - if (style_name == nullptr) { - const pugi::xml_node column_node = m_index.column(column); - if (const pugi::xml_attribute attr = - column_node.attribute("table:default-cell-style-name")) { - style_name = attr.value(); - } - } - - if (style_name != nullptr) { - if (const Style *style = style_(document)->style(style_name); - style != nullptr) { - return style->resolved(); - } - } - - return {}; -} - -abstract::SheetCell *Sheet::cell(const abstract::Document *, - const std::uint32_t column, - const std::uint32_t row) const { - if (const auto cell_it = m_cells.find({column, row}); - cell_it != std::end(m_cells)) { - return cell_it->second; - } - return nullptr; -} - -abstract::Element *Sheet::first_shape(const abstract::Document *) const { - return m_first_shape; -} - -TableStyle Sheet::style(const abstract::Document *document) const { - return partial_style(document).table_style; -} - -TableColumnStyle Sheet::column_style(const abstract::Document *document, - const std::uint32_t column) const { - if (const pugi::xml_node column_node = m_index.column(column); column_node) { - if (const pugi::xml_attribute attr = - column_node.attribute("table:style-name")) { - if (const Style *style = style_(document)->style(attr.value()); - style != nullptr) { - return style->resolved().table_column_style; - } - } - } - return {}; -} - -TableRowStyle Sheet::row_style(const abstract::Document *document, - const std::uint32_t row) const { - if (const pugi::xml_node column_node = m_index.row(row); column_node) { - if (const pugi::xml_attribute attr = - column_node.attribute("table:style-name")) { - if (const Style *style = style_(document)->style(attr.value()); - style != nullptr) { - return style->resolved().table_row_style; - } - } - } - return {}; -} - -TableCellStyle Sheet::cell_style(const abstract::Document *document, - const std::uint32_t column, - const std::uint32_t row) const { - return cell_style_(document, column, row).table_cell_style; -} - -void Sheet::init_column_(const std::uint32_t column, - const std::uint32_t repeated, - const pugi::xml_node element) { - m_index.init_column(column, repeated, element); -} - -void Sheet::init_row_(const std::uint32_t row, const std::uint32_t repeated, - const pugi::xml_node element) { - m_index.init_row(row, repeated, element); -} - -void Sheet::init_cell_(const std::uint32_t column, const std::uint32_t row, - const std::uint32_t columns_repeated, - const std::uint32_t rows_repeated, - const pugi::xml_node element) { - m_index.init_cell(column, row, columns_repeated, rows_repeated, element); -} - -void Sheet::init_cell_element_(const std::uint32_t column, - const std::uint32_t row, SheetCell *element) { - m_cells[{column, row}] = element; - element->m_parent = this; -} - -void Sheet::init_dimensions_(const TableDimensions dimensions) { - m_index.dimensions = dimensions; -} - -void Sheet::append_shape_(Element *shape) { - shape->m_previous_sibling = m_last_shape; - shape->m_parent = this; - if (m_last_shape == nullptr) { - m_first_shape = shape; - } else { - m_last_shape->m_next_sibling = shape; - } - m_last_shape = shape; -} - -} // namespace odr::internal::odf - -namespace odr::internal { - -template <> -std::tuple -odf::parse_element_tree(Document &document, pugi::xml_node node) { - if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); - } - - auto sheet_unique = std::make_unique(node); - auto &sheet = *sheet_unique; - document.register_element_(std::move(sheet_unique)); - - TableDimensions dimensions; - TableCursor cursor; - - for (auto column_node : node.children("table:table-column")) { - const auto columns_repeated = - column_node.attribute("table:number-columns-repeated").as_uint(1); - - sheet.init_column_(cursor.column(), columns_repeated, column_node); - - cursor.add_column(columns_repeated); - } - - dimensions.columns = cursor.column(); - cursor = {}; - - for (auto row_node : node.children("table:table-row")) { - const auto rows_repeated = - row_node.attribute("table:number-rows-repeated").as_uint(1); - - sheet.init_row_(cursor.row(), rows_repeated, row_node); - - // TODO covered cells - for (auto cell_node : row_node.children("table:table-cell")) { - const auto columns_repeated = - cell_node.attribute("table:number-columns-repeated").as_uint(1); - const auto colspan = - cell_node.attribute("table:number-columns-spanned").as_uint(1); - const auto rowspan = - cell_node.attribute("table:number-rows-spanned").as_uint(1); - const bool is_repeated = columns_repeated > 1 || rows_repeated > 1; - - sheet.init_cell_(cursor.column(), cursor.row(), columns_repeated, - rows_repeated, cell_node); - - bool empty = false; - if (!cell_node.first_child()) { - empty = true; - } - - if (!empty) { - for (std::uint32_t row_repeat = 0; row_repeat < rows_repeated; - ++row_repeat) { - for (std::uint32_t column_repeat = 0; - column_repeat < columns_repeated; ++column_repeat) { - auto [cell, _] = parse_element_tree( - document, cell_node, cursor.column() + column_repeat, - cursor.row() + row_repeat, is_repeated); - sheet.init_cell_element_(cursor.column() + column_repeat, - cursor.row() + row_repeat, cell); - } - } - } - - cursor.add_cell(colspan, rowspan, columns_repeated); - } - - cursor.add_row(rows_repeated); - } - - dimensions.rows = cursor.row(); - - sheet.init_dimensions_(dimensions); - - for (const pugi::xml_node shape_node : - node.child("table:shapes").children()) { - if (auto [shape, _] = parse_any_element_tree(document, shape_node); - shape != nullptr) { - sheet.append_shape_(shape); - } - } - - return std::make_tuple(&sheet, node.next_sibling()); -} - -} // namespace odr::internal diff --git a/src/odr/internal/odf/odf_spreadsheet.hpp b/src/odr/internal/odf/odf_spreadsheet.hpp deleted file mode 100644 index 574f201c..00000000 --- a/src/odr/internal/odf/odf_spreadsheet.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -namespace pugi { -class xml_node; -} - -namespace odr::internal::odf { -class Document; -class SheetCell; - -class SpreadsheetRoot final : public Root { -public: - using Root::Root; -}; - -struct SheetIndex final { - struct Row { - pugi::xml_node row; - std::map cells; - }; - - TableDimensions dimensions; - - std::map columns; - std::map rows; - - void init_column(std::uint32_t column, std::uint32_t repeated, - pugi::xml_node element); - void init_row(std::uint32_t row, std::uint32_t repeated, - pugi::xml_node element); - void init_cell(std::uint32_t column, std::uint32_t row, - std::uint32_t columns_repeated, std::uint32_t rows_repeated, - pugi::xml_node element); - - [[nodiscard]] pugi::xml_node column(std::uint32_t) const; - [[nodiscard]] pugi::xml_node row(std::uint32_t) const; - [[nodiscard]] pugi::xml_node cell(std::uint32_t column, - std::uint32_t row) const; -}; - -class Sheet final : public Element, public abstract::Sheet { -public: - using Element::Element; - - [[nodiscard]] std::string name(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions - dimensions(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions - content(const abstract::Document *, - std::optional range) const override; - - abstract::SheetCell *cell(const abstract::Document *, std::uint32_t column, - std::uint32_t row) const override; - - [[nodiscard]] abstract::Element * - first_shape(const abstract::Document *) const override; - - [[nodiscard]] TableStyle style(const abstract::Document *) const override; - [[nodiscard]] TableColumnStyle - column_style(const abstract::Document *, std::uint32_t column) const override; - [[nodiscard]] TableRowStyle row_style(const abstract::Document *, - std::uint32_t row) const override; - [[nodiscard]] TableCellStyle cell_style(const abstract::Document *, - std::uint32_t column, - std::uint32_t row) const override; - - void init_column_(std::uint32_t column, std::uint32_t repeated, - pugi::xml_node element); - void init_row_(std::uint32_t row, std::uint32_t repeated, - pugi::xml_node element); - void init_cell_(std::uint32_t column, std::uint32_t row, - std::uint32_t columns_repeated, std::uint32_t rows_repeated, - pugi::xml_node element); - void init_cell_element_(std::uint32_t column, std::uint32_t row, - SheetCell *element); - void init_dimensions_(TableDimensions dimensions); - void append_shape_(Element *shape); - - ResolvedStyle cell_style_(const abstract::Document *, std::uint32_t column, - std::uint32_t row) const; - -private: - SheetIndex m_index; - - std::unordered_map m_cells; - Element *m_first_shape{nullptr}; - Element *m_last_shape{nullptr}; -}; - -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node); - -} // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_style.cpp b/src/odr/internal/odf/odf_style.cpp index 2a632cc4..4b28bcd7 100644 --- a/src/odr/internal/odf/odf_style.cpp +++ b/src/odr/internal/odf/odf_style.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -556,7 +557,7 @@ Style *StyleRegistry::generate_style_(const std::string &name, void StyleRegistry::generate_master_pages_(Document &document) { for (const auto &[name, node] : m_index_master_page) { m_master_page_elements[name] = - dynamic_cast(parse_tree(document, node)); + parse_tree(document.element_registry(), node); } if (m_first_master_page) { @@ -588,7 +589,7 @@ pugi::xml_node StyleRegistry::font_face_node(const std::string &name) const { return {}; } -MasterPage *StyleRegistry::master_page(const std::string &name) const { +ElementIdentifier StyleRegistry::master_page(const std::string &name) const { if (const auto master_page_elements_it = m_master_page_elements.find(name); master_page_elements_it != std::end(m_master_page_elements)) { return master_page_elements_it->second; @@ -596,7 +597,7 @@ MasterPage *StyleRegistry::master_page(const std::string &name) const { return {}; } -MasterPage *StyleRegistry::first_master_page() const { +ElementIdentifier StyleRegistry::first_master_page() const { return m_first_master_page_element; } diff --git a/src/odr/internal/odf/odf_style.hpp b/src/odr/internal/odf/odf_style.hpp index edbd24c2..fc3e87ab 100644 --- a/src/odr/internal/odf/odf_style.hpp +++ b/src/odr/internal/odf/odf_style.hpp @@ -1,7 +1,8 @@ #pragma once +#include + #include -#include #include #include @@ -9,6 +10,8 @@ #include #include +#include + namespace odr { struct PageLayout; struct GraphicStyle; @@ -73,8 +76,8 @@ class StyleRegistry final { [[nodiscard]] pugi::xml_node font_face_node(const std::string &name) const; - [[nodiscard]] MasterPage *master_page(const std::string &name) const; - [[nodiscard]] MasterPage *first_master_page() const; + [[nodiscard]] ElementIdentifier master_page(const std::string &name) const; + [[nodiscard]] ElementIdentifier first_master_page() const; private: std::unordered_map m_index_font_face; @@ -89,8 +92,8 @@ class StyleRegistry final { std::unordered_map> m_default_styles; std::unordered_map> m_styles; - std::unordered_map m_master_page_elements; - MasterPage *m_first_master_page_element{}; + std::unordered_map m_master_page_elements; + ElementIdentifier m_first_master_page_element{}; void generate_indices_(pugi::xml_node content_root, pugi::xml_node styles_root); diff --git a/src/odr/internal/ooxml/ooxml_file.cpp b/src/odr/internal/ooxml/ooxml_file.cpp index 4db64651..f39543e6 100644 --- a/src/odr/internal/ooxml/ooxml_file.cpp +++ b/src/odr/internal/ooxml/ooxml_file.cpp @@ -16,9 +16,9 @@ namespace odr::internal::ooxml { OfficeOpenXmlFile::OfficeOpenXmlFile( - std::shared_ptr filesystem) - : m_filesystem(std::move(filesystem)) { - m_file_meta = parse_file_meta(*m_filesystem); + std::shared_ptr files) + : m_files(std::move(files)) { + m_file_meta = parse_file_meta(*m_files); if (m_file_meta.password_encrypted) { m_encryption_state = EncryptionState::encrypted; @@ -65,8 +65,8 @@ OfficeOpenXmlFile::decrypt(const std::string &password) const { throw NotEncryptedError(); } - const std::string encryption_info = util::stream::read( - *m_filesystem->open(AbsPath("/EncryptionInfo"))->stream()); + const std::string encryption_info = + util::stream::read(*m_files->open(AbsPath("/EncryptionInfo"))->stream()); // TODO cache Crypto::Util const crypto::Util util(encryption_info); const std::string key = util.derive_key(password); @@ -75,15 +75,14 @@ OfficeOpenXmlFile::decrypt(const std::string &password) const { } const std::string encrypted_package = util::stream::read( - *m_filesystem->open(AbsPath("/EncryptedPackage"))->stream()); + *m_files->open(AbsPath("/EncryptedPackage"))->stream()); std::string decrypted_package = util.decrypt(encrypted_package, key); const auto memory_file = std::make_shared(std::move(decrypted_package)); auto decrypted = std::make_shared(*this); - decrypted->m_filesystem = - zip::ZipFile(memory_file).archive()->as_filesystem(); - decrypted->m_file_meta = parse_file_meta(*decrypted->m_filesystem); + decrypted->m_files = zip::ZipFile(memory_file).archive()->as_filesystem(); + decrypted->m_file_meta = parse_file_meta(*decrypted->m_files); decrypted->m_encryption_state = EncryptionState::decrypted; return decrypted; } @@ -99,11 +98,11 @@ std::shared_ptr OfficeOpenXmlFile::document() const { switch (file_type()) { case FileType::office_open_xml_document: - return std::make_shared(m_filesystem); + return std::make_shared(m_files); case FileType::office_open_xml_presentation: - return std::make_shared(m_filesystem); + return std::make_shared(m_files); case FileType::office_open_xml_workbook: - return std::make_shared(m_filesystem); + return std::make_shared(m_files); default: throw UnsupportedFileType(file_type()); } diff --git a/src/odr/internal/ooxml/ooxml_file.hpp b/src/odr/internal/ooxml/ooxml_file.hpp index 2b88b294..e166b575 100644 --- a/src/odr/internal/ooxml/ooxml_file.hpp +++ b/src/odr/internal/ooxml/ooxml_file.hpp @@ -20,7 +20,7 @@ namespace odr::internal::ooxml { class OfficeOpenXmlFile final : public abstract::DocumentFile { public: explicit OfficeOpenXmlFile( - std::shared_ptr storage); + std::shared_ptr files); [[nodiscard]] std::shared_ptr file() const noexcept override; @@ -42,7 +42,7 @@ class OfficeOpenXmlFile final : public abstract::DocumentFile { [[nodiscard]] std::shared_ptr document() const override; private: - std::shared_ptr m_filesystem; + std::shared_ptr m_files; FileMeta m_file_meta; EncryptionState m_encryption_state{EncryptionState::not_encrypted}; }; diff --git a/src/odr/internal/ooxml/ooxml_util.hpp b/src/odr/internal/ooxml/ooxml_util.hpp index 449c8626..b301d733 100644 --- a/src/odr/internal/ooxml/ooxml_util.hpp +++ b/src/odr/internal/ooxml/ooxml_util.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -47,6 +48,9 @@ std::optional read_border_node(pugi::xml_node); std::string read_text_property(pugi::xml_node); using Relations = std::unordered_map; +using XmlDocumentsAndRelations = + std::unordered_map>; +using SharedStrings = std::vector; std::unordered_map parse_relationships(const pugi::xml_document &relations); diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp index c7dbfebf..8d98f1a2 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp @@ -2,28 +2,45 @@ #include #include +#include +#include +#include #include +#include +#include #include #include #include +#include + namespace odr::internal::ooxml::presentation { -Document::Document(std::shared_ptr filesystem) - : TemplateDocument(FileType::office_open_xml_presentation, - DocumentType::presentation, - std::move(filesystem)) { - m_document_xml = - util::xml::parse(*m_filesystem, AbsPath("/ppt/presentation.xml")); +namespace { +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry); +} + +Document::Document(std::shared_ptr files) + : internal::Document(FileType::office_open_xml_presentation, + DocumentType::presentation, std::move(files)) { + m_document_xml = util::xml::parse(*m_files, AbsPath("/ppt/presentation.xml")); - for (const auto &relationships : - parse_relationships(*m_filesystem, AbsPath("/ppt/presentation.xml"))) { - m_slides_xml[relationships.first] = util::xml::parse( - *m_filesystem, AbsPath("/ppt").join(RelPath(relationships.second))); + for (const auto &[id, target] : + parse_relationships(*m_files, AbsPath("/ppt/presentation.xml"))) { + m_slides_xml[id] = + util::xml::parse(*m_files, AbsPath("/ppt").join(RelPath(target))); } - m_root_element = parse_tree(*this, m_document_xml.document_element()); + m_root_element = parse_tree(m_element_registry, + m_document_xml.document_element(), m_slides_xml); + + m_element_adapter = create_element_adapter(*this, m_element_registry); +} + +const ElementRegistry &Document::element_registry() const { + return m_element_registry; } bool Document::is_editable() const noexcept { return false; } @@ -40,11 +57,691 @@ void Document::save(const Path & /*path*/, const char * /*password*/) const { throw UnsupportedOperation(); } -pugi::xml_node Document::get_slide_root(const std::string &ref) const { - if (auto it = m_slides_xml.find(ref); it != std::end(m_slides_xml)) { - return it->second.document_element(); +namespace { + +void resolve_text_style_(const pugi::xml_node node, TextStyle &result) { + const pugi::xml_node run_properties = node.child("a:rPr"); + + if (const pugi::xml_attribute font_name = + run_properties.child("rFonts").attribute("ascii")) { + result.font_name = font_name.value(); + } + if (const std::optional font_size = + read_hundredth_point_attribute(run_properties.attribute("sz"))) { + result.font_size = font_size; + } + if (const std::optional font_weight = + read_font_weight_attribute(run_properties.attribute("b"))) { + result.font_weight = font_weight; + } + if (const std::optional font_style = + read_font_style_attribute(run_properties.attribute("i"))) { + result.font_style = font_style; + } + if (const bool font_underline = + read_line_attribute(run_properties.attribute("u"))) { + result.font_underline = font_underline; + } + if (const bool font_line_through = + read_line_attribute(run_properties.attribute("strike"))) { + result.font_line_through = font_line_through; + } + if (const std::optional font_shadow = + read_shadow_attribute(run_properties.attribute("shadow"))) { + result.font_shadow = font_shadow; + } + if (const std::optional font_color = + read_color_attribute(run_properties.attribute("color"))) { + result.font_color = font_color; + } + if (const std::optional background_color = + read_color_attribute(run_properties.attribute("highlight"))) { + result.background_color = background_color; + } +} + +void resolve_paragraph_style_(const pugi::xml_node node, + ParagraphStyle &result) { + const pugi::xml_node paragraph_properties = node.child("a:pPr"); + + if (const std::optional text_align = + read_text_align_attribute(paragraph_properties.attribute("jc"))) { + result.text_align = text_align; + } + if (const std::optional margin_left = read_twips_attribute( + paragraph_properties.child("ind").attribute("left"))) { + result.margin.left = margin_left; + } + if (const std::optional margin_left = read_twips_attribute( + paragraph_properties.child("ind").attribute("start"))) { + result.margin.left = margin_left; + } + if (const std::optional margin_right = read_twips_attribute( + paragraph_properties.child("ind").attribute("right"))) { + result.margin.right = margin_right; + } + if (const std::optional margin_right = read_twips_attribute( + paragraph_properties.child("ind").attribute("end"))) { + result.margin.right = margin_right; + } +} + +class ElementAdapter final : public abstract::ElementAdapter, + public abstract::SlideAdapter, + public abstract::LineBreakAdapter, + public abstract::ParagraphAdapter, + public abstract::SpanAdapter, + public abstract::TextAdapter, + public abstract::LinkAdapter, + public abstract::BookmarkAdapter, + public abstract::ListItemAdapter, + public abstract::TableAdapter, + public abstract::TableColumnAdapter, + public abstract::TableRowAdapter, + public abstract::TableCellAdapter, + public abstract::FrameAdapter, + public abstract::ImageAdapter { +public: + ElementAdapter(const Document &document, ElementRegistry ®istry) + : m_document(&document), m_registry(®istry) {} + + [[nodiscard]] ElementType + element_type(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->type; + } + return ElementType::none; + } + + [[nodiscard]] ElementIdentifier + element_parent(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->parent_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_first_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->first_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_last_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->last_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_previous_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->previous_sibling_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_next_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->next_sibling_id; + } + return null_element_id; + } + + [[nodiscard]] bool + element_is_editable(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->is_editable; + } + return false; + } + + [[nodiscard]] const abstract::TextRootAdapter * + text_root_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const SlideAdapter * + slide_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::slide) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetAdapter * + sheet_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetCellAdapter * + sheet_cell_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::line_break) { + return nullptr; + } + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::paragraph) { + return nullptr; + } + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::span) { + return nullptr; + } + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::text) { + return nullptr; + } + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::link) { + return nullptr; + } + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::bookmark) { + return nullptr; + } + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::list_item) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_column) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_row) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_cell) { + return nullptr; + } + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::frame) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::image) { + return nullptr; + } + return this; + } + + [[nodiscard]] PageLayout + slide_page_layout(const ElementIdentifier element_id) const override { + (void)element_id; + // TODO + return { + .width = Measure("11.02 in"), + .height = Measure("8.27 in"), + .print_orientation = {}, + .margin = {}, + }; + } + [[nodiscard]] ElementIdentifier + slide_master_page(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] std::string + slide_name(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] TextStyle + line_break_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle + paragraph_text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ElementIdentifier element_id) const override { + const ElementRegistry::Text *text_element = + m_registry->text_element(element_id); + if (text_element == nullptr) { + return ""; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + std::string result; + for (pugi::xml_node node = first; node != last.next_sibling(); + node = node.next_sibling()) { + result += get_text(node); + } + return result; + } + void text_set_content(const ElementIdentifier element_id, + const std::string &text) const override { + ElementRegistry::Element *element = m_registry->element(element_id); + ElementRegistry::Text *text_element = m_registry->text_element(element_id); + if (element == nullptr || text_element == nullptr) { + return; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + pugi::xml_node parent = first.parent(); + const pugi::xml_node old_first = first; + const pugi::xml_node old_last = last; + pugi::xml_node new_first = old_first; + pugi::xml_node new_last = last; + + const auto insert_node = [&](const char *node) { + const pugi::xml_node new_node = + parent.insert_child_before(node, old_first); + if (new_first == old_first) { + new_first = new_node; + } + new_last = new_node; + return new_node; + }; + + for (const util::xml::StringToken &token : util::xml::tokenize_text(text)) { + switch (token.type) { + case util::xml::StringToken::Type::none: + break; + case util::xml::StringToken::Type::string: { + auto text_node = insert_node("a:t"); + text_node.append_child(pugi::xml_node_type::node_pcdata) + .text() + .set(token.string.c_str()); + } break; + case util::xml::StringToken::Type::spaces: { + auto text_node = insert_node("a:t"); + text_node.append_attribute("xml:space").set_value("preserve"); + text_node.append_child(pugi::xml_node_type::node_pcdata) + .text() + .set(token.string.c_str()); + } break; + case util::xml::StringToken::Type::tabs: { + for (std::size_t i = 0; i < token.string.size(); ++i) { + insert_node("a:tab"); + } + } break; + } + } + + element->node = new_first; + text_element->last = new_last; + + for (pugi::xml_node node = old_first; node != old_last.next_sibling();) { + const pugi::xml_node next = node.next_sibling(); + parent.remove_child(node); + node = next; + } + } + [[nodiscard]] TextStyle + text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] std::string + bookmark_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("text:name").value(); + } + + [[nodiscard]] TextStyle + list_item_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; } - return {}; + + [[nodiscard]] TableDimensions + table_dimensions(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + + TableDimensions result; + TableCursor cursor; + + for (auto column : node.children("table:table-column")) { + const auto columns_repeated = + column.attribute("table:number-columns-repeated").as_uint(1); + cursor.add_column(columns_repeated); + } + + result.columns = cursor.column(); + cursor = {}; + + for (auto row : node.children("table:table-row")) { + const auto rows_repeated = + row.attribute("table:number-rows-repeated").as_uint(1); + cursor.add_row(rows_repeated); + } + + result.rows = cursor.row(); + + return result; + } + [[nodiscard]] ElementIdentifier + table_first_column(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Table *table_registry = + m_registry->table_element(element_id); + table_registry != nullptr) { + return table_registry->first_column_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + table_first_row(const ElementIdentifier element_id) const override { + return element_first_child(element_id); + } + [[nodiscard]] TableStyle + table_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + + [[nodiscard]] TableColumnStyle + table_column_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_column_style; + } + + [[nodiscard]] TableRowStyle + table_row_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_row_style; + } + + [[nodiscard]] bool + table_cell_is_covered(const ElementIdentifier element_id) const override { + (void)element_id; + return false; + } + [[nodiscard]] TableDimensions + table_cell_span(const ElementIdentifier element_id) const override { + (void)element_id; + return {1, 1}; // TODO + } + [[nodiscard]] ValueType + table_cell_value_type(const ElementIdentifier element_id) const override { + (void)element_id; + return ValueType::string; + } + [[nodiscard]] TableCellStyle + table_cell_style(const ElementIdentifier element_id) const override { + return get_partial_style(element_id).table_cell_style; + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ElementIdentifier element_id) const override { + (void)element_id; + return AnchorType::at_page; + } + [[nodiscard]] std::optional + frame_x(const ElementIdentifier element_id) const override { + if (const std::optional x = + read_emus_attribute(get_node(element_id) + .child("p:spPr") + .child("a:xfrm") + .child("a:off") + .attribute("x"))) { + return x->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_y(const ElementIdentifier element_id) const override { + if (const std::optional y = + read_emus_attribute(get_node(element_id) + .child("p:spPr") + .child("a:xfrm") + .child("a:off") + .attribute("y"))) { + return y->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_width(const ElementIdentifier element_id) const override { + if (const std::optional cx = + read_emus_attribute(get_node(element_id) + .child("p:spPr") + .child("a:xfrm") + .child("a:ext") + .attribute("cx"))) { + return cx->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_height(const ElementIdentifier element_id) const override { + if (const std::optional cy = + read_emus_attribute(get_node(element_id) + .child("p:spPr") + .child("a:xfrm") + .child("a:ext") + .attribute("cy"))) { + return cy->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_z_index(const ElementIdentifier) const override { + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ElementIdentifier) const override { + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return false; + } + try { + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return m_document->as_filesystem()->is_file(path); + } catch (...) { + } + return false; + } + [[nodiscard]] std::optional + image_file(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return std::nullopt; + } + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return File(m_document->as_filesystem()->open(path)); + } + [[nodiscard]] std::string + image_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("xlink:href").value(); + } + +private: + const Document *m_document{nullptr}; + ElementRegistry *m_registry{nullptr}; + + [[nodiscard]] pugi::xml_node + get_node(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->node; + } + return {}; + } + + [[nodiscard]] static std::string get_text(const pugi::xml_node node) { + const std::string name = node.name(); + + if (name == "a:t") { + return node.text().get(); + } + if (name == "a:tab") { + return "\t"; + } + + return ""; + } + + [[nodiscard]] ResolvedStyle + get_partial_style(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + if (element->type == ElementType::paragraph) { + ResolvedStyle result; + resolve_text_style_(element->node, result.text_style); + resolve_paragraph_style_(element->node, result.paragraph_style); + return result; + } + if (element->type == ElementType::span) { + ResolvedStyle result; + resolve_text_style_(element->node, result.text_style); + return result; + } + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ElementIdentifier element_id) const { + const ElementIdentifier parent_id = element_parent(element_id); + if (parent_id == null_element_id) { + return get_partial_style(element_id); + } + ResolvedStyle base = get_intermediate_style(parent_id); + base.override(get_partial_style(element_id)); + return base; + } +}; + +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry) { + return std::make_unique(document, registry); } +} // namespace + } // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.hpp index f3f035c7..73edf0fa 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.hpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.hpp @@ -1,32 +1,31 @@ #pragma once #include -#include +#include #include -#include #include namespace odr::internal::ooxml::presentation { -class Document final : public TemplateDocument { +class Document final : public internal::Document { public: - explicit Document(std::shared_ptr filesystem); + explicit Document(std::shared_ptr files); - [[nodiscard]] bool is_editable() const noexcept final; - [[nodiscard]] bool is_savable(bool encrypted) const noexcept final; + [[nodiscard]] const ElementRegistry &element_registry() const; - void save(const Path &path) const final; - void save(const Path &path, const char *password) const final; + [[nodiscard]] bool is_editable() const noexcept override; + [[nodiscard]] bool is_savable(bool encrypted) const noexcept override; - pugi::xml_node get_slide_root(const std::string &ref) const; + void save(const Path &path) const override; + void save(const Path &path, const char *password) const override; private: pugi::xml_document m_document_xml; std::unordered_map m_slides_xml; - friend class Element; + ElementRegistry m_element_registry; }; } // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element.cpp deleted file mode 100644 index 5055f700..00000000 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include - -#include - -#include -#include - -#include - -namespace odr::internal::ooxml::presentation { - -namespace { - -void resolve_text_style_(pugi::xml_node node, TextStyle &result) { - auto run_properties = node.child("a:rPr"); - - if (auto font_name = run_properties.child("rFonts").attribute("ascii")) { - result.font_name = font_name.value(); - } - if (auto font_size = - read_hundredth_point_attribute(run_properties.attribute("sz"))) { - result.font_size = font_size; - } - if (auto font_weight = - read_font_weight_attribute(run_properties.attribute("b"))) { - result.font_weight = font_weight; - } - if (auto font_style = - read_font_style_attribute(run_properties.attribute("i"))) { - result.font_style = font_style; - } - if (auto font_underline = - read_line_attribute(run_properties.attribute("u"))) { - result.font_underline = font_underline; - } - if (auto font_line_through = - read_line_attribute(run_properties.attribute("strike"))) { - result.font_line_through = font_line_through; - } - if (auto font_shadow = - read_shadow_attribute(run_properties.attribute("shadow"))) { - result.font_shadow = font_shadow; - } - if (auto font_color = - read_color_attribute(run_properties.attribute("color"))) { - result.font_color = font_color; - } - if (auto background_color = - read_color_attribute(run_properties.attribute("highlight"))) { - result.background_color = background_color; - } -} - -void resolve_paragraph_style_(pugi::xml_node node, ParagraphStyle &result) { - auto paragraph_properties = node.child("a:pPr"); - - if (auto text_align = - read_text_align_attribute(paragraph_properties.attribute("jc"))) { - result.text_align = text_align; - } - if (auto margin_left = read_twips_attribute( - paragraph_properties.child("ind").attribute("left"))) { - result.margin.left = margin_left; - } - if (auto margin_left = read_twips_attribute( - paragraph_properties.child("ind").attribute("start"))) { - result.margin.left = margin_left; - } - if (auto margin_right = read_twips_attribute( - paragraph_properties.child("ind").attribute("right"))) { - result.margin.right = margin_right; - } - if (auto margin_right = read_twips_attribute( - paragraph_properties.child("ind").attribute("end"))) { - result.margin.right = margin_right; - } -} - -} // namespace - -Element::Element(pugi::xml_node node) : m_node{node} { - if (!node) { - // TODO log error - throw std::runtime_error("node not set"); - } -} - -ResolvedStyle Element::partial_style(const abstract::Document *) const { - return {}; -} - -ResolvedStyle -Element::intermediate_style(const abstract::Document *document) const { - abstract::Element *parent = this->parent(document); - if (parent == nullptr) { - return partial_style(document); - } - auto base = dynamic_cast(parent)->intermediate_style(document); - base.override(partial_style(document)); - return base; -} - -bool Element::is_editable(const abstract::Document *document) const { - if (m_parent == nullptr) { - return document_(document)->is_editable(); - } - return m_parent->is_editable(document); -} - -const Document *Element::document_(const abstract::Document *document) { - return dynamic_cast(document); -} - -pugi::xml_node Element::slide_(const abstract::Document *document, - const std::string &id) { - return document_(document)->m_slides_xml.at(id).document_element(); -} - -PageLayout Slide::page_layout(const abstract::Document *) const { - // TODO - return { - .width = Measure("11.02 in"), - .height = Measure("8.27 in"), - .print_orientation = {}, - .margin = {}, - }; -} - -abstract::Element *Slide::master_page(const abstract::Document *) const { - return {}; // TODO -} - -std::string Slide::name(const abstract::Document *) const { - return {}; // TODO -} - -pugi::xml_node Slide::slide_node_(const abstract::Document *document) const { - return slide_(document, m_node.attribute("r:id").value()); -} - -ResolvedStyle Paragraph::partial_style(const abstract::Document *) const { - ResolvedStyle result; - resolve_text_style_(m_node, result.text_style); - resolve_paragraph_style_(m_node, result.paragraph_style); - return result; -} - -ParagraphStyle Paragraph::style(const abstract::Document *document) const { - return partial_style(document).paragraph_style; -} - -TextStyle Paragraph::text_style(const abstract::Document *document) const { - return partial_style(document).text_style; -} - -ResolvedStyle Span::partial_style(const abstract::Document *) const { - ResolvedStyle result; - resolve_text_style_(m_node, result.text_style); - return result; -} - -TextStyle Span::style(const abstract::Document *document) const { - return partial_style(document).text_style; -} - -Text::Text(pugi::xml_node node) : Text(node, node) {} - -Text::Text(pugi::xml_node first, pugi::xml_node last) - : Element(first), m_last{last} {} - -std::string Text::content(const abstract::Document *) const { - std::string result; - for (auto node = m_node; node != m_last.next_sibling(); - node = node.next_sibling()) { - result += text_(node); - } - return result; -} - -void Text::set_content(const abstract::Document *, const std::string &) { - // TODO -} - -TextStyle Text::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -std::string Text::text_(const pugi::xml_node node) { - std::string name = node.name(); - - if (name == "a:t") { - return node.text().get(); - } - if (name == "a:tab") { - return "\t"; - } - - return ""; -} - -TableDimensions TableElement::dimensions(const abstract::Document *) const { - return {}; // TODO -} - -abstract::Element * -TableElement::first_column(const abstract::Document *) const { - return {}; // TODO -} - -abstract::Element *TableElement::first_row(const abstract::Document *) const { - return {}; // TODO -} - -TableStyle TableElement::style(const abstract::Document *document) const { - return partial_style(document).table_style; -} - -TableColumnStyle TableColumn::style(const abstract::Document *document) const { - return partial_style(document).table_column_style; -} - -TableRowStyle TableRow::style(const abstract::Document *document) const { - return partial_style(document).table_row_style; -} - -bool TableCell::is_covered(const abstract::Document *) const { return false; } - -TableDimensions TableCell::span(const abstract::Document *) const { - return {1, 1}; // TODO -} - -ValueType TableCell::value_type(const abstract::Document *) const { - return ValueType::string; -} - -TableCellStyle TableCell::style(const abstract::Document *document) const { - return partial_style(document).table_cell_style; -} - -AnchorType Frame::anchor_type(const abstract::Document *) const { - return AnchorType::at_page; -} - -std::optional Frame::x(const abstract::Document *) const { - if (auto x = read_emus_attribute( - m_node.child("p:spPr").child("a:xfrm").child("a:off").attribute( - "x"))) { - return x->to_string(); - } - return {}; -} - -std::optional Frame::y(const abstract::Document *) const { - if (auto y = read_emus_attribute( - m_node.child("p:spPr").child("a:xfrm").child("a:off").attribute( - "y"))) { - return y->to_string(); - } - return {}; -} - -std::optional Frame::width(const abstract::Document *) const { - if (auto cx = read_emus_attribute( - m_node.child("p:spPr").child("a:xfrm").child("a:ext").attribute( - "cx"))) { - return cx->to_string(); - } - return {}; -} - -std::optional Frame::height(const abstract::Document *) const { - if (auto cy = read_emus_attribute( - m_node.child("p:spPr").child("a:xfrm").child("a:ext").attribute( - "cy"))) { - return cy->to_string(); - } - return {}; -} - -std::optional Frame::z_index(const abstract::Document *) const { - return {}; // TODO -} - -GraphicStyle Frame::style(const abstract::Document *) const { - return {}; // TODO -} - -bool ImageElement::is_internal(const abstract::Document *) const { - return false; -} - -std::optional ImageElement::file(const abstract::Document *) const { - return {}; // TODO -} - -std::string ImageElement::href(const abstract::Document *) const { - return ""; // TODO -} - -} // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element.hpp deleted file mode 100644 index bdb9408a..00000000 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element.hpp +++ /dev/null @@ -1,178 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include - -namespace odr::internal::ooxml::presentation { -class Document; - -class Element : public internal::Element { -public: - explicit Element(pugi::xml_node); - - [[nodiscard]] virtual ResolvedStyle - partial_style(const abstract::Document *) const; - [[nodiscard]] virtual ResolvedStyle - intermediate_style(const abstract::Document *) const; - - [[nodiscard]] bool - is_editable(const abstract::Document *document) const override; - -protected: - static const Document *document_(const abstract::Document *); - static pugi::xml_node slide_(const abstract::Document *, - const std::string &id); - - pugi::xml_node m_node; -}; - -template class DefaultElement : public Element { -public: - using Element::Element; - - [[nodiscard]] ElementType type(const abstract::Document *) const override { - return element_type; - } -}; - -class Root final : public DefaultElement { -public: - using DefaultElement::DefaultElement; -}; - -class Slide final : public Element, public abstract::Slide { -public: - using Element::Element; - - [[nodiscard]] PageLayout page_layout(const abstract::Document *) const final; - - [[nodiscard]] abstract::Element * - master_page(const abstract::Document *) const final; - - [[nodiscard]] std::string name(const abstract::Document *) const final; - -private: - pugi::xml_node slide_node_(const abstract::Document *) const; -}; - -class Paragraph final : public Element, public abstract::Paragraph { -public: - using Element::Element; - - [[nodiscard]] ResolvedStyle - partial_style(const abstract::Document *) const final; - - [[nodiscard]] ParagraphStyle style(const abstract::Document *) const final; - - [[nodiscard]] TextStyle text_style(const abstract::Document *) const final; -}; - -class Span final : public Element, public abstract::Span { -public: - using Element::Element; - - [[nodiscard]] ResolvedStyle - partial_style(const abstract::Document *) const final; - - [[nodiscard]] TextStyle style(const abstract::Document *) const final; -}; - -class Text final : public Element, public abstract::Text { -public: - explicit Text(pugi::xml_node node); - Text(pugi::xml_node first, pugi::xml_node last); - - [[nodiscard]] std::string content(const abstract::Document *) const final; - - void set_content(const abstract::Document *, const std::string &content); - - [[nodiscard]] TextStyle style(const abstract::Document *) const final; - -private: - static std::string text_(const pugi::xml_node node); - - pugi::xml_node m_last; -}; - -class TableElement : public Element, public abstract::Table { -public: - using Element::Element; - - [[nodiscard]] TableDimensions - dimensions(const abstract::Document *) const final; - - [[nodiscard]] abstract::Element * - first_column(const abstract::Document *) const final; - - [[nodiscard]] abstract::Element * - first_row(const abstract::Document *) const final; - - [[nodiscard]] TableStyle style(const abstract::Document *) const final; -}; - -class TableColumn final : public Element, public abstract::TableColumn { -public: - using Element::Element; - - [[nodiscard]] TableColumnStyle style(const abstract::Document *) const final; -}; - -class TableRow final : public Element, public abstract::TableRow { -public: - using Element::Element; - - [[nodiscard]] TableRowStyle style(const abstract::Document *) const final; -}; - -class TableCell final : public Element, public abstract::TableCell { -public: - using Element::Element; - - [[nodiscard]] bool is_covered(const abstract::Document *) const final; - - [[nodiscard]] TableDimensions span(const abstract::Document *) const final; - - [[nodiscard]] ValueType value_type(const abstract::Document *) const final; - - [[nodiscard]] TableCellStyle style(const abstract::Document *) const final; -}; - -class Frame final : public Element, public abstract::Frame { -public: - using Element::Element; - - [[nodiscard]] AnchorType anchor_type(const abstract::Document *) const final; - - [[nodiscard]] std::optional - x(const abstract::Document *) const final; - [[nodiscard]] std::optional - y(const abstract::Document *) const final; - [[nodiscard]] std::optional - width(const abstract::Document *) const final; - [[nodiscard]] std::optional - height(const abstract::Document *) const final; - - [[nodiscard]] std::optional - z_index(const abstract::Document *) const final; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const final; -}; - -class ImageElement final : public Element, public abstract::Image { -public: - using Element::Element; - - [[nodiscard]] bool is_internal(const abstract::Document *) const final; - - [[nodiscard]] std::optional - file(const abstract::Document *) const final; - - [[nodiscard]] std::string href(const abstract::Document *) const final; -}; - -} // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp new file mode 100644 index 00000000..942649ee --- /dev/null +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp @@ -0,0 +1,181 @@ +#include + +#include + +namespace odr::internal::ooxml::presentation { + +void ElementRegistry::clear() noexcept { + m_elements.clear(); + m_tables.clear(); + m_texts.clear(); +} + +[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { + return m_elements.size(); +} + +std::tuple +ElementRegistry::create_element(const ElementType type, + const pugi::xml_node node) { + Element &element = m_elements.emplace_back(); + ElementIdentifier element_id = m_elements.size(); + element.type = type; + element.node = node; + return {element_id, element}; +} + +std::tuple +ElementRegistry::create_table_element(const pugi::xml_node node) { + const auto &[element_id, element] = create_element(ElementType::table, node); + auto [it, success] = m_tables.emplace(element_id, Table{}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_text_element(const pugi::xml_node first_node, + const pugi::xml_node last_node) { + const auto &[element_id, element] = + create_element(ElementType::text, first_node); + auto [it, success] = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, it->second}; +} + +ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) { + check_element_id(id); + return m_elements.at(id - 1); +} + +ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) { + check_table_id(id); + return m_tables.at(id); +} + +const ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id - 1); +} + +const ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) const { + check_table_id(id); + return m_tables.at(id); +} + +ElementRegistry::Element *ElementRegistry::element(const ElementIdentifier id) { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +ElementRegistry::Table * +ElementRegistry::table_element(const ElementIdentifier id) { + if (const auto it = m_tables.find(id); it != m_tables.end()) { + return &it->second; + } + return nullptr; +} + +ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Element * +ElementRegistry::element(const ElementIdentifier id) const { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +const ElementRegistry::Table * +ElementRegistry::table_element(const ElementIdentifier id) const { + if (const auto it = m_tables.find(id); it != m_tables.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) const { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +void ElementRegistry::check_element_id(const ElementIdentifier id) const { + if (id == null_element_id) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ElementIdentifier previous_sibling_id = + element_at(parent_id).last_child_id; + + element_at(child_id).parent_id = parent_id; + element_at(child_id).previous_sibling_id = previous_sibling_id; + + if (element_at(parent_id).first_child_id == null_element_id) { + element_at(parent_id).first_child_id = child_id; + } else { + element_at(previous_sibling_id).next_sibling_id = child_id; + } + element_at(parent_id).last_child_id = child_id; +} + +void ElementRegistry::append_column(const ElementIdentifier table_id, + const ElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + + const ElementIdentifier previous_sibling_id = + table_element_at(table_id).last_column_id; + + element_at(column_id).parent_id = table_id; + element_at(column_id).previous_sibling_id = previous_sibling_id; + + if (table_element_at(table_id).first_column_id == null_element_id) { + table_element_at(table_id).first_column_id = column_id; + } else { + element_at(previous_sibling_id).next_sibling_id = column_id; + } + table_element_at(table_id).last_column_id = column_id; +} + +} // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp new file mode 100644 index 00000000..81cb8239 --- /dev/null +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::presentation { + +class ElementRegistry final { +public: + struct Element final { + ElementIdentifier parent_id{null_element_id}; + ElementIdentifier first_child_id{null_element_id}; + ElementIdentifier last_child_id{null_element_id}; + ElementIdentifier previous_sibling_id{null_element_id}; + ElementIdentifier next_sibling_id{null_element_id}; + ElementType type{ElementType::none}; + pugi::xml_node node; + bool is_editable{false}; + }; + + struct Table final { + ElementIdentifier first_column_id{null_element_id}; + ElementIdentifier last_column_id{null_element_id}; + }; + + struct Text final { + pugi::xml_node last; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + std::tuple create_element(ElementType type, + pugi::xml_node node); + std::tuple + create_table_element(pugi::xml_node node); + std::tuple + create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); + + [[nodiscard]] Element &element_at(ElementIdentifier id); + [[nodiscard]] Table &table_element_at(ElementIdentifier id); + + [[nodiscard]] const Element &element_at(ElementIdentifier id) const; + [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; + + [[nodiscard]] Element *element(ElementIdentifier id); + [[nodiscard]] Table *table_element(ElementIdentifier id); + [[nodiscard]] Text *text_element(ElementIdentifier id); + + [[nodiscard]] const Element *element(ElementIdentifier id) const; + [[nodiscard]] const Table *table_element(ElementIdentifier id) const; + [[nodiscard]] const Text *text_element(ElementIdentifier id) const; + + void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + void append_column(ElementIdentifier table_id, ElementIdentifier column_id); + +private: + std::vector m_elements; + std::unordered_map m_tables; + std::unordered_map m_texts; + + void check_element_id(ElementIdentifier id) const; + void check_table_id(ElementIdentifier id) const; + void check_text_id(ElementIdentifier id) const; +}; + +} // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp index 7ab5a136..f44bc96a 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp @@ -1,65 +1,69 @@ #include -#include +#include +#include #include -#include #include namespace odr::internal::ooxml::presentation { namespace { -template -std::tuple parse_element_tree(Document &document, - pugi::xml_node node); +class Context { +public: + Context(ElementRegistry ®istry, + const std::unordered_map &slides_xml) + : m_registry(®istry), m_slides_xml(&slides_xml) {} -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node); + ElementRegistry ®istry() const { return *m_registry; } + const std::unordered_map & + slides_xml() const { + return *m_slides_xml; + } -void parse_element_children(Document &document, Element *element, - pugi::xml_node node) { - for (auto child_node = node.first_child(); child_node;) { - auto [child, next_sibling] = parse_any_element_tree(document, child_node); - if (child == nullptr) { +private: + ElementRegistry *m_registry{nullptr}; + const std::unordered_map *m_slides_xml{ + nullptr}; +}; + +using TreeParser = std::function( + const Context &context, pugi::xml_node node)>; +using ChildrenParser = std::function; + +std::tuple +parse_any_element_tree(const Context &context, pugi::xml_node node); + +void parse_any_element_children(const Context &context, + const ElementIdentifier parent_id, + const pugi::xml_node node) { + for (pugi::xml_node child_node = node.first_child(); child_node;) { + if (const auto [child_id, next_sibling] = + parse_any_element_tree(context, child_node); + child_id == null_element_id) { child_node = child_node.next_sibling(); } else { - element->append_child_(child); + context.registry().append_child(parent_id, child_id); child_node = next_sibling; } } } -void parse_element_children(Document &document, Root *element, - pugi::xml_node node) { - for (auto child_node : node.child("p:sldIdLst").children("p:sldId")) { - const char *id = child_node.attribute("r:id").value(); - auto slide_node = document.get_slide_root(id); - auto [slide, _] = parse_element_tree(document, slide_node); - element->append_child_(slide); - } -} - -void parse_element_children(Document &document, Slide *element, - pugi::xml_node node) { - parse_element_children(document, dynamic_cast(element), - node.child("p:cSld").child("p:spTree")); -} - -template -std::tuple parse_element_tree(Document &document, - pugi::xml_node node) { +std::tuple +parse_element_tree(const Context &context, const ElementType type, + const pugi::xml_node node, + const ChildrenParser &children_parser) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto element_unique = std::make_unique(node); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _] = context.registry().create_element(type, node); - parse_element_children(document, element, node); + children_parser(context, element_id, node); - return std::make_tuple(element, node.next_sibling()); + return {element_id, node.next_sibling()}; } bool is_text_node(const pugi::xml_node node) { @@ -67,7 +71,7 @@ bool is_text_node(const pugi::xml_node node) { return false; } - std::string name = node.name(); + const std::string name = node.name(); if (name == "w:t") { return true; @@ -79,51 +83,76 @@ bool is_text_node(const pugi::xml_node node) { return false; } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node first) { +std::tuple +parse_text_element(const Context &context, const pugi::xml_node first) { if (!first) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - pugi::xml_node last = first; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { + pugi::xml_node last; + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { } - auto element_unique = std::make_unique(first, last); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _, __] = + context.registry().create_text_element(first, last); - return std::make_tuple(element, last.next_sibling()); + return {element_id, last.next_sibling()}; +} + +void parse_slide_children(const Context &context, + const ElementIdentifier parent_id, + const pugi::xml_node node) { + parse_any_element_children(context, parent_id, + node.child("p:cSld").child("p:spTree")); +} + +void parse_presentation_children(const Context &context, + const ElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : + node.child("p:sldIdLst").children("p:sldId")) { + const std::string id = child_node.attribute("r:id").value(); + const pugi::xml_node slide_node = + context.slides_xml().at(id).document_element(); + auto [child_id, _] = parse_element_tree(context, ElementType::slide, + slide_node, parse_slide_children); + context.registry().append_child(root_id, child_id); + } } -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node) { - using Parser = std::function( - Document & document, pugi::xml_node node)>; - - using Group = DefaultElement; - - static std::unordered_map parser_table{ - {"p:presentation", parse_element_tree}, - {"p:sld", parse_element_tree}, - {"p:sp", parse_element_tree}, - {"p:txBody", parse_element_tree}, - {"a:t", parse_element_tree}, - {"a:p", parse_element_tree}, - {"a:r", parse_element_tree}, - {"a:tbl", parse_element_tree}, - {"a:gridCol", parse_element_tree}, - {"a:tr", parse_element_tree}, - {"a:tc", parse_element_tree}, +std::tuple +parse_any_element_tree(const Context &context, const pugi::xml_node node) { + const auto create_default_tree_parser = + [](const ElementType type, + const ChildrenParser &children_parser = parse_any_element_children) { + return + [type, children_parser](const Context &c, const pugi::xml_node n) { + return parse_element_tree(c, type, n, children_parser); + }; + }; + + static std::unordered_map parser_table{ + {"p:presentation", create_default_tree_parser( + ElementType::root, parse_presentation_children)}, + {"p:sld", create_default_tree_parser(ElementType::slide)}, + {"p:sp", create_default_tree_parser(ElementType::frame)}, + {"p:txBody", create_default_tree_parser(ElementType::group)}, + {"a:t", parse_text_element}, + {"a:p", create_default_tree_parser(ElementType::paragraph)}, + {"a:r", create_default_tree_parser(ElementType::span)}, + {"a:tbl", create_default_tree_parser(ElementType::table)}, + {"a:gridCol", create_default_tree_parser(ElementType::table_column)}, + {"a:tr", create_default_tree_parser(ElementType::table_row)}, + {"a:tc", create_default_tree_parser(ElementType::table_cell)}, }; - if (auto constructor_it = parser_table.find(node.name()); + if (const auto constructor_it = parser_table.find(node.name()); constructor_it != std::end(parser_table)) { - return constructor_it->second(document, node); + return constructor_it->second(context, node); } - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } } // namespace @@ -132,10 +161,11 @@ parse_any_element_tree(Document &document, pugi::xml_node node) { namespace odr::internal::ooxml { -presentation::Element * -presentation::parse_tree(presentation::Document &document, - pugi::xml_node node) { - auto [root, _] = parse_any_element_tree(document, node); +ElementIdentifier presentation::parse_tree( + ElementRegistry ®istry, const pugi::xml_node node, + const std::unordered_map &slides_xml) { + const Context context(registry, slides_xml); + auto [root, _] = parse_any_element_tree(context, node); return root; } diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.hpp index 9231d127..3e006a34 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.hpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.hpp @@ -1,17 +1,17 @@ #pragma once -#include +#include -#include -#include -#include +#include +#include #include namespace odr::internal::ooxml::presentation { -class Document; -class Element; +class ElementRegistry; -Element *parse_tree(Document &document, pugi::xml_node node); +ElementIdentifier parse_tree( + ElementRegistry ®istry, pugi::xml_node node, + const std::unordered_map &slides_xml); } // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp index 11a06e6c..3ccea132 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp @@ -1,7 +1,10 @@ #include #include +#include +#include +#include #include #include #include @@ -10,9 +13,15 @@ namespace odr::internal::ooxml::spreadsheet { -Document::Document(std::shared_ptr filesystem) - : TemplateDocument(FileType::office_open_xml_workbook, - DocumentType::spreadsheet, std::move(filesystem)) { +namespace { +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry, + StyleRegistry &style_registry); +} + +Document::Document(std::shared_ptr files) + : internal::Document(FileType::office_open_xml_workbook, + DocumentType::spreadsheet, std::move(files)) { const AbsPath workbook_path("/xl/workbook.xml"); auto [workbook_xml, workbook_relations] = parse_xml_(workbook_path); auto [styles_xml, _] = parse_xml_(AbsPath("/xl/styles.xml")); @@ -31,7 +40,7 @@ Document::Document(std::shared_ptr filesystem) } } - if (m_filesystem->exists(AbsPath("/xl/sharedStrings.xml"))) { + if (m_files->exists(AbsPath("/xl/sharedStrings.xml"))) { for (auto [shared_strings_xml, _] = parse_xml_(AbsPath("/xl/sharedStrings.xml")); auto shared_string : shared_strings_xml.document_element()) { @@ -41,18 +50,22 @@ Document::Document(std::shared_ptr filesystem) m_style_registry = StyleRegistry(styles_xml.document_element()); - m_root_element = parse_tree(*this, workbook_xml.document_element(), - workbook_path, workbook_relations); + const ParseContext parse_context(workbook_path, workbook_relations, + m_xml_documents_and_relations, + m_shared_strings); + m_root_element = parse_tree(m_element_registry, parse_context, + workbook_xml.document_element()); + + m_element_adapter = + create_element_adapter(*this, m_element_registry, m_style_registry); } -std::pair -Document::parse_xml_(const AbsPath &path) { - pugi::xml_document document = util::xml::parse(*m_filesystem, path); - Relations relations = parse_relationships(*m_filesystem, path); +const ElementRegistry &Document::element_registry() const { + return m_element_registry; +} - auto [it, _] = m_xml.emplace( - path, std::make_pair(std::move(document), std::move(relations))); - return {it->second.first, it->second.second}; +const StyleRegistry &Document::style_registry() const { + return m_style_registry; } bool Document::is_editable() const noexcept { return false; } @@ -69,13 +82,588 @@ void Document::save(const Path & /*path*/, const char * /*password*/) const { throw UnsupportedOperation(); } -std::pair -Document::get_xml(const Path &path) const { - return m_xml.at(path); +std::pair +Document::parse_xml_(const AbsPath &path) { + pugi::xml_document document = util::xml::parse(*m_files, path); + Relations relations = parse_relationships(*m_files, path); + + auto [it, _] = m_xml_documents_and_relations.emplace( + path, std::make_pair(std::move(document), std::move(relations))); + return {it->second.first, it->second.second}; } -pugi::xml_node Document::get_shared_string(const std::size_t index) const { - return m_shared_strings.at(index); +namespace { + +class ElementAdapter final : public abstract::ElementAdapter, + public abstract::SheetAdapter, + public abstract::SheetCellAdapter, + public abstract::LineBreakAdapter, + public abstract::ParagraphAdapter, + public abstract::SpanAdapter, + public abstract::TextAdapter, + public abstract::LinkAdapter, + public abstract::FrameAdapter, + public abstract::ImageAdapter { +public: + ElementAdapter(const Document &document, ElementRegistry ®istry, + StyleRegistry &style_registry) + : m_document(&document), m_registry(®istry), + m_style_registry(&style_registry) {} + + [[nodiscard]] ElementType + element_type(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->type; + } + return ElementType::none; + } + + [[nodiscard]] ElementIdentifier + element_parent(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->parent_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_first_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->first_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_last_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->last_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_previous_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->previous_sibling_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_next_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->next_sibling_id; + } + return null_element_id; + } + + [[nodiscard]] bool + element_is_editable(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->is_editable; + } + return false; + } + + [[nodiscard]] const abstract::TextRootAdapter * + text_root_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const SheetAdapter * + sheet_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::sheet) { + return nullptr; + } + return this; + } + [[nodiscard]] const SheetCellAdapter * + sheet_cell_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::sheet_cell) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::line_break) { + return nullptr; + } + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::paragraph) { + return nullptr; + } + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::span) { + return nullptr; + } + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::text) { + return nullptr; + } + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::link) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::BookmarkAdapter * + bookmark_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::ListItemAdapter * + list_item_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableAdapter * + table_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableColumnAdapter * + table_column_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableRowAdapter * + table_row_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableCellAdapter * + table_cell_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::frame) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::image) { + return nullptr; + } + return this; + } + + [[nodiscard]] std::string + sheet_name(const ElementIdentifier element_id) const override { + return get_node(element_id).attribute("name").value(); + } + [[nodiscard]] TableDimensions + sheet_dimensions(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Sheet *element = + m_registry->sheet_element(element_id); + element != nullptr) { + return element->dimensions; + } + return {}; + } + [[nodiscard]] TableDimensions + sheet_content(const ElementIdentifier element_id, + const std::optional range) const override { + (void)range; + return sheet_dimensions(element_id); // TODO + } + [[nodiscard]] ElementIdentifier + sheet_cell(const ElementIdentifier element_id, const std::uint32_t column, + const std::uint32_t row) const override { + if (const ElementRegistry::Sheet *sheet_element = + m_registry->sheet_element(element_id); + sheet_element != nullptr) { + if (const ElementRegistry::Sheet::Cell *cell = + sheet_element->cell(column, row); + cell != nullptr) { + return cell->element_id; + } + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + sheet_first_shape(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Sheet *sheet_element = + m_registry->sheet_element(element_id); + sheet_element != nullptr) { + return sheet_element->first_shape_id; + } + return null_element_id; + } + [[nodiscard]] TableStyle + sheet_style(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableColumnStyle + sheet_column_style(const ElementIdentifier element_id, + const std::uint32_t column) const override { + TableColumnStyle result; + if (const ElementRegistry::Sheet *sheet_element = + m_registry->sheet_element(element_id); + sheet_element != nullptr) { + const pugi::xml_node column_node = sheet_element->column_node(column); + if (const pugi::xml_attribute width = column_node.attribute("width")) { + result.width = Measure(width.as_float(), DynamicUnit("ch")); + } + } + return result; + } + [[nodiscard]] TableRowStyle + sheet_row_style(const ElementIdentifier element_id, + const std::uint32_t row) const override { + TableRowStyle result; + if (const ElementRegistry::Sheet *sheet_element = + m_registry->sheet_element(element_id); + sheet_element != nullptr) { + const pugi::xml_node row_node = sheet_element->row_node(row); + if (const pugi::xml_attribute height = row_node.attribute("ht")) { + result.height = Measure(height.as_float(), DynamicUnit("pt")); + } + } + return result; + } + [[nodiscard]] TableCellStyle + sheet_cell_style(const ElementIdentifier element_id, + const std::uint32_t column, + const std::uint32_t row) const override { + TableCellStyle result; + if (const ElementRegistry::Sheet *sheet_element = + m_registry->sheet_element(element_id); + sheet_element != nullptr) { + if (const pugi::xml_attribute style_attr = + sheet_element->cell_node(column, row).attribute("s"); + style_attr) { + const std::uint32_t style_id = style_attr.as_uint(); + const ResolvedStyle style = m_style_registry->cell_style(style_id); + result.override(style.table_cell_style); + } + } + return result; + } + + [[nodiscard]] TablePosition + sheet_cell_position(const ElementIdentifier element_id) const override { + if (const ElementRegistry::SheetCell *cell_element = + m_registry->sheet_cell_element(element_id); + cell_element != nullptr) { + return cell_element->position; + } + return {}; + } + [[nodiscard]] bool + sheet_cell_is_covered(const ElementIdentifier element_id) const override { + (void)element_id; + return false; // TODO + } + [[nodiscard]] TableDimensions + sheet_cell_span(const ElementIdentifier element_id) const override { + (void)element_id; + return {1, 1}; // TODO + } + [[nodiscard]] ValueType + sheet_cell_value_type(const ElementIdentifier element_id) const override { + (void)element_id; + return ValueType::string; // TODO + } + + [[nodiscard]] TextStyle + line_break_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle + paragraph_text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ElementIdentifier element_id) const override { + const ElementRegistry::Text *text_element = + m_registry->text_element(element_id); + if (text_element == nullptr) { + return ""; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + std::string result; + for (pugi::xml_node node = first; node != last.next_sibling(); + node = node.next_sibling()) { + result += get_text(node); + } + return result; + } + void text_set_content(const ElementIdentifier element_id, + const std::string &text) const override { + (void)element_id; + (void)text; + // TODO + } + [[nodiscard]] TextStyle + text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ElementIdentifier element_id) const override { + (void)element_id; + return AnchorType::at_page; + } + [[nodiscard]] std::optional + frame_x(const ElementIdentifier element_id) const override { + if (const std::optional x = + read_emus_attribute(get_node(element_id) + .child("xdr:pic") + .child("xdr:spPr") + .child("a:xfrm") + .child("a:off") + .attribute("x"))) { + return x->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_y(const ElementIdentifier element_id) const override { + if (const std::optional y = + read_emus_attribute(get_node(element_id) + .child("xdr:pic") + .child("xdr:spPr") + .child("a:xfrm") + .child("a:off") + .attribute("y"))) { + return y->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_width(const ElementIdentifier element_id) const override { + if (const std::optional width = + read_emus_attribute(get_node(element_id) + .child("xdr:pic") + .child("xdr:spPr") + .child("a:xfrm") + .child("a:ext") + .attribute("cx"))) { + return width->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_height(const ElementIdentifier element_id) const override { + if (const std::optional height = + read_emus_attribute(get_node(element_id) + .child("xdr:pic") + .child("xdr:spPr") + .child("a:xfrm") + .child("a:ext") + .attribute("cy"))) { + return height->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_z_index(const ElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return false; + } + try { + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return m_document->as_filesystem()->is_file(path); + } catch (...) { + } + return false; + } + [[nodiscard]] std::optional + image_file(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return std::nullopt; + } + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return File(m_document->as_filesystem()->open(path)); + } + [[nodiscard]] std::string + image_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute ref = node.attribute("r:embed"); ref) { + if (const auto [relations, origin] = get_relations_and_origin(element_id); + relations != nullptr) { + if (const auto rel = relations->find(ref.value()); + rel != std::end(*relations)) { + return origin.parent().join(RelPath(rel->second)).string(); + } + } + } + return ""; // TODO + } + +private: + const Document *m_document{nullptr}; + ElementRegistry *m_registry{nullptr}; + StyleRegistry *m_style_registry{nullptr}; + + [[nodiscard]] pugi::xml_node + get_node(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->node; + } + return {}; + } + + [[nodiscard]] std::pair + get_relations_and_origin(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr) { + return {nullptr, {}}; + } + if (const ElementRegistry::ElementRelations *element_relations = + m_registry->element_relations(element_id); + element_relations != nullptr) { + return {element_relations->relations, element_relations->origin}; + } + return get_relations_and_origin(element_parent(element_id)); + } + + [[nodiscard]] static std::string get_text(const pugi::xml_node node) { + if (const std::string name = node.name(); name == "t" || name == "v") { + return node.text().get(); + } + + return ""; + } + + [[nodiscard]] ResolvedStyle + get_partial_style(const ElementIdentifier element_id) const { + if (const ElementType type = element_type(element_id); + type == ElementType::sheet_cell) { + return get_partial_cell_style(element_id); + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_partial_cell_style(const ElementIdentifier element_id) const { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute style_id = node.attribute("s")) { + return m_document->style_registry().cell_style(style_id.as_uint()); + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ElementIdentifier element_id) const { + const ElementIdentifier parent_id = element_parent(element_id); + if (parent_id == null_element_id) { + return get_partial_style(element_id); + } + ResolvedStyle base = get_intermediate_style(parent_id); + base.override(get_partial_style(element_id)); + return base; + } +}; + +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry, + StyleRegistry &style_registry) { + return std::make_unique(document, registry, style_registry); } +} // namespace + } // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.hpp index 58af4588..b157dd55 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.hpp @@ -1,21 +1,25 @@ #pragma once -#include - #include #include #include -#include +#include #include #include #include +#include + +#include namespace odr::internal::ooxml::spreadsheet { -class Document final : public TemplateDocument { +class Document final : public internal::Document { public: - explicit Document(std::shared_ptr filesystem); + explicit Document(std::shared_ptr files); + + [[nodiscard]] const ElementRegistry &element_registry() const; + [[nodiscard]] const StyleRegistry &style_registry() const; [[nodiscard]] bool is_editable() const noexcept override; [[nodiscard]] bool is_savable(bool encrypted) const noexcept override; @@ -23,19 +27,14 @@ class Document final : public TemplateDocument { void save(const Path &path) const override; void save(const Path &path, const char *password) const override; - std::pair - get_xml(const Path &) const; - pugi::xml_node get_shared_string(std::size_t index) const; - private: - std::unordered_map> m_xml; + XmlDocumentsAndRelations m_xml_documents_and_relations; + SharedStrings m_shared_strings; + ElementRegistry m_element_registry; StyleRegistry m_style_registry; - std::vector m_shared_strings; std::pair parse_xml_(const AbsPath &path); - - friend class Element; }; } // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.cpp deleted file mode 100644 index feea2ab3..00000000 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.cpp +++ /dev/null @@ -1,401 +0,0 @@ -#include - -#include -#include -#include - -#include - -namespace odr::internal::ooxml::spreadsheet { - -Element::Element(const pugi::xml_node node, const Path & /*document_path*/, - const Relations & /*document_relations*/) - : m_node{node} { - if (!node) { - // TODO log error - throw std::runtime_error("node not set"); - } -} - -ResolvedStyle Element::partial_style(const abstract::Document *) const { - return {}; -} - -ResolvedStyle -Element::intermediate_style(const abstract::Document *document) const { - abstract::Element *parent = this->parent(document); - if (parent == nullptr) { - return partial_style(document); - } - auto base = dynamic_cast(parent)->intermediate_style(document); - base.override(partial_style(document)); - return base; -} - -bool Element::is_editable(const abstract::Document *document) const { - if (m_parent == nullptr) { - return document_(document)->is_editable(); - } - return m_parent->is_editable(document); -} - -const Document *Element::document_(const abstract::Document *document) { - return dynamic_cast(document); -} - -const StyleRegistry * -Element::style_registry_(const abstract::Document *document) { - return &document_(document)->m_style_registry; -} - -const Path &Element::document_path_(const abstract::Document *document) const { - return dynamic_cast(m_parent)->document_path_(document); -} - -const Relations & -Element::document_relations_(const abstract::Document *document) const { - return dynamic_cast(m_parent)->document_relations_(document); -} - -Root::Root(const pugi::xml_node node, Path document_path, - const Relations &document_relations) - : DefaultElement(node, document_path, document_relations), - m_document_path{std::move(document_path)}, - m_document_relations{document_relations} {} - -const Path &Root::document_path_(const abstract::Document *) const { - return m_document_path; -} - -const Relations &Root::document_relations_(const abstract::Document *) const { - return m_document_relations; -} - -void SheetIndex::init_column(std::uint32_t /*min*/, const std::uint32_t max, - const pugi::xml_node element) { - columns[max] = element; -} - -void SheetIndex::init_row(const std::uint32_t row, - const pugi::xml_node element) { - rows[row].row = element; -} - -void SheetIndex::init_cell(const std::uint32_t column, const std::uint32_t row, - const pugi::xml_node element) { - rows[row].cells[column] = element; -} - -pugi::xml_node SheetIndex::column(const std::uint32_t column) const { - if (const auto it = util::map::lookup_greater_or_equals(columns, column); - it != std::end(columns)) { - return it->second; - } - return {}; -} - -pugi::xml_node SheetIndex::row(const std::uint32_t row) const { - if (const auto it = util::map::lookup_greater_or_equals(rows, row); - it != std::end(rows)) { - return it->second.row; - } - return {}; -} - -pugi::xml_node SheetIndex::cell(const std::uint32_t column, - const std::uint32_t row) const { - if (const auto row_it = util::map::lookup_greater_or_equals(rows, row); - row_it != std::end(rows)) { - const auto &cells = row_it->second.cells; - if (const auto cell_it = util::map::lookup_greater_or_equals(cells, column); - cell_it != std::end(cells)) { - return cell_it->second; - } - } - return {}; -} - -Sheet::Sheet(const pugi::xml_node node, Path document_path, - const Relations &document_relations) - : Element(node, document_path, document_relations), - m_document_path{std::move(document_path)}, - m_document_relations{document_relations} {} - -const Path &Sheet::document_path_(const abstract::Document *) const { - return m_document_path; -} - -const Relations &Sheet::document_relations_(const abstract::Document *) const { - return m_document_relations; -} - -std::string Sheet::name(const abstract::Document *) const { - return m_node.attribute("name").value(); -} - -TableDimensions -Sheet::dimensions(const abstract::Document * /*document*/) const { - return m_index.dimensions; -} - -TableDimensions Sheet::content(const abstract::Document *document, - std::optional) const { - return dimensions(document); // TODO -} - -abstract::SheetCell *Sheet::cell(const abstract::Document *, - std::uint32_t column, - std::uint32_t row) const { - if (const auto cell_it = m_cells.find({column, row}); - cell_it != std::end(m_cells)) { - return cell_it->second; - } - return nullptr; -} - -abstract::Element *Sheet::first_shape(const abstract::Document *) const { - return m_first_shape; -} - -TableStyle Sheet::style(const abstract::Document *) const { - return {}; // TODO -} - -TableColumnStyle Sheet::column_style(const abstract::Document *, - const std::uint32_t column) const { - TableColumnStyle result; - const pugi::xml_node column_node = m_index.column(column); - if (const pugi::xml_attribute width = column_node.attribute("width")) { - result.width = Measure(width.as_float(), DynamicUnit("ch")); - } - return result; -} - -TableRowStyle Sheet::row_style(const abstract::Document *, - const std::uint32_t row) const { - TableRowStyle result; - const pugi::xml_node row_node = m_index.row(row); - if (const pugi::xml_attribute height = row_node.attribute("ht")) { - result.height = Measure(height.as_float(), DynamicUnit("pt")); - } - return result; -} - -TableCellStyle Sheet::cell_style(const abstract::Document *document, - const std::uint32_t column, - const std::uint32_t row) const { - TableCellStyle result; - if (const pugi::xml_attribute style_attr = - m_index.cell(column, row).attribute("s"); - style_attr) { - const std::uint32_t style_id = style_attr.as_uint(); - const ResolvedStyle style = style_registry_(document)->cell_style(style_id); - result.override(style.table_cell_style); - } - return result; -} - -void Sheet::init_column_(const std::uint32_t min, const std::uint32_t max, - const pugi::xml_node element) { - m_index.init_column(min, max, element); -} - -void Sheet::init_row_(const std::uint32_t row, const pugi::xml_node element) { - m_index.init_row(row, element); -} - -void Sheet::init_cell_(const std::uint32_t column, const std::uint32_t row, - const pugi::xml_node element) { - m_index.init_cell(column, row, element); -} - -void Sheet::init_cell_element_(const std::uint32_t column, - const std::uint32_t row, SheetCell *element) { - m_cells[{column, row}] = element; - element->m_parent = this; -} - -void Sheet::init_dimensions_(const TableDimensions dimensions) { - m_index.dimensions = dimensions; -} - -void Sheet::append_shape_(Element *shape) { - shape->m_previous_sibling = m_last_shape; - shape->m_parent = this; - if (m_last_shape == nullptr) { - m_first_shape = shape; - } else { - m_last_shape->m_next_sibling = shape; - } - m_last_shape = shape; -} - -bool SheetCell::is_covered(const abstract::Document *) const { - return false; // TODO -} - -ValueType SheetCell::value_type(const abstract::Document *) const { - return ValueType::string; -} - -ResolvedStyle -SheetCell::partial_style(const abstract::Document *document) const { - if (const pugi::xml_attribute style_id = m_node.attribute("s")) { - return style_registry_(document)->cell_style(style_id.as_uint()); - } - return {}; -} - -TableDimensions SheetCell::span(const abstract::Document *) const { - return {1, 1}; -} - -TableCellStyle SheetCell::style(const abstract::Document *document) const { - return partial_style(document).table_cell_style; -} - -TextStyle Span::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -Text::Text(const pugi::xml_node node, const Path &document_path, - const Relations &document_relations) - : Text(node, node, document_path, document_relations) {} - -Text::Text(const pugi::xml_node first, const pugi::xml_node last, - const Path &document_path, const Relations &document_relations) - : Element(first, document_path, document_relations), m_last{last} {} - -std::string Text::content(const abstract::Document *) const { - std::string result; - for (pugi::xml_node node = m_node; node != m_last.next_sibling(); - node = node.next_sibling()) { - result += text_(node); - } - return result; -} - -void Text::set_content(const abstract::Document *, const std::string &) { - // TODO -} - -TextStyle Text::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -std::string Text::text_(const pugi::xml_node node) { - if (const std::string name = node.name(); name == "t" || name == "v") { - return node.text().get(); - } - - return ""; -} - -Frame::Frame(const pugi::xml_node node, Path document_path, - const Relations &document_relations) - : Element(node, document_path, document_relations), - m_document_path{std::move(document_path)}, - m_document_relations{document_relations} {} - -const Path &Frame::document_path_(const abstract::Document *) const { - return m_document_path; -} - -const Relations &Frame::document_relations_(const abstract::Document *) const { - return m_document_relations; -} - -AnchorType Frame::anchor_type(const abstract::Document *) const { - return AnchorType::at_page; -} - -std::optional Frame::x(const abstract::Document *) const { - if (const std::optional x = - read_emus_attribute(m_node.child("xdr:pic") - .child("xdr:spPr") - .child("a:xfrm") - .child("a:off") - .attribute("x"))) { - return x->to_string(); - } - return {}; -} - -std::optional Frame::y(const abstract::Document *) const { - if (const std::optional y = - read_emus_attribute(m_node.child("xdr:pic") - .child("xdr:spPr") - .child("a:xfrm") - .child("a:off") - .attribute("y"))) { - return y->to_string(); - } - return {}; -} - -std::optional Frame::width(const abstract::Document *) const { - if (const std::optional width = - read_emus_attribute(m_node.child("xdr:pic") - .child("xdr:spPr") - .child("a:xfrm") - .child("a:ext") - .attribute("cx"))) { - return width->to_string(); - } - return {}; -} - -std::optional Frame::height(const abstract::Document *) const { - if (const std::optional height = - read_emus_attribute(m_node.child("xdr:pic") - .child("xdr:spPr") - .child("a:xfrm") - .child("a:ext") - .attribute("cy"))) { - return height->to_string(); - } - return {}; -} - -std::optional Frame::z_index(const abstract::Document *) const { - return {}; -} - -GraphicStyle Frame::style(const abstract::Document *) const { return {}; } - -bool ImageElement::is_internal(const abstract::Document *document) const { - const Document *doc = document_(document); - if (doc == nullptr || !doc->as_filesystem()) { - return false; - } - try { - return doc->as_filesystem()->is_file(AbsPath(href(document))); - } catch (...) { - } - return false; -} - -std::optional -ImageElement::file(const abstract::Document *document) const { - const Document *doc = document_(document); - if (doc == nullptr || !is_internal(document)) { - return {}; - } - return File(doc->as_filesystem()->open(AbsPath(href(document)))); -} - -std::string ImageElement::href(const abstract::Document *document) const { - if (const pugi::xml_attribute ref = m_node.attribute("r:embed")) { - const Relations relations = document_relations_(document); - if (const auto rel = relations.find(ref.value()); - rel != std::end(relations)) { - return document_path_(document) - .parent() - .join(RelPath(rel->second)) - .string(); - } - } - return ""; // TODO -} - -} // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.hpp deleted file mode 100644 index 5825d1ac..00000000 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element.hpp +++ /dev/null @@ -1,230 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace odr::internal::ooxml::spreadsheet { -class Document; -class StyleRegistry; - -class SheetCell; - -class Element : public internal::Element { -public: - Element(pugi::xml_node node, const Path &document_path, - const Relations &document_relations); - - [[nodiscard]] virtual ResolvedStyle - partial_style(const abstract::Document *) const; - [[nodiscard]] ResolvedStyle - intermediate_style(const abstract::Document *) const; - - [[nodiscard]] bool is_editable(const abstract::Document *) const override; - - [[nodiscard]] virtual const Path & - document_path_(const abstract::Document *) const; - [[nodiscard]] virtual const Relations & - document_relations_(const abstract::Document *) const; - -protected: - pugi::xml_node m_node; - - static const Document *document_(const abstract::Document *); - static const StyleRegistry *style_registry_(const abstract::Document *); -}; - -template class DefaultElement : public Element { -public: - using Element::Element; - - [[nodiscard]] ElementType type(const abstract::Document *) const override { - return element_type; - } -}; - -class Root final : public DefaultElement { -public: - Root(pugi::xml_node node, Path document_path, - const Relations &document_relations); - - [[nodiscard]] const Path & - document_path_(const abstract::Document *) const override; - [[nodiscard]] const Relations & - document_relations_(const abstract::Document *) const override; - -private: - Path m_document_path; - const Relations &m_document_relations; -}; - -struct SheetIndex final { - struct Row { - pugi::xml_node row; - std::map cells; - }; - - TableDimensions dimensions; - - std::map columns; - std::map rows; - - void init_column(std::uint32_t min, std::uint32_t max, - pugi::xml_node element); - void init_row(std::uint32_t row, pugi::xml_node element); - void init_cell(std::uint32_t column, std::uint32_t row, - pugi::xml_node element); - - pugi::xml_node column(std::uint32_t) const; - pugi::xml_node row(std::uint32_t) const; - pugi::xml_node cell(std::uint32_t column, std::uint32_t row) const; -}; - -class Sheet final : public Element, public abstract::Sheet { -public: - Sheet(pugi::xml_node node, Path document_path, - const Relations &document_relations); - - [[nodiscard]] std::string name(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions - dimensions(const abstract::Document *) const override; - [[nodiscard]] TableDimensions - content(const abstract::Document *, - std::optional) const override; - - [[nodiscard]] abstract::SheetCell *cell(const abstract::Document *, - std::uint32_t column, - std::uint32_t row) const override; - - [[nodiscard]] abstract::Element * - first_shape(const abstract::Document *) const override; - - [[nodiscard]] TableStyle style(const abstract::Document *) const override; - [[nodiscard]] TableColumnStyle - column_style(const abstract::Document *, std::uint32_t column) const override; - [[nodiscard]] TableRowStyle row_style(const abstract::Document *, - std::uint32_t row) const override; - [[nodiscard]] TableCellStyle cell_style(const abstract::Document *, - std::uint32_t column, - std::uint32_t row) const override; - - void init_column_(std::uint32_t min, std::uint32_t max, - pugi::xml_node element); - void init_row_(std::uint32_t row, pugi::xml_node element); - void init_cell_(std::uint32_t column, std::uint32_t row, - pugi::xml_node element); - void init_cell_element_(std::uint32_t column, std::uint32_t row, - SheetCell *element); - void init_dimensions_(TableDimensions dimensions); - void append_shape_(Element *shape); - -protected: - [[nodiscard]] const Path & - document_path_(const abstract::Document *) const override; - [[nodiscard]] const Relations & - document_relations_(const abstract::Document *) const override; - -private: - Path m_document_path; - const Relations &m_document_relations; - - SheetIndex m_index; - - std::unordered_map m_cells; - Element *m_first_shape{nullptr}; - Element *m_last_shape{nullptr}; -}; - -class SheetCell final : public Element, public abstract::SheetCell { -public: - using Element::Element; - - [[nodiscard]] bool is_covered(const abstract::Document *) const override; - [[nodiscard]] TableDimensions span(const abstract::Document *) const override; - [[nodiscard]] ValueType value_type(const abstract::Document *) const override; - - [[nodiscard]] TableCellStyle style(const abstract::Document *) const override; - - [[nodiscard]] ResolvedStyle - partial_style(const abstract::Document *) const override; -}; - -class Span final : public Element, public abstract::Span { -public: - using Element::Element; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Text final : public Element, public abstract::Text { -public: - explicit Text(pugi::xml_node node, const Path &document_path, - const Relations &document_relations); - Text(pugi::xml_node first, pugi::xml_node last, const Path &document_path, - const Relations &document_relations); - - [[nodiscard]] std::string content(const abstract::Document *) const override; - - void set_content(const abstract::Document *, const std::string &) override; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; - -private: - pugi::xml_node m_last; - - static std::string text_(pugi::xml_node node); -}; - -class Frame final : public Element, public abstract::Frame { -public: - Frame(pugi::xml_node node, Path document_path, - const Relations &document_relations); - - [[nodiscard]] AnchorType - anchor_type(const abstract::Document *) const override; - - [[nodiscard]] std::optional - x(const abstract::Document *) const override; - [[nodiscard]] std::optional - y(const abstract::Document *) const override; - [[nodiscard]] std::optional - width(const abstract::Document *) const override; - [[nodiscard]] std::optional - height(const abstract::Document *) const override; - - [[nodiscard]] std::optional - z_index(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; - - [[nodiscard]] const Path & - document_path_(const abstract::Document *) const override; - [[nodiscard]] const Relations & - document_relations_(const abstract::Document *) const override; - -private: - Path m_document_path; - const Relations &m_document_relations; -}; - -class ImageElement final : public Element, public abstract::Image { -public: - using Element::Element; - - [[nodiscard]] bool is_internal(const abstract::Document *) const override; - - [[nodiscard]] std::optional - file(const abstract::Document *) const override; - - [[nodiscard]] std::string href(const abstract::Document *) const override; -}; - -} // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp new file mode 100644 index 00000000..aeefe5cd --- /dev/null +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp @@ -0,0 +1,307 @@ +#include + +#include + +#include + +namespace odr::internal::ooxml::spreadsheet { + +void ElementRegistry::clear() noexcept { + m_elements.clear(); + m_texts.clear(); + m_sheets.clear(); +} + +[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { + return m_elements.size(); +} + +std::tuple +ElementRegistry::create_element(const ElementType type, + const pugi::xml_node node) { + Element &element = m_elements.emplace_back(); + ElementIdentifier element_id = m_elements.size(); + element.type = type; + element.node = node; + return {element_id, element}; +} + +std::tuple +ElementRegistry::create_text_element(const pugi::xml_node first_node, + const pugi::xml_node last_node) { + const auto &[element_id, element] = + create_element(ElementType::text, first_node); + auto [it, success] = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_sheet_element(const pugi::xml_node node) { + const auto &[element_id, element] = create_element(ElementType::sheet, node); + auto [it, success] = m_sheets.emplace(element_id, Sheet{}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_sheet_cell_element(const pugi::xml_node node, + const TablePosition &position) { + const auto &[element_id, element] = + create_element(ElementType::sheet_cell, node); + auto [it, success] = + m_sheet_cells.emplace(element_id, SheetCell{.position = position}); + return {element_id, element, it->second}; +} + +ElementRegistry::ElementRelations & +ElementRegistry::attach_element_relations(const ElementIdentifier id, + const Relations &relations, + const AbsPath &origin) { + check_element_id(id); + if (m_element_relations.contains(id)) { + throw std::runtime_error("DocumentElementRegistry::attach_element_" + "relations: relations already attached"); + } + ElementRelations &result = m_element_relations[id]; + result.relations = &relations; + result.origin = origin; + return result; +} + +ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) { + check_element_id(id); + return m_elements.at(id - 1); +} + +ElementRegistry::Sheet & +ElementRegistry::sheet_element_at(const ElementIdentifier id) { + check_sheet_id(id); + return m_sheets.at(id); +} + +const ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id - 1); +} + +const ElementRegistry::Sheet & +ElementRegistry::sheet_element_at(const ElementIdentifier id) const { + check_sheet_id(id); + return m_sheets.at(id); +} + +ElementRegistry::Element *ElementRegistry::element(const ElementIdentifier id) { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Element * +ElementRegistry::element(const ElementIdentifier id) const { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +const ElementRegistry::ElementRelations * +ElementRegistry::element_relations(const ElementIdentifier id) const { + if (const auto it = m_element_relations.find(id); + it != m_element_relations.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) const { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet * +ElementRegistry::sheet_element(const ElementIdentifier id) const { + if (const auto it = m_sheets.find(id); it != m_sheets.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::SheetCell * +ElementRegistry::sheet_cell_element(const ElementIdentifier id) const { + if (const auto it = m_sheet_cells.find(id); it != m_sheet_cells.end()) { + return &it->second; + } + return nullptr; +} + +void ElementRegistry::check_element_id(const ElementIdentifier id) const { + if (id == null_element_id) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_text_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_cell_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_sheet_cells.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ElementIdentifier previous_sibling_id = + element_at(parent_id).last_child_id; + + element_at(child_id).parent_id = parent_id; + element_at(child_id).previous_sibling_id = previous_sibling_id; + + if (element_at(parent_id).first_child_id == null_element_id) { + element_at(parent_id).first_child_id = child_id; + } else { + element_at(previous_sibling_id).next_sibling_id = child_id; + } + element_at(parent_id).last_child_id = child_id; +} + +void ElementRegistry::append_shape(const ElementIdentifier sheet_id, + const ElementIdentifier shape_id) { + check_sheet_id(sheet_id); + check_element_id(shape_id); + + const ElementIdentifier previous_sibling_id = + sheet_element_at(sheet_id).last_shape_id; + + element_at(shape_id).parent_id = sheet_id; + element_at(shape_id).previous_sibling_id = previous_sibling_id; + + if (sheet_element_at(sheet_id).first_shape_id == null_element_id) { + sheet_element_at(sheet_id).first_shape_id = shape_id; + } else { + element_at(previous_sibling_id).next_sibling_id = shape_id; + } + sheet_element_at(sheet_id).last_shape_id = shape_id; +} + +void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, + const ElementIdentifier cell_id) { + check_sheet_id(sheet_id); + check_element_id(cell_id); + + element_at(cell_id).parent_id = sheet_id; +} + +void ElementRegistry::Sheet::register_column(const std::uint32_t column_min, + const std::uint32_t column_max, + const pugi::xml_node element) { + (void)column_min; + columns[column_max] = {.node = element}; +} + +void ElementRegistry::Sheet::register_row(const std::uint32_t row, + const pugi::xml_node element) { + rows[row].node = element; +} + +void ElementRegistry::Sheet::register_cell(const std::uint32_t column, + const std::uint32_t row, + const pugi::xml_node element, + const ElementIdentifier element_id) { + Cell &cell = cells[TablePosition(column, row)]; + cell.node = element; + cell.element_id = element_id; +} + +const ElementRegistry::Sheet::Column * +ElementRegistry::Sheet::column(const std::uint32_t column) const { + if (const auto it = util::map::lookup_greater_or_equals(columns, column); + it != std::end(columns)) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet::Row * +ElementRegistry::Sheet::row(const std::uint32_t row) const { + if (const auto it = rows.find(row); it != std::end(rows)) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Sheet::Cell * +ElementRegistry::Sheet::cell(const std::uint32_t column, + const std::uint32_t row) const { + if (const auto it = cells.find(TablePosition(column, row)); + it != std::end(cells)) { + return &it->second; + } + return nullptr; +} + +pugi::xml_node +ElementRegistry::Sheet::column_node(const std::uint32_t column) const { + if (const Column *column_entry = this->column(column); + column_entry != nullptr) { + return column_entry->node; + } + return {}; +} + +pugi::xml_node ElementRegistry::Sheet::row_node(const std::uint32_t row) const { + if (const Row *row_entry = this->row(row); row_entry != nullptr) { + return row_entry->node; + } + return {}; +} + +pugi::xml_node +ElementRegistry::Sheet::cell_node(const std::uint32_t column, + const std::uint32_t row) const { + if (const Cell *cell_entry = this->cell(column, row); cell_entry != nullptr) { + return cell_entry->node; + } + return {}; +} + +} // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp new file mode 100644 index 00000000..629449c3 --- /dev/null +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::spreadsheet { + +class ElementRegistry final { +public: + struct Element final { + ElementIdentifier parent_id{null_element_id}; + ElementIdentifier first_child_id{null_element_id}; + ElementIdentifier last_child_id{null_element_id}; + ElementIdentifier previous_sibling_id{null_element_id}; + ElementIdentifier next_sibling_id{null_element_id}; + ElementType type{ElementType::none}; + pugi::xml_node node; + bool is_editable{false}; + }; + + struct ElementRelations final { + const Relations *relations{nullptr}; + AbsPath origin; + }; + + struct Text final { + pugi::xml_node last; + }; + + struct Sheet final { + struct Column final { + pugi::xml_node node; + }; + + struct Row final { + pugi::xml_node node; + }; + + struct Cell final { + pugi::xml_node node; + ElementIdentifier element_id{null_element_id}; + }; + + TableDimensions dimensions; + + std::map columns; + std::unordered_map rows; + std::unordered_map cells; + + ElementIdentifier first_shape_id{null_element_id}; + ElementIdentifier last_shape_id{null_element_id}; + + void register_column(std::uint32_t column_min, std::uint32_t column_max, + pugi::xml_node element); + void register_row(std::uint32_t row, pugi::xml_node element); + void register_cell(std::uint32_t column, std::uint32_t row, + pugi::xml_node element, ElementIdentifier element_id); + + [[nodiscard]] const Column *column(std::uint32_t column) const; + [[nodiscard]] const Row *row(std::uint32_t row) const; + [[nodiscard]] const Cell *cell(std::uint32_t column, + std::uint32_t row) const; + + [[nodiscard]] pugi::xml_node column_node(std::uint32_t column) const; + [[nodiscard]] pugi::xml_node row_node(std::uint32_t row) const; + [[nodiscard]] pugi::xml_node cell_node(std::uint32_t column, + std::uint32_t row) const; + }; + + struct SheetCell final { + TablePosition position; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + std::tuple create_element(ElementType type, + pugi::xml_node node); + std::tuple + create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); + std::tuple + create_sheet_element(pugi::xml_node node); + std::tuple + create_sheet_cell_element(pugi::xml_node node, const TablePosition &position); + + ElementRelations &attach_element_relations(ElementIdentifier id, + const Relations &relations, + const AbsPath &origin); + + [[nodiscard]] Element &element_at(ElementIdentifier id); + [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); + + [[nodiscard]] const Element &element_at(ElementIdentifier id) const; + [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; + + [[nodiscard]] Element *element(ElementIdentifier id); + [[nodiscard]] Text *text_element(ElementIdentifier id); + + [[nodiscard]] const Element *element(ElementIdentifier id) const; + [[nodiscard]] const ElementRelations * + element_relations(ElementIdentifier id) const; + [[nodiscard]] const Text *text_element(ElementIdentifier id) const; + [[nodiscard]] const Sheet *sheet_element(ElementIdentifier id) const; + [[nodiscard]] const SheetCell *sheet_cell_element(ElementIdentifier id) const; + + void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + void append_shape(ElementIdentifier sheet_id, ElementIdentifier shape_id); + void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); + +private: + std::vector m_elements; + std::unordered_map m_element_relations; + std::unordered_map m_texts; + std::unordered_map m_sheets; + std::unordered_map m_sheet_cells; + + void check_element_id(ElementIdentifier id) const; + void check_text_id(ElementIdentifier id) const; + void check_sheet_id(ElementIdentifier id) const; + void check_sheet_cell_id(ElementIdentifier id) const; +}; + +} // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp index 62311022..8bcfc06e 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp @@ -1,143 +1,127 @@ #include +#include +#include #include -#include +#include #include +#include + namespace odr::internal::ooxml::spreadsheet { namespace { -template -std::tuple -parse_element_tree(Document &document, pugi::xml_node node, - const Path &document_path, - const Relations &document_relations); -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node, - const Path &document_path, - const Relations &document_relations); - -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node, - const Path &document_path, - const Relations &document_relations); - -void parse_element_children(Document &document, Element *element, - const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { +using TreeParser = std::function( + ElementRegistry ®istry, const ParseContext &context, + pugi::xml_node node)>; +using ChildrenParser = + std::function; + +std::tuple +parse_any_element_tree(ElementRegistry ®istry, const ParseContext &context, + pugi::xml_node node); + +void parse_any_element_children(ElementRegistry ®istry, + const ParseContext &context, + const ElementIdentifier parent_id, + const pugi::xml_node node) { for (pugi::xml_node child_node = node.first_child(); child_node;) { - auto [child, next_sibling] = parse_any_element_tree( - document, child_node, document_path, document_relations); - if (child == nullptr) { + const auto [child_id, next_sibling] = + parse_any_element_tree(registry, context, child_node); + if (child_id == null_element_id) { child_node = child_node.next_sibling(); - } else { - element->append_child_(child); - child_node = next_sibling; + continue; } + + registry.append_child(parent_id, child_id); + child_node = next_sibling; } } -void parse_element_children(Document &document, Root *element, - const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { +std::tuple +parse_element_tree(ElementRegistry ®istry, const ParseContext &context, + const ElementType type, const pugi::xml_node node, + const ChildrenParser &children_parser) { + if (!node) { + return {null_element_id, pugi::xml_node()}; + } + + const auto &[element_id, _] = registry.create_element(type, node); + + children_parser(registry, context, element_id, node); + + return {element_id, node.next_sibling()}; +} + +void parse_root_children(ElementRegistry ®istry, const ParseContext &context, + const ElementIdentifier parent_id, + const pugi::xml_node node) { for (pugi::xml_node child_node : node.child("sheets").children("sheet")) { const char *id = child_node.attribute("r:id").value(); - Path sheet_path = - document_path.parent().join(RelPath(document_relations.at(id))); - auto [sheet_xml, sheet_relations] = document.get_xml(sheet_path); - auto [sheet, _] = parse_element_tree( - document, sheet_xml.document_element(), sheet_path, sheet_relations); - element->append_child_(sheet); + AbsPath sheet_path = context.get_document_path().parent().join( + RelPath(context.get_document_relations().at(id))); + const auto &[sheet_xml, sheet_relations] = + context.get_documents_and_relations().at(sheet_path); + ParseContext newContext(sheet_path, sheet_relations, + context.get_documents_and_relations(), + context.get_shared_strings()); + const auto &[sheet, _] = parse_any_element_tree( + registry, newContext, sheet_xml.document_element()); + registry.append_child(parent_id, sheet); } } -void parse_element_children(Document &document, SheetCell *element, - const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { +void parse_sheet_cell_children(ElementRegistry ®istry, + const ParseContext &context, + const ElementIdentifier parent_id, + const pugi::xml_node node) { if (const pugi::xml_attribute type_attr = node.attribute("t"); type_attr.value() == std::string("s")) { const pugi::xml_node v_node = node.child("v"); const std::size_t ref = v_node.first_child().text().as_ullong(); - const pugi::xml_node shared_node = document.get_shared_string(ref); - parse_element_children(document, dynamic_cast(element), - shared_node, document_path, document_relations); + const pugi::xml_node shared_node = context.get_shared_strings().at(ref); + parse_any_element_children(registry, context, parent_id, shared_node); return; } - parse_element_children(document, dynamic_cast(element), node, - document_path, document_relations); -} - -void parse_element_children(Document &document, Frame *element, - const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { - if (const pugi::xml_node image_node = - node.child("xdr:pic").child("xdr:blipFill").child("a:blip")) { - auto [image, _] = parse_element_tree( - document, image_node, document_path, document_relations); - element->append_child_(image); - } -} - -template -std::tuple -parse_element_tree(Document &document, const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { - if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); - } - - auto element_unique = - std::make_unique(node, document_path, document_relations); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); - - parse_element_children(document, element, node, document_path, - document_relations); - - return std::make_tuple(element, node.next_sibling()); + parse_any_element_children(registry, context, parent_id, node); } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { +std::tuple +parse_sheet_element(ElementRegistry ®istry, const ParseContext &context, + const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto element_unique = - std::make_unique(node, document_path, document_relations); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _, sheet] = registry.create_sheet_element(node); + registry.attach_element_relations(element_id, + context.get_document_relations(), + context.get_document_path()); for (const pugi::xml_node col_node : node.child("cols").children("col")) { const std::uint32_t min = col_node.attribute("min").as_uint() - 1; const std::uint32_t max = col_node.attribute("max").as_uint() - 1; - element->init_column_(min, max, col_node); + sheet.register_column(min, max, col_node); } for (const pugi::xml_node row_node : node.child("sheetData").children("row")) { const std::uint32_t row = row_node.attribute("r").as_uint() - 1; - element->init_row_(row, row_node); + sheet.register_row(row, row_node); for (const pugi::xml_node cell_node : row_node.children("c")) { TablePosition position(cell_node.attribute("r").value()); - element->init_cell_(position.column(), position.row(), cell_node); - auto [cell, _] = parse_element_tree( - document, cell_node, document_path, document_relations); - element->init_cell_element_(position.column(), position.row(), cell); + const auto &[cell_id, _, __] = + registry.create_sheet_cell_element(cell_node, position); + registry.append_sheet_cell(element_id, cell_id); + sheet.register_cell(position.column(), position.row(), cell_node, + cell_id); + parse_sheet_cell_children(registry, context, cell_id, cell_node); } } @@ -150,28 +134,33 @@ parse_element_tree(Document &document, pugi::xml_node node, } else { position_to = TableRange(dimension_ref).to(); } - element->init_dimensions_( - TableDimensions(position_to.row() + 1, position_to.column() + 1)); + sheet.dimensions = + TableDimensions(position_to.row() + 1, position_to.column() + 1); } if (const pugi::xml_node drawing_node = node.child("drawing")) { const char *id = drawing_node.attribute("r:id").value(); - const Path drawing_path = - document_path.parent().join(RelPath(document_relations.at(id))); + const AbsPath drawing_path = context.get_document_path().parent().join( + RelPath(context.get_document_relations().at(id))); + const auto &[drawing_xml, drawing_relations] = + context.get_documents_and_relations().at(drawing_path); - for (const auto [drawing_xml, drawing_relations] = - document.get_xml(drawing_path); - const pugi::xml_node shape_node : + const ParseContext drawing_context(drawing_path, drawing_relations, + context.get_documents_and_relations(), + context.get_shared_strings()); + + for (const pugi::xml_node shape_node : drawing_xml.document_element().children()) { - auto [shape, _] = parse_any_element_tree(document, shape_node, - drawing_path, drawing_relations); - if (shape != nullptr) { - element->append_shape_(shape); + const auto [shape, _] = + parse_any_element_tree(registry, drawing_context, shape_node); + if (shape == null_element_id) { + continue; } + registry.append_shape(element_id, shape); } } - return std::make_tuple(element, node.next_sibling()); + return {element_id, node.next_sibling()}; } bool is_text_node(const pugi::xml_node node) { @@ -181,61 +170,89 @@ bool is_text_node(const pugi::xml_node node) { const std::string name = node.name(); - if (name == "w:t") { + if (name == "t") { return true; } - if (name == "w:tab") { + if (name == "v") { return true; } return false; } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node first, - const Path &document_path, - const Relations &document_relations) { +std::tuple +parse_text_element(ElementRegistry ®istry, const ParseContext &context, + const pugi::xml_node first) { + (void)context; + if (!first) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - pugi::xml_node last = first; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { + pugi::xml_node last; + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { } - auto element_unique = - std::make_unique(first, last, document_path, document_relations); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _, __] = registry.create_text_element(first, last); + + return {element_id, last.next_sibling()}; +} + +std::tuple +parse_frame_element(ElementRegistry ®istry, const ParseContext &context, + const pugi::xml_node node) { + if (!node) { + return {null_element_id, pugi::xml_node()}; + } + + const auto &[element_id, _] = + registry.create_element(ElementType::frame, node); + registry.attach_element_relations(element_id, + context.get_document_relations(), + context.get_document_path()); + + if (const pugi::xml_node image_node = + node.child("xdr:pic").child("xdr:blipFill").child("a:blip")) { + auto [image, _] = + parse_element_tree(registry, context, ElementType::image, image_node, + parse_any_element_children); + registry.append_child(element_id, image); + } - return std::make_tuple(element, last.next_sibling()); + return {element_id, node.next_sibling()}; } -std::tuple -parse_any_element_tree(Document &document, const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { - using Parser = std::function( - Document & document, pugi::xml_node node, const Path &document_path, - const Relations &document_relations)>; - - static std::unordered_map parser_table{ - {"workbook", parse_element_tree}, - {"worksheet", parse_element_tree}, - {"r", parse_element_tree}, - {"t", parse_element_tree}, - {"v", parse_element_tree}, - {"xdr:twoCellAnchor", parse_element_tree}, - }; +std::tuple +parse_any_element_tree(ElementRegistry ®istry, const ParseContext &context, + const pugi::xml_node node) { + const auto create_default_tree_parser = + [](const ElementType type, + const ChildrenParser &children_parser = parse_any_element_children) { + return + [type, children_parser](ElementRegistry &r, const ParseContext &c, + const pugi::xml_node n) { + return parse_element_tree(r, c, type, n, children_parser); + }; + }; + + static std::unordered_map parser_table{ + {"workbook", + create_default_tree_parser(ElementType::root, parse_root_children)}, + {"worksheet", parse_sheet_element}, + {"c", create_default_tree_parser(ElementType::sheet_cell, + parse_sheet_cell_children)}, + {"r", create_default_tree_parser(ElementType::span)}, + {"t", parse_text_element}, + {"v", parse_text_element}, + {"xdr:twoCellAnchor", parse_frame_element}}; if (const auto constructor_it = parser_table.find(node.name()); constructor_it != std::end(parser_table)) { - return constructor_it->second(document, node, document_path, - document_relations); + return constructor_it->second(registry, context, node); } - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } } // namespace @@ -244,13 +261,11 @@ parse_any_element_tree(Document &document, const pugi::xml_node node, namespace odr::internal::ooxml { -spreadsheet::Element * -spreadsheet::parse_tree(Document &document, const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { - auto [root_id, _] = - parse_any_element_tree(document, node, document_path, document_relations); - return root_id; +ElementIdentifier spreadsheet::parse_tree(ElementRegistry ®istry, + const ParseContext &context, + const pugi::xml_node node) { + auto [root, _] = parse_any_element_tree(registry, context, node); + return root; } } // namespace odr::internal::ooxml diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp index 17a07c68..0a8456c7 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp @@ -1,14 +1,44 @@ #pragma once +#include #include #include +namespace pugi { +class xml_node; +} + namespace odr::internal::ooxml::spreadsheet { -class Document; -class Element; +class ElementRegistry; + +class ParseContext { +public: + ParseContext(const AbsPath &document_path, + const Relations &document_relations, + const XmlDocumentsAndRelations &xml_documents_and_relations, + const SharedStrings &shared_strings) + : m_document_path(&document_path), + m_document_relations(&document_relations), + m_xml_documents_and_relations(&xml_documents_and_relations), + m_shared_strings(&shared_strings) {} + + const AbsPath &get_document_path() const { return *m_document_path; } + const Relations &get_document_relations() const { + return *m_document_relations; + } + const XmlDocumentsAndRelations &get_documents_and_relations() const { + return *m_xml_documents_and_relations; + } + const SharedStrings &get_shared_strings() const { return *m_shared_strings; } + +private: + const AbsPath *m_document_path{}; + const Relations *m_document_relations{}; + const XmlDocumentsAndRelations *m_xml_documents_and_relations{}; + const SharedStrings *m_shared_strings{}; +}; -Element *parse_tree(Document &document, pugi::xml_node node, - const Path &document_path, - const Relations &document_relations); +ElementIdentifier parse_tree(ElementRegistry ®istry, + const ParseContext &context, pugi::xml_node node); } // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp index f2bf4df0..5019a888 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_style.cpp @@ -3,7 +3,6 @@ #include #include -#include namespace odr::internal::ooxml::spreadsheet { @@ -115,7 +114,8 @@ ResolvedStyle StyleRegistry::cell_style(const std::uint32_t i) const { read_horizontal(alignment.attribute("horizontal")); result.table_cell_style.vertical_align = read_vertical(alignment.attribute("vertical")); - if (auto text_rotation = alignment.attribute("textRotation").as_float(); + if (const float text_rotation = + alignment.attribute("textRotation").as_float(); text_rotation != 0) { result.table_cell_style.text_rotation = text_rotation; } diff --git a/src/odr/internal/ooxml/text/ooxml_text_document.cpp b/src/odr/internal/ooxml/text/ooxml_text_document.cpp index 904fd92e..1f112ab2 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_document.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_document.cpp @@ -1,34 +1,63 @@ #include #include +#include +#include #include #include +#include #include #include #include +#include #include #include +#include #include #include namespace odr::internal::ooxml::text { -Document::Document(std::shared_ptr filesystem) - : TemplateDocument(FileType::office_open_xml_document, DocumentType::text, - std::move(filesystem)) { - m_document_xml = - util::xml::parse(*m_filesystem, AbsPath("/word/document.xml")); - m_styles_xml = util::xml::parse(*m_filesystem, AbsPath("/word/styles.xml")); +namespace { +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry, + const Relations &document_relations); +} + +Document::Document(std::shared_ptr files) + : internal::Document(FileType::office_open_xml_document, DocumentType::text, + std::move(files)) { + m_document_xml = util::xml::parse(*m_files, AbsPath("/word/document.xml")); + m_styles_xml = util::xml::parse(*m_files, AbsPath("/word/styles.xml")); m_document_relations = - parse_relationships(*m_filesystem, AbsPath("/word/document.xml")); + parse_relationships(*m_files, AbsPath("/word/document.xml")); - m_root_element = - parse_tree(*this, m_document_xml.document_element().child("w:body")); + m_root_element = parse_tree( + m_element_registry, m_document_xml.document_element().child("w:body")); m_style_registry = StyleRegistry(m_styles_xml.document_element()); + + m_element_adapter = + create_element_adapter(*this, m_element_registry, m_document_relations); +} + +ElementRegistry &Document::element_registry() { return m_element_registry; } + +StyleRegistry &Document::style_registry() { return m_style_registry; } + +const ElementRegistry &Document::element_registry() const { + return m_element_registry; +} + +const StyleRegistry &Document::style_registry() const { + return m_style_registry; +} + +const Relations &Document::document_relations() const { + return m_document_relations; } bool Document::is_editable() const noexcept { return true; } @@ -41,7 +70,7 @@ void Document::save(const Path &path) const { // TODO this would decrypt/inflate and encrypt/deflate again zip::ZipArchive archive; - for (auto walker = m_filesystem->file_walker(AbsPath("/")); !walker->end(); + for (auto walker = m_files->file_walker(AbsPath("/")); !walker->end(); walker->next()) { const AbsPath &abs_path = walker->path(); RelPath rel_path = walker->path().rebase(AbsPath("/")); @@ -57,8 +86,7 @@ void Document::save(const Path &path) const { archive.insert_file(std::end(archive), rel_path, tmp); continue; } - archive.insert_file(std::end(archive), rel_path, - m_filesystem->open(abs_path)); + archive.insert_file(std::end(archive), rel_path, m_files->open(abs_path)); } std::ofstream ostream = util::file::create(path.string()); @@ -69,4 +97,646 @@ void Document::save(const Path & /*path*/, const char * /*password*/) const { throw UnsupportedOperation(); } +namespace { + +class ElementAdapter final : public abstract::ElementAdapter, + public abstract::TextRootAdapter, + public abstract::LineBreakAdapter, + public abstract::ParagraphAdapter, + public abstract::SpanAdapter, + public abstract::TextAdapter, + public abstract::LinkAdapter, + public abstract::BookmarkAdapter, + public abstract::ListItemAdapter, + public abstract::TableAdapter, + public abstract::TableColumnAdapter, + public abstract::TableRowAdapter, + public abstract::TableCellAdapter, + public abstract::FrameAdapter, + public abstract::ImageAdapter { +public: + ElementAdapter(const Document &document, ElementRegistry ®istry, + const Relations &document_relations) + : m_document(&document), m_registry(®istry), + m_document_relations(&document_relations) {} + + [[nodiscard]] ElementType + element_type(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->type; + } + return ElementType::none; + } + + [[nodiscard]] ElementIdentifier + element_parent(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->parent_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_first_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->first_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_last_child(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->last_child_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_previous_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->previous_sibling_id; + } + return null_element_id; + } + [[nodiscard]] ElementIdentifier + element_next_sibling(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->next_sibling_id; + } + return null_element_id; + } + + [[nodiscard]] bool + element_is_editable(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->is_editable; + } + return false; + } + + [[nodiscard]] const TextRootAdapter * + text_root_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::root) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetAdapter * + sheet_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetCellAdapter * + sheet_cell_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::line_break) { + return nullptr; + } + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::paragraph) { + return nullptr; + } + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::span) { + return nullptr; + } + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::text) { + return nullptr; + } + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::link) { + return nullptr; + } + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::bookmark) { + return nullptr; + } + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::list_item) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_column) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_row) { + return nullptr; + } + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::table_cell) { + return nullptr; + } + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::frame) { + return nullptr; + } + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ElementIdentifier element_id) const override { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element == nullptr || element->type != ElementType::image) { + return nullptr; + } + return this; + } + + [[nodiscard]] PageLayout + text_root_page_layout(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + [[nodiscard]] ElementIdentifier text_root_first_master_page( + const ElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] TextStyle + line_break_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle + paragraph_text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ElementIdentifier element_id) const override { + const ElementRegistry::Text *text_element = + m_registry->text_element(element_id); + if (text_element == nullptr) { + return ""; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + std::string result; + for (pugi::xml_node node = first; node != last.next_sibling(); + node = node.next_sibling()) { + result += get_text(node); + } + return result; + } + void text_set_content(const ElementIdentifier element_id, + const std::string &text) const override { + ElementRegistry::Element *element = m_registry->element(element_id); + ElementRegistry::Text *text_element = m_registry->text_element(element_id); + if (element == nullptr || text_element == nullptr) { + return; + } + + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = text_element->last; + + pugi::xml_node parent = first.parent(); + const pugi::xml_node old_first = first; + const pugi::xml_node old_last = last; + pugi::xml_node new_first = old_first; + pugi::xml_node new_last = last; + + const auto insert_node = [&](const char *node) { + const pugi::xml_node new_node = + parent.insert_child_before(node, old_first); + if (new_first == old_first) { + new_first = new_node; + } + new_last = new_node; + return new_node; + }; + + for (const util::xml::StringToken &token : util::xml::tokenize_text(text)) { + switch (token.type) { + case util::xml::StringToken::Type::none: + break; + case util::xml::StringToken::Type::string: { + auto text_node = insert_node("w:t"); + text_node.append_child(pugi::xml_node_type::node_pcdata) + .text() + .set(token.string.c_str()); + } break; + case util::xml::StringToken::Type::spaces: { + auto text_node = insert_node("w:t"); + text_node.append_attribute("xml:space").set_value("preserve"); + text_node.append_child(pugi::xml_node_type::node_pcdata) + .text() + .set(token.string.c_str()); + } break; + case util::xml::StringToken::Type::tabs: { + for (std::size_t i = 0; i < token.string.size(); ++i) { + insert_node("w:tab"); + } + } break; + } + } + + element->node = new_first; + text_element->last = new_last; + + for (pugi::xml_node node = old_first; node != old_last.next_sibling();) { + const pugi::xml_node next = node.next_sibling(); + parent.remove_child(node); + node = next; + } + } + [[nodiscard]] TextStyle + text_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute anchor = node.attribute("w:anchor")) { + return std::string("#") + anchor.value(); + } + if (const pugi::xml_attribute ref = node.attribute("r:id")) { + const auto relations = get_document_relations(); + if (const auto rel = relations.find(ref.value()); + rel != std::end(relations)) { + return rel->second; + } + } + return ""; + } + + [[nodiscard]] std::string + bookmark_name(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("text:name").value(); + } + + [[nodiscard]] TextStyle + list_item_style(const ElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TableDimensions + table_dimensions(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + + TableDimensions result; + TableCursor cursor; + + for (auto column : node.children("table:table-column")) { + const auto columns_repeated = + column.attribute("table:number-columns-repeated").as_uint(1); + cursor.add_column(columns_repeated); + } + + result.columns = cursor.column(); + cursor = {}; + + for (auto row : node.children("table:table-row")) { + const auto rows_repeated = + row.attribute("table:number-rows-repeated").as_uint(1); + cursor.add_row(rows_repeated); + } + + result.rows = cursor.row(); + + return result; + } + [[nodiscard]] ElementIdentifier + table_first_column(const ElementIdentifier element_id) const override { + return m_registry->table_element_at(element_id).first_column_id; + } + [[nodiscard]] ElementIdentifier + table_first_row(const ElementIdentifier element_id) const override { + return element_first_child(element_id); + } + [[nodiscard]] TableStyle + table_style(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return m_document->style_registry().partial_table_style(node).table_style; + } + + [[nodiscard]] TableColumnStyle + table_column_style(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + TableColumnStyle result; + if (const std::optional width = + read_twips_attribute(node.attribute("w:w"))) { + result.width = width; + } + return result; + } + + [[nodiscard]] TableRowStyle + table_row_style(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return m_document->style_registry() + .partial_table_row_style(node) + .table_row_style; + } + + [[nodiscard]] bool + table_cell_is_covered(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return std::strcmp(node.name(), "table:covered-table-cell") == 0; + } + [[nodiscard]] TableDimensions + table_cell_span(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return {node.attribute("table:number-rows-spanned").as_uint(1), + node.attribute("table:number-columns-spanned").as_uint(1)}; + } + [[nodiscard]] ValueType + table_cell_value_type(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const char *value_type = node.attribute("office:value-type").value(); + std::strcmp("float", value_type) == 0) { + return ValueType::float_number; + } + return ValueType::string; + } + [[nodiscard]] TableCellStyle + table_cell_style(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return m_document->style_registry() + .partial_table_cell_style(node) + .table_cell_style; + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (node.child("wp:inline")) { + return AnchorType::as_char; + } + return AnchorType::as_char; // TODO default? + } + [[nodiscard]] std::optional + frame_x(const ElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_y(const ElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_width(const ElementIdentifier element_id) const override { + const pugi::xml_node inner_node = get_frame_inner_node(element_id); + if (const std::optional width = read_emus_attribute( + inner_node.child("wp:extent").attribute("cx"))) { + return width->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_height(const ElementIdentifier element_id) const override { + const pugi::xml_node inner_node = get_frame_inner_node(element_id); + if (const std::optional height = read_emus_attribute( + inner_node.child("wp:extent").attribute("cy"))) { + return height->to_string(); + } + return {}; + } + [[nodiscard]] std::optional + frame_z_index(const ElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return false; + } + try { + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return m_document->as_filesystem()->is_file(path); + } catch (...) { + } + return false; + } + [[nodiscard]] std::optional + image_file(const ElementIdentifier element_id) const override { + if (m_document->as_filesystem() == nullptr) { + return std::nullopt; + } + const AbsPath path = Path(image_href(element_id)).make_absolute(); + return File(m_document->as_filesystem()->open(path)); + } + [[nodiscard]] std::string + image_href(const ElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute ref = node.child("pic:pic") + .child("pic:blipFill") + .child("a:blip") + .attribute("r:embed")) { + if (const auto rel = m_document_relations->find(ref.value()); + rel != std::end(*m_document_relations)) { + return AbsPath("/word").join(RelPath(rel->second)).string(); + } + } + return ""; // TODO + } + +private: + const Document *m_document{nullptr}; + ElementRegistry *m_registry{nullptr}; + const Relations *m_document_relations{nullptr}; + + [[nodiscard]] pugi::xml_node + get_node(const ElementIdentifier element_id) const { + if (const ElementRegistry::Element *element = + m_registry->element(element_id); + element != nullptr) { + return element->node; + } + return {}; + } + + [[nodiscard]] pugi::xml_node + get_frame_inner_node(const ElementIdentifier element_id) const { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_node anchor = node.child("wp:anchor")) { + return anchor; + } + if (const pugi::xml_node inline_node = node.child("wp:inline")) { + return inline_node; + } + return {}; + } + + [[nodiscard]] const Relations &get_document_relations() const { + return m_document->document_relations(); + } + + [[nodiscard]] static std::string get_text(const pugi::xml_node node) { + const std::string name = node.name(); + + if (name == "w:t") { + return node.text().get(); + } + if (name == "w:tab") { + return "\t"; + } + + return ""; + } + + [[nodiscard]] ResolvedStyle + get_partial_style(const ElementIdentifier element_id) const { + const ElementRegistry::Element &element = + m_registry->element_at(element_id); + if (element.type == ElementType::paragraph) { + return m_document->style_registry().partial_paragraph_style(element.node); + } + if (element.type == ElementType::span) { + return m_document->style_registry().partial_text_style(element.node); + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ElementIdentifier element_id) const { + const ElementIdentifier parent_id = element_parent(element_id); + ResolvedStyle base; + if (parent_id == null_element_id) { + base = m_document->style_registry().default_style()->resolved(); + } else { + base = get_intermediate_style(parent_id); + } + base.override(get_partial_style(element_id)); + return base; + } +}; + +std::unique_ptr +create_element_adapter(const Document &document, ElementRegistry ®istry, + const Relations &document_relations) { + return std::make_unique(document, registry, + document_relations); +} + +} // namespace + } // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_document.hpp b/src/odr/internal/ooxml/text/ooxml_text_document.hpp index 95d2a8b3..734311ce 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_document.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_document.hpp @@ -1,19 +1,27 @@ #pragma once #include -#include -#include +#include +#include #include #include -#include #include +#include + namespace odr::internal::ooxml::text { -class Document final : public TemplateDocument { +class Document final : public internal::Document { public: - explicit Document(std::shared_ptr filesystem); + explicit Document(std::shared_ptr files); + + ElementRegistry &element_registry(); + StyleRegistry &style_registry(); + + [[nodiscard]] const ElementRegistry &element_registry() const; + [[nodiscard]] const StyleRegistry &style_registry() const; + [[nodiscard]] const Relations &document_relations() const; [[nodiscard]] bool is_editable() const noexcept override; [[nodiscard]] bool is_savable(bool encrypted) const noexcept override; @@ -25,11 +33,10 @@ class Document final : public TemplateDocument { pugi::xml_document m_document_xml; pugi::xml_document m_styles_xml; - std::unordered_map m_document_relations; + Relations m_document_relations; + ElementRegistry m_element_registry; StyleRegistry m_style_registry; - - friend class Element; }; } // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_element.cpp b/src/odr/internal/ooxml/text/ooxml_text_element.cpp deleted file mode 100644 index 0980ab8c..00000000 --- a/src/odr/internal/ooxml/text/ooxml_text_element.cpp +++ /dev/null @@ -1,323 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include - -#include - -namespace odr::internal::ooxml::text { - -Element::Element(const pugi::xml_node node) : m_node{node} { - if (!node) { - // TODO log error - throw std::runtime_error("node not set"); - } -} - -ResolvedStyle Element::partial_style(const abstract::Document *) const { - return {}; -} - -ResolvedStyle -Element::intermediate_style(const abstract::Document *document) const { - ResolvedStyle base; - if (abstract::Element *parent = this->parent(document); parent == nullptr) { - base = style_(document)->default_style()->resolved(); - } else { - base = dynamic_cast(parent)->intermediate_style(document); - } - base.override(partial_style(document)); - return base; -} - -bool Element::is_editable(const abstract::Document *document) const { - if (m_parent == nullptr) { - return document_(document)->is_editable(); - } - return m_parent->is_editable(document); -} - -const Document *Element::document_(const abstract::Document *document) { - return dynamic_cast(document); -} - -const StyleRegistry *Element::style_(const abstract::Document *document) { - return &dynamic_cast(document)->m_style_registry; -} - -const std::unordered_map & -Element::document_relations_(const abstract::Document *document) { - return dynamic_cast(document)->m_document_relations; -} - -PageLayout Root::page_layout(const abstract::Document *) const { - return {}; // TODO -} - -abstract::Element *Root::first_master_page(const abstract::Document *) const { - return {}; // TODO -} - -ResolvedStyle -Paragraph::partial_style(const abstract::Document *document) const { - return style_(document)->partial_paragraph_style(m_node); -} - -ParagraphStyle Paragraph::style(const abstract::Document *document) const { - return intermediate_style(document).paragraph_style; -} - -TextStyle Paragraph::text_style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -ResolvedStyle Span::partial_style(const abstract::Document *document) const { - return style_(document)->partial_text_style(m_node); -} - -TextStyle Span::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -Text::Text(const pugi::xml_node node) : Text(node, node) {} - -Text::Text(const pugi::xml_node first, const pugi::xml_node last) - : Element(first), m_last{last} { - if (!last) { - // TODO log error - throw std::runtime_error("last not set"); - } -} - -std::string Text::content(const abstract::Document *) const { - std::string result; - for (auto node = m_node; node != m_last.next_sibling(); - node = node.next_sibling()) { - result += text_(node); - } - return result; -} - -void Text::set_content(const abstract::Document *, const std::string &text) { - // TODO http://officeopenxml.com/WPtextSpacing.php - // - // use `xml:space` - - pugi::xml_node parent = m_node.parent(); - const pugi::xml_node old_first = m_node; - const pugi::xml_node old_last = m_last; - pugi::xml_node new_first = old_first; - pugi::xml_node new_last = m_last; - - const auto insert_node = [&](const char *node) { - const pugi::xml_node new_node = parent.insert_child_before(node, old_first); - if (new_first == old_first) { - new_first = new_node; - } - new_last = new_node; - return new_node; - }; - - for (const util::xml::StringToken &token : util::xml::tokenize_text(text)) { - switch (token.type) { - case util::xml::StringToken::Type::none: - break; - case util::xml::StringToken::Type::string: { - auto text_node = insert_node("w:t"); - text_node.append_child(pugi::xml_node_type::node_pcdata) - .text() - .set(token.string.c_str()); - } break; - case util::xml::StringToken::Type::spaces: { - auto text_node = insert_node("w:t"); - text_node.append_attribute("xml:space").set_value("preserve"); - text_node.append_child(pugi::xml_node_type::node_pcdata) - .text() - .set(token.string.c_str()); - } break; - case util::xml::StringToken::Type::tabs: { - for (std::size_t i = 0; i < token.string.size(); ++i) { - insert_node("w:tab"); - } - } break; - } - } - - m_node = new_first; - m_last = new_last; - - for (pugi::xml_node node = old_first; node != old_last.next_sibling();) { - const pugi::xml_node next = node.next_sibling(); - parent.remove_child(node); - node = next; - } -} - -TextStyle Text::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -std::string Text::text_(const pugi::xml_node node) { - const std::string name = node.name(); - - if (name == "w:t") { - return node.text().get(); - } - if (name == "w:tab") { - return "\t"; - } - - return ""; -} - -std::string Link::href(const abstract::Document *document) const { - if (const pugi::xml_attribute anchor = m_node.attribute("w:anchor")) { - return std::string("#") + anchor.value(); - } - if (const pugi::xml_attribute ref = m_node.attribute("r:id")) { - auto relations = document_relations_(document); - if (const auto rel = relations.find(ref.value()); - rel != std::end(relations)) { - return rel->second; - } - } - return ""; -} - -std::string Bookmark::name(const abstract::Document *) const { - return m_node.attribute("w:name").value(); -} - -ElementType List::type(const abstract::Document *) const { - return ElementType::list; -} - -TextStyle ListItem::style(const abstract::Document *document) const { - return intermediate_style(document).text_style; -} - -TableDimensions Table::dimensions(const abstract::Document *) const { - return {}; // TODO -} - -TableStyle Table::style(const abstract::Document *document) const { - return style_(document)->partial_table_style(m_node).table_style; -} - -TableColumnStyle TableColumn::style(const abstract::Document *) const { - TableColumnStyle result; - if (const std::optional width = - read_twips_attribute(m_node.attribute("w:w"))) { - result.width = width; - } - return result; -} - -TableRowStyle TableRow::style(const abstract::Document *document) const { - return style_(document)->partial_table_row_style(m_node).table_row_style; -} - -bool TableCell::is_covered(const abstract::Document *) const { return false; } - -TableDimensions TableCell::span(const abstract::Document *) const { - return {1, 1}; -} - -ValueType TableCell::value_type(const abstract::Document *) const { - return ValueType::string; -} - -TableCellStyle TableCell::style(const abstract::Document *document) const { - return style_(document)->partial_table_cell_style(m_node).table_cell_style; -} - -AnchorType Frame::anchor_type(const abstract::Document *) const { - if (m_node.child("wp:inline")) { - return AnchorType::as_char; - } - return AnchorType::as_char; // TODO default? -} - -std::optional Frame::x(const abstract::Document *) const { - return {}; -} - -std::optional Frame::y(const abstract::Document *) const { - return {}; -} - -std::optional -Frame::width(const abstract::Document *document) const { - if (const std::optional width = read_emus_attribute( - inner_node_(document).child("wp:extent").attribute("cx"))) { - return width->to_string(); - } - return {}; -} - -std::optional -Frame::height(const abstract::Document *document) const { - if (const std::optional height = read_emus_attribute( - inner_node_(document).child("wp:extent").attribute("cy"))) { - return height->to_string(); - } - return {}; -} - -std::optional Frame::z_index(const abstract::Document *) const { - return {}; -} - -GraphicStyle Frame::style(const abstract::Document *) const { return {}; } - -pugi::xml_node Frame::inner_node_(const abstract::Document *) const { - if (const pugi::xml_node anchor = m_node.child("wp:anchor")) { - return anchor; - } - if (const pugi::xml_node inline_node = m_node.child("wp:inline")) { - return inline_node; - } - return {}; -} - -bool Image::is_internal(const abstract::Document *document) const { - const Document *doc = document_(document); - if (doc == nullptr || !doc->as_filesystem()) { - return false; - } - try { - return doc->as_filesystem()->is_file(AbsPath(href(document))); - } catch (...) { - } - return false; -} - -std::optional Image::file(const abstract::Document *document) const { - const Document *doc = document_(document); - if (doc == nullptr || !is_internal(document)) { - return {}; - } - const AbsPath path = Path(href(document)).make_absolute(); - return File(doc->as_filesystem()->open(path)); -} - -std::string Image::href(const abstract::Document *document) const { - if (const pugi::xml_attribute ref = m_node.child("pic:pic") - .child("pic:blipFill") - .child("a:blip") - .attribute("r:embed")) { - auto relations = document_relations_(document); - if (const auto rel = relations.find(ref.value()); - rel != std::end(relations)) { - return AbsPath("/word").join(RelPath(rel->second)).string(); - } - } - return ""; // TODO -} - -} // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_element.hpp b/src/odr/internal/ooxml/text/ooxml_text_element.hpp deleted file mode 100644 index babde708..00000000 --- a/src/odr/internal/ooxml/text/ooxml_text_element.hpp +++ /dev/null @@ -1,200 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -#include - -namespace odr::internal::ooxml::text { -class Document; -class StyleRegistry; -class Style; - -class Element : public virtual internal::Element { -public: - explicit Element(pugi::xml_node); - - virtual ResolvedStyle partial_style(const abstract::Document *) const; - virtual ResolvedStyle intermediate_style(const abstract::Document *) const; - - bool is_editable(const abstract::Document *) const override; - -protected: - pugi::xml_node m_node; - - static const Document *document_(const abstract::Document *); - static const StyleRegistry *style_(const abstract::Document *); - static const std::unordered_map & - document_relations_(const abstract::Document *); - - friend class Style; -}; - -template -class DefaultElement final : public Element { -public: - using Element::Element; - - [[nodiscard]] ElementType type(const abstract::Document *) const override { - return _element_type; - } -}; - -class Root final : public Element, public abstract::TextRoot { -public: - using Element::Element; - - [[nodiscard]] PageLayout - page_layout(const abstract::Document *) const override; - - [[nodiscard]] abstract::Element * - first_master_page(const abstract::Document *) const override; -}; - -class Paragraph final : public Element, public abstract::Paragraph { -public: - using Element::Element; - - ResolvedStyle partial_style(const abstract::Document *) const override; - - [[nodiscard]] ParagraphStyle style(const abstract::Document *) const override; - - [[nodiscard]] TextStyle text_style(const abstract::Document *) const override; -}; - -class Span final : public Element, public abstract::Span { -public: - using Element::Element; - - ResolvedStyle partial_style(const abstract::Document *) const override; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Text final : public Element, public abstract::Text { -public: - using Element::Element; - - explicit Text(pugi::xml_node); - Text(pugi::xml_node first, pugi::xml_node last); - - [[nodiscard]] std::string content(const abstract::Document *) const override; - - void set_content(const abstract::Document *, - const std::string &text) override; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; - -private: - pugi::xml_node m_last; - - static std::string text_(pugi::xml_node node); -}; - -class Link final : public Element, public abstract::Link { -public: - using Element::Element; - - [[nodiscard]] std::string href(const abstract::Document *) const override; -}; - -class Bookmark final : public Element, public abstract::Bookmark { -public: - using Element::Element; - - [[nodiscard]] std::string name(const abstract::Document *) const override; -}; - -class List final : public Element { -public: - using Element::Element; - - [[nodiscard]] ElementType type(const abstract::Document *) const override; -}; - -class ListItem final : public Element, public abstract::ListItem { -public: - using Element::Element; - - [[nodiscard]] TextStyle style(const abstract::Document *) const override; -}; - -class Table final : public Element, public internal::Table { -public: - using Element::Element; - - [[nodiscard]] TableDimensions - dimensions(const abstract::Document *) const override; - - [[nodiscard]] TableStyle style(const abstract::Document *) const override; -}; - -class TableColumn final : public Element, public abstract::TableColumn { -public: - using Element::Element; - - [[nodiscard]] TableColumnStyle - style(const abstract::Document *) const override; -}; - -class TableRow final : public Element, public abstract::TableRow { -public: - using Element::Element; - - [[nodiscard]] TableRowStyle style(const abstract::Document *) const override; -}; - -class TableCell final : public Element, public abstract::TableCell { -public: - using Element::Element; - - [[nodiscard]] bool is_covered(const abstract::Document *) const override; - - [[nodiscard]] TableDimensions span(const abstract::Document *) const override; - - [[nodiscard]] ValueType value_type(const abstract::Document *) const override; - - [[nodiscard]] TableCellStyle style(const abstract::Document *) const override; -}; - -class Frame final : public Element, public abstract::Frame { -public: - using Element::Element; - - [[nodiscard]] AnchorType - anchor_type(const abstract::Document *) const override; - - [[nodiscard]] std::optional - x(const abstract::Document *) const override; - [[nodiscard]] std::optional - y(const abstract::Document *) const override; - [[nodiscard]] std::optional - width(const abstract::Document *) const override; - [[nodiscard]] std::optional - height(const abstract::Document *) const override; - - [[nodiscard]] std::optional - z_index(const abstract::Document *) const override; - - [[nodiscard]] GraphicStyle style(const abstract::Document *) const override; - -private: - [[nodiscard]] pugi::xml_node inner_node_(const abstract::Document *) const; -}; - -class Image final : public Element, public abstract::Image { -public: - using Element::Element; - - [[nodiscard]] bool is_internal(const abstract::Document *) const override; - - [[nodiscard]] std::optional - file(const abstract::Document *) const override; - - [[nodiscard]] std::string href(const abstract::Document *) const override; -}; - -} // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp b/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp new file mode 100644 index 00000000..a0e6da99 --- /dev/null +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp @@ -0,0 +1,163 @@ +#include + +#include + +namespace odr::internal::ooxml::text { + +void ElementRegistry::clear() noexcept { + m_elements.clear(); + m_tables.clear(); + m_texts.clear(); +} + +[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { + return m_elements.size(); +} + +std::tuple +ElementRegistry::create_element(const ElementType type, + const pugi::xml_node node) { + Element &element = m_elements.emplace_back(); + ElementIdentifier element_id = m_elements.size(); + element.type = type; + element.node = node; + return {element_id, element}; +} + +std::tuple +ElementRegistry::create_table_element(const pugi::xml_node node) { + const auto &[element_id, element] = create_element(ElementType::table, node); + auto [it, success] = m_tables.emplace(element_id, Table{}); + return {element_id, element, it->second}; +} + +std::tuple +ElementRegistry::create_text_element(const pugi::xml_node first_node, + const pugi::xml_node last_node) { + const auto &[element_id, element] = + create_element(ElementType::text, first_node); + auto [it, success] = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, it->second}; +} + +ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) { + check_element_id(id); + return m_elements.at(id - 1); +} + +ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) { + return m_tables.at(id); +} + +const ElementRegistry::Element & +ElementRegistry::element_at(const ElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id - 1); +} + +const ElementRegistry::Table & +ElementRegistry::table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); +} + +ElementRegistry::Element *ElementRegistry::element(const ElementIdentifier id) { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +const ElementRegistry::Element * +ElementRegistry::element(const ElementIdentifier id) const { + if (id == null_element_id || id - 1 >= m_elements.size()) { + return nullptr; + } + return &m_elements.at(id - 1); +} + +const ElementRegistry::Text * +ElementRegistry::text_element(const ElementIdentifier id) const { + if (const auto it = m_texts.find(id); it != m_texts.end()) { + return &it->second; + } + return nullptr; +} + +void ElementRegistry::check_element_id(const ElementIdentifier id) const { + if (id == null_element_id) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id)) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ElementIdentifier previous_sibling_id = + element_at(parent_id).last_child_id; + + element_at(child_id).parent_id = parent_id; + element_at(child_id).previous_sibling_id = previous_sibling_id; + + if (element_at(parent_id).first_child_id == null_element_id) { + element_at(parent_id).first_child_id = child_id; + } else { + element_at(previous_sibling_id).next_sibling_id = child_id; + } + element_at(parent_id).last_child_id = child_id; +} + +void ElementRegistry::append_column(const ElementIdentifier table_id, + const ElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + + const ElementIdentifier previous_sibling_id = + table_element_at(table_id).last_column_id; + + element_at(column_id).parent_id = table_id; + element_at(column_id).previous_sibling_id = previous_sibling_id; + + if (table_element_at(table_id).first_column_id == null_element_id) { + table_element_at(table_id).first_column_id = column_id; + } else { + element_at(previous_sibling_id).next_sibling_id = column_id; + } + table_element_at(table_id).last_column_id = column_id; +} + +} // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp new file mode 100644 index 00000000..2fb7f334 --- /dev/null +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::text { + +class ElementRegistry final { +public: + struct Element final { + ElementIdentifier parent_id{null_element_id}; + ElementIdentifier first_child_id{null_element_id}; + ElementIdentifier last_child_id{null_element_id}; + ElementIdentifier previous_sibling_id{null_element_id}; + ElementIdentifier next_sibling_id{null_element_id}; + ElementType type{ElementType::none}; + pugi::xml_node node; + bool is_editable{false}; + }; + + struct Table final { + ElementIdentifier first_column_id{null_element_id}; + ElementIdentifier last_column_id{null_element_id}; + }; + + struct Text final { + pugi::xml_node last; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + std::tuple create_element(ElementType type, + pugi::xml_node node); + std::tuple + create_table_element(pugi::xml_node node); + std::tuple + create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); + + [[nodiscard]] Element &element_at(ElementIdentifier id); + [[nodiscard]] Table &table_element_at(ElementIdentifier id); + + [[nodiscard]] const Element &element_at(ElementIdentifier id) const; + [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; + + [[nodiscard]] Element *element(ElementIdentifier id); + [[nodiscard]] Text *text_element(ElementIdentifier id); + + [[nodiscard]] const Element *element(ElementIdentifier id) const; + [[nodiscard]] const Text *text_element(ElementIdentifier id) const; + + void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + void append_column(ElementIdentifier table_id, ElementIdentifier column_id); + +private: + std::vector m_elements; + std::unordered_map m_tables; + std::unordered_map m_texts; + + void check_element_id(ElementIdentifier id) const; + void check_table_id(ElementIdentifier id) const; + void check_text_id(ElementIdentifier id) const; +}; + +} // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_parser.cpp b/src/odr/internal/ooxml/text/ooxml_text_parser.cpp index 14065cce..4be10069 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_parser.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_parser.cpp @@ -1,50 +1,54 @@ #include -#include -#include +#include +#include #include #include +#include + namespace odr::internal::ooxml::text { namespace { -template -std::tuple parse_element_tree(Document &document, - pugi::xml_node node); +using TreeParser = std::function( + ElementRegistry ®istry, pugi::xml_node node)>; +using ChildrenParser = + std::function; -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node); +std::tuple +parse_any_element_tree(ElementRegistry ®istry, pugi::xml_node node); -void parse_element_children(Document &document, Element *element, - const pugi::xml_node node) { +void parse_any_element_children(ElementRegistry ®istry, + const ElementIdentifier parent_id, + const pugi::xml_node node) { for (pugi::xml_node child_node = node.first_child(); child_node;) { - if (const auto [child, next_sibling] = - parse_any_element_tree(document, child_node); - child == nullptr) { + if (const auto [child_id, next_sibling] = + parse_any_element_tree(registry, child_node); + child_id == null_element_id) { child_node = child_node.next_sibling(); } else { - element->append_child_(child); + registry.append_child(parent_id, child_id); child_node = next_sibling; } } } -template -std::tuple parse_element_tree(Document &document, - pugi::xml_node node) { +std::tuple +parse_element_tree(ElementRegistry ®istry, const ElementType type, + const pugi::xml_node node, + const ChildrenParser &children_parser) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto element_unique = std::make_unique(node); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _] = registry.create_element(type, node); - parse_element_children(document, element, node); + children_parser(registry, element_id, node); - return std::make_tuple(element, node.next_sibling()); + return {element_id, node.next_sibling()}; } bool is_text_node(const pugi::xml_node node) { @@ -64,22 +68,20 @@ bool is_text_node(const pugi::xml_node node) { return false; } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node) { - if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); +std::tuple +parse_text_element(ElementRegistry ®istry, const pugi::xml_node first) { + if (!first) { + return {null_element_id, pugi::xml_node()}; } - pugi::xml_node last = node; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { + pugi::xml_node last; + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { } - auto element_unique = std::make_unique(node, last); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const auto &[element_id, _, __] = registry.create_text_element(first, last); - return std::make_tuple(element, last.next_sibling()); + return {element_id, last.next_sibling()}; } bool is_list_item(const pugi::xml_node node) { @@ -94,19 +96,17 @@ std::int32_t list_level(const pugi::xml_node node) { .as_int(0); } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node) { +std::tuple +parse_list_element(ElementRegistry ®istry, pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto list_unique = std::make_unique(node); - List *list = list_unique.get(); - document.register_element_(std::move(list_unique)); + const auto &[element_id, _] = + registry.create_element(ElementType::list, node); for (; is_list_item(node); node = node.next_sibling()) { - List *base = list; + ElementIdentifier base_id = element_id; const std::int32_t level = list_level(node); for (std::int32_t i = 0; i < level; ++i) { @@ -118,112 +118,113 @@ parse_element_tree(Document &document, pugi::xml_node node) { base->init_append_child(list_item); */ - auto nested_list_unique = std::make_unique(node); - List *nested_list = nested_list_unique.get(); - document.register_element_(std::move(nested_list_unique)); + const auto &[nested_id, _] = + registry.create_element(ElementType::list, node); - // list_item->init_append_child(nested_list); + registry.append_child(base_id, nested_id); - base->append_child_(nested_list); - base = nested_list; + base_id = nested_id; } - auto list_item_unique = std::make_unique(node); - ListItem *list_item = list_item_unique.get(); - document.register_element_(std::move(list_item_unique)); + const auto &[item_id, _] = + registry.create_element(ElementType::list_item, node); - base->append_child_(list_item); + registry.append_child(base_id, item_id); - auto [element, _] = parse_element_tree(document, node); - list_item->append_child_(element); + auto [child_id, __] = parse_element_tree(registry, ElementType::paragraph, + node, parse_any_element_children); + registry.append_child(item_id, child_id); } - return std::make_tuple(list, node); + return {element_id, node}; } -template <> -std::tuple -parse_element_tree(Document &document, pugi::xml_node node) { +std::tuple +parse_table_row_element(ElementRegistry ®istry, const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto table_row_unique = std::make_unique(node); - auto table_row = table_row_unique.get(); - document.register_element_(std::move(table_row_unique)); + const auto &[element_id, _] = + registry.create_element(ElementType::table_row, node); for (const pugi::xml_node cell_node : node.children("w:tc")) { - auto [cell, _] = parse_element_tree(document, cell_node); - table_row->append_child_(cell); + auto [cell_id, _] = + parse_element_tree(registry, ElementType::table_cell, cell_node, + parse_any_element_children); + registry.append_child(element_id, cell_id); } - return std::make_tuple(table_row, node.next_sibling()); + return {element_id, node.next_sibling()}; } -template <> -std::tuple -parse_element_tree
(Document &document, pugi::xml_node node) { +std::tuple +parse_table_element(ElementRegistry ®istry, const pugi::xml_node node) { if (!node) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } - auto table_unique = std::make_unique
(node); - Table *table = table_unique.get(); - document.register_element_(std::move(table_unique)); + const auto &[element_id, _, __] = registry.create_table_element(node); for (const pugi::xml_node column_node : node.child("w:tblGrid").children("w:gridCol")) { - const auto [column, _] = - parse_element_tree(document, column_node); - table->append_column_(column); + const auto [column_id, _] = + parse_element_tree(registry, ElementType::table_column, column_node, + parse_any_element_children); + registry.append_column(element_id, column_id); } for (const pugi::xml_node row_node : node.children("w:tr")) { - const auto [row, _] = parse_element_tree(document, row_node); - table->append_row_(row); + const auto [row_id, _] = parse_element_tree( + registry, ElementType::table_row, row_node, parse_any_element_children); + registry.append_child(element_id, row_id); } - return std::make_tuple(table, node.next_sibling()); + return {element_id, node.next_sibling()}; } -std::tuple -parse_any_element_tree(Document &document, const pugi::xml_node node) { - using Parser = std::function( - Document & document, pugi::xml_node node)>; - - using Group = DefaultElement; - - static std::unordered_map parser_table{ - {"w:body", parse_element_tree}, - {"w:t", parse_element_tree}, - {"w:tab", parse_element_tree}, - {"w:p", parse_element_tree}, - {"w:r", parse_element_tree}, - {"w:bookmarkStart", parse_element_tree}, - {"w:hyperlink", parse_element_tree}, - {"w:tbl", parse_element_tree
}, - {"w:gridCol", parse_element_tree}, - {"w:tr", parse_element_tree}, - {"w:tc", parse_element_tree}, - {"w:sdt", parse_element_tree}, - {"w:sdtContent", parse_element_tree}, - {"w:drawing", parse_element_tree}, - {"wp:anchor", parse_element_tree}, - {"wp:inline", parse_element_tree}, - {"a:graphic", parse_element_tree}, - {"a:graphicData", parse_element_tree}, +std::tuple +parse_any_element_tree(ElementRegistry ®istry, const pugi::xml_node node) { + const auto create_default_tree_parser = + [](const ElementType type, + const ChildrenParser &children_parser = parse_any_element_children) { + return [type, children_parser](ElementRegistry &r, + const pugi::xml_node n) { + return parse_element_tree(r, type, n, children_parser); + }; + }; + + static std::unordered_map parser_table{ + {"w:body", create_default_tree_parser(ElementType::root)}, + {"w:t", parse_text_element}, + {"w:tab", parse_text_element}, + {"w:p", create_default_tree_parser(ElementType::paragraph)}, + {"w:r", create_default_tree_parser(ElementType::span)}, + {"w:bookmarkStart", create_default_tree_parser(ElementType::bookmark)}, + {"w:hyperlink", create_default_tree_parser(ElementType::link)}, + {"w:tbl", parse_table_element}, + {"w:gridCol", create_default_tree_parser(ElementType::table_column)}, + {"w:tr", parse_table_row_element}, + {"w:tc", create_default_tree_parser(ElementType::table_cell)}, + {"w:sdt", create_default_tree_parser(ElementType::group)}, + {"w:sdtContent", create_default_tree_parser(ElementType::group)}, + {"w:drawing", create_default_tree_parser(ElementType::frame)}, + {"wp:anchor", create_default_tree_parser(ElementType::group)}, + {"wp:inline", create_default_tree_parser(ElementType::group)}, + {"a:graphic", create_default_tree_parser(ElementType::group)}, + {"a:graphicData", create_default_tree_parser(ElementType::image)}, }; if (is_list_item(node)) { - return parse_element_tree(document, node); + return parse_list_element(registry, node); } if (const auto constructor_it = parser_table.find(node.name()); constructor_it != std::end(parser_table)) { - return constructor_it->second(document, node); + return constructor_it->second(registry, node); } - return std::make_tuple(nullptr, pugi::xml_node()); + return {null_element_id, pugi::xml_node()}; } } // namespace @@ -232,8 +233,9 @@ parse_any_element_tree(Document &document, const pugi::xml_node node) { namespace odr::internal::ooxml { -text::Element *text::parse_tree(Document &document, const pugi::xml_node node) { - auto [root, _] = parse_any_element_tree(document, node); +ElementIdentifier text::parse_tree(ElementRegistry ®istry, + const pugi::xml_node node) { + auto [root, _] = parse_any_element_tree(registry, node); return root; } diff --git a/src/odr/internal/ooxml/text/ooxml_text_parser.hpp b/src/odr/internal/ooxml/text/ooxml_text_parser.hpp index 32d49f52..6309892a 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_parser.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_parser.hpp @@ -1,11 +1,14 @@ #pragma once -#include +#include + +namespace pugi { +class xml_node; +} namespace odr::internal::ooxml::text { -class Document; -class Element; +class ElementRegistry; -Element *parse_tree(Document &document, pugi::xml_node node); +ElementIdentifier parse_tree(ElementRegistry ®istry, pugi::xml_node node); } // namespace odr::internal::ooxml::text diff --git a/src/odr/internal/ooxml/text/ooxml_text_style.cpp b/src/odr/internal/ooxml/text/ooxml_text_style.cpp index 071cd4f5..71d02b96 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_style.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_style.cpp @@ -233,8 +233,8 @@ StyleRegistry::partial_table_cell_style(const pugi::xml_node node) const { } void StyleRegistry::generate_indices_(const pugi::xml_node styles_root) { - for (auto style : styles_root) { - std::string element_name = style.name(); + for (const pugi::xml_node style : styles_root) { + const std::string element_name = style.name(); if (element_name == "w:style") { m_index[style.attribute("w:styleId").value()] = style; @@ -245,23 +245,24 @@ void StyleRegistry::generate_indices_(const pugi::xml_node styles_root) { void StyleRegistry::generate_styles_(const pugi::xml_node styles_root) { m_default_style = std::make_unique