cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

XLSX: layout reading and writing split to keep functions simple

Dami committed 90892b2
The styles reader checks a list's length once for every list, and the
snapshot, the row import and the excelize reference importer hand their
heights, merges and kept cells to helpers.
commit
90892b2700b0c9094442fc786ca6c32465b5c2f0
parent
e80deb0d8e419ed036d6848cf68fa809369ccdf9
author
Dami <dami.etoile@gmail.com>
kind
refactor risk score 1.2 of 3, Jev's estimate
4 files changed, +80 -50
Minternal/fileio/snapshot.go35+++++++++++++--------
Minternal/fileio/xlsx_excelize_test.go51+++++++++++++++++++++---------
Minternal/fileio/xlsximport.go19++++++++----
Minternal/fileio/xlsxstyles.go25++++++---------
M internal/fileio/snapshot.go +22 -13
@@ -99,19 +99,7 @@ func Snap(s *sheet.Sheet, r sheet.Rect, name string) *Snapshot {
9999 ColFormats: snapLines(s, false), RowFormats: snapLines(s, true), Filter: s.Filter(),100100 CondFormats: s.CondFormats(), Validations: s.Validations()}101101 snap.FrozenRows, snap.FrozenCols = s.Frozen()102- for row, h := range s.Heights() {103- if row >= r.From.Row && row <= r.To.Row {104- if snap.Heights == nil {105- snap.Heights = map[int]int{}106- }107- snap.Heights[row] = h108- }109- }110- for _, m := range s.Merges() {111- if r.Contains(m.From) && r.Contains(m.To) {112- snap.Merges = append(snap.Merges, m)113- }114- }102+ snap.Heights, snap.Merges = heightsMergesIn(s, r)115103 for _, a := range s.NotesIn(notes) {116104 if snap.Notes == nil {117105 snap.Notes = map[sheet.Addr]string{}
@@ -150,6 +138,27 @@ func Snap(s *sheet.Sheet, r sheet.Rect, name string) *Snapshot {
150138 return snap151139 }152140 141+// heightsMergesIn are the heights of the rows of r and the merges inside142+// it.143+func heightsMergesIn(s *sheet.Sheet, r sheet.Rect) (map[int]int, []sheet.Rect) {144+ var heights map[int]int145+ for row, h := range s.Heights() {146+ if row >= r.From.Row && row <= r.To.Row {147+ if heights == nil {148+ heights = map[int]int{}149+ }150+ heights[row] = h151+ }152+ }153+ var merges []sheet.Rect154+ for _, m := range s.Merges() {155+ if r.Contains(m.From) && r.Contains(m.To) {156+ merges = append(merges, m)157+ }158+ }159+ return heights, merges160+}161+153162 // snapLayout adds to a whole sheet's snapshot what its layout needs in154163 // a workbook, growing the range to hold it: rows with heights, merged155164 // cells, and blank cells that draw borders, which count as contents
M internal/fileio/xlsx_excelize_test.go +35 -16
@@ -75,11 +75,7 @@ func importXLSXExcelize(ctx context.Context, name string, opt Options) (*Result,
7575 if err != nil {7676 return nil, err7777 }78- for i, hs := range heights {79- for row, h := range hs {80- book.Sheet(i).LoadRowHeight(row, h)81- }82- }78+ loadHeights(book, heights)8379 notes = append(notes, excelizeNames(x, book)...)8480 notes = append(notes, (&xlsxBook{protected: protected}).protectionNote()...)8581 active := book.Sheet(clamp(x.GetActiveSheetIndex(), 0, book.Len()-1))
@@ -95,6 +91,15 @@ func importXLSXExcelize(ctx context.Context, name string, opt Options) (*Result,
9591 return &Result{Sheet: s, Rows: done, Notes: append(notes, applyFilters(book, filters)...)}, nil9692 }9793 94+// loadHeights gives each sheet of book its rows' heights.95+func loadHeights(book *sheet.Workbook, heights []map[int]int) {96+ for i, hs := range heights {97+ for row, h := range hs {98+ book.Sheet(i).LoadRowHeight(row, h)99+ }100+ }101+}102+98103 // excelizeView freezes s as sheet ws is frozen and loads its notes.99104 func excelizeView(x *excelize.File, ws string, s *sheet.Sheet) error {100105 if p, err := x.GetPanes(ws); err == nil && p.Freeze {
@@ -265,11 +270,7 @@ func excelizeSheet(ctx context.Context, x *excelize.File, b *builder, ws string,
265270 return 0, err266271 }267272 defer rows.Close()268- dimCols := 0269- if dim, err := x.GetSheetDimension(ws); err == nil {270- _, to, _ := strings.Cut(dim, ":")271- dimCols, _, _ = excelize.CellNameToCoordinates(cmp.Or(to, dim))272- }273+ dimCols := excelizeDimCols(x, ws)273274 row, width := 0, 0274275 for ; rows.Next(); row++ {275276 if row%128 == 0 {
@@ -315,14 +316,32 @@ func excelizeSheet(ctx context.Context, x *excelize.File, b *builder, ws string,
315316 }316317 b.s.LoadColWidth(c, max(int(math.Round(w))+excelPadding, 1))317318 }318- if merges, err := x.GetMergeCells(ws, true); err == nil {319- for _, mc := range merges {320- if r, ok := sheet.ParseRange(mc.GetStartAxis() + ":" + mc.GetEndAxis()); ok {321- b.s.LoadMerge(r)322- }319+ excelizeMerges(x, ws, b.s)320+ return row, excelizeView(x, ws, b.s)321+}322+323+// excelizeDimCols is the last column of sheet ws's dimension.324+func excelizeDimCols(x *excelize.File, ws string) int {325+ dim, err := x.GetSheetDimension(ws)326+ if err != nil {327+ return 0328+ }329+ _, to, _ := strings.Cut(dim, ":")330+ cols, _, _ := excelize.CellNameToCoordinates(cmp.Or(to, dim))331+ return cols332+}333+334+// excelizeMerges merges the cells sheet ws merges.335+func excelizeMerges(x *excelize.File, ws string, s *sheet.Sheet) {336+ merges, err := x.GetMergeCells(ws, true)337+ if err != nil {338+ return339+ }340+ for _, mc := range merges {341+ if r, ok := sheet.ParseRange(mc.GetStartAxis() + ":" + mc.GetEndAxis()); ok {342+ s.LoadMerge(r)323343 }324344 }325- return row, excelizeView(x, ws, b.s)326345 }327346 328347 // excelizeNames defines the workbook's named ranges that are a range on
M internal/fileio/xlsximport.go +13 -6
M internal/fileio/xlsxstyles.go +10 -15
@@ -126,50 +126,45 @@ func (s *xlsxStyles) read(p *xlsxPackage, part string) error {
126126 }127127 }128128 129+// styleLists name what each list of the styles part holds, by section130+// and element, for the error when one is too long.131+var styleLists = map[[2]string]string{132+ {"dxfs", "dxf"}: "styles", {"numFmts", "numFmt"}: "styles", {"fonts", "font"}: "fonts",133+ {"borders", "border"}: "borders", {"cellXfs", "xf"}: "cell formats",134+}135+129136 // element reads one element inside a section of the styles part.130137 func (s *xlsxStyles) element(x *xmlStream, se xml.StartElement, section string, cur *xlsxXf) (*xlsxXf, error) {131138 full := len(s.numFmts) >= x.g.p.lim.styles || len(s.fonts) >= x.g.p.lim.styles || len(s.xfs) >= x.g.p.lim.styles ||132139 len(s.dxfs) >= x.g.p.lim.styles || len(s.borders) >= x.g.p.lim.styles140+ if what, listed := styleLists[[2]string{section, se.Name.Local}]; listed && full && x.depth == 3 {141+ return nil, fmt.Errorf("the workbook has more than %d %s: %w", x.g.p.lim.styles, what, errXLSXLimit)142+ }133143 switch {134144 case section == "dxfs" && se.Name.Local == "dxf" && x.depth == 3:135- if full {136- return nil, fmt.Errorf("the workbook has more than %d styles: %w", x.g.p.lim.styles, errXLSXLimit)137- }138145 st, err := readDxf(x)139146 if err != nil {140147 return nil, err141148 }142149 s.dxfs = append(s.dxfs, st)143150 case section == "numFmts" && se.Name.Local == "numFmt" && x.depth == 3:144- if full {145- return nil, fmt.Errorf("the workbook has more than %d styles: %w", x.g.p.lim.styles, errXLSXLimit)146- }147151 id, err := strconv.Atoi(attrOr(se, "numFmtId", ""))148152 if err == nil {149153 s.numFmts[id], _ = attr(se, "formatCode")150154 }151155 case section == "fonts" && se.Name.Local == "font" && x.depth == 3:152- if full {153- return nil, fmt.Errorf("the workbook has more than %d fonts: %w", x.g.p.lim.styles, errXLSXLimit)154- }155156 f, err := readFont(x)156157 if err != nil {157158 return nil, err158159 }159160 s.fonts = append(s.fonts, f)160161 case section == "borders" && se.Name.Local == "border" && x.depth == 3:161- if full {162- return nil, fmt.Errorf("the workbook has more than %d borders: %w", x.g.p.lim.styles, errXLSXLimit)163- }164162 b, err := readBorder(x)165163 if err != nil {166164 return nil, err167165 }168166 s.borders = append(s.borders, b)169167 case section == "cellXfs" && se.Name.Local == "xf" && x.depth == 3:170- if full {171- return nil, fmt.Errorf("the workbook has more than %d cell formats: %w", x.g.p.lim.styles, errXLSXLimit)172- }173168 s.xfs = append(s.xfs, xlsxXf{174169 numFmt: intAttr(se, "numFmtId", -1), font: intAttr(se, "fontId", -1), border: intAttr(se, "borderId", -1),175170 applyFont: boolAttr(se, "applyFont", true), applyAlign: boolAttr(se, "applyAlignment", true),