1#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
4#![forbid(unsafe_code)]
5
6mod builtin;
7mod decimal;
8mod parse;
9mod render;
10
11use std::sync::Arc;
12
13use crate::base::{CellError, DateSystem, Value};
14
15pub use builtin::{builtin_format, builtin_id};
16pub use decimal::format_general_fit;
17
18use parse::{DatePart, SecKind, Section, Tok};
19use render::Out;
20
21const GENERAL_WIDTH: usize = 11;
23const OVERFLOW: &str = "########";
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum FormatColor {
29 Black,
30 Blue,
31 Cyan,
32 Green,
33 Magenta,
34 Red,
35 White,
36 Yellow,
37 Indexed(u8),
39}
40
41impl FormatColor {
42 pub fn rgb(self) -> Option<[u8; 3]> {
44 use FormatColor as F;
45 Some(match self {
46 F::Black => [0, 0, 0],
47 F::Blue => [0, 0, 255],
48 F::Cyan => [0, 255, 255],
49 F::Green => [0, 255, 0],
50 F::Magenta => [255, 0, 255],
51 F::Red => [255, 0, 0],
52 F::White => [255, 255, 255],
53 F::Yellow => [255, 255, 0],
54 F::Indexed(index) => return crate::base::color::indexed_color(index),
55 })
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
61pub enum FormatKind {
62 General,
63 Number,
64 Currency,
65 Accounting,
66 Date,
67 Time,
68 Percentage,
69 Fraction,
70 Scientific,
71 Text,
72 Special,
74 Custom,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq, Default)]
79pub struct Formatted {
80 pub text: String,
81 pub color: Option<FormatColor>,
82 pub fill: Option<(char, usize)>,
85 pub numeric: bool,
88}
89
90#[derive(Debug)]
91struct Inner {
92 code: String,
93 general: bool,
94 sections: Vec<Section>,
95 num_sections: usize,
97 text_section: Option<usize>,
99}
100
101#[derive(Clone, Debug)]
103pub struct NumberFormat {
104 inner: Arc<Inner>,
105}
106
107impl PartialEq for NumberFormat {
108 fn eq(&self, other: &Self) -> bool {
109 self.inner.code == other.inner.code
110 }
111}
112
113impl Default for NumberFormat {
114 fn default() -> Self {
115 NumberFormat::general()
116 }
117}
118
119pub const MAX_FORMAT_CODE_CHARS: usize = 255;
124
125fn too_long(code: &str) -> bool {
127 code.len() > MAX_FORMAT_CODE_CHARS && code.chars().count() > MAX_FORMAT_CODE_CHARS
129}
130
131impl NumberFormat {
132 pub fn parse(code: &str) -> NumberFormat {
135 Self::parse_inner(code, false)
136 }
137
138 #[cfg(test)]
140 fn parse_uncapped(code: &str) -> NumberFormat {
141 Self::parse_inner(code, true)
142 }
143
144 fn parse_inner(code: &str, uncapped: bool) -> NumberFormat {
145 #[cfg(test)]
146 parse_cache::count_parse();
147 let trimmed = code.trim();
148 if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("general") || trimmed == "checkbox" || (!uncapped && too_long(code)) {
151 return NumberFormat {
152 inner: Arc::new(Inner { code: code.to_string(), general: true, sections: Vec::new(), num_sections: 0, text_section: None }),
153 };
154 }
155 let sections = parse::parse_sections(code);
156 let len = sections.len();
157 let last_is_text = sections.last().is_some_and(|s| s.kind == SecKind::Text);
158 let text_section = if len >= 4 {
159 Some(3)
160 } else if last_is_text {
161 Some(len - 1)
162 } else {
163 None
164 };
165 let num_sections = if len >= 4 {
166 3
167 } else if last_is_text {
168 len - 1
169 } else {
170 len
171 };
172 NumberFormat { inner: Arc::new(Inner { code: code.to_string(), general: false, sections, num_sections, text_section }) }
173 }
174
175 pub fn general() -> NumberFormat {
176 NumberFormat::parse("General")
177 }
178
179 pub fn code(&self) -> &str {
180 &self.inner.code
181 }
182
183 pub fn is_general(&self) -> bool {
184 self.inner.general
185 }
186
187 fn num_sections(&self) -> &[Section] {
188 self.inner.sections.get(..self.inner.num_sections).unwrap_or(&[])
189 }
190
191 pub fn is_date(&self) -> bool {
193 self.num_sections().iter().any(|s| s.kind == SecKind::Date)
194 }
195
196 pub fn date_shape(&self, value: f64) -> Option<(bool, bool)> {
200 let s = self.select(value).map(|(s, _)| s).filter(|s| s.kind == SecKind::Date)?;
201 let parts = || s.toks.iter().filter_map(|t| if let Tok::Date(d) = t { Some(*d) } else { None });
202 if parts().any(|d| matches!(d, DatePart::ElapsedH(_) | DatePart::ElapsedM(_) | DatePart::ElapsedS(_))) {
203 return None;
204 }
205 Some((parts().any(DatePart::is_calendar), parts().any(|d| !d.is_calendar())))
206 }
207
208 pub fn is_percent(&self) -> bool {
209 self.num_sections().iter().any(|s| s.kind == SecKind::Number && s.percent > 0)
210 }
211
212 pub fn is_text(&self) -> bool {
214 self.inner.num_sections == 0 && self.inner.text_section.is_some()
215 }
216
217 pub fn kind(&self) -> FormatKind {
219 if self.is_general() {
220 return FormatKind::General;
221 }
222 if self.is_text() {
223 return FormatKind::Text;
224 }
225 const SPECIAL: [&str; 4] = ["00000", "00000-0000", "[<=9999999]###-####;(###) ###-####", "000-00-0000"];
226 if SPECIAL.contains(&self.code()) {
227 return FormatKind::Special;
228 }
229 let Some(s) = self.inner.sections.first() else { return FormatKind::Custom };
230 match s.kind {
231 SecKind::General => return FormatKind::General,
232 SecKind::Text => return FormatKind::Text,
233 SecKind::Date => {
234 let calendar = s.toks.iter().any(|t| matches!(t, Tok::Date(d) if d.is_calendar()));
235 return if calendar { FormatKind::Date } else { FormatKind::Time };
236 }
237 SecKind::Number => {}
238 }
239 if s.fraction {
240 FormatKind::Fraction
241 } else if s.exp {
242 FormatKind::Scientific
243 } else if s.percent > 0 {
244 FormatKind::Percentage
245 } else if s.fill && s.has_digits {
246 FormatKind::Accounting
247 } else if s.currency && s.has_digits {
248 FormatKind::Currency
249 } else if s.has_digits {
250 FormatKind::Number
251 } else {
252 FormatKind::Custom
253 }
254 }
255
256 fn select(&self, v: f64) -> Option<(&Section, bool)> {
259 let secs = self.num_sections();
260 let k = secs.len();
261 let s0 = secs.first()?;
262 let c0 = s0.cond;
263 let c1 = secs.get(1).and_then(|s| s.cond);
264 if c0.is_some() || c1.is_some() {
265 let idx = if c0.is_some_and(|c| c.test(v)) {
266 0
267 } else if c1.is_some_and(|c| c.test(v)) {
268 1
269 } else if c0.is_some() && c1.is_some() {
270 2
271 } else if c0.is_none() {
272 0
273 } else {
274 1
275 };
276 return secs.get(idx).map(|s| (s, k >= 2));
278 }
279 if v < 0.0 && k >= 2 {
280 return secs.get(1).map(|s| (s, true));
281 }
282 if v == 0.0 && k >= 3 {
283 return secs.get(2).map(|s| (s, false));
284 }
285 Some((s0, false))
286 }
287
288 fn format_number(&self, n: f64, sys: DateSystem) -> (Formatted, bool) {
290 let general = |n: f64| Formatted {
291 text: format_general_fit(n, GENERAL_WIDTH).unwrap_or_else(|| OVERFLOW.into()),
292 color: None,
293 fill: None,
294 numeric: true,
295 };
296 if self.is_general() {
297 return (general(n), false);
298 }
299 let Some((sec, drop_sign)) = self.select(n) else { return (general(n), false) };
300 let minus = n < 0.0 && !drop_sign;
301 let out: Option<Out> = match sec.kind {
302 SecKind::Text => Some(render::render_text(sec, &format_general_fit(n, GENERAL_WIDTH).unwrap_or_default())),
303 SecKind::General => {
304 let width = if minus { GENERAL_WIDTH - 1 } else { GENERAL_WIDTH };
305 Some(render::render_general(sec, n.abs(), width))
306 }
307 SecKind::Date => {
308 if minus {
309 None
310 } else {
311 render::render_date(sec, n.abs(), sys)
312 }
313 }
314 SecKind::Number => Some(render::render_number(sec, n.abs())),
315 };
316 let Some(mut out) = out else {
317 return (Formatted { text: OVERFLOW.into(), color: sec.color, fill: None, numeric: true }, true);
318 };
319 if minus && sec.kind != SecKind::Text && !out.zero {
321 out.prepend("-");
322 }
323 (Formatted { text: out.text, color: sec.color, fill: out.fill, numeric: true }, false)
324 }
325
326 pub fn round_as_displayed(&self, n: f64) -> f64 {
335 if self.is_general() || !n.is_finite() {
336 return n;
337 }
338 let Some(sec) = self.rounding_section(n) else { return n };
339 if sec.kind != SecKind::Number {
340 return n;
341 }
342 let Some(m) = render::shown_value(sec, n) else { return n };
343 if m == 0.0 {
344 0.0
345 } else if n < 0.0 {
346 -m
347 } else {
348 m
349 }
350 }
351
352 fn rounding_section(&self, n: f64) -> Option<&Section> {
367 let secs = self.num_sections();
368 let conditional = secs.iter().any(|s| s.cond.is_some());
369 let slot = if n < 0.0 && secs.len() >= 2 {
370 1
371 } else if n < 0.0 && conditional && self.inner.text_section.is_none() {
372 return None;
373 } else {
374 0
375 };
376 let empty = |s: &Section| s.toks.is_empty() && s.color.is_none() && s.cond.is_none();
377 secs.get(slot..)?.iter().find(|s| !empty(s))
378 }
379
380 fn format_text(&self, t: &str) -> Formatted {
381 match self.inner.text_section.and_then(|i| self.inner.sections.get(i)) {
382 Some(sec) => {
383 let out = render::render_text(sec, t);
384 Formatted { text: out.text, color: sec.color, fill: out.fill, numeric: false }
385 }
386 None => Formatted { text: t.to_string(), color: None, fill: None, numeric: false },
387 }
388 }
389}
390
391pub fn format_value(v: &Value, fmt: &NumberFormat, sys: DateSystem) -> Formatted {
393 match v {
394 Value::Empty => Formatted::default(),
395 Value::Number(n) if !n.is_finite() => Formatted { text: CellError::Num.as_str().into(), ..Formatted::default() },
396 Value::Number(n) => fmt.format_number(*n, sys).0,
397 Value::Text(t) => fmt.format_text(t),
398 Value::Bool(b) => Formatted { text: if *b { "TRUE" } else { "FALSE" }.into(), ..Formatted::default() },
399 Value::Error(e) => Formatted { text: e.as_str().into(), ..Formatted::default() },
400 Value::Array(a) => match a.data.first() {
401 Some(Value::Array(_)) | None => Formatted::default(),
402 Some(first) => format_value(first, fmt, sys),
403 },
404 }
405}
406
407mod parse_cache {
411 use std::cell::RefCell;
412 use std::collections::HashMap;
413
414 use super::NumberFormat;
415
416 pub(super) const CAPACITY: usize = 64;
418 pub(super) const MAX_CODE_LEN: usize = 256;
420
421 thread_local! {
422 static CACHE: RefCell<HashMap<String, NumberFormat>> = RefCell::new(HashMap::new());
423 }
424
425 pub(super) fn get(code: &str) -> NumberFormat {
426 if code.len() > MAX_CODE_LEN {
427 return NumberFormat::parse(code);
428 }
429 let cached = CACHE
431 .try_with(|c| {
432 let mut c = c.try_borrow_mut().ok()?;
433 if let Some(f) = c.get(code) {
434 return Some(f.clone());
435 }
436 let f = NumberFormat::parse(code);
437 if c.len() >= CAPACITY {
438 c.clear();
439 }
440 c.insert(code.to_string(), f.clone());
441 Some(f)
442 })
443 .ok()
444 .flatten();
445 cached.unwrap_or_else(|| NumberFormat::parse(code))
446 }
447
448 #[cfg(test)]
449 thread_local! {
450 static PARSES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
451 }
452
453 #[cfg(test)]
454 pub(super) fn count_parse() {
455 let _ = PARSES.try_with(|p| p.set(p.get() + 1));
456 }
457
458 #[cfg(test)]
460 pub(super) fn parses() -> usize {
461 PARSES.try_with(std::cell::Cell::get).unwrap_or(0)
462 }
463
464 #[cfg(test)]
466 pub(super) fn clear() {
467 let _ = CACHE.try_with(|c| c.borrow_mut().clear());
468 }
469}
470
471impl NumberFormat {
472 pub fn parse_cached(code: &str) -> NumberFormat {
475 parse_cache::get(code)
476 }
477}
478
479pub fn text_function(v: &Value, code: &str, sys: DateSystem) -> Result<String, CellError> {
483 if code.is_empty() {
484 return Ok(String::new());
485 }
486 if too_long(code) {
487 return Err(CellError::Value);
488 }
489 let fmt = NumberFormat::parse_cached(code);
490 let n = match v.scalar() {
491 Value::Error(e) => return Err(e),
492 Value::Empty => 0.0,
493 Value::Number(n) => n,
494 Value::Bool(b) => return Ok(fmt.format_text(if b { "TRUE" } else { "FALSE" }).text),
495 Value::Text(t) => match crate::base::parse::parse_number_text(&t) {
496 Some(n) => n,
497 None => return Ok(fmt.format_text(&t).text),
498 },
499 Value::Array(_) => return Err(CellError::Value),
500 };
501 if !n.is_finite() {
502 return Err(CellError::Num);
503 }
504 let (f, overflow) = fmt.format_number(n, sys);
505 if overflow { Err(CellError::Value) } else { Ok(f.text) }
506}
507
508#[cfg(test)]
509mod tests;