Skip to main content

excelize_rs/
col.rs

1//! Column-level API.
2//!
3//! This module corresponds to `col.go` in the Go implementation.
4
5use std::collections::HashMap;
6
7use crate::constants::{
8    DEFAULT_COL_WIDTH, DEFAULT_COL_WIDTH_PIXELS, DEFAULT_FONT_SIZE, MAX_COLUMN_WIDTH, TOTAL_ROWS,
9};
10use crate::errors::Result;
11use crate::errors::{ErrColumnNumber, ErrColumnWidth, ErrMaxRows, ErrSheetNotExist};
12use crate::file::File;
13use crate::lib_util::{cell_name_to_coordinates, column_name_to_number};
14use crate::options::Options;
15use crate::row::cell_value_string;
16use crate::styles::Font;
17use crate::xml::common::RichTextRun;
18use crate::xml::worksheet::{XlsxCol, XlsxCols, XlsxWorksheet};
19
20impl File {
21    /// Set the width of one or more columns.
22    pub fn set_col_width(
23        &self,
24        sheet: &str,
25        start_col: &str,
26        end_col: &str,
27        width: f64,
28    ) -> Result<()> {
29        if width > MAX_COLUMN_WIDTH as f64 {
30            return Err(Box::new(ErrColumnWidth));
31        }
32        let start = column_name_to_number(start_col)?;
33        let end = column_name_to_number(end_col)?;
34        if start > end {
35            return Err(ErrColumnNumber.to_string().into());
36        }
37        let path = self
38            .get_sheet_xml_path(sheet)
39            .ok_or_else(|| ErrSheetNotExist {
40                sheet_name: sheet.to_string(),
41            })?;
42        let mut ws = self.work_sheet_reader(sheet)?;
43        ensure_cols(&mut ws);
44        let cols = ws.cols.as_mut().unwrap();
45        merge_col_range(&mut cols.col, start as i64, end as i64, |col| {
46            col.width = Some(width);
47            col.custom_width = Some(true);
48            col.best_fit = Some(true);
49        });
50        self.sheet.insert(path, ws);
51        Ok(())
52    }
53
54    /// Get the width of a column.
55    pub fn get_col_width(&self, sheet: &str, col: &str) -> Result<f64> {
56        let col_num = column_name_to_number(col)?;
57        let ws = self.work_sheet_reader(sheet)?;
58        if let Some(cols) = &ws.cols {
59            for c in &cols.col {
60                if col_num as i64 >= c.min && col_num as i64 <= c.max {
61                    return Ok(c.width.unwrap_or(DEFAULT_COL_WIDTH));
62                }
63            }
64        }
65        if let Some(fmt) = &ws.sheet_format_pr {
66            if let Some(w) = fmt.default_col_width {
67                return Ok(w);
68            }
69        }
70        Ok(DEFAULT_COL_WIDTH)
71    }
72
73    /// Set the visibility of one or more columns.
74    pub fn set_col_visible(
75        &self,
76        sheet: &str,
77        start_col: &str,
78        end_col: &str,
79        visible: bool,
80    ) -> Result<()> {
81        let start = column_name_to_number(start_col)?;
82        let end = column_name_to_number(end_col)?;
83        let path = self
84            .get_sheet_xml_path(sheet)
85            .ok_or_else(|| ErrSheetNotExist {
86                sheet_name: sheet.to_string(),
87            })?;
88        let mut ws = self.work_sheet_reader(sheet)?;
89        ensure_cols(&mut ws);
90        let cols = ws.cols.as_mut().unwrap();
91        merge_col_range(&mut cols.col, start as i64, end as i64, |col| {
92            col.hidden = Some(!visible);
93        });
94        self.sheet.insert(path, ws);
95        Ok(())
96    }
97
98    /// Get the visibility of a column.
99    pub fn get_col_visible(&self, sheet: &str, col: &str) -> Result<bool> {
100        let col_num = column_name_to_number(col)?;
101        let ws = self.work_sheet_reader(sheet)?;
102        if let Some(cols) = &ws.cols {
103            for c in &cols.col {
104                if col_num as i64 >= c.min && col_num as i64 <= c.max {
105                    return Ok(!c.hidden.unwrap_or(false));
106                }
107            }
108        }
109        Ok(true)
110    }
111
112    /// Set the outline level of one or more columns.
113    pub fn set_col_outline_level(
114        &self,
115        sheet: &str,
116        start_col: &str,
117        end_col: &str,
118        level: u8,
119    ) -> Result<()> {
120        if level == 0 || level > 7 {
121            return Err(Box::new(crate::errors::ErrOutlineLevel));
122        }
123        let start = column_name_to_number(start_col)?;
124        let end = column_name_to_number(end_col)?;
125        let path = self
126            .get_sheet_xml_path(sheet)
127            .ok_or_else(|| ErrSheetNotExist {
128                sheet_name: sheet.to_string(),
129            })?;
130        let mut ws = self.work_sheet_reader(sheet)?;
131        ensure_cols(&mut ws);
132        let cols = ws.cols.as_mut().unwrap();
133        merge_col_range(&mut cols.col, start as i64, end as i64, |col| {
134            col.outline_level = Some(level);
135        });
136        self.sheet.insert(path, ws);
137        Ok(())
138    }
139
140    /// Get the outline level of a column.
141    pub fn get_col_outline_level(&self, sheet: &str, col: &str) -> Result<u8> {
142        let col_num = column_name_to_number(col)?;
143        let ws = self.work_sheet_reader(sheet)?;
144        if let Some(cols) = &ws.cols {
145            for c in &cols.col {
146                if col_num as i64 >= c.min && col_num as i64 <= c.max {
147                    return Ok(c.outline_level.unwrap_or(0));
148                }
149            }
150        }
151        Ok(0)
152    }
153
154    /// Insert `n` columns before `col`.
155    pub fn insert_cols(&self, sheet: &str, col: &str, n: usize) -> Result<()> {
156        if n == 0 {
157            return Ok(());
158        }
159        let col_num = column_name_to_number(col)?;
160        let path = self
161            .get_sheet_xml_path(sheet)
162            .ok_or_else(|| ErrSheetNotExist {
163                sheet_name: sheet.to_string(),
164            })?;
165        let mut ws = self.work_sheet_reader(sheet)?;
166        shift_cols_right(&mut ws, col_num as i64, n as i64);
167        self.sheet.insert(path, ws);
168        Ok(())
169    }
170
171    /// Remove a column.
172    pub fn remove_col(&self, sheet: &str, col: &str) -> Result<()> {
173        let col_num = column_name_to_number(col)?;
174        let path = self
175            .get_sheet_xml_path(sheet)
176            .ok_or_else(|| ErrSheetNotExist {
177                sheet_name: sheet.to_string(),
178            })?;
179        let mut ws = self.work_sheet_reader(sheet)?;
180        // Remove cells in the column and shift others left.
181        for row in &mut ws.sheet_data.row {
182            row.c.retain(|c| {
183                if let Some(name) = &c.r {
184                    if let Ok((cnum, _)) = crate::lib_util::cell_name_to_coordinates(name) {
185                        return cnum != col_num;
186                    }
187                }
188                true
189            });
190            for cell in &mut row.c {
191                if let Some(name) = &cell.r {
192                    if let Ok((cnum, row_num)) = crate::lib_util::cell_name_to_coordinates(name) {
193                        if cnum > col_num {
194                            if let Ok(new_name) =
195                                crate::lib_util::coordinates_to_cell_name(cnum - 1, row_num, false)
196                            {
197                                cell.r = Some(new_name);
198                            }
199                        }
200                    }
201                }
202            }
203        }
204        // Update cols definitions.
205        if let Some(cols) = ws.cols.as_mut() {
206            let mut new_cols = Vec::new();
207            for c in &cols.col {
208                if c.max < col_num as i64 {
209                    new_cols.push(c.clone());
210                } else if c.min > col_num as i64 {
211                    let mut nc = c.clone();
212                    nc.min -= 1;
213                    nc.max -= 1;
214                    new_cols.push(nc);
215                } else {
216                    if c.min < col_num as i64 {
217                        let mut before = c.clone();
218                        before.max = col_num as i64 - 1;
219                        new_cols.push(before);
220                    }
221                    if c.max > col_num as i64 {
222                        let mut after = c.clone();
223                        after.min = col_num as i64;
224                        after.max -= 1;
225                        new_cols.push(after);
226                    }
227                }
228            }
229            cols.col = new_cols;
230        }
231        self.sheet.insert(path, ws);
232        Ok(())
233    }
234
235    /// Apply a style index to one or more columns.
236    pub fn set_col_style(
237        &self,
238        sheet: &str,
239        start_col: &str,
240        end_col: &str,
241        style_id: i32,
242    ) -> Result<()> {
243        if style_id < 0 {
244            return Err(crate::errors::new_invalid_style_id_error(style_id).into());
245        }
246        let style_count = self
247            .styles_reader()?
248            .cell_xfs
249            .as_ref()
250            .map(|x| x.xf.len())
251            .unwrap_or(1) as i32;
252        if style_id >= style_count {
253            return Err(crate::errors::new_invalid_style_id_error(style_id).into());
254        }
255        let start = column_name_to_number(start_col)?;
256        let end = column_name_to_number(end_col)?;
257        let path = self
258            .get_sheet_xml_path(sheet)
259            .ok_or_else(|| ErrSheetNotExist {
260                sheet_name: sheet.to_string(),
261            })?;
262        let mut ws = self.work_sheet_reader(sheet)?;
263        ensure_cols(&mut ws);
264        let cols = ws.cols.as_mut().unwrap();
265        merge_col_range(&mut cols.col, start as i64, end as i64, |col| {
266            col.style = Some(style_id as i64);
267        });
268        self.sheet.insert(path, ws);
269        Ok(())
270    }
271
272    /// Get the style ID applied to a column.
273    pub fn get_col_style(&self, sheet: &str, col: &str) -> Result<i32> {
274        let col_num = column_name_to_number(col)?;
275        let ws = self.work_sheet_reader(sheet)?;
276        if let Some(cols) = &ws.cols {
277            for c in &cols.col {
278                if col_num as i64 >= c.min && col_num as i64 <= c.max {
279                    return Ok(c.style.unwrap_or(0) as i32);
280                }
281            }
282        }
283        Ok(0)
284    }
285
286    /// Auto fit column width according to the text content.
287    ///
288    /// Equivalent to Go `AutoFitColWidth`. The width is calculated approximately
289    /// based on the default font format.
290    pub fn auto_fit_col_width(&self, sheet: &str, columns: &str) -> Result<()> {
291        let (min_col, max_col) = parse_col_range(columns)?;
292        let rows = self.get_rows(sheet, Options::default())?;
293        let default_fnt = read_default_font(self);
294        let path = self
295            .get_sheet_xml_path(sheet)
296            .ok_or_else(|| ErrSheetNotExist {
297                sheet_name: sheet.to_string(),
298            })?;
299        let mut ws = self.work_sheet_reader(sheet)?;
300        for col in min_col..=max_col {
301            let mut width = 0.0;
302            for (row_idx, _) in rows.iter().enumerate() {
303                let cell =
304                    crate::lib_util::coordinates_to_cell_name(col, row_idx as i32 + 1, false)?;
305                let val = match self.calc_cell_value(sheet, &cell) {
306                    Ok(v) => v,
307                    Err(_) => continue,
308                };
309                if val.is_empty() {
310                    continue;
311                }
312                let style_id = self.get_cell_style(sheet, &cell).unwrap_or(0);
313                let mut fnt = default_fnt.clone();
314                if let Ok(style) = self.get_style(style_id) {
315                    if let Some(font) = style.font {
316                        fnt = font;
317                    }
318                }
319                if let Ok(cell_type) = self.get_cell_type(sheet, &cell) {
320                    if cell_type == crate::cell::CellType::InlineString
321                        || cell_type == crate::cell::CellType::SharedString
322                    {
323                        let runs = self.get_cell_rich_text(sheet, &cell).unwrap_or_default();
324                        let w = calc_rich_text_width(&fnt, &runs);
325                        if w > width {
326                            width = w;
327                        }
328                        continue;
329                    }
330                }
331                let w = calc_text_width(&fnt, &val);
332                if w > width {
333                    width = w;
334                }
335            }
336            if width > 0.0 {
337                width += 2.0;
338                if width > MAX_COLUMN_WIDTH as f64 {
339                    width = MAX_COLUMN_WIDTH as f64;
340                }
341                ensure_cols(&mut ws);
342                let cols = ws.cols.as_mut().unwrap();
343                merge_col_range(&mut cols.col, col as i64, col as i64, |c| {
344                    c.hidden = Some(false);
345                    c.width = Some(width);
346                    c.custom_width = Some(true);
347                });
348            }
349        }
350        self.sheet.insert(path, ws);
351        Ok(())
352    }
353}
354
355// ------------------------------------------------------------------
356// Column iterator
357// ------------------------------------------------------------------
358
359/// Streaming column iterator.
360#[derive(Debug)]
361pub struct Cols<'a> {
362    file: &'a File,
363    ws: XlsxWorksheet,
364    cur_col: i32,
365    total_cols: i32,
366    total_rows: i32,
367    err: Option<String>,
368}
369
370impl<'a> Cols<'a> {
371    /// Advance to the next column. Returns `false` when there are no more columns.
372    pub fn next(&mut self) -> bool {
373        self.cur_col += 1;
374        self.cur_col <= self.total_cols
375    }
376
377    /// Return the first error that occurred during iteration, if any.
378    pub fn error(&self) -> Option<&str> {
379        self.err.as_deref()
380    }
381
382    /// Return the values of the current column's cells as strings.
383    pub fn rows(&self, opts: Options) -> Result<Vec<String>> {
384        if self.cur_col < 1 || self.cur_col > self.total_cols || self.total_rows == 0 {
385            return Ok(Vec::new());
386        }
387        let mut cells = vec![String::new(); self.total_rows as usize];
388        for row in &self.ws.sheet_data.row {
389            let row_num = row.r.unwrap_or(0) as i32;
390            if row_num < 1 || row_num > self.total_rows {
391                continue;
392            }
393            let mut cell_col: i32 = 0;
394            for c in &row.c {
395                cell_col += 1;
396                if let Some(name) = &c.r {
397                    cell_col = cell_name_to_coordinates(name)?.0;
398                }
399                if cell_col == self.cur_col {
400                    cells[(row_num - 1) as usize] =
401                        cell_value_string(self.file, c, opts.raw_cell_value);
402                }
403            }
404        }
405        Ok(cells)
406    }
407}
408
409impl File {
410    /// Return a streaming iterator over the columns of a worksheet.
411    pub fn cols(&self, sheet: &str) -> Result<Cols<'_>> {
412        let ws = self.work_sheet_reader(sheet)?;
413        let (total_rows, total_cols) = scan_dimensions(&ws)?;
414        Ok(Cols {
415            file: self,
416            ws,
417            cur_col: 0,
418            total_cols,
419            total_rows,
420            err: None,
421        })
422    }
423
424    /// Return all columns in a worksheet as a two-dimensional vector of strings.
425    pub fn get_cols(&self, sheet: &str, opts: Options) -> Result<Vec<Vec<String>>> {
426        let mut cols = self.cols(sheet)?;
427        let mut results = Vec::new();
428        while cols.next() {
429            results.push(cols.rows(opts.clone())?);
430        }
431        Ok(results)
432    }
433}
434
435fn scan_dimensions(ws: &XlsxWorksheet) -> Result<(i32, i32)> {
436    let mut total_rows = 0;
437    let mut total_cols = 0;
438    let mut row_num = 0;
439    for row in &ws.sheet_data.row {
440        row_num += 1;
441        if let Some(r) = row.r {
442            row_num = r as i32;
443        }
444        if row_num > TOTAL_ROWS {
445            return Err(Box::new(ErrMaxRows));
446        }
447        total_rows = total_rows.max(row_num);
448        let mut cell_col = 0;
449        for c in &row.c {
450            cell_col += 1;
451            if let Some(name) = &c.r {
452                cell_col = cell_name_to_coordinates(name)?.0;
453            }
454            total_cols = total_cols.max(cell_col);
455        }
456    }
457    Ok((total_rows, total_cols))
458}
459
460// ------------------------------------------------------------------
461// Internal helpers
462// ------------------------------------------------------------------
463
464fn ensure_cols(ws: &mut crate::xml::worksheet::XlsxWorksheet) {
465    if ws.cols.is_none() {
466        ws.cols = Some(XlsxCols::default());
467    }
468}
469
470/// Apply a mutation to every column record covering `[min, max]`, splitting
471/// existing records when needed so the range is exactly represented.
472fn merge_col_range(cols: &mut Vec<XlsxCol>, min: i64, max: i64, mut f: impl FnMut(&mut XlsxCol)) {
473    let mut new_cols = Vec::new();
474    let mut covered = false;
475    for col in cols.drain(..) {
476        if col.max < min || col.min > max {
477            new_cols.push(col);
478            continue;
479        }
480        covered = true;
481        if col.min < min {
482            let mut before = col.clone();
483            before.max = min - 1;
484            new_cols.push(before);
485        }
486        let mut target = col.clone();
487        target.min = target.min.max(min);
488        target.max = target.max.min(max);
489        f(&mut target);
490        new_cols.push(target);
491        if col.max > max {
492            let mut after = col;
493            after.min = max + 1;
494            new_cols.push(after);
495        }
496    }
497    if !covered {
498        let mut target = XlsxCol {
499            min,
500            max,
501            ..Default::default()
502        };
503        f(&mut target);
504        new_cols.push(target);
505    }
506    // Merge adjacent identical definitions.
507    new_cols.sort_by_key(|c| c.min);
508    let mut merged: Vec<XlsxCol> = Vec::new();
509    for col in new_cols {
510        if let Some(last) = merged.last_mut() {
511            if last.width == col.width
512                && last.hidden == col.hidden
513                && last.outline_level == col.outline_level
514                && last.style == col.style
515                && last.custom_width == col.custom_width
516                && last.best_fit == col.best_fit
517                && last.max + 1 == col.min
518            {
519                last.max = col.max;
520                continue;
521            }
522        }
523        merged.push(col);
524    }
525    *cols = merged;
526}
527
528fn shift_cols_right(ws: &mut crate::xml::worksheet::XlsxWorksheet, start_col: i64, n: i64) {
529    // Shift cell references.
530    for row in &mut ws.sheet_data.row {
531        for cell in &mut row.c {
532            if let Some(name) = &cell.r {
533                if let Ok((col, row_num)) = crate::lib_util::cell_name_to_coordinates(name) {
534                    if col >= start_col as i32 {
535                        let new_col = (col as i64 + n) as i32;
536                        if let Ok(new_name) =
537                            crate::lib_util::coordinates_to_cell_name(new_col, row_num, false)
538                        {
539                            cell.r = Some(new_name);
540                        }
541                    }
542                }
543            }
544        }
545    }
546    // Shift col definitions.
547    if let Some(cols) = ws.cols.as_mut() {
548        for col in &mut cols.col {
549            if col.min >= start_col {
550                col.min += n;
551                col.max += n;
552            } else if col.max >= start_col {
553                col.max += n;
554            }
555        }
556    }
557}
558
559fn parse_col_range(columns: &str) -> Result<(i32, i32)> {
560    let parts: Vec<&str> = columns.split(':').collect();
561    let min = column_name_to_number(parts[0])?;
562    let max = if parts.len() == 2 {
563        column_name_to_number(parts[1])?
564    } else {
565        min
566    };
567    if min <= max {
568        Ok((min, max))
569    } else {
570        Ok((max, min))
571    }
572}
573
574fn read_default_font(file: &File) -> Font {
575    if let Ok(styles) = file.styles_reader() {
576        if let Some(fonts) = styles.fonts {
577            if let Some(f) = fonts.font.first() {
578                return Font {
579                    size: f.sz.as_ref().and_then(|s| s.val),
580                    name: f.name.as_ref().and_then(|n| n.val.clone()),
581                    family: f.family.as_ref().and_then(|v| v.val),
582                    bold: f.b.as_ref().and_then(|b| b.val),
583                    italic: f.i.as_ref().and_then(|i| i.val),
584                    strike: f.strike.as_ref().and_then(|s| s.val),
585                    ..Default::default()
586                };
587            }
588        }
589    }
590    Font {
591        size: Some(DEFAULT_FONT_SIZE),
592        name: Some("Calibri".to_string()),
593        ..Default::default()
594    }
595}
596
597fn font_width_factors(name: Option<&str>) -> (f64, f64, f64) {
598    let map: HashMap<String, (f64, f64, f64)> = [
599        ("calibri", (0.97, 1.30, 1.00)),
600        ("aptos", (1.03, 1.37, 1.00)),
601        ("arial", (1.07, 1.42, 1.00)),
602        ("arial narrow", (0.88, 1.20, 1.00)),
603        ("calibri light", (0.98, 1.19, 1.00)),
604        ("cambria", (1.07, 1.28, 1.00)),
605        ("consolas", (1.21, 1.21, 1.00)),
606        ("courier new", (1.45, 1.45, 1.00)),
607        ("times new roman", (1.07, 1.42, 1.00)),
608        ("verdana", (1.10, 1.45, 1.00)),
609    ]
610    .iter()
611    .map(|(k, v)| (k.to_string(), *v))
612    .collect();
613    name.and_then(|n| map.get(&n.to_lowercase()))
614        .copied()
615        .unwrap_or((1.0, 1.0, 1.0))
616}
617
618fn is_wide_rune(r: char) -> bool {
619    matches!(
620        r,
621        '\u{1100}'..='\u{115F}'
622            | '\u{2E80}'..='\u{9FFF}'
623            | '\u{A960}'..='\u{A97F}'
624            | '\u{AC00}'..='\u{D7AF}'
625            | '\u{F900}'..='\u{FAFF}'
626            | '\u{FF01}'..='\u{FF60}'
627            | '\u{FFE0}'..='\u{FFE6}'
628    )
629}
630
631fn calc_text_width(font: &Font, text: &str) -> f64 {
632    let mut lower = 0.0;
633    let mut upper = 0.0;
634    let mut wide = 0.0;
635    for r in text.chars() {
636        if r == '\n' || r == '\r' {
637            continue;
638        }
639        if is_wide_rune(r) {
640            wide += 2.0;
641        } else if r.is_uppercase() {
642            upper += 1.0;
643        } else {
644            lower += 1.0;
645        }
646    }
647    let size = font.size.unwrap_or(DEFAULT_FONT_SIZE);
648    let (lf, uf, wf) = font_width_factors(font.name.as_deref());
649    let mut w = lower * lf + upper * uf + wide * wf;
650    w *= size / DEFAULT_FONT_SIZE;
651    if font.bold.unwrap_or(false) {
652        w *= 1.05;
653    }
654    if font.italic.unwrap_or(false) {
655        w *= 1.05;
656    }
657    if font.vert_align.as_deref().unwrap_or("baseline") != "baseline" {
658        w *= 0.6;
659    }
660    w
661}
662
663fn calc_rich_text_width(default_font: &Font, runs: &[RichTextRun]) -> f64 {
664    let mut w = 0.0;
665    let mut width = 0.0;
666    for run in runs {
667        let mut fnt = default_font.clone();
668        if let Some(font) = &run.font {
669            fnt = font.clone();
670        }
671        if let Some(i) = run.text.find(['\n', '\r'].as_ref()) {
672            let first = &run.text[..i];
673            let rest = &run.text[i + 1..];
674            w += calc_text_width(&fnt, first);
675            if w > width {
676                width = w;
677            }
678            w = calc_text_width(&fnt, rest);
679        } else {
680            w += calc_text_width(&fnt, &run.text);
681        }
682    }
683    if w > width {
684        width = w;
685    }
686    width
687}
688
689#[cfg(test)]
690mod tests {
691    use super::*;
692
693    #[test]
694    fn col_width_round_trip() {
695        let f = File::new_with_options(crate::options::Options::default());
696        f.set_col_width("Sheet1", "A", "C", 20.0).unwrap();
697        assert_eq!(f.get_col_width("Sheet1", "B").unwrap(), 20.0);
698        assert_eq!(f.get_col_width("Sheet1", "D").unwrap(), DEFAULT_COL_WIDTH);
699    }
700
701    #[test]
702    fn col_visible_round_trip() {
703        let f = File::new_with_options(crate::options::Options::default());
704        f.set_col_visible("Sheet1", "B", "B", false).unwrap();
705        assert!(!f.get_col_visible("Sheet1", "B").unwrap());
706        assert!(f.get_col_visible("Sheet1", "A").unwrap());
707    }
708
709    #[test]
710    fn cols_iterator_and_get_cols() {
711        let f = File::new_with_options(crate::options::Options::default());
712        f.set_cell_str("Sheet1", "A1", "a").unwrap();
713        f.set_cell_str("Sheet1", "B2", "b").unwrap();
714
715        let mut cols = f.cols("Sheet1").unwrap();
716        assert!(cols.next());
717        assert_eq!(cols.rows(Options::default()).unwrap(), vec!["a", ""]);
718        assert!(cols.next());
719        assert_eq!(cols.rows(Options::default()).unwrap(), vec!["", "b"]);
720        assert!(!cols.next());
721
722        let all = f.get_cols("Sheet1", Options::default()).unwrap();
723        assert_eq!(all, vec![vec!["a", ""], vec!["", "b"]]);
724    }
725
726    #[test]
727    fn col_style_round_trip() {
728        let f = File::new_with_options(crate::options::Options::default());
729        f.set_col_style("Sheet1", "B", "D", 0).unwrap();
730        assert_eq!(f.get_col_style("Sheet1", "C").unwrap(), 0);
731    }
732
733    #[test]
734    fn auto_fit_col_width_basic() {
735        let f = File::new_with_options(crate::options::Options::default());
736        f.set_cell_str("Sheet1", "A1", "Hello, World!").unwrap();
737        let before = f.get_col_width("Sheet1", "A").unwrap();
738        f.auto_fit_col_width("Sheet1", "A").unwrap();
739        let after = f.get_col_width("Sheet1", "A").unwrap();
740        assert!(after > before);
741    }
742}