Repository navigation
feat(ooxml,cli): XLSX adapter — M5 5.3 - #38
Merged
Merged
Conversation
Sheet direction, cell alignment, and preserving formulas and defined names
through a repair. The third OOXML vocabulary and the second format with a
writer — and the first whose writer cannot repair anything by editing it
in place.
A cell is a paragraph and the worksheet around it is the container. The
cell is one run of text with one set of properties deciding how it
renders, which is what `TextUnit` is; it lays out no other unit, and
every container this model has is something whose direction decides where
*other* units begin. `sheetView/@rightToLeft` is that: it decides which
side column A sits on, the sentence `a:tblPr/@rtl` and
`w:tblPr/w:bidiVisual` already state. So a sheet is a `Table` and carries
a table's id — emitted only where the sheet holds text in two or more
columns, on the reasoning that made a text body a `Columns` container at
`numCol >= 2` and not below it: with one column there is nothing to start
at the wrong end of, and a container reported on a single column of text
would be a finding on a margin.
The suite gained by that rather than losing. Two of its cases stated a
table of one cell, which no format has a column order for and which
PowerPoint and Word happen to emit a container for anyway; both now state
two. A degenerate table was proving less than it looked.
Excel is the first format where a container is also the chain above its
cells. `AGENTS.md` says a cell's text does not inherit its table's column
order, and in the other two formats that is simply true. `rightToLeft` is
not only a column order: it is the reading order every cell in the sheet
falls back to when its own format record states none. So a cell with no
`readingOrder` resolves to the sheet's, `Inherited` naming the worksheet
part, and ADR 0007 judges it from there.
`readingOrder="0"` is `Unset`, and that is the argument `dir="auto"` got.
It is context dependent: it asks the application to take the direction
from the first strong character, which is the sentence `Resolved::Unset`
carries. A cell under it is correct by today's text and undefined for
tomorrow's, which is the fragile tier and not a decision.
`horizontal="general"` is `Unset` too, for a sharper reason. It looks
like a direction-relative alignment and is not one: it is Excel's default
chosen by the cell's data type, text at the start edge and numbers at the
end. Reading it as `Start` would silence `alignment-unset` on the
commonest cell in any workbook — a rule going quiet on a value nobody
stated, which is invariant 2 run backwards.
Three things SpreadsheetML cannot say, and one it says only once. There
is no list feature, so a marker at the front of a cell is always a glyph
somebody typed and `--convert-bullets` has nothing to convert to. There
is one font per cell and it draws every script, so
`complex-font-missing`'s precondition is unreachable exactly as it is on
HTML. `alignment/@indent` is measured from the start edge like every
other OOXML indent, so `inset` stays absent. And there is no language tag
anywhere on a cell, a run, a font or a style: `docProps/core.xml`'s
`dc:language` is the only place a workbook states one, so that is what
every cell inherits — and the repair is refused, because one tag answers
for the whole file and setting it to satisfy an Arabic cell would relabel
the English beside it. The finding therefore lands on every Arabic cell
of an untagged workbook. That repetition is the format's shape and the
corpus carries it rather than hiding it.
A formula is a source the adapter could not read, which is ADR 0009
reaching a second format. `<c><f>…</f><v>…</v></c>` stores a cached
result, and the cache is not the document's text: Excel recomputes it on
open, so a finding on it is a finding on a string no author wrote and no
repair can change — and a re-audit after a recalculation could honestly
disagree with the first. So a formula cell produces no unit, and one
whose cache carries Arabic is named in `sources.unread`. A cache holding
no Arabic is not named, since there is nothing there this tool would have
judged. `{"unread": []}` stops being HTML's alone.
The writer had to append where every previous one edited. `<c s="3"/>`
names a record in `cellXfs`, and forty cells across four sheets may name
the same one; writing the repaired `readingOrder` into it would set the
reading order of all forty, English included. So `sheet.rs` clones the
record the cell already has, changes the one attribute, appends it, and
repoints that cell's `@s` — answering identical requests with one
appended record, so two hundred cells needing the same fix grow `cellXfs`
by one. Shared strings work the same way: the repaired text is a new
`<si>`, cloned from the old one so its rich-text runs survive the edit,
with the cell's `<v>` repointed at it and the original left for whatever
else was pointing there.
The offsets had to leave the furigana out. A `<t>` inside an `<rPh>` is a
phonetic reading beside the text rather than part of it, so the reader
skips it — and a rewriter that did not skip it too would apply a deletion
computed against the string a reader sees to the annotation next to it.
One bug the tests caught rather than a user's workbook. `alignment-unset`
fires on a cell whose direction is inherited *and correct*, so no
`SetDirection` arrives beside the alignment to say which way the cell
reads. The first version lowered `Start` against the cell's own record,
found nothing there, and wrote `horizontal="left"` under Arabic — the
very hard left edge `alignment-incoherent` exists to report.
`sheet::Inherited` is the fix, and it is `rewrite::Inherited` again for
the same reason.
Thirty-four conformance cases pass with XLSX among the formats,
twenty-eight of them without a line changing. Four refusals join the
committed list — a native list and two paragraphs in different languages,
which are Excel's alone, and a direction-relative alignment and an
unevenly filled pair of font slots, which it now refuses beside
PowerPoint and HTML — and `alignment-incoherent` is asserted on three
formats where it was asserted on two.
`tests/fixtures/quarterly-figures.xlsx` carries one instance of every
defect the adapter can see, a formula whose cached result is Arabic, and
two defined names; `quarterly-figures-correct.xlsx` is the workbook the
tool must leave completely alone, and does. Both validate against the
published ECMA-376 schemas, and so does the repaired copy. Through the
writer, the repair leaves `xl/workbook.xml` byte-identical and every
`<f>` where it was, and the two findings that survive it are the two
repairs the format cannot express — listed in `repairs.skipped`, never
claimed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sheet direction, cell alignment, and preserving formulas and defined names through a repair — PLAN §5.3, which closes M5. The third OOXML vocabulary, the second format with a writer, and the first whose writer cannot repair anything by editing it in place.
A cell is a paragraph and the worksheet is the container
The question §5.3 set. A cell is one run of text with one set of properties deciding how it renders, which is what
TextUnitis; it lays out no other unit, and every container this model has is something whose direction decides where other units begin.sheetView/@rightToLeftis that — it decides which side column A sits on, the sentencea:tblPr/@rtlandw:tblPr/w:bidiVisualalready state.The grid is emitted only where the sheet holds text in two or more columns, on the reasoning that made a text body a
Columnscontainer atnumCol >= 2and not below it: with one column there is nothing to start at the wrong end of.Excel is the first format where a container is also the chain above its cells.
rightToLeftis not only a column order — it is the reading order every cell falls back to when its own record states none. That is the format's rule, not the adapter's.What the format cannot say
docProps/core.xml'sdc:languageis the only place a workbook states one, so that is what every cell inherits — andSetLanguageis refused, because one tag answers for the whole file and setting it to satisfy an Arabic cell would relabel the English beside it.--convert-bulletshas nothing to convert to.complex-font-missing's precondition is unreachable exactly as it is on HTML.readingOrder="0"andhorizontal="general"both come backUnset— the first is context dependent, the second is Excel's default chosen by the cell's data type rather than an edge anybody stated.A formula is a source that was not read
<c><f>…</f><v>…</v></c>stores a cached result Excel recomputes on open, so it is not the document's text. A formula cell produces no unit, and one whose cache carries Arabic is named insources.unread— ADR 0009 reaching a second format.{"unread": []}stops being HTML's alone.The writer appends where every previous one edited
<c s="3"/>names a record incellXfsthat forty cells may share; writing the repairedreadingOrderinto it would re-format all forty, English included. So a repair clones the record, changes one attribute, appends it, and repoints that cell's@s. Identical requests share one appended record. Shared strings work the same way — cloned, so rich-text runs survive.<f>and<definedNames>come through untouched, and mechanically so:xl/workbook.xmlis not a part a plan can name.One bug the tests caught
alignment-unsetfires on a cell whose direction is inherited and correct, so noSetDirectionarrives beside the alignment. The first version loweredStartagainst the cell's own record, found nothing, and wrotehorizontal="left"under Arabic — the very hard left edgealignment-incoherentexists to report.sheet::Inheritedis the fix, and it isrewrite::Inheritedagain for the same reason.Conformance
34 cases pass, 28 without a line changing. Four refusals join the committed list — a native list and two paragraphs in different languages (Excel's alone), and a direction-relative alignment and an unevenly filled pair of font slots (now refused beside PowerPoint and HTML).
alignment-incoherentis asserted on three formats where it was asserted on two.Two cases stated a table of one cell, which no format has a column order for; both now state two. A degenerate table was proving less than it looked.
Corpus
quarterly-figures.xlsxcarries one instance of every defect the adapter can see, a formula whose cached result is Arabic, and two defined names.quarterly-figures-correct.xlsxis the workbook the tool must leave completely alone, and does.Both validate against the published ECMA-376 schemas — and so does the repaired copy. Through the writer,
xl/workbook.xmlcomes out byte-identical and every<f>is where it was; the two findings that survive are the two repairs the format cannot express, listed inrepairs.skippedand never claimed.Checks
make verifyclean — version, fmt, clippy-D warnings, 34 test targets, docs, release build.make validate-fixturespasses on both new workbooks.