Skip to content

Linked workbooks: references typed by file name or path go through a link, shown with the file - #829

Open
marcoarnulfo wants to merge 10 commits into
storytold:mainfrom
marcoarnulfo:fix/external-workbook-refs
Open

marcoarnulfo wants to merge 10 commits into
storytold:mainfrom
marcoarnulfo:fix/external-workbook-refs

Conversation

@marcoarnulfo

Copy link
Copy Markdown
Contributor

Fixes #804.

Built on #464 (it needs its ExternalBook and the reading of the link parts), so until #464 is merged this branch shows its three commits too; the last three commits are this PR. It sits next to #824 (#801): that one adds Workbook::source_dir and rebases kept link targets on Save As, and this one adds ExternalBook::path. They touch passthrough.rs and its tests in different places, and I'll rebase whichever lands second.

The model and the file keep only [n], the position of the link. What changes is the way in and the way out. When a formula or a name is entered, a reference to another workbook typed by file name or path ([Source.xlsx]Sheet1!A1, 'C:\links\[Source.xlsx]Sheet1'!A1, Source.xlsx!Rate, 'C:\links\Source.xlsx'!Rate) is rewritten as [n]Sheet1!A1 / [n]!Rate. The link is matched by path, or by file name when no folder is given. Otherwise a new link is added with the typed path. A new link has no values yet, so its sheets are marked refreshError, and the reference is #REF! as in Excel when the file is missing. ExternalBook now keeps the link's path, read from the externalLinkPath relationship (file:///C:\links\Source.xlsx → C:\links\Source.xlsx, a relative target stays relative). The formula bar, Show Formulas, the Name Manager and document.inspect print [n] back as the file ('C:\links\[Source.xlsx]Sheet1'!A1), and that text goes back in unchanged when you press Enter. On save, the links read from the file are still passed through as they were. Each new link gets its own externalLink part and rels after them, so [1] stays [1]. <f> only ever holds [n].

In the formula language: the unquoted [Source.xlsx]Sheet1!A1 now lexes (it was refused with unexpected Sheet("Sheet1", None)), and a drive's C: inside a quoted path is no longer read as the colon of a 3-D range. That colon is what produced the 'C:\…\[links[Source.xlsx]Sheet1]…' text Excel showed. [Source.xlsx]Sheet1 prints unquoted when the sheet name allows it.

Choices I'd flag (happy to switch):

  • A link read from the file with a relative target (Target="Source.xlsx") is shown as [Source.xlsx]Sheet1!A1. Excel shows the absolute path there, resolved against the workbook's folder. With XLSX: Save As into another folder keeps links on the same workbook #824's source_dir that's a small follow-up, so I left it out here.
  • Book.xlsx!Name is read as another workbook only when the qualifier isn't a sheet of this workbook and has a folder or a file extension. So Data.v2!Rate stays local when there's a sheet Data.v2, and Plain!Rate isn't treated as a file.
  • If you type a sheet or name that an existing (kept) link doesn't have, it's added to the model, so the reference is #REF!. It isn't added to the kept part, which is written byte for byte. A new link's part is generated from the model.
  • Reading the linked file (Update Values / Edit Links) is still out, as the issue says.

