From b02f864eab5edb2155601b9dd640f99fbd442cb3 Mon Sep 17 00:00:00 2001
From: raochq <31030448+raochq@users.noreply.github.com>
Date: Sun, 15 Aug 2021 01:19:49 +0800
Subject: [PATCH] This closes #844, support get shared formula
---
cell.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++--
cell_test.go | 22 ++++++++++++++
excelize_test.go | 2 +-
lib.go | 5 ++--
4 files changed, 100 insertions(+), 7 deletions(-)
diff --git a/cell.go b/cell.go
index 6ad5f44..d44a552 100644
--- a/cell.go
+++ b/cell.go
@@ -392,7 +392,7 @@ func (f *File) GetCellFormula(sheet, axis string) (string, error) {
return "", false, nil
}
if c.F.T == STCellFormulaTypeShared {
- return getSharedForumula(x, c.F.Si), true, nil
+ return getSharedForumula(x, c.F.Si, c.R), true, nil
}
return c.F.Content, true, nil
})
@@ -977,6 +977,48 @@ func isOverlap(rect1, rect2 []int) bool {
cellInRef([]int{rect2[2], rect2[3]}, rect1)
}
+// parseSharedFormula generate dynamic part of shared formula for target cell
+// by given column and rows distance and origin shared formula.
+func parseSharedFormula(dCol, dRow int, orig []byte) (res string, start int) {
+ var (
+ end int
+ stringLiteral bool
+ )
+ for end = 0; end < len(orig); end++ {
+ c := orig[end]
+ if c == '"' {
+ stringLiteral = !stringLiteral
+ }
+ if stringLiteral {
+ continue // Skip characters in quotes
+ }
+ if c >= 'A' && c <= 'Z' || c == '$' {
+ res += string(orig[start:end])
+ start = end
+ end++
+ foundNum := false
+ for ; end < len(orig); end++ {
+ idc := orig[end]
+ if idc >= '0' && idc <= '9' || idc == '$' {
+ foundNum = true
+ } else if idc >= 'A' && idc <= 'Z' {
+ if foundNum {
+ break
+ }
+ } else {
+ break
+ }
+ }
+ if foundNum {
+ cellID := string(orig[start:end])
+ res += shiftCell(cellID, dCol, dRow)
+ start = end
+ }
+ }
+ }
+ return
+}
+
// getSharedForumula find a cell contains the same formula as another cell,
// the "shared" value can be used for the t attribute and the si attribute can
// be used to refer to the cell containing the formula. Two formulas are
@@ -985,13 +1027,43 @@ func isOverlap(rect1, rect2 []int) bool {
//
// Note that this function not validate ref tag to check the cell if or not in
// allow area, and always return origin shared formula.
-func getSharedForumula(ws *xlsxWorksheet, si string) string {
+func getSharedForumula(ws *xlsxWorksheet, si string, axis string) string {
for _, r := range ws.SheetData.Row {
for _, c := range r.C {
if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si == si {
- return c.F.Content
+ col, row, _ := CellNameToCoordinates(axis)
+ sharedCol, sharedRow, _ := CellNameToCoordinates(c.R)
+ dCol := col - sharedCol
+ dRow := row - sharedRow
+ orig := []byte(c.F.Content)
+ res, start := parseSharedFormula(dCol, dRow, orig)
+ if start < len(orig) {
+ res += string(orig[start:])
+ }
+ return res
}
}
}
return ""
}
+
+// shiftCell returns the cell shifted according to dCol and dRow taking into
+// consideration of absolute references with dollar sign ($)
+func shiftCell(cellID string, dCol, dRow int) string {
+ fCol, fRow, _ := CellNameToCoordinates(cellID)
+ signCol, signRow := "", ""
+ if strings.Index(cellID, "$") == 0 {
+ signCol = "$"
+ } else {
+ // Shift column
+ fCol += dCol
+ }
+ if strings.LastIndex(cellID, "$") > 0 {
+ signRow = "$"
+ } else {
+ // Shift row
+ fRow += dRow
+ }
+ colName, _ := ColumnNumberToName(fCol)
+ return signCol + colName + signRow + strconv.Itoa(fRow)
+}
diff --git a/cell_test.go b/cell_test.go
index 3954438..0af0097 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -226,6 +226,28 @@ func TestGetCellFormula(t *testing.T) {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.GetCellFormula("Sheet1", "A1")
assert.NoError(t, err)
+
+ // Test get cell shared formula
+ f = NewFile()
+ sheetData := `12*A1
2%s
3
4
5
6
7
`
+
+ for sharedFormula, expected := range map[string]string{
+ `2*A2`: `2*A3`,
+ `2*A1A`: `2*A2A`,
+ `2*$A$2+LEN("")`: `2*$A$2+LEN("")`,
+ } {
+ f.Sheet.Delete("xl/worksheets/sheet1.xml")
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, sharedFormula)))
+ formula, err := f.GetCellFormula("Sheet1", "B3")
+ assert.NoError(t, err)
+ assert.Equal(t, expected, formula)
+ }
+
+ f.Sheet.Delete("xl/worksheets/sheet1.xml")
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`
`))
+ formula, err := f.GetCellFormula("Sheet1", "B2")
+ assert.NoError(t, err)
+ assert.Equal(t, "", formula)
}
func ExampleFile_SetCellFloat() {
diff --git a/excelize_test.go b/excelize_test.go
index 918279b..f33c3d5 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -83,7 +83,7 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, err)
_, err = f.GetCellFormula("Sheet2", "I11")
assert.NoError(t, err)
- getSharedForumula(&xlsxWorksheet{}, "")
+ getSharedForumula(&xlsxWorksheet{}, "", "")
// Test read cell value with given illegal rows number.
_, err = f.GetCellValue("Sheet2", "a-1")
diff --git a/lib.go b/lib.go
index 712576d..81f1e53 100644
--- a/lib.go
+++ b/lib.go
@@ -93,13 +93,12 @@ func readFile(file *zip.File) ([]byte, error) {
//
func SplitCellName(cell string) (string, int, error) {
alpha := func(r rune) bool {
- return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z')
+ return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || (r == 36)
}
-
if strings.IndexFunc(cell, alpha) == 0 {
i := strings.LastIndexFunc(cell, alpha)
if i >= 0 && i < len(cell)-1 {
- col, rowstr := cell[:i+1], cell[i+1:]
+ col, rowstr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
if row, err := strconv.Atoi(rowstr); err == nil && row > 0 {
return col, row, nil
}