Skip to content

Commit e12da49

Browse files
committed
This fixes qax-os#2056, fix incorrect formula calculation result caused by shared formula parsed error
- Also reference qax-os#844 - Update unit test - Update documentation of the SheetPropsOptions data structure - Update dependencies modules
1 parent 3650e5c commit e12da49

File tree

5 files changed

+61
-71
lines changed

5 files changed

+61
-71
lines changed

cell.go

+45-58
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"strings"
2222
"time"
2323
"unicode/utf8"
24+
25+
"github.com/xuri/efp"
2426
)
2527

2628
// CellType is the type of cell value type.
@@ -1640,44 +1642,16 @@ func isOverlap(rect1, rect2 []int) bool {
16401642

16411643
// parseSharedFormula generate dynamic part of shared formula for target cell
16421644
// by given column and rows distance and origin shared formula.
1643-
func parseSharedFormula(dCol, dRow int, orig []byte) (res string, start int) {
1644-
var (
1645-
end int
1646-
stringLiteral bool
1647-
)
1648-
for end = 0; end < len(orig); end++ {
1649-
c := orig[end]
1650-
if c == '"' {
1651-
stringLiteral = !stringLiteral
1652-
}
1653-
if stringLiteral {
1654-
continue // Skip characters in quotes
1655-
}
1656-
if c >= 'A' && c <= 'Z' || c == '$' {
1657-
res += string(orig[start:end])
1658-
start = end
1659-
end++
1660-
foundNum := false
1661-
for ; end < len(orig); end++ {
1662-
idc := orig[end]
1663-
if idc >= '0' && idc <= '9' || idc == '$' {
1664-
foundNum = true
1665-
} else if idc >= 'A' && idc <= 'Z' {
1666-
if foundNum {
1667-
break
1668-
}
1669-
} else {
1670-
break
1671-
}
1672-
}
1673-
if foundNum {
1674-
cellID := string(orig[start:end])
1675-
res += shiftCell(cellID, dCol, dRow)
1676-
start = end
1677-
}
1645+
func parseSharedFormula(dCol, dRow int, orig string) string {
1646+
ps := efp.ExcelParser()
1647+
tokens := ps.Parse(string(orig))
1648+
for i := 0; i < len(tokens); i++ {
1649+
token := tokens[i]
1650+
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
1651+
tokens[i].TValue = shiftCell(token.TValue, dCol, dRow)
16781652
}
16791653
}
1680-
return
1654+
return ps.Render()
16811655
}
16821656

16831657
// getSharedFormula find a cell contains the same formula as another cell,
@@ -1698,12 +1672,7 @@ func getSharedFormula(ws *xlsxWorksheet, si int, cell string) string {
16981672
sharedCol, sharedRow, _ := CellNameToCoordinates(c.R)
16991673
dCol := col - sharedCol
17001674
dRow := row - sharedRow
1701-
orig := []byte(c.F.Content)
1702-
res, start := parseSharedFormula(dCol, dRow, orig)
1703-
if start < len(orig) {
1704-
res += string(orig[start:])
1705-
}
1706-
return res
1675+
return parseSharedFormula(dCol, dRow, c.F.Content)
17071676
}
17081677
}
17091678
}
@@ -1712,21 +1681,39 @@ func getSharedFormula(ws *xlsxWorksheet, si int, cell string) string {
17121681

17131682
// shiftCell returns the cell shifted according to dCol and dRow taking into
17141683
// consideration absolute references with dollar sign ($)
1715-
func shiftCell(cellID string, dCol, dRow int) string {
1716-
fCol, fRow, _ := CellNameToCoordinates(cellID)
1717-
signCol, signRow := "", ""
1718-
if strings.Index(cellID, "$") == 0 {
1719-
signCol = "$"
1720-
} else {
1721-
// Shift column
1722-
fCol += dCol
1723-
}
1724-
if strings.LastIndex(cellID, "$") > 0 {
1725-
signRow = "$"
1726-
} else {
1727-
// Shift row
1728-
fRow += dRow
1684+
func shiftCell(val string, dCol, dRow int) string {
1685+
parts := strings.Split(val, ":")
1686+
for j := 0; j < len(parts); j++ {
1687+
cell := parts[j]
1688+
trimmedCellName := strings.ReplaceAll(cell, "$", "")
1689+
c, r, err := CellNameToCoordinates(trimmedCellName)
1690+
if err == nil {
1691+
absCol := strings.Index(cell, "$") == 0
1692+
absRow := strings.LastIndex(cell, "$") > 0
1693+
if !absCol && !absRow {
1694+
parts[j], _ = CoordinatesToCellName(c+dCol, r+dRow)
1695+
}
1696+
if !absCol && absRow {
1697+
colName, _ := ColumnNumberToName(c + dCol)
1698+
parts[j] = colName + "$" + strconv.Itoa(r)
1699+
}
1700+
if absCol && !absRow {
1701+
colName, _ := ColumnNumberToName(c)
1702+
parts[j] = "$" + colName + strconv.Itoa(r+dRow)
1703+
}
1704+
continue
1705+
}
1706+
// Cell reference is a column name
1707+
c, err = ColumnNameToNumber(trimmedCellName)
1708+
if err == nil && !strings.HasPrefix(cell, "$") {
1709+
parts[j], _ = ColumnNumberToName(c + dCol)
1710+
continue
1711+
}
1712+
// Cell reference is a row number
1713+
r, err = strconv.Atoi(trimmedCellName)
1714+
if err == nil && !strings.HasPrefix(cell, "$") {
1715+
parts[j] = strconv.Itoa(r + dRow)
1716+
}
17291717
}
1730-
colName, _ := ColumnNumberToName(fCol)
1731-
return signCol + colName + signRow + strconv.Itoa(fRow)
1718+
return strings.Join(parts, ":")
17321719
}

cell_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,11 @@ func TestGetCellFormula(t *testing.T) {
591591
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData><row r="1"><c r="A1"><v>1</v></c><c r="B1"><f>2*A1</f></c></row><row r="2"><c r="A2"><v>2</v></c><c r="B2"><f t="shared" ref="B2:B7" si="0">%s</f></c></row><row r="3"><c r="A3"><v>3</v></c><c r="B3"><f t="shared" si="0"/></c></row><row r="4"><c r="A4"><v>4</v></c><c r="B4"><f t="shared" si="0"/></c></row><row r="5"><c r="A5"><v>5</v></c><c r="B5"><f t="shared" si="0"/></c></row><row r="6"><c r="A6"><v>6</v></c><c r="B6"><f t="shared" si="0"/></c></row><row r="7"><c r="A7"><v>7</v></c><c r="B7"><f t="shared" si="0"/></c></row></sheetData></worksheet>`
592592

593593
for sharedFormula, expected := range map[string]string{
594-
`2*A2`: `2*A3`,
595-
`2*A1A`: `2*A2A`,
596-
`2*$A$2+LEN("")`: `2*$A$2+LEN("")`,
594+
`2*A2`: `2*A3`,
595+
`2*A1A`: `2*A1A`,
596+
`2*$A$2+LEN("")`: `2*$A$2+LEN("")`,
597+
`SUMIF(A:A,$B11, 5:5)`: `SUMIF(A:A,$B12,6:6)`,
598+
`SUMIF(A:A,B$11, 5:5)`: `SUMIF(A:A,B$11,6:6)`,
597599
} {
598600
f.Sheet.Delete("xl/worksheets/sheet1.xml")
599601
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, sharedFormula)))

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ go 1.23.0
55
require (
66
github.com/richardlehane/mscfb v1.0.4
77
github.com/stretchr/testify v1.10.0
8-
github.com/tiendc/go-deepcopy v1.5.0
8+
github.com/tiendc/go-deepcopy v1.5.1
99
github.com/xuri/efp v0.0.0-20250227110027-3491fafc2b79
1010
github.com/xuri/nfp v0.0.0-20250226145837-86d5fc24b2ba
1111
golang.org/x/crypto v0.36.0
1212
golang.org/x/image v0.25.0
13-
golang.org/x/net v0.37.0
13+
golang.org/x/net v0.38.0
1414
golang.org/x/text v0.23.0
1515
)
1616

go.sum

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM
99
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
1010
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1111
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
12-
github.com/tiendc/go-deepcopy v1.5.0 h1:TbtS9hclrKZcF1AHby8evDm4mIQU36i6tSYuvx/TstY=
13-
github.com/tiendc/go-deepcopy v1.5.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
12+
github.com/tiendc/go-deepcopy v1.5.1 h1:5ymXIB8ReIywehne6oy3HgywC8LicXYucPBNnj5QQxE=
13+
github.com/tiendc/go-deepcopy v1.5.1/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
1414
github.com/xuri/efp v0.0.0-20250227110027-3491fafc2b79 h1:78nKszZqigiBRBVcoe/AuPzyLTWW5B+ltBaUX1rlIXA=
1515
github.com/xuri/efp v0.0.0-20250227110027-3491fafc2b79/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
1616
github.com/xuri/nfp v0.0.0-20250226145837-86d5fc24b2ba h1:DhIu6n3qU0joqG9f4IO6a/Gkerd+flXrmlJ+0yX2W8U=
@@ -19,8 +19,8 @@ golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
1919
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
2020
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
2121
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
22-
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
23-
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
22+
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
23+
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
2424
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
2525
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
2626
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

xmlWorksheet.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -1071,10 +1071,11 @@ type ViewOptions struct {
10711071
ZoomScale *float64
10721072
}
10731073

1074-
// SetSheetProps provides a function to set worksheet properties. There 4 kinds
1075-
// of presets "Custom Scaling Options" in the spreadsheet applications, if you
1076-
// need to set those kind of scaling options, please using the "SetSheetProps"
1077-
// and "SetPageLayout" functions to approach these 4 scaling options:
1074+
// SheetPropsOptions provides a function to set worksheet properties. There 4
1075+
// kinds of presets "Custom Scaling Options" in the spreadsheet applications, if
1076+
// you need to set those kind of scaling options, please using the
1077+
// "SetSheetProps" and "SetPageLayout" functions to approach these 4 scaling
1078+
// options:
10781079
//
10791080
// 1. No Scaling (Print sheets at their actual size):
10801081
//

0 commit comments

Comments
 (0)