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) } }