Skip to main content

excelize_rs/
adjust.rs

1//! Row/column adjustment helpers.
2//!
3//! This module corresponds to `adjust.go` in the Go implementation. It updates
4//! cell references, formulas, merged cells, auto filters, tables, hyperlinks,
5//! conditional formats, data validations and the calculation chain when rows or
6//! columns are inserted or deleted.
7
8#![allow(dead_code, unused_imports)]
9
10use quick_xml::de::from_reader as xml_from_reader;
11use quick_xml::se::to_string as xml_to_string;
12
13use crate::constants::{MAX_COLUMNS, TOTAL_ROWS};
14use crate::errors::{ErrColumnNumber, ErrMaxRows, Result, new_not_worksheet_error};
15use crate::file::{File, namespace_strict_to_transitional};
16use crate::lib_util::{
17    cell_name_to_coordinates, column_number_to_name, coordinates_to_cell_name,
18    coordinates_to_range_ref, join_cell_name, range_ref_to_coordinates, split_cell_name,
19};
20use crate::xml::common::XlsxInnerXml;
21use crate::xml::drawing::{XdrCellAnchor, XlsxFrom, XlsxTo};
22use crate::xml::table::{XlsxAutoFilter, XlsxTable};
23use crate::xml::workbook::XlsxWorkbook;
24use crate::xml::worksheet::{
25    XlsxC, XlsxConditionalFormatting, XlsxDataValidation, XlsxDataValidations, XlsxF,
26    XlsxHyperlinks, XlsxMergeCells, XlsxRow, XlsxTableParts, XlsxWorksheet,
27};
28
29/// Direction of the adjustment operation.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum AdjustDirection {
32    Columns,
33    Rows,
34}
35
36impl File {
37    /// Main entry point used by row/column insertion and deletion routines.
38    pub(crate) fn adjust_helper(
39        &self,
40        sheet: &str,
41        dir: AdjustDirection,
42        num: i32,
43        offset: i32,
44    ) -> Result<()> {
45        let path =
46            self.get_sheet_xml_path(sheet)
47                .ok_or_else(|| crate::errors::ErrSheetNotExist {
48                    sheet_name: sheet.to_string(),
49                })?;
50        let mut ws = self.work_sheet_reader(sheet)?;
51        self.clear_calc_cache();
52        let sheet_id = self.get_sheet_id(sheet);
53
54        if dir == AdjustDirection::Rows {
55            self.adjust_row_dimensions(sheet, &mut ws, num, offset)?;
56        } else {
57            self.adjust_col_dimensions(sheet, &mut ws, num, offset)?;
58        }
59
60        self.adjust_hyperlinks(&mut ws, sheet, dir, num, offset);
61        let _ = self.check_sheet(&mut ws);
62        let _ = self.check_row(&mut ws);
63
64        self.adjust_conditional_formats(&mut ws, sheet, dir, num, offset, sheet_id)?;
65        self.adjust_data_validations(&mut ws, sheet, dir, num, offset, sheet_id)?;
66        self.adjust_defined_names(sheet, dir, num, offset)?;
67        self.adjust_drawings(&mut ws, sheet, dir, num, offset)?;
68        self.adjust_merge_cells(&mut ws, sheet, dir, num, offset, sheet_id)?;
69        self.adjust_auto_filter(&mut ws, sheet, dir, num, offset, sheet_id)?;
70        self.adjust_calc_chain(&mut ws, sheet, dir, num, offset, sheet_id)?;
71        self.adjust_table(&mut ws, sheet, dir, num, offset, sheet_id)?;
72        self.adjust_volatile_deps(&mut ws, sheet, dir, num, offset, sheet_id)?;
73
74        if let Some(merge_cells) = &ws.merge_cells {
75            if merge_cells.cells.is_empty() {
76                ws.merge_cells = None;
77            }
78        }
79
80        self.sheet.insert(path, ws);
81        Ok(())
82    }
83
84    // ------------------------------------------------------------------
85    // Dimension adjustments
86    // ------------------------------------------------------------------
87
88    fn adjust_cols(&self, ws: &mut XlsxWorksheet, col: i32, offset: i32) -> Result<()> {
89        let cols = match ws.cols.as_mut() {
90            Some(c) => c,
91            None => return Ok(()),
92        };
93
94        let mut i = 0;
95        while i < cols.col.len() {
96            if offset > 0 {
97                if cols.col[i].min >= col as i64 {
98                    let new_min = cols.col[i].min + offset as i64;
99                    if new_min > MAX_COLUMNS as i64 {
100                        cols.col.remove(i);
101                        continue;
102                    }
103                    cols.col[i].min = new_min;
104                }
105                if cols.col[i].max >= col as i64 || cols.col[i].max + 1 == col as i64 {
106                    let new_max = cols.col[i].max + offset as i64;
107                    cols.col[i].max = new_max.min(MAX_COLUMNS as i64);
108                }
109                i += 1;
110                continue;
111            }
112
113            if cols.col[i].min == col as i64 && cols.col[i].max == col as i64 {
114                cols.col.remove(i);
115                continue;
116            }
117            if cols.col[i].min > col as i64 {
118                cols.col[i].min += offset as i64;
119            }
120            if cols.col[i].max >= col as i64 {
121                cols.col[i].max += offset as i64;
122            }
123            i += 1;
124        }
125
126        if cols.col.is_empty() {
127            ws.cols = None;
128        }
129        Ok(())
130    }
131
132    fn adjust_col_dimensions(
133        &self,
134        sheet: &str,
135        ws: &mut XlsxWorksheet,
136        col: i32,
137        offset: i32,
138    ) -> Result<()> {
139        for row in &ws.sheet_data.row {
140            for cell in &row.c {
141                if let Some(r) = &cell.r {
142                    if let Ok((cell_col, _)) = cell_name_to_coordinates(r) {
143                        if col <= cell_col {
144                            let new_col = cell_col + offset;
145                            if new_col > 0 && new_col > MAX_COLUMNS {
146                                return Err(Box::new(ErrColumnNumber));
147                            }
148                        }
149                    }
150                }
151            }
152        }
153
154        for sheet_n in self.get_sheet_list() {
155            let mut worksheet = match self.work_sheet_reader(&sheet_n) {
156                Ok(w) => w,
157                Err(e) => {
158                    if e.to_string() == new_not_worksheet_error(&sheet_n) {
159                        continue;
160                    }
161                    return Err(e);
162                }
163            };
164            let path = match self.get_sheet_xml_path(&sheet_n) {
165                Some(p) => p,
166                None => continue,
167            };
168
169            for row_idx in 0..worksheet.sheet_data.row.len() {
170                for col_idx in 0..worksheet.sheet_data.row[row_idx].c.len() {
171                    let cell = &worksheet.sheet_data.row[row_idx].c[col_idx];
172                    if let Some(r) = &cell.r {
173                        if let Ok((cell_col, cell_row)) = cell_name_to_coordinates(r) {
174                            if sheet_n.eq_ignore_ascii_case(sheet) && col <= cell_col {
175                                let new_col = cell_col + offset;
176                                if new_col > 0 {
177                                    worksheet.sheet_data.row[row_idx].c[col_idx].r =
178                                        Some(coordinates_to_cell_name(new_col, cell_row, false)?);
179                                }
180                            }
181                        }
182                    }
183                    let cell_ref = &mut worksheet.sheet_data.row[row_idx].c[col_idx];
184                    self.adjust_formula(
185                        sheet,
186                        &sheet_n,
187                        cell_ref,
188                        AdjustDirection::Columns,
189                        col,
190                        offset,
191                        false,
192                    )?;
193                }
194            }
195            self.sheet.insert(path, worksheet);
196        }
197
198        // Synchronize `ws` with the updated active-sheet copy that was written
199        // back into the sheet cache during the loop above.
200        let path = self.get_sheet_xml_path(sheet).unwrap_or_default();
201        if let Some(updated) = self.sheet.get(&path) {
202            *ws = updated.clone();
203        }
204
205        self.adjust_cols(ws, col, offset)
206    }
207
208    fn adjust_row_dimensions(
209        &self,
210        sheet: &str,
211        ws: &mut XlsxWorksheet,
212        row: i32,
213        offset: i32,
214    ) -> Result<()> {
215        for sheet_n in self.get_sheet_list() {
216            if sheet_n.eq_ignore_ascii_case(sheet) {
217                continue;
218            }
219            let mut worksheet = match self.work_sheet_reader(&sheet_n) {
220                Ok(w) => w,
221                Err(e) => {
222                    if e.to_string() == new_not_worksheet_error(&sheet_n) {
223                        continue;
224                    }
225                    return Err(e);
226                }
227            };
228            let path = self.get_sheet_xml_path(&sheet_n).unwrap_or_default();
229            for i in 0..worksheet.sheet_data.row.len() {
230                let r = &mut worksheet.sheet_data.row[i];
231                self.adjust_single_row_formulas(sheet, &sheet_n, r, row, offset, false)?;
232            }
233            self.sheet.insert(path, worksheet);
234        }
235
236        let total_rows = ws.sheet_data.row.len();
237        if total_rows == 0 {
238            return Ok(());
239        }
240        if let Some(last_r) = ws.sheet_data.row[total_rows - 1].r {
241            let new_row = last_r + offset as i64;
242            if last_r >= row as i64 && new_row > 0 && new_row > TOTAL_ROWS as i64 {
243                return Err(Box::new(ErrMaxRows));
244            }
245        }
246
247        for i in 0..ws.sheet_data.row.len() {
248            if let Some(row_num) = ws.sheet_data.row[i].r {
249                let new_row = row_num + offset as i64;
250                if row_num >= row as i64 && new_row > 0 {
251                    adjust_single_row_dimensions(&mut ws.sheet_data.row[i], offset);
252                }
253            }
254            let r = &mut ws.sheet_data.row[i];
255            self.adjust_single_row_formulas(sheet, sheet, r, row, offset, false)?;
256        }
257        Ok(())
258    }
259
260    pub(crate) fn adjust_single_row_formulas(
261        &self,
262        sheet: &str,
263        sheet_n: &str,
264        r: &mut XlsxRow,
265        num: i32,
266        offset: i32,
267        si: bool,
268    ) -> Result<()> {
269        for i in 0..r.c.len() {
270            self.adjust_formula(
271                sheet,
272                sheet_n,
273                &mut r.c[i],
274                AdjustDirection::Rows,
275                num,
276                offset,
277                si,
278            )?;
279        }
280        Ok(())
281    }
282
283    // ------------------------------------------------------------------
284    // Cell reference / formula adjustment
285    // ------------------------------------------------------------------
286
287    fn adjust_cell_ref(
288        &self,
289        cell_ref: &str,
290        dir: AdjustDirection,
291        num: i32,
292        offset: i32,
293    ) -> Result<String> {
294        let mut sq_ref = Vec::new();
295        for ref_ in cell_ref.split(' ').filter(|s| !s.is_empty()) {
296            let mut expanded = ref_.to_string();
297            if !expanded.contains(':') {
298                expanded.push(':');
299                expanded.push_str(ref_);
300            }
301            let mut coordinates = range_ref_to_coordinates(&expanded)?;
302            if dir == AdjustDirection::Columns {
303                if offset < 0 && coordinates[0] == coordinates[2] && num == coordinates[0] {
304                    continue;
305                }
306                apply_offset(&mut coordinates, 0, 2, MAX_COLUMNS, num, offset);
307            } else {
308                if offset < 0 && coordinates[1] == coordinates[3] && num == coordinates[1] {
309                    continue;
310                }
311                apply_offset(&mut coordinates, 1, 3, TOTAL_ROWS, num, offset);
312            }
313            sq_ref.push(coordinates_to_range_ref(&coordinates, false)?);
314        }
315        Ok(sq_ref.join(" "))
316    }
317
318    fn adjust_formula(
319        &self,
320        sheet: &str,
321        sheet_n: &str,
322        cell: &mut XlsxC,
323        dir: AdjustDirection,
324        num: i32,
325        offset: i32,
326        si: bool,
327    ) -> Result<()> {
328        if let Some(f) = &mut cell.f {
329            if !f.content.is_empty() {
330                f.content =
331                    self.adjust_formula_ref(sheet, sheet_n, &f.content, false, dir, num, offset)?;
332            }
333            if f.r#ref.is_some() && sheet == sheet_n {
334                if let Some(ref_) = &mut f.r#ref {
335                    *ref_ = self.adjust_cell_ref(ref_, dir, num, offset)?;
336                }
337                if si {
338                    if let Some(si_val) = f.si.as_mut() {
339                        *si_val += 1;
340                    }
341                }
342            }
343        }
344        Ok(())
345    }
346
347    /// Formula reference rewriter.
348    ///
349    /// Parses the formula with the calculation-engine parser, walks the AST and
350    /// shifts every cell/range reference that belongs to the sheet being
351    /// adjusted, then re-serializes the formula.  If parsing fails the original
352    /// formula is returned unchanged so that file operations are not broken by
353    /// constructs the parser does not yet handle.
354
355    fn adjust_formula_ref(
356        &self,
357        sheet: &str,
358        sheet_n: &str,
359        formula: &str,
360        keep_relative: bool,
361        dir: AdjustDirection,
362        num: i32,
363        offset: i32,
364    ) -> Result<String> {
365        if formula.trim().is_empty() {
366            return Ok(formula.to_string());
367        }
368        let had_leading_eq = formula.starts_with('=');
369        let expr = match crate::calc::parse_formula(formula) {
370            Ok(e) => e,
371            Err(_) => return Ok(formula.to_string()),
372        };
373        let mut expr = expr;
374        adjust_expr(&mut expr, dir, num, offset, keep_relative, sheet, sheet_n);
375        let mut result = format_expr(&expr)?;
376        if had_leading_eq {
377            result.insert(0, '=');
378        }
379        Ok(result)
380    }
381
382    // ------------------------------------------------------------------
383    // Hyperlinks
384    // ------------------------------------------------------------------
385
386    fn adjust_hyperlinks(
387        &self,
388        ws: &mut XlsxWorksheet,
389        sheet: &str,
390        dir: AdjustDirection,
391        num: i32,
392        offset: i32,
393    ) {
394        if ws.hyperlinks.is_none() || ws.hyperlinks.as_ref().unwrap().hyperlink.is_empty() {
395            return;
396        }
397
398        if offset < 0 {
399            let mut i = ws.hyperlinks.as_ref().unwrap().hyperlink.len();
400            while i > 0 {
401                i -= 1;
402                let link_data = ws.hyperlinks.as_ref().unwrap().hyperlink[i].clone();
403                if let Ok((col_num, row_num)) = cell_name_to_coordinates(&link_data.r#ref) {
404                    if (dir == AdjustDirection::Rows && num == row_num)
405                        || (dir == AdjustDirection::Columns && num == col_num)
406                    {
407                        if let Some(rid) = &link_data.rid {
408                            self.delete_sheet_relationships(sheet, rid);
409                        }
410                        if ws.hyperlinks.as_ref().unwrap().hyperlink.len() > 1 {
411                            ws.hyperlinks.as_mut().unwrap().hyperlink.remove(i);
412                        } else {
413                            ws.hyperlinks = None;
414                        }
415                    }
416                }
417            }
418        }
419
420        if ws.hyperlinks.is_none() {
421            return;
422        }
423        for link in ws.hyperlinks.as_mut().unwrap().hyperlink.iter_mut() {
424            if let Ok(new_ref) =
425                self.adjust_formula_ref(sheet, sheet, &link.r#ref, false, dir, num, offset)
426            {
427                link.r#ref = new_ref;
428            }
429        }
430    }
431
432    // ------------------------------------------------------------------
433    // Tables
434    // ------------------------------------------------------------------
435
436    fn adjust_table(
437        &self,
438        ws: &mut XlsxWorksheet,
439        sheet: &str,
440        dir: AdjustDirection,
441        num: i32,
442        offset: i32,
443        _sheet_id: i32,
444    ) -> Result<()> {
445        if ws.table_parts.is_none() || ws.table_parts.as_ref().unwrap().table_part.is_empty() {
446            return Ok(());
447        }
448
449        let mut idx = 0;
450        while idx < ws.table_parts.as_ref().unwrap().table_part.len() {
451            let tbl = ws.table_parts.as_ref().unwrap().table_part[idx].clone();
452            let target =
453                self.get_sheet_relationships_target_by_id(sheet, tbl.rid.as_deref().unwrap_or(""));
454            if target.is_empty() {
455                idx += 1;
456                continue;
457            }
458            let table_xml = target.replace("..", "xl");
459            let content = self.read_xml(&table_xml);
460            if content.is_empty() {
461                idx += 1;
462                continue;
463            }
464            let mut t: XlsxTable =
465                xml_from_reader(namespace_strict_to_transitional(&content).as_slice())?;
466            let mut coordinates = range_ref_to_coordinates(&t.r#ref)?;
467
468            if dir == AdjustDirection::Rows && num == coordinates[1] && offset == -1 {
469                ws.table_parts.as_mut().unwrap().table_part.remove(idx);
470                continue;
471            }
472
473            coordinates = self.adjust_auto_filter_helper(dir, coordinates, num, offset);
474            let (x1, y1, x2, y2) = (
475                coordinates[0],
476                coordinates[1],
477                coordinates[2],
478                coordinates[3],
479            );
480            if y2 - y1 < 1 || x2 - x1 < 0 {
481                ws.table_parts.as_mut().unwrap().table_part.remove(idx);
482                continue;
483            }
484
485            t.r#ref = coordinates_to_range_ref(&coordinates, false)?;
486            if let Some(af) = &mut t.auto_filter {
487                af.r#ref = t.r#ref.clone();
488            }
489            self.set_table_columns(sheet, true, x1, y1, x2, &mut t)?;
490            t.table_type = None;
491            t.totals_row_count = 0;
492            t.connection_id = 0;
493
494            let table = xml_to_string(&t)?.into_bytes();
495            self.save_file_list(&table_xml, &table);
496            idx += 1;
497        }
498
499        if let Some(parts) = ws.table_parts.as_mut() {
500            parts.count = Some(parts.table_part.len() as i64);
501            if parts.table_part.is_empty() {
502                ws.table_parts = None;
503            }
504        }
505        Ok(())
506    }
507
508    // ------------------------------------------------------------------
509    // Auto filter
510    // ------------------------------------------------------------------
511
512    fn adjust_auto_filter(
513        &self,
514        ws: &mut XlsxWorksheet,
515        _sheet: &str,
516        dir: AdjustDirection,
517        num: i32,
518        offset: i32,
519        _sheet_id: i32,
520    ) -> Result<()> {
521        if ws.auto_filter.is_none() {
522            return Ok(());
523        }
524        let af = ws.auto_filter.as_mut().unwrap();
525        let mut coordinates = range_ref_to_coordinates(&af.r#ref)?;
526        let (x1, y1, x2, y2) = (
527            coordinates[0],
528            coordinates[1],
529            coordinates[2],
530            coordinates[3],
531        );
532
533        if (dir == AdjustDirection::Rows && y1 == num && offset < 0)
534            || (dir == AdjustDirection::Columns && x1 == num && x2 == num)
535        {
536            ws.auto_filter = None;
537            for row in &mut ws.sheet_data.row {
538                if let Some(r) = row.r {
539                    if r > y1 as i64 && r <= y2 as i64 {
540                        row.hidden = Some(false);
541                    }
542                }
543            }
544            return Ok(());
545        }
546
547        coordinates = self.adjust_auto_filter_helper(dir, coordinates, num, offset);
548        af.r#ref = coordinates_to_range_ref(&coordinates, false)?;
549        Ok(())
550    }
551
552    fn adjust_auto_filter_helper(
553        &self,
554        dir: AdjustDirection,
555        mut coordinates: Vec<i32>,
556        num: i32,
557        offset: i32,
558    ) -> Vec<i32> {
559        if dir == AdjustDirection::Rows {
560            if coordinates[1] >= num {
561                coordinates[1] += offset;
562            }
563            if coordinates[3] >= num {
564                coordinates[3] += offset;
565            }
566        } else {
567            if coordinates[0] >= num {
568                coordinates[0] += offset;
569            }
570            if coordinates[2] >= num {
571                coordinates[2] += offset;
572            }
573        }
574        coordinates
575    }
576
577    // ------------------------------------------------------------------
578    // Merge cells
579    // ------------------------------------------------------------------
580
581    fn adjust_merge_cells(
582        &self,
583        ws: &mut XlsxWorksheet,
584        _sheet: &str,
585        dir: AdjustDirection,
586        num: i32,
587        offset: i32,
588        _sheet_id: i32,
589    ) -> Result<()> {
590        if ws.merge_cells.is_none() {
591            return Ok(());
592        }
593
594        let mut i = 0;
595        while i < ws.merge_cells.as_ref().unwrap().cells.len() {
596            let merged_cells_ref = ws.merge_cells.as_ref().unwrap().cells[i]
597                .r#ref
598                .clone()
599                .unwrap_or_default();
600            let mut merged_ref = merged_cells_ref.clone();
601            if !merged_ref.contains(':') {
602                merged_ref.push(':');
603                merged_ref.push_str(&merged_cells_ref);
604            }
605            let mut coordinates = range_ref_to_coordinates(&merged_ref)?;
606            let (mut x1, mut y1, mut x2, mut y2) = (
607                coordinates[0],
608                coordinates[1],
609                coordinates[2],
610                coordinates[3],
611            );
612
613            if dir == AdjustDirection::Rows {
614                if y1 == num && y2 == num && offset < 0 {
615                    delete_merge_cell(ws, i);
616                    continue;
617                }
618                (y1, y2) = adjust_merge_cells_helper(y1, y2, num, offset);
619            } else {
620                if x1 == num && x2 == num && offset < 0 {
621                    delete_merge_cell(ws, i);
622                    continue;
623                }
624                (x1, x2) = adjust_merge_cells_helper(x1, x2, num, offset);
625            }
626
627            if x1 == x2 && y1 == y2 {
628                delete_merge_cell(ws, i);
629                continue;
630            }
631
632            coordinates = vec![x1, y1, x2, y2];
633            if let Some(cell) = ws.merge_cells.as_mut().unwrap().cells.get_mut(i) {
634                cell.r#ref = Some(coordinates_to_range_ref(&coordinates, false)?);
635            }
636            i += 1;
637        }
638        Ok(())
639    }
640
641    // ------------------------------------------------------------------
642    // Calculation chain
643    // ------------------------------------------------------------------
644
645    fn adjust_calc_chain(
646        &self,
647        _ws: &mut XlsxWorksheet,
648        _sheet: &str,
649        dir: AdjustDirection,
650        num: i32,
651        offset: i32,
652        sheet_id: i32,
653    ) -> Result<()> {
654        let mut guard = self.calc_chain.lock().unwrap();
655        let cc = match guard.as_mut() {
656            Some(c) => c,
657            None => return Ok(()),
658        };
659
660        let mut prev_sheet_id = 0;
661        let mut i = 0;
662        while i < cc.c.len() {
663            let mut ci = cc.c[i].clone();
664            if ci.i == 0 {
665                ci.i = prev_sheet_id;
666            }
667            prev_sheet_id = ci.i;
668            if ci.i != sheet_id {
669                i += 1;
670                continue;
671            }
672
673            let (col_num, row_num) = cell_name_to_coordinates(&ci.r)?;
674            let mut updated = false;
675            if dir == AdjustDirection::Rows && num <= row_num {
676                if num == row_num && offset == -1 {
677                    cc.c.remove(i);
678                    continue;
679                }
680                cc.c[i].r = adjust_cell_name(&ci.r, dir, col_num, row_num, offset)?;
681                updated = true;
682            }
683            if !updated && dir == AdjustDirection::Columns && num <= col_num {
684                if num == col_num && offset == -1 {
685                    cc.c.remove(i);
686                    continue;
687                }
688                cc.c[i].r = adjust_cell_name(&ci.r, dir, col_num, row_num, offset)?;
689            }
690            i += 1;
691        }
692        Ok(())
693    }
694
695    // ------------------------------------------------------------------
696    // Conditional formatting
697    // ------------------------------------------------------------------
698
699    fn adjust_conditional_formats(
700        &self,
701        ws: &mut XlsxWorksheet,
702        _sheet: &str,
703        dir: AdjustDirection,
704        num: i32,
705        offset: i32,
706        _sheet_id: i32,
707    ) -> Result<()> {
708        let mut i = 0;
709        while i < ws.conditional_formatting.len() {
710            let sqref = match &ws.conditional_formatting[i].sqref {
711                Some(s) => s.clone(),
712                None => {
713                    i += 1;
714                    continue;
715                }
716            };
717            let ref_ = self.adjust_cell_ref(&sqref, dir, num, offset)?;
718            if ref_.is_empty() {
719                ws.conditional_formatting.remove(i);
720                continue;
721            }
722            ws.conditional_formatting[i].sqref = Some(ref_);
723            i += 1;
724        }
725        Ok(())
726    }
727
728    // ------------------------------------------------------------------
729    // Data validations
730    // ------------------------------------------------------------------
731
732    fn adjust_data_validations(
733        &self,
734        _ws: &mut XlsxWorksheet,
735        sheet: &str,
736        dir: AdjustDirection,
737        num: i32,
738        offset: i32,
739        _sheet_id: i32,
740    ) -> Result<()> {
741        for sheet_n in self.get_sheet_list() {
742            let mut worksheet = match self.work_sheet_reader(&sheet_n) {
743                Ok(w) => w,
744                Err(e) => {
745                    if e.to_string() == new_not_worksheet_error(&sheet_n) {
746                        continue;
747                    }
748                    return Err(e);
749                }
750            };
751            let path = match self.get_sheet_xml_path(&sheet_n) {
752                Some(p) => p,
753                None => continue,
754            };
755
756            if worksheet.data_validations.is_none() {
757                continue;
758            }
759
760            let dvs = worksheet.data_validations.as_mut().unwrap();
761            let mut i = 0;
762            while i < dvs.data_validation.len() {
763                if sheet.eq_ignore_ascii_case(&sheet_n) {
764                    let ref_ =
765                        self.adjust_cell_ref(&dvs.data_validation[i].sqref, dir, num, offset)?;
766                    if ref_.is_empty() {
767                        dvs.data_validation.remove(i);
768                        continue;
769                    }
770                    dvs.data_validation[i].sqref = ref_;
771                }
772
773                if let Some(f1) = dvs.data_validation[i].formula1.as_mut() {
774                    if inner_xml_is_formula(&f1.content) {
775                        let formula = formula_unescaper_replace(&f1.content);
776                        let adjusted = self.adjust_formula_ref(
777                            sheet, &sheet_n, &formula, false, dir, num, offset,
778                        )?;
779                        f1.content = formula_escaper_replace(&adjusted);
780                    }
781                }
782                if let Some(f2) = dvs.data_validation[i].formula2.as_mut() {
783                    if inner_xml_is_formula(&f2.content) {
784                        let formula = formula_unescaper_replace(&f2.content);
785                        let adjusted = self.adjust_formula_ref(
786                            sheet, &sheet_n, &formula, false, dir, num, offset,
787                        )?;
788                        f2.content = formula_escaper_replace(&adjusted);
789                    }
790                }
791                i += 1;
792            }
793            dvs.count = Some(dvs.data_validation.len() as i64);
794            if dvs.data_validation.is_empty() {
795                worksheet.data_validations = None;
796            }
797            self.sheet.insert(path, worksheet);
798        }
799        Ok(())
800    }
801
802    // ------------------------------------------------------------------
803    // Drawings
804    // ------------------------------------------------------------------
805
806    fn adjust_drawings(
807        &self,
808        ws: &mut XlsxWorksheet,
809        sheet: &str,
810        dir: AdjustDirection,
811        num: i32,
812        offset: i32,
813    ) -> Result<()> {
814        let drawing_rid = match ws.drawing.as_ref().and_then(|d| d.rid.clone()) {
815            Some(rid) => rid,
816            None => return Ok(()),
817        };
818        let target = self.get_sheet_relationships_target_by_id(sheet, &drawing_rid);
819        if target.is_empty() {
820            return Ok(());
821        }
822        let drawing_xml = target
823            .replace("..", "xl")
824            .trim_start_matches('/')
825            .to_string();
826        let (mut ws_dr, _) = self.drawing_parser(&drawing_xml)?;
827
828        for anchor in ws_dr.two_cell_anchor.iter_mut() {
829            adjust_cell_anchor(anchor, dir, num, offset)?;
830        }
831        for anchor in ws_dr.one_cell_anchor.iter_mut() {
832            adjust_cell_anchor(anchor, dir, num, offset)?;
833        }
834
835        self.drawings.insert(drawing_xml, ws_dr);
836        Ok(())
837    }
838
839    // ------------------------------------------------------------------
840    // Defined names
841    // ------------------------------------------------------------------
842
843    fn adjust_defined_names(
844        &self,
845        sheet: &str,
846        dir: AdjustDirection,
847        num: i32,
848        offset: i32,
849    ) -> Result<()> {
850        let mut wb = self.workbook_reader()?;
851        if let Some(dns) = wb.defined_names.as_mut() {
852            for i in 0..dns.defined_name.len() {
853                let data = dns.defined_name[i].data.clone();
854                if let Ok(adjusted) =
855                    self.adjust_formula_ref(sheet, "", &data, true, dir, num, offset)
856                {
857                    dns.defined_name[i].data = adjusted;
858                }
859            }
860        }
861        *self.workbook.lock().unwrap() = Some(wb);
862        Ok(())
863    }
864
865    // ------------------------------------------------------------------
866    // Volatile dependencies
867    // ------------------------------------------------------------------
868
869    fn adjust_volatile_deps(
870        &self,
871        _ws: &mut XlsxWorksheet,
872        _sheet: &str,
873        dir: AdjustDirection,
874        num: i32,
875        offset: i32,
876        sheet_id: i32,
877    ) -> Result<()> {
878        let mut vol_types = match self.volatile_deps_reader()? {
879            Some(vt) => vt,
880            None => return Ok(()),
881        };
882
883        let mut i1 = 0;
884        while i1 < vol_types.vol_type.len() {
885            let mut i2 = 0;
886            while i2 < vol_types.vol_type[i1].main.len() {
887                let mut i3 = 0;
888                while i3 < vol_types.vol_type[i1].main[i2].tp.len() {
889                    let mut i4 = 0;
890                    while i4 < vol_types.vol_type[i1].main[i2].tp[i3].tr.len() {
891                        let tr = &vol_types.vol_type[i1].main[i2].tp[i3].tr[i4];
892                        if tr.s != sheet_id {
893                            i4 += 1;
894                            continue;
895                        }
896                        let (col, row) = cell_name_to_coordinates(&tr.r)?;
897                        let should_delete = match dir {
898                            AdjustDirection::Rows => num <= row && num == row && offset == -1,
899                            AdjustDirection::Columns => num <= col && num == col && offset == -1,
900                        };
901                        if should_delete {
902                            crate::calc_chain::delete_vol_topic_ref(&mut vol_types, i1, i2, i3, i4);
903                            continue;
904                        }
905                        let adjusted = match dir {
906                            AdjustDirection::Rows if num <= row => {
907                                adjust_cell_name(&tr.r, dir, col, row, offset)?
908                            }
909                            AdjustDirection::Columns if num <= col => {
910                                adjust_cell_name(&tr.r, dir, col, row, offset)?
911                            }
912                            _ => tr.r.clone(),
913                        };
914                        vol_types.vol_type[i1].main[i2].tp[i3].tr[i4].r = adjusted;
915                        i4 += 1;
916                    }
917                    i3 += 1;
918                }
919                i2 += 1;
920            }
921            i1 += 1;
922        }
923
924        *self.volatile_deps.lock().unwrap() = Some(vol_types);
925        Ok(())
926    }
927
928    // ------------------------------------------------------------------
929    // Internal helpers used by the adjusters
930    // ------------------------------------------------------------------
931
932    /// Ensure every row element has an `r` attribute and then fill each row so
933    /// that column references are continuous.
934    ///
935    /// Equivalent to Go `xlsxWorksheet.checkSheet` simplified to the core
936    /// "fill row numbers + check rows" behaviour.
937    pub(crate) fn check_sheet(&self, ws: &mut XlsxWorksheet) -> Result<()> {
938        for (row_idx, row) in ws.sheet_data.row.iter_mut().enumerate() {
939            if row.r.is_none() {
940                row.r = Some(row_idx as i64 + 1);
941            }
942        }
943        self.check_row(ws)
944    }
945
946    /// Check and fill each column element for all rows, making cell references
947    /// continuous within a worksheet.
948    ///
949    /// Equivalent to Go `xlsxWorksheet.checkRow`.
950    pub(crate) fn check_row(&self, ws: &mut XlsxWorksheet) -> Result<()> {
951        for (row_idx, row) in ws.sheet_data.row.iter_mut().enumerate() {
952            let row_num = row.r.unwrap_or((row_idx as i64) + 1);
953            let col_count = row.c.len();
954            if col_count == 0 {
955                continue;
956            }
957
958            // Fill missing `r` attributes in a row element.
959            let mut r_count = 0;
960            for cell in &mut row.c {
961                r_count += 1;
962                if let Some(ref name) = cell.r {
963                    if let Ok((col, _)) = cell_name_to_coordinates(name) {
964                        if col > r_count {
965                            r_count = col;
966                        }
967                    }
968                    continue;
969                }
970                cell.r = coordinates_to_cell_name(r_count, row_num as i32, false).ok();
971            }
972
973            let last_col = if let Some(ref name) = row.c.last().and_then(|c| c.r.as_ref()) {
974                cell_name_to_coordinates(name)?.0
975            } else {
976                continue;
977            };
978
979            if col_count < last_col as usize {
980                let mut target = Vec::with_capacity(last_col as usize);
981                for col_idx in 0..last_col {
982                    let cell_name = coordinates_to_cell_name(col_idx + 1, row_num as i32, false)?;
983                    target.push(XlsxC {
984                        r: Some(cell_name),
985                        ..Default::default()
986                    });
987                }
988                for cell in row.c.drain(..) {
989                    if let Some(ref name) = cell.r {
990                        if let Ok((col, _)) = cell_name_to_coordinates(name) {
991                            target[(col - 1) as usize] = cell;
992                        }
993                    }
994                }
995                row.c = target;
996            }
997        }
998        Ok(())
999    }
1000}
1001
1002// ------------------------------------------------------------------
1003// Free helpers
1004// ------------------------------------------------------------------
1005
1006fn adjust_single_row_dimensions(row: &mut XlsxRow, offset: i32) {
1007    if let Some(r) = row.r.as_mut() {
1008        *r += offset as i64;
1009    }
1010    let new_row = row.r.unwrap_or(0);
1011    for cell in &mut row.c {
1012        if let Some(name) = &cell.r {
1013            if let Ok((col, _)) = split_cell_name(name) {
1014                if let Ok(new_name) = join_cell_name(&col, new_row as i32) {
1015                    cell.r = Some(new_name);
1016                }
1017            }
1018        }
1019    }
1020}
1021
1022fn adjust_expr(
1023    expr: &mut crate::calc::Expr,
1024    dir: AdjustDirection,
1025    num: i32,
1026    offset: i32,
1027    keep_relative: bool,
1028    sheet: &str,
1029    active_sheet: &str,
1030) {
1031    use crate::calc::Expr;
1032    match expr {
1033        Expr::Cell(r) => {
1034            if should_adjust_ref(r, sheet, active_sheet) {
1035                adjust_cell_reference(r, dir, num, offset, keep_relative);
1036            }
1037        }
1038        Expr::Range(start, end) => {
1039            if should_adjust_ref(start, sheet, active_sheet) {
1040                adjust_cell_reference(start, dir, num, offset, keep_relative);
1041            }
1042            if should_adjust_ref(end, sheet, active_sheet) {
1043                adjust_cell_reference(end, dir, num, offset, keep_relative);
1044            }
1045        }
1046        Expr::Call(_, args) => {
1047            for arg in args {
1048                adjust_expr(arg, dir, num, offset, keep_relative, sheet, active_sheet);
1049            }
1050        }
1051        Expr::Unary(_, e) => {
1052            adjust_expr(e, dir, num, offset, keep_relative, sheet, active_sheet);
1053        }
1054        Expr::Binary(_, l, r) => {
1055            adjust_expr(l, dir, num, offset, keep_relative, sheet, active_sheet);
1056            adjust_expr(r, dir, num, offset, keep_relative, sheet, active_sheet);
1057        }
1058        _ => {}
1059    }
1060}
1061
1062fn should_adjust_ref(r: &crate::calc::CellRef, sheet: &str, active_sheet: &str) -> bool {
1063    match &r.sheet {
1064        Some(s) => s.eq_ignore_ascii_case(sheet),
1065        None => active_sheet.eq_ignore_ascii_case(sheet),
1066    }
1067}
1068
1069fn adjust_cell_reference(
1070    r: &mut crate::calc::CellRef,
1071    dir: AdjustDirection,
1072    num: i32,
1073    offset: i32,
1074    keep_relative: bool,
1075) {
1076    match dir {
1077        AdjustDirection::Columns => {
1078            if (!keep_relative || r.col_abs) && r.col >= num {
1079                r.col += offset;
1080                if r.col < 1 {
1081                    r.col = 1;
1082                }
1083                if r.col > MAX_COLUMNS {
1084                    r.col = MAX_COLUMNS;
1085                }
1086            }
1087        }
1088        AdjustDirection::Rows => {
1089            if (!keep_relative || r.row_abs) && r.row >= num {
1090                r.row += offset;
1091                if r.row < 1 {
1092                    r.row = 1;
1093                }
1094                if r.row > TOTAL_ROWS {
1095                    r.row = TOTAL_ROWS;
1096                }
1097            }
1098        }
1099    }
1100}
1101
1102fn format_expr(expr: &crate::calc::Expr) -> Result<String> {
1103    use crate::calc::Expr;
1104    Ok(match expr {
1105        Expr::Number(n) => {
1106            if n.fract() == 0.0 {
1107                format!("{}", *n as i64)
1108            } else {
1109                format!("{}", n)
1110            }
1111        }
1112        Expr::String(s) => format!("\"{}\"", s.replace('"', "\"\"")),
1113        Expr::Bool(true) => "TRUE".to_string(),
1114        Expr::Bool(false) => "FALSE".to_string(),
1115        Expr::Cell(r) => format_cell_ref(r),
1116        Expr::Range(start, end) => {
1117            if start.sheet.is_some() && start.sheet == end.sheet {
1118                let sheet = start.sheet.as_deref().unwrap();
1119                format!(
1120                    "{}!{}:{}",
1121                    escape_sheet_name(sheet),
1122                    format_cell_ref_without_sheet(start),
1123                    format_cell_ref_without_sheet(end)
1124                )
1125            } else {
1126                format!("{}:{}", format_cell_ref(start), format_cell_ref(end))
1127            }
1128        }
1129        Expr::Call(name, args) => {
1130            let args_str = args
1131                .iter()
1132                .map(format_expr)
1133                .collect::<Result<Vec<_>>>()?
1134                .join(",");
1135            format!("{}({})", name, args_str)
1136        }
1137        Expr::Unary(op, e) => format!("{}{}", op, format_expr(e)?),
1138        Expr::Binary(op, l, r) => {
1139            format!("{}{}{}", format_expr(l)?, op, format_expr(r)?)
1140        }
1141        Expr::Range3D(s1, s2, start, end) => {
1142            format!(
1143                "{}:{}!{}:{}",
1144                escape_sheet_name(s1),
1145                escape_sheet_name(s2),
1146                format_cell_ref_without_sheet(start),
1147                format_cell_ref_without_sheet(end)
1148            )
1149        }
1150        Expr::Name(name) => name.clone(),
1151        Expr::Array(rows) => {
1152            let rows_str = rows
1153                .iter()
1154                .map(|row| {
1155                    row.iter()
1156                        .map(format_expr)
1157                        .collect::<Result<Vec<_>>>()
1158                        .map(|v| v.join(","))
1159                })
1160                .collect::<Result<Vec<_>>>()?
1161                .join(";");
1162            format!("{{{}}}", rows_str)
1163        }
1164    })
1165}
1166
1167fn format_cell_ref(r: &crate::calc::CellRef) -> String {
1168    let sheet_prefix = r
1169        .sheet
1170        .as_deref()
1171        .map(|s| format!("{}!", escape_sheet_name(s)))
1172        .unwrap_or_default();
1173    format!("{}{}", sheet_prefix, format_cell_ref_without_sheet(r))
1174}
1175
1176fn format_cell_ref_without_sheet(r: &crate::calc::CellRef) -> String {
1177    let col_name = column_number_to_name(r.col).unwrap_or_default();
1178    let col_part = if r.col_abs {
1179        format!("${}", col_name)
1180    } else {
1181        col_name
1182    };
1183    let row_part = if r.row_abs {
1184        format!("${}", r.row)
1185    } else {
1186        r.row.to_string()
1187    };
1188    format!("{}{}", col_part, row_part)
1189}
1190
1191fn apply_offset(
1192    coordinates: &mut [i32],
1193    idx1: usize,
1194    idx2: usize,
1195    max_val: i32,
1196    num: i32,
1197    offset: i32,
1198) {
1199    if coordinates[idx1] >= num {
1200        coordinates[idx1] += offset;
1201    }
1202    if coordinates[idx2] >= num {
1203        coordinates[idx2] += offset;
1204        if coordinates[idx2] > max_val {
1205            coordinates[idx2] = max_val;
1206        }
1207    }
1208}
1209
1210fn adjust_merge_cells_helper(mut p1: i32, mut p2: i32, num: i32, offset: i32) -> (i32, i32) {
1211    if p2 < p1 {
1212        std::mem::swap(&mut p1, &mut p2);
1213    }
1214    if offset >= 0 {
1215        if num <= p1 {
1216            p1 += offset;
1217            p2 += offset;
1218        } else if num <= p2 {
1219            p2 += offset;
1220        }
1221    } else {
1222        if num < p1 || (num == p1 && num == p2) {
1223            p1 += offset;
1224            p2 += offset;
1225        } else if num <= p2 {
1226            p2 += offset;
1227        }
1228    }
1229    (p1, p2)
1230}
1231
1232fn delete_merge_cell(ws: &mut XlsxWorksheet, idx: usize) {
1233    if ws.merge_cells.is_none() {
1234        return;
1235    }
1236    let merges = ws.merge_cells.as_mut().unwrap();
1237    if idx >= merges.cells.len() {
1238        return;
1239    }
1240    merges.cells.remove(idx);
1241    merges.count = Some(merges.cells.len() as i64);
1242}
1243
1244fn adjust_cell_name(
1245    _cell: &str,
1246    dir: AdjustDirection,
1247    c: i32,
1248    r: i32,
1249    offset: i32,
1250) -> Result<String> {
1251    if dir == AdjustDirection::Rows {
1252        let rn = r + offset;
1253        if rn > 0 {
1254            return coordinates_to_cell_name(c, rn, false).map_err(|e| e.into());
1255        }
1256    }
1257    coordinates_to_cell_name(c + offset, r, false).map_err(|e| e.into())
1258}
1259
1260fn inner_xml_is_formula(content: &str) -> bool {
1261    let trimmed = content.trim_start();
1262    !trimmed.is_empty() && (trimmed.starts_with('=') || trimmed.contains("<formula"))
1263}
1264
1265fn formula_unescaper_replace(s: &str) -> String {
1266    s.to_string()
1267}
1268
1269fn formula_escaper_replace(s: &str) -> String {
1270    s.to_string()
1271}
1272
1273fn escape_sheet_name(name: &str) -> String {
1274    if name.chars().any(|r| !r.is_alphanumeric()) {
1275        format!("'{}'", name.replace('\'', "''"))
1276    } else {
1277        name.to_string()
1278    }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use super::*;
1284    use crate::options::Options;
1285
1286    #[test]
1287    fn adjust_helper_shifts_on_row_insert() {
1288        let f = File::new_with_options(Options::default());
1289        f.merge_cell("Sheet1", "A1", "B2").unwrap();
1290        f.set_cell_value("Sheet1", "C5", "value").unwrap();
1291        f.adjust_helper("Sheet1", AdjustDirection::Rows, 2, 1)
1292            .unwrap();
1293
1294        let merges = f.get_merge_cells("Sheet1").unwrap();
1295        assert!(merges.contains(&"A1:B3".to_string()));
1296        assert!(f.get_cell_value("Sheet1", "C6").unwrap().contains("value"));
1297    }
1298
1299    fn get_cell_formula(f: &File, sheet: &str, cell: &str) -> String {
1300        let ws = f.work_sheet_reader(sheet).unwrap();
1301        for row in &ws.sheet_data.row {
1302            for c in &row.c {
1303                if c.r
1304                    .as_deref()
1305                    .map(|r| r.eq_ignore_ascii_case(cell))
1306                    .unwrap_or(false)
1307                {
1308                    return c.f.as_ref().map(|f| f.content.clone()).unwrap_or_default();
1309                }
1310            }
1311        }
1312        String::new()
1313    }
1314
1315    #[test]
1316    fn adjust_formula_ref_shifts_relative_row_refs() {
1317        let f = File::new_with_options(Options::default());
1318        assert_eq!(
1319            f.adjust_formula_ref("Sheet1", "Sheet1", "A1", false, AdjustDirection::Rows, 1, 1)
1320                .unwrap(),
1321            "A2"
1322        );
1323    }
1324
1325    #[test]
1326    fn adjust_formula_ref_keeps_refs_below_row_insert() {
1327        let f = File::new_with_options(Options::default());
1328        assert_eq!(
1329            f.adjust_formula_ref("Sheet1", "Sheet1", "A1", false, AdjustDirection::Rows, 2, 1)
1330                .unwrap(),
1331            "A1"
1332        );
1333    }
1334
1335    #[test]
1336    fn adjust_formula_ref_shifts_absolute_row_refs() {
1337        let f = File::new_with_options(Options::default());
1338        assert_eq!(
1339            f.adjust_formula_ref(
1340                "Sheet1",
1341                "Sheet1",
1342                "$A$1",
1343                false,
1344                AdjustDirection::Rows,
1345                1,
1346                1
1347            )
1348            .unwrap(),
1349            "$A$2"
1350        );
1351    }
1352
1353    #[test]
1354    fn adjust_formula_ref_shifts_relative_col_refs() {
1355        let f = File::new_with_options(Options::default());
1356        assert_eq!(
1357            f.adjust_formula_ref(
1358                "Sheet1",
1359                "Sheet1",
1360                "A1",
1361                false,
1362                AdjustDirection::Columns,
1363                1,
1364                1
1365            )
1366            .unwrap(),
1367            "B1"
1368        );
1369    }
1370
1371    #[test]
1372    fn adjust_formula_ref_keeps_other_sheet_refs() {
1373        let f = File::new_with_options(Options::default());
1374        assert_eq!(
1375            f.adjust_formula_ref(
1376                "Sheet1",
1377                "Sheet1",
1378                "Sheet2!A1",
1379                false,
1380                AdjustDirection::Rows,
1381                1,
1382                1
1383            )
1384            .unwrap(),
1385            "Sheet2!A1"
1386        );
1387    }
1388
1389    #[test]
1390    fn adjust_formula_ref_adjusts_same_sheet_qualified_refs() {
1391        let f = File::new_with_options(Options::default());
1392        assert_eq!(
1393            f.adjust_formula_ref(
1394                "Sheet1",
1395                "Sheet1",
1396                "Sheet1!A1",
1397                false,
1398                AdjustDirection::Rows,
1399                1,
1400                1
1401            )
1402            .unwrap(),
1403            "Sheet1!A2"
1404        );
1405    }
1406
1407    #[test]
1408    fn adjust_formula_ref_adjusts_ranges() {
1409        let f = File::new_with_options(Options::default());
1410        assert_eq!(
1411            f.adjust_formula_ref(
1412                "Sheet1",
1413                "Sheet1",
1414                "SUM(A1:B3)",
1415                false,
1416                AdjustDirection::Rows,
1417                2,
1418                1
1419            )
1420            .unwrap(),
1421            "SUM(A1:B4)"
1422        );
1423    }
1424
1425    #[test]
1426    fn adjust_formula_ref_keep_relative_only_adjusts_absolute() {
1427        let f = File::new_with_options(Options::default());
1428        assert_eq!(
1429            f.adjust_formula_ref(
1430                "Sheet1",
1431                "Sheet1",
1432                "$A$1+A1",
1433                true,
1434                AdjustDirection::Rows,
1435                1,
1436                1
1437            )
1438            .unwrap(),
1439            "$A$2+A1"
1440        );
1441    }
1442
1443    #[test]
1444    fn adjust_formula_ref_preserves_defined_names() {
1445        let f = File::new_with_options(Options::default());
1446        assert_eq!(
1447            f.adjust_formula_ref(
1448                "Sheet1",
1449                "Sheet1",
1450                "=MYRANGE+A1",
1451                false,
1452                AdjustDirection::Rows,
1453                1,
1454                1
1455            )
1456            .unwrap(),
1457            "=MYRANGE+A2"
1458        );
1459    }
1460
1461    #[test]
1462    fn adjust_helper_updates_cell_formula_on_row_insert() {
1463        let f = File::new_with_options(Options::default());
1464        f.set_cell_formula("Sheet1", "C5", "A5+B5").unwrap();
1465        f.adjust_helper("Sheet1", AdjustDirection::Rows, 3, 1)
1466            .unwrap();
1467        assert_eq!(get_cell_formula(&f, "Sheet1", "C6"), "A6+B6");
1468    }
1469
1470    #[test]
1471    fn adjust_helper_updates_cell_formula_on_col_insert() {
1472        let f = File::new_with_options(Options::default());
1473        f.set_cell_formula("Sheet1", "D1", "A1+B1").unwrap();
1474        f.adjust_helper("Sheet1", AdjustDirection::Columns, 2, 1)
1475            .unwrap();
1476        assert_eq!(get_cell_formula(&f, "Sheet1", "E1"), "A1+C1");
1477    }
1478}
1479
1480fn adjust_cell_anchor(
1481    anchor: &mut XdrCellAnchor,
1482    dir: AdjustDirection,
1483    num: i32,
1484    offset: i32,
1485) -> Result<()> {
1486    let edit_as = anchor.edit_as.as_deref().unwrap_or("");
1487    if (anchor.from.is_none() && (anchor.to.is_none() || anchor.ext.is_none()))
1488        || edit_as == "absolute"
1489    {
1490        return Ok(());
1491    }
1492    let ok = adjust_from(anchor.from.as_mut().unwrap(), dir, num, offset, edit_as)?;
1493    if let Some(to) = anchor.to.as_mut() {
1494        adjust_to(to, dir, num, offset, ok || edit_as.is_empty())?;
1495    }
1496    Ok(())
1497}
1498
1499fn adjust_from(
1500    from: &mut XlsxFrom,
1501    dir: AdjustDirection,
1502    num: i32,
1503    offset: i32,
1504    edit_as: &str,
1505) -> Result<bool> {
1506    let mut ok = false;
1507    if dir == AdjustDirection::Columns
1508        && from.col + 1 >= num as i64
1509        && from.col + offset as i64 >= 0
1510    {
1511        if from.col + offset as i64 >= MAX_COLUMNS as i64 {
1512            return Err(Box::new(ErrColumnNumber));
1513        }
1514        from.col += offset as i64;
1515        ok = edit_as == "oneCell";
1516    }
1517    if dir == AdjustDirection::Rows && from.row + 1 >= num as i64 && from.row + offset as i64 >= 0 {
1518        if from.row + offset as i64 >= TOTAL_ROWS as i64 {
1519            return Err(Box::new(ErrMaxRows));
1520        }
1521        from.row += offset as i64;
1522        ok = edit_as == "oneCell";
1523    }
1524    Ok(ok)
1525}
1526
1527fn adjust_to(to: &mut XlsxTo, dir: AdjustDirection, num: i32, offset: i32, ok: bool) -> Result<()> {
1528    if !ok {
1529        return Ok(());
1530    }
1531    if dir == AdjustDirection::Columns && to.col + 1 >= num as i64 && to.col + offset as i64 >= 0 {
1532        if to.col + offset as i64 >= MAX_COLUMNS as i64 {
1533            return Err(Box::new(ErrColumnNumber));
1534        }
1535        to.col += offset as i64;
1536    }
1537    if dir == AdjustDirection::Rows && to.row + 1 >= num as i64 && to.row + offset as i64 >= 0 {
1538        if to.row + offset as i64 >= TOTAL_ROWS as i64 {
1539            return Err(Box::new(ErrMaxRows));
1540        }
1541        to.row += offset as i64;
1542    }
1543    Ok(())
1544}