1use std::collections::BTreeMap;
6
7use crate::adjust::AdjustDirection;
8use crate::constants::{DEFAULT_ROW_HEIGHT, MAX_CELL_STYLES, MAX_ROW_HEIGHT, TOTAL_ROWS};
9use crate::errors::Result;
10use crate::errors::{ErrMaxRowHeight, ErrMaxRows, ErrSheetNotExist};
11use crate::file::File;
12use crate::lib_util::{
13 cell_name_to_coordinates, coordinates_to_cell_name, coordinates_to_range_ref,
14 range_ref_to_coordinates, sort_coordinates,
15};
16use crate::options::Options;
17use crate::xml::worksheet::{XlsxConditionalFormatting, XlsxDataValidation, XlsxWorksheet};
18
19impl File {
20 pub fn set_row_height(&self, sheet: &str, row: i32, height: f64) -> Result<()> {
22 if height > MAX_ROW_HEIGHT as f64 {
23 return Err(Box::new(ErrMaxRowHeight));
24 }
25 let path = self
26 .get_sheet_xml_path(sheet)
27 .ok_or_else(|| ErrSheetNotExist {
28 sheet_name: sheet.to_string(),
29 })?;
30 let mut ws = self.work_sheet_reader(sheet)?;
31 let r = get_or_make_row(&mut ws, row);
32 r.ht = Some(height);
33 r.custom_height = Some(true);
34 self.sheet.insert(path, ws);
35 Ok(())
36 }
37
38 pub fn get_row_height(&self, sheet: &str, row: i32) -> Result<f64> {
40 let ws = self.work_sheet_reader(sheet)?;
41 for r in &ws.sheet_data.row {
42 if r.r == Some(row as i64) {
43 return Ok(r.ht.unwrap_or(DEFAULT_ROW_HEIGHT));
44 }
45 }
46 if let Some(fmt) = &ws.sheet_format_pr {
47 return Ok(fmt.default_row_height);
48 }
49 Ok(DEFAULT_ROW_HEIGHT)
50 }
51
52 pub fn set_row_visible(&self, sheet: &str, row: i32, visible: bool) -> Result<()> {
54 let path = self
55 .get_sheet_xml_path(sheet)
56 .ok_or_else(|| ErrSheetNotExist {
57 sheet_name: sheet.to_string(),
58 })?;
59 let mut ws = self.work_sheet_reader(sheet)?;
60 let r = get_or_make_row(&mut ws, row);
61 r.hidden = Some(!visible);
62 self.sheet.insert(path, ws);
63 Ok(())
64 }
65
66 pub fn get_row_visible(&self, sheet: &str, row: i32) -> Result<bool> {
68 let ws = self.work_sheet_reader(sheet)?;
69 for r in &ws.sheet_data.row {
70 if r.r == Some(row as i64) {
71 return Ok(!r.hidden.unwrap_or(false));
72 }
73 }
74 Ok(true)
75 }
76
77 pub fn set_row_outline_level(&self, sheet: &str, row: i32, level: u8) -> Result<()> {
79 if level == 0 || level > 7 {
80 return Err(Box::new(crate::errors::ErrOutlineLevel));
81 }
82 let path = self
83 .get_sheet_xml_path(sheet)
84 .ok_or_else(|| ErrSheetNotExist {
85 sheet_name: sheet.to_string(),
86 })?;
87 let mut ws = self.work_sheet_reader(sheet)?;
88 let r = get_or_make_row(&mut ws, row);
89 r.outline_level = Some(level);
90 self.sheet.insert(path, ws);
91 Ok(())
92 }
93
94 pub fn get_row_outline_level(&self, sheet: &str, row: i32) -> Result<u8> {
96 let ws = self.work_sheet_reader(sheet)?;
97 for r in &ws.sheet_data.row {
98 if r.r == Some(row as i64) {
99 return Ok(r.outline_level.unwrap_or(0));
100 }
101 }
102 Ok(0)
103 }
104
105 pub fn insert_rows(&self, sheet: &str, row: i32, n: usize) -> Result<()> {
107 if n == 0 {
108 return Ok(());
109 }
110 let path = self
111 .get_sheet_xml_path(sheet)
112 .ok_or_else(|| ErrSheetNotExist {
113 sheet_name: sheet.to_string(),
114 })?;
115 let mut ws = self.work_sheet_reader(sheet)?;
116 shift_rows_down(&mut ws, row as i64, n as i64);
117 self.sheet.insert(path, ws);
118 Ok(())
119 }
120
121 pub fn remove_row(&self, sheet: &str, row: i32) -> Result<()> {
123 if row < 1 {
124 return Ok(());
125 }
126 let path = self
127 .get_sheet_xml_path(sheet)
128 .ok_or_else(|| ErrSheetNotExist {
129 sheet_name: sheet.to_string(),
130 })?;
131 let mut ws = self.work_sheet_reader(sheet)?;
132
133 let indices: Vec<(usize, usize)> = ws
136 .sheet_data
137 .row
138 .iter()
139 .enumerate()
140 .filter(|(_, r)| r.r == Some(row as i64))
141 .flat_map(|(ri, r)| r.c.iter().enumerate().map(move |(ci, _)| (ri, ci)))
142 .collect();
143 for (ri, ci) in indices {
144 let mut cell = std::mem::take(&mut ws.sheet_data.row[ri].c[ci]);
145 self.remove_formula(&mut cell, &mut ws, sheet)?;
146 ws.sheet_data.row[ri].c[ci] = cell;
147 }
148
149 ws.sheet_data.row.retain(|r| r.r != Some(row as i64));
150 self.sheet.insert(path, ws);
151 self.adjust_helper(sheet, AdjustDirection::Rows, row, -1)?;
152 Ok(())
153 }
154
155 pub fn duplicate_row(&self, sheet: &str, row: i32) -> Result<()> {
157 self.duplicate_row_to(sheet, row, row + 1)
158 }
159
160 pub fn duplicate_row_to(&self, sheet: &str, row: i32, row2: i32) -> Result<()> {
164 if row < 1 || row2 < 1 || row == row2 {
165 return Ok(());
166 }
167 let path = self
168 .get_sheet_xml_path(sheet)
169 .ok_or_else(|| ErrSheetNotExist {
170 sheet_name: sheet.to_string(),
171 })?;
172 let ws = self.work_sheet_reader(sheet)?;
173 let source = ws
174 .sheet_data
175 .row
176 .iter()
177 .find(|r| r.r == Some(row as i64))
178 .cloned();
179
180 self.sheet.insert(path.clone(), ws);
183 self.adjust_helper(sheet, AdjustDirection::Rows, row2, 1)?;
184
185 let mut ws = self.work_sheet_reader(sheet)?;
186 if let Some(mut source) = source {
187 source.r = Some(row2 as i64);
188 let offset = row2 - row;
189 for c in &mut source.c {
190 if let Some(name) = &c.r {
191 if let Ok((col, old_row)) = cell_name_to_coordinates(name) {
192 if let Ok(new_name) = coordinates_to_cell_name(col, old_row + offset, false)
193 {
194 c.r = Some(new_name);
195 }
196 }
197 }
198 }
199 self.adjust_single_row_formulas(sheet, sheet, &mut source, row, offset, true)?;
200
201 if let Some(idx) = ws
202 .sheet_data
203 .row
204 .iter()
205 .position(|r| r.r == Some(row2 as i64))
206 {
207 ws.sheet_data.row[idx] = source;
208 } else {
209 ws.sheet_data.row.push(source);
210 }
211 ws.sheet_data.row.sort_by_key(|r| r.r.unwrap_or(0));
212
213 self.duplicate_row_conditional_formats(&mut ws, row, row2)?;
215 self.duplicate_row_data_validations(&mut ws, row, row2)?;
216 self.duplicate_row_merge_cells(&mut ws, row, row2)?;
217 }
218 self.sheet.insert(path, ws);
219 Ok(())
220 }
221
222 fn duplicate_row_conditional_formats(
225 &self,
226 ws: &mut XlsxWorksheet,
227 row: i32,
228 row2: i32,
229 ) -> Result<()> {
230 let mut new_cfs: Vec<XlsxConditionalFormatting> = Vec::new();
231 for cf in &ws.conditional_formatting {
232 let mut sqrefs = Vec::new();
233 if let Some(sqref) = &cf.sqref {
234 for r#ref in sqref.split_whitespace() {
235 if let Some(new_ref) = duplicate_sq_ref_helper(row, row2, r#ref)? {
236 sqrefs.push(new_ref);
237 }
238 }
239 }
240 if !sqrefs.is_empty() {
241 let mut cf_copy = cf.clone();
242 cf_copy.sqref = Some(sqrefs.join(" "));
243 new_cfs.push(cf_copy);
244 }
245 }
246 ws.conditional_formatting.extend(new_cfs);
247 Ok(())
248 }
249
250 fn duplicate_row_data_validations(
253 &self,
254 ws: &mut XlsxWorksheet,
255 row: i32,
256 row2: i32,
257 ) -> Result<()> {
258 let dvs = match ws.data_validations.as_mut() {
259 Some(dvs) => dvs,
260 None => return Ok(()),
261 };
262 let mut new_dvs: Vec<XlsxDataValidation> = Vec::new();
263 for dv in &dvs.data_validation {
264 let mut sqrefs = Vec::new();
265 for r#ref in dv.sqref.split_whitespace() {
266 if let Some(new_ref) = duplicate_sq_ref_helper(row, row2, r#ref)? {
267 sqrefs.push(new_ref);
268 }
269 }
270 if !sqrefs.is_empty() {
271 let mut dv_copy = dv.clone();
272 dv_copy.sqref = sqrefs.join(" ");
273 new_dvs.push(dv_copy);
274 }
275 }
276 dvs.data_validation.extend(new_dvs);
277 dvs.count = Some(dvs.data_validation.len() as i64);
278 Ok(())
279 }
280
281 fn duplicate_row_merge_cells(&self, ws: &mut XlsxWorksheet, row: i32, row2: i32) -> Result<()> {
283 if ws.merge_cells.is_none() {
284 return Ok(());
285 }
286 let mut source_row = row;
287 if source_row > row2 {
288 source_row += 1;
289 }
290
291 if let Some(merges) = ws.merge_cells.as_ref() {
293 for m in &merges.cells {
294 if let Some(ref_) = &m.r#ref {
295 if let Ok(coords) = range_ref_to_coordinates(ref_) {
296 if coords[1] < row2 && row2 < coords[3] {
297 return Ok(());
298 }
299 }
300 }
301 }
302 }
303
304 let refs: Vec<Option<String>> = ws
305 .merge_cells
306 .as_ref()
307 .unwrap()
308 .cells
309 .iter()
310 .map(|m| m.r#ref.clone())
311 .collect();
312 for ref_ in refs.into_iter().flatten() {
313 let mut coords = range_ref_to_coordinates(&ref_)?;
314 sort_coordinates(&mut coords)?;
315 let (x1, y1, x2, y2) = (coords[0], coords[1], coords[2], coords[3]);
316 if y1 == y2 && y1 == source_row {
317 let from = coordinates_to_cell_name(x1, row2, false)?;
318 let to = coordinates_to_cell_name(x2, row2, false)?;
319 crate::merge::add_merge(ws, &from, &to)?;
320 }
321 }
322 Ok(())
323 }
324
325 pub fn set_row_style(
331 &self,
332 sheet: &str,
333 mut start: i32,
334 mut end: i32,
335 style_id: i32,
336 ) -> Result<()> {
337 if end < start {
338 std::mem::swap(&mut start, &mut end);
339 }
340 if start < 1 {
341 return Err(crate::errors::new_invalid_row_number_error(start).into());
342 }
343 if end > crate::constants::TOTAL_ROWS {
344 return Err(Box::new(crate::errors::ErrMaxRows));
345 }
346 if style_id < 0 {
347 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
348 }
349 let style_count = self
350 .styles_reader()?
351 .cell_xfs
352 .as_ref()
353 .map(|x| x.xf.len())
354 .unwrap_or(1) as i32;
355 if style_id >= style_count {
356 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
357 }
358 let path = self
359 .get_sheet_xml_path(sheet)
360 .ok_or_else(|| ErrSheetNotExist {
361 sheet_name: sheet.to_string(),
362 })?;
363 let mut ws = self.work_sheet_reader(sheet)?;
364 for row in start..=end {
365 let _ = get_or_make_row(&mut ws, row);
366 }
367 for r in &mut ws.sheet_data.row {
368 if let Some(row_num) = r.r {
369 if row_num >= start as i64 && row_num <= end as i64 {
370 r.s = Some(style_id as i64);
371 r.custom_format = Some(true);
372 for c in &mut r.c {
373 if let Some(ref cell_name) = c.r {
374 if let Ok((_, cell_row)) =
375 crate::lib_util::cell_name_to_coordinates(cell_name)
376 {
377 if cell_row == row_num as i32 {
378 c.s = Some(style_id as i64);
379 }
380 }
381 } else {
382 c.s = Some(style_id as i64);
383 }
384 }
385 }
386 }
387 }
388 self.sheet.insert(path, ws);
389 Ok(())
390 }
391}
392
393#[derive(Debug, Default, Clone, PartialEq)]
399pub struct RowOpts {
400 pub height: f64,
402 pub hidden: bool,
404 pub style_id: i32,
406}
407
408#[derive(Debug)]
410pub struct Rows<'a> {
411 file: &'a File,
412 ws: XlsxWorksheet,
413 cur_row: i32,
414 seek_row: i32,
415 max_row: i32,
416 err: Option<String>,
417 row_opts: RowOpts,
418}
419
420impl<'a> Rows<'a> {
421 pub fn next(&mut self) -> bool {
423 self.seek_row += 1;
424 if self.cur_row >= self.seek_row {
425 return self.cur_row > 0 && self.cur_row <= self.max_row;
426 }
427 if self.seek_row > self.max_row {
428 return false;
429 }
430 self.cur_row = self.seek_row;
431 self.row_opts = self.extract_row_opts();
432 true
433 }
434
435 pub fn get_row_opts(&self) -> RowOpts {
437 self.row_opts.clone()
438 }
439
440 pub fn error(&self) -> Option<&str> {
442 self.err.as_deref()
443 }
444
445 pub fn close(&self) -> Result<()> {
448 if let Some(e) = &self.err {
449 return Err(e.clone().into());
450 }
451 Ok(())
452 }
453
454 pub fn columns(&self, opts: Options) -> Result<Vec<String>> {
456 if self.cur_row < 1 || self.cur_row > self.max_row {
457 return Ok(Vec::new());
458 }
459 let row = &self.ws.sheet_data.row[(self.cur_row - 1) as usize];
460 let raw = opts.raw_cell_value;
461 let mut cells: Vec<String> = Vec::new();
462 let mut cell_col: i32 = 0;
463 for c in &row.c {
464 cell_col += 1;
465 if let Some(name) = &c.r {
466 cell_col = cell_name_to_coordinates(name)?.0;
467 }
468 let val = cell_value_string(self.file, c, raw);
469 if !val.is_empty() || c.f.is_some() {
470 let blank = cell_col - cells.len() as i32;
471 for _ in 1..blank {
472 cells.push(String::new());
473 }
474 cells.push(val);
475 }
476 }
477 Ok(cells)
478 }
479
480 fn extract_row_opts(&self) -> RowOpts {
481 if let Some(row) = self.ws.sheet_data.row.get((self.cur_row - 1) as usize) {
482 RowOpts {
483 height: row.ht.unwrap_or(DEFAULT_ROW_HEIGHT),
484 hidden: row.hidden.unwrap_or(false),
485 style_id: row
486 .s
487 .filter(|&s| s > 0 && s < MAX_CELL_STYLES as i64)
488 .unwrap_or(0) as i32,
489 }
490 } else {
491 RowOpts::default()
492 }
493 }
494}
495
496impl File {
497 pub fn rows(&self, sheet: &str) -> Result<Rows<'_>> {
499 let mut ws = self.work_sheet_reader(sheet)?;
500 prepare_rows(&mut ws);
501 if ws
502 .sheet_data
503 .row
504 .iter()
505 .any(|r| r.r.unwrap_or(0) > TOTAL_ROWS as i64)
506 {
507 return Err(Box::new(ErrMaxRows));
508 }
509 let max_row = ws
510 .sheet_data
511 .row
512 .last()
513 .map(|r| r.r.unwrap_or(0) as i32)
514 .unwrap_or(0);
515 Ok(Rows {
516 file: self,
517 ws,
518 cur_row: 0,
519 seek_row: 0,
520 max_row,
521 err: None,
522 row_opts: RowOpts::default(),
523 })
524 }
525
526 pub fn get_rows(&self, sheet: &str, opts: Options) -> Result<Vec<Vec<String>>> {
528 let mut rows = self.rows(sheet)?;
529 let mut results = Vec::new();
530 while rows.next() {
531 results.push(rows.columns(opts.clone())?);
532 }
533 Ok(results)
534 }
535}
536
537fn get_or_make_row(
542 ws: &mut crate::xml::worksheet::XlsxWorksheet,
543 row: i32,
544) -> &mut crate::xml::worksheet::XlsxRow {
545 if let Some(idx) = ws
546 .sheet_data
547 .row
548 .iter()
549 .position(|r| r.r == Some(row as i64))
550 {
551 return &mut ws.sheet_data.row[idx];
552 }
553 ws.sheet_data.row.push(crate::xml::worksheet::XlsxRow {
554 r: Some(row as i64),
555 ..Default::default()
556 });
557 let last = ws.sheet_data.row.len() - 1;
558 &mut ws.sheet_data.row[last]
559}
560
561fn duplicate_sq_ref_helper(row: i32, row2: i32, r#ref: &str) -> Result<Option<String>> {
564 let abs = r#ref.contains('$');
565 let mut expanded = r#ref.to_string();
566 if !expanded.contains(':') {
567 expanded.push(':');
568 expanded.push_str(r#ref);
569 }
570 let coords = range_ref_to_coordinates(&expanded)?;
571 let (x1, y1, x2, y2) = (coords[0], coords[1], coords[2], coords[3]);
572 if y1 == y2 && y1 == row {
573 Ok(Some(coordinates_to_range_ref(&[x1, row2, x2, row2], abs)?))
574 } else {
575 Ok(None)
576 }
577}
578
579fn shift_rows_down(ws: &mut crate::xml::worksheet::XlsxWorksheet, start_row: i64, n: i64) {
580 for row in &mut ws.sheet_data.row {
581 if let Some(row_num) = row.r {
582 if row_num >= start_row {
583 row.r = Some(row_num + n);
584 for c in &mut row.c {
585 if let Some(name) = &c.r {
586 if let Ok((col, old_row)) = crate::lib_util::cell_name_to_coordinates(name)
587 {
588 if let Ok(new_name) = crate::lib_util::coordinates_to_cell_name(
589 col,
590 old_row + n as i32,
591 false,
592 ) {
593 c.r = Some(new_name);
594 }
595 }
596 }
597 }
598 }
599 }
600 }
601}
602
603pub(crate) fn prepare_rows(ws: &mut XlsxWorksheet) {
606 if ws.sheet_data.row.is_empty() {
607 return;
608 }
609 let mut map: BTreeMap<i64, crate::xml::worksheet::XlsxRow> = BTreeMap::new();
610 let mut max_row = 0_i64;
611 let mut next = 1_i64;
612 for row in ws.sheet_data.row.drain(..) {
613 let r = row.r.unwrap_or(next);
614 max_row = max_row.max(r);
615 if !map.contains_key(&r) {
616 map.insert(r, row);
617 }
618 next = r + 1;
619 }
620 let mut rows = Vec::with_capacity(max_row as usize);
621 for r in 1..=max_row {
622 let mut row = map.remove(&r).unwrap_or_default();
623 row.r = Some(r);
624 rows.push(row);
625 }
626 ws.sheet_data.row = rows;
627}
628
629pub(crate) fn cell_value_string(
631 file: &File,
632 c: &crate::xml::worksheet::XlsxC,
633 raw: bool,
634) -> String {
635 let raw_value = match c.t.as_deref() {
636 Some("s") => {
637 if let Some(v) = &c.v {
638 if let Ok(idx) = v.parse::<i32>() {
639 return read_shared_string(file, idx);
640 }
641 }
642 return String::new();
643 }
644 Some("inlineStr") => {
645 return c.is.as_ref().map(inline_string_text).unwrap_or_default();
646 }
647 Some("b") => {
648 return c.v.as_deref().map_or(String::new(), |v| {
649 if v == "1" {
650 "TRUE".to_string()
651 } else {
652 "FALSE".to_string()
653 }
654 });
655 }
656 Some("str") => return c.v.clone().unwrap_or_default(),
657 _ => c.v.clone().unwrap_or_default(),
658 };
659
660 if c.f.is_some() && c.v.is_none() {
662 return format!("={}", c.f.as_ref().unwrap().content);
663 }
664
665 if raw {
666 return raw_value;
667 }
668
669 if let Some(num_fmt_id) = get_cell_num_fmt_id(file, c) {
670 if let Ok(value) = raw_value.parse::<f64>() {
671 let format_code = get_num_fmt_code(file, num_fmt_id);
672 let date1904 = file
673 .workbook_reader()
674 .ok()
675 .and_then(|wb| wb.workbook_pr.as_ref().and_then(|p| p.date1904))
676 .unwrap_or(false);
677 return crate::numfmt::apply_number_format(
678 value,
679 num_fmt_id,
680 format_code.as_deref(),
681 date1904,
682 );
683 }
684 }
685
686 raw_value
687}
688
689fn read_shared_string(file: &File, idx: i32) -> String {
690 let sst = file.shared_strings_reader().unwrap_or_default();
691 sst.si.get(idx as usize).map_or(String::new(), |si| {
692 if let Some(t) = &si.t {
693 t.val.clone()
694 } else {
695 si.r.iter()
696 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
697 .collect()
698 }
699 })
700}
701
702fn inline_string_text(si: &crate::xml::shared_strings::XlsxSi) -> String {
703 if let Some(t) = &si.t {
704 return t.val.clone();
705 }
706 si.r.iter()
707 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
708 .collect()
709}
710
711fn get_cell_num_fmt_id(file: &File, c: &crate::xml::worksheet::XlsxC) -> Option<i32> {
712 let style_id = c.s.unwrap_or(0) as usize;
713 let styles = file.styles_reader().ok()?;
714 let cell_xfs = styles.cell_xfs?;
715 let xf = cell_xfs.xf.get(style_id)?;
716 xf.num_fmt_id.map(|id| id as i32)
717}
718
719fn get_num_fmt_code(file: &File, num_fmt_id: i32) -> Option<String> {
720 let styles = file.styles_reader().ok()?;
721 styles
722 .num_fmts?
723 .num_fmt
724 .into_iter()
725 .find(|n| n.num_fmt_id == num_fmt_id as i64)
726 .map(|n| n.format_code)
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732
733 #[test]
734 fn row_height_round_trip() {
735 let f = File::new_with_options(crate::options::Options::default());
736 f.set_row_height("Sheet1", 2, 30.0).unwrap();
737 assert_eq!(f.get_row_height("Sheet1", 2).unwrap(), 30.0);
738 assert_eq!(f.get_row_height("Sheet1", 3).unwrap(), DEFAULT_ROW_HEIGHT);
739 }
740
741 #[test]
742 fn row_visible_round_trip() {
743 let f = File::new_with_options(crate::options::Options::default());
744 f.set_row_visible("Sheet1", 2, false).unwrap();
745 assert!(!f.get_row_visible("Sheet1", 2).unwrap());
746 assert!(f.get_row_visible("Sheet1", 1).unwrap());
747 }
748
749 #[test]
750 fn set_row_style_range() {
751 let f = File::new_with_options(crate::options::Options::default());
752 let style = f
753 .new_style(&crate::styles::Style {
754 fill: crate::styles::Fill {
755 r#type: "pattern".to_string(),
756 pattern: 1,
757 color: vec!["63BE7B".to_string()],
758 ..Default::default()
759 },
760 ..Default::default()
761 })
762 .unwrap();
763 f.set_cell_str("Sheet1", "B2", "x").unwrap();
764 f.set_cell_str("Sheet1", "C1", "y").unwrap();
765
766 f.set_row_style("Sheet1", 2, 3, style).unwrap();
768 assert_eq!(f.get_cell_style("Sheet1", "B2").unwrap(), style);
769 assert_eq!(f.get_cell_style("Sheet1", "A3").unwrap(), style);
770 assert_eq!(f.get_cell_style("Sheet1", "C1").unwrap(), 0);
771
772 f.set_cell_str("Sheet1", "A5", "z").unwrap();
774 f.set_row_style("Sheet1", 5, 4, style).unwrap();
775 assert_eq!(f.get_cell_style("Sheet1", "A5").unwrap(), style);
776
777 assert!(f.set_row_style("Sheet1", 0, 1, style).is_err());
779 assert!(f.set_row_style("Sheet1", 1, TOTAL_ROWS + 1, style).is_err());
780 assert!(f.set_row_style("Sheet1", 1, 1, -1).is_err());
781 assert!(f.set_row_style("Sheet1", 1, 1, 999).is_err());
782 }
783
784 #[test]
785 fn rows_iterator_and_get_rows() {
786 let f = File::new_with_options(crate::options::Options::default());
787 f.set_cell_str("Sheet1", "A1", "a").unwrap();
788 f.set_cell_str("Sheet1", "C1", "c").unwrap();
789 f.set_cell_str("Sheet1", "A3", "x").unwrap();
790
791 let mut rows = f.rows("Sheet1").unwrap();
792 assert!(rows.next());
793 assert_eq!(
794 rows.columns(Options::default()).unwrap(),
795 vec!["a", "", "c"]
796 );
797 assert!(rows.next());
798 assert_eq!(
799 rows.columns(Options::default()).unwrap(),
800 Vec::<String>::new()
801 );
802 assert!(rows.next());
803 assert_eq!(rows.columns(Options::default()).unwrap(), vec!["x"]);
804 assert!(!rows.next());
805 rows.close().unwrap();
806
807 let all = f.get_rows("Sheet1", Options::default()).unwrap();
808 assert_eq!(all, vec![vec!["a", "", "c"], vec![], vec!["x"]]);
809 }
810
811 #[test]
812 fn rows_iterator_opts() {
813 let mut opts = Options::default();
814 opts.raw_cell_value = true;
815 let f = File::new_with_options(opts.clone());
816 f.set_cell_value(
817 "Sheet1",
818 "A1",
819 crate::cell::CellValue::Date(chrono::NaiveDate::from_ymd_opt(2024, 7, 13).unwrap()),
820 )
821 .unwrap();
822 let mut rows = f.rows("Sheet1").unwrap();
823 assert!(rows.next());
824 assert_eq!(rows.columns(opts).unwrap(), vec!["45486"]);
825 }
826
827 #[test]
828 fn duplicate_row_to_basic() {
829 let f = File::new_with_options(crate::options::Options::default());
830 f.set_cell_str("Sheet1", "A2", "x").unwrap();
831 f.set_cell_str("Sheet1", "B2", "y").unwrap();
832 f.duplicate_row_to("Sheet1", 2, 5).unwrap();
833 assert_eq!(f.get_cell_value("Sheet1", "A2").unwrap(), "x");
834 assert_eq!(f.get_cell_value("Sheet1", "A5").unwrap(), "x");
835 assert_eq!(f.get_cell_value("Sheet1", "B5").unwrap(), "y");
836 }
837
838 #[test]
839 fn remove_row_shifts_formulas_and_values() {
840 let f = File::new_with_options(crate::options::Options::default());
841 f.set_cell_str("Sheet1", "A1", "a").unwrap();
842 f.set_cell_formula("Sheet1", "A3", "A1+1").unwrap();
843 f.set_cell_formula("Sheet1", "A4", "A3+1").unwrap();
844 f.remove_row("Sheet1", 2).unwrap();
845
846 assert_eq!(f.get_cell_formula("Sheet1", "A2").unwrap(), "A1+1");
847 assert_eq!(f.get_cell_formula("Sheet1", "A3").unwrap(), "A2+1");
848 }
849
850 #[test]
851 fn remove_row_clears_shared_formula_siblings() {
852 let f = File::new_with_options(crate::options::Options::default());
853 let path = f.get_sheet_xml_path("Sheet1").unwrap();
854 let mut ws = f.work_sheet_reader("Sheet1").unwrap();
855
856 ws.sheet_data.row.push(crate::xml::worksheet::XlsxRow {
857 r: Some(1),
858 c: vec![crate::xml::worksheet::XlsxC {
859 r: Some("A1".to_string()),
860 f: Some(crate::xml::worksheet::XlsxF {
861 content: "A2*2".to_string(),
862 t: Some("shared".to_string()),
863 r#ref: Some("A1:A3".to_string()),
864 si: Some(0),
865 ..Default::default()
866 }),
867 ..Default::default()
868 }],
869 ..Default::default()
870 });
871 ws.sheet_data.row.push(crate::xml::worksheet::XlsxRow {
872 r: Some(2),
873 c: vec![crate::xml::worksheet::XlsxC {
874 r: Some("A2".to_string()),
875 f: Some(crate::xml::worksheet::XlsxF {
876 t: Some("shared".to_string()),
877 si: Some(0),
878 ..Default::default()
879 }),
880 ..Default::default()
881 }],
882 ..Default::default()
883 });
884 ws.sheet_data.row.push(crate::xml::worksheet::XlsxRow {
885 r: Some(3),
886 c: vec![crate::xml::worksheet::XlsxC {
887 r: Some("A3".to_string()),
888 f: Some(crate::xml::worksheet::XlsxF {
889 t: Some("shared".to_string()),
890 si: Some(0),
891 ..Default::default()
892 }),
893 ..Default::default()
894 }],
895 ..Default::default()
896 });
897 f.sheet.insert(path, ws);
898
899 f.remove_row("Sheet1", 1).unwrap();
900 let ws = f.work_sheet_reader("Sheet1").unwrap();
901 assert!(
902 ws.sheet_data
903 .row
904 .iter()
905 .flat_map(|r| &r.c)
906 .all(|c| c.f.is_none())
907 );
908 }
909
910 #[test]
911 fn duplicate_row_to_copies_single_row_merge() {
912 let f = File::new_with_options(crate::options::Options::default());
913 f.set_cell_str("Sheet1", "A1", "merged").unwrap();
914 f.merge_cell("Sheet1", "A1", "B1").unwrap();
915 f.duplicate_row_to("Sheet1", 1, 3).unwrap();
916
917 let merges = f.get_merge_cells("Sheet1").unwrap();
918 assert!(merges.contains(&"A1:B1".to_string()));
919 assert!(merges.contains(&"A3:B3".to_string()));
920 assert_eq!(f.get_cell_value("Sheet1", "A3").unwrap(), "merged");
921 }
922}