Skip to content

Commit 7bee15d

Browse files
donislawdevclaude
andcommitted
format: an xlsx sheet can be wider than a spreadsheet will open
xlsx columns stopped at 64, with the reason "the width a person would actually look at". That describes a document somebody reads, and this tool writes fixtures somebody tests with - a ceiling belongs to the reader under test. At 64 there was no way to build a sheet that asks Excel about its own limit at all, while csv answered the same question about the same reader with 32768 and nothing compared the two. Measured 2026-09-08 with LibreOffice Calc 26.2.5.2 headless, on workbooks built outside this tool because this tool could not build them: 16384 columns come back whole, 16385 come back as 16384 with the last column dropped, exit 0 and not one word on either stream. After the change our own 16385 column workbook behaves identically, and hits the requested size to the byte. The encoder needed no change - column() counts letters in base 26, so it walks past XFD on its own. rows times columns still cannot pass 2 million cells, MinBytes is computed from a one by one sheet so it does not move, and a sheet of 64 columns or fewer is byte for byte what it was. The site is regenerated for the two lines that name the range. The guard asks every registered format that declares columns, so a third tabular format is covered on the day it arrives. It asks about the OFFER rather than building the file, because building one costs twelve megabytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 89be0a6 commit 7bee15d

5 files changed

Lines changed: 114 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,21 @@ because it turns other people's test suites red.
246246

247247
### Changed
248248

249+
- **A spreadsheet can now be built wider than a spreadsheet can open.** The
250+
`columns` setting of `xlsx` used to stop at 64. It now reaches 32768, which is
251+
the ceiling `csv` already had.
252+
253+
The number matters because of where a reader stops. Excel and LibreOffice
254+
Calc both hold 16384 columns. Measured with Calc: a sheet of 16384 columns
255+
opens whole, and a sheet of 16385 opens as 16384 - the last column is dropped
256+
and nothing is said about it. At a ceiling of 64 there was no way to build a
257+
file that asks a spreadsheet about its own limit, which is the kind of file
258+
this tool exists to produce.
259+
260+
`rows` times `columns` still cannot pass 2 million cells, so a sheet 16385
261+
columns wide holds up to 122 rows. Nothing about a sheet of 64 columns or
262+
fewer changes, and the default is still one column.
263+
249264
- **Byte counts are grouped in threes.** A total used to print as
250265
`2516582400 B`. It now prints as `2 516 582 400 B`, in every message that
251266
names a number of bytes - `tfg formats`, the summary a run prints, what a

