This closes #844, support get shared formula

This commit is contained in:
raochq 2021-08-15 01:19:49 +08:00 committed by GitHub
parent 48c16de8bf
commit b02f864eab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 7 deletions

78
cell.go
View File

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

View File

@ -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 := `<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>`
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(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData><row r="2"><c r="B2"><f t="shared" si="0"></f></c></row></sheetData></worksheet>`))
formula, err := f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "", formula)
}
func ExampleFile_SetCellFloat() {

View File

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

5
lib.go
View File

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