This closes #1861, fix missing parentheses in the adjusted formula

- Allow adjust cell reference with max rows/columns
- Fix incorrect data validation escape result
- Update out date reference link in the documentation
- Update unit tests
This commit is contained in:
xuri 2024-03-25 08:33:29 +08:00
parent 5975d87f7e
commit 703b73779c
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
3 changed files with 44 additions and 31 deletions

View File

@ -239,6 +239,17 @@ func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, of
// adjustCellRef provides a function to adjust cell reference.
func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset int) (string, error) {
var SQRef []string
applyOffset := func(coordinates []int, idx1, idx2, maxVal int) []int {
if coordinates[idx1] >= num {
coordinates[idx1] += offset
}
if coordinates[idx2] >= num {
if coordinates[idx2] += offset; coordinates[idx2] > maxVal {
coordinates[idx2] = maxVal
}
}
return coordinates
}
for _, ref := range strings.Split(cellRef, " ") {
if !strings.Contains(ref, ":") {
ref += ":" + ref
@ -251,22 +262,12 @@ func (f *File) adjustCellRef(cellRef string, dir adjustDirection, num, offset in
if offset < 0 && coordinates[0] == coordinates[2] {
continue
}
if coordinates[0] >= num {
coordinates[0] += offset
}
if coordinates[2] >= num {
coordinates[2] += offset
}
coordinates = applyOffset(coordinates, 0, 2, MaxColumns)
} else {
if offset < 0 && coordinates[1] == coordinates[3] {
continue
}
if coordinates[1] >= num {
coordinates[1] += offset
}
if coordinates[3] >= num {
coordinates[3] += offset
}
coordinates = applyOffset(coordinates, 1, 3, TotalRows)
}
if ref, err = coordinatesToRangeRef(coordinates); err != nil {
return "", err
@ -446,12 +447,8 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
val += operand
continue
}
if isFunctionStartToken(token) {
val += token.TValue + string(efp.ParenOpen)
continue
}
if isFunctionStopToken(token) {
val += token.TValue + string(efp.ParenClose)
if paren := transformParenthesesToken(token); paren != "" {
val += transformParenthesesToken(token)
continue
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
@ -463,6 +460,18 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
return val, nil
}
// transformParenthesesToken returns formula part with parentheses by given
// token.
func transformParenthesesToken(token efp.Token) string {
if isFunctionStartToken(token) || isBeginParenthesesToken(token) {
return token.TValue + string(efp.ParenOpen)
}
if isFunctionStopToken(token) || isEndParenthesesToken(token) {
return token.TValue + string(efp.ParenClose)
}
return ""
}
// adjustRangeSheetName returns replaced range reference by given source and
// target sheet name.
func adjustRangeSheetName(rng, source, target string) string {
@ -551,12 +560,8 @@ func transformArrayFormula(tokens []efp.Token, afs []arrayFormulaOperandToken) s
if skip {
continue
}
if isFunctionStartToken(token) {
val += token.TValue + string(efp.ParenOpen)
continue
}
if isFunctionStopToken(token) {
val += token.TValue + string(efp.ParenClose)
if paren := transformParenthesesToken(token); paren != "" {
val += transformParenthesesToken(token)
continue
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
@ -985,14 +990,14 @@ func (f *File) adjustDataValidations(ws *xlsxWorksheet, sheet string, dir adjust
worksheet.DataValidations.DataValidation[i].Sqref = ref
}
if worksheet.DataValidations.DataValidation[i].Formula1 != nil {
formula := unescapeDataValidationFormula(worksheet.DataValidations.DataValidation[i].Formula1.Content)
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula1.Content)
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
return err
}
worksheet.DataValidations.DataValidation[i].Formula1 = &xlsxInnerXML{Content: formulaEscaper.Replace(formula)}
}
if worksheet.DataValidations.DataValidation[i].Formula2 != nil {
formula := unescapeDataValidationFormula(worksheet.DataValidations.DataValidation[i].Formula2.Content)
formula := formulaUnescaper.Replace(worksheet.DataValidations.DataValidation[i].Formula2.Content)
if formula, err = f.adjustFormulaRef(sheet, sheetN, formula, false, dir, num, offset); err != nil {
return err
}

View File

@ -463,6 +463,14 @@ func TestAdjustCols(t *testing.T) {
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
assert.Nil(t, ws.(*xlsxWorksheet).Cols)
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "(1-0.5)/2"))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
formula, err := f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "(1-0.5)/2", formula)
assert.NoError(t, f.Close())
}
func TestAdjustColDimensions(t *testing.T) {
@ -1061,13 +1069,13 @@ func TestAdjustDataValidations(t *testing.T) {
// Test adjust data validation with multiple cell range
dv = NewDataValidation(true)
dv.Sqref = "G1:G3 H1:H3"
dv.Sqref = "G1:G3 H1:H3 A3:A1048576"
assert.NoError(t, dv.SetDropList([]string{"1", "2", "3"}))
assert.NoError(t, f.AddDataValidation("Sheet1", dv))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
dvs, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "G1:G4 H1:H4", dvs[3].Sqref)
assert.Equal(t, "G1:G4 H1:H4 A4:A1048576", dvs[3].Sqref)
dv = NewDataValidation(true)
dv.Sqref = "C5:D6"

View File

@ -453,14 +453,14 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel application. This function will be remove value tag when met a
// cell have a linked value. Reference
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating
// https://learn.microsoft.com/en-us/archive/msdn-technet-forums/e16bae1f-6a2c-4325-8013-e989a3479066
//
// Notice: after opening generated workbook, Excel will update the linked value
// and generate a new value and will prompt to save the file or not.
//
// For example:
//
// <row r="19" spans="2:2">
// <row r="19">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// <v>100</v>
@ -469,7 +469,7 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
//
// to
//
// <row r="19" spans="2:2">
// <row r="19">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// </c>