Preserve cell formatting through decode→encode round-trip - #449
Open
ben-milanko wants to merge 1 commit into
Open
ben-milanko wants to merge 1 commit into
ben-milanko wants to merge 1 commit into
Conversation
When loading a styled .xlsx, mutating one cell, and re-encoding, the package was silently rewriting the styles table and dropping every untouched cell's font/fill/border/alignment. Four compounding bugs caused this: 1. `_putData` overwrote `cell._cellStyle` with a fresh `CellStyle(numberFormat: defaultFor(value))` on every value write, wiping the existing visual style. 2. The follow-up guard `cell._cellStyle != NumFormat.standard_0` compared a `CellStyle?` to a `NumFormat` (different types — always true), so `_styleChanges` flipped on every write and triggered a styles-table rebuild that re-numbered every cell's `<c s="N">` index. 3. The parser drives every cell through `Sheet.updateCell` while loading the file, so `_styleChanges`/`_mergeChanges`/`_rtlChanges` were already true after a no-op decode. Reset them at the end of `Excel._` so those flags only reflect post-decode user mutations. 4. `StandardNumericNumFormat.accepts(TextCellValue())` returned true only for numFmtId 0 (General), not 49 (`@`/text). Writing text into a text-formatted column was therefore deemed format-incompatible and triggered a numberFormat overwrite. Replaces the unconditional cellStyle reassignment in `_putData` with a targeted update that: - assigns a value-appropriate default only when the cell had no style; - promotes General to a value-specific format (preserving the test contract that writing a Double into a General cell yields 0.00); - resets numberFormat to General when value is null; - patches incompatible explicit formats (e.g. date written into currency cell) but leaves font/fill/border/alignment intact via `copyWith`. Adds two regression tests using `borders.xlsx` that assert font weight, borders, fill, and font family on an A1 header survive both an unrelated-cell write and a same-cell value overwrite. End-to-end verification with a real Numbers-exported template (~95 cellXfs, dataValidations, conditionalFormatting): styles.xml is now byte-identical after a value write that previously rebuilt the table into 117 cellXfs, and every cell's `<c s="N">` index is preserved. All 36 existing tests still pass.
|
hello @ben-milanko I created a fork of Flutter Excel. The name is excel_community you can find it here link |
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.
Problem
Loading a styled
.xlsx, mutating one cell, then re-encoding silently rebuilds the styles table and drops every untouched cell's font / fill / border / alignment. End-to-end on a real Numbers-exported template (~95cellXfs, dataValidations, conditionalFormatting),styles.xmlgrows to 117 cellXfs (22 genericborderId="0" fillId="0" fontId="0"entries appended) and every cell's<c s="N">index is remapped to point into the new table — so headers render as plain text, dropdowns vanish visually, etc.Root cause
Four compounding bugs:
Sheet._putDataclobbers existing styles. Every value write unconditionally didcell._cellStyle = CellStyle(numberFormat: NumFormat.defaultFor(value)), wiping the cell's font/fill/border/alignment.if (cell._cellStyle != NumFormat.standard_0)compares aCellStyle?to aNumFormat(different types — always true), so_styleChangesflipped on every value write and triggered a styles-table rebuild.Sheet.updateCellwhile loading the file, so_styleChanges/_mergeChanges/_rtlChangeswere alreadytrueafter a no-op decode. Those flags should reflect post-decode user mutations, not the act of decoding itself.StandardNumericNumFormat.accepts(TextCellValue())returned true only fornumFmtId == 0(General), not49(@/text). Writing text into a text-formatted column was therefore deemed format-incompatible and triggered a numberFormat overwrite.Fix
Targeted, minimal — preserves the existing public contract (writing a
Doubleinto aGeneralcell still upgrades todefaultFloat; writingnullresets toGeneral):Sheet._putDatakeeps the existingCellStyleand only patchesnumberFormatviacopyWith. PromotesGeneralto a value-specific default; resets toGeneralonnull; otherwise leaves explicit formats alone unlessaccepts(value)isfalse.Excel._resets_styleChanges/_mergeChanges/_rtlChangesafterparser._startParsing()so a no-op decode→encode skips the rebuild paths.StandardNumericNumFormat.accepts(TextCellValue())accepts bothnumFmtId == 0andnumFmtId == 49.Tests
All 34 existing tests still pass. Two new tests in
Style preservation on round-tripcover the regression onborders.xlsx:writing into one cell keeps an untouched cell's style intact— writesZ50, assertsA1's bold + medium-border header survives.overwriting a styled cell's value preserves font/fill/border— writes a new value intoA1, asserts every visual attribute is intact after round-trip.Verification on a real template
On a Numbers-exported template with the bulk-import write pattern (5 text writes into the valueList sheet + setColumnAutoFit on 11 columns):
<fonts count><fills count><cellXfs count>styles.xmldiff vs originalA1<c s="N">s="76"(was 60)s="60"✓<dataValidations><conditionalFormatting>