‎internal/format/xlsx/xlsx.go‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,23 @@ const (
3939
maxRows = 200_000
4040

4141
minColumns = 1
42-
// Sixteen thousand three hundred and eighty four is the format's own limit.
43-
// This one is the width a person would actually look at.
44-
maxColumns = 64
42+
// maxColumns is deliberately ABOVE the width of a spreadsheet, and that is
43+
// the whole reason for the number. It is the same ceiling CSV carries, for
44+
// the same question asked of the same reader.
45+
//
46+
// It said 64 until 2026-09-08, with the reason "the width a person would
47+
// actually look at". That reason describes a document somebody reads, and
48+
// this tool writes fixtures somebody tests with - a ceiling belongs to the
49+
// reader under test, not to our own comfort. At 64 there was no way to
50+
// build a sheet that asks Excel about its own limit at all.
51+
//
52+
// Measured 2026-09-08 with LibreOffice Calc 26.2.5.2 headless, on
53+
// workbooks built outside this tool because this tool could not build
54+
// them: 16384 columns come back whole, and 16385 come back as 16384 with
55+
// the last column dropped, exit 0 and not one word on either stream. That
56+
// silent loss is the thing a tester needs a fixture for, and standing on
57+
// both sides of the line is what a boundary set is.
58+
maxColumns = 32768
4559

4660
// The sheet is held in memory while the package is built, so the pair is
4761
// bounded as well as each side.
@@ -75,7 +89,7 @@ func init() {
7589
Name: "columns", Kind: format.PropertyInt,
7690
Min: minColumns, Max: maxColumns, Unit: "columns",
7791
Default: "1",
78-
Detail: "How many columns each row has.",
92+
Detail: "How many columns each row has. Above 16384 a spreadsheet may show only the first 16384 and drop the rest without a word.",
7993
},
8094
},
8195
JointLimits: []format.JointLimit{{
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package guard
2+
3+
import (
4+
"testing"
5+
6+
"github.com/donislawdev/TestingFilesGenerator/internal/format"
7+
_ "github.com/donislawdev/TestingFilesGenerator/internal/format/all"
8+
)
9+
10+
// spreadsheetWidth is where a spreadsheet stops, measured rather than
11+
// remembered: 2026-09-03 for CSV and again 2026-09-08 for XLSX, both with
12+
// LibreOffice Calc 26.2.5.2 headless. A table of this many columns comes back
13+
// whole. One column more comes back with this many, the last column dropped,
14+
// exit 0 and not one word on either stream.
15+
const spreadsheetWidth = 16384
16+
17+
// narrowerOnPurpose names a format whose columns cannot reach past a
18+
// spreadsheet because the format itself stops first, with the reason.
19+
//
20+
// It is empty, and that is the point rather than an oversight. A format ends up
21+
// here only when its OWN structure caps it - the way an icon stores each side
22+
// in a single byte - and never because a smaller number felt tidier. Writing
23+
// the reason down is the price of the exception, which is what stops this
24+
// becoming the sort of list somebody adds to instead of thinking.
25+
var narrowerOnPurpose = map[string]string{}
26+
27+
// A format with columns has to offer more of them than a spreadsheet accepts.
28+
//
29+
// The ceiling of a setting belongs to the reader under test, not to what this
30+
// tool finds comfortable to write. Standing on both sides of a limit is what a
31+
// boundary set is for, so a ceiling that stops at the limit offers the last
32+
// table that survives and never the first that does not.
33+
//
34+
// This is not hypothetical tidiness, it is a defect this project shipped.
35+
// XLSX declared 64 from the day it was written until 2026-09-08, with the
36+
// reason "the width a person would actually look at" - which describes a
37+
// document somebody reads rather than a fixture somebody tests with. CSV asked
38+
// the same question of the same reader and answered 32768. Nothing compared
39+
// them, and at 64 there was no way to build a sheet that asks Excel about its
40+
// own limit at all.
41+
//
42+
// What it asks about is the OFFER, because that is what a ceiling is. That the
43+
// file itself works was measured on 2026-09-08 rather than asserted here: a
44+
// workbook of 16385 columns built by this tool converts through Calc at exit 0
45+
// and comes back with 16384, which is the same silent loss a workbook from
46+
// another writer produces. Building one costs twelve megabytes, which is not a
47+
// price worth paying on every run of this suite.
48+
//
49+
// Asked of every registered format rather than of the two that have the
50+
// setting today, so a third tabular format is covered on the day it arrives.
51+
func TestAFormatWithColumnsReachesPastWhatASpreadsheetAccepts(t *testing.T) {
52+
examined := 0
53+
54+
for _, d := range format.All() {
55+
for _, p := range d.Properties {
56+
if p.Name != "columns" || p.Kind != format.PropertyInt {
57+
continue
58+
}
59+
if why, narrow := narrowerOnPurpose[d.ID]; narrow {
60+
if p.Max > spreadsheetWidth {
61+
t.Errorf("%s is excused as narrower on purpose (%s) and reaches %d anyway - take the excuse off",
62+
d.ID, why, p.Max)
63+
}
64+
continue
65+
}
66+
examined++
67+
if p.Max <= spreadsheetWidth {
68+
t.Errorf("%s offers at most %d columns and a spreadsheet accepts %d, "+
69+
"so no fixture built from it can ask the reader about its own limit - "+
70+
"raise the ceiling, or name the structural reason in narrowerOnPurpose",
71+
d.ID, p.Max, spreadsheetWidth)
72+
}
73+
}
74+
}
75+
76+
if examined == 0 {
77+
t.Fatal("no format offers a column count, so this proved nothing")
78+
}
79+
}

‎web/public/formats/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ <h2>Settings each format accepts</h2>
623623
<tr>
624624
<td></td>
625625
<td><code>columns</code></td>
626-
<td>1 - 64 columns</td>
626+
<td>1 - 32768 columns</td>
627627
</tr>
628628
<tr>
629629
<td><code>xml</code></td>

‎web/public/pl/formaty/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ <h2>Ustawienia, które przyjmuje każdy format</h2>
623623
<tr>
624624
<td></td>
625625
<td><code>columns</code></td>
626-
<td>1 - 64 kolumn</td>
626+
<td>1 - 32768 kolumn</td>
627627
</tr>
628628
<tr>
629629
<td><code>xml</code></td>

0 commit comments

Comments
 (0)