Recursive AI

The 1.0 corpus gate

All onedocument docs

What was run over the public corpus before 1.0, with the numbers and an explanation for every difference. The runs are one pass each, driven by tools/gate/gate.sh (stages), the probes under tools/, and the pinned oracles (LibreOffice 26.8.2.2, Jing 20241231 with the ODF 1.3 RelaxNG schema, lxml/libxml2 for XSD, Apache Tika 3.3.3, veraPDF 1.30.3), on the build machine. Corpus files stay on it; only counts are written here. The fixes the gate found are in the last section.

Corpus (all fetched by tools/corpus and tools/setup, none committed): Common Crawl Office samples (2,000 DOCX, 365 XLSX), public bug-tracker attachments (800 DOCX, 702 XLSX, 300 PPTX, 18 XLSB), LibreOffice-converted sets made from govdocs1 (453 XLSX, 237 PPTX), govdocs1 itself (810 DOC, 453 XLS, 680 PPT, 23 RTF), generated documents (25) and the 1M × 20 stress workbook. 4,900 OOXML/XLSB packages in all. There is no public ODF/RTF corpus in the set: ODF files are ours (written from the corpus) and LibreOffice's (converted from it), see the ODF section.

1. Round trip

Untouched = open and save with no edit. The comparison is per part by SHA-256 against the input, read by an independent zip reader (the BCL's), and for the hub sweeps also RoundTrip (raw entries preserved = stored bytes, method and CRC unchanged).

Layer / setFilesResultParts comparedIdentical
Package layer (PackageSweep opc), all OOXML/XLSB sets4,900 tried4,894 identical; 6 rejected with a typed defect (5 × packaging.missing-content-types, 1 × packaging.damaged); 0 untyped failures111,239111,239 (100%)
– of which recovered from damage (truncated downloads, rewritten from the local headers)44included in the identical count, defects reported
DOCX hub (Document.Open → Save)2,8102,806 saved: 2,773 intact files, and the 33 of 36 truncated downloads (no central directory) that Document.Open now recovers from the local headers with typed defects; 4 typed (3 truncated packaging.missing-content-types, 1 not a package)56,316 (intact files; RoundTrip reads only files with a directory, the recovered ones are compared at the package layer)56,316; raw entries preserved 56,316 of 56,316
XLSX hub (XlsxProbe sweep)1,5201,519 ok (1,508 intact, 11 truncated downloads recovered); 1 typed packaging.missing-content-types39,328 (intact files)39,328
PPTX hub (PptxProbe sweep)537537 ok43,98643,986
XLSB (PackageSweep, XlsbProbe sweep)1818 identical334334

Edits (the hub changes only the part it edits; unknown markup stays because the part is rewritten from the tree):

EditFilesResult
DOCX: one inserted paragraph2,668 edited (Common Crawl 1,935, bug tracker 723, generated 10; the others are among the 37 above, had no paragraph to edit, or raised 1 typed xml.duplicate-attribute on a main part)exactly one part changed in every one (the main part; in 3 files it is named document2.xml); all other parts identical, raw entries preserved
XLSX: one cell set, and every worksheet rewritten from the cell store1,508the single-cell edit changed exactly one part (the worksheet) in all 1,508; every sheet rewritten from the store, saved, reopened and compared cell by cell: 103.8M cells, 0 mismatches
PPTX: text, cell, add, duplicate, delete, move (PptxProbe)537 decks, 2,193 edits (add 537, text 452, dup 537, delete 302, move 302, cell 63)only the slide, presentation.xml, its rels and [Content_Types].xml changed among existing parts (0 other); added and removed parts are the slide's own

Differences explained: the 36 DOCX and 11 XLSX that first raised format.unrecognised are truncated downloads (they stop at the 5 MiB crawl cap and have no end-of-central-directory record). The format sniffer still answers Unknown for a zip without a readable directory (a test pins that), but Document.Open, Workbook.Open and Presentation.Open now hand a file that starts with a zip header and has no end-of-directory record to the package reader, which scans the local headers and reports each repair as a located defect: 33 of the 36 DOCX and all 11 XLSX open and save now; the other 3 raise the typed packaging.missing-content-types because no content types entry survived the cut.

2. Write validity

Our output against the pinned schemas (ECMA-376 5th ed. Transitional and OPC, plus the Microsoft extension schemas under schemas/ms, markup compatibility applied first, xml:space on text elements not counted because Office writes it too), compared with the same check on the input: a part is newly invalid if it is invalid in the output and valid (or absent) in the input.

OutputSampleParts checkedInvalid in outputInherited from the inputNewly invalid
DOCX with one paragraph added (paired_validate.py)275 pairs4,5681,018 in 207 files1,0180
XLSX with every worksheet rewritten from the cell store (xlsx_edit_checks.py)360 pairsall parts1,1911,1910 valid → invalid; the error count rose in 4 already-invalid worksheets (below)
PPTX after each edit, existing parts515 decks, 2,101 outputsall parts0; added parts invalid beyond the inherited errors: 0
LibreOffice opens the edit outputsXLSX 180 (CSV of every sheet equal to the original's in 180), PPTX 121 (all convert)

The error count rose in worksheets that were already invalid (49 → 50 and 9 → 50 in Common Crawl files, 1 → 2 in a bug-tracker file; a rerun of the 150-file samples listed 15 such parts in 4 Common Crawl files and 1 in a bug-tracker file, 0 in the LibreOffice-converted set). The validator reports at most 50 errors per part, so a 49 → 50 is a cap effect or one more error. The one non-capped case (1 → 2) is the xr:uid attribute on hyperlink: the original carries the same attribute on the same element and on the root, and both are declared ignorable with mc:Ignorable; the worksheet root is identical in the original and the rewrite, so it is not an attribute our writer adds. Why the validator reports it for the rewrite only was not established (open item).

Legacy → OOXML (the conversions the hub does on open), 100 files per format sampled by name order, schema-validated and opened by LibreOffice:

Source → outputConvertedTypedUntypedValid / opened by LibreOfficeFiles with a loss report
DOC → DOCX (810 DOC, sample)900090 / 9062 of 90
XLS → XLSX (453 XLS, sample)901 (format.unrecognised)090 / 9052 of 90
PPT → PPTX (680 PPT, sample)980098 / 9896 of 98
RTF → DOCX (all 23)230023 / 237 of 23

Loss codes by count of files (items): DOC loss.doc-properties 43 (45), loss.doc-sprms 24 (4,205), loss.doc-drawing-shapes 32 (289), loss.doc-text-boxes 23 (662), loss.doc-header-text-boxes 13 (13), loss.doc-ole-objects 8 (22), loss.doc-form-fields 1 (126). XLS loss.rich-text 35 (784), loss.drawing-shapes 13 (156), loss.charts 11 (73), loss.document-properties 11 (11), loss.vba 11 (11), loss.external-links 4 (7), loss.defined-names 2 (3), and one each of loss.autofilter-criteria, loss.form-controls, loss.macro-names, loss.pictures, loss.pivot-tables. PPT ppt.animations-dropped 76 (9,880), ppt.document-properties-not-carried 91 (118), ppt.shape-adjust-dropped 22 (228), ppt.ole-object-flattened 18 (43), ppt.shape-approximated 15 (65), ppt.table-borders-approximated 13 (37), ppt.fill-approximated 4 (10), ppt.picture-pict 4 (35), ppt.wordart-flattened 3 (3), ppt.table-as-group 2 (5), ppt.path-escape 1 (6), ppt.transition-dropped 1 (13). RTF loss.rtf-text-box 5 (36), loss.rtf-shape 5 (52), loss.rtf-frames 2 (11). Every conversion that lost something carried a report; a conversion with an empty report was lossless (DOC 28, XLS 38, PPT 2, RTF 16). The numbers are after the TotalTime fix below; the first pass found 1 invalid PPT-derived app.xml.

OOXML → other formats we write, sample of 60 per format (Common Crawl, bug tracker), loss reports counted, LibreOffice opening the output:

OutputSavedTypedUntypedLibreOffice opensNotes
DOCX → RTF590059 / 59all 59 carry a report (loss.rtf-shape-position 304 items, loss.rtf-table-style 99, loss.rtf-run-effect 87 …)
DOCX → HTML590059 / 5935 with reports
PPTX → HTML500050 / 5050 with reports (loss.html-slide-layout, loss.html-pictures, loss.html-animations)
DOCX → Markdown, XLSX → CSV59, 510, 20n/areports present where content was dropped
XLSX → XLS (BIFF8)51 + 59 (two sets)2 (truncated inputs)0 after the fix below (2 before)51 / 51, 59 / 59reports on formulas, defined names, charts, pictures
XLSX → XLSB512 (truncated inputs)049 / 51; 2 LibreOffice timeouts at 120 slossless

3. OpenDocument

No ODF corpus is public; the sets are ours (written from the corpus by the exporters) and LibreOffice's (converted from the same files). Jing validates every XML part and the manifest against the ODF 1.3 RelaxNG schema; foreign-namespace attributes and elements (LibreOffice extensions) are dropped before checking because the OASIS schema allows none.

SetFilesPartsJing-valid filesLibreOffice opensEntry round tripOne-entry edit
Our ODT from 59 DOCX5923659 (0 fail), after the fixes below59 / 59340 / 340 entries identical59 / 59 leave the other entries identical
Our ODS from 53 XLSX sampled (51 written, 2 truncated inputs typed)5125551 (0 fail), after the fixes below51 / 51306 / 30651 / 51
Our ODP from 50 PPTX502005050 / 50464 / 46450 / 50
LibreOffice's ODT / ODS / ODP (59 / 49 / 38) → read by us → OOXML (59 / 48 + 1 typed / 38)59 / 49 / 38642 / 642, 699 / 699, 389 / 38959, 49, 38 of 59, 49, 38
– the OOXML we write from them59 DOCX, 48 XLSX, 38 PPTXschema-valid: 59, 48, 38LibreOffice opens 59, 48, 38

The entry round trip is entry content (the package's zip container is rewritten; entries are copied raw). The first pass failed 3 ODT and 1 ODS file: text:soft-hyphen inside a text:span (6 elements; the schema has no such element, a soft hyphen is the character U+00AD), a table:table-row with no cell (3; the source row had no cells), style:header-first without a style:header before it (1; the schema nests the left and first variants inside the default one), and number styles with more than one number:number or with two number:text after it (a phone-number code such as (0) 0-0; the first digit group is kept, the rest are reported as multiple-digit-groups). All four are fixed in the exporters and the same 59 and 51 files, rewritten, now validate. Earlier, before the first fixes, the run failed 5 ODT, 10 ODS and 8 ODP files.

4. Text extraction against Apache Tika 3.3.3

150 files per format (every k-th by name), our text (Office.ExtractText) against Tika's, as the Dice coefficient of character 5-grams ("sim", white space ignored). "precision" is the share of our word tokens that Tika also has; "recall (no digits)" is the share of Tika's tokens without digits that we have (workbooks leave numbers out of the text by design, docs/text-extraction.md).

FormatFilessim mean / median / p5precision meanrecall (no digits) mean / medianFiles with sim < 0.8
DOCX (Common Crawl)1360.949 / 0.990 / 0.8311.0000.962 / 0.9985
DOCX (bug tracker)1340.843 / 0.996 / 0.0000.9910.878 / 1.00026
DOC1350.982 / 0.995 / 0.9380.9970.986 / 0.9982
RTF (all 23)230.991 / 0.997 / 0.9611.0000.997 / 1.0000
XLSX1200.751 / 0.839 / 0.1891.0000.898 / 0.98850
XLS1140.792 / 0.895 / 0.2080.9990.941 / 0.99639
XLSB180.565 / 0.735 / 0.0001.0000.820 / 0.97712
PPTX1000.599 / 0.818 / 0.0000.9920.606 / 0.82950
PPT1350.861 / 0.952 / 0.1770.9850.883 / 0.97123

Nothing of ours is invented (precision ≥ 0.985 everywhere). The differences are what Tika emits and we do not, by policy:

  • numbers, booleans and error values of workbook cells (#REF!, TRUE), and hyperlink targets (mailto:): XLSX, XLS, XLSB;
  • the file names of embedded pictures and objects (image1.png, image2.svg, oleObject1.bin): 22 of 100 PPTX and 8 of 134 bug-tracker DOCX are exactly the files where Tika lists only such names and we have no text;
  • master and layout text of presentations (footer placeholders such as a copyright line, the title-layout "PowerPoint Presentation") and the slide-number field ‹#›;
  • text inside embedded OLE objects (a bug-tracker DOCX carries 105,027 characters of Lorem ipsum in embedded documents) and chart parts of DOCX: Tika recurses, we extract the document's own text. The weakest DOCX, XLSX and PPT rows are each a handful of files of this kind. No extraction raised an untyped error (typed errors: 2 truncated XLSX, 1 PPT).

5. Calc: cached values reproduced

Every formula cell is remembered, the workbook recalculated in full, and the result compared with the cached value (numbers at 15 significant digits, text, booleans and errors exactly). Cells with no cached value, cells whose formula spells #REF! and results in it, and cells whose formula calls a function the registry lacks or another workbook are counted but not testable. The floor is matches / testable. Mismatch classes: precision = equal to 9 digits but not to 15; volatile = NOW/TODAY/RAND* itself; volatile-downstream = in a workbook that calls them; import-gap-ref = a legacy reference into another workbook that the converter leaves as #REF! (documented in docs/xls.md), and what depends on it; bug = the rest, grouped by workbook below.

SetWorkbooksFormulasTestableMatchFloorMismatches by class
Common Crawl XLSX354 intact (rerun after the fixes: identical)713,769701,782698,40199.52%bug 1,595 (10 workbooks), volatile-downstream 1,585, external 3,196, precision 165, volatile 31, unimplemented 21, downstream-or-bug 5
– the 11 Common Crawl workbooks that are truncated downloads (read by recovery since the fix in section 8)111,055,8581,045,776921,28988.10% (information)bug 92,957 (2 workbooks), volatile-downstream 29,984, unimplemented 83, precision 9, volatile 5, external 2
Bug-tracker XLSX702 (the 31 GB workbook included)1,701,0251,640,9841,526,41093.02% (was 88.46%)bug 20,224 (75 workbooks), precision 83,814 (80,128 in one workbook whose cached values have 10 digits), volatile-downstream 5,506, downstream-or-bug 3,849, external 291, volatile 460, import-gap-ref 148, unimplemented 4
LibreOffice-converted XLSX (from XLS)4532,238,3012,228,0782,089,80793.79%bug 79,957 (17 workbooks; 79,428 are the external-reference chains of 4 workbooks), precision 58,298, external 3,356, import-gap-ref 8, volatile 4
govdocs1 XLS (BIFF)450 (3 unreadable)2,234,3182,229,8772,121,46895.14%import-gap-ref 98,758 (5 workbooks), bug 3,005 (8), volatile-downstream 3,136, precision 2,195, downstream-or-bug 1,310, unimplemented 1,205 (XLM macro functions), volatile 5
Bug-tracker XLSB1883,84682,34781,97199.54%import-gap-ref 222, precision 123, volatile-downstream 28, volatile 3
ODS (LibreOffice-converted from XLSX)48 (1 unreadable)25,34610,7527,02165.30%bug 3,730 (4 workbooks: 3,704 are one workbook whose formulas contain #REF! and depend on it, cached #NAME? by LibreOffice, ours #REF!), unimplemented 3, volatile 1

What the bug column is made of, after the fixes below (the biggest first, from tools/gate/calc_causes.py):

  • References into other workbooks in legacy files (LibreOffice and XLS sets, ~79,000 and ~98,000 cells in 4–5 workbooks): the BIFF/LibreOffice conversion cannot express the reference, so the formula becomes #REF! and everything depending on it follows. The cached values are kept until recalculation. This is the documented limit (docs/xls.md, external workbook references); it is a conversion gap, not a calc-engine result.
  • SUBTOTAL(9, …) over a range (cached 120,401, ours 264,410 in one Common Crawl workbook): probably rows hidden by an autofilter, which the engine does not know; not traced.
  • Numerics: NORMINV/NORMSINV/NORMDIST differ from the cache in the 8th digit in files from the XLS era (577 + 134 cells), consistent with the older approximation Excel used then (not confirmed against an old Excel). precision cells (≤ 1e-9 relative) are cancellation noise in subtraction chains.
  • Bug-tracker XLSX, the 20,224 bug cells by workbook (the largest, from tools/gate/calc_causes.py):
    • 4,674 cells: VLOOKUP(x, mitglieder, 2, 0) and MAXIFS(…, #REF!, …) in the workbook that needed 31 GB. LibreOffice cached #NAME? for them, ours is #REF! (the #REF! argument is the answer in Excel). Not comparable.
    • 4,405: YEAR(DATEVALUE("01. Januar 2023")): German month names, which DATEVALUE reads only under a German locale; Excel under en-US gives #VALUE! too. 3,358 and 2,476: formulas through names or INDEX/MATCH over whole columns that LibreOffice cached as #NAME? or empty text; not traced.
    • 1,475 (INDEX(L2:L32, COUNT(L2:L32)), 312.75 against 303.68), 1,156 (an empty-text cache), 777, 499, 280: not traced. 499 of them are IF(XLOOKUP(B2, Data!G:G, Data!H:H) = 0, "", …) with an empty B2: the cache says "" (the lookup found a blank row), ours is #N/A; whether Excel matches an empty lookup value against empty cells is one of the Excel-grid questions at the Office run.
    • The rest are under 150 cells each (locale-dependent TEXT(B7, "ddd."), CELL("filename"), DATE with text years). Fixed since the first pass (their cells now match): SUMIFS and XLOOKUP over whole columns on another sheet (the clipped column was compared with the unclipped reference), ISFORMULA (the workbook never answered it), a table's name used alone as a range (VLOOKUP(2, Dow, 2)), and a mismatched pair of criteria ranges (answered #VALUE! after reading both).
  • GETPIVOTDATA (13 cells in one ODS) answered #REF! where the cached pivot has the value.
  • Cells whose formula is literally #REF! and whose result is an error are not compared (error-literal-formula: 2,457 / 4,543 / 3,018 / 1,854 / 1,499 cells in the Common Crawl, LibreOffice-converted, XLS, ODS and XLSB sets): another implementation's cached error (#NAME?) for a formula that spells #REF! says nothing about the engine.

Recalculation was run with one thread per shard. One workbook (a 114 KB bug-tracker file, 11,876 formulas: MAXIFS over ranges of different sizes, A2:ZW9960 against C2:C9960, 700 times) needed about 31 GB of resident memory before the fix in section 8; it now recalculates in 1.1 s at a 113 MB peak under a 4 GB heap limit. The LibreOffice-converted XLSX, govdocs1 XLS and XLSB rows reran with identical numbers; the ODS row was not rerun (its inputs were deleted with the other outputs).

6. Rendering: veraPDF 1.30.3

Tagged PDF written by LayoutProbe render (the same code path as Office.Convert), a deterministic sample per kind, each in the profile asked for; veraPDF is run with the matching flavour. When the profile cannot be met the writer raises render.failed with the violated rules instead of writing a file, so a PDF that exists is the one veraPDF sees.

Kind, profileSampledPDF writtenveraPDF compliantRefused by our own check
DOCX, PDF/UA-1251111 / 1113 (10 × 7.5 a table without header cells, 3 × 7.3 a figure without alternate text) and 1 not a package
XLSX, PDF/UA-1201111 / 118 (6 × 7.5 rows covering different column counts, 2 × 7.5 table without header cells) and 1 not a package
PPTX, PDF/UA-1191616 / 163 (7.3 figure without alternate text)
DOCX, PDF/A-2a252019 / 204 (6.7 figure without alternate text) and 1 not a package; the one failure is rule 6.2.11.7.3 (a character in the Private Use Area without ActualText), 9 checks in 1 file
PPTX, PDF/A-2a191615 / 163 (6.7); the one failure is the same rule 6.2.11.7.3, 4 checks in 1 file
DOCX / XLSX / PPTX, tagged with no profile, checked as PDF/UA-124 / 19 / 19all0not applicable: they fail 7.1 #8 (no pdfuaid in the catalog metadata) as expected without the profile; beyond that: 7.2 #42/#43 table rows of different widths in 4 DOCX and 6 + 3 XLSX, 7.3 #1 figure without alt text in 4 DOCX and 3 PPTX

So the profile either refuses a document it cannot make conformant (10 of 24 readable Word documents are refused for PDF/UA because a table has no header row; this is the known table-header item), or produces a PDF veraPDF passes, except for the Private Use Area ActualText case in PDF/A. Cause: a symbol-font bullet or label (U+F0xx) that SymbolMap has no Unicode twin for stays a private-use code point in the run text we hand to the display list (DrawGlyphRun takes the source text and the cluster map for exactly this purpose); the PDF writer then maps the glyph to that private-use code point in /ToUnicode and writes no /ActualText, which PDF/A-2 §6.2.11.7.3 requires. The change is in the PDF backend (write /ActualText for a text run whose text holds private-use characters, or map them to U+FFFD in /ToUnicode), not in the layout; widening SymbolMap would only hide the two files. The first run of this stage refused 22 of 23 DOCX because the probe set no title; that was the probe, fixed (Title = name).

7. Memory

GateInputResult
Lazy open, 1,000-page DOCXstress-1000p.docx, 55 partsopen 59 ms; 199 KiB read in 7 reads; 358 KiB allocated; 2 of 55 parts' content read; peak working set 37 MB
Lazy open, 500 MB packagegenerated, 503 MB, 39 partsopen 67 ms; 197 KiB read (0.038% of the file); 282 KiB allocated; 2 parts read; the 200 MB main part read only when asked (556 ms, then peak 241 MB)
Streaming read1,000,001 rows × 20 = 20,000,020 cells, 127 MB3.98 s; 52,656 bytes allocated in all, 0.0026 B/cell; working set 49 / 50 / 50 MB at 10k / 100k / 1M rows (41 MB before reading)
Streaming write1,000,000 × 20, 194 MB written2.27 s; 717 KB allocated in all (0.036 B/cell); working set 38 / 41 / 41 MB at 10k / 100k / 1M rows
Full model, bytes retained per cellthe same 20,000,020-cell workbookOneDocument 37.2, ClosedXML 78.8, NPOI 360.4 (743,967,768 / 1,575,651,240 / 7,208,587,912 bytes)

The timings printed by the probes are indicative only.

8. What the gate found and what was fixed

Fixed (each with a test):

  • ADDRESS(r, c, , , "Sheet"): an empty fourth argument was read as R1C1, not A1 (LookupFunctions.cs). 41,000 cells in one legacy workbook went from #REF! to Excel's values.
  • ROW(A2) in A2 (and COLUMN, ROWS, COLUMNS of a reference to the cell itself) counted as a circular reference and evaluated to 0 (DependencyWalker.cs): these functions read where a reference is, not its value.
  • A PPT/DOC whose edit-time field held a stray timestamp wrote TotalTime beyond xsd:int, so docProps/app.xml failed the schema (OleDocumentProperties.cs): reported as lost instead.
  • BIFF8 writer: a workbook with a style sheet that lists no fills or borders raised an untyped ArgumentOutOfRangeException from SaveAs (XlsStyleMap.cs; 2 of 51 Common Crawl workbooks).
  • ODT writer: style names that start with a digit, hyphen or full stop are not XML names (OdfConversionSupport.cs); ODS writer: scale-to written beside scale-to-X/Y for a fit-to-page sheet (OdsExporter.Features.cs); ODP writer: negative text insets as padding and connectors without svg:viewBox (OdpExporter.Shapes.cs, OdpExporter.Text.cs).
  • Probes: the calc sweep reads XLS, XLSB, ODS and classifies mismatches; the text and PDF stages set a title and register all kinds.

Fixed in the second pass (each with a test):

  • Memory of one calc input. The 114 KB workbook that needed ~31 GB held 700 MAXIFS formulas whose value range (C2:C9960) and criteria range (A2:ZW9960, seven million cells) differ in size, which Excel answers #VALUE!. The engine read both ranges before comparing them. The *IFS functions (SUMIFS, COUNTIFS, AVERAGEIFS, MAXIFS, MINIFS) now return an error argument in a range position first, then compare the references' sizes, and read only after that. Whatever else a formula reads, the blocks it has read are added up and a formula that passes MaxAllocationBytes (1 GiB by default) stops with calc.limit-exceeded located at the cell and shows #VALUE!; the run goes on. The workbook now recalculates in 1.1 s at 113 MB. (MathAggregates.cs, DescriptiveFunctions.cs, WorkbookResolver.cs, CalcEngine.cs.)
  • Whole-column ranges. SUMIFS(Valuation!D:D, Valuation!E:E, A5) answered #VALUE! and XLOOKUP(x, Data!G:G, Data!H:H) too: the lookup array was clipped to the used extent and the return array was not.
  • ISFORMULA always answered FALSE: the workbook resolver never implemented the question (WorkbookResolver.IsFormula); 32,000 cells.
  • A table's name used alone (VLOOKUP(2, Dow, 2)) was #NAME?; it now stands for the table's data rows, as Dow[#Data] does.
  • Document.Open, Workbook.Open, Presentation.Open recover a truncated package (section 1).
  • ODF writers (section 3): soft hyphen as U+00AD, a default header before a first-page or even-page one, an empty source row gets one empty cell, one number element per number style and merged adjacent text.

Open items (not fixed here):

  1. SUBTOTAL over rows hidden by an autofilter (the 120,401 against 264,410 workbook) was not traced; GETPIVOTDATA against a cached pivot (13 cells in one ODS) answers #REF!; external-workbook references in BIFF become #REF! (convert them to [n]Sheet!A1 with the cached values) — each stays an explained mismatch.
  2. XLSX single-cell edit: the schema error count rose in a few worksheets that were already invalid (above); the cause was not established.
  3. PDF/A-2a: the private-use ActualText rule in the PDF backend (section 6); the change belongs to OnePDF/OneRender's PDF writer.
  4. Text: no option yet to include numbers, booleans and error values of workbook cells for a Tika-like dump.
  5. XLOOKUP with an empty lookup value against whole columns: the cached value says it matches an empty cell, ours is #N/A; settled by the Excel grid at the Office run.
  6. ODS calc was not rerun after the second-pass fixes (its inputs were deleted with the other outputs); 65.30% stands from the first pass.