Skip to main content

spreadsheet_kit/
write.rs

1use crate::types::{CellEdit, CoreWarning};
2use anyhow::{Context, Result, anyhow, bail};
3use std::path::Path;
4
5pub fn normalize_shorthand_edit(entry: &str) -> Result<(CellEdit, Vec<CoreWarning>)> {
6    let Some((address_raw, rhs_raw)) = entry.split_once('=') else {
7        bail!("invalid shorthand edit: '{entry}' (expected like 'A1=100' or 'B2==SUM(A1:A2)')");
8    };
9
10    let address = address_raw.trim();
11    if address.is_empty() {
12        bail!("invalid shorthand edit: '{entry}' (missing cell address before '=')");
13    }
14
15    let mut warnings = vec![CoreWarning {
16        code: "WARN_SHORTHAND_EDIT".to_string(),
17        message: format!("Parsed shorthand edit '{}'", entry),
18    }];
19
20    let rhs_trimmed = rhs_raw.trim_start();
21    if let Some(stripped) = rhs_trimmed.strip_prefix('=') {
22        warnings.push(CoreWarning {
23            code: "WARN_FORMULA_PREFIX".to_string(),
24            message: format!("Stripped leading '=' for formula '{}'", entry),
25        });
26        Ok((
27            CellEdit {
28                address: address.to_string(),
29                value: stripped.to_string(),
30                is_formula: true,
31            },
32            warnings,
33        ))
34    } else {
35        Ok((
36            CellEdit {
37                address: address.to_string(),
38                value: rhs_raw.to_string(),
39                is_formula: false,
40            },
41            warnings,
42        ))
43    }
44}
45
46pub fn normalize_object_edit(
47    address: &str,
48    value: Option<String>,
49    formula: Option<String>,
50    is_formula: Option<bool>,
51) -> Result<(CellEdit, Vec<CoreWarning>)> {
52    let address = address.trim();
53    if address.is_empty() {
54        bail!("edit address is required");
55    }
56
57    let mut warnings = Vec::new();
58    let (value, is_formula) = if let Some(formula) = formula {
59        if let Some(stripped) = formula.strip_prefix('=') {
60            warnings.push(CoreWarning {
61                code: "WARN_FORMULA_PREFIX".to_string(),
62                message: format!("Stripped leading '=' for formula at {}", address),
63            });
64            (stripped.to_string(), true)
65        } else {
66            (formula, true)
67        }
68    } else if let Some(value) = value {
69        if let Some(stripped) = value.strip_prefix('=') {
70            warnings.push(CoreWarning {
71                code: "WARN_FORMULA_PREFIX".to_string(),
72                message: format!("Stripped leading '=' for formula at {}", address),
73            });
74            (stripped.to_string(), true)
75        } else {
76            (value, is_formula.unwrap_or(false))
77        }
78    } else {
79        return Err(anyhow!("edit value or formula is required for {address}"));
80    };
81
82    Ok((
83        CellEdit {
84            address: address.to_string(),
85            value,
86            is_formula,
87        },
88        warnings,
89    ))
90}
91
92pub fn apply_edits_to_file(path: &Path, sheet_name: &str, edits: &[CellEdit]) -> Result<()> {
93    let mut book = umya_spreadsheet::reader::xlsx::read(path)
94        .with_context(|| format!("failed to open workbook '{}'", path.display()))?;
95
96    let sheet = book
97        .get_sheet_by_name_mut(sheet_name)
98        .ok_or_else(|| anyhow!("sheet '{}' not found", sheet_name))?;
99
100    for edit in edits {
101        let cell = sheet.get_cell_mut(edit.address.as_str());
102        if edit.is_formula {
103            cell.set_formula(edit.value.clone());
104            cell.get_cell_value_mut()
105                .set_formula_result_default(String::new());
106        } else {
107            cell.set_value(edit.value.clone());
108        }
109    }
110
111    umya_spreadsheet::writer::xlsx::write(&book, path)
112        .with_context(|| format!("failed to save workbook '{}'", path.display()))?;
113    Ok(())
114}