1use std::sync::Arc;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use crate::base::DateSystem;
7use crate::formula::Expr;
8use serde::{Deserialize, Serialize};
9
10use crate::model::cell::Formula;
11use crate::model::sheet::Sheet;
12use crate::model::style::{StyleTable, Theme};
13
14pub const MAX_EQUATION_LAYOUT_BYTES: usize = 128 * 1024;
17
18pub const EQUATION_BUDGET_MESSAGE: &str = "This workbook has more equations than are laid out at once; edit this equation to lay it out.";
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
22pub enum CalcMode {
23 #[default]
24 Automatic,
25 AutomaticExceptTables,
26 Manual,
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
33pub enum ShowObjects {
34 #[default]
35 All,
36 Placeholders,
37 None,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
51pub struct WorkbookView {
52 #[serde(default = "yes")]
53 pub show_sheet_tabs: bool,
54 #[serde(default = "yes")]
55 pub show_horizontal_scroll: bool,
56 #[serde(default = "yes")]
57 pub show_vertical_scroll: bool,
58 #[serde(default = "default_tab_ratio")]
59 pub tab_ratio: u32,
60}
61
62fn yes() -> bool {
63 true
64}
65
66fn default_tab_ratio() -> u32 {
67 600
68}
69
70impl Default for WorkbookView {
71 fn default() -> Self {
72 WorkbookView { show_sheet_tabs: true, show_horizontal_scroll: true, show_vertical_scroll: true, tab_ratio: default_tab_ratio() }
73 }
74}
75
76impl WorkbookView {
77 pub const MAX_TAB_RATIO: u32 = 1000;
79
80 fn is_default(&self) -> bool {
81 *self == WorkbookView::default()
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
87pub struct CalcSettings {
88 pub mode: CalcMode,
89 pub iterative: bool,
92 pub max_iterations: u32,
94 pub max_change: f64,
96 pub precision_as_displayed: bool,
98 #[serde(default)]
101 pub r1c1: bool,
102}
103
104impl Default for CalcSettings {
105 fn default() -> Self {
106 CalcSettings {
107 mode: CalcMode::Automatic,
108 iterative: false,
109 max_iterations: Self::DEFAULT_ITERATIONS,
110 max_change: Self::DEFAULT_CHANGE,
111 precision_as_displayed: false,
112 r1c1: false,
113 }
114 }
115}
116
117impl CalcSettings {
118 pub const DEFAULT_ITERATIONS: u32 = 100;
119 pub const DEFAULT_CHANGE: f64 = 0.001;
120 pub const MAX_ITERATIONS: u32 = 32_767;
122
123 pub fn set_max_iterations(&mut self, n: u32) {
125 self.max_iterations = n.clamp(1, Self::MAX_ITERATIONS);
126 }
127
128 pub fn set_max_change(&mut self, d: f64) {
130 if d.is_finite() {
131 self.max_change = d.abs();
132 }
133 }
134}
135
136#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
137pub struct DefinedName {
138 pub name: String,
139 pub scope: Option<usize>,
141 pub formula: String,
143 pub comment: String,
144 pub hidden: bool,
145}
146
147#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
148pub struct DocProps {
149 pub title: String,
150 pub subject: String,
151 pub author: String,
152 pub company: String,
153 pub keywords: String,
154 pub description: String,
155}
156
157#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
164pub struct PhoneticRun {
165 pub start: u32,
166 pub end: u32,
167 pub text: String,
168}
169
170#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
174pub struct PhoneticProps {
175 #[serde(default)]
178 pub font_id: u32,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub kind: Option<String>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub alignment: Option<String>,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
191pub struct PhoneticString {
192 pub text: String,
193 pub runs: Vec<PhoneticRun>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub props: Option<PhoneticProps>,
196}
197
198#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
199pub struct Workbook {
200 pub sheets: Vec<Arc<Sheet>>,
201 pub names: Vec<DefinedName>,
202 pub styles: StyleTable,
203 pub theme: Theme,
204 pub date_system: DateSystem,
205 pub calc: CalcSettings,
206 #[serde(default, skip_serializing_if = "is_all")]
207 pub show_objects: ShowObjects,
208 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
213 pub remove_personal_info: bool,
214 pub active_sheet: ActiveSheet,
216 #[serde(default, skip_serializing_if = "WorkbookView::is_default")]
218 pub view: WorkbookView,
219 pub props: DocProps,
220 pub protected_structure: bool,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub structure_password: Option<crate::model::PasswordHash>,
225 pub custom_lists: Vec<Vec<String>>,
227 pub cell_styles: Vec<(String, crate::model::style::Style)>,
229 #[serde(default, skip_serializing_if = "Vec::is_empty")]
239 pub phonetics: Vec<PhoneticString>,
240 #[serde(default, with = "crate::model::b64::opt")]
245 pub vba_project: Option<Arc<Vec<u8>>>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub csv_delimiter: Option<u8>,
250 #[serde(default, skip_serializing_if = "crate::model::macros::Macros::is_empty", skip_deserializing)]
253 pub macros: crate::model::macros::Macros,
254 #[serde(rename = "macros", default, skip_serializing, deserialize_with = "crate::model::macros::entries")]
258 pub macros_read: Vec<serde_json::Value>,
259 #[serde(skip)]
263 pub macros_unread: Arc<Vec<serde_json::Value>>,
264 #[serde(skip)]
266 pub preserved: Option<Arc<crate::model::preserved::Preserved>>,
267 #[serde(skip)]
270 pub source_dir: Option<std::path::PathBuf>,
271 #[serde(skip)]
277 pub source_path: Option<std::path::PathBuf>,
278}
279
280fn is_all(s: &ShowObjects) -> bool {
281 *s == ShowObjects::All
282}
283
284impl Default for Workbook {
285 fn default() -> Self {
286 Workbook::new()
287 }
288}
289
290#[derive(Default, Serialize, Deserialize)]
299#[serde(from = "usize", into = "usize")]
300pub struct ActiveSheet(AtomicUsize);
301
302impl ActiveSheet {
303 pub fn get(&self) -> usize {
304 self.0.load(Ordering::Relaxed)
305 }
306 pub fn set(&self, i: usize) {
307 self.0.store(i, Ordering::Relaxed);
308 }
309}
310
311impl From<usize> for ActiveSheet {
312 fn from(i: usize) -> ActiveSheet {
313 ActiveSheet(AtomicUsize::new(i))
314 }
315}
316
317impl From<ActiveSheet> for usize {
318 fn from(a: ActiveSheet) -> usize {
319 a.get()
320 }
321}
322
323impl Clone for ActiveSheet {
324 fn clone(&self) -> ActiveSheet {
325 ActiveSheet::from(self.get())
326 }
327}
328
329impl PartialEq for ActiveSheet {
330 fn eq(&self, other: &ActiveSheet) -> bool {
331 self.get() == other.get()
332 }
333}
334
335impl std::fmt::Debug for ActiveSheet {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 self.get().fmt(f)
338 }
339}
340
341impl Workbook {
342 pub fn new() -> Workbook {
344 Workbook {
345 sheets: vec![Arc::new(Sheet::new("Sheet1"))],
346 names: vec![],
347 styles: StyleTable::default(),
348 theme: Theme::default(),
349 date_system: DateSystem::D1900,
350 calc: CalcSettings::default(),
351 show_objects: ShowObjects::All,
352 remove_personal_info: false,
353 active_sheet: ActiveSheet::default(),
354 view: WorkbookView::default(),
355 props: DocProps::default(),
356 protected_structure: false,
357 structure_password: None,
358 custom_lists: vec![],
359 cell_styles: vec![],
360 phonetics: vec![],
361 vba_project: None,
362 csv_delimiter: None,
363 macros: crate::model::macros::Macros::default(),
364 macros_read: vec![],
365 macros_unread: Arc::default(),
366 preserved: None,
367 source_dir: None,
368 source_path: None,
369 }
370 }
371 pub fn sheet(&self, i: usize) -> Option<&Sheet> {
372 self.sheets.get(i).map(|s| s.as_ref())
373 }
374 pub fn sheet_mut(&mut self, i: usize) -> Option<&mut Sheet> {
375 self.sheets.get_mut(i).map(Arc::make_mut)
376 }
377 pub fn sheet_index(&self, name: &str) -> Option<usize> {
378 self.sheets.iter().position(|s| s.name.eq_ignore_ascii_case(name))
379 }
380 pub fn active_sheet(&self) -> usize {
382 self.active_sheet.get()
383 }
384 pub fn set_active_sheet(&self, i: usize) {
387 self.active_sheet.set(i);
388 }
389 pub fn active(&self) -> Option<&Sheet> {
390 self.sheet(self.active_sheet())
391 }
392 pub fn next_sheet_name(&self) -> String {
394 (1..).map(|n| format!("Sheet{n}")).find(|n| self.sheet_index(n).is_none()).unwrap_or_else(|| "Sheet".into())
395 }
396 pub fn check_sheet_name(&self, name: &str, except: Option<usize>) -> Result<(), String> {
399 let n = name.chars().count();
400 if n == 0 {
401 return Err("A sheet name can't be blank.".into());
402 }
403 if n > 31 {
404 return Err("A sheet name can't be longer than 31 characters.".into());
405 }
406 if name.chars().any(|c| matches!(c, ':' | '\\' | '/' | '?' | '*' | '[' | ']')) {
407 return Err("A sheet name can't contain any of these characters: : \\ / ? * [ ]".into());
408 }
409 if name.starts_with('\'') || name.ends_with('\'') {
410 return Err("A sheet name can't begin or end with an apostrophe.".into());
411 }
412 if name.eq_ignore_ascii_case("History") {
413 return Err("\"History\" is a reserved name.".into());
414 }
415 if let Some(i) = self.sheet_index(name)
416 && Some(i) != except
417 {
418 return Err("That name is already taken. Try a different one.".into());
419 }
420 Ok(())
421 }
422 pub fn name(&self, name: &str, sheet: usize) -> Option<&DefinedName> {
424 self.names
425 .iter()
426 .find(|n| n.scope == Some(sheet) && n.name.eq_ignore_ascii_case(name))
427 .or_else(|| self.names.iter().find(|n| n.scope.is_none() && n.name.eq_ignore_ascii_case(name)))
428 }
429 pub fn name_call_case(&self, e: Expr) -> Expr {
432 spell_calls(e, &self.lambda_names())
433 }
434 pub fn apply_name_call_case(&mut self) {
437 let lambdas = self.lambda_names();
438 if lambdas.is_empty() {
439 return;
440 }
441 for sheet in self.sheets.iter_mut() {
442 let fixed: Vec<_> = sheet
443 .cells
444 .iter()
445 .filter_map(|(c, cell)| {
446 let f = cell.formula.as_ref()?;
447 let e = f.expr()?;
448 let ne = spell_calls(e.clone(), &lambdas);
449 (ne != e).then(|| (c, Formula { array: f.array, dynamic: f.dynamic, ..Formula::from_expr(ne) }))
450 })
451 .collect();
452 if fixed.is_empty() {
453 continue;
454 }
455 let sheet = Arc::make_mut(sheet);
456 for (c, f) in fixed {
457 if let Some(cell) = sheet.cells.get_mut(c) {
458 cell.formula = Some(Arc::new(f));
459 }
460 }
461 }
462 for n in self.names.iter_mut() {
463 if let Ok(e) = crate::formula::parse_name(&n.formula) {
464 let ne = spell_calls(e.clone(), &lambdas);
465 if ne != e {
466 n.formula = crate::formula::print(&ne);
467 }
468 }
469 }
470 }
471 fn lambda_names(&self) -> Vec<String> {
473 let is_lambda = |f: &str| f.trim_start_matches('=').trim_start().get(..7).is_some_and(|p| p.eq_ignore_ascii_case("LAMBDA("));
474 self.names.iter().filter(|n| is_lambda(&n.formula)).map(|n| n.name.clone()).collect()
475 }
476 pub fn table(&self, name: &str) -> Option<(usize, usize)> {
478 for (si, s) in self.sheets.iter().enumerate() {
479 if let Some(ti) = s.tables.iter().position(|t| t.name.eq_ignore_ascii_case(name)) {
480 return Some((si, ti));
481 }
482 }
483 None
484 }
485 pub fn refresh_equations(&mut self) {
491 let stale = |s: &crate::model::Shape| s.kind == crate::model::ShapeKind::Equation && s.math.get(&s.text).is_none();
492 let mut budget = MAX_EQUATION_LAYOUT_BYTES;
493 for sh in self.sheets.iter_mut().filter(|sh| sh.shapes.iter().any(stale)) {
495 for s in Arc::make_mut(sh).shapes.iter_mut().filter(|s| stale(s)) {
496 let cost = s.text.len().max(1);
499 if cost > budget {
500 s.math.refuse(&s.text, EQUATION_BUDGET_MESSAGE);
501 continue;
502 }
503 budget -= cost;
504 s.math.refresh(&s.text);
505 }
506 }
507 }
508
509 pub fn next_object_id(&self) -> u32 {
512 self.free_object_ids(1).first().copied().unwrap_or(u32::MAX)
513 }
514
515 pub fn free_object_ids(&self, count: usize) -> Vec<u32> {
519 let mut used: Vec<u32> = self
520 .sheets
521 .iter()
522 .flat_map(|s| {
523 s.charts
524 .iter()
525 .map(|o| o.id)
526 .chain(s.images.iter().map(|o| o.id))
527 .chain(s.shapes.iter().map(|o| o.id))
528 .chain(s.tables.iter().map(|o| o.id))
529 .chain(s.slicers.iter().map(|o| o.id))
530 .chain(s.controls.iter().map(|o| o.id))
531 })
532 .collect();
533 used.sort_unstable();
534 used.dedup();
535 let max = used.last().copied().unwrap_or(0);
536 let mut out = Vec::with_capacity(count.min(1 << 16));
537 let mut next = max.checked_add(1);
538 while out.len() < count
539 && let Some(id) = next
540 {
541 out.push(id);
542 next = id.checked_add(1);
543 }
544 let mut taken = used.iter().copied().peekable();
546 let mut id = 1u32;
547 while out.len() < count && id < max {
548 while taken.next_if(|&t| t < id).is_some() {}
549 if taken.peek() == Some(&id) {
550 taken.next();
551 } else {
552 out.push(id);
553 }
554 id += 1;
555 }
556 out.sort_unstable();
557 out
558 }
559}
560
561fn spell_calls(e: Expr, lambdas: &[String]) -> Expr {
563 if lambdas.is_empty() {
564 return e;
565 }
566 e.map(&mut |x| match x {
567 Expr::Call(n, args) => match lambdas.iter().find(|l| l.eq_ignore_ascii_case(&n)) {
568 Some(l) => Expr::Call(l.clone(), args),
569 None => Expr::Call(n, args),
570 },
571 other => other,
572 })
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578
579 #[test]
580 fn phonetics_are_additive_in_json() -> Result<(), Box<dyn std::error::Error>> {
581 let mut wb = Workbook::new();
582 assert!(wb.phonetics.is_empty(), "new workbooks have no phonetic data");
583 wb.phonetics.push(PhoneticString {
584 text: "東".into(),
585 runs: vec![PhoneticRun { start: 0, end: 1, text: "ヒガシ".into() }],
586 props: Some(PhoneticProps { font_id: 3, kind: Some("Hiragana".into()), alignment: None }),
587 });
588 let json = serde_json::to_string(&wb)?;
589 assert_eq!(serde_json::from_str::<Workbook>(&json)?.phonetics, wb.phonetics);
590 let mut value: serde_json::Value = serde_json::from_str(&json)?;
592 value.as_object_mut().ok_or("none")?.remove("phonetics");
593 assert!(serde_json::from_value::<Workbook>(value)?.phonetics.is_empty());
594 Ok(())
595 }
596
597 #[test]
598 fn sheet_names() {
599 let wb = Workbook::new();
600 assert_eq!(wb.next_sheet_name(), "Sheet2");
601 assert!(wb.check_sheet_name("sheet1", None).is_err());
602 assert!(wb.check_sheet_name("sheet1", Some(0)).is_ok());
603 assert!(wb.check_sheet_name("a/b", None).is_err());
604 assert!(wb.check_sheet_name(&"x".repeat(32), None).is_err());
605 assert!(wb.check_sheet_name("Budget 2026", None).is_ok());
606 }
607
608 #[test]
609 fn object_ids_never_overflow_or_repeat() {
610 let mut wb = Workbook::new();
611 let image = |id| crate::model::Image { id, anchor: Default::default(), data: Default::default(), mime: String::new(), alt: String::new() };
612 assert_eq!(wb.next_object_id(), 1);
613 if let Some(sh) = wb.sheet_mut(0) {
614 sh.images.push(image(7));
615 }
616 assert_eq!(wb.next_object_id(), 8);
617 if let Some(sh) = wb.sheet_mut(0) {
618 sh.controls.push(crate::model::FormControl { id: 12, ..Default::default() });
619 }
620 assert_eq!(wb.next_object_id(), 13, "form controls take ids from the same sequence");
621 if let Some(sh) = wb.sheet_mut(0) {
622 sh.images.push(image(u32::MAX));
623 }
624 assert_eq!(wb.next_object_id(), 1);
626 if let Some(sh) = wb.sheet_mut(0) {
627 sh.images.push(image(1));
628 sh.images.push(image(3));
629 }
630 assert_eq!(wb.next_object_id(), 2);
631 assert_eq!(wb.free_object_ids(4), vec![2, 4, 5, 6], "the gaps, skipping 1, 3 and 7");
632 let mut wb = Workbook::new();
633 if let Some(sh) = wb.sheet_mut(0) {
634 sh.images.push(image(u32::MAX - 1));
635 }
636 assert_eq!(wb.free_object_ids(3), vec![1, 2, u32::MAX], "the top first, then from 1");
637 }
638
639 #[test]
642 fn free_object_ids_skip_a_form_controls_id() {
643 let mut wb = Workbook::new();
644 if let Some(sh) = wb.sheet_mut(0) {
645 sh.controls.push(crate::model::FormControl { id: 3, ..Default::default() });
646 sh.controls.push(crate::model::FormControl { id: u32::MAX, ..Default::default() });
647 }
648 assert_eq!(wb.free_object_ids(4), vec![1, 2, 4, 5], "3 and the largest id are taken");
649 assert_eq!(wb.next_object_id(), 1);
650 }
651
652 #[test]
656 fn free_object_ids_come_from_below_when_nothing_is_free_above() {
657 let mut wb = Workbook::new();
658 let image = |id| crate::model::Image { id, anchor: Default::default(), data: Default::default(), mime: String::new(), alt: String::new() };
659 if let Some(sh) = wb.sheet_mut(0) {
660 sh.images.push(image(u32::MAX));
661 sh.images.push(image(u32::MAX - 1));
662 sh.images.push(image(0));
663 }
664 assert_eq!(wb.free_object_ids(3), vec![1, 2, 3]);
665 assert_eq!(wb.next_object_id(), 1);
666 let many = wb.free_object_ids(100_000);
668 assert_eq!(many.len(), 100_000);
669 assert!(many.windows(2).all(|w| w[0] < w[1]), "ascending without repeats");
670 assert!(!many.contains(&0) && !many.contains(&(u32::MAX - 1)) && !many.contains(&u32::MAX));
671 assert_eq!(wb.free_object_ids(0), Vec::<u32>::new());
672 }
673}