OSDN Git Service

Support update conditional formatting on inserting/deleting columns/rows (#1717)
[excelize/excelize.git] / adjust.go
1 // Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
2 // this source code is governed by a BSD-style license that can be found in
3 // the LICENSE file.
4 //
5 // Package excelize providing a set of functions that allow you to write to and
6 // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
7 // writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
8 // Supports complex components by high compatibility, and provided streaming
9 // API for generating or reading data from a worksheet with huge amounts of
10 // data. This library needs Go version 1.16 or later.
11
12 package excelize
13
14 import (
15         "bytes"
16         "encoding/xml"
17         "io"
18         "strconv"
19         "strings"
20         "unicode"
21
22         "github.com/xuri/efp"
23 )
24
25 type adjustDirection bool
26
27 const (
28         columns adjustDirection = false
29         rows    adjustDirection = true
30 )
31
32 // adjustHelperFunc defines functions to adjust helper.
33 var adjustHelperFunc = [8]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{
34         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
35                 return f.adjustConditionalFormats(ws, sheet, dir, num, offset, sheetID)
36         },
37         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
38                 return f.adjustDefinedNames(ws, sheet, dir, num, offset, sheetID)
39         },
40         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
41                 return f.adjustDrawings(ws, sheet, dir, num, offset, sheetID)
42         },
43         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
44                 return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID)
45         },
46         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
47                 return f.adjustAutoFilter(ws, sheet, dir, num, offset, sheetID)
48         },
49         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
50                 return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID)
51         },
52         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
53                 return f.adjustTable(ws, sheet, dir, num, offset, sheetID)
54         },
55         func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
56                 return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID)
57         },
58 }
59
60 // adjustHelper provides a function to adjust rows and columns dimensions,
61 // hyperlinks, merged cells and auto filter when inserting or deleting rows or
62 // columns.
63 //
64 // sheet: Worksheet name that we're editing
65 // column: Index number of the column we're inserting/deleting before
66 // row: Index number of the row we're inserting/deleting before
67 // offset: Number of rows/column to insert/delete negative values indicate deletion
68 //
69 // TODO: adjustComments, adjustDataValidations, adjustPageBreaks, adjustProtectedCells
70 func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
71         ws, err := f.workSheetReader(sheet)
72         if err != nil {
73                 return err
74         }
75         sheetID := f.getSheetID(sheet)
76         if dir == rows {
77                 err = f.adjustRowDimensions(sheet, ws, num, offset)
78         } else {
79                 err = f.adjustColDimensions(sheet, ws, num, offset)
80         }
81         if err != nil {
82                 return err
83         }
84         f.adjustHyperlinks(ws, sheet, dir, num, offset)
85         ws.checkSheet()
86         _ = ws.checkRow()
87         for _, fn := range adjustHelperFunc {
88                 if err := fn(f, ws, sheet, dir, num, offset, sheetID); err != nil {
89                         return err
90                 }
91         }
92         if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
93                 ws.MergeCells = nil
94         }
95         return nil
96 }
97
98 // adjustCols provides a function to update column style when inserting or
99 // deleting columns.
100 func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error {
101         if ws.Cols == nil {
102                 return nil
103         }
104         for i := 0; i < len(ws.Cols.Col); i++ {
105                 if offset > 0 {
106                         if ws.Cols.Col[i].Min >= col {
107                                 if ws.Cols.Col[i].Min += offset; ws.Cols.Col[i].Min > MaxColumns {
108                                         ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
109                                         i--
110                                         continue
111                                 }
112                         }
113                         if ws.Cols.Col[i].Max >= col || ws.Cols.Col[i].Max+1 == col {
114                                 if ws.Cols.Col[i].Max += offset; ws.Cols.Col[i].Max > MaxColumns {
115                                         ws.Cols.Col[i].Max = MaxColumns
116                                 }
117                         }
118                         continue
119                 }
120                 if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
121                         ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
122                         i--
123                         continue
124                 }
125                 if ws.Cols.Col[i].Min > col {
126                         ws.Cols.Col[i].Min += offset
127                 }
128                 if ws.Cols.Col[i].Max >= col {
129                         ws.Cols.Col[i].Max += offset
130                 }
131         }
132         if len(ws.Cols.Col) == 0 {
133                 ws.Cols = nil
134         }
135         return nil
136 }
137
138 // adjustColDimensions provides a function to update column dimensions when
139 // inserting or deleting rows or columns.
140 func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset int) error {
141         for rowIdx := range ws.SheetData.Row {
142                 for _, v := range ws.SheetData.Row[rowIdx].C {
143                         if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol {
144                                 if newCol := cellCol + offset; newCol > 0 && newCol > MaxColumns {
145                                         return ErrColumnNumber
146                                 }
147                         }
148                 }
149         }
150         for _, sheetN := range f.GetSheetList() {
151                 worksheet, err := f.workSheetReader(sheetN)
152                 if err != nil {
153                         if err.Error() == newNotWorksheetError(sheetN).Error() {
154                                 continue
155                         }
156                         return err
157                 }
158                 for rowIdx := range worksheet.SheetData.Row {
159                         for colIdx, v := range worksheet.SheetData.Row[rowIdx].C {
160                                 if cellCol, cellRow, _ := CellNameToCoordinates(v.R); sheetN == sheet && col <= cellCol {
161                                         if newCol := cellCol + offset; newCol > 0 {
162                                                 worksheet.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
163                                         }
164                                 }
165                                 if err := f.adjustFormula(sheet, sheetN, worksheet.SheetData.Row[rowIdx].C[colIdx].F, columns, col, offset, false); err != nil {
166                                         return err
167                                 }
168                         }
169                 }
170         }
171         return f.adjustCols(ws, col, offset)
172 }
173
174 // adjustRowDimensions provides a function to update row dimensions when
175 // inserting or deleting rows or columns.
176 func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset int) error {
177         for _, sheetN := range f.GetSheetList() {
178                 if sheetN == sheet {
179                         continue
180                 }
181                 worksheet, err := f.workSheetReader(sheetN)
182                 if err != nil {
183                         if err.Error() == newNotWorksheetError(sheetN).Error() {
184                                 continue
185                         }
186                         return err
187                 }
188                 numOfRows := len(worksheet.SheetData.Row)
189                 for i := 0; i < numOfRows; i++ {
190                         r := &worksheet.SheetData.Row[i]
191                         if err = f.adjustSingleRowFormulas(sheet, sheetN, r, row, offset, false); err != nil {
192                                 return err
193                         }
194                 }
195         }
196         totalRows := len(ws.SheetData.Row)
197         if totalRows == 0 {
198                 return nil
199         }
200         lastRow := &ws.SheetData.Row[totalRows-1]
201         if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow > TotalRows {
202                 return ErrMaxRows
203         }
204         numOfRows := len(ws.SheetData.Row)
205         for i := 0; i < numOfRows; i++ {
206                 r := &ws.SheetData.Row[i]
207                 if newRow := r.R + offset; r.R >= row && newRow > 0 {
208                         r.adjustSingleRowDimensions(offset)
209                 }
210                 if err := f.adjustSingleRowFormulas(sheet, sheet, r, row, offset, false); err != nil {
211                         return err
212                 }
213         }
214         return nil
215 }
216
217 // adjustSingleRowDimensions provides a function to adjust single row dimensions.
218 func (r *xlsxRow) adjustSingleRowDimensions(offset int) {
219         r.R += offset
220         for i, col := range r.C {
221                 colName, _, _ := SplitCellName(col.R)
222                 r.C[i].R, _ = JoinCellName(colName, r.R)
223         }
224 }
225
226 // adjustSingleRowFormulas provides a function to adjust single row formulas.
227 func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, offset int, si bool) error {
228         for _, col := range r.C {
229                 if err := f.adjustFormula(sheet, sheetN, col.F, rows, num, offset, si); err != nil {
230                         return err
231                 }
232         }
233         return nil
234 }
235
236 // adjustCellRef provides a function to adjust cell reference.
237 func (f *File) adjustCellRef(ref string, dir adjustDirection, num, offset int) (string, bool, error) {
238         if !strings.Contains(ref, ":") {
239                 ref += ":" + ref
240         }
241         var delete bool
242         coordinates, err := rangeRefToCoordinates(ref)
243         if err != nil {
244                 return ref, delete, err
245         }
246         if dir == columns {
247                 if offset < 0 && coordinates[0] == coordinates[2] {
248                         delete = true
249                 }
250                 if coordinates[0] >= num {
251                         coordinates[0] += offset
252                 }
253                 if coordinates[2] >= num {
254                         coordinates[2] += offset
255                 }
256         } else {
257                 if offset < 0 && coordinates[1] == coordinates[3] {
258                         delete = true
259                 }
260                 if coordinates[1] >= num {
261                         coordinates[1] += offset
262                 }
263                 if coordinates[3] >= num {
264                         coordinates[3] += offset
265                 }
266         }
267         ref, err = f.coordinatesToRangeRef(coordinates)
268         return ref, delete, err
269 }
270
271 // adjustFormula provides a function to adjust formula reference and shared
272 // formula reference.
273 func (f *File) adjustFormula(sheet, sheetN string, formula *xlsxF, dir adjustDirection, num, offset int, si bool) error {
274         if formula == nil {
275                 return nil
276         }
277         var err error
278         if formula.Ref != "" && sheet == sheetN {
279                 if formula.Ref, _, err = f.adjustCellRef(formula.Ref, dir, num, offset); err != nil {
280                         return err
281                 }
282                 if si && formula.Si != nil {
283                         formula.Si = intPtr(*formula.Si + 1)
284                 }
285         }
286         if formula.Content != "" {
287                 if formula.Content, err = f.adjustFormulaRef(sheet, sheetN, formula.Content, false, dir, num, offset); err != nil {
288                         return err
289                 }
290         }
291         return nil
292 }
293
294 // isFunctionStop provides a function to check if token is a function stop.
295 func isFunctionStop(token efp.Token) bool {
296         return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop
297 }
298
299 // isFunctionStart provides a function to check if token is a function start.
300 func isFunctionStart(token efp.Token) bool {
301         return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart
302 }
303
304 // escapeSheetName enclose sheet name in single quotation marks if the giving
305 // worksheet name includes spaces or non-alphabetical characters.
306 func escapeSheetName(name string) string {
307         if strings.IndexFunc(name, func(r rune) bool {
308                 return !unicode.IsLetter(r) && !unicode.IsNumber(r)
309         }) != -1 {
310                 return "'" + strings.ReplaceAll(name, "'", "''") + "'"
311         }
312         return name
313 }
314
315 // adjustFormulaColumnName adjust column name in the formula reference.
316 func adjustFormulaColumnName(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
317         if name == "" || (!abs && keepRelative) {
318                 return "", operand + name, abs, nil
319         }
320         col, err := ColumnNameToNumber(name)
321         if err != nil {
322                 return "", operand, false, err
323         }
324         if dir == columns && col >= num {
325                 col += offset
326                 colName, err := ColumnNumberToName(col)
327                 return "", operand + colName, false, err
328         }
329         return "", operand + name, false, nil
330 }
331
332 // adjustFormulaRowNumber adjust row number in the formula reference.
333 func adjustFormulaRowNumber(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
334         if name == "" || (!abs && keepRelative) {
335                 return "", operand + name, abs, nil
336         }
337         row, _ := strconv.Atoi(name)
338         if dir == rows && row >= num {
339                 row += offset
340                 if row <= 0 || row > TotalRows {
341                         return "", operand + name, false, ErrMaxRows
342                 }
343                 return "", operand + strconv.Itoa(row), false, nil
344         }
345         return "", operand + name, false, nil
346 }
347
348 // adjustFormulaOperandRef adjust cell reference in the operand tokens for the formula.
349 func adjustFormulaOperandRef(row, col, operand string, abs, keepRelative bool, dir adjustDirection, num int, offset int) (string, string, string, bool, error) {
350         var err error
351         col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
352         if err != nil {
353                 return row, col, operand, abs, err
354         }
355         row, operand, abs, err = adjustFormulaRowNumber(row, operand, abs, keepRelative, dir, num, offset)
356         return row, col, operand, abs, err
357 }
358
359 // adjustFormulaOperand adjust range operand tokens for the formula.
360 func (f *File) adjustFormulaOperand(sheet, sheetN string, keepRelative bool, token efp.Token, dir adjustDirection, num int, offset int) (string, error) {
361         var (
362                 err                          error
363                 abs                          bool
364                 sheetName, col, row, operand string
365                 cell                         = token.TValue
366                 tokens                       = strings.Split(token.TValue, "!")
367         )
368         if len(tokens) == 2 { // have a worksheet
369                 sheetName, cell = tokens[0], tokens[1]
370                 operand = escapeSheetName(sheetName) + "!"
371         }
372         if sheet != sheetN && sheet != sheetName {
373                 return operand + cell, err
374         }
375         for _, r := range cell {
376                 if r == '$' {
377                         if col, operand, _, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset); err != nil {
378                                 return operand, err
379                         }
380                         abs = true
381                         operand += string(r)
382                         continue
383                 }
384                 if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') {
385                         col += string(r)
386                         continue
387                 }
388                 if '0' <= r && r <= '9' {
389                         row += string(r)
390                         col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
391                         if err != nil {
392                                 return operand, err
393                         }
394                         continue
395                 }
396                 if row, col, operand, abs, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset); err != nil {
397                         return operand, err
398                 }
399                 operand += string(r)
400         }
401         _, _, operand, _, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset)
402         return operand, err
403 }
404
405 // adjustFormulaRef returns adjusted formula by giving adjusting direction and
406 // the base number of column or row, and offset.
407 func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool, dir adjustDirection, num, offset int) (string, error) {
408         var (
409                 val          string
410                 definedNames []string
411                 ps           = efp.ExcelParser()
412         )
413         for _, definedName := range f.GetDefinedName() {
414                 if definedName.Scope == "Workbook" || definedName.Scope == sheet {
415                         definedNames = append(definedNames, definedName.Name)
416                 }
417         }
418         for _, token := range ps.Parse(formula) {
419                 if token.TType == efp.TokenTypeUnknown {
420                         val = formula
421                         break
422                 }
423                 if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
424                         if inStrSlice(definedNames, token.TValue, true) != -1 {
425                                 val += token.TValue
426                                 continue
427                         }
428                         if strings.ContainsAny(token.TValue, "[]") {
429                                 val += token.TValue
430                                 continue
431                         }
432                         operand, err := f.adjustFormulaOperand(sheet, sheetN, keepRelative, token, dir, num, offset)
433                         if err != nil {
434                                 return val, err
435                         }
436                         val += operand
437                         continue
438                 }
439                 if isFunctionStart(token) {
440                         val += token.TValue + string(efp.ParenOpen)
441                         continue
442                 }
443                 if isFunctionStop(token) {
444                         val += token.TValue + string(efp.ParenClose)
445                         continue
446                 }
447                 if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
448                         val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble)
449                         continue
450                 }
451                 val += token.TValue
452         }
453         return val, nil
454 }
455
456 // adjustHyperlinks provides a function to update hyperlinks when inserting or
457 // deleting rows or columns.
458 func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
459         // short path
460         if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 {
461                 return
462         }
463
464         // order is important
465         if offset < 0 {
466                 for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- {
467                         linkData := ws.Hyperlinks.Hyperlink[i]
468                         colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
469
470                         if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
471                                 f.deleteSheetRelationships(sheet, linkData.RID)
472                                 if len(ws.Hyperlinks.Hyperlink) > 1 {
473                                         ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i],
474                                                 ws.Hyperlinks.Hyperlink[i+1:]...)
475                                 } else {
476                                         ws.Hyperlinks = nil
477                                 }
478                         }
479                 }
480         }
481         if ws.Hyperlinks == nil {
482                 return
483         }
484         for i := range ws.Hyperlinks.Hyperlink {
485                 link := &ws.Hyperlinks.Hyperlink[i] // get reference
486                 link.Ref, _ = f.adjustFormulaRef(sheet, sheet, link.Ref, false, dir, num, offset)
487         }
488 }
489
490 // adjustTable provides a function to update the table when inserting or
491 // deleting rows or columns.
492 func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
493         if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
494                 return nil
495         }
496         for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
497                 tbl := ws.TableParts.TableParts[idx]
498                 target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
499                 tableXML := strings.ReplaceAll(target, "..", "xl")
500                 content, ok := f.Pkg.Load(tableXML)
501                 if !ok {
502                         continue
503                 }
504                 t := xlsxTable{}
505                 if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
506                         Decode(&t); err != nil && err != io.EOF {
507                         return err
508                 }
509                 coordinates, err := rangeRefToCoordinates(t.Ref)
510                 if err != nil {
511                         return err
512                 }
513                 // Remove the table when deleting the header row of the table
514                 if dir == rows && num == coordinates[0] && offset == -1 {
515                         ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
516                         ws.TableParts.Count = len(ws.TableParts.TableParts)
517                         idx--
518                         continue
519                 }
520                 coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
521                 x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
522                 if y2-y1 < 1 || x2-x1 < 0 {
523                         ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
524                         ws.TableParts.Count = len(ws.TableParts.TableParts)
525                         idx--
526                         continue
527                 }
528                 t.Ref, _ = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
529                 if t.AutoFilter != nil {
530                         t.AutoFilter.Ref = t.Ref
531                 }
532                 _ = f.setTableColumns(sheet, true, x1, y1, x2, &t)
533                 // Currently doesn't support query table
534                 t.TableType, t.TotalsRowCount, t.ConnectionID = "", 0, 0
535                 table, _ := xml.Marshal(t)
536                 f.saveFileList(tableXML, table)
537         }
538         return nil
539 }
540
541 // adjustAutoFilter provides a function to update the auto filter when
542 // inserting or deleting rows or columns.
543 func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
544         if ws.AutoFilter == nil {
545                 return nil
546         }
547
548         coordinates, err := rangeRefToCoordinates(ws.AutoFilter.Ref)
549         if err != nil {
550                 return err
551         }
552         x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
553
554         if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
555                 ws.AutoFilter = nil
556                 for rowIdx := range ws.SheetData.Row {
557                         rowData := &ws.SheetData.Row[rowIdx]
558                         if rowData.R > y1 && rowData.R <= y2 {
559                                 rowData.Hidden = false
560                         }
561                 }
562                 return err
563         }
564
565         coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
566         x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
567
568         ws.AutoFilter.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
569         return err
570 }
571
572 // adjustAutoFilterHelper provides a function for adjusting auto filter to
573 // compare and calculate cell reference by the giving adjusting direction,
574 // operation reference and offset.
575 func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
576         if dir == rows {
577                 if coordinates[1] >= num {
578                         coordinates[1] += offset
579                 }
580                 if coordinates[3] >= num {
581                         coordinates[3] += offset
582                 }
583                 return coordinates
584         }
585         if coordinates[0] >= num {
586                 coordinates[0] += offset
587         }
588         if coordinates[2] >= num {
589                 coordinates[2] += offset
590         }
591         return coordinates
592 }
593
594 // adjustMergeCells provides a function to update merged cells when inserting
595 // or deleting rows or columns.
596 func (f *File) adjustMergeCells(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
597         if ws.MergeCells == nil {
598                 return nil
599         }
600
601         for i := 0; i < len(ws.MergeCells.Cells); i++ {
602                 mergedCells := ws.MergeCells.Cells[i]
603                 mergedCellsRef := mergedCells.Ref
604                 if !strings.Contains(mergedCellsRef, ":") {
605                         mergedCellsRef += ":" + mergedCellsRef
606                 }
607                 coordinates, err := rangeRefToCoordinates(mergedCellsRef)
608                 if err != nil {
609                         return err
610                 }
611                 x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
612                 if dir == rows {
613                         if y1 == num && y2 == num && offset < 0 {
614                                 f.deleteMergeCell(ws, i)
615                                 continue
616                         }
617
618                         y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset)
619                 } else {
620                         if x1 == num && x2 == num && offset < 0 {
621                                 f.deleteMergeCell(ws, i)
622                                 continue
623                         }
624
625                         x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset)
626                 }
627                 if x1 == x2 && y1 == y2 {
628                         f.deleteMergeCell(ws, i)
629                         i--
630                         continue
631                 }
632                 mergedCells.rect = []int{x1, y1, x2, y2}
633                 if mergedCells.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
634                         return err
635                 }
636         }
637         return nil
638 }
639
640 // adjustMergeCellsHelper provides a function for adjusting merge cells to
641 // compare and calculate cell reference by the given pivot, operation reference and
642 // offset.
643 func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) {
644         if p2 < p1 {
645                 p1, p2 = p2, p1
646         }
647
648         if offset >= 0 {
649                 if num <= p1 {
650                         p1 += offset
651                         p2 += offset
652                 } else if num <= p2 {
653                         p2 += offset
654                 }
655                 return p1, p2
656         }
657         if num < p1 || (num == p1 && num == p2) {
658                 p1 += offset
659                 p2 += offset
660         } else if num <= p2 {
661                 p2 += offset
662         }
663         return p1, p2
664 }
665
666 // deleteMergeCell provides a function to delete merged cell by given index.
667 func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
668         if idx < 0 {
669                 return
670         }
671         if len(ws.MergeCells.Cells) > idx {
672                 ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...)
673                 ws.MergeCells.Count = len(ws.MergeCells.Cells)
674         }
675 }
676
677 // adjustCellName returns updated cell name by giving column/row number and
678 // offset on inserting or deleting rows or columns.
679 func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string, error) {
680         if dir == rows {
681                 if rn := r + offset; rn > 0 {
682                         return CoordinatesToCellName(c, rn)
683                 }
684         }
685         return CoordinatesToCellName(c+offset, r)
686 }
687
688 // adjustCalcChain provides a function to update the calculation chain when
689 // inserting or deleting rows or columns.
690 func (f *File) adjustCalcChain(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
691         if f.CalcChain == nil {
692                 return nil
693         }
694         // If sheet ID is omitted, it is assumed to be the same as the i value of
695         // the previous cell.
696         var prevSheetID int
697         for i := 0; i < len(f.CalcChain.C); i++ {
698                 c := f.CalcChain.C[i]
699                 if c.I == 0 {
700                         c.I = prevSheetID
701                 }
702                 prevSheetID = c.I
703                 if c.I != sheetID {
704                         continue
705                 }
706                 colNum, rowNum, err := CellNameToCoordinates(c.R)
707                 if err != nil {
708                         return err
709                 }
710                 if dir == rows && num <= rowNum {
711                         if num == rowNum && offset == -1 {
712                                 _ = f.deleteCalcChain(c.I, c.R)
713                                 i--
714                                 continue
715                         }
716                         f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
717                 }
718                 if dir == columns && num <= colNum {
719                         if num == colNum && offset == -1 {
720                                 _ = f.deleteCalcChain(c.I, c.R)
721                                 i--
722                                 continue
723                         }
724                         f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
725                 }
726         }
727         return nil
728 }
729
730 // adjustVolatileDepsTopic updates the volatile dependencies topic when
731 // inserting or deleting rows or columns.
732 func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection, indexes []int) (int, error) {
733         num, offset, i1, i2, i3, i4 := indexes[0], indexes[1], indexes[2], indexes[3], indexes[4], indexes[5]
734         colNum, rowNum, err := CellNameToCoordinates(cell)
735         if err != nil {
736                 return i4, err
737         }
738         if dir == rows && num <= rowNum {
739                 if num == rowNum && offset == -1 {
740                         vt.deleteVolTopicRef(i1, i2, i3, i4)
741                         i4--
742                         return i4, err
743                 }
744                 vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
745         }
746         if dir == columns && num <= colNum {
747                 if num == colNum && offset == -1 {
748                         vt.deleteVolTopicRef(i1, i2, i3, i4)
749                         i4--
750                         return i4, err
751                 }
752                 if name, _ := adjustCellName(cell, dir, colNum, rowNum, offset); name != "" {
753                         vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
754                 }
755         }
756         return i4, err
757 }
758
759 // adjustVolatileDeps updates the volatile dependencies when inserting or
760 // deleting rows or columns.
761 func (f *File) adjustVolatileDeps(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
762         volTypes, err := f.volatileDepsReader()
763         if err != nil || volTypes == nil {
764                 return err
765         }
766         for i1 := 0; i1 < len(volTypes.VolType); i1++ {
767                 for i2 := 0; i2 < len(volTypes.VolType[i1].Main); i2++ {
768                         for i3 := 0; i3 < len(volTypes.VolType[i1].Main[i2].Tp); i3++ {
769                                 for i4 := 0; i4 < len(volTypes.VolType[i1].Main[i2].Tp[i3].Tr); i4++ {
770                                         ref := volTypes.VolType[i1].Main[i2].Tp[i3].Tr[i4]
771                                         if ref.S != sheetID {
772                                                 continue
773                                         }
774                                         if i4, err = volTypes.adjustVolatileDepsTopic(ref.R, dir, []int{num, offset, i1, i2, i3, i4}); err != nil {
775                                                 return err
776                                         }
777                                 }
778                         }
779                 }
780         }
781         return nil
782 }
783
784 // adjustConditionalFormats updates the cell reference of the worksheet
785 // conditional formatting when inserting or deleting rows or columns.
786 func (f *File) adjustConditionalFormats(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
787         for i := 0; i < len(ws.ConditionalFormatting); i++ {
788                 cf := ws.ConditionalFormatting[i]
789                 if cf == nil {
790                         continue
791                 }
792                 ref, del, err := f.adjustCellRef(cf.SQRef, dir, num, offset)
793                 if err != nil {
794                         return err
795                 }
796                 if del {
797                         ws.ConditionalFormatting = append(ws.ConditionalFormatting[:i],
798                                 ws.ConditionalFormatting[i+1:]...)
799                         i--
800                         continue
801                 }
802                 ws.ConditionalFormatting[i].SQRef = ref
803         }
804         return nil
805 }
806
807 // adjustDrawings updates the starting anchor of the two cell anchor pictures
808 // and charts object when inserting or deleting rows or columns.
809 func (from *xlsxFrom) adjustDrawings(dir adjustDirection, num, offset int, editAs string) (bool, error) {
810         var ok bool
811         if dir == columns && from.Col+1 >= num && from.Col+offset >= 0 {
812                 if from.Col+offset >= MaxColumns {
813                         return false, ErrColumnNumber
814                 }
815                 from.Col += offset
816                 ok = editAs == "oneCell"
817         }
818         if dir == rows && from.Row+1 >= num && from.Row+offset >= 0 {
819                 if from.Row+offset >= TotalRows {
820                         return false, ErrMaxRows
821                 }
822                 from.Row += offset
823                 ok = editAs == "oneCell"
824         }
825         return ok, nil
826 }
827
828 // adjustDrawings updates the ending anchor of the two cell anchor pictures
829 // and charts object when inserting or deleting rows or columns.
830 func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, editAs string, ok bool) error {
831         if dir == columns && to.Col+1 >= num && to.Col+offset >= 0 && ok {
832                 if to.Col+offset >= MaxColumns {
833                         return ErrColumnNumber
834                 }
835                 to.Col += offset
836         }
837         if dir == rows && to.Row+1 >= num && to.Row+offset >= 0 && ok {
838                 if to.Row+offset >= TotalRows {
839                         return ErrMaxRows
840                 }
841                 to.Row += offset
842         }
843         return nil
844 }
845
846 // adjustDrawings updates the two cell anchor pictures and charts object when
847 // inserting or deleting rows or columns.
848 func (a *xdrCellAnchor) adjustDrawings(dir adjustDirection, num, offset int) error {
849         editAs := a.EditAs
850         if a.From == nil || a.To == nil || editAs == "absolute" {
851                 return nil
852         }
853         ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
854         if err != nil {
855                 return err
856         }
857         return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "")
858 }
859
860 // adjustDrawings updates the existing two cell anchor pictures and charts
861 // object when inserting or deleting rows or columns.
862 func (a *xlsxCellAnchorPos) adjustDrawings(dir adjustDirection, num, offset int, editAs string) error {
863         if a.From == nil || a.To == nil || editAs == "absolute" {
864                 return nil
865         }
866         ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
867         if err != nil {
868                 return err
869         }
870         return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "")
871 }
872
873 // adjustDrawings updates the pictures and charts object when inserting or
874 // deleting rows or columns.
875 func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
876         if ws.Drawing == nil {
877                 return nil
878         }
879         target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
880         drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
881         var (
882                 err  error
883                 wsDr *xlsxWsDr
884         )
885         if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
886                 return err
887         }
888         anchorCb := func(a *xdrCellAnchor) error {
889                 if a.GraphicFrame == "" {
890                         return a.adjustDrawings(dir, num, offset)
891                 }
892                 deCellAnchor := decodeCellAnchor{}
893                 deCellAnchorPos := decodeCellAnchorPos{}
894                 _ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + a.GraphicFrame + "</decodeCellAnchor>")).Decode(&deCellAnchor)
895                 _ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchorPos>" + a.GraphicFrame + "</decodeCellAnchorPos>")).Decode(&deCellAnchorPos)
896                 xlsxCellAnchorPos := xlsxCellAnchorPos(deCellAnchorPos)
897                 for i := 0; i < len(xlsxCellAnchorPos.AlternateContent); i++ {
898                         xlsxCellAnchorPos.AlternateContent[i].XMLNSMC = SourceRelationshipCompatibility.Value
899                 }
900                 if deCellAnchor.From != nil {
901                         xlsxCellAnchorPos.From = &xlsxFrom{
902                                 Col: deCellAnchor.From.Col, ColOff: deCellAnchor.From.ColOff,
903                                 Row: deCellAnchor.From.Row, RowOff: deCellAnchor.From.RowOff,
904                         }
905                 }
906                 if deCellAnchor.To != nil {
907                         xlsxCellAnchorPos.To = &xlsxTo{
908                                 Col: deCellAnchor.To.Col, ColOff: deCellAnchor.To.ColOff,
909                                 Row: deCellAnchor.To.Row, RowOff: deCellAnchor.To.RowOff,
910                         }
911                 }
912                 if err = xlsxCellAnchorPos.adjustDrawings(dir, num, offset, a.EditAs); err != nil {
913                         return err
914                 }
915                 cellAnchor, _ := xml.Marshal(xlsxCellAnchorPos)
916                 a.GraphicFrame = strings.TrimSuffix(strings.TrimPrefix(string(cellAnchor), "<xlsxCellAnchorPos>"), "</xlsxCellAnchorPos>")
917                 return err
918         }
919         for _, anchor := range wsDr.TwoCellAnchor {
920                 if err = anchorCb(anchor); err != nil {
921                         return err
922                 }
923         }
924         return nil
925 }
926
927 // adjustDefinedNames updates the cell reference of the defined names when
928 // inserting or deleting rows or columns.
929 func (f *File) adjustDefinedNames(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
930         wb, err := f.workbookReader()
931         if err != nil {
932                 return err
933         }
934         if wb.DefinedNames != nil {
935                 for i := 0; i < len(wb.DefinedNames.DefinedName); i++ {
936                         data := wb.DefinedNames.DefinedName[i].Data
937                         if data, err = f.adjustFormulaRef(sheet, "", data, true, dir, num, offset); err == nil {
938                                 wb.DefinedNames.DefinedName[i].Data = data
939                         }
940                 }
941         }
942         return nil
943 }