mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
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:
parent
988f2e3290
commit
968e4ba29d
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user