use super::{CellRef, EngineError, Json, RangeRef, Result, Session, TableColumn, TotalsFn, Value, bad, edit, json, ok, str_param, target_range}; pub(super) fn table_resize(s: &mut Session, p: &Json) -> Result { let r = str_param(p, "range").and_then(RangeRef::parse).ok_or_else(|| bad("table.resize", "missing `range`"))?; let d = s.doc()?; let si = d.wb.active_sheet(); let sh = d.wb.sheet(si).ok_or(EngineError::NoDocument)?; let ti = match str_param(p, "table") { Some(n) => sh.tables.iter().position(|t| t.name.eq_ignore_ascii_case(n)), None => sh.tables.iter().position(|t| t.range.contains(d.selection.active)), } .ok_or_else(|| bad("table.resize", "select a cell in a table"))?; edit(s, |cx| { let sheet = cx.sheet_mut(si)?; let headers: Vec = (r.start.col..=r.end.col).map(|c| sheet.value(CellRef::new(r.start.row, c)).display()).collect(); if sheet.tables.iter().enumerate().any(|(i, o)| i != ti && o.range.intersects(&r)) { return Err(EngineError::Other("A table can't overlap another table.".into())); } let t = sheet.tables.get_mut(ti).ok_or_else(|| bad("table", "gone"))?; if r.start.row != t.range.start.row || !r.intersects(&t.range) { return Err(EngineError::Other( "The headers must remain in the same row, and the resulting table range must overlap the original table range.".into(), )); } let mut cols = Vec::new(); for (i, h) in headers.iter().enumerate() { let name = if h.is_empty() { format!("Column{}", i + 1) } else { h.clone() }; let existing = t.columns.iter().find(|c| c.name.eq_ignore_ascii_case(&name)).cloned(); cols.push(existing.unwrap_or(TableColumn { name, totals: TotalsFn::None, totals_label: None, formula: None, unique_name: None, xml: None, })); } t.columns = cols; t.range = r; cx.structural = true; Ok(Json::Null) }) } pub(super) fn from_csv(s: &mut Session, p: &Json) -> Result { let bytes = if let Some(t) = str_param(p, "text") { t.as_bytes().to_vec() } else if let Some(path) = str_param(p, "path") { crate::io::read_file(path)? } else { s.ui_requests.push(crate::UiRequest::Dialog("open".into(), json!({}))); return ok(); }; let text = String::from_utf8_lossy(&bytes); // JSON array of objects → rows. let rows: Vec> = if text.trim_start().starts_with('[') { let arr: Vec = serde_json::from_str(&text).map_err(|e| bad("data.getData", e.to_string()))?; let mut keys: Vec = Vec::new(); for o in &arr { if let Some(m) = o.as_object() { for k in m.keys() { if !keys.contains(k) { keys.push(k.clone()); } } } } let mut out = vec![keys.iter().map(|k| json!(k)).collect::>()]; for o in &arr { out.push(keys.iter().map(|k| o.get(k).cloned().unwrap_or(Json::Null)).collect()); } out } else { let delim = str_param(p, "delimiter") .and_then(|d| d.chars().next()) .unwrap_or(if text.lines().next().is_some_and(|l| l.contains('\t')) { '\t' } else { ',' }); let opts = gridcraft_xlsx::CsvOptions { delimiter: delim as u8, ..Default::default() }; let wb = gridcraft_xlsx::read_csv(&bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?; let sh = wb.sheet(0).ok_or(EngineError::NoDocument)?; let Some(u) = sh.used_range() else { return Err(EngineError::Other("The file is empty.".into())) }; (u.start.row..=u.end.row) .map(|r| { (u.start.col..=u.end.col) .map(|c| match sh.value(CellRef::new(r, c)) { Value::Number(n) => json!(n), Value::Bool(b) => json!(b), Value::Empty => Json::Null, v => json!(v.display()), }) .collect() }) .collect() }; if rows.is_empty() || rows.len() > 1_048_576 { return Err(EngineError::Other("Nothing to import.".into())); } let name = str_param(p, "sheet").map(str::to_string).or_else(|| { str_param(p, "path").and_then(|x| std::path::Path::new(x).file_stem().and_then(std::ffi::OsStr::to_str).map(|f| f.chars().take(31).collect())) }); let r = s.execute("home.insertSheet", json!({"name": name})).or_else(|_| s.execute("home.insertSheet", json!({})))?; let w = rows.iter().map(Vec::len).max().unwrap_or(1).max(1); s.execute("range.setValues", json!({"range": "A1", "values": rows}))?; let range = RangeRef::new(CellRef::new(0, 0), CellRef::new(rows.len() as u32 - 1, w as u32 - 1)); let _ = s.execute("insert.table", json!({"range": range.a1(), "header": true})); let _ = s.execute("home.autofitColumnWidth", json!({"cols": RangeRef::cols(0, w as u32 - 1).a1()})); Ok(json!({"sheet": r["name"], "rows": rows.len(), "cols": w})) } pub(super) fn from_table_range(s: &mut Session, p: &Json) -> Result { let r = target_range(s, p)?; let d = s.doc()?; let sh = d.wb.active().ok_or(EngineError::NoDocument)?; let r = if r.is_single() { crate::selection::current_region(sh, r.start) } else { r }; let text = crate::display::range_text(&d.wb, d.wb.active_sheet(), r); from_csv(s, &json!({"text": text, "delimiter": "\t", "sheet": format!("{} (2)", sh.name).chars().take(31).collect::()})) } pub(super) fn queries(s: &mut Session, _: &Json) -> Result { let d = s.doc()?; Ok(Json::Array( d.wb.sheets .iter() .flat_map(|sh| { sh.tables .iter() .map(move |t| json!({"name": t.name, "sheet": sh.name, "range": t.range.a1(), "rows": t.range.height().saturating_sub(1)})) }) .collect(), )) }