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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- 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.

- `TablePosition::try_to_column_num` / `try_to_row_num` read a column or row
spelling as an optional instead of throwing, and without case. **Fix**:
`to_column_num` wrapped silently past the index range instead of refusing.
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ set(ODR_SOURCE_FILES
"src/odr/internal/csv/csv_file.cpp"
"src/odr/internal/csv/csv_util.cpp"

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

"src/odr/internal/html/common.cpp"
"src/odr/internal/html/document.cpp"
Expand Down
6 changes: 6 additions & 0 deletions docs/design/spreadsheet-editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,12 @@ Each step ships on its own. "Both" means `.ods` and `.xlsx`.
where the two part. A named expression, a reference over several sheets and
a spelling past the grid (`A0`) stay opaque names; a formula that does not
parse answers nothing.

A writer and a `shift` over the tree come with it, which is what makes an
ooxml shared formula readable: a group spells its expression on the master
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".
3. View: a commit marks dependents stale (a class, the host is told); the
Expand Down
54 changes: 54 additions & 0 deletions src/odr/internal/formula/formula_ast.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <odr/internal/formula/formula_ast.hpp>

#include <limits>
#include <variant>

namespace odr::internal::formula {

namespace {

constexpr std::int64_t index_limit = std::numeric_limits<std::uint32_t>::max();

[[nodiscard]] bool shift_coordinate(std::optional<Coordinate> &coordinate,
const std::int64_t by) {
if (!coordinate.has_value() || coordinate->absolute) {
return true;
}
const std::int64_t moved = static_cast<std::int64_t>(coordinate->index) + by;
if (moved < 0 || moved > index_limit) {
return false;
}
coordinate->index = static_cast<std::uint32_t>(moved);
return true;
}

[[nodiscard]] bool shift_cell(CellReference &cell, const std::int64_t columns,
const std::int64_t rows) {
return shift_coordinate(cell.column, columns) &&
shift_coordinate(cell.row, rows);
}

} // namespace

} // namespace odr::internal::formula

namespace odr::internal {

void formula::shift(Node &node, const std::int64_t columns,
const std::int64_t rows) {
if (auto *cell = std::get_if<CellReference>(&node.content)) {
if (!shift_cell(*cell, columns, rows)) {
node.content = ErrorLiteral{ErrorType::reference};
}
} else if (auto *range = std::get_if<RangeReference>(&node.content)) {
if (!shift_cell(range->from, columns, rows) ||
!shift_cell(range->to, columns, rows)) {
node.content = ErrorLiteral{ErrorType::reference};
}
}
for (Node &child : node.children) {
shift(child, columns, rows);
}
}

} // namespace odr::internal
4 changes: 4 additions & 0 deletions src/odr/internal/formula/formula_ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,8 @@ struct Node final {
}
};

/// Moves every relative reference in @p node by (@p columns, @p rows). An
/// absolute (`$`) axis stays, and one moved off the grid becomes `#REF!`.
void shift(Node &node, std::int64_t columns, std::int64_t rows);

} // namespace odr::internal::formula
4 changes: 3 additions & 1 deletion src/odr/internal/formula/formula_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <string>
Expand Down Expand Up @@ -410,7 +411,8 @@ class Parser final : private TextCursor {
const std::string text(rest().substr(0, length));
char *end = nullptr;
const double value = std::strtod(text.c_str(), &end);
if (end != text.c_str() + text.size()) {
// an overflow answers infinity, which no formula spells
if (end != text.c_str() + text.size() || !std::isfinite(value)) {
return {};
}
advance(length);
Expand Down
Loading
Loading