diff options
| author | Simen A. W. Olsen <hello@simenandre.no> | 2026-10-27 17:19:57 +0100 |
|---|---|---|
| committer | Simen A. W. Olsen <hello@simenandre.no> | 2026-10-27 17:19:57 +0100 |
| commit | 0571b30b6da27dc4c9e617403deab09b974ac641 (patch) | |
| tree | f3dfab18e22a48d06c64e563ba9bcf478c01a5bc /internal/xls/xls_test.go | |
| download | accounted-multicase-intgr-0571b30b6da27dc4c9e617403deab09b974ac641.tar.gz accounted-multicase-intgr-0571b30b6da27dc4c9e617403deab09b974ac641.zip | |
Diffstat (limited to 'internal/xls/xls_test.go')
| -rw-r--r-- | internal/xls/xls_test.go | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/internal/xls/xls_test.go b/internal/xls/xls_test.go new file mode 100644 index 0000000..71ed21a --- /dev/null +++ b/internal/xls/xls_test.go @@ -0,0 +1,99 @@ +package xls + +import ( + "encoding/binary" + "math" + "reflect" + "testing" +) + +func rkInt(n int32, div100 bool) uint32 { + rk := uint32(n)<<2 | 0x02 + if div100 { + rk |= 0x01 + } + return rk +} + +func TestDecodeRK(t *testing.T) { + tests := []struct { + rk uint32 + want float64 + }{ + {rkInt(1460, false), 1460}, + {rkInt(-677083, true), -6770.83}, + {rkInt(-1, false), -1}, + {rkInt(4670, true), 46.7}, + // Float form: the top 30 bits of an IEEE double. + {uint32(math.Float64bits(-11800) >> 32), -11800}, + } + for _, tt := range tests { + if got := decodeRK(tt.rk); math.Abs(got-tt.want) > 1e-9 { + t.Errorf("decodeRK(%#x) = %v, want %v", tt.rk, got, tt.want) + } + } +} + +type builder struct{ b []byte } + +func (w *builder) rec(typ uint16, data []byte) { + w.b = binary.LittleEndian.AppendUint16(w.b, typ) + w.b = binary.LittleEndian.AppendUint16(w.b, uint16(len(data))) + w.b = append(w.b, data...) +} + +func cellHead(row, col uint16) []byte { + b := binary.LittleEndian.AppendUint16(nil, row) + b = binary.LittleEndian.AppendUint16(b, col) + return binary.LittleEndian.AppendUint16(b, 0) // xf +} + +func TestParseWorkbook(t *testing.T) { + // SST with two strings; the second ("Bærum", stored as UTF-16) is split + // across a CONTINUE record, which restarts with its own option byte. + sst := binary.LittleEndian.AppendUint32(nil, 2) + sst = binary.LittleEndian.AppendUint32(sst, 2) + sst = append(sst, 5, 0, 0) + sst = append(sst, "Konto"...) + sst = append(sst, 5, 0, 1, 'B', 0, 0xE6, 0) + cont := []byte{0, 'r', 'u', 'm'} + + var sheet builder + sheet.rec(recBOF, make([]byte, 16)) + sheet.rec(recLabelSST, binary.LittleEndian.AppendUint32(cellHead(0, 0), 0)) + sheet.rec(recLabelSST, binary.LittleEndian.AppendUint32(cellHead(0, 1), 1)) + sheet.rec(recRK, binary.LittleEndian.AppendUint32(cellHead(1, 0), rkInt(1460, false))) + mulrk := binary.LittleEndian.AppendUint16(nil, 1) + mulrk = binary.LittleEndian.AppendUint16(mulrk, 1) + for _, v := range []uint32{rkInt(-677083, true), rkInt(5, false)} { + mulrk = binary.LittleEndian.AppendUint16(mulrk, 0) + mulrk = binary.LittleEndian.AppendUint32(mulrk, v) + } + mulrk = binary.LittleEndian.AppendUint16(mulrk, 2) + sheet.rec(recMulRK, mulrk) + sheet.rec(recNumber, binary.LittleEndian.AppendUint64(cellHead(2, 0), math.Float64bits(44587.125))) + sheet.rec(recEOF, nil) + + // The globals substream, with the BOUNDSHEET offset patched once its + // own length is known. + var globals builder + globals.rec(recBOF, make([]byte, 16)) + globals.rec(recBoundSheet, append(make([]byte, 6), 1, 0, 'S')) + globals.rec(recSST, sst) + globals.rec(recContinue, cont) + globals.rec(recEOF, nil) + binary.LittleEndian.PutUint32(globals.b[20+4:], uint32(len(globals.b))) + + grid, err := ParseWorkbook(append(globals.b, sheet.b...)) + if err != nil { + t.Fatal(err) + } + want := [][]string{ + {"Konto", "Bærum"}, + {"1460", "-6770.83", "5"}, + {"44587.125"}, + } + if !reflect.DeepEqual(grid, want) { + t.Fatalf("grid = %q, want %q", grid, want) + } +} |
