From 159a1560e8d9bcd6f0c06d14075249b0bc28c472 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 6 Sep 2026 09:27:57 +0200 Subject: [PATCH 1/3] refactor(document): share the element registry and adapter odf and ooxml pasted Every engine that builds an element tree writes the same flat store, the same tree links and the same adapter navigation. Two headers in `internal/common` now hold that shape, and odf and the three ooxml engines are ported onto it: - `internal::ElementRegistry` owns the element vector, the id/index convention, `element_at`, `append_child` and `link_child`, over an element the engine derives from `ElementNode` and a payload side table (hashed, or odf's sorted one) that carries its own bounds check. - `internal::ElementAdapter` answers every `*_adapter(id)` hook from the adapters it is given, and `RegistryElementAdapter` adds the six navigation methods. No behaviour change: the reference output is byte-identical. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VEsRyBu8o4TGJGn3thNEDc --- src/odr/internal/common/element_adapter.hpp | 196 ++++++++++++++++++ src/odr/internal/common/element_registry.hpp | 185 +++++++++++++++++ src/odr/internal/odf/odf_document.cpp | 187 ++--------------- src/odr/internal/odf/odf_element_registry.cpp | 166 ++------------- src/odr/internal/odf/odf_element_registry.hpp | 157 ++++---------- .../ooxml_presentation_document.cpp | 130 +----------- .../ooxml_presentation_element_registry.cpp | 123 +---------- .../ooxml_presentation_element_registry.hpp | 58 +++--- .../ooxml_spreadsheet_document.cpp | 107 +--------- .../ooxml_spreadsheet_element_registry.cpp | 169 ++------------- .../ooxml_spreadsheet_element_registry.hpp | 68 +++--- .../ooxml/text/ooxml_text_document.cpp | 136 +----------- .../text/ooxml_text_element_registry.cpp | 140 ++----------- .../text/ooxml_text_element_registry.hpp | 61 +++--- 14 files changed, 613 insertions(+), 1270 deletions(-) create mode 100644 src/odr/internal/common/element_adapter.hpp create mode 100644 src/odr/internal/common/element_registry.hpp diff --git a/src/odr/internal/common/element_adapter.hpp b/src/odr/internal/common/element_adapter.hpp new file mode 100644 index 000000000..8cb87e52f --- /dev/null +++ b/src/odr/internal/common/element_adapter.hpp @@ -0,0 +1,196 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace odr::internal { + +/// Answers the `*_adapter(id)` hooks for the @p Adapters it is given, and +/// inherits them: a hook whose adapter is in the pack returns `this` for its +/// element type, the rest keep the abstract nullptr. +template +class ElementAdapter : public abstract::ElementAdapter, public Adapters... { +public: + [[nodiscard]] bool element_is_unique( + [[maybe_unused]] const ElementIdentifier element_id) const override { + return true; + } + [[nodiscard]] bool element_is_self_locatable( + [[maybe_unused]] const ElementIdentifier element_id) const override { + return true; + } + [[nodiscard]] bool element_is_editable( + [[maybe_unused]] const ElementIdentifier element_id) const override { + return false; + } + [[nodiscard]] DocumentPath + element_document_path(const ElementIdentifier element_id) const override { + return util::document::extract_path(*this, element_id, null_element_id); + } + [[nodiscard]] ElementIdentifier + element_navigate_path(const ElementIdentifier element_id, + const DocumentPath &path) const override { + return util::document::navigate_path(*this, element_id, path); + } + + [[nodiscard]] const abstract::TextRootAdapter * + text_root_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::SlideAdapter * + slide_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::PageAdapter * + page_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::SheetAdapter * + sheet_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::SheetCellAdapter * + sheet_cell_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::MasterPageAdapter * + master_page_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::LineBreakAdapter * + line_break_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::ParagraphAdapter * + paragraph_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::SpanAdapter * + span_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::TextAdapter * + text_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::LinkAdapter * + link_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::BookmarkAdapter * + bookmark_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::ListAdapter * + list_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::ListItemAdapter * + list_item_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::TableAdapter * + table_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::TableColumnAdapter * + table_column_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::TableRowAdapter * + table_row_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::TableCellAdapter * + table_cell_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::FrameAdapter * + frame_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::RectAdapter * + rect_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::LineAdapter * + line_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::CircleAdapter * + circle_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + [[nodiscard]] const abstract::CustomShapeAdapter * + custom_shape_adapter(const ElementIdentifier element_id) const override { + return adapter_( + element_id); + } + [[nodiscard]] const abstract::ImageAdapter * + image_adapter(const ElementIdentifier element_id) const override { + return adapter_(element_id); + } + +private: + template + [[nodiscard]] const Adapter * + adapter_(const ElementIdentifier element_id) const { + if constexpr ((std::is_same_v || ...)) { + return element_type(element_id) == type ? this : nullptr; + } else { + return nullptr; + } + } +}; + +/// Navigates the tree of a registry whose `element_at(id)` yields the links. +template +class RegistryElementAdapter : public ElementAdapter { +public: + explicit RegistryElementAdapter(Registry ®istry) : m_registry(®istry) {} + + [[nodiscard]] ElementType + element_type(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).type; + } + + [[nodiscard]] ElementIdentifier + element_parent(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).parent_id; + } + [[nodiscard]] ElementIdentifier + element_first_child(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).first_child_id; + } + [[nodiscard]] ElementIdentifier + element_last_child(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).last_child_id; + } + [[nodiscard]] ElementIdentifier + element_previous_sibling(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).previous_sibling_id; + } + [[nodiscard]] ElementIdentifier + element_next_sibling(const ElementIdentifier element_id) const override { + return m_registry->element_at(element_id).next_sibling_id; + } + +protected: + Registry *m_registry{nullptr}; +}; + +} // namespace odr::internal diff --git a/src/odr/internal/common/element_registry.hpp b/src/odr/internal/common/element_registry.hpp new file mode 100644 index 000000000..35acc1ca1 --- /dev/null +++ b/src/odr/internal/common/element_registry.hpp @@ -0,0 +1,185 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace odr::internal { + +/// The tree links every registry element carries, stored @p Id wide. +template struct ElementNode { + Id parent_id{null_element_id}; + Id first_child_id{null_element_id}; + Id last_child_id{null_element_id}; + Id previous_sibling_id{null_element_id}; + Id next_sibling_id{null_element_id}; + ElementType type{ElementType::none}; +}; + +/// A per-type payload keyed by element id, for payloads written out of parse +/// order. @ref SortedSideTable is cheaper where they are not. +template class SideTable final { +public: + T &emplace(const ElementIdentifier id, T value) { + return m_entries.insert_or_assign(id, std::move(value)).first->second; + } + + [[nodiscard]] T *find(const ElementIdentifier id) { + return const_cast(std::as_const(*this).find(id)); + } + + [[nodiscard]] const T *find(const ElementIdentifier id) const { + const auto it = m_entries.find(id); + return it != std::end(m_entries) ? &it->second : nullptr; + } + + [[nodiscard]] T &at(const ElementIdentifier id) { + return const_cast(std::as_const(*this).at(id)); + } + + [[nodiscard]] const T &at(const ElementIdentifier id) const { + const T *entry = find(id); + if (entry == nullptr) { + throw std::out_of_range("SideTable::at: identifier not found"); + } + return *entry; + } + +private: + std::unordered_map m_entries; +}; + +/// A per-type payload keyed by element id and written in id order, so a lookup +/// is a binary search. A deque: `emplace` hands out a reference. +template +class SortedSideTable final { +public: + T &emplace(const ElementIdentifier id, T value) { + if (!m_entries.empty() && m_entries.back().first >= id) { + throw std::invalid_argument( + "SortedSideTable::emplace: identifier out of order"); + } + return m_entries.emplace_back(static_cast(id), std::move(value)).second; + } + + [[nodiscard]] T *find(const ElementIdentifier id) { + return const_cast(std::as_const(*this).find(id)); + } + + [[nodiscard]] const T *find(const ElementIdentifier id) const { + const auto it = std::ranges::lower_bound(m_entries, id, {}, &Entry::first); + return it != std::end(m_entries) && it->first == id ? &it->second : nullptr; + } + + [[nodiscard]] T &at(const ElementIdentifier id) { + return const_cast(std::as_const(*this).at(id)); + } + + [[nodiscard]] const T &at(const ElementIdentifier id) const { + const T *entry = find(id); + if (entry == nullptr) { + throw std::out_of_range("SortedSideTable::at: identifier not found"); + } + return *entry; + } + + [[nodiscard]] auto begin() const noexcept { return m_entries.begin(); } + [[nodiscard]] auto end() const noexcept { return m_entries.end(); } + [[nodiscard]] auto begin() noexcept { return m_entries.begin(); } + [[nodiscard]] auto end() noexcept { return m_entries.end(); } + +private: + using Entry = std::pair; + + std::deque m_entries; +}; + +/// The flat store an engine builds its element tree in: an id is the index +/// plus one, and @p ElementT derives from @ref ElementNode for the links. +/// +/// A deque by default, because `create_element_` hands back a reference the +/// parser holds on to. +template > +class ElementRegistry { +public: + using Element = ElementT; + + [[nodiscard]] std::size_t size() const noexcept { return m_elements.size(); } + + [[nodiscard]] Element &element_at(const ElementIdentifier id) { + check_element_id(id); + return m_elements[id - 1]; + } + + [[nodiscard]] const Element &element_at(const ElementIdentifier id) const { + check_element_id(id); + return m_elements[id - 1]; + } + + void append_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id) { + Element &parent = element_at(parent_id); + link_child(parent_id, child_id, parent.first_child_id, + parent.last_child_id); + } + +protected: + ~ElementRegistry() = default; + + std::tuple create_element_(ElementType type) { + if (m_elements.size() >= std::numeric_limits::max()) { + throw std::overflow_error( + "ElementRegistry::create_element: out of identifiers"); + } + + Element &element = m_elements.emplace_back(); + const ElementIdentifier element_id = m_elements.size(); + element.type = type; + return {element_id, element}; + } + + /// Links @p child_id as the last child of the chain @p first_id / @p last_id + /// - the element's own, or one of the secondary chains a payload holds. + void link_child(const ElementIdentifier parent_id, + const ElementIdentifier child_id, Id &first_id, Id &last_id) { + Element &child = element_at(child_id); + if (child.parent_id != null_element_id) { + throw std::invalid_argument( + "ElementRegistry::link_child: child already has a parent"); + } + + child.parent_id = static_cast(parent_id); + child.previous_sibling_id = last_id; + + if (first_id == null_element_id) { + first_id = static_cast(child_id); + } else { + element_at(last_id).next_sibling_id = static_cast(child_id); + } + last_id = static_cast(child_id); + } + + void check_element_id(const ElementIdentifier id) const { + if (id == null_element_id) { + throw std::out_of_range( + "ElementRegistry::check_element_id: null identifier"); + } + if (id - 1 >= m_elements.size()) { + throw std::out_of_range( + "ElementRegistry::check_element_id: identifier out of range"); + } + } + + Container m_elements; +}; + +} // namespace odr::internal diff --git a/src/odr/internal/odf/odf_document.cpp b/src/odr/internal/odf/odf_document.cpp index a1336ddd7..97a3b7dfc 100644 --- a/src/odr/internal/odf/odf_document.cpp +++ b/src/odr/internal/odf/odf_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -171,69 +172,23 @@ std::optional connector_box(const pugi::xml_node node) { return read_path(node); } -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::TextRootAdapter, - public abstract::SlideAdapter, - public abstract::PageAdapter, - public abstract::SheetAdapter, - public abstract::SheetCellAdapter, - public abstract::MasterPageAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::SpanAdapter, - public abstract::TextAdapter, - public abstract::LinkAdapter, - public abstract::BookmarkAdapter, - public abstract::ListAdapter, - 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 { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::TextRootAdapter, abstract::SlideAdapter, + abstract::PageAdapter, abstract::SheetAdapter, abstract::SheetCellAdapter, + abstract::MasterPageAdapter, abstract::LineBreakAdapter, + abstract::ParagraphAdapter, abstract::SpanAdapter, abstract::TextAdapter, + abstract::LinkAdapter, abstract::BookmarkAdapter, abstract::ListAdapter, + abstract::ListItemAdapter, abstract::TableAdapter, + abstract::TableColumnAdapter, abstract::TableRowAdapter, + abstract::TableCellAdapter, abstract::FrameAdapter, abstract::RectAdapter, + abstract::LineAdapter, abstract::CircleAdapter, + abstract::CustomShapeAdapter, abstract::ImageAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry) - : m_document(&document), m_registry(®istry) {} + : AdapterBase(registry), m_document(&document) {} - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } [[nodiscard]] bool element_is_editable(const ElementIdentifier element_id) const override { const ElementRegistry::Element &element = @@ -246,117 +201,6 @@ class ElementAdapter final : public abstract::ElementAdapter, } return true; } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const SlideAdapter * - slide_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::slide ? this : nullptr; - } - [[nodiscard]] const PageAdapter * - page_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::page ? this : nullptr; - } - [[nodiscard]] const SheetAdapter * - sheet_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet ? this : nullptr; - } - [[nodiscard]] const SheetCellAdapter * - sheet_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet_cell ? this : nullptr; - } - [[nodiscard]] const MasterPageAdapter * - master_page_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::master_page ? this - : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const LinkAdapter * - link_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::link ? this : nullptr; - } - [[nodiscard]] const BookmarkAdapter * - bookmark_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::bookmark ? this : nullptr; - } - [[nodiscard]] const ListAdapter * - list_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list ? this : nullptr; - } - - [[nodiscard]] const ListItemAdapter * - list_item_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list_item ? this : nullptr; - } - [[nodiscard]] const TableAdapter * - table_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table ? this : nullptr; - } - [[nodiscard]] const TableColumnAdapter * - table_column_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_column ? this - : nullptr; - } - [[nodiscard]] const TableRowAdapter * - table_row_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_row ? this : nullptr; - } - [[nodiscard]] const TableCellAdapter * - table_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_cell ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const RectAdapter * - rect_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::rect ? this : nullptr; - } - [[nodiscard]] const LineAdapter * - line_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line ? this : nullptr; - } - [[nodiscard]] const CircleAdapter * - circle_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::circle ? this : nullptr; - } - [[nodiscard]] const CustomShapeAdapter * - custom_shape_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::custom_shape ? this - : nullptr; - } - [[nodiscard]] const ImageAdapter * - image_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::image ? this : nullptr; - } [[nodiscard]] PageLayout text_root_page_layout(const ElementIdentifier element_id) const override { @@ -1052,7 +896,6 @@ class ElementAdapter final : public abstract::ElementAdapter, private: const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; mutable std::mutex m_charts_mutex; mutable std::unordered_map> m_charts; diff --git a/src/odr/internal/odf/odf_element_registry.cpp b/src/odr/internal/odf/odf_element_registry.cpp index a630ae0fc..772254686 100644 --- a/src/odr/internal/odf/odf_element_registry.cpp +++ b/src/odr/internal/odf/odf_element_registry.cpp @@ -1,36 +1,14 @@ #include #include -#include #include namespace odr::internal::odf { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); - m_tables.clear(); - m_sheets.clear(); - m_sheet_cells.clear(); - m_list_types.clear(); - m_list_markers.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type, const pugi::xml_node node) { - if (m_elements.size() >= std::numeric_limits::max()) { - throw std::overflow_error( - "ElementRegistry::create_element: out of identifiers"); - } - - Element &element = m_elements.emplace_back(); - const ElementIdentifier element_id = m_elements.size(); - element.type = type; + const auto &[element_id, element] = create_element_(type); element.node = node; return {element_id, element}; } @@ -73,146 +51,32 @@ ElementRegistry::create_sheet_cell_element(const pugi::xml_node node, return {element_id, element, sheet_cell}; } -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_element_id(id); - return m_texts.at(id); -} - -ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) { - check_element_id(id); - return m_tables.at(id); -} - -ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) { - check_element_id(id); - return m_sheets.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_tables.at(id); -} - -const ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_sheets.at(id); -} - -const ElementRegistry::SheetCell & -ElementRegistry::sheet_cell_element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_sheet_cells.at(id); -} - -const ElementRegistry::SheetCell * -ElementRegistry::sheet_cell_element(const ElementIdentifier id) const { - return m_sheet_cells.find(id); -} - -void ElementRegistry::link_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id, - StoredId &first_id, StoredId &last_id) { - Element &child = element_at(child_id); - child.parent_id = static_cast(parent_id); - child.previous_sibling_id = last_id; - - if (first_id == null_element_id) { - first_id = static_cast(child_id); - } else { - element_at(last_id).next_sibling_id = static_cast(child_id); - } - last_id = static_cast(child_id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "DocumentElementRegistry::append_child: child already has a parent"); - } - - Element &parent = element_at(parent_id); - link_child(parent_id, child_id, parent.first_child_id, parent.last_child_id); -} - void ElementRegistry::append_column(const ElementIdentifier table_id, const ElementIdentifier column_id) { Table &table = table_element_at(table_id); - check_element_id(column_id); - if (element_at(column_id).parent_id != null_element_id) { - throw std::invalid_argument( - "DocumentElementRegistry::append_column: child already has a parent"); - } - link_child(table_id, column_id, table.first_column_id, table.last_column_id); } void ElementRegistry::append_shape(const ElementIdentifier sheet_id, const ElementIdentifier shape_id) { Sheet &sheet = sheet_element_at(sheet_id); - check_element_id(shape_id); - if (element_at(shape_id).parent_id != null_element_id) { - throw std::invalid_argument( - "DocumentElementRegistry::append_shape: child already has a parent"); - } - link_child(sheet_id, shape_id, sheet.first_shape_id, sheet.last_shape_id); } void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, const ElementIdentifier cell_id) { - check_sheet_id(sheet_id); - check_element_id(cell_id); - if (element_at(cell_id).parent_id != null_element_id) { - throw std::invalid_argument("DocumentElementRegistry::append_sheet_cell: " - "child already has a parent"); - } - - element_at(cell_id).parent_id = static_cast(sheet_id); -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { + if (m_sheets.find(sheet_id) == nullptr) { throw std::out_of_range( - "DocumentElementRegistry::check_id: null identifier"); + "ElementRegistry::append_sheet_cell: not a sheet identifier"); } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier out of range"); - } -} -void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { - check_element_id(id); - if (m_sheets.find(id) == nullptr) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); + Element &cell = element_at(cell_id); + if (cell.parent_id != null_element_id) { + throw std::invalid_argument( + "ElementRegistry::append_sheet_cell: child already has a parent"); } + + cell.parent_id = static_cast(sheet_id); } void ElementRegistry::Sheet::register_column(const std::uint32_t column, @@ -331,26 +195,26 @@ ElementRegistry::Sheet::cell_node(const std::uint32_t column, void ElementRegistry::set_list_type(const ElementIdentifier id, const ListType type) { check_element_id(id); - m_list_types[id] = type; + m_list_types.emplace(id, type); } void ElementRegistry::set_list_marker(const ElementIdentifier id, ListMarker marker) { check_element_id(id); - m_list_markers[id] = std::move(marker); + m_list_markers.emplace(id, std::move(marker)); } [[nodiscard]] ListType ElementRegistry::list_type(const ElementIdentifier id) const { - const auto it = m_list_types.find(id); - return it != std::end(m_list_types) ? it->second : ListType::unordered; + const ListType *type = m_list_types.find(id); + return type != nullptr ? *type : ListType::unordered; } [[nodiscard]] const ListMarker & ElementRegistry::list_marker(const ElementIdentifier id) const { static const ListMarker none; - const auto it = m_list_markers.find(id); - return it != std::end(m_list_markers) ? it->second : none; + const ListMarker *marker = m_list_markers.find(id); + return marker != nullptr ? *marker : none; } } // namespace odr::internal::odf diff --git a/src/odr/internal/odf/odf_element_registry.hpp b/src/odr/internal/odf/odf_element_registry.hpp index 99badd90f..bc77d2258 100644 --- a/src/odr/internal/odf/odf_element_registry.hpp +++ b/src/odr/internal/odf/odf_element_registry.hpp @@ -3,39 +3,31 @@ #include #include +#include #include #include #include -#include #include -#include #include -#include -#include -#include +#include #include #include namespace odr::internal::odf { -class ElementRegistry final { -public: - /// An element is five ids, so the width is most of what one costs. Widened - /// back at every boundary; an id past @ref check_element_id fits. - using StoredId = std::uint32_t; - - struct Element final { - StoredId parent_id{null_element_id}; - StoredId first_child_id{null_element_id}; - StoredId last_child_id{null_element_id}; - StoredId previous_sibling_id{null_element_id}; - StoredId next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - pugi::xml_node node; - }; +/// An element is five ids, so the width is most of what one costs. Widened +/// back at every boundary; an id past `check_element_id` fits. +using StoredId = std::uint32_t; + +struct RegistryElement final : ElementNode { + pugi::xml_node node; +}; +class ElementRegistry final + : public internal::ElementRegistry { +public: struct Table final { StoredId first_column_id{null_element_id}; StoredId last_column_id{null_element_id}; @@ -105,68 +97,6 @@ class ElementRegistry final { bool is_repeated{false}; }; - /// Payloads keyed by element id and written in id order, so a lookup is a - /// binary search. A deque: `create_*_element` hands out a reference. - template class SideTable final { - public: - T &emplace(const ElementIdentifier id, T value) { - if (!m_entries.empty() && m_entries.back().first >= id) { - throw std::invalid_argument( - "ElementRegistry::SideTable::emplace: identifier out of order"); - } - return m_entries.emplace_back(static_cast(id), std::move(value)) - .second; - } - - [[nodiscard]] T *find(const ElementIdentifier id) { - const auto it = - std::ranges::lower_bound(m_entries, id, {}, &Entry::first); - return it != std::end(m_entries) && it->first == id ? &it->second - : nullptr; - } - - [[nodiscard]] const T *find(const ElementIdentifier id) const { - const auto it = - std::ranges::lower_bound(m_entries, id, {}, &Entry::first); - return it != std::end(m_entries) && it->first == id ? &it->second - : nullptr; - } - - [[nodiscard]] T &at(const ElementIdentifier id) { - T *entry = find(id); - if (entry == nullptr) { - throw std::out_of_range( - "ElementRegistry::SideTable::at: identifier not found"); - } - return *entry; - } - - [[nodiscard]] const T &at(const ElementIdentifier id) const { - const T *entry = find(id); - if (entry == nullptr) { - throw std::out_of_range( - "ElementRegistry::SideTable::at: identifier not found"); - } - return *entry; - } - - void clear() noexcept { m_entries.clear(); } - - [[nodiscard]] auto begin() const noexcept { return m_entries.begin(); } - [[nodiscard]] auto end() const noexcept { return m_entries.end(); } - [[nodiscard]] auto begin() noexcept { return m_entries.begin(); } - [[nodiscard]] auto end() noexcept { return m_entries.end(); } - - private: - using Entry = std::pair; - - std::deque m_entries; - }; - - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type, pugi::xml_node node); std::tuple @@ -179,19 +109,36 @@ class ElementRegistry final { create_sheet_cell_element(pugi::xml_node node, const TablePosition &position, bool is_repeated); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Table &table_element_at(ElementIdentifier id); - [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; - [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Table &table_element_at(const ElementIdentifier id) { + return m_tables.at(id); + } + [[nodiscard]] Sheet &sheet_element_at(const ElementIdentifier id) { + return m_sheets.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Table & + table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); + } + [[nodiscard]] const Sheet & + sheet_element_at(const ElementIdentifier id) const { + return m_sheets.at(id); + } [[nodiscard]] const SheetCell & - sheet_cell_element_at(ElementIdentifier id) const; + sheet_cell_element_at(const ElementIdentifier id) const { + return m_sheet_cells.at(id); + } - [[nodiscard]] const SheetCell *sheet_cell_element(ElementIdentifier id) const; + [[nodiscard]] const SheetCell * + sheet_cell_element(const ElementIdentifier id) const { + return m_sheet_cells.find(id); + } void set_list_type(ElementIdentifier id, ListType type); void set_list_marker(ElementIdentifier id, ListMarker marker); @@ -199,30 +146,18 @@ class ElementRegistry final { [[nodiscard]] ListType list_type(ElementIdentifier id) const; [[nodiscard]] const ListMarker &list_marker(ElementIdentifier id) const; - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); void append_column(ElementIdentifier table_id, ElementIdentifier column_id); void append_shape(ElementIdentifier sheet_id, ElementIdentifier shape_id); void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); private: - /// A deque, not a vector: `create_element` hands back a reference the parser - /// holds on to, and a vector both invalidates it and peaks holding two - /// copies. - std::deque m_elements; - SideTable m_texts; - SideTable m_tables; - SideTable m_sheets; - SideTable m_sheet_cells; + SortedSideTable m_texts; + SortedSideTable m_tables; + SortedSideTable m_sheets; + SortedSideTable m_sheet_cells; // out of id order: written when a list is resolved, not when it is parsed - std::unordered_map m_list_types; - std::unordered_map m_list_markers; - - /// Links `child_id` as the last child of the chain `first_id`/`last_id`. - void link_child(ElementIdentifier parent_id, ElementIdentifier child_id, - StoredId &first_id, StoredId &last_id); - - void check_element_id(ElementIdentifier id) const; - void check_sheet_id(ElementIdentifier id) const; + SideTable m_list_types; + SideTable m_list_markers; }; } // namespace odr::internal::odf diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp index 2eae7c387..e0d261159 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -139,127 +140,17 @@ const ElementRegistry &Document::element_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::SlideAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::SpanAdapter, - public abstract::TextAdapter, - public abstract::LinkAdapter, - public abstract::BookmarkAdapter, - public abstract::TableAdapter, - public abstract::TableColumnAdapter, - public abstract::TableRowAdapter, - public abstract::TableCellAdapter, - public abstract::FrameAdapter, - public abstract::ImageAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::SlideAdapter, abstract::LineBreakAdapter, + abstract::ParagraphAdapter, abstract::SpanAdapter, abstract::TextAdapter, + abstract::LinkAdapter, abstract::BookmarkAdapter, abstract::TableAdapter, + abstract::TableColumnAdapter, abstract::TableRowAdapter, + abstract::TableCellAdapter, abstract::FrameAdapter, abstract::ImageAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry) - : m_document(&document), m_registry(®istry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - // read-only; text_set_content below stays dormant until save is wired up - return false; - } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const SlideAdapter * - slide_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::slide ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const LinkAdapter * - link_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::link ? this : nullptr; - } - [[nodiscard]] const BookmarkAdapter * - bookmark_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::bookmark ? this : nullptr; - } - [[nodiscard]] const TableAdapter * - table_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table ? this : nullptr; - } - [[nodiscard]] const TableColumnAdapter * - table_column_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_column ? this - : nullptr; - } - [[nodiscard]] const TableRowAdapter * - table_row_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_row ? this : nullptr; - } - [[nodiscard]] const TableCellAdapter * - table_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_cell ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const ImageAdapter * - image_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::image ? this : nullptr; - } + : AdapterBase(registry), m_document(&document) {} [[nodiscard]] PageLayout slide_page_layout(const ElementIdentifier element_id) const override { @@ -528,7 +419,6 @@ class ElementAdapter final : public abstract::ElementAdapter, private: const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; [[nodiscard]] pugi::xml_node get_node(const ElementIdentifier element_id) const { diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp index d0ac87244..cc26d350c 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.cpp @@ -1,25 +1,11 @@ #include -#include - namespace odr::internal::ooxml::presentation { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_tables.clear(); - m_texts.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type, const pugi::xml_node node) { - Element &element = m_elements.emplace_back(); - const ElementIdentifier element_id = m_elements.size(); - element.type = type; + const auto &[element_id, element] = create_element_(type); element.node = node; return {element_id, element}; } @@ -28,8 +14,8 @@ std::tuple ElementRegistry::create_table_element(const pugi::xml_node node) { const auto &[element_id, element] = create_element(ElementType::table, node); - auto [it, success] = m_tables.emplace(element_id, Table{}); - return {element_id, element, it->second}; + Table &table = m_tables.emplace(element_id, Table{}); + return {element_id, element, table}; } std::tuplesecond}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) { - check_table_id(id); - return m_tables.at(id); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) const { - check_table_id(id); - return m_tables.at(id); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_table_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_tables.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + Text &text = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, text}; } void ElementRegistry::append_column(const ElementIdentifier table_id, const ElementIdentifier column_id) { - check_table_id(table_id); - check_element_id(column_id); - - const ElementIdentifier previous_sibling_id = - table_element_at(table_id).last_column_id; - - element_at(column_id).parent_id = table_id; - element_at(column_id).previous_sibling_id = previous_sibling_id; - - if (table_element_at(table_id).first_column_id == null_element_id) { - table_element_at(table_id).first_column_id = column_id; - } else { - element_at(previous_sibling_id).next_sibling_id = column_id; - } - table_element_at(table_id).last_column_id = column_id; + Table &table = table_element_at(table_id); + link_child(table_id, column_id, table.first_column_id, table.last_column_id); } } // namespace odr::internal::ooxml::presentation diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp index 1530caa36..baa32205c 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp @@ -3,26 +3,23 @@ #include #include -#include -#include +#include + +#include #include #include namespace odr::internal::ooxml::presentation { -class ElementRegistry final { -public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - pugi::xml_node node; - }; +struct RegistryElement final : ElementNode { + pugi::xml_node node; +}; +class ElementRegistry final + : public internal::ElementRegistry> { +public: struct Table final { ElementIdentifier first_column_id{null_element_id}; ElementIdentifier last_column_id{null_element_id}; @@ -32,10 +29,6 @@ class ElementRegistry final { pugi::xml_node last; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type, pugi::xml_node node); std::tuple @@ -43,25 +36,26 @@ class ElementRegistry final { std::tuple create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Table &table_element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); + [[nodiscard]] Table &table_element_at(const ElementIdentifier id) { + return m_tables.at(id); + } + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + + [[nodiscard]] const Table & + table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); + } + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); void append_column(ElementIdentifier table_id, ElementIdentifier column_id); private: - std::vector m_elements; - std::unordered_map m_tables; - std::unordered_map m_texts; - - void check_element_id(ElementIdentifier id) const; - void check_table_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; + SideTable
m_tables; + SideTable m_texts; }; } // 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 d1fe65e0c..489a5ce1d 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -80,105 +81,16 @@ Document::parse_xml_(const AbsPath &path) { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::SheetAdapter, - public abstract::SheetCellAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::SpanAdapter, - public abstract::TextAdapter, - public abstract::LinkAdapter, - public abstract::FrameAdapter, - public abstract::ImageAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::SheetAdapter, abstract::SheetCellAdapter, + abstract::LineBreakAdapter, abstract::ParagraphAdapter, + abstract::SpanAdapter, abstract::TextAdapter, abstract::LinkAdapter, + abstract::FrameAdapter, abstract::ImageAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry) - : m_document(&document), m_registry(®istry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return false; - } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const SheetAdapter * - sheet_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet ? this : nullptr; - } - [[nodiscard]] const SheetCellAdapter * - sheet_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet_cell ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const LinkAdapter * - link_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::link ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const ImageAdapter * - image_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::image ? this : nullptr; - } + : AdapterBase(registry), m_document(&document) {} [[nodiscard]] std::string sheet_name(const ElementIdentifier element_id) const override { @@ -423,7 +335,6 @@ class ElementAdapter final : public abstract::ElementAdapter, private: const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; [[nodiscard]] pugi::xml_node get_node(const ElementIdentifier element_id) const { diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp index eb4b2a3f6..d639ed2ef 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.cpp @@ -6,24 +6,10 @@ namespace odr::internal::ooxml::spreadsheet { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_element_relations.clear(); - m_texts.clear(); - m_sheets.clear(); - m_sheet_cells.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type, const pugi::xml_node node) { - Element &element = m_elements.emplace_back(); - const ElementIdentifier element_id = m_elements.size(); - element.type = type; + const auto &[element_id, element] = create_element_(type); element.node = node; return {element_id, element}; } @@ -34,16 +20,16 @@ ElementRegistry::create_text_element(const pugi::xml_node first_node, const pugi::xml_node last_node) { const auto &[element_id, element] = create_element(ElementType::text, first_node); - auto [it, success] = m_texts.emplace(element_id, Text{last_node}); - return {element_id, element, it->second}; + Text &text = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, text}; } std::tuple ElementRegistry::create_sheet_element(const pugi::xml_node node) { const auto &[element_id, element] = create_element(ElementType::sheet, node); - auto [it, success] = m_sheets.emplace(element_id, Sheet{}); - return {element_id, element, it->second}; + Sheet &sheet = m_sheets.emplace(element_id, Sheet{}); + return {element_id, element, sheet}; } std::tuplesecond}; + return {element_id, element, sheet_cell}; } ElementRegistry::ElementRelations & @@ -62,145 +48,26 @@ ElementRegistry::attach_element_relations(const ElementIdentifier id, const Relations &relations, const AbsPath &origin) { check_element_id(id); - if (m_element_relations.contains(id)) { - throw std::runtime_error("DocumentElementRegistry::attach_element_" - "relations: relations already attached"); - } - ElementRelations &result = m_element_relations[id]; - result.relations = &relations; - result.origin = origin; - return result; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) { - check_sheet_id(id); - return m_sheets.at(id); -} - -ElementRegistry::SheetCell & -ElementRegistry::sheet_cell_element_at(const ElementIdentifier id) { - check_sheet_cell_id(id); - return m_sheet_cells.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) const { - check_sheet_id(id); - return m_sheets.at(id); -} - -const ElementRegistry::SheetCell & -ElementRegistry::sheet_cell_element_at(const ElementIdentifier id) const { - check_sheet_cell_id(id); - return m_sheet_cells.at(id); -} - -const ElementRegistry::ElementRelations * -ElementRegistry::element_relations(const ElementIdentifier id) const { - if (const auto it = m_element_relations.find(id); - it != m_element_relations.end()) { - return &it->second; + if (m_element_relations.find(id) != nullptr) { + throw std::runtime_error("ElementRegistry::attach_element_relations: " + "relations already attached"); } - return nullptr; -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_sheets.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_sheet_cell_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_sheet_cells.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + return m_element_relations.emplace( + id, ElementRelations{.relations = &relations, .origin = origin}); } void ElementRegistry::append_shape(const ElementIdentifier sheet_id, const ElementIdentifier shape_id) { - check_sheet_id(sheet_id); - check_element_id(shape_id); - - const ElementIdentifier previous_sibling_id = - sheet_element_at(sheet_id).last_shape_id; - - element_at(shape_id).parent_id = sheet_id; - element_at(shape_id).previous_sibling_id = previous_sibling_id; - - if (sheet_element_at(sheet_id).first_shape_id == null_element_id) { - sheet_element_at(sheet_id).first_shape_id = shape_id; - } else { - element_at(previous_sibling_id).next_sibling_id = shape_id; - } - sheet_element_at(sheet_id).last_shape_id = shape_id; + Sheet &sheet = sheet_element_at(sheet_id); + link_child(sheet_id, shape_id, sheet.first_shape_id, sheet.last_shape_id); } void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, const ElementIdentifier cell_id) { - check_sheet_id(sheet_id); - check_element_id(cell_id); - + if (m_sheets.find(sheet_id) == nullptr) { + throw std::out_of_range( + "ElementRegistry::append_sheet_cell: not a sheet identifier"); + } element_at(cell_id).parent_id = sheet_id; } diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp index 4ef63f107..e1913ec2a 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include #include @@ -18,18 +20,14 @@ namespace odr::internal::ooxml::spreadsheet { -class ElementRegistry final { -public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - pugi::xml_node node; - }; +struct RegistryElement final : ElementNode { + pugi::xml_node node; +}; +class ElementRegistry final + : public internal::ElementRegistry> { +public: struct ElementRelations final { const Relations *relations{nullptr}; AbsPath origin; @@ -88,10 +86,6 @@ class ElementRegistry final { bool is_covered{false}; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type, pugi::xml_node node); std::tuple @@ -105,34 +99,38 @@ class ElementRegistry final { const Relations &relations, const AbsPath &origin); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); - [[nodiscard]] SheetCell &sheet_cell_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; + [[nodiscard]] Sheet &sheet_element_at(const ElementIdentifier id) { + return m_sheets.at(id); + } + [[nodiscard]] SheetCell &sheet_cell_element_at(const ElementIdentifier id) { + return m_sheet_cells.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Sheet & + sheet_element_at(const ElementIdentifier id) const { + return m_sheets.at(id); + } [[nodiscard]] const SheetCell & - sheet_cell_element_at(ElementIdentifier id) const; + sheet_cell_element_at(const ElementIdentifier id) const { + return m_sheet_cells.at(id); + } [[nodiscard]] const ElementRelations * - element_relations(ElementIdentifier id) const; + element_relations(const ElementIdentifier id) const { + return m_element_relations.find(id); + } - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); void append_shape(ElementIdentifier sheet_id, ElementIdentifier shape_id); void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); private: - std::vector m_elements; - std::unordered_map m_element_relations; - std::unordered_map m_texts; - std::unordered_map m_sheets; - std::unordered_map m_sheet_cells; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; - void check_sheet_id(ElementIdentifier id) const; - void check_sheet_cell_id(ElementIdentifier id) const; + SideTable m_element_relations; + SideTable m_texts; + SideTable m_sheets; + SideTable m_sheet_cells; }; } // namespace odr::internal::ooxml::spreadsheet diff --git a/src/odr/internal/ooxml/text/ooxml_text_document.cpp b/src/odr/internal/ooxml/text/ooxml_text_document.cpp index 6b44f00ca..d1b4d4b40 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_document.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -155,137 +156,23 @@ void Document::save(std::ostream & /*out*/, const char * /*password*/) const { 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::ListAdapter, - public abstract::ListItemAdapter, - public abstract::TableAdapter, - public abstract::TableColumnAdapter, - public abstract::TableRowAdapter, - public abstract::TableCellAdapter, - public abstract::FrameAdapter, - public abstract::ImageAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::TextRootAdapter, abstract::LineBreakAdapter, + abstract::ParagraphAdapter, abstract::SpanAdapter, abstract::TextAdapter, + abstract::LinkAdapter, abstract::BookmarkAdapter, abstract::ListAdapter, + abstract::ListItemAdapter, abstract::TableAdapter, + abstract::TableColumnAdapter, abstract::TableRowAdapter, + abstract::TableCellAdapter, abstract::FrameAdapter, abstract::ImageAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry) - : m_document(&document), m_registry(®istry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } + : AdapterBase(registry), m_document(&document) {} - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } [[nodiscard]] bool element_is_editable( [[maybe_unused]] const ElementIdentifier element_id) const override { return true; } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const LinkAdapter * - link_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::link ? this : nullptr; - } - [[nodiscard]] const BookmarkAdapter * - bookmark_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::bookmark ? this : nullptr; - } - [[nodiscard]] const ListAdapter * - list_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list ? this : nullptr; - } - - [[nodiscard]] const ListItemAdapter * - list_item_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list_item ? this : nullptr; - } - [[nodiscard]] const TableAdapter * - table_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table ? this : nullptr; - } - [[nodiscard]] const TableColumnAdapter * - table_column_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_column ? this - : nullptr; - } - [[nodiscard]] const TableRowAdapter * - table_row_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_row ? this : nullptr; - } - [[nodiscard]] const TableCellAdapter * - table_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_cell ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const ImageAdapter * - image_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::image ? this : nullptr; - } [[nodiscard]] PageLayout text_root_page_layout( [[maybe_unused]] const ElementIdentifier element_id) const override { @@ -611,7 +498,6 @@ class ElementAdapter final : public abstract::ElementAdapter, private: const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; [[nodiscard]] pugi::xml_node get_node(const ElementIdentifier element_id) const { diff --git a/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp b/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp index 1b8870bc1..960f6325e 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.cpp @@ -1,27 +1,11 @@ #include -#include - namespace odr::internal::ooxml::text { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_tables.clear(); - m_texts.clear(); - m_list_types.clear(); - m_list_markers.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type, const pugi::xml_node node) { - Element &element = m_elements.emplace_back(); - const ElementIdentifier element_id = m_elements.size(); - element.type = type; + const auto &[element_id, element] = create_element_(type); element.node = node; return {element_id, element}; } @@ -30,8 +14,8 @@ std::tuple ElementRegistry::create_table_element(const pugi::xml_node node) { const auto &[element_id, element] = create_element(ElementType::table, node); - auto [it, success] = m_tables.emplace(element_id, Table{}); - return {element_id, element, it->second}; + Table &table = m_tables.emplace(element_id, Table{}); + return {element_id, element, table}; } std::tuplesecond}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) { - return m_tables.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) const { - return m_tables.at(id); -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_table_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_tables.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + Text &text = m_texts.emplace(element_id, Text{last_node}); + return {element_id, element, text}; } void ElementRegistry::append_column(const ElementIdentifier table_id, const ElementIdentifier column_id) { - check_table_id(table_id); - check_element_id(column_id); - - const ElementIdentifier previous_sibling_id = - table_element_at(table_id).last_column_id; - - element_at(column_id).parent_id = table_id; - element_at(column_id).previous_sibling_id = previous_sibling_id; - - if (table_element_at(table_id).first_column_id == null_element_id) { - table_element_at(table_id).first_column_id = column_id; - } else { - element_at(previous_sibling_id).next_sibling_id = column_id; - } - table_element_at(table_id).last_column_id = column_id; + Table &table = table_element_at(table_id); + link_child(table_id, column_id, table.first_column_id, table.last_column_id); } void ElementRegistry::set_list_type(const ElementIdentifier id, const ListType type) { check_element_id(id); - m_list_types[id] = type; + m_list_types.emplace(id, type); } void ElementRegistry::set_list_marker(const ElementIdentifier id, ListMarker marker) { check_element_id(id); - m_list_markers[id] = std::move(marker); + m_list_markers.emplace(id, std::move(marker)); } -[[nodiscard]] ListType -ElementRegistry::list_type(const ElementIdentifier id) const { - const auto it = m_list_types.find(id); - return it != std::end(m_list_types) ? it->second : ListType::unordered; +ListType ElementRegistry::list_type(const ElementIdentifier id) const { + const ListType *type = m_list_types.find(id); + return type != nullptr ? *type : ListType::unordered; } -[[nodiscard]] const ListMarker & +const ListMarker & ElementRegistry::list_marker(const ElementIdentifier id) const { static const ListMarker none; - const auto it = m_list_markers.find(id); - return it != std::end(m_list_markers) ? it->second : none; + const ListMarker *marker = m_list_markers.find(id); + return marker != nullptr ? *marker : none; } } // 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 index e3c9eb9e9..822da5903 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp @@ -3,28 +3,24 @@ #include #include +#include #include -#include -#include +#include #include #include namespace odr::internal::ooxml::text { -class ElementRegistry final { -public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - pugi::xml_node node; - }; +struct RegistryElement final : ElementNode { + pugi::xml_node node; +}; +class ElementRegistry final + : public internal::ElementRegistry> { +public: struct Table final { ElementIdentifier first_column_id{null_element_id}; ElementIdentifier last_column_id{null_element_id}; @@ -34,10 +30,6 @@ class ElementRegistry final { pugi::xml_node last; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type, pugi::xml_node node); std::tuple @@ -45,15 +37,21 @@ class ElementRegistry final { std::tuple create_text_element(pugi::xml_node first_node, pugi::xml_node last_node); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Table &table_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Table &table_element_at(const ElementIdentifier id) { + return m_tables.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Table & + table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); + } - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); void append_column(ElementIdentifier table_id, ElementIdentifier column_id); void set_list_type(ElementIdentifier id, ListType type); @@ -63,15 +61,10 @@ class ElementRegistry final { [[nodiscard]] const ListMarker &list_marker(ElementIdentifier id) const; private: - std::vector m_elements; - std::unordered_map m_tables; - std::unordered_map m_texts; - std::unordered_map m_list_types; - std::unordered_map m_list_markers; - - void check_element_id(ElementIdentifier id) const; - void check_table_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; + SideTable
m_tables; + SideTable m_texts; + SideTable m_list_types; + SideTable m_list_markers; }; } // namespace odr::internal::ooxml::text From 6ac7626e2079564985fa5435006f053377cc1a5f Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 6 Sep 2026 09:45:56 +0200 Subject: [PATCH 2/3] refactor(document): port the last seven engines onto the shared registry and adapter rtf, markdown, iwork, the three oldms formats and csv follow odf and ooxml onto `internal::ElementRegistry` and `internal::RegistryElementAdapter`; csv, which packs its ids rather than keeping a registry, takes the hook base alone and keeps its own navigation. The element store is now a `std::deque` for every engine, which is what makes handing a parser an `Element &` safe. The registries lose the `clear()` nothing ever called and the per-payload `check_*_id`, and the three ooxml `append_child` regain the "child already has a parent" guard the others kept. The side tables reach their const and non-const accessors through one static that deduces the constness from its argument, rather than a `const_cast` back from the const overload. `AGENTS.md`, `odf/AGENTS.md`, `ooxml/AGENTS.md` and `rtf/AGENTS.md` taught the copy; they now point at the shared shape. No behaviour change: the reference output is byte-identical. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VEsRyBu8o4TGJGn3thNEDc --- AGENTS.md | 47 ++-- src/odr/internal/common/element_registry.hpp | 83 +++--- src/odr/internal/csv/csv_document.cpp | 45 +--- src/odr/internal/iwork/iwork_document.cpp | 125 +-------- .../internal/iwork/iwork_element_registry.cpp | 249 ++---------------- .../internal/iwork/iwork_element_registry.hpp | 100 +++---- .../internal/markdown/markdown_document.cpp | 127 +-------- .../markdown/markdown_element_registry.cpp | 170 ++---------- .../markdown/markdown_element_registry.hpp | 80 +++--- src/odr/internal/odf/AGENTS.md | 33 ++- .../oldms/presentation/ppt_document.cpp | 95 +------ .../presentation/ppt_element_registry.cpp | 147 +---------- .../presentation/ppt_element_registry.hpp | 65 ++--- .../oldms/spreadsheet/xls_document.cpp | 83 +----- .../spreadsheet/xls_element_registry.cpp | 141 +--------- .../spreadsheet/xls_element_registry.hpp | 61 ++--- src/odr/internal/oldms/text/doc_document.cpp | 88 +------ .../oldms/text/doc_element_registry.cpp | 95 +------ .../oldms/text/doc_element_registry.hpp | 42 +-- src/odr/internal/ooxml/AGENTS.md | 11 +- .../ooxml_presentation_element_registry.hpp | 4 +- .../ooxml_spreadsheet_element_registry.hpp | 4 +- .../text/ooxml_text_element_registry.hpp | 4 +- src/odr/internal/rtf/AGENTS.md | 2 +- src/odr/internal/rtf/rtf_document.cpp | 83 +----- src/odr/internal/rtf/rtf_element_registry.cpp | 84 +----- src/odr/internal/rtf/rtf_element_registry.hpp | 40 +-- 27 files changed, 423 insertions(+), 1685 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index de871673b..12c0b49ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,20 +35,34 @@ bytes ─▶ magic/open_strategy ─▶ DecodedFile ─▶ Document ─▶ Eleme ### The element-adapter pattern (every engine follows it) -- An **`ElementRegistry`**: a flat `std::vector` (id = index + 1); each - `Element` holds `parent`/`first_child`/`last_child`/`prev`/`next` ids and a - `type`, plus side maps for per-type payloads. Builders: `create_element` / - `create_*_element` / `append_child`. Minimal example: - `oldms/presentation/ppt_element_registry.*`. +**The machinery is shared — do not write it again.** `internal/common/element_registry.hpp` +and `internal/common/element_adapter.hpp` hold it; an engine writes only what +its own format has. Compact example: `rtf/rtf_element_registry.*` + +`rtf/rtf_document.cpp`. + +- An **`ElementRegistry`**: `internal::ElementRegistry` is the flat + store (id = index + 1, a `std::deque` so `create_element_` can hand back a + reference), with `element_at`, `append_child`, `link_child` and + `check_element_id`. The engine derives `struct RegistryElement final : + ElementNode` — adding a field only if it has one, as odf/ooxml add the + `pugi::xml_node` — and declares its per-type payloads as `SideTable` + (hashed) or `SortedSideTable` (binary search, for payloads written in + id order). Both carry their own bounds check, so an accessor is + `return m_texts.at(id);` and nothing else. Only the `create_*_element` and + the secondary-chain `append_*` are per engine. - An **`ElementIdentifier` is opaque, and 64 bits wide**: registry engines use it as index + 1, `csv` packs a kind, a row and a column into its bits. An engine - wanting a narrower id narrows it inside its own store (`odf`'s - `ElementRegistry::StoredId`) and widens at the boundary. -- An **`ElementAdapter`**: one class implementing `abstract::ElementAdapter` (tree - navigation by id) and, via multiple inheritance, the per-type adapters it - supports (`SlideAdapter`, `ParagraphAdapter`, …). Each `*_adapter(id)` returns - `this` when the element is that type, else `nullptr`. Compact example: - `oldms/presentation/ppt_document.cpp`. + wanting a narrower id passes it as `internal::ElementRegistry`'s `Id` + (`odf::StoredId`) and widens at the boundary. +- An **`ElementAdapter`**: `internal::RegistryElementAdapter` + implements the tree navigation over the registry, and its base + `internal::ElementAdapter` inherits the per-type adapters named in + the pack and answers **every** `*_adapter(id)` hook from them — an engine + writes no hook of its own, it only lists the adapters. `element_is_unique` and + `element_is_self_locatable` default to true and `element_is_editable` to + false; override only where that is wrong (odf, ooxml text). An engine with no + registry (`csv`) derives from `internal::ElementAdapter` and writes its own + navigation. `ElementType` is the shared enum in `src/odr/document_element.hpp`. @@ -64,7 +78,7 @@ producer's layout recorded — odf's `text:soft-page-break` — are not parsed. |------|------| | `src/odr/*.hpp` | **Public API**: `file`, `document`, `document_element`, `html`, `style`, `quantity` (`Measure`), `odr`. | | `src/odr/internal/abstract/` | Core interfaces: `File`/`DecodedFile`, `Document` + `ElementAdapter`, `Filesystem`, `Archive`, `HtmlService`. | -| `src/odr/internal/common/` | Reusable impls: `Path`/`AbsPath`, base `Document`, filesystem, `style`, table cursor/range, temp files. | +| `src/odr/internal/common/` | Reusable impls: `Path`/`AbsPath`, base `Document`, the shared `ElementRegistry` + `ElementAdapter`, filesystem, `style`, table cursor/range, temp files. | | `src/odr/internal/util/` | Helpers: `byte_stream_util`, `string_util`, `stream_util`, `document_util`. | | `src/odr/internal/magic.*`, `open_strategy.*` | File-type detection + open/dispatch. | | `src/odr/internal/file_type_table.*` | **The** per-`FileType` table: extensions, MIME types, category, document type, `FileTypeCapabilities`. Every public lookup in `odr.hpp` is a thin forward into it — extend the table, not the lookups. | @@ -247,9 +261,10 @@ Dispatch `release.yml` against main, publish the draft that appears — alias is claimed twice, or if the declared capabilities exceed what the engines actually do. 2. For documents: subclass `internal::Document`; in its constructor build an - `ElementRegistry` and an `ElementAdapter` (pattern above). It defaults to - read-only — override `is_editable`/`is_savable`/`save` only for an engine - that can write. + `ElementRegistry` (derived from `internal::ElementRegistry`) and an + `ElementAdapter` (derived from `internal::RegistryElementAdapter`) — pattern + above, and neither is written from scratch. It defaults to read-only — + override `is_editable`/`is_savable`/`save` only for an engine that can write. 3. Implement the per-element adapters you can populate; the **generic HTML renderer then works for free**. 4. Register the factory (e.g. `oldms_file.cpp::document()` switches on diff --git a/src/odr/internal/common/element_registry.hpp b/src/odr/internal/common/element_registry.hpp index 35acc1ca1..cc07aaada 100644 --- a/src/odr/internal/common/element_registry.hpp +++ b/src/odr/internal/common/element_registry.hpp @@ -24,8 +24,9 @@ template struct ElementNode { ElementType type{ElementType::none}; }; -/// A per-type payload keyed by element id, for payloads written out of parse -/// order. @ref SortedSideTable is cheaper where they are not. +/// A per-type payload keyed by element id, hashed, filled in any order. +/// Prefer @ref SortedSideTable for a payload written when its element is +/// created: it is a binary search over a narrow-keyed array instead. template class SideTable final { public: T &emplace(const ElementIdentifier id, T value) { @@ -33,32 +34,41 @@ template class SideTable final { } [[nodiscard]] T *find(const ElementIdentifier id) { - return const_cast(std::as_const(*this).find(id)); + return find_(m_entries, id); } - [[nodiscard]] const T *find(const ElementIdentifier id) const { - const auto it = m_entries.find(id); - return it != std::end(m_entries) ? &it->second : nullptr; + return find_(m_entries, id); } - [[nodiscard]] T &at(const ElementIdentifier id) { - return const_cast(std::as_const(*this).at(id)); + [[nodiscard]] T &at(const ElementIdentifier id) { return at_(*this, id); } + [[nodiscard]] const T &at(const ElementIdentifier id) const { + return at_(*this, id); } - [[nodiscard]] const T &at(const ElementIdentifier id) const { - const T *entry = find(id); +private: + std::unordered_map m_entries; + + /// One body for both constnesses: the argument carries the const and the + /// deduced return takes it on. + template + static auto *find_(Entries &entries, const ElementIdentifier id) { + const auto it = entries.find(id); + return it != std::end(entries) ? &it->second : nullptr; + } + + template + static auto &at_(Self &self, const ElementIdentifier id) { + auto *entry = self.find(id); if (entry == nullptr) { throw std::out_of_range("SideTable::at: identifier not found"); } return *entry; } - -private: - std::unordered_map m_entries; }; -/// A per-type payload keyed by element id and written in id order, so a lookup -/// is a binary search. A deque: `emplace` hands out a reference. +/// A per-type payload appended as its elements are created, so the ids only +/// grow and a lookup is a binary search. `emplace` refuses one out of order, +/// and hands out a reference — hence a deque. template class SortedSideTable final { public: @@ -71,24 +81,15 @@ class SortedSideTable final { } [[nodiscard]] T *find(const ElementIdentifier id) { - return const_cast(std::as_const(*this).find(id)); + return find_(m_entries, id); } - [[nodiscard]] const T *find(const ElementIdentifier id) const { - const auto it = std::ranges::lower_bound(m_entries, id, {}, &Entry::first); - return it != std::end(m_entries) && it->first == id ? &it->second : nullptr; - } - - [[nodiscard]] T &at(const ElementIdentifier id) { - return const_cast(std::as_const(*this).at(id)); + return find_(m_entries, id); } + [[nodiscard]] T &at(const ElementIdentifier id) { return at_(*this, id); } [[nodiscard]] const T &at(const ElementIdentifier id) const { - const T *entry = find(id); - if (entry == nullptr) { - throw std::out_of_range("SortedSideTable::at: identifier not found"); - } - return *entry; + return at_(*this, id); } [[nodiscard]] auto begin() const noexcept { return m_entries.begin(); } @@ -100,15 +101,31 @@ class SortedSideTable final { using Entry = std::pair; std::deque m_entries; + + /// One body for both constnesses: the argument carries the const and the + /// deduced return takes it on. + template + static auto *find_(Entries &entries, const ElementIdentifier id) { + const auto it = std::ranges::lower_bound(entries, id, {}, &Entry::first); + return it != std::end(entries) && it->first == id ? &it->second : nullptr; + } + + template + static auto &at_(Self &self, const ElementIdentifier id) { + auto *entry = self.find(id); + if (entry == nullptr) { + throw std::out_of_range("SortedSideTable::at: identifier not found"); + } + return *entry; + } }; /// The flat store an engine builds its element tree in: an id is the index /// plus one, and @p ElementT derives from @ref ElementNode for the links. /// -/// A deque by default, because `create_element_` hands back a reference the -/// parser holds on to. -template > +/// A deque, not a vector: `create_element_` hands back a reference the parser +/// holds on to, and a vector both invalidates it and peaks holding two copies. +template class ElementRegistry { public: using Element = ElementT; @@ -179,7 +196,7 @@ class ElementRegistry { } } - Container m_elements; + std::deque m_elements; }; } // namespace odr::internal diff --git a/src/odr/internal/csv/csv_document.cpp b/src/odr/internal/csv/csv_document.cpp index 1cc21e042..3fb65073d 100644 --- a/src/odr/internal/csv/csv_document.cpp +++ b/src/odr/internal/csv/csv_document.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -54,10 +55,11 @@ std::uint32_t column_of(const ElementIdentifier element_id) { return static_cast(element_id & column_mask); } -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::SheetAdapter, - public abstract::SheetCellAdapter, - public abstract::TextAdapter { +using AdapterBase = + internal::ElementAdapter; + +class ElementAdapter final : public AdapterBase { public: explicit ElementAdapter(const CsvDocument &document) : m_document{&document} {} @@ -119,41 +121,6 @@ class ElementAdapter final : public abstract::ElementAdapter, return null_element_id; } - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return false; - } - [[nodiscard]] DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const SheetAdapter * - sheet_adapter(const ElementIdentifier element_id) const override { - return kind_of(element_id) == Kind::sheet ? this : nullptr; - } - [[nodiscard]] const SheetCellAdapter * - sheet_cell_adapter(const ElementIdentifier element_id) const override { - return kind_of(element_id) == Kind::cell ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return kind_of(element_id) == Kind::text ? this : nullptr; - } - // SheetAdapter [[nodiscard]] std::string sheet_name( diff --git a/src/odr/internal/iwork/iwork_document.cpp b/src/odr/internal/iwork/iwork_document.cpp index 502226801..b4e4480ae 100644 --- a/src/odr/internal/iwork/iwork_document.cpp +++ b/src/odr/internal/iwork/iwork_document.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -55,119 +56,17 @@ const ElementRegistry &Document::element_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::TextRootAdapter, - public abstract::SlideAdapter, - public abstract::SheetAdapter, - public abstract::SheetCellAdapter, - public abstract::TableAdapter, - public abstract::TableColumnAdapter, - public abstract::TableRowAdapter, - public abstract::TableCellAdapter, - public abstract::FrameAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::TextAdapter { -public: - explicit ElementAdapter(ElementRegistry ®istry) : m_registry(®istry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::TextRootAdapter, abstract::SlideAdapter, + abstract::SheetAdapter, abstract::SheetCellAdapter, abstract::TableAdapter, + abstract::TableColumnAdapter, abstract::TableRowAdapter, + abstract::TableCellAdapter, abstract::FrameAdapter, + abstract::LineBreakAdapter, abstract::ParagraphAdapter, + abstract::TextAdapter>; - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return false; - } - [[nodiscard]] DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const SlideAdapter * - slide_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::slide ? this : nullptr; - } - [[nodiscard]] const SheetAdapter * - sheet_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet ? this : nullptr; - } - [[nodiscard]] const SheetCellAdapter * - sheet_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet_cell ? this : nullptr; - } - [[nodiscard]] const TableAdapter * - table_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table ? this : nullptr; - } - [[nodiscard]] const TableColumnAdapter * - table_column_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_column ? this - : nullptr; - } - [[nodiscard]] const TableRowAdapter * - table_row_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_row ? this : nullptr; - } - [[nodiscard]] const TableCellAdapter * - table_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_cell ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } +class ElementAdapter final : public AdapterBase { +public: + explicit ElementAdapter(ElementRegistry ®istry) : AdapterBase(registry) {} // The page geometry sits in the document archive and the styles in // `Index/DocumentStylesheet.iwa`; neither is read yet. @@ -416,8 +315,6 @@ class ElementAdapter final : public abstract::ElementAdapter, } return points(*value); } - - ElementRegistry *m_registry{nullptr}; }; std::unique_ptr diff --git a/src/odr/internal/iwork/iwork_element_registry.cpp b/src/odr/internal/iwork/iwork_element_registry.cpp index d188b533c..a18e1e139 100644 --- a/src/odr/internal/iwork/iwork_element_registry.cpp +++ b/src/odr/internal/iwork/iwork_element_registry.cpp @@ -1,280 +1,85 @@ #include -#include -#include - namespace odr::internal::iwork { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); - m_frames.clear(); - m_slides.clear(); - m_tables.clear(); - m_sheets.clear(); - m_cells.clear(); -} - ElementIdentifier ElementRegistry::Sheet::cell(const std::uint32_t column, const std::uint32_t row) const { const auto it = cells.find({row, column}); return it == cells.end() ? null_element_id : it->second; } -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - auto [it, success] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } std::tuple ElementRegistry::create_frame_element() { - const auto &[element_id, element] = create_element(ElementType::frame); - auto [it, success] = m_frames.emplace(element_id, Frame{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::frame); + Frame &frame = m_frames.emplace(element_id, Frame{}); + return {element_id, element, frame}; } std::tuple ElementRegistry::create_slide_element() { - const auto &[element_id, element] = create_element(ElementType::slide); - auto [it, success] = m_slides.emplace(element_id, Slide{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::slide); + Slide &slide = m_slides.emplace(element_id, Slide{}); + return {element_id, element, slide}; } std::tuple ElementRegistry::create_table_element() { - const auto &[element_id, element] = create_element(ElementType::table); - auto [it, success] = m_tables.emplace(element_id, Table{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::table); + Table &table = m_tables.emplace(element_id, Table{}); + return {element_id, element, table}; } std::tuple ElementRegistry::create_sheet_element() { - const auto &[element_id, element] = create_element(ElementType::sheet); - auto [it, success] = m_sheets.emplace(element_id, Sheet{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::sheet); + Sheet &sheet = m_sheets.emplace(element_id, Sheet{}); + return {element_id, element, sheet}; } std::tuple ElementRegistry::create_cell_element(const ElementType type) { - const auto &[element_id, element] = create_element(type); - auto [it, success] = m_cells.emplace(element_id, Cell{}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -ElementRegistry::Frame & -ElementRegistry::frame_element_at(const ElementIdentifier id) { - check_frame_id(id); - return m_frames.at(id); -} - -ElementRegistry::Slide & -ElementRegistry::slide_element_at(const ElementIdentifier id) { - check_slide_id(id); - return m_slides.at(id); -} - -ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) { - check_table_id(id); - return m_tables.at(id); -} - -ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) { - check_sheet_id(id); - return m_sheets.at(id); -} - -ElementRegistry::Cell & -ElementRegistry::cell_element_at(const ElementIdentifier id) { - check_cell_id(id); - return m_cells.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Frame & -ElementRegistry::frame_element_at(const ElementIdentifier id) const { - check_frame_id(id); - return m_frames.at(id); -} - -const ElementRegistry::Slide & -ElementRegistry::slide_element_at(const ElementIdentifier id) const { - check_slide_id(id); - return m_slides.at(id); -} - -const ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) const { - check_table_id(id); - return m_tables.at(id); -} - -const ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) const { - check_sheet_id(id); - return m_sheets.at(id); -} - -const ElementRegistry::Cell & -ElementRegistry::cell_element_at(const ElementIdentifier id) const { - check_cell_id(id); - return m_cells.at(id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_child: child already has a parent"); - } - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + const auto &[element_id, element] = create_element_(type); + Cell &cell = m_cells.emplace(element_id, Cell{}); + return {element_id, element, cell}; } void ElementRegistry::append_table_column(const ElementIdentifier table_id, const ElementIdentifier column_id) { - check_table_id(table_id); - check_element_id(column_id); - if (element_at(column_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_table_column: column already has a parent"); - } - Table &table = table_element_at(table_id); - const ElementIdentifier previous_id = table.last_column_id; - - element_at(column_id).parent_id = table_id; - element_at(column_id).previous_sibling_id = previous_id; - if (previous_id == null_element_id) { - table.first_column_id = column_id; - } else { - element_at(previous_id).next_sibling_id = column_id; - } - table.last_column_id = column_id; + link_child(table_id, column_id, table.first_column_id, table.last_column_id); } void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, const ElementIdentifier cell_id) { - check_sheet_id(sheet_id); - check_cell_id(cell_id); - if (element_at(cell_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_sheet_cell: cell already has a parent"); - } - + Sheet &sheet = sheet_element_at(sheet_id); const Cell &cell = cell_element_at(cell_id); - element_at(cell_id).parent_id = sheet_id; - sheet_element_at(sheet_id).cells.emplace(std::pair(cell.row, cell.column), - cell_id); -} -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range("ElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "ElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_frame_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_frames.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_slide_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_slides.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_table_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_tables.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_sheets.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); + Element &cell_element = element_at(cell_id); + if (cell_element.parent_id != null_element_id) { + throw std::invalid_argument( + "ElementRegistry::append_sheet_cell: cell already has a parent"); } -} -void ElementRegistry::check_cell_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_cells.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } + cell_element.parent_id = sheet_id; + sheet.cells.emplace(std::pair(cell.row, cell.column), cell_id); } } // namespace odr::internal::iwork diff --git a/src/odr/internal/iwork/iwork_element_registry.hpp b/src/odr/internal/iwork/iwork_element_registry.hpp index ab9a9c399..aed11335e 100644 --- a/src/odr/internal/iwork/iwork_element_registry.hpp +++ b/src/odr/internal/iwork/iwork_element_registry.hpp @@ -3,19 +3,19 @@ #include #include -#include +#include + #include #include #include #include #include -#include #include -#include namespace odr::internal::iwork { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: struct Size final { float width{}; @@ -31,15 +31,6 @@ class ElementRegistry final { std::optional height{}; }; - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; @@ -90,10 +81,6 @@ class ElementRegistry final { std::uint32_t row) const; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); std::tuple create_frame_element(); @@ -103,23 +90,47 @@ class ElementRegistry final { std::tuple create_cell_element(ElementType type); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Frame &frame_element_at(ElementIdentifier id); - [[nodiscard]] Slide &slide_element_at(ElementIdentifier id); - [[nodiscard]] Table &table_element_at(ElementIdentifier id); - [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); - [[nodiscard]] Cell &cell_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Frame &frame_element_at(ElementIdentifier id) const; - [[nodiscard]] const Slide &slide_element_at(ElementIdentifier id) const; - [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; - [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; - [[nodiscard]] const Cell &cell_element_at(ElementIdentifier id) const; - - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Frame &frame_element_at(const ElementIdentifier id) { + return m_frames.at(id); + } + [[nodiscard]] Slide &slide_element_at(const ElementIdentifier id) { + return m_slides.at(id); + } + [[nodiscard]] Table &table_element_at(const ElementIdentifier id) { + return m_tables.at(id); + } + [[nodiscard]] Sheet &sheet_element_at(const ElementIdentifier id) { + return m_sheets.at(id); + } + [[nodiscard]] Cell &cell_element_at(const ElementIdentifier id) { + return m_cells.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Frame & + frame_element_at(const ElementIdentifier id) const { + return m_frames.at(id); + } + [[nodiscard]] const Slide & + slide_element_at(const ElementIdentifier id) const { + return m_slides.at(id); + } + [[nodiscard]] const Table & + table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); + } + [[nodiscard]] const Sheet & + sheet_element_at(const ElementIdentifier id) const { + return m_sheets.at(id); + } + [[nodiscard]] const Cell &cell_element_at(const ElementIdentifier id) const { + return m_cells.at(id); + } /// Links @p column_id into @p table_id's column chain. Columns are not /// children: a table's child chain is its rows. @@ -130,21 +141,12 @@ class ElementRegistry final { void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); private: - std::vector m_elements; - std::unordered_map m_texts; - std::unordered_map m_frames; - std::unordered_map m_slides; - std::unordered_map m_tables; - std::unordered_map m_sheets; - std::unordered_map m_cells; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; - void check_frame_id(ElementIdentifier id) const; - void check_slide_id(ElementIdentifier id) const; - void check_table_id(ElementIdentifier id) const; - void check_sheet_id(ElementIdentifier id) const; - void check_cell_id(ElementIdentifier id) const; + SideTable m_texts; + SideTable m_frames; + SideTable m_slides; + SideTable
m_tables; + SideTable m_sheets; + SideTable m_cells; }; } // namespace odr::internal::iwork diff --git a/src/odr/internal/markdown/markdown_document.cpp b/src/odr/internal/markdown/markdown_document.cpp index d4daa6a93..3014765cc 100644 --- a/src/odr/internal/markdown/markdown_document.cpp +++ b/src/odr/internal/markdown/markdown_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -36,124 +37,19 @@ const StyleRegistry &Document::style_registry() const { 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::ListAdapter, - public abstract::ListItemAdapter, - public abstract::TableAdapter, - public abstract::TableColumnAdapter, - public abstract::TableRowAdapter, - public abstract::TableCellAdapter { +using AdapterBase = internal::RegistryElementAdapter< + const ElementRegistry, abstract::TextRootAdapter, + abstract::LineBreakAdapter, abstract::ParagraphAdapter, + abstract::SpanAdapter, abstract::TextAdapter, abstract::LinkAdapter, + abstract::ListAdapter, abstract::ListItemAdapter, abstract::TableAdapter, + abstract::TableColumnAdapter, abstract::TableRowAdapter, + abstract::TableCellAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const ElementRegistry ®istry, const StyleRegistry &style_registry) - : m_registry(®istry), m_style_registry(&style_registry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool - element_is_unique(const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool - element_is_self_locatable(const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool - element_is_editable(const ElementIdentifier element_id) const override { - (void)element_id; - return false; - } - [[nodiscard]] DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const LinkAdapter * - link_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::link ? this : nullptr; - } - [[nodiscard]] const ListAdapter * - list_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list ? this : nullptr; - } - [[nodiscard]] const ListItemAdapter * - list_item_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::list_item ? this : nullptr; - } - [[nodiscard]] const TableAdapter * - table_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table ? this : nullptr; - } - [[nodiscard]] const TableColumnAdapter * - table_column_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_column ? this - : nullptr; - } - [[nodiscard]] const TableRowAdapter * - table_row_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_row ? this : nullptr; - } - [[nodiscard]] const TableCellAdapter * - table_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::table_cell ? this : nullptr; - } + : AdapterBase(registry), m_style_registry(&style_registry) {} /// Markdown is flow content: it has no page, and the viewport is the width. [[nodiscard]] PageLayout @@ -286,7 +182,6 @@ class ElementAdapter final : public abstract::ElementAdapter, } private: - const ElementRegistry *m_registry{nullptr}; const StyleRegistry *m_style_registry{nullptr}; [[nodiscard]] TextStyle diff --git a/src/odr/internal/markdown/markdown_element_registry.cpp b/src/odr/internal/markdown/markdown_element_registry.cpp index f5dc63496..c0fe14957 100644 --- a/src/odr/internal/markdown/markdown_element_registry.cpp +++ b/src/odr/internal/markdown/markdown_element_registry.cpp @@ -1,208 +1,88 @@ #include -#include - namespace odr::internal::markdown { -namespace { - -/// Looks a payload up, turning a missing entry into an out-of-range error -/// rather than the map's default-constructed one. -template auto &payload_at(Map &map, const ElementIdentifier id) { - const auto it = map.find(id); - if (it == map.end()) { - throw std::out_of_range( - "markdown::ElementRegistry: element has no payload of that kind"); - } - return it->second; -} - -} // namespace - -std::size_t ElementRegistry::size() const noexcept { return m_elements.size(); } - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - const ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - const auto [it, inserted] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } std::tuple ElementRegistry::create_link_element() { - const auto &[element_id, element] = create_element(ElementType::link); - const auto [it, inserted] = m_links.emplace(element_id, Link{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::link); + Link &link = m_links.emplace(element_id, Link{}); + return {element_id, element, link}; } std::tuple ElementRegistry::create_list_element() { - const auto &[element_id, element] = create_element(ElementType::list); - const auto [it, inserted] = m_lists.emplace(element_id, List{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::list); + List &list = m_lists.emplace(element_id, List{}); + return {element_id, element, list}; } std::tuple ElementRegistry::create_list_item_element() { - const auto &[element_id, element] = create_element(ElementType::list_item); - const auto [it, inserted] = m_list_items.emplace(element_id, ListItem{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::list_item); + ListItem &list_item = m_list_items.emplace(element_id, ListItem{}); + return {element_id, element, list_item}; } std::tuple ElementRegistry::create_table_element() { - const auto &[element_id, element] = create_element(ElementType::table); - const auto [it, inserted] = m_tables.emplace(element_id, Table{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::table); + Table &table = m_tables.emplace(element_id, Table{}); + return {element_id, element, table}; } std::tuple ElementRegistry::create_table_cell_element() { - const auto &[element_id, element] = create_element(ElementType::table_cell); - const auto [it, inserted] = m_table_cells.emplace(element_id, TableCell{}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - return payload_at(m_texts, id); -} - -ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) { - return payload_at(m_tables, id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - return payload_at(m_texts, id); -} - -const ElementRegistry::Link & -ElementRegistry::link_element_at(const ElementIdentifier id) const { - return payload_at(m_links, id); -} - -const ElementRegistry::List & -ElementRegistry::list_element_at(const ElementIdentifier id) const { - return payload_at(m_lists, id); -} - -const ElementRegistry::ListItem & -ElementRegistry::list_item_element_at(const ElementIdentifier id) const { - return payload_at(m_list_items, id); -} - -const ElementRegistry::Table & -ElementRegistry::table_element_at(const ElementIdentifier id) const { - return payload_at(m_tables, id); -} - -const ElementRegistry::TableCell & -ElementRegistry::table_cell_element_at(const ElementIdentifier id) const { - return payload_at(m_table_cells, id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "markdown::ElementRegistry::append_child: child already has a parent"); - } - - Element &parent = element_at(parent_id); - link_child(parent_id, child_id, parent.first_child_id, parent.last_child_id); + const auto &[element_id, element] = create_element_(ElementType::table_cell); + TableCell &table_cell = m_table_cells.emplace(element_id, TableCell{}); + return {element_id, element, table_cell}; } void ElementRegistry::append_column(const ElementIdentifier table_id, const ElementIdentifier column_id) { - check_element_id(column_id); - if (element_at(column_id).parent_id != null_element_id) { - throw std::invalid_argument( - "markdown::ElementRegistry::append_column: child already has a parent"); - } - Table &table = table_element_at(table_id); link_child(table_id, column_id, table.first_column_id, table.last_column_id); } -void ElementRegistry::link_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id, - ElementIdentifier &first_id, - ElementIdentifier &last_id) { - Element &child = element_at(child_id); - child.parent_id = parent_id; - child.previous_sibling_id = last_id; - - if (first_id == null_element_id) { - first_id = child_id; - } else { - element_at(last_id).next_sibling_id = child_id; - } - last_id = child_id; -} - void ElementRegistry::set_element_text_style_index(const ElementIdentifier id, const std::uint32_t index) { check_element_id(id); - m_text_style_indices[id] = index; + m_text_style_indices.emplace(id, index); } std::uint32_t ElementRegistry::element_text_style_index(const ElementIdentifier id) const { - const auto it = m_text_style_indices.find(id); - return it != m_text_style_indices.end() ? it->second : 0; + const std::uint32_t *index = m_text_style_indices.find(id); + return index != nullptr ? *index : 0; } void ElementRegistry::set_element_paragraph_style_index( const ElementIdentifier id, const std::uint32_t index) { check_element_id(id); - m_paragraph_style_indices[id] = index; + m_paragraph_style_indices.emplace(id, index); } std::uint32_t ElementRegistry::element_paragraph_style_index( const ElementIdentifier id) const { - const auto it = m_paragraph_style_indices.find(id); - return it != m_paragraph_style_indices.end() ? it->second : 0; -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range( - "markdown::ElementRegistry::check_element_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "markdown::ElementRegistry::check_element_id: identifier out of range"); - } + const std::uint32_t *index = m_paragraph_style_indices.find(id); + return index != nullptr ? *index : 0; } } // namespace odr::internal::markdown diff --git a/src/odr/internal/markdown/markdown_element_registry.hpp b/src/odr/internal/markdown/markdown_element_registry.hpp index 05ec095b6..3a97f1bc4 100644 --- a/src/odr/internal/markdown/markdown_element_registry.hpp +++ b/src/odr/internal/markdown/markdown_element_registry.hpp @@ -5,26 +5,18 @@ #include #include +#include + #include #include #include #include -#include -#include namespace odr::internal::markdown { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; @@ -55,8 +47,6 @@ class ElementRegistry final { std::optional horizontal_align; }; - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); std::tuple create_link_element(); @@ -67,21 +57,35 @@ class ElementRegistry final { std::tuple create_table_cell_element(); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Table &table_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Link &link_element_at(ElementIdentifier id) const; - [[nodiscard]] const List &list_element_at(ElementIdentifier id) const; + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Table &table_element_at(const ElementIdentifier id) { + return m_tables.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Link &link_element_at(const ElementIdentifier id) const { + return m_links.at(id); + } + [[nodiscard]] const List &list_element_at(const ElementIdentifier id) const { + return m_lists.at(id); + } [[nodiscard]] const ListItem & - list_item_element_at(ElementIdentifier id) const; - [[nodiscard]] const Table &table_element_at(ElementIdentifier id) const; + list_item_element_at(const ElementIdentifier id) const { + return m_list_items.at(id); + } + [[nodiscard]] const Table & + table_element_at(const ElementIdentifier id) const { + return m_tables.at(id); + } [[nodiscard]] const TableCell & - table_cell_element_at(ElementIdentifier id) const; + table_cell_element_at(const ElementIdentifier id) const { + return m_table_cells.at(id); + } - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); void append_column(ElementIdentifier table_id, ElementIdentifier column_id); /// Character style of an element, as an index into the document's @@ -97,22 +101,14 @@ class ElementRegistry final { element_paragraph_style_index(ElementIdentifier id) const; private: - std::vector m_elements; - std::unordered_map m_texts; - std::unordered_map m_links; - std::unordered_map m_lists; - std::unordered_map m_list_items; - std::unordered_map m_tables; - std::unordered_map m_table_cells; - std::unordered_map m_text_style_indices; - std::unordered_map - m_paragraph_style_indices; - - void check_element_id(ElementIdentifier id) const; - - /// Links @p child_id onto the chain @p first_id / @p last_id delimit. - void link_child(ElementIdentifier parent_id, ElementIdentifier child_id, - ElementIdentifier &first_id, ElementIdentifier &last_id); + SideTable m_texts; + SideTable m_links; + SideTable m_lists; + SideTable m_list_items; + SideTable
m_tables; + SideTable m_table_cells; + SideTable m_text_style_indices; + SideTable m_paragraph_style_indices; }; } // namespace odr::internal::markdown diff --git a/src/odr/internal/odf/AGENTS.md b/src/odr/internal/odf/AGENTS.md index 2078b8e6f..de0bb5678 100644 --- a/src/odr/internal/odf/AGENTS.md +++ b/src/odr/internal/odf/AGENTS.md @@ -26,11 +26,13 @@ goes back to the node. **This is why ODF alone can edit and save**: a text edit is a local DOM splice, and `save` re-serialises the mutated tree. The other engines throw away the source, so their models are read-only. -Everything else follows the shared registry/adapter pattern: a flat element -store, id = index + 1, `null_element_id == 0`, parent/child/ -sibling ids, per-subtype payload tables (`m_texts`, `m_tables`, `m_sheets`, -`m_sheet_cells`). One mega `ElementAdapter` multiply-inherits every abstract -per-type adapter and dispatches by returning `this`/`nullptr` on `element_type`. +Everything else comes from the shared machinery in `internal/common/`: +`internal::ElementRegistry` is the flat store (id = +index + 1, `null_element_id == 0`, parent/child/sibling ids), and this module +adds only the `pugi::xml_node` on the element and its payload tables +(`m_texts`, `m_tables`, `m_sheets`, `m_sheet_cells`). One mega `ElementAdapter` +derives from `internal::RegistryElementAdapter`, naming every abstract per-type +adapter in its pack; the `*_adapter(id)` dispatch is the base's. ## Design decisions @@ -64,18 +66,21 @@ vectors. The cells of every row live in one array per sheet, each row recording where its own run starts, so a sheet is two allocations rather than one per row. `register_cell` therefore has to follow its row's `register_row`. -The elements themselves are a `std::deque`: `create_element` hands back a -reference the parser holds on to, and a vector both invalidates it and peaks -holding two copies. `parse_sheet` counts the row and cell nodes first, so the +The elements themselves are a `std::deque` — that is now the shared +`internal::ElementRegistry`'s doing, for the reason odf needed it: +`create_element_` hands back a reference the parser holds on to, and a vector +both invalidates it and peaks holding two copies. +`parse_sheet` counts the row and cell nodes first, so the arrays are allocated once at the size they end at — a repeat collapses onto one entry, so the count is an upper bound, and one bounded by the dom. -**Ids are stored narrow, payloads in sorted arrays.** `StoredId` is 32 bits and -every boundary widens back to the public `ElementIdentifier`, which stays 64 — -`csv` packs coordinates into it. The payload tables are written in id order as -elements are created, so a binary search replaces a hash map; `m_list_types` and -`m_list_markers` stay hash maps, written when a list is resolved rather than -parsed. +**Ids are stored narrow, payloads in sorted arrays.** `StoredId` is 32 bits — +the `Id` this module gives `internal::ElementRegistry` — and every boundary +widens back to the public `ElementIdentifier`, which stays 64: `csv` packs +coordinates into it. The payload tables are written in id order as elements are +created, so `SortedSideTable`'s binary search replaces a hash map; +`m_list_types` and `m_list_markers` are plain `SideTable`s, written when a list +is resolved rather than parsed. **Styles resolve to a flattened `ResolvedStyle`, eagerly.** `StyleRegistry` first builds name→node indices from *both* files (automatic and named styles land diff --git a/src/odr/internal/oldms/presentation/ppt_document.cpp b/src/odr/internal/oldms/presentation/ppt_document.cpp index 42d8667f4..5e50c7f6a 100644 --- a/src/odr/internal/oldms/presentation/ppt_document.cpp +++ b/src/odr/internal/oldms/presentation/ppt_document.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -43,97 +44,18 @@ const StyleRegistry &Document::style_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::SlideAdapter, - public abstract::FrameAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::SpanAdapter, - public abstract::TextAdapter, - public abstract::ImageAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::SlideAdapter, abstract::FrameAdapter, + abstract::LineBreakAdapter, abstract::ParagraphAdapter, + abstract::SpanAdapter, abstract::TextAdapter, abstract::ImageAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry, const StyleRegistry &style_registry) - : m_document(&document), m_registry(®istry), + : AdapterBase(registry), m_document(&document), m_style_registry(&style_registry) {} - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - return false; - } - [[nodiscard]] DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const SlideAdapter * - slide_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::slide ? this : nullptr; - } - [[nodiscard]] const FrameAdapter * - frame_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::frame ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] const ImageAdapter * - image_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::image ? this : nullptr; - } - [[nodiscard]] PageLayout slide_page_layout( [[maybe_unused]] const ElementIdentifier element_id) const override { // The DocumentAtom's slide size, with the default 4:3 slide as fallback @@ -283,7 +205,6 @@ class ElementAdapter final : public abstract::ElementAdapter, [[maybe_unused]] const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; const StyleRegistry *m_style_registry{nullptr}; }; diff --git a/src/odr/internal/oldms/presentation/ppt_element_registry.cpp b/src/odr/internal/oldms/presentation/ppt_element_registry.cpp index eea1434b3..9e1d1b958 100644 --- a/src/odr/internal/oldms/presentation/ppt_element_registry.cpp +++ b/src/odr/internal/oldms/presentation/ppt_element_registry.cpp @@ -1,123 +1,34 @@ #include -#include - namespace odr::internal::oldms::presentation { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); - m_frames.clear(); - m_images.clear(); - m_style_indices.clear(); - m_slide_size.reset(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - auto [it, success] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } std::tuple ElementRegistry::create_frame_element() { - const auto &[element_id, element] = create_element(ElementType::frame); - auto [it, success] = m_frames.emplace(element_id, Frame{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::frame); + Frame &frame = m_frames.emplace(element_id, Frame{}); + return {element_id, element, frame}; } std::tuple ElementRegistry::create_image_element() { - const auto &[element_id, element] = create_element(ElementType::image); - auto [it, success] = m_images.emplace(element_id, Image{}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -ElementRegistry::Frame & -ElementRegistry::frame_element_at(const ElementIdentifier id) { - check_frame_id(id); - return m_frames.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Frame & -ElementRegistry::frame_element_at(const ElementIdentifier id) const { - check_frame_id(id); - return m_frames.at(id); -} - -ElementRegistry::Image & -ElementRegistry::image_element_at(const ElementIdentifier id) { - check_image_id(id); - return m_images.at(id); -} - -const ElementRegistry::Image & -ElementRegistry::image_element_at(const ElementIdentifier id) const { - check_image_id(id); - return m_images.at(id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_child: child already has a parent"); - } - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + const auto &[element_id, element] = create_element_(ElementType::image); + Image &image = m_images.emplace(element_id, Image{}); + return {element_id, element, image}; } void ElementRegistry::set_slide_size(const std::int32_t width, @@ -133,45 +44,13 @@ ElementRegistry::slide_size() const { void ElementRegistry::set_element_style_index(const ElementIdentifier id, const std::uint32_t index) { check_element_id(id); - m_style_indices[id] = index; + m_style_indices.emplace(id, index); } std::uint32_t ElementRegistry::element_style_index(const ElementIdentifier id) const { - const auto it = m_style_indices.find(id); - return it != m_style_indices.end() ? it->second : 0; -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range("ElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "ElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_frame_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_frames.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } -} - -void ElementRegistry::check_image_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_images.contains(id)) { - throw std::out_of_range( - "ElementRegistry::check_id: image identifier not found"); - } + const std::uint32_t *index = m_style_indices.find(id); + return index != nullptr ? *index : 0; } } // namespace odr::internal::oldms::presentation diff --git a/src/odr/internal/oldms/presentation/ppt_element_registry.hpp b/src/odr/internal/oldms/presentation/ppt_element_registry.hpp index 590b4c1f4..db4000267 100644 --- a/src/odr/internal/oldms/presentation/ppt_element_registry.hpp +++ b/src/odr/internal/oldms/presentation/ppt_element_registry.hpp @@ -3,29 +3,20 @@ #include #include +#include #include #include #include #include #include -#include #include -#include namespace odr::internal::oldms::presentation { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; @@ -40,26 +31,32 @@ class ElementRegistry final { std::string href; //< pseudo-path naming the BLIP (no real container path) }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); std::tuple create_frame_element(); std::tuple create_image_element(); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Frame &frame_element_at(ElementIdentifier id); - [[nodiscard]] Image &image_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Frame &frame_element_at(ElementIdentifier id) const; - [[nodiscard]] const Image &image_element_at(ElementIdentifier id) const; - - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Frame &frame_element_at(const ElementIdentifier id) { + return m_frames.at(id); + } + [[nodiscard]] Image &image_element_at(const ElementIdentifier id) { + return m_images.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Frame & + frame_element_at(const ElementIdentifier id) const { + return m_frames.at(id); + } + [[nodiscard]] const Image & + image_element_at(const ElementIdentifier id) const { + return m_images.at(id); + } /// Character style of a span or paragraph element, as an index into the /// document's `StyleRegistry` (0 is the default style). @@ -72,17 +69,11 @@ class ElementRegistry final { slide_size() const; private: - std::vector m_elements; - std::unordered_map m_texts; - std::unordered_map m_frames; - std::unordered_map m_images; - std::unordered_map m_style_indices; + SideTable m_texts; + SideTable m_frames; + SideTable m_images; + SideTable m_style_indices; std::optional> m_slide_size; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; - void check_frame_id(ElementIdentifier id) const; - void check_image_id(ElementIdentifier id) const; }; } // namespace odr::internal::oldms::presentation diff --git a/src/odr/internal/oldms/spreadsheet/xls_document.cpp b/src/odr/internal/oldms/spreadsheet/xls_document.cpp index a3df0d20d..7f01db121 100644 --- a/src/odr/internal/oldms/spreadsheet/xls_document.cpp +++ b/src/odr/internal/oldms/spreadsheet/xls_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -39,86 +40,17 @@ const StyleRegistry &Document::style_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::SheetAdapter, - public abstract::SheetCellAdapter, - public abstract::ParagraphAdapter, - public abstract::TextAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::SheetAdapter, abstract::SheetCellAdapter, + abstract::ParagraphAdapter, abstract::TextAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry, const StyleRegistry &style_registry) - : m_document(&document), m_registry(®istry), + : AdapterBase(registry), m_document(&document), m_style_registry(&style_registry) {} - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return false; - } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const SheetAdapter * - sheet_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet ? this : nullptr; - } - [[nodiscard]] const SheetCellAdapter * - sheet_cell_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::sheet_cell ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] std::string sheet_name(const ElementIdentifier element_id) const override { return m_registry->sheet_element_at(element_id).name; @@ -234,7 +166,6 @@ class ElementAdapter final : public abstract::ElementAdapter, // TODO remove maybe_unused [[maybe_unused]] const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; const StyleRegistry *m_style_registry{nullptr}; /// The font style of the sheet_cell ancestor (paragraph and text elements diff --git a/src/odr/internal/oldms/spreadsheet/xls_element_registry.cpp b/src/odr/internal/oldms/spreadsheet/xls_element_registry.cpp index 6c4a965d0..d5c66e6a4 100644 --- a/src/odr/internal/oldms/spreadsheet/xls_element_registry.cpp +++ b/src/odr/internal/oldms/spreadsheet/xls_element_registry.cpp @@ -1,7 +1,6 @@ #include #include -#include namespace odr::internal::oldms::spreadsheet { @@ -14,118 +13,34 @@ ElementIdentifier ElementRegistry::Sheet::cell(const std::uint32_t column, return null_element_id; } -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); - m_sheets.clear(); - m_sheet_cells.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - auto [it, success] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } std::tuple ElementRegistry::create_sheet_element() { - const auto &[element_id, element] = create_element(ElementType::sheet); - auto [it, success] = m_sheets.emplace(element_id, Sheet{}); - return {element_id, element, it->second}; + const auto &[element_id, element] = create_element_(ElementType::sheet); + Sheet &sheet = m_sheets.emplace(element_id, Sheet{}); + return {element_id, element, sheet}; } std::tuple ElementRegistry::create_sheet_cell_element(const TablePosition &position) { - const auto &[element_id, element] = create_element(ElementType::sheet_cell); - auto [it, success] = m_sheet_cells.emplace(element_id, SheetCell{position}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) { - check_sheet_id(id); - return m_sheets.at(id); -} - -ElementRegistry::SheetCell & -ElementRegistry::sheet_cell_element_at(const ElementIdentifier id) { - check_sheet_cell_id(id); - return m_sheet_cells.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Sheet & -ElementRegistry::sheet_element_at(const ElementIdentifier id) const { - check_sheet_id(id); - return m_sheets.at(id); -} - -const ElementRegistry::SheetCell & -ElementRegistry::sheet_cell_element_at(const ElementIdentifier id) const { - check_sheet_cell_id(id); - return m_sheet_cells.at(id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_child: child already has a parent"); - } - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + const auto &[element_id, element] = create_element_(ElementType::sheet_cell); + SheetCell &sheet_cell = + m_sheet_cells.emplace(element_id, SheetCell{.position = position}); + return {element_id, element, sheet_cell}; } void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, @@ -140,38 +55,4 @@ void ElementRegistry::append_sheet_cell(const ElementIdentifier sheet_id, std::max(sheet.content.columns, cell.position.column + 1); } -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range("ElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "ElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range( - "ElementRegistry::check_id: text identifier not found"); - } -} - -void ElementRegistry::check_sheet_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_sheets.contains(id)) { - throw std::out_of_range( - "ElementRegistry::check_id: sheet identifier not found"); - } -} - -void ElementRegistry::check_sheet_cell_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_sheet_cells.contains(id)) { - throw std::out_of_range( - "ElementRegistry::check_id: sheet cell identifier not found"); - } -} - } // namespace odr::internal::oldms::spreadsheet diff --git a/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp b/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp index 0f5db7a74..b4d477e4f 100644 --- a/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp +++ b/src/odr/internal/oldms/spreadsheet/xls_element_registry.hpp @@ -5,24 +5,17 @@ #include #include +#include + #include #include #include -#include namespace odr::internal::oldms::spreadsheet { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; @@ -48,43 +41,43 @@ class ElementRegistry final { std::uint16_t ixfe{0}; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); std::tuple create_sheet_element(); std::tuple create_sheet_cell_element(const TablePosition &position); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - [[nodiscard]] Sheet &sheet_element_at(ElementIdentifier id); - [[nodiscard]] SheetCell &sheet_cell_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - [[nodiscard]] const Sheet &sheet_element_at(ElementIdentifier id) const; + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] Sheet &sheet_element_at(const ElementIdentifier id) { + return m_sheets.at(id); + } + [[nodiscard]] SheetCell &sheet_cell_element_at(const ElementIdentifier id) { + return m_sheet_cells.at(id); + } + + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } + [[nodiscard]] const Sheet & + sheet_element_at(const ElementIdentifier id) const { + return m_sheets.at(id); + } [[nodiscard]] const SheetCell & - sheet_cell_element_at(ElementIdentifier id) const; + sheet_cell_element_at(const ElementIdentifier id) const { + return m_sheet_cells.at(id); + } - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); /// Registers a cell with its sheet: sets the cell's parent and adds it to /// the sheet's position lookup. Cells are not part of the sibling chain; /// they are addressed via `Sheet::cell` (like the ooxml/spreadsheet module). void append_sheet_cell(ElementIdentifier sheet_id, ElementIdentifier cell_id); private: - std::vector m_elements; - std::unordered_map m_texts; - std::unordered_map m_sheets; - std::unordered_map m_sheet_cells; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; - void check_sheet_id(ElementIdentifier id) const; - void check_sheet_cell_id(ElementIdentifier id) const; + SideTable m_texts; + SideTable m_sheets; + SideTable m_sheet_cells; }; } // namespace odr::internal::oldms::spreadsheet diff --git a/src/odr/internal/oldms/text/doc_document.cpp b/src/odr/internal/oldms/text/doc_document.cpp index bb57bcf06..5f320df1a 100644 --- a/src/odr/internal/oldms/text/doc_document.cpp +++ b/src/odr/internal/oldms/text/doc_document.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -38,91 +39,17 @@ const StyleRegistry &Document::style_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::TextRootAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::SpanAdapter, - public abstract::TextAdapter { +using AdapterBase = internal::RegistryElementAdapter< + ElementRegistry, abstract::TextRootAdapter, abstract::LineBreakAdapter, + abstract::ParagraphAdapter, abstract::SpanAdapter, abstract::TextAdapter>; + +class ElementAdapter final : public AdapterBase { public: ElementAdapter(const Document &document, ElementRegistry ®istry, const StyleRegistry &style_registry) - : m_document(&document), m_registry(®istry), + : AdapterBase(registry), m_document(&document), m_style_registry(&style_registry) {} - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool element_is_unique( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool element_is_self_locatable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool element_is_editable( - [[maybe_unused]] const ElementIdentifier element_id) const override { - (void)element_id; - return false; - } - [[nodiscard]] - DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const SpanAdapter * - span_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::span ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } - [[nodiscard]] PageLayout text_root_page_layout( [[maybe_unused]] const ElementIdentifier element_id) const override { (void)element_id; @@ -176,7 +103,6 @@ class ElementAdapter final : public abstract::ElementAdapter, // TODO remove maybe_unused [[maybe_unused]] const Document *m_document{nullptr}; - ElementRegistry *m_registry{nullptr}; const StyleRegistry *m_style_registry{nullptr}; /// The character style stored for a paragraph or span element. diff --git a/src/odr/internal/oldms/text/doc_element_registry.cpp b/src/odr/internal/oldms/text/doc_element_registry.cpp index fa1e46279..0572d3e1e 100644 --- a/src/odr/internal/oldms/text/doc_element_registry.cpp +++ b/src/odr/internal/oldms/text/doc_element_registry.cpp @@ -1,111 +1,30 @@ #include -#include - namespace odr::internal::oldms::text { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); - m_style_indices.clear(); -} - -[[nodiscard]] std::size_t ElementRegistry::size() const noexcept { - return m_elements.size(); -} - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - auto [it, success] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "DocumentElementRegistry::append_child: child already has a parent"); - } - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } void ElementRegistry::set_element_style_index(const ElementIdentifier id, const std::uint32_t index) { check_element_id(id); - m_style_indices[id] = index; + m_style_indices.emplace(id, index); } std::uint32_t ElementRegistry::element_style_index(const ElementIdentifier id) const { - const auto it = m_style_indices.find(id); - return it != m_style_indices.end() ? it->second : 0; -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range( - "DocumentElementRegistry::check_id: identifier not found"); - } + const std::uint32_t *index = m_style_indices.find(id); + return index != nullptr ? *index : 0; } } // namespace odr::internal::oldms::text diff --git a/src/odr/internal/oldms/text/doc_element_registry.hpp b/src/odr/internal/oldms/text/doc_element_registry.hpp index ad28267b4..d1e34ca20 100644 --- a/src/odr/internal/oldms/text/doc_element_registry.hpp +++ b/src/odr/internal/oldms/text/doc_element_registry.hpp @@ -3,42 +3,30 @@ #include #include +#include + #include #include -#include -#include +#include namespace odr::internal::oldms::text { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } /// Character style of a span or paragraph element, as an index into the /// document's `StyleRegistry` (0 is the default style). @@ -46,12 +34,8 @@ class ElementRegistry final { [[nodiscard]] std::uint32_t element_style_index(ElementIdentifier id) const; private: - std::vector m_elements; - std::unordered_map m_texts; - std::unordered_map m_style_indices; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; + SideTable m_texts; + SideTable m_style_indices; }; } // namespace odr::internal::oldms::text diff --git a/src/odr/internal/ooxml/AGENTS.md b/src/odr/internal/ooxml/AGENTS.md index c0d5e615e..fc4c26b1f 100644 --- a/src/odr/internal/ooxml/AGENTS.md +++ b/src/odr/internal/ooxml/AGENTS.md @@ -19,11 +19,12 @@ detection**; each is a self-contained module with its own `AGENTS.md`: ## Shared element model (same as ODF) Like [`odf/`](../odf/), every format keeps its parsed XML parts **resident** and -the `ElementRegistry` is a thin index over them: each `Element` holds a live -`pugi::xml_node` plus its tree ids (flat vector, id = index + 1, -`null_element_id == 0`, parent/child/sibling ids, per-subtype side maps). One -mega `ElementAdapter` per format multiply-inherits the abstract per-type adapters -and dispatches by returning `this`/`nullptr` on `element_type`. Parsing is a +the `ElementRegistry` is a thin index over them: its `RegistryElement` adds a +live `pugi::xml_node` to the shared `ElementNode`, and the store, the tree links +and the `SideTable` payloads come from `internal::ElementRegistry` (see the top +level [`AGENTS.md`](../../../../AGENTS.md)). One mega `ElementAdapter` per format +derives from `internal::RegistryElementAdapter`, naming the abstract per-type +adapters it answers for in the template pack; the hooks are the base's. Parsing is a static `unordered_map` dispatch table; unknown tags are skipped (children still visited). Text runs are coalesced into one `text` Element over a `[first, last]` node span. **Editing, where present, splices these live nodes;** diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp index baa32205c..43f0aea7e 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_element_registry.hpp @@ -6,7 +6,6 @@ #include #include -#include #include @@ -17,8 +16,7 @@ struct RegistryElement final : ElementNode { }; class ElementRegistry final - : public internal::ElementRegistry> { + : public internal::ElementRegistry { public: struct Table final { ElementIdentifier first_column_id{null_element_id}; diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp index e1913ec2a..1d04c4860 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_element_registry.hpp @@ -14,7 +14,6 @@ #include #include #include -#include #include @@ -25,8 +24,7 @@ struct RegistryElement final : ElementNode { }; class ElementRegistry final - : public internal::ElementRegistry> { + : public internal::ElementRegistry { public: struct ElementRelations final { const Relations *relations{nullptr}; diff --git a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp index 822da5903..3743699b2 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp +++ b/src/odr/internal/ooxml/text/ooxml_text_element_registry.hpp @@ -7,7 +7,6 @@ #include #include -#include #include @@ -18,8 +17,7 @@ struct RegistryElement final : ElementNode { }; class ElementRegistry final - : public internal::ElementRegistry> { + : public internal::ElementRegistry { public: struct Table final { ElementIdentifier first_column_id{null_element_id}; diff --git a/src/odr/internal/rtf/AGENTS.md b/src/odr/internal/rtf/AGENTS.md index df6c3fcb8..797c6de28 100644 --- a/src/odr/internal/rtf/AGENTS.md +++ b/src/odr/internal/rtf/AGENTS.md @@ -22,7 +22,7 @@ bytes ─▶ Tokenizer ─▶ TreeBuilder ─▶ ElementRegistry ─▶ Document | `rtf_tokenizer.*` | Bytes → tokens. Knows nothing about groups or destinations. | | `rtf_state.*` | The group stack: `{` saves, `}` restores. | | `rtf_parser.*` | `parse_tree` — tokens → `root → (paragraph \| page break) → (text \| line break)`. | -| `rtf_element_registry.*` | The flat registry, copied from `oldms/text` minus its style index. | +| `rtf_element_registry.*` | `internal::ElementRegistry` plus one `Text` payload — the smallest registry there is, and the one to read first. | | `rtf_document.*` | `internal::Document` + the element adapter. | | `rtf_file.*` | `abstract::DocumentFile`; validates the magic, hands out the document. | diff --git a/src/odr/internal/rtf/rtf_document.cpp b/src/odr/internal/rtf/rtf_document.cpp index 260d715b2..d11410f89 100644 --- a/src/odr/internal/rtf/rtf_document.cpp +++ b/src/odr/internal/rtf/rtf_document.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -35,82 +36,15 @@ const ElementRegistry &Document::element_registry() const { namespace { -class ElementAdapter final : public abstract::ElementAdapter, - public abstract::TextRootAdapter, - public abstract::LineBreakAdapter, - public abstract::ParagraphAdapter, - public abstract::TextAdapter { +using AdapterBase = internal::RegistryElementAdapter< + const ElementRegistry, abstract::TextRootAdapter, + abstract::LineBreakAdapter, abstract::ParagraphAdapter, + abstract::TextAdapter>; + +class ElementAdapter final : public AdapterBase { public: explicit ElementAdapter(const ElementRegistry ®istry) - : m_registry(®istry) {} - - [[nodiscard]] ElementType - element_type(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).type; - } - - [[nodiscard]] ElementIdentifier - element_parent(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).parent_id; - } - [[nodiscard]] ElementIdentifier - element_first_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).first_child_id; - } - [[nodiscard]] ElementIdentifier - element_last_child(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).last_child_id; - } - [[nodiscard]] ElementIdentifier - element_previous_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).previous_sibling_id; - } - [[nodiscard]] ElementIdentifier - element_next_sibling(const ElementIdentifier element_id) const override { - return m_registry->element_at(element_id).next_sibling_id; - } - - [[nodiscard]] bool - element_is_unique(const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool - element_is_self_locatable(const ElementIdentifier element_id) const override { - (void)element_id; - return true; - } - [[nodiscard]] bool - element_is_editable(const ElementIdentifier element_id) const override { - (void)element_id; - return false; - } - [[nodiscard]] DocumentPath - element_document_path(const ElementIdentifier element_id) const override { - return util::document::extract_path(*this, element_id, null_element_id); - } - [[nodiscard]] ElementIdentifier - element_navigate_path(const ElementIdentifier element_id, - const DocumentPath &path) const override { - return util::document::navigate_path(*this, element_id, path); - } - - [[nodiscard]] const TextRootAdapter * - text_root_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::root ? this : nullptr; - } - [[nodiscard]] const LineBreakAdapter * - line_break_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::line_break ? this : nullptr; - } - [[nodiscard]] const ParagraphAdapter * - paragraph_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::paragraph ? this : nullptr; - } - [[nodiscard]] const TextAdapter * - text_adapter(const ElementIdentifier element_id) const override { - return element_type(element_id) == ElementType::text ? this : nullptr; - } + : AdapterBase(registry) {} [[nodiscard]] PageLayout text_root_page_layout(const ElementIdentifier element_id) const override { @@ -158,7 +92,6 @@ class ElementAdapter final : public abstract::ElementAdapter, } private: - const ElementRegistry *m_registry{nullptr}; }; std::unique_ptr diff --git a/src/odr/internal/rtf/rtf_element_registry.cpp b/src/odr/internal/rtf/rtf_element_registry.cpp index 31df8dbbf..fc706b88b 100644 --- a/src/odr/internal/rtf/rtf_element_registry.cpp +++ b/src/odr/internal/rtf/rtf_element_registry.cpp @@ -1,94 +1,18 @@ #include -#include - namespace odr::internal::rtf { -void ElementRegistry::clear() noexcept { - m_elements.clear(); - m_texts.clear(); -} - -std::size_t ElementRegistry::size() const noexcept { return m_elements.size(); } - std::tuple ElementRegistry::create_element(const ElementType type) { - Element &element = m_elements.emplace_back(); - ElementIdentifier element_id = m_elements.size(); - element.type = type; - return {element_id, element}; + return create_element_(type); } std::tuple ElementRegistry::create_text_element() { - const auto &[element_id, element] = create_element(ElementType::text); - auto [it, success] = m_texts.emplace(element_id, Text{}); - return {element_id, element, it->second}; -} - -ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) { - check_element_id(id); - return m_elements.at(id - 1); -} - -ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) { - check_text_id(id); - return m_texts.at(id); -} - -const ElementRegistry::Element & -ElementRegistry::element_at(const ElementIdentifier id) const { - check_element_id(id); - return m_elements.at(id - 1); -} - -const ElementRegistry::Text & -ElementRegistry::text_element_at(const ElementIdentifier id) const { - check_text_id(id); - return m_texts.at(id); -} - -void ElementRegistry::append_child(const ElementIdentifier parent_id, - const ElementIdentifier child_id) { - check_element_id(parent_id); - check_element_id(child_id); - if (element_at(child_id).parent_id != null_element_id) { - throw std::invalid_argument( - "ElementRegistry::append_child: child already has a parent"); - } - - const ElementIdentifier previous_sibling_id = - element_at(parent_id).last_child_id; - - element_at(child_id).parent_id = parent_id; - element_at(child_id).previous_sibling_id = previous_sibling_id; - - if (element_at(parent_id).first_child_id == null_element_id) { - element_at(parent_id).first_child_id = child_id; - } else { - element_at(previous_sibling_id).next_sibling_id = child_id; - } - element_at(parent_id).last_child_id = child_id; -} - -void ElementRegistry::check_element_id(const ElementIdentifier id) const { - if (id == null_element_id) { - throw std::out_of_range("ElementRegistry::check_id: null identifier"); - } - if (id - 1 >= m_elements.size()) { - throw std::out_of_range( - "ElementRegistry::check_id: identifier out of range"); - } -} - -void ElementRegistry::check_text_id(const ElementIdentifier id) const { - check_element_id(id); - if (!m_texts.contains(id)) { - throw std::out_of_range("ElementRegistry::check_id: identifier not found"); - } + const auto &[element_id, element] = create_element_(ElementType::text); + Text &text = m_texts.emplace(element_id, Text{}); + return {element_id, element, text}; } } // namespace odr::internal::rtf diff --git a/src/odr/internal/rtf/rtf_element_registry.hpp b/src/odr/internal/rtf/rtf_element_registry.hpp index cf1f8b013..c59c12226 100644 --- a/src/odr/internal/rtf/rtf_element_registry.hpp +++ b/src/odr/internal/rtf/rtf_element_registry.hpp @@ -3,50 +3,32 @@ #include #include -#include +#include + #include #include -#include -#include namespace odr::internal::rtf { -class ElementRegistry final { +class ElementRegistry final + : public internal::ElementRegistry> { public: - struct Element final { - ElementIdentifier parent_id{null_element_id}; - ElementIdentifier first_child_id{null_element_id}; - ElementIdentifier last_child_id{null_element_id}; - ElementIdentifier previous_sibling_id{null_element_id}; - ElementIdentifier next_sibling_id{null_element_id}; - ElementType type{ElementType::none}; - }; - struct Text final { std::string text; }; - void clear() noexcept; - - [[nodiscard]] std::size_t size() const noexcept; - std::tuple create_element(ElementType type); std::tuple create_text_element(); - [[nodiscard]] Element &element_at(ElementIdentifier id); - [[nodiscard]] Text &text_element_at(ElementIdentifier id); - - [[nodiscard]] const Element &element_at(ElementIdentifier id) const; - [[nodiscard]] const Text &text_element_at(ElementIdentifier id) const; - - void append_child(ElementIdentifier parent_id, ElementIdentifier child_id); + [[nodiscard]] Text &text_element_at(const ElementIdentifier id) { + return m_texts.at(id); + } + [[nodiscard]] const Text &text_element_at(const ElementIdentifier id) const { + return m_texts.at(id); + } private: - std::vector m_elements; - std::unordered_map m_texts; - - void check_element_id(ElementIdentifier id) const; - void check_text_id(ElementIdentifier id) const; + SideTable m_texts; }; } // namespace odr::internal::rtf From 74f2c9a5f47eb953ac66b6d4a64c5b4d561e4791 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 6 Sep 2026 10:37:07 +0200 Subject: [PATCH 3/3] refactor(document): drop what the shared base made dead The stray `private:` `rtf`'s adapter was left with, its registry pointer having moved to the base, and the `document_util` / `document_path` includes eleven adapters kept after the two path forwards moved with it. Dropping them turned up `element_adapter.hpp` returning `DocumentPath` by value on nothing but a forward declaration - it had been compiling on whichever include the including `.cpp` happened to write first. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VEsRyBu8o4TGJGn3thNEDc --- src/odr/internal/common/element_adapter.hpp | 1 + src/odr/internal/csv/csv_document.cpp | 2 -- src/odr/internal/iwork/iwork_document.cpp | 2 -- src/odr/internal/markdown/markdown_document.cpp | 2 -- src/odr/internal/odf/odf_document.cpp | 2 -- src/odr/internal/oldms/presentation/ppt_document.cpp | 2 -- src/odr/internal/oldms/spreadsheet/xls_document.cpp | 2 -- src/odr/internal/oldms/text/doc_document.cpp | 2 -- .../ooxml/presentation/ooxml_presentation_document.cpp | 2 -- .../internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp | 2 -- src/odr/internal/ooxml/text/ooxml_text_document.cpp | 2 -- src/odr/internal/rtf/rtf_document.cpp | 4 ---- 12 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/odr/internal/common/element_adapter.hpp b/src/odr/internal/common/element_adapter.hpp index 8cb87e52f..624d31cff 100644 --- a/src/odr/internal/common/element_adapter.hpp +++ b/src/odr/internal/common/element_adapter.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/src/odr/internal/csv/csv_document.cpp b/src/odr/internal/csv/csv_document.cpp index 3fb65073d..038eeca18 100644 --- a/src/odr/internal/csv/csv_document.cpp +++ b/src/odr/internal/csv/csv_document.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -9,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/src/odr/internal/iwork/iwork_document.cpp b/src/odr/internal/iwork/iwork_document.cpp index b4e4480ae..c17bf45dd 100644 --- a/src/odr/internal/iwork/iwork_document.cpp +++ b/src/odr/internal/iwork/iwork_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -10,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/src/odr/internal/markdown/markdown_document.cpp b/src/odr/internal/markdown/markdown_document.cpp index 3014765cc..cb94ed09c 100644 --- a/src/odr/internal/markdown/markdown_document.cpp +++ b/src/odr/internal/markdown/markdown_document.cpp @@ -1,13 +1,11 @@ #include -#include #include #include #include #include #include -#include #include diff --git a/src/odr/internal/odf/odf_document.cpp b/src/odr/internal/odf/odf_document.cpp index 97a3b7dfc..e02367fbb 100644 --- a/src/odr/internal/odf/odf_document.cpp +++ b/src/odr/internal/odf/odf_document.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -15,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/src/odr/internal/oldms/presentation/ppt_document.cpp b/src/odr/internal/oldms/presentation/ppt_document.cpp index 5e50c7f6a..07d6c5a80 100644 --- a/src/odr/internal/oldms/presentation/ppt_document.cpp +++ b/src/odr/internal/oldms/presentation/ppt_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -9,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/src/odr/internal/oldms/spreadsheet/xls_document.cpp b/src/odr/internal/oldms/spreadsheet/xls_document.cpp index 7f01db121..8cee4648c 100644 --- a/src/odr/internal/oldms/spreadsheet/xls_document.cpp +++ b/src/odr/internal/oldms/spreadsheet/xls_document.cpp @@ -1,13 +1,11 @@ #include -#include #include #include #include #include #include -#include #include diff --git a/src/odr/internal/oldms/text/doc_document.cpp b/src/odr/internal/oldms/text/doc_document.cpp index 5f320df1a..25ee70758 100644 --- a/src/odr/internal/oldms/text/doc_document.cpp +++ b/src/odr/internal/oldms/text/doc_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -8,7 +7,6 @@ #include #include #include -#include namespace odr::internal::oldms::text { diff --git a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp index e0d261159..44c905ad9 100644 --- a/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp +++ b/src/odr/internal/ooxml/presentation/ooxml_presentation_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -11,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp index 489a5ce1d..c8134e3fe 100644 --- a/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp +++ b/src/odr/internal/ooxml/spreadsheet/ooxml_spreadsheet_document.cpp @@ -1,13 +1,11 @@ #include -#include #include #include #include #include #include -#include #include #include diff --git a/src/odr/internal/ooxml/text/ooxml_text_document.cpp b/src/odr/internal/ooxml/text/ooxml_text_document.cpp index d1b4d4b40..32f902dc6 100644 --- a/src/odr/internal/ooxml/text/ooxml_text_document.cpp +++ b/src/odr/internal/ooxml/text/ooxml_text_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -9,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/src/odr/internal/rtf/rtf_document.cpp b/src/odr/internal/rtf/rtf_document.cpp index d11410f89..941e57a50 100644 --- a/src/odr/internal/rtf/rtf_document.cpp +++ b/src/odr/internal/rtf/rtf_document.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -8,7 +7,6 @@ #include #include #include -#include #include #include @@ -90,8 +88,6 @@ class ElementAdapter final : public AdapterBase { (void)element_id; return {}; } - -private: }; std::unique_ptr