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