Repository navigation
fix(sheet): stop padding no-model rows one column past a supplied head - #1196
Sean-Walker0 wants to merge 1 commit into
Conversation
7745b09 to
3e5ce44
Compare
Mikkey-f
left a comment
There was a problem hiding this comment.
I went through this end to end and reproduced both sides locally — the analysis holds up.
Root cause, confirmed in source
calculateHeadSize really is answering two different questions, and the single bound papered over it:
- supplied head →
headMap.size()is a column count:ExcelHeadPropertyinserts contiguous keys0..n-1for the supplied list; - no head →
maxNotEmptyDataHeadSizeis the last non-empty column index of the final head row:DefaultAnalysisEventProcessor.buildHeadsets it per non-empty cell (last write wins on an ordered cell map).
<= is correct for an index and off by one for a count. Normalizing both branches to counts and keeping < is the right shape, and it keeps the fastexcel#220 fix intact.
Independent verification (JDK 17, Windows)
- Red: with only the product file reverted to current main (
bf9bf923) and your tests kept,NoModelHeadSizeTestfails exactly as you describe — 2 of 3,expected: <4> but was: <5>on both explicit-head cases; the headless pinning case passes on both sides. - Green: on your head all 3 pass.
- fastexcel#220 guard:
MaxHeadSizeTest(all three fixtures, including the "fewer head columns than data columns" one) passes 3/3 — headless padding is unchanged. - Full suite, CI-equivalent command:
mvn clean package -Dmaven.test.skip=false -pl fesod-common,fesod-shaded,fesod-sheet→ 60 + 1047 tests, 0 failures.spotless:checkclean.
The disclosed empty-row change, checked
I built a file with POI (empty first row, data, then a truly empty row) and read it with ignoreEmptyRow(false): the empty row now returns {} where main returned {0=null}; with an explicit head it returns exactly head-width nulls (main returned width + 1). Both fall out of the same fix as intended. As you say, it only surfaces with ignoreEmptyRow(false) — RowTagHandler marks all-empty rows RowTypeEnum.EMPTY and endRow drops them while the flag is true (the default) — so one small test would pin it if you want it locked in, but it's optional.
Two small nits
- The new javadoc says "the widest head row answers with its last column index" — the value actually comes from the last head row only (
buildHeadruns a single time, whenrowIndex + 1 == headRowNumber); for a multi-row head the two can differ, so "last head row" would be exact. - Tiny description nit: in Notes, #1128's
converterMap()→converterRegistry()rename is insidebuildNoModelitself (line 86), not "other methods" of the file. No line overlap with your change either way — just a trivial rebase for whoever lands second.
Heads-up that the three workflow runs on this PR are still action_required (the fork approval gate), so CI hasn't executed yet; the numbers above come from rerunning the CI-equivalent command locally.
LGTM from my side — minimal change, both documented behaviors preserved, tests pin the exact fix.
3e5ce44 to
1ca27ea
Compare
|
Thanks for the thorough independent verification — all three points addressed and force-pushed as 1ca27ea:
|
|
Thanks @Sean-Walker0 — checked 1ca27ea and all three points look addressed; the pinned |
1ca27ea to
1265e51
Compare
buildNoModel pads each no-model row with null cells up to the head size, but the loop bound was inclusive while calculateHeadSize answers a column count when a head is supplied. Every row of a no-model read with an explicit head therefore carried one phantom trailing null column: a four-column head produced five-entry maps (keys 0..4). The loop originally used an exclusive bound, which was later switched to an inclusive one for the headless branch, where calculateHeadSize answers the last column index of the widest head row instead of a count (fix for apache#220, commit 8ad28c2) - and that switch re-broke the explicit-head case the exclusive bound had fixed. Make calculateHeadSize answer a column count in both branches and use an exclusive bound. The explicit-head padding is fixed, while headless rows keep being padded through the widest head column. A side effect is that a headless row with no cells at all no longer reports a single phantom null column. Verified with NoModelHeadSizeTest (red before the change, green after) plus the full fesod-sheet suite (919 tests) and spotless:check.
1265e51 to
9222d90
Compare
Purpose of the pull request
No-model reads (
doReadSync()returningMap<Integer, String>rows) that supply an explicit.head(...)return one phantom trailingnullcolumn on every row. A four-column head produces five-entry maps (keys0..4); a short data row is padded to five entries as well. Any consumer that sizes output from the map (row.size(),values().toArray(), re-writing rows as arrays) gets a wrong-width row.No issue exists for this yet; found by code audit while reviewing
buildNoModel's padding loop.What's changed?
ModelBuildEventListener.buildNoModelpads rows withwhile (index <= headSize), butcalculateHeadSizeanswers a column count when a head is supplied, versus the last column index of the widest head row when no head is supplied:0..3), the loop leavesindex == 4and4 <= 4stores one extranullunder key4;8ad28c2), wherecalculateHeadSizereturns an index — and it re-broke the explicit-head case that the loop's original exclusive bound had fixed.The change makes
calculateHeadSizeanswer a column count in both branches (maxNotEmptyDataHeadSize + 1for the headless case) and uses an exclusive<bound. Explicit-head rows are now exactly head-wide; headless padding through the widest head column is unchanged.Behavior notes:
nullcolumn — same defect family, disclosed here for review;NoModelHeadSizeTestcovers both directions: exact width for explicit heads (full and short rows, no key4) and unchanged headless padding to the widest head row.Checklist
Verifying this change
NoModelHeadSizeTestfails on unpatched code (expected: <4> but was: <5>on both explicit-head cases) and passes after the fix; the headless-pinning case passes before and after../mvnw clean package -B -Dmaven.test.skip=false -pl fesod-common,fesod-shaded,fesod-sheet: 919 tests, 0 failures, 0 errors../mvnw -pl fesod-sheet spotless:check: clean.Notes
ModelBuildEventListener; refactor: decouple read/write responsibilities and introduce ColumnBinding #1128 renamesconverterMap()->converterRegistry()insidebuildNoModelitself (a different statement from the loop changed here, so no line overlap either way).8ad28c2) repaired headless padding but reintroduced the explicit-head phantom column; this change keeps both behaviors correct.