Skip to content

Commit 63bc6f3

Browse files
committed
fix(calc): strip quotes from sheet names
Excel wraps sheet names containing spaces or special characters in single quotes. The calculation engine previously failed to resolve these references because it included literal quotes in the sheet lookup. Strip surrounding quotes during parsing to ensure formulas correctly resolve data across sheets with spaces, hyphens, or other special characters.
1 parent 418be6d commit 63bc6f3

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed

calc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,11 @@ func parseRef(ref string) (cellRef, bool, bool, error) {
15341534
)
15351535
if len(tokens) == 2 { // have a worksheet
15361536
cr.Sheet, cell = tokens[0], tokens[1]
1537+
// Strip surrounding single quotes from sheet name if present
1538+
// Excel requires quotes around sheet names with spaces/special chars: 'Sheet Name'!A1
1539+
if len(cr.Sheet) >= 2 && cr.Sheet[0] == '\'' && cr.Sheet[len(cr.Sheet)-1] == '\'' {
1540+
cr.Sheet = cr.Sheet[1 : len(cr.Sheet)-1]
1541+
}
15371542
}
15381543
if cr.Col, cr.Row, err = CellNameToCoordinates(cell); err != nil {
15391544
if cr.Col, colErr = ColumnNameToNumber(cell); colErr == nil { // cast to column

calc_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6821,3 +6821,175 @@ func TestCalcTrendGrowthRegression(t *testing.T) {
68216821
mtx := [][]float64{}
68226822
calcTrendGrowthRegression(false, false, 0, 0, 0, 0, 0, mtx, mtx, mtx, mtx)
68236823
}
6824+
6825+
func TestCalcCellValueWithNamedRangesInFormula(t *testing.T) {
6826+
f := NewFile()
6827+
defer func() {
6828+
assert.NoError(t, f.Close())
6829+
}()
6830+
6831+
sheetName := "Test - Sheet"
6832+
idx, err := f.NewSheet(sheetName)
6833+
assert.NoError(t, err)
6834+
f.SetActiveSheet(idx)
6835+
6836+
// Create cells with values
6837+
assert.NoError(t, f.SetCellValue(sheetName, "A1", 100))
6838+
assert.NoError(t, f.SetCellValue(sheetName, "A2", 20))
6839+
assert.NoError(t, f.SetCellValue(sheetName, "A3", 30))
6840+
assert.NoError(t, f.SetCellValue(sheetName, "A4", 5))
6841+
6842+
// Define named ranges with periods in names to test dot notation support
6843+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6844+
Name: "value.first",
6845+
RefersTo: "'Test - Sheet'!$A$1",
6846+
Scope: "Workbook",
6847+
}))
6848+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6849+
Name: "value_second",
6850+
RefersTo: "'Test - Sheet'!$A$2",
6851+
Scope: "Workbook",
6852+
}))
6853+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6854+
Name: "value_third",
6855+
RefersTo: "'Test - Sheet'!$A$3",
6856+
Scope: "Workbook",
6857+
}))
6858+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6859+
Name: "value_fourth",
6860+
RefersTo: "'Test - Sheet'!$A$4",
6861+
Scope: "Workbook",
6862+
}))
6863+
6864+
// Set formula that references multiple named ranges in arithmetic operations
6865+
assert.NoError(t, f.SetCellFormula(sheetName, "B1", "=value.first-value_second-value_third-value_fourth"))
6866+
6867+
// Define a named range for the result cell
6868+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6869+
Name: "result",
6870+
RefersTo: "'Test - Sheet'!$B$1",
6871+
Scope: "Workbook",
6872+
}))
6873+
6874+
// This should calculate to 100 - 20 - 30 - 5 = 45
6875+
result, err := f.CalcCellValue(sheetName, "B1")
6876+
assert.NoError(t, err)
6877+
assert.NotEmpty(t, result, "Formula with named ranges should return a calculated value")
6878+
assert.Equal(t, "45", result, "Formula should calculate correctly: 100 - 20 - 30 - 5")
6879+
}
6880+
6881+
func TestCalcWithNamedRangesVariousScenarios(t *testing.T) {
6882+
// Test 1: Named ranges on sheet without special chars
6883+
t.Run("SimpleSheetName", func(t *testing.T) {
6884+
f := NewFile()
6885+
defer func() {
6886+
assert.NoError(t, f.Close())
6887+
}()
6888+
6889+
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 100))
6890+
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 50))
6891+
6892+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6893+
Name: "Value1",
6894+
RefersTo: "Sheet1!$A$1",
6895+
Scope: "Workbook",
6896+
}))
6897+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6898+
Name: "Value2",
6899+
RefersTo: "Sheet1!$A$2",
6900+
Scope: "Workbook",
6901+
}))
6902+
6903+
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "=Value1+Value2"))
6904+
result, err := f.CalcCellValue("Sheet1", "B1")
6905+
assert.NoError(t, err)
6906+
assert.Equal(t, "150", result)
6907+
})
6908+
6909+
// Test 2: Named ranges in functions
6910+
t.Run("NamedRangesInFunctions", func(t *testing.T) {
6911+
f := NewFile()
6912+
defer func() {
6913+
assert.NoError(t, f.Close())
6914+
}()
6915+
6916+
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10))
6917+
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 20))
6918+
assert.NoError(t, f.SetCellValue("Sheet1", "A3", 30))
6919+
6920+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6921+
Name: "DataRange",
6922+
RefersTo: "Sheet1!$A$1:$A$3",
6923+
Scope: "Workbook",
6924+
}))
6925+
6926+
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "=SUM(DataRange)"))
6927+
result, err := f.CalcCellValue("Sheet1", "B1")
6928+
assert.NoError(t, err)
6929+
assert.Equal(t, "60", result)
6930+
})
6931+
6932+
// Test 3: Named ranges with sheet names containing various special chars
6933+
t.Run("SheetWithHyphens", func(t *testing.T) {
6934+
f := NewFile()
6935+
defer func() {
6936+
assert.NoError(t, f.Close())
6937+
}()
6938+
6939+
sheetName := "Data-Sheet-2025"
6940+
idx, err := f.NewSheet(sheetName)
6941+
assert.NoError(t, err)
6942+
f.SetActiveSheet(idx)
6943+
6944+
assert.NoError(t, f.SetCellValue(sheetName, "C5", 42))
6945+
6946+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6947+
Name: "SpecialValue",
6948+
RefersTo: "'Data-Sheet-2025'!$C$5",
6949+
Scope: "Workbook",
6950+
}))
6951+
6952+
assert.NoError(t, f.SetCellFormula(sheetName, "D5", "=SpecialValue*2"))
6953+
result, err := f.CalcCellValue(sheetName, "D5")
6954+
assert.NoError(t, err)
6955+
assert.Equal(t, "84", result)
6956+
})
6957+
6958+
// Test 4: Multiple arithmetic operations with named ranges
6959+
t.Run("ComplexArithmetic", func(t *testing.T) {
6960+
f := NewFile()
6961+
defer func() {
6962+
assert.NoError(t, f.Close())
6963+
}()
6964+
6965+
sheetName := "My Sheet"
6966+
idx, err := f.NewSheet(sheetName)
6967+
assert.NoError(t, err)
6968+
f.SetActiveSheet(idx)
6969+
6970+
assert.NoError(t, f.SetCellValue(sheetName, "A1", 1000))
6971+
assert.NoError(t, f.SetCellValue(sheetName, "A2", 200))
6972+
assert.NoError(t, f.SetCellValue(sheetName, "A3", 50))
6973+
6974+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6975+
Name: "Total",
6976+
RefersTo: "'My Sheet'!$A$1",
6977+
Scope: "Workbook",
6978+
}))
6979+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6980+
Name: "Cost",
6981+
RefersTo: "'My Sheet'!$A$2",
6982+
Scope: "Workbook",
6983+
}))
6984+
assert.NoError(t, f.SetDefinedName(&DefinedName{
6985+
Name: "Tax",
6986+
RefersTo: "'My Sheet'!$A$3",
6987+
Scope: "Workbook",
6988+
}))
6989+
6990+
assert.NoError(t, f.SetCellFormula(sheetName, "B1", "=(Total-Cost)*Tax/100"))
6991+
result, err := f.CalcCellValue(sheetName, "B1")
6992+
assert.NoError(t, err)
6993+
assert.Equal(t, "400", result)
6994+
})
6995+
}

0 commit comments

Comments
 (0)