Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,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`, the shared `ElementRegistry` + `ElementAdapter`, filesystem, `style`, table cursor/range, `TextCursor`, temp files. |
| `src/odr/internal/common/` | Reusable impls: `Path`/`AbsPath`, base `Document`, the shared `ElementRegistry` + `ElementAdapter`, filesystem, `style`, table cursor/range, `TextCursor`, `SheetDependencies`, 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. |
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- `Document::dependents(position)` answers which cells' formulas read a
position, directly or through another, and `unresolved_formulas()` those
whose references could not all be read. A position is the new
`SheetPosition`: the sheet by its place among the document's sheets.

- A shared formula in an `.xlsx` is read for every cell of its group, not only
the master that spells it: `SheetCell::value().formula()` answers a member
with the expression moved to it, and `#REF!` where it moved off the grid.
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ set(ODR_SOURCE_FILES
"src/odr/logger.cpp"
"src/odr/odr.cpp"
"src/odr/quantity.cpp"
"src/odr/sheet_position.cpp"
"src/odr/style.cpp"
"src/odr/table_dimension.cpp"
"src/odr/table_position.cpp"
Expand Down Expand Up @@ -176,6 +177,7 @@ set(ODR_SOURCE_FILES
"src/odr/internal/common/media_file.cpp"
"src/odr/internal/common/path.cpp"
"src/odr/internal/common/random.cpp"
"src/odr/internal/common/sheet_dependencies.cpp"
"src/odr/internal/common/style.cpp"
"src/odr/internal/common/table_cursor.cpp"
"src/odr/internal/common/table_range.cpp"
Expand All @@ -189,6 +191,7 @@ set(ODR_SOURCE_FILES
"src/odr/internal/csv/csv_util.cpp"

"src/odr/internal/formula/formula_ast.cpp"
"src/odr/internal/formula/formula_dependencies.cpp"
"src/odr/internal/formula/formula_parser.cpp"
"src/odr/internal/formula/formula_writer.cpp"

Expand Down
18 changes: 16 additions & 2 deletions docs/design/spreadsheet-editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,22 @@ Each step ships on its own. "Both" means `.ods` and `.xlsx`.
alone ([ECMA-376] 18.3.1.40), so a member now reads it moved by the offset
between the two cells, `#REF!` where that leaves the grid. An array
formula's members carry no `<f>` at all and still report none.
2. Reference extraction → dependency graph per document; `Document` answers
"which cells depend on this position".
2. **Landed.** Reference extraction → `internal::SheetDependencies`: every
sheet walked, every formula parsed, each reference resolved to the
rectangle of a sheet it reads. `Document::dependents(position)` answers
which cells read it, directly or through another formula. The graph is
built once off the decoded document and kept, because writing a formula is
refused until step 4.

A formula that names something no position can be read out of is in
`Document::unresolved_formulas()` instead: it may read anything, and the
graph cannot say what. A reference into another document is neither — no
edit here reaches it.

The walk goes through `SheetAdapter::sheet_visit_formulas`, which hands out
the cells the file *spells* rather than the positions they cover: a
repeated ODS row of 1024 columns over 1048576 rows is a handful of nodes
and a billion positions, and only the first is walked.
3. View: a commit marks dependents stale (a class, the host is told); the
locked formula cell exposes its text (`data-odr-formula`, formula cells
only) so a formula bar or a tooltip can show it.
Expand Down
16 changes: 16 additions & 0 deletions src/odr/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <odr/internal/abstract/document.hpp>
#include <odr/internal/common/filesystem.hpp>
#include <odr/internal/common/sheet_dependencies.hpp>
#include <odr/internal/util/file_util.hpp>

#include <cstdint>
Expand All @@ -17,6 +18,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -375,6 +377,20 @@ Paragraph Document::insert_paragraph_after(const Paragraph &paragraph) const {
return {adapter, identifier, adapter->paragraph_adapter(identifier)};
}

std::vector<SheetPosition>
Document::dependents(const SheetPosition &position) const {
return dependents(std::vector<SheetPosition>{position});
}

std::vector<SheetPosition>
Document::dependents(const std::vector<SheetPosition> &positions) const {
return m_impl->sheet_dependencies().dependents(positions);
}

std::vector<SheetPosition> Document::unresolved_formulas() const {
return m_impl->sheet_dependencies().unresolved();
}

Filesystem Document::as_filesystem() const {
if (std::shared_ptr<internal::abstract::ReadableFilesystem> files =
m_impl->as_filesystem()) {
Expand Down
22 changes: 22 additions & 0 deletions src/odr/document.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
#include <odr/definitions.hpp>
#include <odr/logger.hpp>

#include <odr/sheet_position.hpp>

#include <iosfwd>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

namespace odr::internal::abstract {
class Document;
Expand Down Expand Up @@ -104,6 +107,25 @@ class Document final {

/// @}

/// @name Formulas
/// The graph is built the first time one of these is asked and kept.
/// @{

/// The cells whose formula reads @p position, directly or through another
/// formula. Sorted by sheet and then in reading order, each named once.
[[nodiscard]] std::vector<SheetPosition>
dependents(const SheetPosition &position) const;
/// The same for a whole batch of edited positions, which costs one walk
/// rather than one per position.
[[nodiscard]] std::vector<SheetPosition>
dependents(const std::vector<SheetPosition> &positions) const;

/// The cells holding a formula whose references could not all be read: it
/// may read any position, so a caller that must be right assumes it does.
[[nodiscard]] std::vector<SheetPosition> unresolved_formulas() const;

/// @}

/// The files the document is packaged from; empty for a document that is
/// one file.
[[nodiscard]] Filesystem as_filesystem() const;
Expand Down
21 changes: 21 additions & 0 deletions src/odr/internal/abstract/document.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <odr/quantity.hpp>

#include <cstdint>
#include <functional>
#include <iosfwd>
#include <memory>
#include <optional>
Expand All @@ -30,6 +31,10 @@ struct ParagraphStyle;
struct GraphicStyle;
} // namespace odr

namespace odr::internal {
class SheetDependencies;
} // namespace odr::internal

namespace odr::internal::abstract {
class ReadableFilesystem;
class ElementAdapter;
Expand Down Expand Up @@ -76,6 +81,10 @@ class Document {

[[nodiscard]] virtual ElementIdentifier root_element() const = 0;
[[nodiscard]] virtual const ElementAdapter *element_adapter() const = 0;

/// Built on the first question and kept: writing a formula is refused, so
/// nothing a write does changes it.
[[nodiscard]] virtual const SheetDependencies &sheet_dependencies() const = 0;
};

class ElementAdapter {
Expand Down Expand Up @@ -245,6 +254,11 @@ class PageAdapter {
page_name(ElementIdentifier element_id) const = 0;
};

/// What a formula cell states: the position the file states it at, and the
/// expression in the engine's own syntax.
using SheetFormulaVisitor = std::function<void(
std::uint32_t column, std::uint32_t row, const std::string &formula)>;

class SheetAdapter {
public:
virtual ~SheetAdapter() = default;
Expand All @@ -268,6 +282,13 @@ class SheetAdapter {
[[nodiscard]] virtual ElementIdentifier
sheet_first_shape(ElementIdentifier element_id) const = 0;

/// Calls @p visitor for every cell of the sheet stating a formula, once per
/// cell the file spells — a repeated run at its first position, so the grid
/// a repeat stands for is never walked. Visits none by default.
virtual void sheet_visit_formulas(
[[maybe_unused]] const ElementIdentifier element_id,
[[maybe_unused]] const SheetFormulaVisitor &visitor) const {}

/// Writes @p value into the cell at (@p column, @p row). A value stating
/// nothing clears it.
/// @throws UnsupportedOperation where the engine cannot write, the cell is
Expand Down
9 changes: 9 additions & 0 deletions src/odr/internal/common/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <odr/exceptions.hpp>

#include <odr/internal/abstract/filesystem.hpp>
#include <odr/internal/common/sheet_dependencies.hpp>

namespace odr::internal {

Expand Down Expand Up @@ -45,6 +46,14 @@ const abstract::ElementAdapter *Document::element_adapter() const {
return m_element_adapter.get();
}

const SheetDependencies &Document::sheet_dependencies() const {
if (!m_sheet_dependencies) {
m_sheet_dependencies =
std::make_unique<SheetDependencies>(SheetDependencies::of(*this));
}
return *m_sheet_dependencies;
}

bool Document::is_decrypted() const noexcept {
return m_encryption_state == EncryptionState::decrypted;
}
Expand Down
7 changes: 7 additions & 0 deletions src/odr/internal/common/document.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class ReadableFilesystem;

namespace odr::internal {

class SheetDependencies;

class Document : public abstract::Document {
public:
Document(FileType file_type, DocumentType document_type,
Expand All @@ -37,6 +39,8 @@ class Document : public abstract::Document {
[[nodiscard]] const abstract::ElementAdapter *
element_adapter() const override;

[[nodiscard]] const SheetDependencies &sheet_dependencies() const final;

/// Decoded from a package that was password-encrypted. `save` has no
/// encryption to put back, so a savable engine refuses one.
[[nodiscard]] bool is_decrypted() const noexcept;
Expand All @@ -50,6 +54,9 @@ class Document : public abstract::Document {

ElementIdentifier m_root_element{null_element_id};
std::unique_ptr<abstract::ElementAdapter> m_element_adapter;

private:
mutable std::unique_ptr<SheetDependencies> m_sheet_dependencies;
};

} // namespace odr::internal
Loading
Loading