1package sheet23import (4 "bytes"5 "encoding/json"6 "errors"7 "fmt"8 "io"9 "strconv"10 "strings"1112 "src.rest/cat/012/internal/locale"13)1415// The reader streams the file (see scanner): cells go straight into16// their sheet as they are read, so opening a file allocates about what17// the workbook holds rather than a tree of the whole document. The18// other fields of the workbook and of each sheet are small; they are19// kept as written and decoded with encoding/json once the stream ends.20//21// Keys match fields as encoding/json matches them, case aside, and a22// field given twice takes its last value, cells adding up, except that23// the version and the list of sheets, which say where cells go, are24// refused twice once cells were read by them. Cells come25// after "version" in every file Write makes; any read before it (a file26// written by hand) are kept as written until the version is known.27type fileReader struct {28 sc *scanner29 w *Workbook30 version int31 // hasVersion is set once "version" is read, and streamed once cells32 // were read with it, which it may then no longer change.33 hasVersion, streamed bool34 listed bool // the list of sheets was read35 single *Sheet // versions 1 to 3: the sheet of the top-level cells36 pending []json.RawMessage // top-level cells read before the version, in order37 sheets []readSheet // version 4: the sheets read38 sheetsRaw json.RawMessage // "sheets" read before the version39}4041// readSheet is a sheet of a version 4 file, its cells read, and its42// other fields.43type readSheet struct {44 s *Sheet45 body fileSheet46}4748// field is a member of an object, its value as written.49type field struct {50 key string51 raw json.RawMessage52}5354func readBook(r io.Reader, trace any) (*Workbook, error) {55 rd := &fileReader{sc: newScanner(r), w: emptyBook()}56 f, err := rd.top()57 if err != nil {58 return nil, err59 }60 if f.Version < 1 || f.Version > fileVersion {61 return nil, fmt.Errorf("unsupported file version %d", f.Version)62 }63 if err := rd.late(); err != nil {64 return nil, err65 }66 w := rd.w67 w.trace = trace68 list := rd.sheets69 if f.Version < 4 {70 if rd.single == nil {71 rd.single = w.newSheet("")72 }73 list = []readSheet{{rd.single, f.fileSheet}}74 if list[0].body.Name == "" {75 list[0].body.Name = "Sheet1"76 }77 }78 if len(list) == 0 {79 return nil, errors.New("the file has no sheets")80 }81 for _, rs := range list {82 if err := w.checkName(nil, rs.body.Name); err != nil {83 return nil, fmt.Errorf("sheet %q: %w", rs.body.Name, err)84 }85 rs.s.name = rs.body.Name86 w.insert(rs.s, len(w.sheets))87 }88 if err := w.readNames(f.Names); err != nil {89 return nil, err90 }91 var old []oldRegion92 for _, rs := range list {93 if err := rs.s.read(rs.body, &old); err != nil {94 if len(list) > 1 {95 err = fmt.Errorf("sheet %s: %w", rs.body.Name, err)96 }97 return nil, err98 }99 }100 w.active = clampInt(f.Active, 0, len(w.sheets)-1)101 w.settleHidden()102 // Anything but "decimal" (say, a mode from a later build) computes in103 // binary, as the file would in a build without the setting.104 w.decimal = f.Arithmetic == "decimal"105 // A locale this build doesn't know follows the default.106 if l, ok := locale.Lookup(f.Locale); ok {107 w.locale = l108 }109 if err := w.readMacros(f.Macros); err != nil {110 return nil, err111 }112 w.macroOrigin = f.MacroOrigin113 w.convertOld(old)114 w.nb.changed = 0 // the outputs read are the file's115 w.RecalcAll()116 return w, nil117}118119// top reads the file's object, streaming the cells, and returns its120// other fields.121func (rd *fileReader) top() (fileFormat, error) {122 var f fileFormat123 if null, err := rd.sc.null(); err != nil || null {124 return f, err125 }126 var rest []field127 err := rd.sc.members(func(key string) error {128 switch {129 case strings.EqualFold(key, "version"):130 return rd.readVersion()131 case strings.EqualFold(key, "cells"):132 return rd.topCells()133 case strings.EqualFold(key, "sheets"):134 return rd.topSheets()135 }136 raw, err := rd.sc.raw()137 rest = append(rest, field{key, raw})138 return err139 })140 if err == nil {141 err = unmarshalFields(rest, &f)142 }143 f.Version = rd.version144 return f, err145}146147func (rd *fileReader) readVersion() error {148 raw, err := rd.sc.raw()149 if err != nil {150 return err151 }152 v := rd.version153 if err := json.Unmarshal(raw, &v); err != nil {154 return err155 }156 if rd.streamed && v != rd.version {157 return errTwice158 }159 rd.version, rd.hasVersion = v, true160 return nil161}162163// topCells reads the top-level cells: the only sheet's, before version 4.164func (rd *fileReader) topCells() error {165 if !rd.hasVersion || len(rd.pending) > 0 {166 raw, err := rd.sc.raw()167 rd.pending = append(rd.pending, raw)168 return err169 }170 if rd.version >= 4 {171 raw, err := rd.sc.raw()172 if err == nil {173 err = checkCells(raw)174 }175 return err176 }177 return rd.cells(rd.singleSheet(), rd.sc)178}179180func (rd *fileReader) singleSheet() *Sheet {181 if rd.single == nil {182 rd.single = rd.w.newSheet("")183 }184 return rd.single185}186187// checkCells checks cells a file of this version ignores, as decoding188// them into the file's fields would.189func checkCells(raw json.RawMessage) error {190 var m map[string]json.RawMessage191 return json.Unmarshal(raw, &m)192}193194// topSheets reads the list of sheets of version 4.195func (rd *fileReader) topSheets() error {196 if !rd.hasVersion || rd.version < 4 {197 raw, err := rd.sc.raw()198 if err != nil {199 return err200 }201 if !rd.hasVersion {202 if rd.sheetsRaw != nil {203 return errTwice204 }205 rd.sheetsRaw = raw206 return nil207 }208 var ignored []fileSheet209 return json.Unmarshal(raw, &ignored)210 }211 return rd.readSheets(rd.sc)212}213214// errTwice is returned for a version or a list of sheets given twice215// once cells were read by the first.216var errTwice = errors.New("the file gives its version or its sheets twice")217218// readSheets reads a list of sheets from sc.219func (rd *fileReader) readSheets(sc *scanner) error {220 if rd.listed {221 return errTwice222 }223 rd.listed, rd.sheetsRaw = true, nil224 if null, err := sc.null(); err != nil || null {225 return err226 }227 return sc.elements(func() error {228 s := rd.w.newSheet("")229 var rest []field230 if null, err := sc.null(); err != nil || null {231 rd.sheets = append(rd.sheets, readSheet{s: s})232 return err233 }234 name := ""235 err := sc.members(func(key string) error {236 if strings.EqualFold(key, "cells") {237 err := rd.cells(s, sc)238 if err != nil && name != "" {239 err = fmt.Errorf("sheet %s: %w", name, err)240 }241 return err242 }243 raw, err := sc.raw()244 if strings.EqualFold(key, "name") {245 json.Unmarshal(raw, &name) // for errors only: the body decodes it246 }247 rest = append(rest, field{key, raw})248 return err249 })250 var body fileSheet251 if err == nil {252 err = unmarshalFields(rest, &body)253 }254 rd.sheets = append(rd.sheets, readSheet{s, body})255 return err256 })257}258259// late reads what came before the version, now that it is known.260func (rd *fileReader) late() error {261 for _, raw := range rd.pending {262 if rd.version >= 4 {263 if err := checkCells(raw); err != nil {264 return err265 }266 continue267 }268 if err := rd.cells(rd.singleSheet(), newScanner(bytes.NewReader(raw))); err != nil {269 return err270 }271 }272 if rd.sheetsRaw == nil {273 return nil274 }275 if rd.version < 4 {276 var ignored []fileSheet277 return json.Unmarshal(rd.sheetsRaw, &ignored)278 }279 return rd.readSheets(newScanner(bytes.NewReader(rd.sheetsRaw)))280}281282// cells reads a sheet's cells from sc into s. Version 1 had no formats,283// so there entries imply them, as if typed again; since version 2 the284// stored format wins.285func (rd *fileReader) cells(s *Sheet, sc *scanner) error {286 rd.streamed = true287 if null, err := sc.null(); err != nil || null {288 return err289 }290 implied := rd.version < 2291 return sc.fields(func(key []byte, plain bool) error {292 a, name, err := cellKey(key, plain)293 if err != nil {294 return err295 }296 if err := s.readCell(sc, a, implied); err != nil {297 if name == "" {298 name = a.String()299 }300 return fmt.Errorf("%s: %w", name, err)301 }302 return nil303 })304}305306// cellKey is the cell a key of "cells" names, and its name when it isn't307// a's (for errors). A plain key is parsed from its bytes, which are308// valid only until the next read, without making a string of it.309func cellKey(key []byte, plain bool) (Addr, string, error) {310 if plain {311 a, ok := ParseAddr(string(key))312 if !ok {313 return a, "", fmt.Errorf("invalid cell %q", key)314 }315 return a, "", nil316 }317 var name string318 if err := json.Unmarshal(quote(key), &name); err != nil {319 return Addr{}, "", err320 }321 a, ok := ParseAddr(name)322 if !ok {323 return a, "", fmt.Errorf("invalid cell %q", name)324 }325 return a, name, nil326}327328// readCell reads the cell at a from sc: a string for an entry alone, an329// object for one with formatting or a note (see fileFormat).330func (s *Sheet) readCell(sc *scanner, a Addr, implied bool) error {331 c, err := sc.peek()332 if err != nil {333 return err334 }335 var e fileEntry336 if c == '"' {337 body, plain, err := sc.text()338 if err != nil {339 return err340 }341 if plain && s.loadNum(a, body, Format{}, Style{}) {342 return nil343 }344 if e.input, err = unquote(body, plain); err != nil {345 return err346 }347 } else {348 raw, err := sc.raw()349 if err != nil {350 return err351 }352 if e, err = decodeNoted(raw); err != nil {353 return err354 }355 if e.note == "" && e.text == nil && s.loadNum(a, []byte(e.input), e.f, e.st) {356 return nil357 }358 }359 cell, err := e.cell(implied)360 if err != nil {361 return err362 }363 // An empty entry leaves no cell, even where the key came before.364 if cell != nil || s.cells.has(a) {365 s.place(a, cell)366 }367 return nil368}369370// loadNum stores a number typed plainly ("12.5", "-3", "0.50", the most371// common entry by far) at a, from a file, straight into its slot: what372// newCell and place would store, without making a Cell or a string. It373// reports false for any other entry, a cell a holds with contents, or a374// style that wraps or draws borders (place indexes those by row), all375// of which go the general way. A sheet being read has no undo step,376// pivot or spill yet for place to see to.377func (s *Sheet) loadNum(a Addr, input []byte, f Format, st Style) bool {378 if f.Kind == FmtText || st.shapes() || s.cells.richAt(a) != nil {379 return false380 }381 d, ok := plainForm(string(input))382 if !ok {383 return false384 }385 v, err := strconv.ParseFloat(string(input), 64)386 if err != nil {387 return false388 }389 lk, ok := s.cells.lookID(look{f: f, st: st})390 if !ok {391 return false392 }393 s.version++394 s.cells.setSlot(a, slot{kind: slotNum, num: v, dec: d, look: lk})395 return true396}397398// unmarshalFields decodes an object's fields, as written, into v.399func unmarshalFields(fields []field, v any) error {400 if len(fields) == 0 {401 return nil402 }403 var b bytes.Buffer404 b.WriteByte('{')405 for i, f := range fields {406 if i > 0 {407 b.WriteByte(',')408 }409 b.WriteString(jsonString(f.key))410 b.WriteByte(':')411 b.Write(f.raw)412 }413 b.WriteByte('}')414 return json.Unmarshal(b.Bytes(), v)415}