adjust.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. package excelize
  12. import (
  13. "bytes"
  14. "encoding/xml"
  15. "io"
  16. "strings"
  17. )
  18. type adjustDirection bool
  19. const (
  20. columns adjustDirection = false
  21. rows adjustDirection = true
  22. )
  23. // adjustHelper provides a function to adjust rows and columns dimensions,
  24. // hyperlinks, merged cells and auto filter when inserting or deleting rows or
  25. // columns.
  26. //
  27. // sheet: Worksheet name that we're editing
  28. // column: Index number of the column we're inserting/deleting before
  29. // row: Index number of the row we're inserting/deleting before
  30. // offset: Number of rows/column to insert/delete negative values indicate deletion
  31. //
  32. // TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
  33. func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
  34. ws, err := f.workSheetReader(sheet)
  35. if err != nil {
  36. return err
  37. }
  38. sheetID := f.getSheetID(sheet)
  39. if dir == rows {
  40. err = f.adjustRowDimensions(ws, num, offset)
  41. } else {
  42. err = f.adjustColDimensions(ws, num, offset)
  43. }
  44. if err != nil {
  45. return err
  46. }
  47. f.adjustHyperlinks(ws, sheet, dir, num, offset)
  48. f.adjustTable(ws, sheet, dir, num, offset)
  49. if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
  50. return err
  51. }
  52. if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil {
  53. return err
  54. }
  55. if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
  56. return err
  57. }
  58. checkSheet(ws)
  59. _ = checkRow(ws)
  60. if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
  61. ws.MergeCells = nil
  62. }
  63. return nil
  64. }
  65. // adjustCols provides a function to update column style when inserting or
  66. // deleting columns.
  67. func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error {
  68. if ws.Cols == nil {
  69. return nil
  70. }
  71. for i := 0; i < len(ws.Cols.Col); i++ {
  72. if offset > 0 {
  73. if ws.Cols.Col[i].Max+1 == col {
  74. ws.Cols.Col[i].Max += offset
  75. continue
  76. }
  77. if ws.Cols.Col[i].Min >= col {
  78. ws.Cols.Col[i].Min += offset
  79. ws.Cols.Col[i].Max += offset
  80. continue
  81. }
  82. if ws.Cols.Col[i].Min < col && ws.Cols.Col[i].Max >= col {
  83. ws.Cols.Col[i].Max += offset
  84. }
  85. }
  86. if offset < 0 {
  87. if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
  88. if len(ws.Cols.Col) > 1 {
  89. ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
  90. } else {
  91. ws.Cols.Col = nil
  92. }
  93. i--
  94. continue
  95. }
  96. if ws.Cols.Col[i].Min > col {
  97. ws.Cols.Col[i].Min += offset
  98. ws.Cols.Col[i].Max += offset
  99. continue
  100. }
  101. if ws.Cols.Col[i].Min <= col && ws.Cols.Col[i].Max >= col {
  102. ws.Cols.Col[i].Max += offset
  103. }
  104. }
  105. }
  106. return nil
  107. }
  108. // adjustColDimensions provides a function to update column dimensions when
  109. // inserting or deleting rows or columns.
  110. func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error {
  111. for rowIdx := range ws.SheetData.Row {
  112. for _, v := range ws.SheetData.Row[rowIdx].C {
  113. if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol {
  114. if newCol := cellCol + offset; newCol > 0 && newCol > MaxColumns {
  115. return ErrColumnNumber
  116. }
  117. }
  118. }
  119. }
  120. for rowIdx := range ws.SheetData.Row {
  121. for colIdx, v := range ws.SheetData.Row[rowIdx].C {
  122. if cellCol, cellRow, _ := CellNameToCoordinates(v.R); col <= cellCol {
  123. if newCol := cellCol + offset; newCol > 0 {
  124. ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
  125. }
  126. }
  127. }
  128. }
  129. return f.adjustCols(ws, col, offset)
  130. }
  131. // adjustRowDimensions provides a function to update row dimensions when
  132. // inserting or deleting rows or columns.
  133. func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) error {
  134. totalRows := len(ws.SheetData.Row)
  135. if totalRows == 0 {
  136. return nil
  137. }
  138. lastRow := &ws.SheetData.Row[totalRows-1]
  139. if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow >= TotalRows {
  140. return ErrMaxRows
  141. }
  142. for i := 0; i < len(ws.SheetData.Row); i++ {
  143. r := &ws.SheetData.Row[i]
  144. if newRow := r.R + offset; r.R >= row && newRow > 0 {
  145. f.adjustSingleRowDimensions(r, newRow)
  146. }
  147. }
  148. return nil
  149. }
  150. // adjustSingleRowDimensions provides a function to adjust single row dimensions.
  151. func (f *File) adjustSingleRowDimensions(r *xlsxRow, num int) {
  152. r.R = num
  153. for i, col := range r.C {
  154. colName, _, _ := SplitCellName(col.R)
  155. r.C[i].R, _ = JoinCellName(colName, num)
  156. }
  157. }
  158. // adjustHyperlinks provides a function to update hyperlinks when inserting or
  159. // deleting rows or columns.
  160. func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
  161. // short path
  162. if ws.Hyperlinks == nil || len(ws.Hyperlinks.Hyperlink) == 0 {
  163. return
  164. }
  165. // order is important
  166. if offset < 0 {
  167. for i := len(ws.Hyperlinks.Hyperlink) - 1; i >= 0; i-- {
  168. linkData := ws.Hyperlinks.Hyperlink[i]
  169. colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
  170. if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
  171. f.deleteSheetRelationships(sheet, linkData.RID)
  172. if len(ws.Hyperlinks.Hyperlink) > 1 {
  173. ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink[:i],
  174. ws.Hyperlinks.Hyperlink[i+1:]...)
  175. } else {
  176. ws.Hyperlinks = nil
  177. }
  178. }
  179. }
  180. }
  181. if ws.Hyperlinks == nil {
  182. return
  183. }
  184. for i := range ws.Hyperlinks.Hyperlink {
  185. link := &ws.Hyperlinks.Hyperlink[i] // get reference
  186. colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
  187. if dir == rows {
  188. if rowNum >= num {
  189. link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
  190. }
  191. } else {
  192. if colNum >= num {
  193. link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
  194. }
  195. }
  196. }
  197. }
  198. // adjustTable provides a function to update the table when inserting or
  199. // deleting rows or columns.
  200. func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
  201. if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
  202. return
  203. }
  204. for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
  205. tbl := ws.TableParts.TableParts[idx]
  206. target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
  207. tableXML := strings.ReplaceAll(target, "..", "xl")
  208. content, ok := f.Pkg.Load(tableXML)
  209. if !ok {
  210. continue
  211. }
  212. t := xlsxTable{}
  213. if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
  214. Decode(&t); err != nil && err != io.EOF {
  215. return
  216. }
  217. coordinates, err := rangeRefToCoordinates(t.Ref)
  218. if err != nil {
  219. return
  220. }
  221. // Remove the table when deleting the header row of the table
  222. if dir == rows && num == coordinates[0] {
  223. ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
  224. ws.TableParts.Count = len(ws.TableParts.TableParts)
  225. idx--
  226. continue
  227. }
  228. coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
  229. x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  230. if y2-y1 < 2 || x2-x1 < 1 {
  231. ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
  232. ws.TableParts.Count = len(ws.TableParts.TableParts)
  233. idx--
  234. continue
  235. }
  236. t.Ref, _ = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
  237. if t.AutoFilter != nil {
  238. t.AutoFilter.Ref = t.Ref
  239. }
  240. _, _ = f.setTableHeader(sheet, true, x1, y1, x2)
  241. table, _ := xml.Marshal(t)
  242. f.saveFileList(tableXML, table)
  243. }
  244. }
  245. // adjustAutoFilter provides a function to update the auto filter when
  246. // inserting or deleting rows or columns.
  247. func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
  248. if ws.AutoFilter == nil {
  249. return nil
  250. }
  251. coordinates, err := rangeRefToCoordinates(ws.AutoFilter.Ref)
  252. if err != nil {
  253. return err
  254. }
  255. x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  256. if (dir == rows && y1 == num && offset < 0) || (dir == columns && x1 == num && x2 == num) {
  257. ws.AutoFilter = nil
  258. for rowIdx := range ws.SheetData.Row {
  259. rowData := &ws.SheetData.Row[rowIdx]
  260. if rowData.R > y1 && rowData.R <= y2 {
  261. rowData.Hidden = false
  262. }
  263. }
  264. return err
  265. }
  266. coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
  267. x1, y1, x2, y2 = coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  268. ws.AutoFilter.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
  269. return err
  270. }
  271. // adjustAutoFilterHelper provides a function for adjusting auto filter to
  272. // compare and calculate cell reference by the given adjust direction, operation
  273. // reference and offset.
  274. func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int {
  275. if dir == rows {
  276. if coordinates[1] >= num {
  277. coordinates[1] += offset
  278. }
  279. if coordinates[3] >= num {
  280. coordinates[3] += offset
  281. }
  282. return coordinates
  283. }
  284. if coordinates[0] >= num {
  285. coordinates[0] += offset
  286. }
  287. if coordinates[2] >= num {
  288. coordinates[2] += offset
  289. }
  290. return coordinates
  291. }
  292. // adjustMergeCells provides a function to update merged cells when inserting
  293. // or deleting rows or columns.
  294. func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
  295. if ws.MergeCells == nil {
  296. return nil
  297. }
  298. for i := 0; i < len(ws.MergeCells.Cells); i++ {
  299. mergedCells := ws.MergeCells.Cells[i]
  300. mergedCellsRef := mergedCells.Ref
  301. if !strings.Contains(mergedCellsRef, ":") {
  302. mergedCellsRef += ":" + mergedCellsRef
  303. }
  304. coordinates, err := rangeRefToCoordinates(mergedCellsRef)
  305. if err != nil {
  306. return err
  307. }
  308. x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
  309. if dir == rows {
  310. if y1 == num && y2 == num && offset < 0 {
  311. f.deleteMergeCell(ws, i)
  312. i--
  313. continue
  314. }
  315. y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset)
  316. } else {
  317. if x1 == num && x2 == num && offset < 0 {
  318. f.deleteMergeCell(ws, i)
  319. i--
  320. continue
  321. }
  322. x1, x2 = f.adjustMergeCellsHelper(x1, x2, num, offset)
  323. }
  324. if x1 == x2 && y1 == y2 {
  325. f.deleteMergeCell(ws, i)
  326. i--
  327. continue
  328. }
  329. mergedCells.rect = []int{x1, y1, x2, y2}
  330. if mergedCells.Ref, err = f.coordinatesToRangeRef([]int{x1, y1, x2, y2}); err != nil {
  331. return err
  332. }
  333. }
  334. return nil
  335. }
  336. // adjustMergeCellsHelper provides a function for adjusting merge cells to
  337. // compare and calculate cell reference by the given pivot, operation reference and
  338. // offset.
  339. func (f *File) adjustMergeCellsHelper(p1, p2, num, offset int) (int, int) {
  340. if p2 < p1 {
  341. p1, p2 = p2, p1
  342. }
  343. if offset >= 0 {
  344. if num <= p1 {
  345. p1 += offset
  346. p2 += offset
  347. } else if num <= p2 {
  348. p2 += offset
  349. }
  350. return p1, p2
  351. }
  352. if num < p1 || (num == p1 && num == p2) {
  353. p1 += offset
  354. p2 += offset
  355. } else if num <= p2 {
  356. p2 += offset
  357. }
  358. return p1, p2
  359. }
  360. // deleteMergeCell provides a function to delete merged cell by given index.
  361. func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
  362. if idx < 0 {
  363. return
  364. }
  365. if len(ws.MergeCells.Cells) > idx {
  366. ws.MergeCells.Cells = append(ws.MergeCells.Cells[:idx], ws.MergeCells.Cells[idx+1:]...)
  367. ws.MergeCells.Count = len(ws.MergeCells.Cells)
  368. }
  369. }
  370. // adjustCalcChain provides a function to update the calculation chain when
  371. // inserting or deleting rows or columns.
  372. func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error {
  373. if f.CalcChain == nil {
  374. return nil
  375. }
  376. for index, c := range f.CalcChain.C {
  377. if c.I != sheetID {
  378. continue
  379. }
  380. colNum, rowNum, err := CellNameToCoordinates(c.R)
  381. if err != nil {
  382. return err
  383. }
  384. if dir == rows && num <= rowNum {
  385. if newRow := rowNum + offset; newRow > 0 {
  386. f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow)
  387. }
  388. }
  389. if dir == columns && num <= colNum {
  390. if newCol := colNum + offset; newCol > 0 {
  391. f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum)
  392. }
  393. }
  394. }
  395. return nil
  396. }