diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a4089b8..e079fd2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,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" @@ -133,25 +132,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/conan.lock b/conan.lock index a421ccd4..3b2a841d 100644 --- a/conan.lock +++ b/conan.lock @@ -1,83 +1,79 @@ { "version": "0.5", "requires": [ - "zstd/1.5.7#fde461c0d847a22f16d3066774f61b11%1744114235.235", + "zstd/1.5.7#b68ca8e3de04ba5957761751d1d661f4%1760955092.069", "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1733936244.862", "xz_utils/5.4.5#b885d1d79c9d30cff3803f7f551dbe66%1724318972.064", - "wvware/1.2.9-odr#c822f203f7a2dec7b34e4100eabac388%1764497609.565", + "wvware/1.2.9-odr#c822f203f7a2dec7b34e4100eabac388%1761496871.6559849", "vincentlaucsb-csv-parser/2.3.0#ac67e368e82c9e3da4a663c35e3a1b2f%1718528275.177", "util-linux-libuuid/2.39.2#637bd312b6310c18190469fae4e1d480%1729761467.688", "utfcpp/4.0.4#cd6efc5d62de4e9ee0b5f3abd0df37a9%1722968964.685", "uchardet/0.0.8#6ab25e452021fcdb560f4e37f4a27bc1%1759735438.978", "pugixml/1.14#c6afdcf73d71858303d8260b0d76ff91%1696206310.014", - "poppler-data/0.4.12-odr#06cdb12e4cab52261a5eb6c7d7dad273%1764497608.501", - "poppler/24.08.0-odr#2929132c6c3c67155c6c8f050c923916%1764497607.478", - "pixman/0.43.4#0dcdf859941e32fcc7bfb73ea1946a7f%1718828937.421", - "pdf2htmlex/0.18.8.rc1-odr-git-eb5d291#99dd5c7844a56c308fa8221dc8c8c2b2%1764497606.474", + "poppler-data/0.4.12-odr#06cdb12e4cab52261a5eb6c7d7dad273%1761496872.9032168", + "poppler/24.08.0-odr#2929132c6c3c67155c6c8f050c923916%1761496876.004568", + "pixman/0.43.4#60427f74e9514f007eb3cb8c52b6fbd6%1752742517.637", + "pdf2htmlex/0.18.8.rc1-odr-git-eb5d291#99dd5c7844a56c308fa8221dc8c8c2b2%1761496872.136318", "pcre2/10.42#9a35f5089feb875ec61a38eca364ce77%1743524593.693", - "openlibm/0.8.3#61d8454cf655e95e0d3d3359bde58ac7%1754339136.456942", "openjpeg/2.5.2#6f7b733e151d1bbf5ed05cbabb846828%1709653017.024", "nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d%1701220705.259", "miniz/3.0.2#bfbce07c6654293cce27ee24129d2df7%1743673472.805", "lzo/2.10#f00b10acc508cea70645727d970a23e1%1759909644.842", "libxml2/2.12.7#1c4d20b7ab8b618ce699733723ba4df6%1721306327.767", "libselinux/3.6#5a78ff6ae5034eeaac8da723361a8ce4%1717655459.344", - "libpng/1.6.48#dd0fc04a42b9a23bce065545a81d4847%1746141949.748", + "libpng/1.6.50#c96b3b9fa67d44545d6583bb0c348904%1751616189.701", "libmagic/5.45#791d5bad38d33272bb120994a198b1ac%1727273086.09", "libjpeg/9f#8edfe2699565c80c825d0256002504ff%1723665907.087", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1751451666.321", - "libgsf/1.14.52#9b22c41267004c80ba5cde5d01e23a24%1764497589.738", - "libgettext/0.22#35d2811b2dd27a98f69e4daa86ca2000%1714393058.647", - "libffi/3.4.8#06926dca35bcf8e321fcc24def952cde%1748531860.405", + "libgsf/1.14.52#9b22c41267004c80ba5cde5d01e23a24%1761496872.4449549", + "libgettext/0.22#766c603ca03bfbcad907369de5368c29%1755528994.227", + "libffi/3.4.8#a045c00fb26779635e3bed40e80c5254%1753360042.396", "libelf/0.8.13#ba59bbc89757ed62cfd7690a73bf81be%1741781951.327", - "lcms/2.16#fb083506ff40fd950c9e5c39df8bed54%1703969656.459", + "lcms/2.16#803222ca149d2e07e9c0ab32342e47e6%1753689779.344", "gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1743410807.169", - "glib/2.81.0-odr#ddf445d5af468f972978af93c44d26e1%1764497588.731", + "glib/2.81.0-odr#ddf445d5af468f972978af93c44d26e1%1761496871.9808278", "giflib/5.2.2#3923fc0f7ffec2f0bdbdee9b548f9248%1731663927.701", - "freetype/2.13.2#5d2563803c8558d4ef47271a82c73d20%1728736671.752", - "fontforge/20240423-git#525c82a5e57385c14b647b966e86ea58%1764497587.707", - "fontconfig/2.15.0-odr#2febbef44ca469204c8ff38cfd21db59%1764497586.969", - "expat/2.6.3#39b80d3109fbe578fddfe4951f0b1d57%1725469045.298", - "cryptopp/8.9.0#fe3de584c28c0ecc938a1671e3f1bd72%1731421245.374", + "freetype/2.13.2#18656f7a6d52256a930f1cbd79f1509d%1756828316.696", + "fontforge/20240423-git#525c82a5e57385c14b647b966e86ea58%1761496876.150338", + "fontconfig/2.15.0-odr#2febbef44ca469204c8ff38cfd21db59%1761496872.602404", + "expat/2.7.3#f529802a90f0758a01f498a18f8c657b%1759399780.465", + "cryptopp/8.9.0#7a51e0038756b21bc3a6b82d681d5906%1758206597.119", "cpp-httplib/0.16.3#7aa89fbb81ffd19539a49fc132502966%1748426320.106", - "cairo/1.18.0-odr#c1d0ad14a91ad6d161e756d54277ac13%1764497585.932", + "cairo/1.18.0-odr#c1d0ad14a91ad6d161e756d54277ac13%1761496872.291054", "bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1744702067.178", "brotli/1.1.0#406ce8f1c997f4ef7852fa01ff85ef9f%1743158659.041", - "boost/1.86.0#cd839a2082585255010f9e82eea94c7f%1728027203.247", - "argon2/20190702-odr#965901884bc82ec8a7c0a1305d42c127%1764497584.637" + "boost/1.86.0#0675eb54da69d8eed264eb22203a7117%1761059017.02", + "argon2/20190702-odr#965901884bc82ec8a7c0a1305d42c127%1761496871.819448" ], "build_requires": [ - "zstd/1.5.7#fde461c0d847a22f16d3066774f61b11%1744114235.235", + "zstd/1.5.7#b68ca8e3de04ba5957761751d1d661f4%1760955092.069", "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1733936244.862", "xz_utils/5.4.5#b885d1d79c9d30cff3803f7f551dbe66%1724318972.064", - "pkgconf/2.2.0#6462942a22803086372db44689ba825f%1713364853.749", - "pkgconf/2.1.0#27f44583701117b571307cf5b5fe5605%1701537936.436", - "pkgconf/2.0.3#f996677e96e61e6552d85e83756c328b%1696606182.229", + "pkgconf/2.2.0#4ac315b50ef734072b00ff3aacbf52bf%1755505628.021", + "pkgconf/2.1.0#21f96520faf7660b99f872e956d2ac13%1755505630.82", + "pkgconf/2.0.3#c7ba7fd1d7d22534ee070ae60ed79604%1755505633.107", "pcre2/10.42#9a35f5089feb875ec61a38eca364ce77%1743524593.693", - "ninja/1.13.0#53ff096207a5599ced46a633271b3cef%1751046277.036", - "msys2/cci.latest#5b73b10144f73cc5bfe0572ed9be39e1%1751977009.857", + "ninja/1.13.1#294f8721dbcde145674f7ba44994700e%1753400352.374", + "meson/1.9.1#abbc783cd297bedce14581b4aec060b8%1758626166.349", "meson/1.4.0#2262941cc8fbb0099dd0c196ca2a6c01%1726730116.631", - "meson/1.3.2#26ce8a76a36cc275cdfee1d757bc6561%1726730118.251", - "meson/1.2.2#21b73818ba96d9eea465b310b5bbc993%1726730120.212", - "meson/1.2.1#f2b0c7763308df8e33172744dace8845%1726730117.905", "m4/1.4.19#b38ced39a01e31fef5435bc634461fd2%1700758725.451", - "libtool/2.4.7#a182d7ce8d4c346a19dbd4a5d532ef68%1742900203.747", + "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", "libselinux/3.6#5a78ff6ae5034eeaac8da723361a8ce4%1717655459.344", "libmagic/5.45#791d5bad38d33272bb120994a198b1ac%1727273086.09", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1751451666.321", - "libgettext/0.22#35d2811b2dd27a98f69e4daa86ca2000%1714393058.647", - "libffi/3.4.8#06926dca35bcf8e321fcc24def952cde%1748531860.405", + "libgettext/0.22#766c603ca03bfbcad907369de5368c29%1755528994.227", + "libffi/3.4.8#a045c00fb26779635e3bed40e80c5254%1753360042.396", "libelf/0.8.13#ba59bbc89757ed62cfd7690a73bf81be%1741781951.327", "gtk-doc-stub/cci.20181216#09072d684ce1458596b44a30a747494c%1687277608.37", - "gperf/3.1#1d622ad9717e9348ed3685c9994ad0b9%1709324989.76", - "gnu-config/cci.20210814#dc430d754f465e8c74463019672fb97b%1701248168.479", - "glib/2.81.0-odr#ddf445d5af468f972978af93c44d26e1%1764497588.731", - "gettext/0.22.5#a1f31cc77dee0345699745ef39686dd0%1750252839.982", - "flex/2.6.4#e35bc44b3fcbcd661e0af0dc5b5b1ad4%1674818991.113", - "cmake/3.31.8#dd6e07c418afc4b30cb1c21584dccc49%1750223587.75", + "gperf/3.1#a7afdf8f7cccdc2dcd4d962370c33d4f%1755780571.156", + "gnu-config/cci.20210814#69fde734e1a46fd1655b4af46ab40945%1746203214.947", + "glib/2.81.0-odr#ddf445d5af468f972978af93c44d26e1%1761496871.9808278", + "gettext/0.22.5#4705a1582f4a611eadb15d0417427993%1755528989.046", + "flex/2.6.4#e5cd857e69595b17f7599ba56be7dad2%1761206488.085", + "cmake/3.31.9#2032c6471fe4f5a3e17f65fed518d545%1758832282.188", "bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1744702067.178", - "automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50%1688481772.751", - "autoconf/2.71#f9307992909d7fb3df459340f1932809%1711983104.648" + "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", + "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86" ], "python_requires": [], "config_requires": [] diff --git a/scripts/conan b/scripts/conan deleted file mode 100755 index 6107326f..00000000 --- a/scripts/conan +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -conan install . --output-folder=cmake-build-relwithdebinfo --build=missing -s build_type=RelWithDebInfo -s "&:build_type=RelWithDebInfo" -conan install . --output-folder=cmake-build-debug --build=missing -s build_type=RelWithDebInfo -s "&:build_type=Debug" -conan install . --output-folder=cmake-build-release --build=missing -s build_type=RelWithDebInfo -s "&:build_type=Release" 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..854971dd 100644 --- a/src/odr/document_element.cpp +++ b/src/odr/document_element.cpp @@ -4,129 +4,164 @@ #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 ExtendedElementIdentifier 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.is_null(); +} 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}; } +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_document, m_element}; } +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 ExtendedElementIdentifier 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 +170,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.is_null(); +} 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 +190,390 @@ 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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(); + return exists_() ? m_adapter2->sheet_content(m_identifier, range) + : TableDimensions(); } SheetColumn Sheet::column(const std::uint32_t column) const { - return exists_() ? SheetColumn(m_document, m_element, column) : SheetColumn(); + if (!exists_()) { + return {}; + } + const ExtendedElementIdentifier column_id = + m_adapter2->sheet_column(m_identifier, column); + return {m_adapter, column_id, m_adapter->sheet_column_adapter(column_id)}; } SheetRow Sheet::row(const std::uint32_t row) const { - return exists_() ? SheetRow(m_document, m_element, row) : SheetRow(); + if (!exists_()) { + return {}; + } + const ExtendedElementIdentifier row_id = + m_adapter2->sheet_row(m_identifier, row); + return {m_adapter, row_id, m_adapter->sheet_row_adapter(row_id)}; } 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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} {} - TableColumnStyle SheetColumn::style() const { - return exists_() ? m_element->column_style(m_document, m_column) + return exists_() ? m_adapter2->sheet_column_style(m_identifier) : 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(); + return exists_() ? m_adapter2->sheet_row_style(m_identifier) + : 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; } +std::uint32_t SheetCell::column() const { + return exists_() ? m_adapter2->sheet_cell_column(m_identifier) : 0; +} -std::uint32_t SheetCell::row() const { return m_row; } +std::uint32_t SheetCell::row() const { + return exists_() ? m_adapter2->sheet_cell_row(m_identifier) : 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; + return exists_() ? m_adapter2->sheet_cell_value_type(m_identifier) + : 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_style(m_identifier) + : TableCellStyle(); } 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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..dcdd0a15 100644 --- a/src/odr/document_element.hpp +++ b/src/odr/document_element.hpp @@ -4,33 +4,37 @@ #include #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; +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 { @@ -136,10 +140,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, + ExtendedElementIdentifier identifier); explicit operator bool() const; @@ -178,8 +180,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}; + ExtendedElementIdentifier m_identifier; + + friend bool operator==(const Element &lhs, const Element &rhs) { + return lhs.m_identifier == rhs.m_identifier; + } [[nodiscard]] bool exists_() const; }; @@ -194,11 +200,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, + ExtendedElementIdentifier identifier); reference operator*() const; @@ -206,8 +209,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}; + ExtendedElementIdentifier m_identifier; + + friend bool operator==(const ElementIterator &lhs, + const ElementIterator &rhs) { + return lhs.m_identifier == rhs.m_identifier; + } [[nodiscard]] bool exists_() const; }; @@ -216,8 +224,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 +236,25 @@ 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 ExtendedElementIdentifier identifier, const T *adapter2) + : Element(adapter, identifier), m_adapter2{adapter2} {} 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 +262,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 +274,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; @@ -285,40 +292,28 @@ class Sheet final : public TypedElement { }; /// @brief Represents a sheet column element in a document. -class SheetColumn final : public TypedElement { +class SheetColumn final + : public ElementBase { public: - SheetColumn() = default; - SheetColumn(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, std::uint32_t column); + using ElementBase::ElementBase; [[nodiscard]] TableColumnStyle style() const; - -private: - std::uint32_t m_column{}; }; /// @brief Represents a sheet row element in a document. -class SheetRow final : public TypedElement { +class SheetRow final : public ElementBase { public: - SheetRow() = default; - SheetRow(const internal::abstract::Document *document, - internal::abstract::Sheet *sheet, std::uint32_t row); + using ElementBase::ElementBase; [[nodiscard]] TableRowStyle style() const; - -private: - std::uint32_t m_row{}; }; /// @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); + using ElementBase::ElementBase; - [[nodiscard]] Sheet sheet() const; [[nodiscard]] std::uint32_t column() const; [[nodiscard]] std::uint32_t row() const; [[nodiscard]] bool is_covered() const; @@ -326,17 +321,12 @@ class SheetCell final : public TypedElement { [[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 +336,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 +383,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 +423,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 +453,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 +468,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 +481,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 +494,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 +507,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 +521,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_element_identifier.hpp b/src/odr/document_element_identifier.hpp new file mode 100644 index 00000000..e7de158c --- /dev/null +++ b/src/odr/document_element_identifier.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include + +namespace odr { + +using ElementIdentifier = std::uint64_t; + +static constexpr ElementIdentifier null_element_id{0}; + +class ExtendedElementIdentifier { +public: + using Extra = std::uint32_t; + + static constexpr Extra no_extra{0}; + + static constexpr ExtendedElementIdentifier null() noexcept { return {}; } + static constexpr ExtendedElementIdentifier + make_column(const ElementIdentifier element_id, + const std::uint32_t column) noexcept { + return ExtendedElementIdentifier{element_id, column + 1, no_extra}; + } + static constexpr ExtendedElementIdentifier + make_row(const ElementIdentifier element_id, + const std::uint32_t row) noexcept { + return ExtendedElementIdentifier{element_id, no_extra, row + 1}; + } + static constexpr ExtendedElementIdentifier + make_cell(const ElementIdentifier element_id, const std::uint32_t column, + const std::uint32_t row) noexcept { + return ExtendedElementIdentifier{element_id, column + 1, row + 1}; + } + + constexpr ExtendedElementIdentifier() noexcept = default; + + explicit constexpr ExtendedElementIdentifier( + const ElementIdentifier element_id, const Extra extra0 = no_extra, + const Extra extra1 = no_extra) noexcept + : m_element_id{element_id}, m_extra0{extra0}, m_extra1{extra1} {} + + [[nodiscard]] constexpr ElementIdentifier element_id() const noexcept { + return m_element_id; + } + [[nodiscard]] constexpr Extra extra0() const noexcept { return m_extra0; } + [[nodiscard]] constexpr Extra extra1() const noexcept { return m_extra1; } + + [[nodiscard]] constexpr bool is_null() const noexcept { + return m_element_id == null_element_id; + } + [[nodiscard]] constexpr bool has_extra0() const noexcept { + return m_extra0 != no_extra; + } + [[nodiscard]] constexpr bool has_extra1() const noexcept { + return m_extra1 != no_extra; + } + + [[nodiscard]] constexpr bool has_column() const noexcept { + return has_extra0() && !has_extra1(); + } + [[nodiscard]] constexpr bool has_row() const noexcept { + return !has_extra0() && has_extra1(); + } + [[nodiscard]] constexpr bool has_cell() const noexcept { + return has_extra0() && has_extra1(); + } + + [[nodiscard]] std::uint32_t column() const noexcept { return m_extra0 - 1; } + [[nodiscard]] std::uint32_t row() const noexcept { return m_extra1 - 1; } + [[nodiscard]] std::pair cell() const noexcept { + return {column(), row()}; + } + + [[nodiscard]] ExtendedElementIdentifier without_extra() const noexcept { + return ExtendedElementIdentifier(m_element_id); + } + +private: + ElementIdentifier m_element_id{null_element_id}; + Extra m_extra0{no_extra}; + Extra m_extra1{no_extra}; + + friend constexpr bool + operator==(const ExtendedElementIdentifier &lhs, + const ExtendedElementIdentifier &rhs) noexcept { + return lhs.m_element_id == rhs.m_element_id && + lhs.m_extra0 == rhs.m_extra0 && lhs.m_extra1 == rhs.m_extra1; + } + + friend constexpr std::strong_ordering + operator<=>(const ExtendedElementIdentifier &a, + const ExtendedElementIdentifier &b) noexcept { + if (const std::strong_ordering cmp = a.m_element_id <=> b.m_element_id; + cmp != 0) { + return cmp; + } + if (const std::strong_ordering cmp = a.m_extra0 <=> b.m_extra0; cmp != 0) { + return cmp; + } + return a.m_extra1 <=> b.m_extra1; + } +}; + +} // namespace odr diff --git a/src/odr/internal/abstract/document.hpp b/src/odr/internal/abstract/document.hpp index fb8e0e4a..88847f40 100644 --- a/src/odr/internal/abstract/document.hpp +++ b/src/odr/internal/abstract/document.hpp @@ -1,10 +1,9 @@ #pragma once -#include - #include namespace odr { +class ExtendedElementIdentifier; class File; enum class FileType; enum class DocumentType; @@ -16,7 +15,7 @@ class Path; namespace odr::internal::abstract { class ReadableFilesystem; -class Element; +class ElementAdapter; class Document { public: @@ -47,7 +46,9 @@ 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 ExtendedElementIdentifier root_element() const = 0; + + [[nodiscard]] virtual const ElementAdapter *element_adapter() const = 0; }; } // namespace odr::internal::abstract diff --git a/src/odr/internal/abstract/document_element.hpp b/src/odr/internal/abstract/document_element.hpp index 477ddad3..27990d0d 100644 --- a/src/odr/internal/abstract/document_element.hpp +++ b/src/odr/internal/abstract/document_element.hpp @@ -2,294 +2,437 @@ #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 ExtendedElementIdentifier; +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(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual ExtendedElementIdentifier + element_parent(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + element_first_child(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + element_last_child(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + element_previous_sibling(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + element_next_sibling(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual bool + element_is_editable(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual const TextRootAdapter * + text_root_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SlideAdapter * + slide_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const PageAdapter * + page_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetAdapter * + sheet_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetColumnAdapter * + sheet_column_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetRowAdapter * + sheet_row_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SheetCellAdapter * + sheet_cell_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const MasterPageAdapter * + master_page_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LineBreakAdapter * + line_break_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ParagraphAdapter * + paragraph_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const SpanAdapter * + span_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TextAdapter * + text_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LinkAdapter * + link_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const BookmarkAdapter * + bookmark_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ListItemAdapter * + list_item_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableAdapter * + table_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableColumnAdapter * + table_column_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableRowAdapter * + table_row_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const TableCellAdapter * + table_cell_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const FrameAdapter * + frame_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const RectAdapter * + rect_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const LineAdapter * + line_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const CircleAdapter * + circle_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const CustomShapeAdapter * + custom_shape_adapter(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual const ImageAdapter * + image_adapter(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual bool is_editable(const Document *document) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + text_root_first_master_page(ExtendedElementIdentifier 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 + slide_page_layout(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + slide_master_page(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *first_master_page(const Document *) const = 0; + [[nodiscard]] virtual std::string + slide_name(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *master_page(const Document *) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + page_master_page(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual std::string name(const Document *) const = 0; + [[nodiscard]] virtual std::string + page_name(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual TableDimensions + sheet_dimensions(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + sheet_content(ExtendedElementIdentifier element_id, + std::optional range) const = 0; + + [[nodiscard]] virtual ExtendedElementIdentifier + sheet_column(ExtendedElementIdentifier element_id, + std::uint32_t column) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + sheet_row(ExtendedElementIdentifier element_id, std::uint32_t row) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + sheet_cell(ExtendedElementIdentifier element_id, std::uint32_t column, + std::uint32_t row) const = 0; + + [[nodiscard]] virtual ExtendedElementIdentifier + sheet_first_shape(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual TableStyle + sheet_style(ExtendedElementIdentifier element_id) const = 0; +}; - [[nodiscard]] virtual PageLayout page_layout(const Document *) const = 0; +class SheetColumnAdapter { +public: + virtual ~SheetColumnAdapter() = default; - [[nodiscard]] virtual Element *master_page(const Document *) const = 0; + [[nodiscard]] virtual TableColumnStyle + sheet_column_style(ExtendedElementIdentifier element_id) const = 0; +}; - [[nodiscard]] virtual std::string name(const Document *) const = 0; +class SheetRowAdapter { +public: + virtual ~SheetRowAdapter() = default; + + [[nodiscard]] virtual TableRowStyle + sheet_row_style(ExtendedElementIdentifier element_id) const = 0; +}; + +class SheetCellAdapter { +public: + virtual ~SheetCellAdapter() = default; + + [[nodiscard]] virtual std::uint32_t + sheet_cell_column(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::uint32_t + sheet_cell_row(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual bool + sheet_cell_is_covered(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + sheet_cell_span(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ValueType + sheet_cell_value_type(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual TableCellStyle + sheet_cell_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TextStyle + paragraph_text_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + virtual void text_set_content(ExtendedElementIdentifier element_id, + const std::string &text) const = 0; - [[nodiscard]] virtual TextStyle style(const Document *) const = 0; + [[nodiscard]] virtual TextStyle + text_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual Element *first_column(const Document *) const = 0; - [[nodiscard]] virtual Element *first_row(const Document *) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + table_first_column(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ExtendedElementIdentifier + table_first_row(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual TableStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableStyle + table_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual TableDimensions + table_cell_span(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual ValueType + table_cell_value_type(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual TableCellStyle style(const Document *) const = 0; + [[nodiscard]] virtual TableCellStyle + table_cell_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - x(const Document *) const = 0; + frame_x(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - y(const Document *) const = 0; + frame_y(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - width(const Document *) const = 0; + frame_width(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - height(const Document *) const = 0; + frame_height(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - z_index(const Document *) const = 0; + frame_z_index(ExtendedElementIdentifier element_id) const = 0; - [[nodiscard]] virtual GraphicStyle style(const Document *) const = 0; + [[nodiscard]] virtual GraphicStyle + frame_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_y(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_width(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + rect_height(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + rect_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_y1(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_x2(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + line_y2(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + line_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_y(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_width(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + circle_height(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + circle_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + custom_shape_width(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + custom_shape_height(ExtendedElementIdentifier element_id) const = 0; + + [[nodiscard]] virtual GraphicStyle + custom_shape_style(ExtendedElementIdentifier 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(ExtendedElementIdentifier element_id) const = 0; [[nodiscard]] virtual std::optional - file(const Document *) const = 0; - [[nodiscard]] virtual std::string href(const Document *) const = 0; + image_file(ExtendedElementIdentifier element_id) const = 0; + [[nodiscard]] virtual std::string + image_href(ExtendedElementIdentifier 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..0f322992 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,15 @@ DocumentType Document::document_type() const noexcept { std::shared_ptr Document::as_filesystem() const noexcept { - return m_filesystem; + return m_files; +} + +ExtendedElementIdentifier 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..6dd24f96 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]] ExtendedElementIdentifier 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{}; + ExtendedElementIdentifier 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/odf/odf_document.cpp b/src/odr/internal/odf/odf_document.cpp index e6139def..d09b06d3 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 { return true; } @@ -42,12 +69,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("/")); @@ -69,7 +96,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()); @@ -82,8 +109,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()); @@ -95,4 +121,868 @@ 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::SheetColumnAdapter, + public abstract::SheetRowAdapter, + 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 ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).type; + } + + [[nodiscard]] ExtendedElementIdentifier + element_parent(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).parent_id; + } + [[nodiscard]] ExtendedElementIdentifier element_first_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).first_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_last_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).last_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_previous_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).previous_sibling_id); + } + [[nodiscard]] ExtendedElementIdentifier element_next_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).next_sibling_id); + } + + [[nodiscard]] bool element_is_editable( + const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).is_editable; + } + + [[nodiscard]] const TextRootAdapter * + text_root_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SlideAdapter * + slide_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const PageAdapter * + page_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetAdapter * + sheet_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetColumnAdapter * + sheet_column_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetRowAdapter * + sheet_row_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetCellAdapter * + sheet_cell_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const MasterPageAdapter * + master_page_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const RectAdapter * + rect_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const LineAdapter * + line_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const CircleAdapter * + circle_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const CustomShapeAdapter * + custom_shape_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ExtendedElementIdentifier) const override { + return this; + } + + [[nodiscard]] PageLayout text_root_page_layout( + const ExtendedElementIdentifier element_id) const override { + if (const ExtendedElementIdentifier master_page_id = + text_root_first_master_page(element_id); + !master_page_id.is_null()) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ExtendedElementIdentifier text_root_first_master_page( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + if (const ExtendedElementIdentifier first_master_page_id = + m_document->style_registry().first_master_page(); + !first_master_page_id.is_null()) { + return first_master_page_id; + } + return {}; + } + + [[nodiscard]] PageLayout + slide_page_layout(const ExtendedElementIdentifier element_id) const override { + if (const ExtendedElementIdentifier master_page_id = + slide_master_page(element_id); + !master_page_id.is_null()) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ExtendedElementIdentifier + slide_master_page(const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("draw:name").value(); + } + + [[nodiscard]] PageLayout + page_layout(const ExtendedElementIdentifier element_id) const override { + if (const ExtendedElementIdentifier master_page_id = + page_master_page(element_id); + !master_page_id.is_null()) { + return master_page_page_layout(master_page_id); + } + return {}; + } + [[nodiscard]] ExtendedElementIdentifier + page_master_page(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("table:name").value(); + } + [[nodiscard]] TableDimensions + sheet_dimensions(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableDimensions + sheet_content(const ExtendedElementIdentifier element_id, + const std::optional range) const override { + (void)element_id; + (void)range; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_column(const ExtendedElementIdentifier element_id, + const std::uint32_t column) const override { + (void)element_id; + (void)column; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_row(const ExtendedElementIdentifier element_id, + const std::uint32_t row) const override { + (void)element_id; + (void)row; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_cell(const ExtendedElementIdentifier element_id, + const std::uint32_t column, + const std::uint32_t row) const override { + (void)element_id; + (void)column; + (void)row; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_first_shape(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableStyle + sheet_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] TableColumnStyle sheet_column_style( + const ExtendedElementIdentifier element_id) const override { + const ElementRegistry::Sheet &sheet_registry = + m_registry->sheet_element(element_id); + + if (const pugi::xml_node column_node = + sheet_registry.column(element_id.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 ExtendedElementIdentifier element_id) const override { + const ElementRegistry::Sheet &sheet_registry = + m_registry->sheet_element(element_id); + + if (const pugi::xml_node column_node = sheet_registry.row(element_id.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]] std::uint32_t + sheet_cell_column(const ExtendedElementIdentifier element_id) const override { + return element_id.column(); + } + [[nodiscard]] std::uint32_t + sheet_cell_row(const ExtendedElementIdentifier element_id) const override { + return element_id.row(); + } + [[nodiscard]] bool sheet_cell_is_covered( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 + sheet_cell_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_cell_style(element_id).table_cell_style; + } + + [[nodiscard]] PageLayout master_page_page_layout( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle paragraph_text_style( + const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ExtendedElementIdentifier element_id) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id, + const std::string &text) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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; + } + } + + m_registry->element(element_id).node = new_first; + m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TableDimensions + table_dimensions(const ExtendedElementIdentifier 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]] ExtendedElementIdentifier table_first_column( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->table_element(element_id).first_column_id); + } + [[nodiscard]] ExtendedElementIdentifier + table_first_row(const ExtendedElementIdentifier element_id) const override { + return element_first_child(element_id); + } + [[nodiscard]] TableStyle + table_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + + [[nodiscard]] TableColumnStyle table_column_style( + const ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_column_style; + } + + [[nodiscard]] TableRowStyle + table_row_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_row_style; + } + + [[nodiscard]] bool table_cell_is_covered( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_cell_style; + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + if (const pugi::xml_attribute attribute = node.attribute("svg:z-index")) { + return attribute.value(); + } + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + rect_x(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:height").value(); + } + [[nodiscard]] GraphicStyle + rect_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + line_x1(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:y2").value(); + } + [[nodiscard]] GraphicStyle + line_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::string + circle_x(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + const pugi::xml_node node = get_node(element_id); + return node.attribute("svg:height").value(); + } + [[nodiscard]] GraphicStyle + circle_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] std::optional + custom_shape_x(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).graphic_style; + } + + [[nodiscard]] bool + image_is_internal(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + return m_registry->element(element_id).node; + } + + [[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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + if (const ElementType type = element_type(element_id); + type == ElementType::sheet_cell) { + return get_partial_cell_style(element_id); + } + if (const char *style_name = get_style_name(element_id)) { + if (const Style *style = m_document->style_registry().style(style_name)) { + return style->resolved(); + } + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ExtendedElementIdentifier element_id) const { + const ExtendedElementIdentifier parent_id = element_parent(element_id); + if (parent_id.is_null()) { + 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 ExtendedElementIdentifier element_id) const { + const char *style_name = nullptr; + + const ElementRegistry::Sheet sheet_registry = + m_registry->sheet_element(element_id.without_extra()); + const auto [column, row] = element_id.cell(); + + const pugi::xml_node cell_node = sheet_registry.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 = sheet_registry.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 = sheet_registry.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 = 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..c7d9ec84 --- /dev/null +++ b/src/odr/internal/odf/odf_element_registry.cpp @@ -0,0 +1,271 @@ +#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(); +} + +ExtendedElementIdentifier ElementRegistry::create_element() { + m_elements.emplace_back(); + return ExtendedElementIdentifier(m_elements.size()); +} + +ElementRegistry::Table & +ElementRegistry::create_table_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_tables.emplace(id.element_id(), Table{}); + return it->second; +} + +ElementRegistry::Text & +ElementRegistry::create_text_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_texts.emplace(id.element_id(), Text{}); + return it->second; +} + +ElementRegistry::Sheet & +ElementRegistry::create_sheet_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_sheets.emplace(id.element_id(), Sheet{}); + return it->second; +} + +[[nodiscard]] ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] const ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) const { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) const { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] ElementRegistry::Sheet & +ElementRegistry::sheet_element(const ExtendedElementIdentifier id) { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } + return m_sheets.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Sheet & +ElementRegistry::sheet_element(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } + return m_sheets.at(id.element_id()); +} + +void ElementRegistry::append_child(const ExtendedElementIdentifier parent_id, + const ExtendedElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ExtendedElementIdentifier previous_sibling_id( + element(parent_id).last_child_id); + + element(child_id).parent_id = parent_id; + element(child_id).first_child_id = null_element_id; + element(child_id).last_child_id = null_element_id; + element(child_id).previous_sibling_id = previous_sibling_id.element_id(); + element(child_id).next_sibling_id = null_element_id; + + if (element(parent_id).first_child_id == null_element_id) { + element(parent_id).first_child_id = child_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = child_id.element_id(); + } + element(parent_id).last_child_id = child_id.element_id(); +} + +void ElementRegistry::append_column(const ExtendedElementIdentifier table_id, + const ExtendedElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + + const ExtendedElementIdentifier previous_sibling_id( + table_element(table_id).last_column_id); + + element(column_id).parent_id = table_id; + element(column_id).first_child_id = null_element_id; + element(column_id).last_child_id = null_element_id; + element(column_id).previous_sibling_id = previous_sibling_id.element_id(); + element(column_id).next_sibling_id = null_element_id; + + if (table_element(table_id).first_column_id == null_element_id) { + table_element(table_id).first_column_id = column_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = column_id.element_id(); + } + table_element(table_id).last_column_id = column_id.element_id(); +} + +void ElementRegistry::append_shape(const ExtendedElementIdentifier sheet_id, + const ExtendedElementIdentifier shape_id) { + check_sheet_id(sheet_id); + check_element_id(shape_id); + + const ExtendedElementIdentifier previous_sibling_id( + sheet_element(sheet_id).last_shape_id); + + element(shape_id).parent_id = sheet_id; + element(shape_id).first_child_id = null_element_id; + element(shape_id).last_child_id = null_element_id; + element(shape_id).previous_sibling_id = previous_sibling_id.element_id(); + element(shape_id).next_sibling_id = null_element_id; + + if (sheet_element(sheet_id).first_shape_id == null_element_id) { + sheet_element(sheet_id).first_shape_id = shape_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = shape_id.element_id(); + } + sheet_element(sheet_id).last_shape_id = shape_id.element_id(); +} + +void ElementRegistry::append_sheet_cell( + const ExtendedElementIdentifier sheet_id, + const ExtendedElementIdentifier cell_id) { + check_sheet_id(sheet_id); + check_element_id(cell_id); + + element(cell_id).parent_id = sheet_id; + element(cell_id).first_child_id = null_element_id; + element(cell_id).last_child_id = null_element_id; + element(cell_id).previous_sibling_id = null_element_id; + element(cell_id).next_sibling_id = null_element_id; +} + +void ElementRegistry::check_element_id( + const ExtendedElementIdentifier id) const { + if (id.is_null()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id.element_id() - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::Sheet::create_column(const std::uint32_t column, + const std::uint32_t repeated, + const pugi::xml_node element) { + columns[column + repeated] = {.node = element}; +} + +void ElementRegistry::Sheet::create_row(const std::uint32_t row, + const std::uint32_t repeated, + const pugi::xml_node element) { + rows[row + repeated].node = element; +} + +void ElementRegistry::Sheet::create_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 bool is_repeated = columns_repeated > 1 || rows_repeated > 1; + + Cell &cell = rows[row + rows_repeated].cells[column + columns_repeated]; + cell.node = element; + cell.is_repeated = is_repeated; +} + +pugi::xml_node +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.node; + } + return {}; +} + +pugi::xml_node 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.node; + } + return {}; +} + +pugi::xml_node ElementRegistry::Sheet::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.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..1fa126a3 --- /dev/null +++ b/src/odr/internal/odf/odf_element_registry.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::odf { + +class ElementRegistry final { +public: + struct Element final { + ExtendedElementIdentifier parent_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; + }; + + struct Sheet final { + struct Column final { + pugi::xml_node node; + }; + + struct Cell final { + pugi::xml_node node; + bool is_repeated{false}; + }; + + 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 create_column(std::uint32_t column, std::uint32_t repeated, + pugi::xml_node element); + void create_row(std::uint32_t row, std::uint32_t repeated, + pugi::xml_node element); + void create_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; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + ExtendedElementIdentifier create_element(); + Table &create_table_element(ExtendedElementIdentifier id); + Text &create_text_element(ExtendedElementIdentifier id); + Sheet &create_sheet_element(ExtendedElementIdentifier id); + + [[nodiscard]] Element &element(ExtendedElementIdentifier id); + [[nodiscard]] const Element &element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Table &table_element(ExtendedElementIdentifier id); + [[nodiscard]] const Table &table_element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Text &text_element(ExtendedElementIdentifier id); + [[nodiscard]] const Text &text_element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Sheet &sheet_element(ExtendedElementIdentifier id); + [[nodiscard]] const Sheet &sheet_element(ExtendedElementIdentifier id) const; + + void append_child(ExtendedElementIdentifier parent_id, + ExtendedElementIdentifier child_id); + void append_column(ExtendedElementIdentifier table_id, + ExtendedElementIdentifier column_id); + void append_shape(ExtendedElementIdentifier sheet_id, + ExtendedElementIdentifier shape_id); + void append_sheet_cell(ExtendedElementIdentifier sheet_id, + ExtendedElementIdentifier cell_id); + +private: + std::vector m_elements; + std::unordered_map m_tables; + std::unordered_map m_texts; + std::unordered_map m_sheets; + + void check_element_id(ExtendedElementIdentifier id) const; + void check_table_id(ExtendedElementIdentifier id) const; + void check_text_id(ExtendedElementIdentifier id) const; + void check_sheet_id(ExtendedElementIdentifier 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..03f6d99d 100644 --- a/src/odr/internal/odf/odf_parser.cpp +++ b/src/odr/internal/odf/odf_parser.cpp @@ -1,14 +1,58 @@ #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 ExtendedElementIdentifier 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.is_null()) { + 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; + } + + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = type; + + 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 +73,264 @@ 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 {ExtendedElementIdentifier::null(), 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); - } -} + const ExtendedElementIdentifier element_id = registry.create_element(); + auto &[last] = registry.create_text_element(element_id); -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); + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { } -} -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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - pugi::xml_node last = node; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { - } + const ExtendedElementIdentifier element_id = registry.create_element(); - 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - auto table_unique = std::make_unique(node); - auto table = table_unique.get(); - document.register_element_(std::move(table_unique)); + const ExtendedElementIdentifier element_id = registry.create_element(); // 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 {ExtendedElementIdentifier::null(), 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 ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Sheet &sheet = registry.create_sheet_element(element_id); - 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.create_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.create_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.create_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) { + const ExtendedElementIdentifier cell_id = + ExtendedElementIdentifier::make_cell( + element_id.element_id(), cursor.column() + column_repeat, + cursor.row() + row_repeat); + registry.append_sheet_cell(element_id, cell_id); + parse_any_element_children(registry, cell_id, cell_node); + } + } + } + + cursor.add_cell(colspan, rowspan, columns_repeated); + } + + cursor.add_row(rows_repeated); + } + + 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.is_null()) { + registry.append_shape(element_id, shape_id); + } + } + + return {element_id, node.next_sibling()}; +} + +void parse_presentation_children(ElementRegistry ®istry, + const ExtendedElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : node.children("draw:page")) { + auto [child_id, _] = parse_any_element_tree(registry, child_node); + registry.append_child(root_id, child_id); + } +} + +void parse_spreadsheet_children(ElementRegistry ®istry, + const ExtendedElementIdentifier 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); } +} - return std::make_tuple(table_row, node.next_sibling()); +void parse_drawing_children(ElementRegistry ®istry, + const ExtendedElementIdentifier root_id, + const pugi::xml_node node) { + for (const pugi::xml_node child_node : node.children("draw:page")) { + auto [child_id, _] = parse_any_element_tree(registry, child_node); + registry.append_child(root_id, child_id); + } } -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}}; +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", create_default_tree_parser(ElementType::text)}, + {"text:tab", create_default_tree_parser(ElementType::text)}, + {"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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; +} + +} // namespace + +} // namespace odr::internal::odf + +namespace odr::internal { + +ExtendedElementIdentifier 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..f65f8da2 100644 --- a/src/odr/internal/odf/odf_parser.hpp +++ b/src/odr/internal/odf/odf_parser.hpp @@ -1,58 +1,17 @@ #pragma once -#include +namespace pugi { +class xml_node; +} // namespace pugi -#include -#include -#include - -namespace odr::internal::odf { -class Element; -class PresentationRoot; -class SpreadsheetRoot; -class DrawingRoot; -class Text; -class Table; -class TableRow; - -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()); +namespace odr { +class ExtendedElementIdentifier; } -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); +namespace odr::internal::odf { +class ElementRegistry; -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); +ExtendedElementIdentifier 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..bcf12cd3 100644 --- a/src/odr/internal/odf/odf_style.cpp +++ b/src/odr/internal/odf/odf_style.cpp @@ -1,3 +1,5 @@ +#include "odf_document.hpp" + #include #include @@ -556,7 +558,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 +590,8 @@ pugi::xml_node StyleRegistry::font_face_node(const std::string &name) const { return {}; } -MasterPage *StyleRegistry::master_page(const std::string &name) const { +ExtendedElementIdentifier +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 +599,7 @@ MasterPage *StyleRegistry::master_page(const std::string &name) const { return {}; } -MasterPage *StyleRegistry::first_master_page() const { +ExtendedElementIdentifier 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..e789a5b1 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,9 @@ 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]] ExtendedElementIdentifier + master_page(const std::string &name) const; + [[nodiscard]] ExtendedElementIdentifier first_master_page() const; private: std::unordered_map m_index_font_face; @@ -89,8 +93,9 @@ 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; + ExtendedElementIdentifier 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..eed2bc29 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp @@ -3,27 +3,41 @@ #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"))) { + parse_relationships(*m_files, AbsPath("/ppt/presentation.xml"))) { m_slides_xml[relationships.first] = util::xml::parse( - *m_filesystem, AbsPath("/ppt").join(RelPath(relationships.second))); + *m_files, AbsPath("/ppt").join(RelPath(relationships.second))); } - 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_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 +54,502 @@ 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 { + +class ElementAdapter final : public abstract::ElementAdapter, + public abstract::PageAdapter, + 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 ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).type; + } + + [[nodiscard]] ExtendedElementIdentifier + element_parent(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).parent_id; + } + [[nodiscard]] ExtendedElementIdentifier element_first_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).first_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_last_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).last_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_previous_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).previous_sibling_id); + } + [[nodiscard]] ExtendedElementIdentifier element_next_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).next_sibling_id); + } + + [[nodiscard]] bool element_is_editable( + const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).is_editable; + } + + [[nodiscard]] const abstract::TextRootAdapter * + text_root_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const PageAdapter * + page_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::SheetAdapter * + sheet_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetColumnAdapter * + sheet_column_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetRowAdapter * + sheet_row_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetCellAdapter * + sheet_cell_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ExtendedElementIdentifier) const override { + return this; + } + + [[nodiscard]] PageLayout + page_layout(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + page_master_page(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] std::string + page_name(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] TextStyle + line_break_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle paragraph_text_style( + const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ExtendedElementIdentifier element_id) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id, + const std::string &text) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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; + } + } + + m_registry->element(element_id).node = new_first; + m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] std::string + bookmark_name(const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TableDimensions + table_dimensions(const ExtendedElementIdentifier 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]] ExtendedElementIdentifier table_first_column( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->table_element(element_id).first_column_id); + } + [[nodiscard]] ExtendedElementIdentifier + table_first_row(const ExtendedElementIdentifier element_id) const override { + return element_first_child(element_id); } - return {}; + [[nodiscard]] TableStyle + table_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + + [[nodiscard]] TableColumnStyle table_column_style( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] bool table_cell_is_covered( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_y(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_width(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + return m_registry->element(element_id).node; + } + + [[nodiscard]] pugi::xml_node + get_frame_inner_node(const ExtendedElementIdentifier 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]] 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]] const char * + get_style_name(const ExtendedElementIdentifier element_id) const { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] ResolvedStyle + get_partial_style(const ExtendedElementIdentifier element_id) const { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ExtendedElementIdentifier element_id) const { + (void)element_id; + return {}; // TODO + } +}; + +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.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..bfe162b2 --- /dev/null +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp @@ -0,0 +1,144 @@ +#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(); +} + +ExtendedElementIdentifier ElementRegistry::create_element() { + m_elements.emplace_back(); + return ExtendedElementIdentifier(m_elements.size()); +} + +ElementRegistry::Table & +ElementRegistry::create_table_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_tables.emplace(id.element_id(), Table{}); + return it->second; +} + +ElementRegistry::Text & +ElementRegistry::create_text_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_texts.emplace(id.element_id(), Text{}); + return it->second; +} + +[[nodiscard]] ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] const ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) const { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) const { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +void ElementRegistry::check_element_id( + const ExtendedElementIdentifier id) const { + if (id.is_null()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id.element_id() - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ExtendedElementIdentifier parent_id, + const ExtendedElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ExtendedElementIdentifier previous_sibling_id( + element(parent_id).last_child_id); + + element(child_id).parent_id = parent_id; + element(child_id).first_child_id = null_element_id; + element(child_id).last_child_id = null_element_id; + element(child_id).previous_sibling_id = previous_sibling_id.element_id(); + element(child_id).next_sibling_id = null_element_id; + + if (element(parent_id).first_child_id == null_element_id) { + element(parent_id).first_child_id = child_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = child_id.element_id(); + } + element(parent_id).last_child_id = child_id.element_id(); +} + +void ElementRegistry::append_column(const ExtendedElementIdentifier table_id, + const ExtendedElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + + const ExtendedElementIdentifier previous_sibling_id( + table_element(table_id).last_column_id); + + element(column_id).parent_id = table_id; + element(column_id).first_child_id = null_element_id; + element(column_id).last_child_id = null_element_id; + element(column_id).previous_sibling_id = previous_sibling_id.element_id(); + element(column_id).next_sibling_id = null_element_id; + + if (table_element(table_id).first_column_id == null_element_id) { + table_element(table_id).first_column_id = column_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = column_id.element_id(); + } + table_element(table_id).last_column_id = column_id.element_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..0a6e112b --- /dev/null +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::presentation { + +class ElementRegistry final { +public: + struct Element final { + ExtendedElementIdentifier parent_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; + + ExtendedElementIdentifier create_element(); + Table &create_table_element(ExtendedElementIdentifier id); + Text &create_text_element(ExtendedElementIdentifier id); + + [[nodiscard]] Element &element(ExtendedElementIdentifier id); + [[nodiscard]] const Element &element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Table &table_element(ExtendedElementIdentifier id); + [[nodiscard]] const Table &table_element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Text &text_element(ExtendedElementIdentifier id); + [[nodiscard]] const Text &text_element(ExtendedElementIdentifier id) const; + + void append_child(ExtendedElementIdentifier parent_id, + ExtendedElementIdentifier child_id); + void append_column(ExtendedElementIdentifier table_id, + ExtendedElementIdentifier column_id); + +private: + std::vector m_elements; + std::unordered_map m_tables; + std::unordered_map m_texts; + + void check_element_id(ExtendedElementIdentifier id) const; + void check_table_id(ExtendedElementIdentifier id) const; + void check_text_id(ExtendedElementIdentifier 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..fb6c715e 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.cpp @@ -1,65 +1,57 @@ #include -#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); - -std::tuple -parse_any_element_tree(Document &document, pugi::xml_node node); - -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) { +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 ExtendedElementIdentifier 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(registry, child_node); + child_id.is_null()) { child_node = child_node.next_sibling(); } else { - element->append_child_(child); + 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(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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - auto element_unique = std::make_unique(node); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = type; - 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) { @@ -67,7 +59,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 +71,53 @@ 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(ElementRegistry ®istry, pugi::xml_node first) { if (!first) { - return std::make_tuple(nullptr, pugi::xml_node()); + return {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - pugi::xml_node last = first; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { - } + const ExtendedElementIdentifier element_id = registry.create_element(); + auto &[last] = registry.create_text_element(element_id); - auto element_unique = std::make_unique(first, last); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { + } - return std::make_tuple(element, last.next_sibling()); + return {element_id, last.next_sibling()}; } -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(ElementRegistry ®istry, 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{ + {"p:presentation", create_default_tree_parser(ElementType::root)}, + {"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()); 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } } // namespace @@ -132,10 +126,9 @@ 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); +ExtendedElementIdentifier presentation::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/presentation/ooxml_presentation_parser.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_parser.hpp index 9231d127..243efe5c 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 +namespace pugi { +class xml_node; +} -#include -#include -#include - -#include +namespace odr { +class ExtendedElementIdentifier; +} namespace odr::internal::ooxml::presentation { -class Document; -class Element; +class ElementRegistry; -Element *parse_tree(Document &document, pugi::xml_node node); +ExtendedElementIdentifier parse_tree(ElementRegistry ®istry, + pugi::xml_node node); } // 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..e161a280 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp @@ -1,18 +1,26 @@ #include #include +#include +#include #include #include +#include #include #include 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); +} + +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 +39,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 +49,21 @@ 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); } -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 +80,505 @@ 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::SheetColumnAdapter, + public abstract::SheetRowAdapter, + public abstract::SheetCellAdapter, + public abstract::LineBreakAdapter, + public abstract::ParagraphAdapter, + public abstract::SpanAdapter, + public abstract::TextAdapter, + public abstract::FrameAdapter, + public abstract::ImageAdapter { +public: + ElementAdapter(const Document &document, ElementRegistry ®istry) + : m_document(&document), m_registry(®istry) {} + + [[nodiscard]] ElementType + element_type(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).type; + } + + [[nodiscard]] ExtendedElementIdentifier + element_parent(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).parent_id; + } + [[nodiscard]] ExtendedElementIdentifier element_first_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).first_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_last_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).last_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_previous_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).previous_sibling_id); + } + [[nodiscard]] ExtendedElementIdentifier element_next_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).next_sibling_id); + } + + [[nodiscard]] bool element_is_editable( + const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).is_editable; + } + + [[nodiscard]] const abstract::TextRootAdapter * + text_root_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const SheetAdapter * + sheet_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetColumnAdapter * + sheet_column_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetRowAdapter * + sheet_row_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SheetCellAdapter * + sheet_cell_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::LinkAdapter * + link_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::BookmarkAdapter * + bookmark_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::ListItemAdapter * + list_item_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableAdapter * + table_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableColumnAdapter * + table_column_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableRowAdapter * + table_row_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::TableCellAdapter * + table_cell_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ExtendedElementIdentifier) const override { + return this; + } + + [[nodiscard]] std::string + sheet_name(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableDimensions + sheet_dimensions(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableDimensions + sheet_content(const ExtendedElementIdentifier element_id, + const std::optional range) const override { + (void)element_id; + (void)range; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_column(const ExtendedElementIdentifier element_id, + const std::uint32_t column) const override { + (void)element_id; + (void)column; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_row(const ExtendedElementIdentifier element_id, + const std::uint32_t row) const override { + (void)element_id; + (void)row; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_cell(const ExtendedElementIdentifier element_id, + const std::uint32_t column, + const std::uint32_t row) const override { + (void)element_id; + (void)column; + (void)row; + return {}; // TODO + } + [[nodiscard]] ExtendedElementIdentifier + sheet_first_shape(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableStyle + sheet_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] TableColumnStyle sheet_column_style( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] TableRowStyle + sheet_row_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + + [[nodiscard]] std::uint32_t + sheet_cell_column(const ExtendedElementIdentifier element_id) const override { + return element_id.column(); + } + [[nodiscard]] std::uint32_t + sheet_cell_row(const ExtendedElementIdentifier element_id) const override { + return element_id.row(); + } + [[nodiscard]] bool sheet_cell_is_covered( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableDimensions + sheet_cell_span(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] ValueType sheet_cell_value_type( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; // TODO + } + [[nodiscard]] TableCellStyle + sheet_cell_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_cell_style(element_id).table_cell_style; + } + + [[nodiscard]] TextStyle + line_break_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle paragraph_text_style( + const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ExtendedElementIdentifier element_id) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id, + const std::string &text) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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; + } + } + + m_registry->element(element_id).node = new_first; + m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] AnchorType + frame_anchor_type(const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_y(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_width(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + return m_registry->element(element_id).node; + } + + [[nodiscard]] pugi::xml_node + get_frame_inner_node(const ExtendedElementIdentifier 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]] 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]] const char * + get_style_name(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + const ExtendedElementIdentifier parent_id = element_parent(element_id); + if (parent_id.is_null()) { + 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::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.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..e2807bc9 --- /dev/null +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp @@ -0,0 +1,216 @@ +#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(); +} + +ExtendedElementIdentifier ElementRegistry::create_element() { + m_elements.emplace_back(); + return ExtendedElementIdentifier(m_elements.size()); +} + +ElementRegistry::Text & +ElementRegistry::create_text_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_texts.emplace(id.element_id(), Text{}); + return it->second; +} + +ElementRegistry::Sheet & +ElementRegistry::create_sheet_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_sheets.emplace(id.element_id(), Sheet{}); + return it->second; +} + +[[nodiscard]] ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] const ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) const { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] ElementRegistry::Sheet & +ElementRegistry::sheet_element(const ExtendedElementIdentifier id) { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } + return m_sheets.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Sheet & +ElementRegistry::sheet_element(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } + return m_sheets.at(id.element_id()); +} + +void ElementRegistry::check_element_id( + const ExtendedElementIdentifier id) const { + if (id.is_null()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id.element_id() - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_text_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_sheet_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_sheets.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ExtendedElementIdentifier parent_id, + const ExtendedElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ExtendedElementIdentifier previous_sibling_id( + element(parent_id).last_child_id); + + element(child_id).parent_id = parent_id; + element(child_id).first_child_id = null_element_id; + element(child_id).last_child_id = null_element_id; + element(child_id).previous_sibling_id = previous_sibling_id.element_id(); + element(child_id).next_sibling_id = null_element_id; + + if (element(parent_id).first_child_id == null_element_id) { + element(parent_id).first_child_id = child_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = child_id.element_id(); + } + element(parent_id).last_child_id = child_id.element_id(); +} + +void ElementRegistry::append_shape(const ExtendedElementIdentifier sheet_id, + const ExtendedElementIdentifier shape_id) { + check_sheet_id(sheet_id); + check_element_id(shape_id); + + const ExtendedElementIdentifier previous_sibling_id( + sheet_element(sheet_id).last_shape_id); + + element(shape_id).parent_id = sheet_id; + element(shape_id).first_child_id = null_element_id; + element(shape_id).last_child_id = null_element_id; + element(shape_id).previous_sibling_id = previous_sibling_id.element_id(); + element(shape_id).next_sibling_id = null_element_id; + + if (sheet_element(sheet_id).first_shape_id == null_element_id) { + sheet_element(sheet_id).first_shape_id = shape_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = shape_id.element_id(); + } + sheet_element(sheet_id).last_shape_id = shape_id.element_id(); +} + +void ElementRegistry::append_sheet_cell( + const ExtendedElementIdentifier sheet_id, + const ExtendedElementIdentifier cell_id) { + check_sheet_id(sheet_id); + check_element_id(cell_id); + + element(cell_id).parent_id = sheet_id; + element(cell_id).first_child_id = null_element_id; + element(cell_id).last_child_id = null_element_id; + element(cell_id).previous_sibling_id = null_element_id; + element(cell_id).next_sibling_id = null_element_id; +} + +void ElementRegistry::Sheet::create_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::create_row(const std::uint32_t row, + const pugi::xml_node element) { + rows[row].node = element; +} + +void ElementRegistry::Sheet::create_cell(const std::uint32_t column, + const std::uint32_t row, + const pugi::xml_node element) { + Cell &cell = rows[row].cells[column]; + cell.node = element; +} + +pugi::xml_node +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.node; + } + return {}; +} + +pugi::xml_node 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.node; + } + return {}; +} + +pugi::xml_node ElementRegistry::Sheet::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.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..4e7fd8ee --- /dev/null +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::spreadsheet { + +class ElementRegistry final { +public: + struct Element final { + ExtendedElementIdentifier parent_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 Text final { + pugi::xml_node last; + }; + + struct Sheet final { + struct Column final { + pugi::xml_node node; + }; + + struct Cell final { + pugi::xml_node node; + }; + + 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 create_column(std::uint32_t column_min, std::uint32_t column_max, + pugi::xml_node element); + void create_row(std::uint32_t row, pugi::xml_node element); + void create_cell(std::uint32_t column, std::uint32_t row, + 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; + }; + + void clear() noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + + ExtendedElementIdentifier create_element(); + Text &create_text_element(ExtendedElementIdentifier id); + Sheet &create_sheet_element(ExtendedElementIdentifier id); + + [[nodiscard]] Element &element(ExtendedElementIdentifier id); + [[nodiscard]] const Element &element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Text &text_element(ExtendedElementIdentifier id); + [[nodiscard]] const Text &text_element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Sheet &sheet_element(ExtendedElementIdentifier id); + [[nodiscard]] const Sheet &sheet_element(ExtendedElementIdentifier id) const; + + void append_child(ExtendedElementIdentifier parent_id, + ExtendedElementIdentifier child_id); + void append_shape(ExtendedElementIdentifier sheet_id, + ExtendedElementIdentifier shape_id); + void append_sheet_cell(ExtendedElementIdentifier sheet_id, + ExtendedElementIdentifier cell_id); + +private: + std::vector m_elements; + std::unordered_map m_texts; + std::unordered_map m_sheets; + + void check_element_id(ExtendedElementIdentifier id) const; + void check_text_id(ExtendedElementIdentifier id) const; + void check_sheet_id(ExtendedElementIdentifier 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..f4ca72cd 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.cpp @@ -1,143 +1,137 @@ #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 ExtendedElementIdentifier 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) { + if (const auto [child_id, next_sibling] = + parse_any_element_tree(registry, context, child_node); + child_id.is_null()) { child_node = child_node.next_sibling(); } else { - element->append_child_(child); + 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; + } + + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = type; + + children_parser(registry, context, element_id, node); + + return {element_id, node.next_sibling()}; +} + +void parse_root_children(ElementRegistry ®istry, const ParseContext &context, + ExtendedElementIdentifier 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, + ExtendedElementIdentifier 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); + parse_any_element_children(registry, context, parent_id, node); } -void parse_element_children(Document &document, Frame *element, - const pugi::xml_node node, - const Path &document_path, - const Relations &document_relations) { +void parse_frame_children(ElementRegistry ®istry, + const ParseContext &context, + ExtendedElementIdentifier parent_id, + const pugi::xml_node node) { 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 [image, _] = parse_any_element_tree(registry, context, image_node); + registry.append_child(parent_id, image); } - - 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()); } -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 {ExtendedElementIdentifier::null(), 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 ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = ElementType::sheet; + ElementRegistry::Sheet &sheet = registry.create_sheet_element(element_id); 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.create_column(min, max - min, 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.create_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); + sheet.create_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); + auto [cell, _] = parse_any_element_tree(registry, context, cell_node); + registry.append_sheet_cell(element_id, cell); } } @@ -150,28 +144,27 @@ 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 : + 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); + auto [shape, _] = parse_any_element_tree(registry, context, shape_node); + if (!shape.is_null()) { + 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) { @@ -191,51 +184,57 @@ bool is_text_node(const pugi::xml_node node) { 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - pugi::xml_node last = first; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { - } + const ExtendedElementIdentifier element_id = registry.create_element(); + auto &[last] = registry.create_text_element(element_id); - auto element_unique = - std::make_unique(first, last, document_path, document_relations); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { + } - return std::make_tuple(element, last.next_sibling()); + return {element_id, last.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", + create_default_tree_parser(ElementType::frame, parse_frame_children)}, }; 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } } // namespace @@ -244,13 +243,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; +ExtendedElementIdentifier 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..82ffa0f8 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_parser.hpp @@ -3,12 +3,46 @@ #include #include +namespace pugi { +class xml_node; +} + +namespace odr { +class ExtendedElementIdentifier; +} + 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); +ExtendedElementIdentifier 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 4103bfa8..252fb925 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_document.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_document.cpp @@ -2,33 +2,59 @@ #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); +} + +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); +} + +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 false; } @@ -41,7 +67,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 +83,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 +94,531 @@ 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) + : m_document(&document), m_registry(®istry) {} + + [[nodiscard]] ElementType + element_type(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).type; + } + + [[nodiscard]] ExtendedElementIdentifier + element_parent(const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).parent_id; + } + [[nodiscard]] ExtendedElementIdentifier element_first_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).first_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_last_child( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).last_child_id); + } + [[nodiscard]] ExtendedElementIdentifier element_previous_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).previous_sibling_id); + } + [[nodiscard]] ExtendedElementIdentifier element_next_sibling( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->element(element_id).next_sibling_id); + } + + [[nodiscard]] bool element_is_editable( + const ExtendedElementIdentifier element_id) const override { + return m_registry->element(element_id).is_editable; + } + + [[nodiscard]] const TextRootAdapter * + text_root_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetAdapter * + sheet_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetColumnAdapter * + sheet_column_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetRowAdapter * + sheet_row_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::SheetCellAdapter * + sheet_cell_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const LineBreakAdapter * + line_break_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ParagraphAdapter * + paragraph_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const SpanAdapter * + span_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TextAdapter * + text_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const LinkAdapter * + link_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const BookmarkAdapter * + bookmark_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const ListItemAdapter * + list_item_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableAdapter * + table_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableColumnAdapter * + table_column_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableRowAdapter * + table_row_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const TableCellAdapter * + table_cell_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const FrameAdapter * + frame_adapter(const ExtendedElementIdentifier) const override { + return this; + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ExtendedElementIdentifier) const override { + return nullptr; + } + [[nodiscard]] const ImageAdapter * + image_adapter(const ExtendedElementIdentifier) const override { + return this; + } + + [[nodiscard]] PageLayout text_root_page_layout( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + [[nodiscard]] ExtendedElementIdentifier text_root_first_master_page( + const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] TextStyle + line_break_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] ParagraphStyle + paragraph_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).paragraph_style; + } + [[nodiscard]] TextStyle paragraph_text_style( + const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TextStyle + span_style(const ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + text_content(const ExtendedElementIdentifier element_id) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id, + const std::string &text) const override { + const pugi::xml_node first = get_node(element_id); + const pugi::xml_node last = m_registry->text_element(element_id).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; + } + } + + m_registry->element(element_id).node = new_first; + m_registry->text_element(element_id).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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] std::string + link_href(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + return get_intermediate_style(element_id).text_style; + } + + [[nodiscard]] TableDimensions + table_dimensions(const ExtendedElementIdentifier 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]] ExtendedElementIdentifier table_first_column( + const ExtendedElementIdentifier element_id) const override { + return ExtendedElementIdentifier( + m_registry->table_element(element_id).first_column_id); + } + [[nodiscard]] ExtendedElementIdentifier + table_first_row(const ExtendedElementIdentifier element_id) const override { + return element_first_child(element_id); + } + [[nodiscard]] TableStyle + table_style(const ExtendedElementIdentifier element_id) const override { + return get_partial_style(element_id).table_style; + } + + [[nodiscard]] TableColumnStyle table_column_style( + const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_y(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] std::optional + frame_width(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const override { + (void)element_id; + return std::nullopt; + } + [[nodiscard]] GraphicStyle + frame_style(const ExtendedElementIdentifier element_id) const override { + (void)element_id; + return {}; + } + + [[nodiscard]] bool + image_is_internal(const ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + return m_registry->element(element_id).node; + } + + [[nodiscard]] pugi::xml_node + get_frame_inner_node(const ExtendedElementIdentifier 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]] const char * + get_style_name(const ExtendedElementIdentifier 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 ExtendedElementIdentifier element_id) const { + if (const char *style_name = get_style_name(element_id)) { + if (const Style *style = m_document->style_registry().style(style_name)) { + return style->resolved(); + } + } + return {}; + } + + [[nodiscard]] ResolvedStyle + get_intermediate_style(const ExtendedElementIdentifier element_id) const { + const ExtendedElementIdentifier parent_id = element_parent(element_id); + ResolvedStyle base; + if (parent_id.is_null()) { + 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) { + return std::make_unique(document, registry); +} + +} // 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..3702215e --- /dev/null +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp @@ -0,0 +1,144 @@ +#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(); +} + +ExtendedElementIdentifier ElementRegistry::create_element() { + m_elements.emplace_back(); + return ExtendedElementIdentifier(m_elements.size()); +} + +ElementRegistry::Table & +ElementRegistry::create_table_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_tables.emplace(id.element_id(), Table{}); + return it->second; +} + +ElementRegistry::Text & +ElementRegistry::create_text_element(const ExtendedElementIdentifier id) { + check_element_id(id); + auto [it, success] = m_texts.emplace(id.element_id(), Text{}); + return it->second; +} + +[[nodiscard]] ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] const ElementRegistry::Element & +ElementRegistry::element(const ExtendedElementIdentifier id) const { + check_element_id(id); + return m_elements.at(id.element_id() - 1); +} + +[[nodiscard]] ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Table & +ElementRegistry::table_element(const ExtendedElementIdentifier id) const { + check_table_id(id); + return m_tables.at(id.element_id()); +} + +[[nodiscard]] ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +[[nodiscard]] const ElementRegistry::Text & +ElementRegistry::text_element(const ExtendedElementIdentifier id) const { + check_text_id(id); + return m_texts.at(id.element_id()); +} + +void ElementRegistry::check_element_id( + const ExtendedElementIdentifier id) const { + if (id.is_null()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: null identifier"); + } + if (id.element_id() - 1 >= m_elements.size()) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier out of range"); + } +} + +void ElementRegistry::check_table_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_tables.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::check_text_id(const ExtendedElementIdentifier id) const { + check_element_id(id); + if (!m_texts.contains(id.element_id())) { + throw std::out_of_range( + "DocumentElementRegistry::check_id: identifier not found"); + } +} + +void ElementRegistry::append_child(const ExtendedElementIdentifier parent_id, + const ExtendedElementIdentifier child_id) { + check_element_id(parent_id); + check_element_id(child_id); + + const ExtendedElementIdentifier previous_sibling_id( + element(parent_id).last_child_id); + + element(child_id).parent_id = parent_id; + element(child_id).first_child_id = null_element_id; + element(child_id).last_child_id = null_element_id; + element(child_id).previous_sibling_id = previous_sibling_id.element_id(); + element(child_id).next_sibling_id = null_element_id; + + if (element(parent_id).first_child_id == null_element_id) { + element(parent_id).first_child_id = child_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = child_id.element_id(); + } + element(parent_id).last_child_id = child_id.element_id(); +} + +void ElementRegistry::append_column(const ExtendedElementIdentifier table_id, + const ExtendedElementIdentifier column_id) { + check_table_id(table_id); + check_element_id(column_id); + + const ExtendedElementIdentifier previous_sibling_id( + table_element(table_id).last_column_id); + + element(column_id).parent_id = table_id; + element(column_id).first_child_id = null_element_id; + element(column_id).last_child_id = null_element_id; + element(column_id).previous_sibling_id = previous_sibling_id.element_id(); + element(column_id).next_sibling_id = null_element_id; + + if (table_element(table_id).first_column_id == null_element_id) { + table_element(table_id).first_column_id = column_id.element_id(); + } else { + element(previous_sibling_id).next_sibling_id = column_id.element_id(); + } + table_element(table_id).last_column_id = column_id.element_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..d9ea9405 --- /dev/null +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace odr::internal::ooxml::text { + +class ElementRegistry final { +public: + struct Element final { + ExtendedElementIdentifier parent_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; + + ExtendedElementIdentifier create_element(); + Table &create_table_element(ExtendedElementIdentifier id); + Text &create_text_element(ExtendedElementIdentifier id); + + [[nodiscard]] Element &element(ExtendedElementIdentifier id); + [[nodiscard]] const Element &element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Table &table_element(ExtendedElementIdentifier id); + [[nodiscard]] const Table &table_element(ExtendedElementIdentifier id) const; + + [[nodiscard]] Text &text_element(ExtendedElementIdentifier id); + [[nodiscard]] const Text &text_element(ExtendedElementIdentifier id) const; + + void append_child(ExtendedElementIdentifier parent_id, + ExtendedElementIdentifier child_id); + void append_column(ExtendedElementIdentifier table_id, + ExtendedElementIdentifier column_id); + +private: + std::vector m_elements; + std::unordered_map m_tables; + std::unordered_map m_texts; + + void check_element_id(ExtendedElementIdentifier id) const; + void check_table_id(ExtendedElementIdentifier id) const; + void check_text_id(ExtendedElementIdentifier 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..1a361e4d 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_parser.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_parser.cpp @@ -1,50 +1,57 @@ #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 ExtendedElementIdentifier 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.is_null()) { 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - auto element_unique = std::make_unique(node); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = type; - 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 +71,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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - pugi::xml_node last = node; - for (; is_text_node(last.next_sibling()); last = last.next_sibling()) { - } + const ExtendedElementIdentifier element_id = registry.create_element(); + auto &[last] = registry.create_text_element(element_id); - auto element_unique = std::make_unique(node, last); - auto element = element_unique.get(); - document.register_element_(std::move(element_unique)); + for (last = first; is_text_node(last.next_sibling()); + last = last.next_sibling()) { + } - 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 +99,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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - auto list_unique = std::make_unique(node); - List *list = list_unique.get(); - document.register_element_(std::move(list_unique)); + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = ElementType::list; for (; is_list_item(node); node = node.next_sibling()) { - List *base = list; const std::int32_t level = list_level(node); for (std::int32_t i = 0; i < level; ++i) { @@ -118,112 +121,115 @@ 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)); - - // list_item->init_append_child(nested_list); + const ExtendedElementIdentifier nested_id = registry.create_element(); + ElementRegistry::Element &nested_element = registry.element(nested_id); + nested_element.type = ElementType::list; - base->append_child_(nested_list); - base = nested_list; + registry.append_child(element_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 ExtendedElementIdentifier item_id = registry.create_element(); + ElementRegistry::Element &item_element = registry.element(item_id); + item_element.type = ElementType::list_item; - base->append_child_(list_item); + registry.append_child(element_id, item_id); - auto [element, _] = parse_element_tree(document, node); - list_item->append_child_(element); + auto [child_id, _] = parse_any_element_tree(registry, node); + registry.append_child(item_id, child_id); } - return std::make_tuple(list, node); + return {element_id, node.next_sibling()}; } -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 {ExtendedElementIdentifier::null(), 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 ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = ElementType::table_row; 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } - auto table_unique = std::make_unique
(node); - Table *table = table_unique.get(); - document.register_element_(std::move(table_unique)); + const ExtendedElementIdentifier element_id = registry.create_element(); + ElementRegistry::Element &element = registry.element(element_id); + element.type = ElementType::table_row; 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 {ExtendedElementIdentifier::null(), pugi::xml_node()}; } } // namespace @@ -232,8 +238,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); +ExtendedElementIdentifier 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..df30d329 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_parser.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_parser.hpp @@ -1,11 +1,17 @@ #pragma once -#include +namespace pugi { +class xml_node; +} + +namespace odr { +class ExtendedElementIdentifier; +} namespace odr::internal::ooxml::text { -class Document; -class Element; +class ElementRegistry; -Element *parse_tree(Document &document, pugi::xml_node node); +ExtendedElementIdentifier 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