Validation:

  • linked_workbooks.rs (engine, new), one assertion per row of the issue's table: what is kept ([1]Sheet1!A1, SUM([1]Sheet1!A1:A3), '[1]P&L Data'!A1, [1]!Rate), the value (#REF! for a new link; 10, 20, 30, 60 and 0.05 through an existing link), the saved <f> and the externalLink part with its rels target, the formula bar text before and after reopening, and the Name Manager / document.inspect refersTo. It doesn't build on XLSX: formulas into linked workbooks calculate from the values saved with the link #464's head (no path in ExternalBook). With the entry rewrite removed, it fails as on main ([Source.xlsx]Sheet1!A1 kept instead of [1]Sheet1!A1).
  • another_workbook_by_file_name_or_path_parses (formula): fails on XLSX: formulas into linked workbooks calculate from the values saved with the link #464's head ([Source.xlsx]Sheet1!A1: unexpected Sheet("Sheet1", None)), passes here.
  • a_new_link_follows_the_kept_ones (xlsx): a kept link plus a typed one are written as [1] and [2], both read back with their paths. damaged_kept_content_is_dropped_without_failing_the_read still holds: when the file's links can't be kept, nothing is generated after them.
  • Mutations, each one caught: no rewrite on entry; always a new link; [n] in the formula bar, the Name Manager or inspect; a defined name kept as typed; any qualifier read as a file; no new link part; the path not decoded; a new link without refreshError (0 instead of #REF!); a drive colon splitting the sheet; the file name quoted by the printer.
  • cargo test -p gridcraft-formula --lib, cargo test -p gridcraft-xlsx --lib a_new_link_follows, cargo test -p gridcraft-engine --test linked_workbooks, cargo test -p gridcraft-xlsx --lib passthrough_tests, cargo test -p gridcraft-calc --lib linked_workbooks, cargo clippy -p gridcraft-formula -p gridcraft-model -p gridcraft-calc -p gridcraft-xlsx -p gridcraft-engine -p gridcraft-ui-egui --all-targets -- -D warnings, cargo fmt --all -- --check
  • Not checked in Excel: I wrote the new link part the way Excel writes one for a missing file (sheetNames, definedNames with no refersTo, refreshError sheet data, file:/// path for absolute paths). Opening it in Excel 365 is the check I can't do here.
  • (macOS, Apple silicon)

neilcawse and others added 10 commits October 15, 2026 10:46
…/footer labels

CSV/TSV export ran General numbers through the 11-character cell display, so a
barcode like 124412140710 was saved as 1.24412E+11. display::value_text now
writes a General number as & converts it (number_to_text), as its doc already
said; cell_text keeps the cell display. Fixes the two storytold#426 tests that broke when

Adds the French "Different First Page" / "Different Odd & Even Pages" labels
missing after storytold#413, which failed the i18n completeness test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with the link

A formula reading another workbook ([1]Prices!B2, '[2]P&L'!A1:B9, a name over
such a range) recalculated to #REF!: the externalLink parts were passed through
on save but never read. Excel calculates these from the cached copy of the
linked cells it saves in the file, as it does whenever the source is closed.
On a 4,224-file real-world corpus this was the largest root cause of
calculation mismatches.

The reader now loads each link's sheet names, cached cells, refresh-error
flags and defined names into Workbook::external_books; the lexer reads
unquoted [1]Sheet!A1 and [1]!Name; references, linked names and [0]!Name
evaluate from that data; the *IF(S) range arguments refuse
linked-workbook values and written errors, as Excel does with a closed
source; and linked sheets print unquoted when the sheet
name allows, so saved formulas keep their text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… prints back

References into a linked workbook became value arrays when evaluated, so `@`
and implicit intersection took the top-left value (`=@[1]Data!B:B` in row 4
gave the header), ROWS/COLUMNS/INDEX saw the block trimmed to the last cached
cell (`ROWS([1]Data!A:A)` = 3), and every lookup rebuilt the whole block.
They are now Areas whose sheet number (from LINKED_SHEETS up) stands for the
linked workbook and sheet; cells are read from the saved values only when the
reference is dereferenced (Evaluator::cell, Evaluator::block_values), and a
linked sheet's extent is remembered per pass like a local used range.

The printer dropped the quotes from '[1]Jan:Dec'!A1 and the lexer couldn't
read [1]Jan:Dec!A1. The lexer now accepts the unquoted MS-XLSX sheet-range
form, and the printer quotes a range when either sheet name needs it.
…torytold#567's name quoting

Main now quotes the sheet of a sheet-qualified name when printing it. A linked
workbook's own name has no sheet, so [1]!Rate stays unquoted, and the shown
name is stored bare so the printer quotes the path once.
@marcoarnulfo
marcoarnulfo force-pushed the fix/external-workbook-refs branch from 435b6a9 to 48d4342 Compare October 15, 2026 09:23
@marcoarnulfo

Copy link
Copy Markdown
Contributor Author

Rebased onto current main. One commit is new rather than a conflict fix: after #567 the printer quotes sheet-qualified names, which turned a linked workbook's name [1]!Rate into '[1]'!Rate (and quoted a typed path twice). 48d4342 keeps #567's quoting for sheets and leaves the book-only [1]!Rate form bare, with a parse → print → parse check in the formula tests; quoted_sheet_qualified_name_keeps_quotes still passes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(formula): references to another workbook by file name or path are refused or #REF!, saved in a form Excel misreads, and shown as [1]

2 participants