From 968e4ba29d6ddfc9e1de9ff5032b66851446c088 Mon Sep 17 00:00:00 2001 From: Todd Date: Sun, 1 Oct 2017 12:10:05 -0500 Subject: [PATCH] spreadsheet: use ParseCellReference from reference package Drop the old cell reference parsing code and use the new code that parses to a struct and identifies absolute references --- spreadsheet/cell.go | 11 +++---- spreadsheet/comments.go | 11 ++++--- spreadsheet/compare.go | 10 +++--- spreadsheet/parse.go | 50 ---------------------------- spreadsheet/parse_test.go | 47 -------------------------- spreadsheet/row.go | 10 +++--- spreadsheet/sheet.go | 69 ++++++++++++++++----------------------- 7 files changed, 51 insertions(+), 157 deletions(-) delete mode 100644 spreadsheet/parse.go delete mode 100644 spreadsheet/parse_test.go diff --git a/spreadsheet/cell.go b/spreadsheet/cell.go index 0ad05a75..b104baed 100644 --- a/spreadsheet/cell.go +++ b/spreadsheet/cell.go @@ -104,11 +104,10 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error { c.x.F = sml.NewCT_CellFormula() c.x.F.TAttr = sml.ST_CellFormulaTypeShared c.x.F.Content = formula - col, rowIdx, err := ParseCellReference(c.Reference()) + cref, err := reference.ParseCellReference(c.Reference()) if err != nil { return err } - colIdx := reference.ColumnToIndex(col) sid := uint32(0) for _, r := range c.s.SheetData.Row { @@ -119,13 +118,13 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error { } } - ref := fmt.Sprintf("%s%d:%s%d", col, rowIdx, reference.IndexToColumn(colIdx+cols), rowIdx+rows) + ref := fmt.Sprintf("%s%d:%s%d", cref.Column, cref.RowIdx, reference.IndexToColumn(cref.ColumnIdx+cols), cref.RowIdx+rows) c.x.F.RefAttr = gooxml.String(ref) c.x.F.SiAttr = gooxml.Uint32(sid) sheet := Sheet{c.w, nil, c.s} - for row := rowIdx; row <= rowIdx+rows; row++ { - for col := colIdx; col <= colIdx+cols; col++ { - if row == rowIdx && col == colIdx { + for row := cref.RowIdx; row <= cref.RowIdx+rows; row++ { + for col := cref.ColumnIdx; col <= cref.ColumnIdx+cols; col++ { + if row == cref.RowIdx && col == cref.ColumnIdx { continue } ref := fmt.Sprintf("%s%d", reference.IndexToColumn(col), row) diff --git a/spreadsheet/comments.go b/spreadsheet/comments.go index 3e9dbd44..716f22a8 100644 --- a/spreadsheet/comments.go +++ b/spreadsheet/comments.go @@ -65,7 +65,7 @@ func (c Comments) AddComment(cellRef string, author string) RichText { } // AddCommentWithStyle adds a new comment styled in a default way -func (c Comments) AddCommentWithStyle(cellRef string, author string, comment string) { +func (c Comments) AddCommentWithStyle(cellRef string, author string, comment string) error { rt := c.AddComment(cellRef, author) run := rt.AddRun() run.SetBold(true) @@ -80,7 +80,10 @@ func (c Comments) AddCommentWithStyle(cellRef string, author string, comment str run.SetColor(color.Black) run.SetText("\r\n" + comment + "\r\n") - col, rowIdx, _ := ParseCellReference(cellRef) - colIdx := reference.ColumnToIndex(col) - c.w.vmlDrawings[0].Shape = append(c.w.vmlDrawings[0].Shape, vmldrawing.NewCommentShape(int64(colIdx), int64(rowIdx-1))) + cref, err := reference.ParseCellReference(cellRef) + if err != nil { + return err + } + c.w.vmlDrawings[0].Shape = append(c.w.vmlDrawings[0].Shape, vmldrawing.NewCommentShape(int64(cref.ColumnIdx), int64(cref.RowIdx-1))) + return nil } diff --git a/spreadsheet/compare.go b/spreadsheet/compare.go index f2ba9cd6..db2b616a 100644 --- a/spreadsheet/compare.go +++ b/spreadsheet/compare.go @@ -9,6 +9,8 @@ package spreadsheet import ( "strconv" + + "baliance.com/gooxml/spreadsheet/reference" ) // SortOrder is a column sort order. @@ -32,16 +34,16 @@ type Comparer struct { func (c Comparer) LessRows(column string, lhs, rhs Row) bool { var lhsCell, rhsCell Cell for _, c := range lhs.Cells() { - cellCol, _, _ := ParseCellReference(c.Reference()) - if cellCol == column { + cref, _ := reference.ParseCellReference(c.Reference()) + if cref.Column == column { lhsCell = c break } } for _, c := range rhs.Cells() { - cellCol, _, _ := ParseCellReference(c.Reference()) - if cellCol == column { + cref, _ := reference.ParseCellReference(c.Reference()) + if cref.Column == column { rhsCell = c break } diff --git a/spreadsheet/parse.go b/spreadsheet/parse.go deleted file mode 100644 index 0814a27e..00000000 --- a/spreadsheet/parse.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2017 Baliance. All rights reserved. -// -// Use of this source code is governed by the terms of the Affero GNU General -// Public License version 3.0 as published by the Free Software Foundation and -// appearing in the file LICENSE included in the packaging of this file. A -// commercial license can be purchased by contacting sales@baliance.com. - -package spreadsheet - -import ( - "fmt" - "strconv" - "strings" -) - -// ParseRangeReference splits a range reference of the form "A1:B5" into its -// components. -func ParseRangeReference(s string) (from, to string, err error) { - sp := strings.Split(s, ":") - if len(sp) == 2 { - return sp[0], sp[1], nil - } - return "", "", fmt.Errorf("invaid range reference: %s", s) -} - -// ParseCellReference parses a cell reference of the form 'A10' and splits it -// into column/row segments. -func ParseCellReference(s string) (col string, row uint32, err error) { - s = strings.Replace(s, "$", "", -1) - split := -1 -lfor: - for i := 0; i < len(s); i++ { - switch { - case s[i] >= '0' && s[i] <= '9': - split = i - break lfor - } - } - switch split { - case 0: - return col, row, fmt.Errorf("no letter prefix in %s", s) - case -1: - return col, row, fmt.Errorf("no digits in %s", s) - } - - col = s[0:split] - r64, err := strconv.ParseUint(s[split:], 10, 32) - row = uint32(r64) - return col, row, err -} diff --git a/spreadsheet/parse_test.go b/spreadsheet/parse_test.go deleted file mode 100644 index 715b4593..00000000 --- a/spreadsheet/parse_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Baliance. All rights reserved. -// -// Use of this source code is governed by the terms of the Affero GNU General -// Public License version 3.0 as published by the Free Software Foundation and -// appearing in the file LICENSE included in the packaging of this file. A -// commercial license can be purchased by contacting sales@baliance.com. - -package spreadsheet_test - -import ( - "testing" - - "baliance.com/gooxml/spreadsheet" -) - -func TestParseCellReference(t *testing.T) { - td := []struct { - Inp string - ExpCol string - ExpRow uint32 - HasError bool - }{ - {"A1", "A", 1, false}, - {"B25", "B", 25, false}, - {"AZ9", "AZ", 9, false}, - {"A", "", 0, true}, - {"1", "", 0, true}, - } - for _, tc := range td { - col, row, err := spreadsheet.ParseCellReference(tc.Inp) - if tc.HasError { - if err == nil { - t.Errorf("expected error for input %s", tc.Inp) - } - } else if err != nil { - t.Errorf("expected no error for input %s, got %s", tc.Inp, err) - } - - if col != tc.ExpCol { - t.Errorf("expected col = %s for %s, got %s", tc.ExpCol, tc.Inp, col) - } - - if row != tc.ExpRow { - t.Errorf("expected row = %d for %s, got %d", tc.ExpRow, tc.Inp, row) - } - } -} diff --git a/spreadsheet/row.go b/spreadsheet/row.go index 96ba2f14..1bb90f76 100644 --- a/spreadsheet/row.go +++ b/spreadsheet/row.go @@ -80,9 +80,9 @@ func (r Row) AddCell() Cell { nextIdx := uint32(0) for _, c := range r.x.C { if c.RAttr != nil { - col, _, _ := ParseCellReference(*c.RAttr) - if col := reference.ColumnToIndex(col); col >= nextIdx { - nextIdx = col + 1 + cref, _ := reference.ParseCellReference(*c.RAttr) + if cref.ColumnIdx >= nextIdx { + nextIdx = cref.ColumnIdx + 1 } } } @@ -131,9 +131,9 @@ func (r Row) Cell(col string) Cell { func (r Row) renumberAs(rowNumber uint32) { r.x.RAttr = gooxml.Uint32(rowNumber) for _, c := range r.Cells() { - col, _, err := ParseCellReference(c.Reference()) + cref, err := reference.ParseCellReference(c.Reference()) if err == nil { - newRef := fmt.Sprintf("%s%d", col, rowNumber) + newRef := fmt.Sprintf("%s%d", cref.Column, rowNumber) c.x.RAttr = gooxml.String(newRef) } } diff --git a/spreadsheet/sheet.go b/spreadsheet/sheet.go index fe2867a1..341aee49 100644 --- a/spreadsheet/sheet.go +++ b/spreadsheet/sheet.go @@ -52,12 +52,12 @@ func (s Sheet) Row(rowNum uint32) Row { // Cell creates or returns a cell given a cell reference of the form 'A10' func (s Sheet) Cell(cellRef string) Cell { - col, row, err := ParseCellReference(cellRef) + cref, err := reference.ParseCellReference(cellRef) if err != nil { gooxml.Log("error parsing cell reference: %s", err) return s.AddRow().AddCell() } - return s.Row(row).Cell(col) + return s.Row(cref.RowIdx).Cell(cref.Column) } // AddNumberedRow adds a row with a given row number. If you reuse a row number @@ -183,22 +183,12 @@ func (s Sheet) validateRowCellNumbers() error { func (s Sheet) validateMergedCells() error { mergedCells := map[uint64]struct{}{} for _, mc := range s.MergedCells() { - from, to, err := ParseRangeReference(mc.Reference()) + from, to, err := reference.ParseRangeReference(mc.Reference()) if err != nil { return fmt.Errorf("sheet name '%s' has invalid merged cell reference %s", s.Name(), mc.Reference()) } - fc, frIdx, err := ParseCellReference(from) - if err != nil { - return fmt.Errorf("sheet name '%s' has invalid merged cell reference %s", s.Name(), mc.Reference()) - } - tc, trIdx, err := ParseCellReference(to) - if err != nil { - return fmt.Errorf("sheet name '%s' has invalid merged cell reference %s", s.Name(), mc.Reference()) - } - fcIdx := reference.ColumnToIndex(fc) - tcIdx := reference.ColumnToIndex(tc) - for r := frIdx; r <= trIdx; r++ { - for c := fcIdx; c <= tcIdx; c++ { + for r := from.RowIdx; r <= to.RowIdx; r++ { + for c := from.ColumnIdx; c <= to.ColumnIdx; c++ { idx := uint64(r)<<32 | uint64(c) if _, ok := mergedCells[idx]; ok { return fmt.Errorf("sheet name '%s' has overlapping merged cell range", s.Name()) @@ -270,13 +260,13 @@ func (s Sheet) AddHyperlink(url string) common.Hyperlink { // invalidate the reference. func (s Sheet) RangeReference(n string) string { sp := strings.Split(n, ":") - fc, fr, _ := ParseCellReference(sp[0]) - from := fmt.Sprintf("$%s$%d", fc, fr) + cref, _ := reference.ParseCellReference(sp[0]) + from := fmt.Sprintf("$%s$%d", cref.Column, cref.RowIdx) if len(sp) == 1 { return fmt.Sprintf(`'%s'!%s`, s.Name(), from) } - tc, tr, _ := ParseCellReference(sp[1]) - to := fmt.Sprintf("$%s$%d", tc, tr) + tref, _ := reference.ParseCellReference(sp[1]) + to := fmt.Sprintf("$%s$%d", tref.Column, tref.RowIdx) return fmt.Sprintf(`'%s'!%s:%s`, s.Name(), from, to) } @@ -386,14 +376,13 @@ func (s Sheet) ExtentsIndex() (string, uint32, string, uint32) { } for _, c := range r.Cells() { - col, _, err := ParseCellReference(c.Reference()) + cref, err := reference.ParseCellReference(c.Reference()) if err == nil { // column index is zero based here - colIdx := reference.ColumnToIndex(col) - if colIdx < minCol { - minCol = colIdx - } else if colIdx > maxCol { - maxCol = colIdx + if cref.ColumnIdx < minCol { + minCol = cref.ColumnIdx + } else if cref.ColumnIdx > maxCol { + maxCol = cref.ColumnIdx } } } @@ -485,20 +474,10 @@ func (s Sheet) Comments() Comments { // breaks apart a single border into its components and applies it to cells as // needed to give the effect of a border applying to multiple cells. func (s Sheet) SetBorder(cellRange string, border Border) error { - from, to, err := ParseRangeReference(cellRange) + from, to, err := reference.ParseRangeReference(cellRange) if err != nil { return err } - tlCol, tlRowIdx, err := ParseCellReference(from) - if err != nil { - return err - } - brCol, brRowIdx, err := ParseCellReference(to) - if err != nil { - return err - } - tlColIdx := reference.ColumnToIndex(tlCol) - brColIdx := reference.ColumnToIndex(brCol) topLeftStyle := s.w.StyleSheet.AddCellStyle() topLeftBorder := s.w.StyleSheet.AddBorder() @@ -544,6 +523,11 @@ func (s Sheet) SetBorder(cellRange string, border Border) error { bottomRightBorder.x.Bottom = border.x.Bottom bottomRightBorder.x.Right = border.x.Right + tlRowIdx := from.RowIdx + tlColIdx := from.ColumnIdx + brRowIdx := to.RowIdx + brColIdx := to.ColumnIdx + for row := tlRowIdx; row <= brRowIdx; row++ { for col := tlColIdx; col <= brColIdx; col++ { ref := fmt.Sprintf("%s%d", reference.IndexToColumn(col), row) @@ -642,16 +626,19 @@ func (s *Sheet) RecalculateFormulas() { // setArray expands an array into cached values starting at the origin which // should be a cell reference of the type "A1". This is used when evaluating // array type formulas. -func (s *Sheet) setArray(origin string, arr formula.Result) { - colStr, rowIdx, _ := ParseCellReference(origin) - colIdx := reference.ColumnToIndex(colStr) +func (s *Sheet) setArray(origin string, arr formula.Result) error { + cref, err := reference.ParseCellReference(origin) + if err != nil { + return err + } for ir, row := range arr.ValueArray { - sr := s.Row(rowIdx + uint32(ir)) + sr := s.Row(cref.RowIdx + uint32(ir)) for ic, val := range row { - cell := sr.Cell(reference.IndexToColumn(colIdx + uint32(ic))) + cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic))) cell.SetCachedFormulaResult(val.String()) } } + return nil } // SheetViews returns the sheet views defined. This is where splits and frozen