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
This commit is contained in:
Todd 2017-10-01 12:10:05 -05:00
parent 988f2e3290
commit 968e4ba29d
7 changed files with 51 additions and 157 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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