Repository navigation
Conversation
In the default stringMode "shared", plain text went to xl/sharedStrings.xml but a rich text cell was always written inline as t="inlineStr" with an <is> of runs. The file was valid, but tools that only read the shared string table never saw the rich cells, and it is not what Excel writes. The shared strings collector now has addRich(runs), keyed by the serialized <r> runs in a map of its own, so equal rich strings share one entry and a plain string with the same text stays separate. writeSharedStringsXml writes those entries as <si> holding the same <r> runs as the inline form, and serializeCell writes the cell as t="s" when stringMode is "shared". This also covers saveXlsx, which always uses shared strings. Inline mode is byte-identical. The reader already returned richText from <si> runs. Tests that pinned t="inlineStr" for the default mode now either expect the shared form or ask for inline mode explicitly. Closes productdevbook#602
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
Closes #602
What was wrong
With
stringMode: "shared", which is the default, plain text goes intoxl/sharedStrings.xml. A rich text cell was always written inline, whatever the mode:The old output is valid. But tools that only read the shared string table, such as template engines, find/replace and string indexing, never saw the rich cells. It also isn't what Excel writes. An
<si>can hold the same<r>runs as an inline<is>(ECMA-376 Part 1 §18.4.8, §18.4.4, §18.3.1.53).Why the tests didn't catch it
Several tests asserted the inline form for the default mode, so they pinned the behaviour:
coverage-xlsx-writer.test.ts(t="inlineStr"on a styled rich cell),rich-text-write.test.ts(the mixed plain/rich sheet),one-cell-serializer.test.ts(<is>in the sheet XML),xml-space-preserve.test.ts(looked for the rich run's<t>in the sheet XML) andbatch-fixes.test.ts. The round-trip tests passed either way, because the reader already turns both<is>and<si>runs intorichText.What landed
SharedStringsCollectorgetsaddRich(runs). The dedup key is the serialized<r>XML (serializeRichTextRuns), kept in a map of its own. Two cells with equal runs share one entry. A plain string with the same text, or the same text with different formatting, gets its own entry.writeSharedStringsXmlwrites a rich entry as<si>holding those runs.serializeCellwrites a rich cell ast="s"with the shared index whenstringModeis"shared". The style index is kept. The comment there cites Rich text cells ignorestringMode: always inline strings, even with"shared"#602.saveXlsxalways writes shared strings, so it now puts rich text in the table too. A rich cell read from an inline-only file moves into the table on save, which matches Excel.docs/PARITY.mddoesn't say where rich text is stored, so it is unchanged too.stringMode: "inline"(orinlineStrings: true) when the test is about the inline form's runs.Tests: a new
test/rich-text-shared-strings.test.tswith seven tests. Five of them fail againstmain: shared<si>plust="s", the style index, dedup apart from plain text, the shared string part being written when rich text is the only text, andsaveXlsx. The other two passed already: the read-back round trip, and the exact inline output. The four updated assertions in the existing tests also fail againstmain, nine failures in total.bun run testpasses on Node 24.22.0, with 241 files and 10,705 tests.Checked in Excel
In Excel for Mac, with A1 as "Bold red then normal" and A2 as plain text, written by
mainand by this branch. Both files open without a repair prompt and look the same: the first run is bold and red, the rest is plain. In the after file the rich text is insharedStrings.xmlas an<si>with two<r>runs, and A1 ist="s". In the before file it is an inline<is>. So Excel reads the new form as it reads the old one.Not verified
Summary by CodeRabbit