1use crate::cell::find_cell_mut;
6use crate::errors::Result;
7use crate::errors::{ErrParameterInvalid, ErrSheetNotExist};
8use crate::file::File;
9use crate::lib_util::{
10 cell_name_to_coordinates, coordinates_to_cell_name, coordinates_to_range_ref, sort_coordinates,
11};
12
13impl File {
14 pub fn merge_cell(&self, sheet: &str, top_left: &str, bottom_right: &str) -> Result<()> {
16 let range = normalize_range(top_left, bottom_right)?;
17 let path = self
18 .get_sheet_xml_path(sheet)
19 .ok_or_else(|| ErrSheetNotExist {
20 sheet_name: sheet.to_string(),
21 })?;
22 let mut ws = self.work_sheet_reader(sheet)?;
23 ensure_merge_cells(&mut ws);
24 let merges = ws.merge_cells.as_mut().unwrap();
25 merges.cells.retain(|m| {
27 if let Some(existing) = &m.r#ref {
28 !ranges_overlap(existing, &range)
29 } else {
30 true
31 }
32 });
33 merges.cells.push(crate::xml::worksheet::XlsxMergeCell {
34 r#ref: Some(range.clone()),
35 });
36 merges.count = Some(merges.cells.len() as i64);
37 self.sheet.insert(path, ws);
38 Ok(())
39 }
40
41 pub fn unmerge_cell(&self, sheet: &str, top_left: &str, bottom_right: &str) -> Result<()> {
43 let range = normalize_range(top_left, bottom_right)?;
44 let path = self
45 .get_sheet_xml_path(sheet)
46 .ok_or_else(|| ErrSheetNotExist {
47 sheet_name: sheet.to_string(),
48 })?;
49 let mut ws = self.work_sheet_reader(sheet)?;
50 if let Some(merges) = ws.merge_cells.as_mut() {
51 merges.cells.retain(|m| m.r#ref.as_deref() != Some(&range));
52 merges.count = Some(merges.cells.len() as i64);
53 if merges.cells.is_empty() {
54 ws.merge_cells = None;
55 }
56 }
57 self.sheet.insert(path, ws);
58 Ok(())
59 }
60
61 pub fn get_merge_cells(&self, sheet: &str) -> Result<Vec<String>> {
63 let ws = self.work_sheet_reader(sheet)?;
64 let mut out = Vec::new();
65 if let Some(merges) = ws.merge_cells {
66 for m in merges.cells {
67 if let Some(r) = m.r#ref {
68 out.push(r);
69 }
70 }
71 }
72 Ok(out)
73 }
74}
75
76pub(crate) fn add_merge(
81 ws: &mut crate::xml::worksheet::XlsxWorksheet,
82 top_left: &str,
83 bottom_right: &str,
84) -> Result<()> {
85 let range = normalize_range(top_left, bottom_right)?;
86 ensure_merge_cells(ws);
87 let merges = ws.merge_cells.as_mut().unwrap();
88 merges.cells.retain(|m| {
89 if let Some(existing) = &m.r#ref {
90 !ranges_overlap(existing, &range)
91 } else {
92 true
93 }
94 });
95 merges.cells.push(crate::xml::worksheet::XlsxMergeCell {
96 r#ref: Some(range.clone()),
97 });
98 merges.count = Some(merges.cells.len() as i64);
99
100 let parts: Vec<&str> = range.split(':').collect();
102 let (tl, br) = (parts[0], parts[1]);
103 let mut coords = cell_refs_to_coordinates(tl, br)?;
104 sort_coordinates(&mut coords)?;
105 let (min_col, min_row, max_col, max_row) = (coords[0], coords[1], coords[2], coords[3]);
106 for col in min_col..=max_col {
107 for row in min_row..=max_row {
108 if col == min_col && row == min_row {
109 continue;
110 }
111 let cell_ref = coordinates_to_cell_name(col, row, false)?;
112 if let Some(c) = find_cell_mut(ws, &cell_ref) {
113 c.t = None;
114 c.v = None;
115 c.f = None;
116 c.is = None;
117 }
118 }
119 }
120 Ok(())
121}
122
123fn ensure_merge_cells(ws: &mut crate::xml::worksheet::XlsxWorksheet) {
128 if ws.merge_cells.is_none() {
129 ws.merge_cells = Some(crate::xml::worksheet::XlsxMergeCells::default());
130 }
131}
132
133fn normalize_range(top_left: &str, bottom_right: &str) -> Result<String> {
134 let mut coords = cell_refs_to_coordinates(top_left, bottom_right)?;
135 sort_coordinates(&mut coords)?;
136 Ok(coordinates_to_range_ref(&coords, false)?)
137}
138
139fn cell_refs_to_coordinates(first: &str, last: &str) -> Result<Vec<i32>> {
140 let mut coords = vec![0; 4];
141 let (col, row) = cell_name_to_coordinates(first)?;
142 coords[0] = col;
143 coords[1] = row;
144 let (col, row) = cell_name_to_coordinates(last)?;
145 coords[2] = col;
146 coords[3] = row;
147 Ok(coords)
148}
149
150fn ranges_overlap(a: &str, b: &str) -> bool {
151 let Ok(ca) = normalize_single_range(a) else {
152 return true;
153 };
154 let Ok(cb) = normalize_single_range(b) else {
155 return true;
156 };
157 if ca[2] < cb[0] || ca[0] > cb[2] || ca[3] < cb[1] || ca[1] > cb[3] {
158 return false;
159 }
160 true
161}
162
163fn normalize_single_range(r: &str) -> Result<Vec<i32>> {
164 let parts: Vec<&str> = r.split(':').collect();
165 if parts.len() != 2 {
166 return Err(ErrParameterInvalid.to_string().into());
167 }
168 let mut coords = cell_refs_to_coordinates(parts[0], parts[1])?;
169 sort_coordinates(&mut coords)?;
170 Ok(coords)
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn merge_and_unmerge() {
179 let f = File::new_with_options(crate::options::Options::default());
180 f.merge_cell("Sheet1", "A1", "B2").unwrap();
181 assert_eq!(f.get_merge_cells("Sheet1").unwrap(), vec!["A1:B2"]);
182 f.merge_cell("Sheet1", "C1", "C3").unwrap();
183 assert!(
184 f.get_merge_cells("Sheet1")
185 .unwrap()
186 .contains(&"C1:C3".to_string())
187 );
188 f.unmerge_cell("Sheet1", "A1", "B2").unwrap();
189 assert!(
190 !f.get_merge_cells("Sheet1")
191 .unwrap()
192 .contains(&"A1:B2".to_string())
193 );
194 }
195}