1use std::collections::HashMap;
6use std::sync::LazyLock;
7
8use quick_xml::de::from_reader as xml_from_reader;
9use quick_xml::se::to_string as xml_to_string;
10use serde::{Deserialize, Serialize};
11
12use crate::constants::{
13 EXT_URI_CONDITIONAL_FORMATTING_RULE_ID, EXT_URI_CONDITIONAL_FORMATTINGS, MAX_CELL_STYLES,
14 MAX_COLUMNS, MAX_FONT_FAMILY_LENGTH, MAX_FONT_SIZE, MIN_FONT_SIZE,
15 NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN, NAMESPACE_SPREADSHEET_X14, TOTAL_ROWS,
16 WORKSHEET_EXT_URI_PRIORITY,
17};
18use crate::data_validation::delete_cells_from_sqref;
19use crate::errors::{
20 ErrCellStyles, ErrCustomNumFmt, ErrFillGradientColor, ErrFillGradientShading, ErrFillPattern,
21 ErrFillPatternColor, ErrFillType, ErrFontLength, ErrFontSize, ErrParameterInvalid,
22 ErrParameterRequired, Result,
23};
24use crate::file::File;
25use crate::hsl::theme_color;
26use crate::lib_util::{
27 cell_name_to_coordinates, column_name_to_number, coordinates_to_cell_name, count_utf16_string,
28 flat_sqref, in_str_slice,
29};
30use crate::templates::{
31 INDEXED_COLOR_MAPPING, SUPPORTED_UNDERLINE_TYPES, SUPPORTED_VERT_ALIGN_TYPES,
32};
33use crate::xml::common::{
34 AttrValBool, AttrValFloat, AttrValInt, AttrValString, XlsxColor, XlsxExt, XlsxExtLst,
35};
36use crate::xml::styles::{
37 XlsxAlignment, XlsxBorder, XlsxBorders, XlsxCellStyles, XlsxCellXfs, XlsxDxf, XlsxDxfs,
38 XlsxFill, XlsxFills, XlsxFont, XlsxFonts, XlsxGradientFill, XlsxGradientFillStop, XlsxLine,
39 XlsxNumFmt, XlsxNumFmts, XlsxPatternFill, XlsxProtection, XlsxStyleSheet, XlsxXf,
40};
41use crate::xml::worksheet::{
42 ConditionalFormatOptions, DecodeX14CfRule, DecodeX14Cfvo, DecodeX14ConditionalFormattingRules,
43 DecodeX14DataBar, DecodeX14IconSet, Xlsx14Cfvo, Xlsx14DataBar, Xlsx14IconSet, XlsxCfRule,
44 XlsxCfvo, XlsxColorScale, XlsxConditionalFormatting, XlsxDataBar, XlsxIconSet, XlsxWorksheet,
45 XlsxX14CfRule, XlsxX14ConditionalFormatting,
46};
47
48#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct Alignment {
55 #[serde(default)]
56 pub horizontal: String,
57 #[serde(default)]
58 pub indent: i64,
59 #[serde(default)]
60 pub justify_last_line: bool,
61 #[serde(default)]
62 pub reading_order: u64,
63 #[serde(default)]
64 pub relative_indent: i64,
65 #[serde(default)]
66 pub shrink_to_fit: bool,
67 #[serde(default)]
68 pub text_rotation: i64,
69 #[serde(default)]
70 pub vertical: String,
71 #[serde(default)]
72 pub wrap_text: bool,
73}
74
75#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
77pub struct Font {
78 #[serde(rename = "sz", default, skip_serializing_if = "Option::is_none")]
79 pub size: Option<f64>,
80 #[serde(rename = "name", default, skip_serializing_if = "Option::is_none")]
82 pub name: Option<String>,
83 #[serde(rename = "family", default, skip_serializing_if = "Option::is_none")]
85 pub family: Option<i64>,
86 #[serde(rename = "b", default, skip_serializing_if = "Option::is_none")]
87 pub bold: Option<bool>,
88 #[serde(rename = "i", default, skip_serializing_if = "Option::is_none")]
89 pub italic: Option<bool>,
90 #[serde(rename = "strike", default, skip_serializing_if = "Option::is_none")]
91 pub strike: Option<bool>,
92 #[serde(rename = "u", default, skip_serializing_if = "Option::is_none")]
93 pub underline: Option<String>,
94 #[serde(rename = "color", default, skip_serializing_if = "Option::is_none")]
96 pub color: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub color_indexed: Option<i64>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub color_theme: Option<i64>,
101 #[serde(default)]
102 pub color_tint: f64,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub vert_align: Option<String>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub charset: Option<i64>,
107}
108
109#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub struct Border {
112 #[serde(default)]
113 pub r#type: String,
114 #[serde(default)]
115 pub color: String,
116 #[serde(default)]
117 pub style: i64,
118}
119
120#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct Fill {
124 #[serde(default)]
125 pub r#type: String,
126 #[serde(default)]
127 pub pattern: i64,
128 #[serde(default)]
129 pub color: Vec<String>,
130 #[serde(default)]
131 pub shading: i64,
132 #[serde(default)]
133 pub transparency: i64,
134}
135
136#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct Protection {
139 #[serde(default)]
140 pub hidden: bool,
141 #[serde(default)]
142 pub locked: bool,
143}
144
145#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
147pub struct Style {
148 #[serde(default)]
149 pub border: Vec<Border>,
150 #[serde(default)]
151 pub fill: Fill,
152 #[serde(default)]
153 pub font: Option<Font>,
154 #[serde(default)]
155 pub alignment: Option<Alignment>,
156 #[serde(default)]
157 pub protection: Option<Protection>,
158 #[serde(default)]
159 pub num_fmt: i64,
160 #[serde(default)]
161 pub decimal_places: Option<i64>,
162 #[serde(default)]
163 pub custom_num_fmt: Option<String>,
164 #[serde(default)]
165 pub neg_red: bool,
166}
167
168impl File {
173 pub fn new_style(&self, style: &Style) -> Result<i32> {
177 let mut ss = self.styles.lock().unwrap();
178 let mut style_sheet = ss.take().unwrap_or_default();
179
180 let mut fs = parse_format_style_set(style)?;
181
182 if let Some(dp) = fs.decimal_places {
183 if dp < 0 || dp > 30 {
184 fs.decimal_places = Some(2);
186 }
187 }
188
189 let existing = get_style_id(&style_sheet, &fs);
190 if existing != -1 {
191 *ss = Some(style_sheet);
192 return Ok(existing);
193 }
194
195 let num_fmt_id = new_num_fmt(&mut style_sheet, &fs);
196
197 let font_id = if let Some(ref font) = fs.font {
198 let fid = get_font_id(&style_sheet, font);
199 if fid == -1 {
200 ensure_fonts(&mut style_sheet);
201 style_sheet
202 .fonts
203 .as_mut()
204 .unwrap()
205 .font
206 .push(new_font(font));
207 let id = style_sheet.fonts.as_ref().unwrap().count;
208 style_sheet.fonts.as_mut().unwrap().count = id + 1;
209 id as i32
210 } else {
211 fid
212 }
213 } else {
214 0
215 };
216
217 let border_id = {
218 let bid = get_border_id(&style_sheet, &fs);
219 if bid == -1 {
220 if fs.border.is_empty() {
221 0
222 } else {
223 ensure_borders(&mut style_sheet);
224 style_sheet
225 .borders
226 .as_mut()
227 .unwrap()
228 .border
229 .push(new_borders(&fs));
230 let id = style_sheet.borders.as_ref().unwrap().count;
231 style_sheet.borders.as_mut().unwrap().count = id + 1;
232 id as i32
233 }
234 } else {
235 bid
236 }
237 };
238
239 let fill_id = {
240 let fid = get_fill_id(&style_sheet, &fs);
241 if fid == -1 {
242 if let Some(xfill) = new_fills(&fs, true) {
243 ensure_fills(&mut style_sheet);
244 style_sheet.fills.as_mut().unwrap().fill.push(xfill);
245 let id = style_sheet.fills.as_ref().unwrap().count;
246 style_sheet.fills.as_mut().unwrap().count = id + 1;
247 id as i32
248 } else {
249 0
250 }
251 } else {
252 fid
253 }
254 };
255
256 let apply_alignment = fs.alignment.is_some();
257 let alignment = new_alignment(&fs);
258 let apply_protection = fs.protection.is_some();
259 let protection = new_protection(&fs);
260
261 let id = set_cell_xfs(
262 &mut style_sheet,
263 font_id,
264 num_fmt_id,
265 fill_id,
266 border_id,
267 apply_alignment,
268 apply_protection,
269 alignment,
270 protection,
271 )?;
272
273 *ss = Some(style_sheet);
274 Ok(id)
275 }
276
277 pub fn get_style(&self, style_id: i32) -> Result<Style> {
281 let style_sheet = {
286 let ss = self.styles.lock().unwrap();
287 let style_sheet = ss
288 .as_ref()
289 .ok_or_else(|| crate::errors::new_invalid_style_id_error(style_id))?;
290 if style_id < 0
291 || style_sheet.cell_xfs.is_none()
292 || style_sheet.cell_xfs.as_ref().unwrap().xf.len() as i32 <= style_id
293 {
294 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
295 }
296 style_sheet.clone()
297 };
298
299 let xf = &style_sheet.cell_xfs.as_ref().unwrap().xf[style_id as usize];
300 let mut style = Style::default();
301
302 if should_extract_fill(xf, &style_sheet) {
303 if let Some(fill) = style_sheet
304 .fills
305 .as_ref()
306 .and_then(|f| f.fill.get(*xf.fill_id.as_ref().unwrap() as usize))
307 {
308 extract_fill(self, fill, &mut style);
309 }
310 }
311 if should_extract_border(xf, &style_sheet) {
312 if let Some(border) = style_sheet
313 .borders
314 .as_ref()
315 .and_then(|b| b.border.get(*xf.border_id.as_ref().unwrap() as usize))
316 {
317 extract_border(self, border, &mut style);
318 }
319 }
320 if should_extract_font(xf, &style_sheet) {
321 if let Some(font) = style_sheet
322 .fonts
323 .as_ref()
324 .and_then(|f| f.font.get(*xf.font_id.as_ref().unwrap() as usize))
325 {
326 style.font = Some(extract_font(font));
327 }
328 }
329 if should_extract_alignment(xf, &style_sheet) {
330 if let Some(ref alignment) = xf.alignment {
331 style.alignment = Some(extract_alignment(alignment));
332 }
333 }
334 if should_extract_protection(xf, &style_sheet) {
335 if let Some(ref protection) = xf.protection {
336 style.protection = Some(extract_protection(protection));
337 }
338 }
339 if let Some(num_fmt_id) = xf.num_fmt_id {
340 extract_num_fmt(num_fmt_id as i32, &style_sheet, &mut style);
341 }
342 Ok(style)
343 }
344
345 pub fn get_default_font(&self) -> Result<String> {
349 let ss = self.styles.lock().unwrap();
350 let style_sheet = ss
351 .as_ref()
352 .ok_or_else(|| crate::errors::new_invalid_style_id_error(0))?;
353 if style_sheet
354 .fonts
355 .as_ref()
356 .map(|f| f.font.is_empty())
357 .unwrap_or(true)
358 {
359 return Ok("Calibri".to_string());
360 }
361 Ok(style_sheet.fonts.as_ref().unwrap().font[0]
362 .name
363 .as_ref()
364 .and_then(|n| n.val.clone())
365 .unwrap_or_default())
366 }
367
368 pub fn set_default_font(&self, font_name: &str) -> Result<()> {
372 let mut ss = self.styles.lock().unwrap();
373 let mut style_sheet = ss.take().unwrap_or_default();
374 if style_sheet
375 .fonts
376 .as_ref()
377 .map(|f| f.font.is_empty())
378 .unwrap_or(true)
379 {
380 *ss = Some(style_sheet);
381 return Ok(());
382 }
383 style_sheet.fonts.as_mut().unwrap().font[0].name = Some(AttrValString {
384 val: Some(font_name.to_string()),
385 });
386 if let Some(ref mut cs) = style_sheet.cell_styles {
387 if !cs.cell_style.is_empty() {
388 cs.cell_style[0].custom_built_in = Some(true);
389 }
390 }
391 *ss = Some(style_sheet);
392 Ok(())
393 }
394
395 pub fn new_conditional_style(&self, style: &Style) -> Result<i32> {
399 let mut ss = self.styles.lock().unwrap();
400 let mut style_sheet = ss.take().unwrap_or_default();
401 let mut fs = parse_format_style_set(style)?;
402 if let Some(dp) = fs.decimal_places {
403 if dp < 0 || dp > 30 {
404 fs.decimal_places = Some(2);
406 }
407 }
408 let mut dxf = XlsxDxf::default();
409 dxf.fill = new_fills(&fs, false);
410 dxf.alignment = new_alignment(&fs);
411 if !fs.border.is_empty() {
412 dxf.border = Some(new_borders(&fs));
413 }
414 if let Some(ref font) = fs.font {
415 dxf.font = Some(new_font(font));
416 }
417 dxf.protection = new_protection(&fs);
418 dxf.num_fmt = new_dxf_num_fmt(&style_sheet, style);
419 if style_sheet.dxfs.is_none() {
420 style_sheet.dxfs = Some(XlsxDxfs::default());
421 }
422 let dxfs = style_sheet.dxfs.as_mut().unwrap();
423 dxfs.count += 1;
424 dxfs.dxfs.push(dxf);
425 let id = dxfs.count - 1;
426 *ss = Some(style_sheet);
427 Ok(id as i32)
428 }
429
430 pub fn get_conditional_style(&self, style_id: i32) -> Result<Style> {
434 let (dxf, num_fmt_id) = {
437 let ss = self.styles.lock().unwrap();
438 let style_sheet = ss
439 .as_ref()
440 .ok_or_else(|| crate::errors::new_invalid_style_id_error(style_id))?;
441 if style_id < 0
442 || style_sheet.dxfs.is_none()
443 || style_sheet.dxfs.as_ref().unwrap().dxfs.len() as i32 <= style_id
444 {
445 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
446 }
447 let dxf = style_sheet.dxfs.as_ref().unwrap().dxfs[style_id as usize].clone();
448 let num_fmt_id = dxf.num_fmt.as_ref().map(|n| n.num_fmt_id as i32);
449 (dxf, num_fmt_id)
450 };
451
452 let mut style = Style::default();
453 if let Some(mut fill) = dxf.fill {
454 if fill.pattern_fill.is_some()
455 && fill
456 .pattern_fill
457 .as_ref()
458 .unwrap()
459 .pattern_type
460 .as_deref()
461 .unwrap_or("")
462 == ""
463 {
464 fill.pattern_fill.as_mut().unwrap().pattern_type = Some("solid".to_string());
465 }
466 extract_fill(self, &fill, &mut style);
467 }
468 if let Some(ref border) = dxf.border {
469 extract_border(self, border, &mut style);
470 }
471 if let Some(ref font) = dxf.font {
472 style.font = Some(extract_font(font));
473 }
474 if let Some(ref alignment) = dxf.alignment {
475 style.alignment = Some(extract_alignment(alignment));
476 }
477 if let Some(ref protection) = dxf.protection {
478 style.protection = Some(extract_protection(protection));
479 }
480 if let Some(num_fmt_id) = num_fmt_id {
481 let style_sheet = self.styles_reader()?;
482 extract_num_fmt(num_fmt_id, &style_sheet, &mut style);
483 }
484 Ok(style)
485 }
486
487 pub fn set_conditional_format(
491 &self,
492 sheet: &str,
493 range_ref: &str,
494 opts: &[ConditionalFormatOptions],
495 ) -> Result<()> {
496 let path = self.get_sheet_xml_path(sheet).ok_or_else(|| {
497 Box::new(crate::errors::ErrSheetNotExist {
498 sheet_name: sheet.to_string(),
499 }) as Box<dyn std::error::Error + Send + Sync>
500 })?;
501 let mut ws = self.work_sheet_reader(sheet)?;
502 let (sqref, mast_cell) = prepare_conditional_format_range(range_ref)?;
503 let mut rules: i64 = 0;
504 for cf in &ws.conditional_formatting {
505 rules += cf.cf_rule.len() as i64;
506 }
507 let no_criteria_types = [
508 "containsBlanks",
509 "notContainsBlanks",
510 "containsErrors",
511 "notContainsErrors",
512 "expression",
513 "iconSet",
514 ];
515 let sheet_id = self.get_sheet_id(sheet) as u32;
516 let mut cf_rules: Vec<XlsxCfRule> = Vec::new();
517 for (i, opt) in opts.iter().enumerate() {
518 let vt = VALID_TYPE.get(opt.r#type.as_str()).copied();
519 let Some(vt) = vt else {
520 return Err(Box::new(ErrParameterInvalid));
521 };
522 let ct = CRITERIA_TYPE
523 .get(opt.criteria.as_str())
524 .copied()
525 .unwrap_or("");
526 let ct_ok = !ct.is_empty() || in_str_slice(&no_criteria_types, vt, true) != -1;
527 if !ct_ok {
528 return Err(Box::new(ErrParameterInvalid));
529 }
530 let Some(draw_func) = DRAW_COND_FMT_FUNC.get(vt) else {
531 return Err(Box::new(ErrParameterInvalid));
532 };
533 let priority = rules + i as i64;
534 let guid = format!(
535 "{{00000000-0000-0000-{priority:04X}-{sheet_id:012X}}}",
536 priority = priority,
537 sheet_id = sheet_id
538 );
539 let (rule, x14_rule) = draw_func(priority as i32, ct, &mast_cell, &guid, opt);
540 if rule.is_none() && x14_rule.is_none() {
541 return Err(Box::new(ErrParameterInvalid));
542 }
543 if let Some(rule) = rule {
544 cf_rules.push(rule);
545 }
546 if let Some(x14_rule) = x14_rule {
547 append_cf_rule(&mut ws, &x14_rule, &sqref)?;
548 self.add_sheet_name_space(sheet, NAMESPACE_SPREADSHEET_X14);
549 }
550 }
551 if !cf_rules.is_empty() {
552 ws.conditional_formatting.push(XlsxConditionalFormatting {
553 sqref: Some(sqref.clone()),
554 cf_rule: cf_rules,
555 ..Default::default()
556 });
557 }
558 self.sheet.insert(path, ws);
559 Ok(())
560 }
561
562 pub fn get_conditional_formats(
566 &self,
567 sheet: &str,
568 ) -> Result<HashMap<String, Vec<ConditionalFormatOptions>>> {
569 let mut result: HashMap<String, Vec<ConditionalFormatOptions>> = HashMap::new();
570 let ws = self.work_sheet_reader(sheet)?;
571 for cf in &ws.conditional_formatting {
572 let (_, mast_cell) =
573 prepare_conditional_format_range(cf.sqref.as_deref().unwrap_or(""))?;
574 let mut opts = Vec::new();
575 for cr in &cf.cf_rule {
576 if let Some(t) = cr.r#type.as_deref() {
577 if let Some(extract_func) = EXTRACT_COND_FMT_FUNC.get(t) {
578 opts.push(extract_func(self, &mast_cell, cr, ws.ext_lst.as_ref()));
579 }
580 }
581 }
582 if let Some(sqref) = cf.sqref.clone() {
583 result.insert(sqref, opts);
584 }
585 }
586 if let Some(ext_lst) = ws.ext_lst.as_ref() {
587 for ext in &ext_lst.ext {
588 if ext.uri.as_deref() == Some(EXT_URI_CONDITIONAL_FORMATTINGS) {
589 let decoded = parse_x14_conditional_formattings(&ext.content)?;
590 for cond_fmt in &decoded.cond_fmt {
591 let mut opts = Vec::new();
592 for rule in &cond_fmt.cf_rule {
593 if rule.r#type.as_deref() == Some("iconSet") {
594 opts.push(extract_x14_cond_fmt_icon_set(rule));
595 }
596 }
597 if let Some(sqref) = cond_fmt.sqref.as_deref() {
598 result.entry(sqref.to_string()).or_default().extend(opts);
599 }
600 }
601 }
602 }
603 }
604 Ok(result)
605 }
606
607 pub fn unset_conditional_format(&self, sheet: &str, range_ref: &str) -> Result<()> {
611 let path = self.get_sheet_xml_path(sheet).ok_or_else(|| {
612 Box::new(crate::errors::ErrSheetNotExist {
613 sheet_name: sheet.to_string(),
614 }) as Box<dyn std::error::Error + Send + Sync>
615 })?;
616 let mut ws = self.work_sheet_reader(sheet)?;
617 let del_cells = flat_sqref(range_ref)?;
618 let mut i = 0;
619 while i < ws.conditional_formatting.len() {
620 let sqref = ws.conditional_formatting[i]
621 .sqref
622 .as_deref()
623 .unwrap_or("")
624 .to_string();
625 let new_sqref = delete_cells_from_sqref(&sqref, &del_cells)?;
626 if new_sqref.is_empty() {
627 ws.conditional_formatting.remove(i);
628 } else {
629 ws.conditional_formatting[i].sqref = Some(new_sqref);
630 i += 1;
631 }
632 }
633 if let Some(ext_lst) = ws.ext_lst.as_mut() {
634 let mut idx = 0;
635 while idx < ext_lst.ext.len() {
636 if ext_lst.ext[idx].uri.as_deref() == Some(EXT_URI_CONDITIONAL_FORMATTINGS) {
637 let content = ext_lst.ext[idx].content.clone();
638 let decoded = parse_x14_conditional_formattings(&content)?;
639 let new_content = delete_x14_cf_rule(&decoded, &del_cells)?;
640 ext_lst.ext[idx].content = new_content;
641 if ext_lst.ext[idx].content.len() == 57 {
642 ext_lst.ext.remove(idx);
643 continue;
644 }
645 }
646 idx += 1;
647 }
648 sort_ext_lst(ext_lst);
649 if ext_lst.ext.is_empty() {
650 ws.ext_lst = None;
651 }
652 }
653 self.sheet.insert(path, ws);
654 Ok(())
655 }
656
657 pub fn get_base_color(
662 &self,
663 hex_color: &str,
664 indexed_color: i64,
665 theme_color: Option<i64>,
666 ) -> String {
667 if let Ok(Some(theme)) = self.theme_reader() {
668 if let Some(tc) = theme_color {
669 let clr_scheme = &theme.theme_elements.clr_scheme;
670 let choices: [(i64, &crate::xml::theme::DecodeCtColor); 10] = [
671 (0, &clr_scheme.lt1),
672 (1, &clr_scheme.dk1),
673 (2, &clr_scheme.lt2),
674 (3, &clr_scheme.dk2),
675 (4, &clr_scheme.accent1),
676 (5, &clr_scheme.accent2),
677 (6, &clr_scheme.accent3),
678 (7, &clr_scheme.accent4),
679 (8, &clr_scheme.accent5),
680 (9, &clr_scheme.accent6),
681 ];
682 for (idx, clr) in choices {
683 if idx == tc {
684 if let Some(val) = decode_ct_color_choice(clr) {
685 return val;
686 }
687 break;
688 }
689 }
690 }
691 }
692 if hex_color.len() == 6 {
693 return hex_color.to_string();
694 }
695 if hex_color.len() == 8 {
696 return hex_color
697 .strip_prefix("FF")
698 .unwrap_or(hex_color)
699 .to_string();
700 }
701 if let Ok(styles) = self.styles_reader() {
702 if let Some(colors) = styles.colors {
703 if let Some(indexed) = colors.indexed_colors {
704 if let Some(rgb) = indexed.rgb_color.get(indexed_color as usize) {
705 if let Some(rgb_val) = rgb.rgb.as_ref() {
706 return rgb_val.strip_prefix("FF").unwrap_or(rgb_val).to_string();
707 }
708 }
709 }
710 }
711 }
712 if let Some(color) = INDEXED_COLOR_MAPPING.get(indexed_color as usize) {
713 return color.to_string();
714 }
715 hex_color.to_string()
716 }
717}
718
719fn parse_format_style_set(style: &Style) -> Result<Style> {
724 let fs = style.clone();
725 if let Some(ref font) = fs.font {
726 if count_utf16_string(font.name.as_deref().unwrap_or("")) > MAX_FONT_FAMILY_LENGTH {
727 return Err(Box::new(ErrFontLength));
728 }
729 if let Some(size) = font.size {
730 if size > MAX_FONT_SIZE as f64 {
731 return Err(Box::new(ErrFontSize));
732 }
733 }
734 }
735 match fs.fill.r#type.as_str() {
736 "gradient" => {
737 if fs.fill.color.len() != 2 {
738 return Err(Box::new(ErrFillGradientColor));
739 }
740 if fs.fill.shading < 0 || fs.fill.shading > 16 {
741 return Err(Box::new(ErrFillGradientShading));
742 }
743 }
744 "pattern" => {
745 if fs.fill.color.len() > 1 {
746 return Err(Box::new(ErrFillPatternColor));
747 }
748 if fs.fill.pattern < 0 || fs.fill.pattern > 18 {
749 return Err(Box::new(ErrFillPattern));
750 }
751 }
752 "" => {}
753 _ => return Err(Box::new(ErrFillType)),
754 }
755 if let Some(ref cnf) = fs.custom_num_fmt {
756 if cnf.is_empty() {
757 return Err(Box::new(ErrCustomNumFmt));
758 }
759 }
760 Ok(fs)
761}
762
763fn new_font(font: &Font) -> XlsxFont {
768 let mut xfont = XlsxFont {
769 family: Some(AttrValInt { val: Some(2) }),
770 ..Default::default()
771 };
772 if let Some(size) = font.size {
773 if size >= MIN_FONT_SIZE as f64 {
774 xfont.sz = Some(AttrValFloat { val: Some(size) });
775 }
776 }
777 if let Some(name) = &font.name {
778 xfont.name = Some(AttrValString {
779 val: Some(name.clone()),
780 });
781 }
782 if let Some(charset) = font.charset {
783 xfont.charset = Some(AttrValInt { val: Some(charset) });
784 }
785 xfont.color = new_font_color(font);
786 if let Some(bold) = font.bold {
787 xfont.b = Some(AttrValBool { val: Some(bold) });
788 }
789 if let Some(italic) = font.italic {
790 xfont.i = Some(AttrValBool { val: Some(italic) });
791 }
792 if let Some(strike) = font.strike {
793 xfont.strike = Some(AttrValBool { val: Some(strike) });
794 }
795 if let Some(underline) = &font.underline {
796 if SUPPORTED_UNDERLINE_TYPES.contains(&underline.as_str()) {
797 xfont.u = Some(AttrValString {
798 val: Some(underline.clone()),
799 });
800 }
801 }
802 if let Some(vert_align) = &font.vert_align {
803 if SUPPORTED_VERT_ALIGN_TYPES.contains(&vert_align.as_str()) {
804 xfont.vert_align = Some(AttrValString {
805 val: Some(vert_align.clone()),
806 });
807 }
808 }
809 xfont
810}
811
812fn new_font_color(font: &Font) -> Option<XlsxColor> {
813 let mut color: Option<XlsxColor> = None;
814 if let Some(c) = &font.color {
815 color.get_or_insert_default().rgb = Some(get_palette_color(c));
816 }
817 if let Some(indexed) = font.color_indexed {
818 if indexed >= 0 && indexed <= (INDEXED_COLOR_MAPPING.len() as i64 + 1) {
819 color.get_or_insert_default().indexed = Some(indexed);
820 }
821 }
822 if let Some(theme) = font.color_theme {
823 color.get_or_insert_default().theme = Some(theme);
824 }
825 if font.color_tint != 0.0 {
826 color.get_or_insert_default().tint = Some(font.color_tint);
827 }
828 color
829}
830
831fn decode_ct_color_choice(clr: &crate::xml::theme::DecodeCtColor) -> Option<String> {
832 if let Some(srgb) = &clr.srgb_clr {
833 if let Some(val) = &srgb.val {
834 return Some(val.clone());
835 }
836 }
837 if let Some(sys) = &clr.sys_clr {
838 return Some(sys.last_clr.clone());
839 }
840 None
841}
842
843pub(crate) fn get_palette_color(color: &str) -> String {
844 format!("FF{}", color.to_uppercase().replace('#', ""))
845}
846
847fn get_font_id(style_sheet: &XlsxStyleSheet, font: &Font) -> i32 {
848 if style_sheet.fonts.is_none() {
849 return -1;
850 }
851 let target = new_font(font);
852 for (idx, fnt) in style_sheet.fonts.as_ref().unwrap().font.iter().enumerate() {
853 if *fnt == target {
854 return idx as i32;
855 }
856 }
857 -1
858}
859
860fn new_fills(style: &Style, fg: bool) -> Option<XlsxFill> {
861 let mut fill = XlsxFill::default();
862 match style.fill.r#type.as_str() {
863 "gradient" => {
864 let mut variants = style_fill_variants();
865 let shading = style.fill.shading as usize;
866 if shading >= variants.len() {
867 return None;
868 }
869 let mut gradient = variants.remove(shading);
870 if let Some(c) = style.fill.color.get(0) {
871 if gradient.stop.is_empty() {
872 gradient.stop.push(XlsxGradientFillStop::default());
873 }
874 gradient.stop[0].color = Some(XlsxColor {
875 rgb: Some(get_palette_color(c)),
876 ..Default::default()
877 });
878 }
879 if let Some(c) = style.fill.color.get(1) {
880 if gradient.stop.len() < 2 {
881 gradient.stop.push(XlsxGradientFillStop {
882 position: 1.0,
883 color: None,
884 });
885 }
886 gradient.stop[1].color = Some(XlsxColor {
887 rgb: Some(get_palette_color(c)),
888 ..Default::default()
889 });
890 }
891 if gradient.stop.len() == 3 {
892 if let Some(c) = style.fill.color.get(0) {
893 gradient.stop[2].color = Some(XlsxColor {
894 rgb: Some(get_palette_color(c)),
895 ..Default::default()
896 });
897 }
898 }
899 fill.gradient_fill = Some(gradient);
900 }
901 "pattern" => {
902 let mut pattern = XlsxPatternFill::default();
903 let pattern_idx = style.fill.pattern as usize;
904 if pattern_idx < STYLE_FILL_PATTERNS.len() {
905 pattern.pattern_type = Some(STYLE_FILL_PATTERNS[pattern_idx].to_string());
906 }
907 if style.fill.color.is_empty() {
908 if style.fill.pattern == 1 {
909 pattern.fg_color = Some(XlsxColor {
910 auto: Some(true),
911 ..Default::default()
912 });
913 pattern.bg_color = Some(XlsxColor {
914 auto: Some(true),
915 ..Default::default()
916 });
917 }
918 } else if fg {
919 pattern.fg_color = Some(XlsxColor {
920 rgb: Some(get_palette_color(&style.fill.color[0])),
921 ..Default::default()
922 });
923 } else {
924 pattern.bg_color = Some(XlsxColor {
925 rgb: Some(get_palette_color(&style.fill.color[0])),
926 ..Default::default()
927 });
928 }
929 fill.pattern_fill = Some(pattern);
930 }
931 _ => return None,
932 }
933 Some(fill)
934}
935
936fn get_fill_id(style_sheet: &XlsxStyleSheet, style: &Style) -> i32 {
937 if style_sheet.fills.is_none() || style.fill.r#type.is_empty() {
938 return -1;
939 }
940 let target = match new_fills(style, true) {
941 Some(f) => f,
942 None => return -1,
943 };
944 for (idx, fill) in style_sheet.fills.as_ref().unwrap().fill.iter().enumerate() {
945 if *fill == target {
946 return idx as i32;
947 }
948 }
949 -1
950}
951
952fn new_borders(style: &Style) -> XlsxBorder {
953 let mut border = XlsxBorder::default();
954 for v in &style.border {
955 if v.style >= 0 && v.style < STYLE_BORDERS.len() as i64 {
956 let line = XlsxLine {
957 style: Some(STYLE_BORDERS[v.style as usize].to_string()),
958 color: Some(XlsxColor {
959 rgb: Some(get_palette_color(&v.color)),
960 ..Default::default()
961 }),
962 };
963 match v.r#type.as_str() {
964 "left" => border.left = Some(line),
965 "right" => border.right = Some(line),
966 "top" => border.top = Some(line),
967 "bottom" => border.bottom = Some(line),
968 "diagonalUp" => {
969 border.diagonal = Some(line);
970 border.diagonal_up = Some(true);
971 }
972 "diagonalDown" => {
973 border.diagonal = Some(line);
974 border.diagonal_down = Some(true);
975 }
976 _ => {}
977 }
978 }
979 }
980 border
981}
982
983fn get_border_id(style_sheet: &XlsxStyleSheet, style: &Style) -> i32 {
984 if style_sheet.borders.is_none() || style.border.is_empty() {
985 return -1;
986 }
987 let target = new_borders(style);
988 for (idx, border) in style_sheet
989 .borders
990 .as_ref()
991 .unwrap()
992 .border
993 .iter()
994 .enumerate()
995 {
996 if *border == target {
997 return idx as i32;
998 }
999 }
1000 -1
1001}
1002
1003fn new_alignment(style: &Style) -> Option<XlsxAlignment> {
1004 style.alignment.as_ref().map(|a| XlsxAlignment {
1005 horizontal: Some(a.horizontal.clone()).filter(|s| !s.is_empty()),
1006 indent: Some(a.indent).filter(|&v| v != 0),
1007 justify_last_line: Some(a.justify_last_line).filter(|&v| v),
1008 reading_order: Some(a.reading_order).filter(|&v| v != 0),
1009 relative_indent: Some(a.relative_indent).filter(|&v| v != 0),
1010 shrink_to_fit: Some(a.shrink_to_fit).filter(|&v| v),
1011 text_rotation: Some(a.text_rotation).filter(|&v| v != 0),
1012 vertical: Some(a.vertical.clone()).filter(|s| !s.is_empty()),
1013 wrap_text: Some(a.wrap_text).filter(|&v| v),
1014 })
1015}
1016
1017fn new_protection(style: &Style) -> Option<XlsxProtection> {
1018 style.protection.as_ref().map(|p| XlsxProtection {
1019 hidden: Some(p.hidden).filter(|&v| v),
1020 locked: Some(p.locked).filter(|&v| v),
1021 })
1022}
1023
1024fn set_cell_xfs(
1025 style_sheet: &mut XlsxStyleSheet,
1026 font_id: i32,
1027 num_fmt_id: i32,
1028 fill_id: i32,
1029 border_id: i32,
1030 apply_alignment: bool,
1031 apply_protection: bool,
1032 alignment: Option<XlsxAlignment>,
1033 protection: Option<XlsxProtection>,
1034) -> Result<i32> {
1035 ensure_cell_xfs(style_sheet);
1036 let cell_xfs = style_sheet.cell_xfs.as_mut().unwrap();
1037 if cell_xfs.xf.len() as i32 >= MAX_CELL_STYLES {
1038 return Err(Box::new(ErrCellStyles));
1039 }
1040 let mut xf = XlsxXf::default();
1041 xf.font_id = Some(font_id as i64);
1042 if font_id != 0 {
1043 xf.apply_font = Some(true);
1044 }
1045 xf.num_fmt_id = Some(num_fmt_id as i64);
1046 if num_fmt_id != 0 {
1047 xf.apply_number_format = Some(true);
1048 }
1049 xf.fill_id = Some(fill_id as i64);
1050 if fill_id != 0 {
1051 xf.apply_fill = Some(true);
1052 }
1053 xf.border_id = Some(border_id as i64);
1054 if border_id != 0 {
1055 xf.apply_border = Some(true);
1056 }
1057 xf.xf_id = Some(0);
1058 xf.alignment = alignment;
1059 if apply_alignment {
1060 xf.apply_alignment = Some(true);
1061 }
1062 if apply_protection {
1063 xf.apply_protection = Some(true);
1064 xf.protection = protection;
1065 }
1066 cell_xfs.xf.push(xf);
1067 cell_xfs.count = cell_xfs.xf.len() as i64;
1068 Ok((cell_xfs.count - 1) as i32)
1069}
1070
1071fn get_style_id(style_sheet: &XlsxStyleSheet, style: &Style) -> i32 {
1076 if style_sheet.cell_xfs.is_none() {
1077 return -1;
1078 }
1079 if style.font.is_none() {
1080 return -1;
1081 }
1082 let num_fmt_id = get_num_fmt_id(style_sheet, style);
1083 let border_id = get_border_id(style_sheet, style);
1084 let fill_id = get_fill_id(style_sheet, style);
1085 let font_id = get_font_id(style_sheet, style.font.as_ref().unwrap());
1086 let num_fmt_id_for_custom = if style.custom_num_fmt.is_some() {
1087 get_custom_num_fmt_id(style_sheet, style)
1088 } else {
1089 num_fmt_id
1090 };
1091
1092 for (idx, xf) in style_sheet.cell_xfs.as_ref().unwrap().xf.iter().enumerate() {
1093 if !match_num_fmt(num_fmt_id_for_custom, xf, style) {
1094 continue;
1095 }
1096 if !match_font(font_id, xf, style) {
1097 continue;
1098 }
1099 if !match_fill(fill_id, xf, style) {
1100 continue;
1101 }
1102 if !match_border(border_id, xf, style) {
1103 continue;
1104 }
1105 if !match_alignment(xf, style) {
1106 continue;
1107 }
1108 if !match_protection(xf, style) {
1109 continue;
1110 }
1111 return idx as i32;
1112 }
1113 -1
1114}
1115
1116fn match_num_fmt(num_fmt_id: i32, xf: &XlsxXf, style: &Style) -> bool {
1117 if style.custom_num_fmt.is_none() && num_fmt_id == -1 {
1118 return xf.num_fmt_id.map(|v| v == 0).unwrap_or(false);
1119 }
1120 if style.neg_red || style.decimal_places.map(|v| v != 2).unwrap_or(false) {
1121 return false;
1122 }
1123 xf.num_fmt_id == Some(num_fmt_id as i64)
1124}
1125
1126fn match_font(font_id: i32, xf: &XlsxXf, style: &Style) -> bool {
1127 if style.font.is_none() || font_id == 0 {
1128 return (xf.font_id.is_none() || xf.font_id == Some(0))
1129 && (xf.apply_font.is_none() || !xf.apply_font.unwrap());
1130 }
1131 xf.font_id == Some(font_id as i64) && xf.apply_font == Some(true)
1132}
1133
1134fn match_fill(fill_id: i32, xf: &XlsxXf, style: &Style) -> bool {
1135 if style.fill.r#type.is_empty() || fill_id == 0 {
1136 return (xf.fill_id.is_none() || xf.fill_id == Some(0))
1137 && (xf.apply_fill.is_none() || !xf.apply_fill.unwrap());
1138 }
1139 xf.fill_id == Some(fill_id as i64) && xf.apply_fill == Some(true)
1140}
1141
1142fn match_border(border_id: i32, xf: &XlsxXf, style: &Style) -> bool {
1143 if style.border.is_empty() {
1144 return (xf.border_id.is_none() || xf.border_id == Some(0))
1145 && (xf.apply_border.is_none() || !xf.apply_border.unwrap());
1146 }
1147 xf.border_id == Some(border_id as i64) && xf.apply_border == Some(true)
1148}
1149
1150fn match_alignment(xf: &XlsxXf, style: &Style) -> bool {
1151 if style.alignment.is_none() {
1152 return xf.apply_alignment.is_none() || !xf.apply_alignment.unwrap();
1153 }
1154 xf.alignment == new_alignment(style)
1155}
1156
1157fn match_protection(xf: &XlsxXf, style: &Style) -> bool {
1158 if style.protection.is_none() {
1159 return xf.apply_protection.is_none() || !xf.apply_protection.unwrap();
1160 }
1161 xf.protection == new_protection(style) && xf.apply_protection == Some(true)
1162}
1163
1164static BUILT_IN_NUM_FMT: LazyLock<HashMap<i32, &'static str>> = LazyLock::new(|| {
1169 let mut m = HashMap::new();
1170 m.insert(0, "general");
1171 m.insert(1, "0");
1172 m.insert(2, "0.00");
1173 m.insert(3, "#,##0");
1174 m.insert(4, "#,##0.00");
1175 m.insert(9, "0%");
1176 m.insert(10, "0.00%");
1177 m.insert(11, "0.00E+00");
1178 m.insert(12, "# ?/?");
1179 m.insert(13, "# ??/??");
1180 m.insert(14, "mm-dd-yy");
1181 m.insert(15, "d-mmm-yy");
1182 m.insert(16, "d-mmm");
1183 m.insert(17, "mmm-yy");
1184 m.insert(18, "h:mm AM/PM");
1185 m.insert(19, "h:mm:ss AM/PM");
1186 m.insert(20, "hh:mm");
1187 m.insert(21, "hh:mm:ss");
1188 m.insert(22, "m/d/yy hh:mm");
1189 m.insert(37, "#,##0 ;(#,##0)");
1190 m.insert(38, "#,##0 ;[red](#,##0)");
1191 m.insert(39, "#,##0.00 ;(#,##0.00)");
1192 m.insert(40, "#,##0.00 ;[red](#,##0.00)");
1193 m.insert(41, "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)");
1194 m.insert(
1195 42,
1196 "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)",
1197 );
1198 m.insert(43, "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)");
1199 m.insert(
1200 44,
1201 "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)",
1202 );
1203 m.insert(45, "mm:ss");
1204 m.insert(46, "[h]:mm:ss");
1205 m.insert(47, "mm:ss.0");
1206 m.insert(48, "##0.0E+0");
1207 m.insert(49, "@");
1208 m
1209});
1210
1211static CURRENCY_NUM_FMT: LazyLock<HashMap<i32, &'static str>> = LazyLock::new(|| {
1212 let mut m = HashMap::new();
1213 m.insert(164, "\"\u{00A5}\"#,##0.00");
1214 m.insert(165, "[$$-409]#,##0.00");
1215 m.insert(166, "[$$-45C]#,##0.00");
1216 m.insert(167, "[$$-1004]#,##0.00");
1217 m.insert(168, "[$$-404]#,##0.00");
1218 m.insert(169, "[$$-C09]#,##0.00");
1219 m.insert(170, "[$$-2809]#,##0.00");
1220 m.insert(171, "[$$-1009]#,##0.00");
1221 m.insert(172, "[$$-2009]#,##0.00");
1222 m.insert(173, "[$$-1409]#,##0.00");
1223 m.insert(174, "[$$-4809]#,##0.00");
1224 m.insert(175, "[$$-2C09]#,##0.00");
1225 m.insert(176, "[$$-2409]#,##0.00");
1226 m.insert(177, "[$$-1000]#,##0.00");
1227 m.insert(178, "#,##0.00\\ [$$-C0C]");
1228 m.insert(179, "[$$-475]#,##0.00");
1229 m.insert(180, "[$$-83E]#,##0.00");
1230 m.insert(181, "[$$-86B]\\ #,##0.00");
1231 m.insert(182, "[$$-340A]\\ #,##0.00");
1232 m.insert(183, "[$$-240A]#,##0.00");
1233 m.insert(184, "[$$-300A]\\ #,##0.00");
1234 m.insert(185, "[$$-440A]#,##0.00");
1235 m.insert(186, "[$$-80A]#,##0.00");
1236 m.insert(187, "[$$-500A]#,##0.00");
1237 m.insert(188, "[$$-540A]#,##0.00");
1238 m.insert(189, "[$$-380A]\\ #,##0.00");
1239 m.insert(190, "[$\u{00A3}-809]#,##0.00");
1240 m.insert(191, "[$\u{00A3}-491]#,##0.00");
1241 m.insert(192, "[$\u{00A3}-452]#,##0.00");
1242 m.insert(193, "[$\u{00A5}-804]#,##0.00");
1243 m.insert(194, "[$\u{00A5}-411]#,##0.00");
1244 m.insert(195, "[$\u{00A5}-478]#,##0.00");
1245 m.insert(196, "[$\u{00A5}-451]#,##0.00");
1246 m.insert(197, "[$\u{00A5}-480]#,##0.00");
1247 m.insert(198, "#,##0.00\\ [$\u{058F}-42B]");
1248 m.insert(199, "[$\u{060B}-463]#,##0.00");
1249 m.insert(200, "[$\u{060B}-48C]#,##0.00");
1250 m.insert(201, "[$\u{09F3}-845]\\ #,##0.00");
1251 m.insert(202, "#,##0.00[$\u{17DB}-453]");
1252 m.insert(203, "[$\u{20A1}-140A]#,##0.00");
1253 m.insert(204, "[$\u{20A6}-468]\\ #,##0.00");
1254 m.insert(205, "[$\u{20A6}-470]\\ #,##0.00");
1255 m.insert(206, "[$\u{20A9}-412]#,##0.00");
1256 m.insert(207, "[$\u{20AA}-40D]\\ #,##0.00");
1257 m.insert(208, "#,##0.00\\ [$\u{20AB}-42A]");
1258 m.insert(209, "#,##0.00\\ [$\u{20AC}-42D]");
1259 m.insert(210, "#,##0.00\\ [$\u{20AC}-47E]");
1260 m.insert(211, "#,##0.00\\ [$\u{20AC}-403]");
1261 m.insert(212, "#,##0.00\\ [$\u{20AC}-483]");
1262 m.insert(213, "[$\u{20AC}-813]\\ #,##0.00");
1263 m.insert(214, "[$\u{20AC}-413]\\ #,##0.00");
1264 m.insert(215, "[$\u{20AC}-1809]#,##0.00");
1265 m.insert(216, "#,##0.00\\ [$\u{20AC}-425]");
1266 m.insert(217, "[$\u{20AC}-2]\\ #,##0.00");
1267 m.insert(218, "#,##0.00\\ [$\u{20AC}-1]");
1268 m.insert(219, "#,##0.00\\ [$\u{20AC}-40B]");
1269 m.insert(220, "#,##0.00\\ [$\u{20AC}-80C]");
1270 m.insert(221, "#,##0.00\\ [$\u{20AC}-40C]");
1271 m.insert(222, "#,##0.00\\ [$\u{20AC}-140C]");
1272 m.insert(223, "#,##0.00\\ [$\u{20AC}-180C]");
1273 m.insert(224, "[$\u{20AC}-200C]#,##0.00");
1274 m.insert(225, "#,##0.00\\ [$\u{20AC}-456]");
1275 m.insert(226, "#,##0.00\\ [$\u{20AC}-C07]");
1276 m.insert(227, "#,##0.00\\ [$\u{20AC}-407]");
1277 m.insert(228, "#,##0.00\\ [$\u{20AC}-1007]");
1278 m.insert(229, "#,##0.00\\ [$\u{20AC}-408]");
1279 m.insert(230, "#,##0.00\\ [$\u{20AC}-243B]");
1280 m.insert(231, "[$\u{20AC}-83C]#,##0.00");
1281 m.insert(232, "[$\u{20AC}-410]\\ #,##0.00");
1282 m.insert(233, "[$\u{20AC}-476]#,##0.00");
1283 m.insert(234, "#,##0.00\\ [$\u{20AC}-2C1A]");
1284 m.insert(235, "[$\u{20AC}-426]\\ #,##0.00");
1285 m.insert(236, "#,##0.00\\ [$\u{20AC}-427]");
1286 m.insert(237, "#,##0.00\\ [$\u{20AC}-82E]");
1287 m.insert(238, "#,##0.00\\ [$\u{20AC}-46E]");
1288 m.insert(239, "[$\u{20AC}-43A]#,##0.00");
1289 m.insert(240, "#,##0.00\\ [$\u{20AC}-C3B]");
1290 m.insert(241, "#,##0.00\\ [$\u{20AC}-482]");
1291 m.insert(242, "#,##0.00\\ [$\u{20AC}-816]");
1292 m.insert(243, "#,##0.00\\ [$\u{20AC}-301A]");
1293 m.insert(244, "#,##0.00\\ [$\u{20AC}-203B]");
1294 m.insert(245, "[$\u{20AC}-41B]#,##0.00");
1295 m.insert(246, "#,##0.00\\ [$\u{20AC}-424]");
1296 m.insert(247, "#,##0.00\\ [$\u{20AC}-C0A]");
1297 m.insert(248, "#,##0.00\\ [$\u{20AC}-81D]");
1298 m.insert(249, "#,##0.00\\ [$\u{20AC}-484]");
1299 m.insert(250, "#,##0.00\\ [$\u{20AC}-42E]");
1300 m.insert(251, "[$\u{20AC}-462]\\ #,##0.00");
1301 m.insert(252, "#,##0.00\\ [$\u{20AD}-454]");
1302 m.insert(253, "#,##0.00\\ [$\u{20AE}-450]");
1303 m.insert(254, "[$\u{20AE}-C50]#,##0.00");
1304 m.insert(255, "[$\u{20B1}-3409]#,##0.00");
1305 m.insert(256, "[$\u{20B1}-464]#,##0.00");
1306 m.insert(257, "#,##0.00[$\u{20B4}-422]");
1307 m.insert(258, "[$\u{20B8}-43F]#,##0.00");
1308 m.insert(259, "[$\u{20B9}-460]#,##0.00");
1309 m.insert(260, "[$\u{20B9}-4009]\\ #,##0.00");
1310 m.insert(261, "[$\u{20B9}-447]\\ #,##0.00");
1311 m.insert(262, "[$\u{20B9}-439]\\ #,##0.00");
1312 m.insert(263, "[$\u{20B9}-44B]\\ #,##0.00");
1313 m.insert(264, "[$\u{20B9}-860]#,##0.00");
1314 m.insert(265, "[$\u{20B9}-457]\\ #,##0.00");
1315 m.insert(266, "[$\u{20B9}-458]#,##0.00");
1316 m.insert(267, "[$\u{20B9}-44E]\\ #,##0.00");
1317 m.insert(268, "[$\u{20B9}-861]#,##0.00");
1318 m.insert(269, "[$\u{20B9}-448]\\ #,##0.00");
1319 m.insert(270, "[$\u{20B9}-446]\\ #,##0.00");
1320 m.insert(271, "[$\u{20B9}-44F]\\ #,##0.00");
1321 m.insert(272, "[$\u{20B9}-459]#,##0.00");
1322 m.insert(273, "[$\u{20B9}-449]\\ #,##0.00");
1323 m.insert(274, "[$\u{20B9}-820]#,##0.00");
1324 m.insert(275, "#,##0.00\\ [$\u{20BA}-41F]");
1325 m.insert(276, "#,##0.00\\ [$\u{20BC}-42C]");
1326 m.insert(277, "#,##0.00\\ [$\u{20BC}-82C]");
1327 m.insert(278, "#,##0.00\\ [$\u{20BD}-419]");
1328 m.insert(279, "#,##0.00[$\u{20BD}-485]");
1329 m.insert(280, "#,##0.00\\ [$\u{20BE}-437]");
1330 m.insert(281, "[$B/.-180A]\\ #,##0.00");
1331 m.insert(282, "[$Br-472]#,##0.00");
1332 m.insert(283, "[$Br-477]#,##0.00");
1333 m.insert(284, "#,##0.00[$Br-473]");
1334 m.insert(285, "[$Bs-46B]\\ #,##0.00");
1335 m.insert(286, "[$Bs-400A]\\ #,##0.00");
1336 m.insert(287, "[$Bs.-200A]\\ #,##0.00");
1337 m.insert(288, "[$BWP-832]\\ #,##0.00");
1338 m.insert(289, "[$C$-4C0A]#,##0.00");
1339 m.insert(290, "[$CA$-85D]#,##0.00");
1340 m.insert(291, "[$CA$-47C]#,##0.00");
1341 m.insert(292, "[$CA$-45D]#,##0.00");
1342 m.insert(293, "[$CFA-340C]#,##0.00");
1343 m.insert(294, "[$CFA-280C]#,##0.00");
1344 m.insert(295, "#,##0.00\\ [$CFA-867]");
1345 m.insert(296, "#,##0.00\\ [$CFA-488]");
1346 m.insert(297, "#,##0.00\\ [$CHF-100C]");
1347 m.insert(298, "[$CHF-1407]\\ #,##0.00");
1348 m.insert(299, "[$CHF-807]\\ #,##0.00");
1349 m.insert(300, "[$CHF-810]\\ #,##0.00");
1350 m.insert(301, "[$CHF-417]\\ #,##0.00");
1351 m.insert(302, "[$CLP-47A]\\ #,##0.00");
1352 m.insert(303, "[$CN\u{00A5}-850]#,##0.00");
1353 m.insert(304, "#,##0.00\\ [$DZD-85F]");
1354 m.insert(305, "[$FCFA-2C0C]#,##0.00");
1355 m.insert(306, "#,##0.00\\ [$Ft-40E]");
1356 m.insert(307, "[$G-3C0C]#,##0.00");
1357 m.insert(308, "[$Gs.-3C0A]\\ #,##0.00");
1358 m.insert(309, "[$GTQ-486]#,##0.00");
1359 m.insert(310, "[$HK$-C04]#,##0.00");
1360 m.insert(311, "[$HK$-3C09]#,##0.00");
1361 m.insert(312, "#,##0.00\\ [$HRK-41A]");
1362 m.insert(313, "[$IDR-3809]#,##0.00");
1363 m.insert(314, "[$IQD-492]#,##0.00");
1364 m.insert(315, "#,##0.00\\ [$ISK-40F]");
1365 m.insert(316, "[$K-455]#,##0.00");
1366 m.insert(317, "#,##0.00\\ [$K\u{010D}-405]");
1367 m.insert(318, "#,##0.00\\ [$KM-141A]");
1368 m.insert(319, "#,##0.00\\ [$KM-101A]");
1369 m.insert(320, "#,##0.00\\ [$KM-181A]");
1370 m.insert(321, "[$kr-438]\\ #,##0.00");
1371 m.insert(322, "[$kr-43B]\\ #,##0.00");
1372 m.insert(323, "#,##0.00\\ [$kr-83B]");
1373 m.insert(324, "[$kr-414]\\ #,##0.00");
1374 m.insert(325, "[$kr-814]\\ #,##0.00");
1375 m.insert(326, "#,##0.00\\ [$kr-41D]");
1376 m.insert(327, "[$kr.-406]\\ #,##0.00");
1377 m.insert(328, "[$kr.-46F]\\ #,##0.00");
1378 m.insert(329, "[$Ksh-441]#,##0.00");
1379 m.insert(330, "[$L-818]#,##0.00");
1380 m.insert(331, "[$L-819]#,##0.00");
1381 m.insert(332, "[$L-480A]\\ #,##0.00");
1382 m.insert(333, "#,##0.00\\ [$Lek\u{00EB}-41C]");
1383 m.insert(334, "[$MAD-45F]#,##0.00");
1384 m.insert(335, "[$MAD-380C]#,##0.00");
1385 m.insert(336, "#,##0.00\\ [$MAD-105F]");
1386 m.insert(337, "[$MOP$-1404]#,##0.00");
1387 m.insert(338, "#,##0.00\\ [$MVR-465]_-");
1388 m.insert(339, "#,##0.00[$Nfk-873]");
1389 m.insert(340, "[$NGN-466]#,##0.00");
1390 m.insert(341, "[$NGN-467]#,##0.00");
1391 m.insert(342, "[$NGN-469]#,##0.00");
1392 m.insert(343, "[$NGN-471]#,##0.00");
1393 m.insert(344, "[$NOK-103B]\\ #,##0.00");
1394 m.insert(345, "[$NOK-183B]\\ #,##0.00");
1395 m.insert(346, "[$NZ$-481]#,##0.00");
1396 m.insert(347, "[$PKR-859]\\ #,##0.00");
1397 m.insert(348, "[$PYG-474]#,##0.00");
1398 m.insert(349, "[$Q-100A]#,##0.00");
1399 m.insert(350, "[$R-436]\\ #,##0.00");
1400 m.insert(351, "[$R-1C09]\\ #,##0.00");
1401 m.insert(352, "[$R-435]\\ #,##0.00");
1402 m.insert(353, "[$R$-416]\\ #,##0.00");
1403 m.insert(354, "[$RD$-1C0A]#,##0.00");
1404 m.insert(355, "#,##0.00\\ [$RF-487]");
1405 m.insert(356, "[$RM-4409]#,##0.00");
1406 m.insert(357, "[$RM-43E]#,##0.00");
1407 m.insert(358, "#,##0.00\\ [$RON-418]");
1408 m.insert(359, "[$Rp-421]#,##0.00");
1409 m.insert(360, "[$Rs-420]#,##0.00_-");
1410 m.insert(361, "[$Rs.-849]\\ #,##0.00");
1411 m.insert(362, "#,##0.00\\ [$RSD-81A]");
1412 m.insert(363, "#,##0.00\\ [$RSD-C1A]");
1413 m.insert(364, "#,##0.00\\ [$RUB-46D]");
1414 m.insert(365, "#,##0.00\\ [$RUB-444]");
1415 m.insert(366, "[$S/.-C6B]\\ #,##0.00");
1416 m.insert(367, "[$S/.-280A]\\ #,##0.00");
1417 m.insert(368, "#,##0.00\\ [$SEK-143B]");
1418 m.insert(369, "#,##0.00\\ [$SEK-1C3B]");
1419 m.insert(370, "#,##0.00\\ [$so\u{02BB}m-443]");
1420 m.insert(371, "#,##0.00\\ [$so\u{02BB}m-843]");
1421 m.insert(372, "#,##0.00\\ [$SYP-45A]");
1422 m.insert(373, "[$THB-41E]#,##0.00");
1423 m.insert(374, "#,##0.00[$TMT-442]");
1424 m.insert(375, "[$US$-3009]#,##0.00");
1425 m.insert(376, "[$ZAR-46C]\\ #,##0.00");
1426 m.insert(377, "[$ZAR-430]#,##0.00");
1427 m.insert(378, "[$ZAR-431]#,##0.00");
1428 m.insert(379, "[$ZAR-432]\\ #,##0.00");
1429 m.insert(380, "[$ZAR-433]#,##0.00");
1430 m.insert(381, "[$ZAR-434]\\ #,##0.00");
1431 m.insert(382, "#,##0.00\\ [$z\u{0142}-415]");
1432 m.insert(383, "#,##0.00\\ [$\u{0434}\u{0435}\u{043D}-42F]");
1433 m.insert(384, "#,##0.00\\ [$\u{041A}\u{041C}-201A]");
1434 m.insert(385, "#,##0.00\\ [$\u{041A}\u{041C}-1C1A]");
1435 m.insert(386, "#,##0.00\\ [$\u{043B}\u{0432}.-402]");
1436 m.insert(387, "#,##0.00\\ [$\u{0440}.-423]");
1437 m.insert(388, "#,##0.00\\ [$\u{0441}\u{043E}\u{043C}-440]");
1438 m.insert(389, "#,##0.00\\ [$\u{0441}\u{043E}\u{043C}-428]");
1439 m.insert(390, "[$\u{062C}.\u{0645}.-C01]\\ #,##0.00_-");
1440 m.insert(391, "[$\u{062F}.\u{0623}.-2C01]\\ #,##0.00_-");
1441 m.insert(392, "[$\u{062F}.\u{0625}.-3801]\\ #,##0.00_-");
1442 m.insert(393, "[$\u{062F}.\u{0628}.-3C01]\\ #,##0.00_-");
1443 m.insert(394, "[$\u{062F}.\u{062A}.-1C01]\\ #,##0.00_-");
1444 m.insert(395, "[$\u{062F}.\u{062C}.-1401]\\ #,##0.00_-");
1445 m.insert(396, "[$\u{062F}.\u{0639}.-801]\\ #,##0.00_-");
1446 m.insert(397, "[$\u{062F}.\u{0643}.-3401]\\ #,##0.00_-");
1447 m.insert(398, "[$\u{062F}.\u{0644}.-1001]#,##0.00_-");
1448 m.insert(399, "[$\u{062F}.\u{0645}.-1801]\\ #,##0.00_-");
1449 m.insert(400, "[$\u{0631}-846]\\ #,##0.00");
1450 m.insert(401, "[$\u{0631}.\u{0633}.-401]\\ #,##0.00_-");
1451 m.insert(402, "[$\u{0631}.\u{0639}.-2001]\\ #,##0.00_-");
1452 m.insert(403, "[$\u{0631}.\u{0642}.-4001]\\ #,##0.00_-");
1453 m.insert(404, "[$\u{0631}.\u{064A}.-2401]\\ #,##0.00_-");
1454 m.insert(405, "[$\u{0631}\u{06CC}\u{0627}\u{0644}-429]#,##0.00_-");
1455 m.insert(406, "[$\u{0644}.\u{0633}.-2801]\\ #,##0.00_-");
1456 m.insert(407, "[$\u{0644}.\u{0644}.-3001]\\ #,##0.00_-");
1457 m.insert(408, "[$\u{1265}\u{122D}-45E]#,##0.00");
1458 m.insert(409, "[$\u{0930}\u{0942}-461]#,##0.00");
1459 m.insert(410, "[$\u{0DBB}\u{0DD4}.-45B]\\ #,##0.00");
1460 m.insert(411, "[$ADP]\\ #,##0.00");
1461 m.insert(412, "[$AED]\\ #,##0.00");
1462 m.insert(413, "[$AFA]\\ #,##0.00");
1463 m.insert(414, "[$AFN]\\ #,##0.00");
1464 m.insert(415, "[$ALL]\\ #,##0.00");
1465 m.insert(416, "[$AMD]\\ #,##0.00");
1466 m.insert(417, "[$ANG]\\ #,##0.00");
1467 m.insert(418, "[$AOA]\\ #,##0.00");
1468 m.insert(419, "[$ARS]\\ #,##0.00");
1469 m.insert(420, "[$ATS]\\ #,##0.00");
1470 m.insert(421, "[$AUD]\\ #,##0.00");
1471 m.insert(422, "[$AWG]\\ #,##0.00");
1472 m.insert(423, "[$AZM]\\ #,##0.00");
1473 m.insert(424, "[$AZN]\\ #,##0.00");
1474 m.insert(425, "[$BAM]\\ #,##0.00");
1475 m.insert(426, "[$BBD]\\ #,##0.00");
1476 m.insert(427, "[$BDT]\\ #,##0.00");
1477 m.insert(428, "[$BEF]\\ #,##0.00");
1478 m.insert(429, "[$BGL]\\ #,##0.00");
1479 m.insert(430, "[$BGN]\\ #,##0.00");
1480 m.insert(431, "[$BHD]\\ #,##0.00");
1481 m.insert(432, "[$BIF]\\ #,##0.00");
1482 m.insert(433, "[$BMD]\\ #,##0.00");
1483 m.insert(434, "[$BND]\\ #,##0.00");
1484 m.insert(435, "[$BOB]\\ #,##0.00");
1485 m.insert(436, "[$BOV]\\ #,##0.00");
1486 m.insert(437, "[$BRL]\\ #,##0.00");
1487 m.insert(438, "[$BSD]\\ #,##0.00");
1488 m.insert(439, "[$BTN]\\ #,##0.00");
1489 m.insert(440, "[$BWP]\\ #,##0.00");
1490 m.insert(441, "[$BYR]\\ #,##0.00");
1491 m.insert(442, "[$BZD]\\ #,##0.00");
1492 m.insert(443, "[$CAD]\\ #,##0.00");
1493 m.insert(444, "[$CDF]\\ #,##0.00");
1494 m.insert(445, "[$CHE]\\ #,##0.00");
1495 m.insert(446, "[$CHF]\\ #,##0.00");
1496 m.insert(447, "[$CHW]\\ #,##0.00");
1497 m.insert(448, "[$CLF]\\ #,##0.00");
1498 m.insert(449, "[$CLP]\\ #,##0.00");
1499 m.insert(450, "[$CNY]\\ #,##0.00");
1500 m.insert(451, "[$COP]\\ #,##0.00");
1501 m.insert(452, "[$COU]\\ #,##0.00");
1502 m.insert(453, "[$CRC]\\ #,##0.00");
1503 m.insert(454, "[$CSD]\\ #,##0.00");
1504 m.insert(455, "[$CUC]\\ #,##0.00");
1505 m.insert(456, "[$CVE]\\ #,##0.00");
1506 m.insert(457, "[$CYP]\\ #,##0.00");
1507 m.insert(458, "[$CZK]\\ #,##0.00");
1508 m.insert(459, "[$DEM]\\ #,##0.00");
1509 m.insert(460, "[$DJF]\\ #,##0.00");
1510 m.insert(461, "[$DKK]\\ #,##0.00");
1511 m.insert(462, "[$DOP]\\ #,##0.00");
1512 m.insert(463, "[$DZD]\\ #,##0.00");
1513 m.insert(464, "[$ECS]\\ #,##0.00");
1514 m.insert(465, "[$ECV]\\ #,##0.00");
1515 m.insert(466, "[$EEK]\\ #,##0.00");
1516 m.insert(467, "[$EGP]\\ #,##0.00");
1517 m.insert(468, "[$ERN]\\ #,##0.00");
1518 m.insert(469, "[$ESP]\\ #,##0.00");
1519 m.insert(470, "[$ETB]\\ #,##0.00");
1520 m.insert(471, "[$EUR]\\ #,##0.00");
1521 m.insert(472, "[$FIM]\\ #,##0.00");
1522 m.insert(473, "[$FJD]\\ #,##0.00");
1523 m.insert(474, "[$FKP]\\ #,##0.00");
1524 m.insert(475, "[$FRF]\\ #,##0.00");
1525 m.insert(476, "[$GBP]\\ #,##0.00");
1526 m.insert(477, "[$GEL]\\ #,##0.00");
1527 m.insert(478, "[$GHC]\\ #,##0.00");
1528 m.insert(479, "[$GHS]\\ #,##0.00");
1529 m.insert(480, "[$GIP]\\ #,##0.00");
1530 m.insert(481, "[$GMD]\\ #,##0.00");
1531 m.insert(482, "[$GNF]\\ #,##0.00");
1532 m.insert(483, "[$GRD]\\ #,##0.00");
1533 m.insert(484, "[$GTQ]\\ #,##0.00");
1534 m.insert(485, "[$GYD]\\ #,##0.00");
1535 m.insert(486, "[$HKD]\\ #,##0.00");
1536 m.insert(487, "[$HNL]\\ #,##0.00");
1537 m.insert(488, "[$HRK]\\ #,##0.00");
1538 m.insert(489, "[$HTG]\\ #,##0.00");
1539 m.insert(490, "[$HUF]\\ #,##0.00");
1540 m.insert(491, "[$IDR]\\ #,##0.00");
1541 m.insert(492, "[$IEP]\\ #,##0.00");
1542 m.insert(493, "[$ILS]\\ #,##0.00");
1543 m.insert(494, "[$INR]\\ #,##0.00");
1544 m.insert(495, "[$IQD]\\ #,##0.00");
1545 m.insert(496, "[$IRR]\\ #,##0.00");
1546 m.insert(497, "[$ISK]\\ #,##0.00");
1547 m.insert(498, "[$ITL]\\ #,##0.00");
1548 m.insert(499, "[$JMD]\\ #,##0.00");
1549 m.insert(500, "[$JOD]\\ #,##0.00");
1550 m.insert(501, "[$JPY]\\ #,##0.00");
1551 m.insert(502, "[$KAF]\\ #,##0.00");
1552 m.insert(503, "[$KES]\\ #,##0.00");
1553 m.insert(504, "[$KGS]\\ #,##0.00");
1554 m.insert(505, "[$KHR]\\ #,##0.00");
1555 m.insert(506, "[$KMF]\\ #,##0.00");
1556 m.insert(507, "[$KPW]\\ #,##0.00");
1557 m.insert(508, "[$KRW]\\ #,##0.00");
1558 m.insert(509, "[$KWD]\\ #,##0.00");
1559 m.insert(510, "[$KYD]\\ #,##0.00");
1560 m.insert(511, "[$KZT]\\ #,##0.00");
1561 m.insert(512, "[$LAK]\\ #,##0.00");
1562 m.insert(513, "[$LBP]\\ #,##0.00");
1563 m.insert(514, "[$LKR]\\ #,##0.00");
1564 m.insert(515, "[$LRD]\\ #,##0.00");
1565 m.insert(516, "[$LSL]\\ #,##0.00");
1566 m.insert(517, "[$LTL]\\ #,##0.00");
1567 m.insert(518, "[$LUF]\\ #,##0.00");
1568 m.insert(519, "[$LVL]\\ #,##0.00");
1569 m.insert(520, "[$LYD]\\ #,##0.00");
1570 m.insert(521, "[$MAD]\\ #,##0.00");
1571 m.insert(522, "[$MDL]\\ #,##0.00");
1572 m.insert(523, "[$MGA]\\ #,##0.00");
1573 m.insert(524, "[$MGF]\\ #,##0.00");
1574 m.insert(525, "[$MKD]\\ #,##0.00");
1575 m.insert(526, "[$MMK]\\ #,##0.00");
1576 m.insert(527, "[$MNT]\\ #,##0.00");
1577 m.insert(528, "[$MOP]\\ #,##0.00");
1578 m.insert(529, "[$MRO]\\ #,##0.00");
1579 m.insert(530, "[$MTL]\\ #,##0.00");
1580 m.insert(531, "[$MUR]\\ #,##0.00");
1581 m.insert(532, "[$MVR]\\ #,##0.00");
1582 m.insert(533, "[$MWK]\\ #,##0.00");
1583 m.insert(534, "[$MXN]\\ #,##0.00");
1584 m.insert(535, "[$MXV]\\ #,##0.00");
1585 m.insert(536, "[$MYR]\\ #,##0.00");
1586 m.insert(537, "[$MZM]\\ #,##0.00");
1587 m.insert(538, "[$MZN]\\ #,##0.00");
1588 m.insert(539, "[$NAD]\\ #,##0.00");
1589 m.insert(540, "[$NGN]\\ #,##0.00");
1590 m.insert(541, "[$NIO]\\ #,##0.00");
1591 m.insert(542, "[$NLG]\\ #,##0.00");
1592 m.insert(543, "[$NOK]\\ #,##0.00");
1593 m.insert(544, "[$NPR]\\ #,##0.00");
1594 m.insert(545, "[$NTD]\\ #,##0.00");
1595 m.insert(546, "[$NZD]\\ #,##0.00");
1596 m.insert(547, "[$OMR]\\ #,##0.00");
1597 m.insert(548, "[$PAB]\\ #,##0.00");
1598 m.insert(549, "[$PEN]\\ #,##0.00");
1599 m.insert(550, "[$PGK]\\ #,##0.00");
1600 m.insert(551, "[$PHP]\\ #,##0.00");
1601 m.insert(552, "[$PKR]\\ #,##0.00");
1602 m.insert(553, "[$PLN]\\ #,##0.00");
1603 m.insert(554, "[$PTE]\\ #,##0.00");
1604 m.insert(555, "[$PYG]\\ #,##0.00");
1605 m.insert(556, "[$QAR]\\ #,##0.00");
1606 m.insert(557, "[$ROL]\\ #,##0.00");
1607 m.insert(558, "[$RON]\\ #,##0.00");
1608 m.insert(559, "[$RSD]\\ #,##0.00");
1609 m.insert(560, "[$RUB]\\ #,##0.00");
1610 m.insert(561, "[$RUR]\\ #,##0.00");
1611 m.insert(562, "[$RWF]\\ #,##0.00");
1612 m.insert(563, "[$SAR]\\ #,##0.00");
1613 m.insert(564, "[$SBD]\\ #,##0.00");
1614 m.insert(565, "[$SCR]\\ #,##0.00");
1615 m.insert(566, "[$SDD]\\ #,##0.00");
1616 m.insert(567, "[$SDG]\\ #,##0.00");
1617 m.insert(568, "[$SDP]\\ #,##0.00");
1618 m.insert(569, "[$SEK]\\ #,##0.00");
1619 m.insert(570, "[$SGD]\\ #,##0.00");
1620 m.insert(571, "[$SHP]\\ #,##0.00");
1621 m.insert(572, "[$SIT]\\ #,##0.00");
1622 m.insert(573, "[$SKK]\\ #,##0.00");
1623 m.insert(574, "[$SLL]\\ #,##0.00");
1624 m.insert(575, "[$SOS]\\ #,##0.00");
1625 m.insert(576, "[$SPL]\\ #,##0.00");
1626 m.insert(577, "[$SRD]\\ #,##0.00");
1627 m.insert(578, "[$SRG]\\ #,##0.00");
1628 m.insert(579, "[$STD]\\ #,##0.00");
1629 m.insert(580, "[$SVC]\\ #,##0.00");
1630 m.insert(581, "[$SYP]\\ #,##0.00");
1631 m.insert(582, "[$SZL]\\ #,##0.00");
1632 m.insert(583, "[$THB]\\ #,##0.00");
1633 m.insert(584, "[$TJR]\\ #,##0.00");
1634 m.insert(585, "[$TJS]\\ #,##0.00");
1635 m.insert(586, "[$TMM]\\ #,##0.00");
1636 m.insert(587, "[$TMT]\\ #,##0.00");
1637 m.insert(588, "[$TND]\\ #,##0.00");
1638 m.insert(589, "[$TOP]\\ #,##0.00");
1639 m.insert(590, "[$TRL]\\ #,##0.00");
1640 m.insert(591, "[$TRY]\\ #,##0.00");
1641 m.insert(592, "[$TTD]\\ #,##0.00");
1642 m.insert(593, "[$TWD]\\ #,##0.00");
1643 m.insert(594, "[$TZS]\\ #,##0.00");
1644 m.insert(595, "[$UAH]\\ #,##0.00");
1645 m.insert(596, "[$UGX]\\ #,##0.00");
1646 m.insert(597, "[$USD]\\ #,##0.00");
1647 m.insert(598, "[$USN]\\ #,##0.00");
1648 m.insert(599, "[$USS]\\ #,##0.00");
1649 m.insert(600, "[$UYI]\\ #,##0.00");
1650 m.insert(601, "[$UYU]\\ #,##0.00");
1651 m.insert(602, "[$UZS]\\ #,##0.00");
1652 m.insert(603, "[$VEB]\\ #,##0.00");
1653 m.insert(604, "[$VEF]\\ #,##0.00");
1654 m.insert(605, "[$VND]\\ #,##0.00");
1655 m.insert(606, "[$VUV]\\ #,##0.00");
1656 m.insert(607, "[$WST]\\ #,##0.00");
1657 m.insert(608, "[$XAF]\\ #,##0.00");
1658 m.insert(609, "[$XAG]\\ #,##0.00");
1659 m.insert(610, "[$XAU]\\ #,##0.00");
1660 m.insert(611, "[$XB5]\\ #,##0.00");
1661 m.insert(612, "[$XBA]\\ #,##0.00");
1662 m.insert(613, "[$XBB]\\ #,##0.00");
1663 m.insert(614, "[$XBC]\\ #,##0.00");
1664 m.insert(615, "[$XBD]\\ #,##0.00");
1665 m.insert(616, "[$XCD]\\ #,##0.00");
1666 m.insert(617, "[$XDR]\\ #,##0.00");
1667 m.insert(618, "[$XFO]\\ #,##0.00");
1668 m.insert(619, "[$XFU]\\ #,##0.00");
1669 m.insert(620, "[$XOF]\\ #,##0.00");
1670 m.insert(621, "[$XPD]\\ #,##0.00");
1671 m.insert(622, "[$XPF]\\ #,##0.00");
1672 m.insert(623, "[$XPT]\\ #,##0.00");
1673 m.insert(624, "[$XTS]\\ #,##0.00");
1674 m.insert(625, "[$XXX]\\ #,##0.00");
1675 m.insert(626, "[$YER]\\ #,##0.00");
1676 m.insert(627, "[$YUM]\\ #,##0.00");
1677 m.insert(628, "[$ZAR]\\ #,##0.00");
1678 m.insert(629, "[$ZMK]\\ #,##0.00");
1679 m.insert(630, "[$ZMW]\\ #,##0.00");
1680 m.insert(631, "[$ZWD]\\ #,##0.00");
1681 m.insert(632, "[$ZWL]\\ #,##0.00");
1682 m.insert(633, "[$ZWN]\\ #,##0.00");
1683 m.insert(634, "[$ZWR]\\ #,##0.00");
1684 m
1685});
1686
1687fn get_num_fmt_id(style_sheet: &XlsxStyleSheet, style: &Style) -> i32 {
1688 let num_fmt = style.num_fmt as i32;
1689 if BUILT_IN_NUM_FMT.contains_key(&num_fmt) {
1690 return num_fmt;
1691 }
1692 if (27..=36).contains(&num_fmt) || (50..=81).contains(&num_fmt) {
1693 return num_fmt;
1694 }
1695 if let Some(fmt_code) = CURRENCY_NUM_FMT.get(&num_fmt) {
1696 if let Some(ref num_fmts) = style_sheet.num_fmts {
1697 for num_fmt_entry in &num_fmts.num_fmt {
1698 if &num_fmt_entry.format_code == fmt_code {
1699 return num_fmt_entry.num_fmt_id as i32;
1700 }
1701 }
1702 }
1703 return num_fmt;
1704 }
1705 -1
1706}
1707
1708fn new_num_fmt(style_sheet: &mut XlsxStyleSheet, style: &Style) -> i32 {
1709 let mut dp = String::from("0");
1710 if let Some(places) = style.decimal_places {
1711 if places > 0 {
1712 dp.push('.');
1713 for _ in 0..places {
1714 dp.push('0');
1715 }
1716 }
1717 }
1718 if style.custom_num_fmt.is_some() {
1719 let id = get_custom_num_fmt_id(style_sheet, style);
1720 if id != -1 {
1721 return id;
1722 }
1723 return set_custom_num_fmt(style_sheet, style);
1724 }
1725 let num_fmt = style.num_fmt as i32;
1726 if BUILT_IN_NUM_FMT.contains_key(&num_fmt) {
1727 return num_fmt;
1728 }
1729 if let Some(fmt_code) = CURRENCY_NUM_FMT.get(&num_fmt).copied() {
1730 let mut fc = fmt_code.to_string();
1731 if let Some(places) = style.decimal_places {
1732 if places > 0 {
1733 fc = fc.replace("0.00", &dp);
1734 }
1735 }
1736 if style.neg_red {
1737 fc = format!("{fc};[Red]{fc}");
1738 }
1739 ensure_num_fmts(style_sheet);
1740 let num_fmts = style_sheet.num_fmts.as_mut().unwrap();
1741 let mut num_fmt_id = 164;
1742 if let Some(last) = num_fmts.num_fmt.last() {
1743 num_fmt_id = last.num_fmt_id as i32 + 1;
1744 }
1745 num_fmts.num_fmt.push(XlsxNumFmt {
1746 num_fmt_id: num_fmt_id as i64,
1747 format_code: fc,
1748 format_code_16: None,
1749 });
1750 num_fmts.count += 1;
1751 return num_fmt_id;
1752 }
1753 if is_lang_num_fmt(num_fmt) {
1754 return num_fmt;
1755 }
1756 0
1757}
1758
1759fn set_custom_num_fmt(style_sheet: &mut XlsxStyleSheet, style: &Style) -> i32 {
1760 let mut nf = XlsxNumFmt {
1761 num_fmt_id: 163,
1762 format_code: style.custom_num_fmt.as_ref().unwrap().clone(),
1763 format_code_16: None,
1764 };
1765 ensure_num_fmts(style_sheet);
1766 let num_fmts = style_sheet.num_fmts.as_mut().unwrap();
1767 for num_fmt in &num_fmts.num_fmt {
1768 if num_fmt.num_fmt_id >= nf.num_fmt_id {
1769 nf.num_fmt_id = num_fmt.num_fmt_id;
1770 }
1771 }
1772 nf.num_fmt_id += 1;
1773 let id = nf.num_fmt_id;
1774 num_fmts.num_fmt.push(nf);
1775 num_fmts.count = num_fmts.num_fmt.len() as i64;
1776 id as i32
1777}
1778
1779fn get_custom_num_fmt_id(style_sheet: &XlsxStyleSheet, style: &Style) -> i32 {
1780 if style_sheet.num_fmts.is_none() {
1781 return -1;
1782 }
1783 if let Some(ref custom) = style.custom_num_fmt {
1784 for num_fmt in &style_sheet.num_fmts.as_ref().unwrap().num_fmt {
1785 if &num_fmt.format_code == custom {
1786 return num_fmt.num_fmt_id as i32;
1787 }
1788 }
1789 }
1790 -1
1791}
1792
1793fn is_lang_num_fmt(id: i32) -> bool {
1794 (27..=36).contains(&id) || (50..=62).contains(&id) || (67..=81).contains(&id)
1795}
1796
1797fn should_extract_fill(xf: &XlsxXf, ss: &XlsxStyleSheet) -> bool {
1802 (xf.apply_fill.is_none() || xf.apply_fill.unwrap())
1803 && xf.fill_id.is_some()
1804 && ss.fills.is_some()
1805 && (*xf.fill_id.as_ref().unwrap() as usize) < ss.fills.as_ref().unwrap().fill.len()
1806}
1807
1808fn should_extract_border(xf: &XlsxXf, ss: &XlsxStyleSheet) -> bool {
1809 (xf.apply_border.is_none() || xf.apply_border.unwrap())
1810 && xf.border_id.is_some()
1811 && ss.borders.is_some()
1812 && (*xf.border_id.as_ref().unwrap() as usize) < ss.borders.as_ref().unwrap().border.len()
1813}
1814
1815fn should_extract_font(xf: &XlsxXf, ss: &XlsxStyleSheet) -> bool {
1816 (xf.apply_font.is_none() || xf.apply_font.unwrap())
1817 && xf.font_id.is_some()
1818 && ss.fonts.is_some()
1819 && (*xf.font_id.as_ref().unwrap() as usize) < ss.fonts.as_ref().unwrap().font.len()
1820}
1821
1822fn should_extract_alignment(xf: &XlsxXf, _ss: &XlsxStyleSheet) -> bool {
1823 xf.apply_alignment.is_none() || xf.apply_alignment.unwrap()
1824}
1825
1826fn should_extract_protection(xf: &XlsxXf, _ss: &XlsxStyleSheet) -> bool {
1827 xf.apply_protection.is_none() || xf.apply_protection.unwrap()
1828}
1829
1830fn extract_border(f: &File, border: &XlsxBorder, style: &mut Style) {
1831 let mut extract = |line_type: &str, line: &XlsxLine| {
1832 if line
1833 .style
1834 .as_deref()
1835 .map(|s| !s.is_empty())
1836 .unwrap_or(false)
1837 {
1838 let style_idx =
1839 index_in_static_slice(STYLE_BORDERS, line.style.as_deref().unwrap(), false);
1840 if style_idx != -1 {
1841 style.border.push(Border {
1842 r#type: line_type.to_string(),
1843 color: get_theme_color(f, line.color.as_ref()),
1844 style: style_idx as i64,
1845 });
1846 }
1847 }
1848 };
1849 let types = [
1850 "left",
1851 "right",
1852 "top",
1853 "bottom",
1854 "diagonalUp",
1855 "diagonalDown",
1856 ];
1857 let lines = [
1858 &border.left,
1859 &border.right,
1860 &border.top,
1861 &border.bottom,
1862 &border.diagonal,
1863 &border.diagonal,
1864 ];
1865 for (i, line) in lines.iter().enumerate() {
1866 if let Some(line) = line {
1867 if i < 4 {
1868 extract(types[i], line);
1869 }
1870 if i == 4 && border.diagonal_up == Some(true) {
1871 extract(types[i], line);
1872 }
1873 if i == 5 && border.diagonal_down == Some(true) {
1874 extract(types[i], line);
1875 }
1876 }
1877 }
1878}
1879
1880fn get_theme_color(f: &File, clr: Option<&XlsxColor>) -> String {
1881 let Some(c) = clr else {
1882 return String::new();
1883 };
1884 let rgb = f.get_base_color(
1885 c.rgb.as_deref().unwrap_or(""),
1886 c.indexed.unwrap_or(0),
1887 c.theme,
1888 );
1889 if rgb.is_empty() {
1890 return String::new();
1891 }
1892 let tint = c.tint.unwrap_or(0.0);
1893 if tint != 0.0 {
1894 if let Some(s) = theme_color(&rgb, tint).strip_prefix("FF") {
1895 s.to_string()
1896 } else {
1897 rgb
1898 }
1899 } else {
1900 rgb
1901 }
1902}
1903
1904fn extract_fill(f: &File, fill: &XlsxFill, style: &mut Style) {
1905 if let Some(ref pf) = fill.pattern_fill {
1909 if pf.pattern_type.is_some() || pf.fg_color.is_some() || pf.bg_color.is_some() {
1910 style.fill.r#type = "pattern".to_string();
1911 if let Some(ref pt) = pf.pattern_type {
1912 style.fill.pattern = index_in_static_slice(STYLE_FILL_PATTERNS, pt, false) as i64;
1913 }
1914 if let Some(ref bg) = pf.bg_color {
1915 if bg.auto != Some(true) {
1916 style.fill.color = vec![get_theme_color(f, Some(bg))];
1917 }
1918 }
1919 if let Some(ref fg) = pf.fg_color {
1920 if fg.auto != Some(true) {
1921 style.fill.color = vec![get_theme_color(f, Some(fg))];
1922 }
1923 }
1924 return;
1925 }
1926 }
1927 if let Some(ref gf) = fill.gradient_fill {
1928 style.fill.r#type = "gradient".to_string();
1929 for (shading, variant) in style_fill_variants().iter().enumerate() {
1930 if gf.bottom == variant.bottom
1931 && gf.degree == variant.degree
1932 && gf.left == variant.left
1933 && gf.right == variant.right
1934 && gf.top == variant.top
1935 && gf.r#type == variant.r#type
1936 {
1937 style.fill.shading = shading as i64;
1938 break;
1939 }
1940 }
1941 for stop in &gf.stop {
1942 style
1943 .fill
1944 .color
1945 .push(get_theme_color(f, stop.color.as_ref()));
1946 }
1947 }
1948}
1949
1950fn extract_font(fnt: &XlsxFont) -> Font {
1951 let mut font = Font::default();
1952 font.charset = fnt.charset.as_ref().and_then(|c| c.val);
1953 font.bold = fnt.b.as_ref().and_then(|b| b.val);
1954 font.italic = fnt.i.as_ref().and_then(|i| i.val);
1955 font.strike = fnt.strike.as_ref().and_then(|s| s.val);
1956 if let Some(ref u) = fnt.u {
1957 font.underline = u.val.clone();
1958 if font.underline.as_deref() == Some("") {
1959 font.underline = Some("single".to_string());
1960 }
1961 }
1962 font.name = fnt.name.as_ref().and_then(|n| n.val.clone());
1963 font.size = fnt.sz.as_ref().and_then(|s| s.val);
1964 if let Some(ref color) = fnt.color {
1965 font.color = color
1966 .rgb
1967 .as_ref()
1968 .map(|rgb| rgb.strip_prefix("FF").unwrap_or(rgb).to_string());
1969 font.color_indexed = color.indexed;
1970 font.color_theme = color.theme;
1971 font.color_tint = color.tint.unwrap_or(0.0);
1972 }
1973 font.vert_align = fnt.vert_align.as_ref().and_then(|v| v.val.clone());
1974 font
1975}
1976
1977fn extract_alignment(a: &XlsxAlignment) -> Alignment {
1978 Alignment {
1979 horizontal: a.horizontal.clone().unwrap_or_default(),
1980 indent: a.indent.unwrap_or(0),
1981 justify_last_line: a.justify_last_line.unwrap_or(false),
1982 reading_order: a.reading_order.unwrap_or(0),
1983 relative_indent: a.relative_indent.unwrap_or(0),
1984 shrink_to_fit: a.shrink_to_fit.unwrap_or(false),
1985 text_rotation: a.text_rotation.unwrap_or(0),
1986 vertical: a.vertical.clone().unwrap_or_default(),
1987 wrap_text: a.wrap_text.unwrap_or(false),
1988 }
1989}
1990
1991fn extract_protection(p: &XlsxProtection) -> Protection {
1992 Protection {
1993 hidden: p.hidden.unwrap_or(false),
1994 locked: p.locked.unwrap_or(false),
1995 }
1996}
1997
1998fn extract_num_fmt(num_fmt_id: i32, style_sheet: &XlsxStyleSheet, style: &mut Style) {
1999 if let Some(code) = BUILT_IN_NUM_FMT.get(&num_fmt_id) {
2000 style.num_fmt = num_fmt_id as i64;
2001 if let Some(dp) = extract_num_fmt_decimal_places(code) {
2002 style.decimal_places = Some(dp);
2003 }
2004 return;
2005 }
2006 if is_lang_num_fmt(num_fmt_id) {
2007 style.num_fmt = num_fmt_id as i64;
2008 return;
2009 }
2010 if let Some(ref num_fmts) = style_sheet.num_fmts {
2011 for num_fmt in &num_fmts.num_fmt {
2012 if num_fmt.num_fmt_id as i32 == num_fmt_id {
2013 if let Some(dp) = extract_num_fmt_decimal_places(&num_fmt.format_code) {
2014 style.decimal_places = Some(dp);
2015 }
2016 style.custom_num_fmt = Some(num_fmt.format_code.clone());
2017 if num_fmt.format_code.contains(";[Red]") {
2018 style.neg_red = true;
2019 }
2020 for (&id, fmt_code) in CURRENCY_NUM_FMT.iter() {
2021 let mut fc = fmt_code.to_string();
2022 if style.neg_red {
2023 fc = format!("{fc};[Red]{fc}");
2024 }
2025 if num_fmt.format_code == fc {
2026 style.num_fmt = id as i64;
2027 }
2028 }
2029 }
2030 }
2031 }
2032}
2033
2034fn extract_num_fmt_decimal_places(code: &str) -> Option<i64> {
2035 if let Some(pos) = code.find('.') {
2036 let rest = &code[pos + 1..];
2037 let count = rest.chars().take_while(|c| c.is_ascii_digit()).count();
2038 if count > 0 {
2039 return Some(count as i64);
2040 }
2041 }
2042 None
2043}
2044
2045fn ensure_fonts(ss: &mut XlsxStyleSheet) {
2050 if ss.fonts.is_none() {
2051 ss.fonts = Some(XlsxFonts::default());
2052 }
2053}
2054
2055fn ensure_fills(ss: &mut XlsxStyleSheet) {
2056 if ss.fills.is_none() {
2057 ss.fills = Some(XlsxFills::default());
2058 }
2059}
2060
2061fn ensure_borders(ss: &mut XlsxStyleSheet) {
2062 if ss.borders.is_none() {
2063 ss.borders = Some(XlsxBorders::default());
2064 }
2065}
2066
2067fn ensure_cell_xfs(ss: &mut XlsxStyleSheet) {
2068 if ss.cell_xfs.is_none() {
2069 ss.cell_xfs = Some(XlsxCellXfs::default());
2070 }
2071}
2072
2073fn ensure_num_fmts(ss: &mut XlsxStyleSheet) {
2074 if ss.num_fmts.is_none() {
2075 ss.num_fmts = Some(XlsxNumFmts::default());
2076 }
2077}
2078
2079fn new_dxf_num_fmt(style_sheet: &XlsxStyleSheet, style: &Style) -> Option<XlsxNumFmt> {
2080 let mut dp = String::from("0");
2081 if let Some(places) = style.decimal_places {
2082 if places > 0 {
2083 dp.push('.');
2084 for _ in 0..places {
2085 dp.push('0');
2086 }
2087 }
2088 }
2089 if let Some(ref custom) = style.custom_num_fmt {
2090 let mut num_fmt_id = 164i64;
2091 if let Some(ref dxfs) = style_sheet.dxfs {
2092 for d in &dxfs.dxfs {
2093 if let Some(ref nf) = d.num_fmt {
2094 if nf.num_fmt_id > num_fmt_id {
2095 num_fmt_id = nf.num_fmt_id;
2096 }
2097 }
2098 }
2099 }
2100 return Some(XlsxNumFmt {
2101 num_fmt_id: num_fmt_id + 1,
2102 format_code: custom.clone(),
2103 format_code_16: None,
2104 });
2105 }
2106 let num_fmt = style.num_fmt as i32;
2107 if let Some(code) = BUILT_IN_NUM_FMT.get(&num_fmt) {
2108 return Some(XlsxNumFmt {
2109 num_fmt_id: num_fmt as i64,
2110 format_code: code.to_string(),
2111 format_code_16: None,
2112 });
2113 }
2114 if let Some(fmt_code) = CURRENCY_NUM_FMT.get(&num_fmt).copied() {
2115 let mut fc = fmt_code.to_string();
2116 if let Some(places) = style.decimal_places {
2117 if places > 0 {
2118 fc = fc.replace("0.00", &dp);
2119 }
2120 }
2121 if style.neg_red {
2122 fc = format!("{fc};[Red]{fc}");
2123 }
2124 return Some(XlsxNumFmt {
2125 num_fmt_id: num_fmt as i64,
2126 format_code: fc,
2127 format_code_16: None,
2128 });
2129 }
2130 None
2131}
2132
2133#[allow(dead_code)]
2134fn ensure_cell_styles(ss: &mut XlsxStyleSheet) {
2135 if ss.cell_styles.is_none() {
2136 ss.cell_styles = Some(XlsxCellStyles::default());
2137 }
2138}
2139
2140const STYLE_BORDERS: &[&str] = &[
2145 "none",
2146 "thin",
2147 "medium",
2148 "dashed",
2149 "dotted",
2150 "thick",
2151 "double",
2152 "hair",
2153 "mediumDashed",
2154 "dashDot",
2155 "mediumDashDot",
2156 "dashDotDot",
2157 "mediumDashDotDot",
2158 "slantDashDot",
2159];
2160
2161const STYLE_FILL_PATTERNS: &[&str] = &[
2162 "none",
2163 "solid",
2164 "mediumGray",
2165 "darkGray",
2166 "lightGray",
2167 "darkHorizontal",
2168 "darkVertical",
2169 "darkDown",
2170 "darkUp",
2171 "darkGrid",
2172 "darkTrellis",
2173 "lightHorizontal",
2174 "lightVertical",
2175 "lightDown",
2176 "lightUp",
2177 "lightGrid",
2178 "lightTrellis",
2179 "gray125",
2180 "gray0625",
2181];
2182
2183fn index_in_static_slice(slice: &[&str], value: &str, case_sensitive: bool) -> i64 {
2184 for (idx, item) in slice.iter().enumerate() {
2185 let matches = if case_sensitive {
2186 *item == value
2187 } else {
2188 item.eq_ignore_ascii_case(value)
2189 };
2190 if matches {
2191 return idx as i64;
2192 }
2193 }
2194 -1
2195}
2196
2197fn style_fill_variants() -> Vec<XlsxGradientFill> {
2198 vec![
2199 XlsxGradientFill {
2200 degree: Some(90.0),
2201 stop: vec![
2202 XlsxGradientFillStop::default(),
2203 XlsxGradientFillStop {
2204 position: 1.0,
2205 color: None,
2206 },
2207 ],
2208 ..Default::default()
2209 },
2210 XlsxGradientFill {
2211 degree: Some(270.0),
2212 stop: vec![
2213 XlsxGradientFillStop::default(),
2214 XlsxGradientFillStop {
2215 position: 1.0,
2216 color: None,
2217 },
2218 ],
2219 ..Default::default()
2220 },
2221 XlsxGradientFill {
2222 degree: Some(90.0),
2223 stop: vec![
2224 XlsxGradientFillStop::default(),
2225 XlsxGradientFillStop {
2226 position: 0.5,
2227 color: None,
2228 },
2229 XlsxGradientFillStop {
2230 position: 1.0,
2231 color: None,
2232 },
2233 ],
2234 ..Default::default()
2235 },
2236 XlsxGradientFill {
2237 stop: vec![
2238 XlsxGradientFillStop::default(),
2239 XlsxGradientFillStop {
2240 position: 1.0,
2241 color: None,
2242 },
2243 ],
2244 ..Default::default()
2245 },
2246 XlsxGradientFill {
2247 degree: Some(180.0),
2248 stop: vec![
2249 XlsxGradientFillStop::default(),
2250 XlsxGradientFillStop {
2251 position: 1.0,
2252 color: None,
2253 },
2254 ],
2255 ..Default::default()
2256 },
2257 XlsxGradientFill {
2258 stop: vec![
2259 XlsxGradientFillStop::default(),
2260 XlsxGradientFillStop {
2261 position: 0.5,
2262 color: None,
2263 },
2264 XlsxGradientFillStop {
2265 position: 1.0,
2266 color: None,
2267 },
2268 ],
2269 ..Default::default()
2270 },
2271 XlsxGradientFill {
2272 degree: Some(45.0),
2273 stop: vec![
2274 XlsxGradientFillStop::default(),
2275 XlsxGradientFillStop {
2276 position: 1.0,
2277 color: None,
2278 },
2279 ],
2280 ..Default::default()
2281 },
2282 XlsxGradientFill {
2283 degree: Some(255.0),
2284 stop: vec![
2285 XlsxGradientFillStop::default(),
2286 XlsxGradientFillStop {
2287 position: 1.0,
2288 color: None,
2289 },
2290 ],
2291 ..Default::default()
2292 },
2293 XlsxGradientFill {
2294 degree: Some(45.0),
2295 stop: vec![
2296 XlsxGradientFillStop::default(),
2297 XlsxGradientFillStop {
2298 position: 0.5,
2299 color: None,
2300 },
2301 XlsxGradientFillStop {
2302 position: 1.0,
2303 color: None,
2304 },
2305 ],
2306 ..Default::default()
2307 },
2308 XlsxGradientFill {
2309 degree: Some(135.0),
2310 stop: vec![
2311 XlsxGradientFillStop::default(),
2312 XlsxGradientFillStop {
2313 position: 1.0,
2314 color: None,
2315 },
2316 ],
2317 ..Default::default()
2318 },
2319 XlsxGradientFill {
2320 degree: Some(315.0),
2321 stop: vec![
2322 XlsxGradientFillStop::default(),
2323 XlsxGradientFillStop {
2324 position: 1.0,
2325 color: None,
2326 },
2327 ],
2328 ..Default::default()
2329 },
2330 XlsxGradientFill {
2331 degree: Some(135.0),
2332 stop: vec![
2333 XlsxGradientFillStop::default(),
2334 XlsxGradientFillStop {
2335 position: 0.5,
2336 color: None,
2337 },
2338 XlsxGradientFillStop {
2339 position: 1.0,
2340 color: None,
2341 },
2342 ],
2343 ..Default::default()
2344 },
2345 XlsxGradientFill {
2346 r#type: Some("path".to_string()),
2347 stop: vec![
2348 XlsxGradientFillStop::default(),
2349 XlsxGradientFillStop {
2350 position: 1.0,
2351 color: None,
2352 },
2353 ],
2354 ..Default::default()
2355 },
2356 XlsxGradientFill {
2357 r#type: Some("path".to_string()),
2358 left: Some(1.0),
2359 right: Some(1.0),
2360 stop: vec![
2361 XlsxGradientFillStop::default(),
2362 XlsxGradientFillStop {
2363 position: 1.0,
2364 color: None,
2365 },
2366 ],
2367 ..Default::default()
2368 },
2369 XlsxGradientFill {
2370 r#type: Some("path".to_string()),
2371 bottom: Some(1.0),
2372 top: Some(1.0),
2373 stop: vec![
2374 XlsxGradientFillStop::default(),
2375 XlsxGradientFillStop {
2376 position: 1.0,
2377 color: None,
2378 },
2379 ],
2380 ..Default::default()
2381 },
2382 XlsxGradientFill {
2383 r#type: Some("path".to_string()),
2384 bottom: Some(1.0),
2385 left: Some(1.0),
2386 right: Some(1.0),
2387 top: Some(1.0),
2388 stop: vec![
2389 XlsxGradientFillStop::default(),
2390 XlsxGradientFillStop {
2391 position: 1.0,
2392 color: None,
2393 },
2394 ],
2395 ..Default::default()
2396 },
2397 XlsxGradientFill {
2398 r#type: Some("path".to_string()),
2399 bottom: Some(0.5),
2400 left: Some(0.5),
2401 right: Some(0.5),
2402 top: Some(0.5),
2403 stop: vec![
2404 XlsxGradientFillStop::default(),
2405 XlsxGradientFillStop {
2406 position: 1.0,
2407 color: None,
2408 },
2409 ],
2410 ..Default::default()
2411 },
2412 ]
2413}
2414
2415static VALID_TYPE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
2420 let mut m = HashMap::new();
2421 m.insert("cell", "cellIs");
2422 m.insert("average", "aboveAverage");
2423 m.insert("duplicate", "duplicateValues");
2424 m.insert("unique", "uniqueValues");
2425 m.insert("top", "top10");
2426 m.insert("bottom", "top10");
2427 m.insert("text", "text");
2428 m.insert("time_period", "timePeriod");
2429 m.insert("blanks", "containsBlanks");
2430 m.insert("no_blanks", "notContainsBlanks");
2431 m.insert("errors", "containsErrors");
2432 m.insert("no_errors", "notContainsErrors");
2433 m.insert("2_color_scale", "2_color_scale");
2434 m.insert("3_color_scale", "3_color_scale");
2435 m.insert("data_bar", "dataBar");
2436 m.insert("formula", "expression");
2437 m.insert("icon_set", "iconSet");
2438 m
2439});
2440
2441static CRITERIA_TYPE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
2442 let mut m = HashMap::new();
2443 m.insert("!=", "notEqual");
2444 m.insert("<", "lessThan");
2445 m.insert("<=", "lessThanOrEqual");
2446 m.insert("<>", "notEqual");
2447 m.insert("=", "equal");
2448 m.insert("==", "equal");
2449 m.insert(">", "greaterThan");
2450 m.insert(">=", "greaterThanOrEqual");
2451 m.insert("begins with", "beginsWith");
2452 m.insert("between", "between");
2453 m.insert("containing", "containsText");
2454 m.insert("continue month", "nextMonth");
2455 m.insert("continue week", "nextWeek");
2456 m.insert("ends with", "endsWith");
2457 m.insert("equal to", "equal");
2458 m.insert("greater than or equal to", "greaterThanOrEqual");
2459 m.insert("greater than", "greaterThan");
2460 m.insert("last 7 days", "last7Days");
2461 m.insert("last month", "lastMonth");
2462 m.insert("last week", "lastWeek");
2463 m.insert("less than or equal to", "lessThanOrEqual");
2464 m.insert("less than", "lessThan");
2465 m.insert("not between", "notBetween");
2466 m.insert("not containing", "notContains");
2467 m.insert("not equal to", "notEqual");
2468 m.insert("this month", "thisMonth");
2469 m.insert("this week", "thisWeek");
2470 m.insert("today", "today");
2471 m.insert("tomorrow", "tomorrow");
2472 m.insert("yesterday", "yesterday");
2473 m
2474});
2475
2476static OPERATOR_TYPE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
2477 let mut m = HashMap::new();
2478 m.insert("beginsWith", "begins with");
2479 m.insert("between", "between");
2480 m.insert("containsText", "containing");
2481 m.insert("endsWith", "ends with");
2482 m.insert("equal", "equal to");
2483 m.insert("greaterThan", "greater than");
2484 m.insert("greaterThanOrEqual", "greater than or equal to");
2485 m.insert("last7Days", "last 7 days");
2486 m.insert("lastMonth", "last month");
2487 m.insert("lastWeek", "last week");
2488 m.insert("lessThan", "less than");
2489 m.insert("lessThanOrEqual", "less than or equal to");
2490 m.insert("nextMonth", "continue month");
2491 m.insert("nextWeek", "continue week");
2492 m.insert("notBetween", "not between");
2493 m.insert("notContains", "not containing");
2494 m.insert("notEqual", "not equal to");
2495 m.insert("thisMonth", "this month");
2496 m.insert("thisWeek", "this week");
2497 m.insert("today", "today");
2498 m.insert("tomorrow", "tomorrow");
2499 m.insert("yesterday", "yesterday");
2500 m
2501});
2502
2503const CELL_IS_CRITERIA_TYPE: &[&str] = &[
2504 "equal",
2505 "notEqual",
2506 "greaterThan",
2507 "lessThan",
2508 "greaterThanOrEqual",
2509 "lessThanOrEqual",
2510 "containsText",
2511 "notContains",
2512 "beginsWith",
2513 "endsWith",
2514];
2515
2516static ICON_SET_PRESETS: LazyLock<HashMap<&'static str, XlsxCfRule>> = LazyLock::new(|| {
2517 let cfvo3 = XlsxIconSet {
2518 cfvo: vec![
2519 XlsxCfvo {
2520 r#type: Some("percent".to_string()),
2521 val: Some("0".to_string()),
2522 ..Default::default()
2523 },
2524 XlsxCfvo {
2525 r#type: Some("percent".to_string()),
2526 val: Some("33".to_string()),
2527 ..Default::default()
2528 },
2529 XlsxCfvo {
2530 r#type: Some("percent".to_string()),
2531 val: Some("67".to_string()),
2532 ..Default::default()
2533 },
2534 ],
2535 ..Default::default()
2536 };
2537 let cfvo4 = XlsxIconSet {
2538 cfvo: vec![
2539 XlsxCfvo {
2540 r#type: Some("percent".to_string()),
2541 val: Some("0".to_string()),
2542 ..Default::default()
2543 },
2544 XlsxCfvo {
2545 r#type: Some("percent".to_string()),
2546 val: Some("25".to_string()),
2547 ..Default::default()
2548 },
2549 XlsxCfvo {
2550 r#type: Some("percent".to_string()),
2551 val: Some("50".to_string()),
2552 ..Default::default()
2553 },
2554 XlsxCfvo {
2555 r#type: Some("percent".to_string()),
2556 val: Some("75".to_string()),
2557 ..Default::default()
2558 },
2559 ],
2560 ..Default::default()
2561 };
2562 let cfvo5 = XlsxIconSet {
2563 cfvo: vec![
2564 XlsxCfvo {
2565 r#type: Some("percent".to_string()),
2566 val: Some("0".to_string()),
2567 ..Default::default()
2568 },
2569 XlsxCfvo {
2570 r#type: Some("percent".to_string()),
2571 val: Some("20".to_string()),
2572 ..Default::default()
2573 },
2574 XlsxCfvo {
2575 r#type: Some("percent".to_string()),
2576 val: Some("40".to_string()),
2577 ..Default::default()
2578 },
2579 XlsxCfvo {
2580 r#type: Some("percent".to_string()),
2581 val: Some("60".to_string()),
2582 ..Default::default()
2583 },
2584 XlsxCfvo {
2585 r#type: Some("percent".to_string()),
2586 val: Some("80".to_string()),
2587 ..Default::default()
2588 },
2589 ],
2590 ..Default::default()
2591 };
2592 let mut m = HashMap::new();
2593 for name in [
2594 "3Arrows",
2595 "3ArrowsGray",
2596 "3Flags",
2597 "3Signs",
2598 "3Symbols",
2599 "3Symbols2",
2600 "3TrafficLights1",
2601 "3TrafficLights2",
2602 ] {
2603 m.insert(
2604 name,
2605 XlsxCfRule {
2606 icon_set: Some(cfvo3.clone()),
2607 ..Default::default()
2608 },
2609 );
2610 }
2611 for name in [
2612 "4Arrows",
2613 "4ArrowsGray",
2614 "4Rating",
2615 "4RedToBlack",
2616 "4TrafficLights",
2617 ] {
2618 m.insert(
2619 name,
2620 XlsxCfRule {
2621 icon_set: Some(cfvo4.clone()),
2622 ..Default::default()
2623 },
2624 );
2625 }
2626 for name in ["5Arrows", "5ArrowsGray", "5Quarters", "5Rating"] {
2627 m.insert(
2628 name,
2629 XlsxCfRule {
2630 icon_set: Some(cfvo5.clone()),
2631 ..Default::default()
2632 },
2633 );
2634 }
2635 m
2636});
2637
2638static X14_ICON_SET_PRESETS: LazyLock<HashMap<&'static str, XlsxX14CfRule>> = LazyLock::new(|| {
2639 let cfvo3 = Xlsx14IconSet {
2640 cfvo: vec![
2641 Xlsx14Cfvo {
2642 r#type: Some("percent".to_string()),
2643 f: Some("0".to_string()),
2644 ..Default::default()
2645 },
2646 Xlsx14Cfvo {
2647 r#type: Some("percent".to_string()),
2648 f: Some("33".to_string()),
2649 ..Default::default()
2650 },
2651 Xlsx14Cfvo {
2652 r#type: Some("percent".to_string()),
2653 f: Some("67".to_string()),
2654 ..Default::default()
2655 },
2656 ],
2657 ..Default::default()
2658 };
2659 let cfvo5 = Xlsx14IconSet {
2660 cfvo: vec![
2661 Xlsx14Cfvo {
2662 r#type: Some("percent".to_string()),
2663 f: Some("0".to_string()),
2664 ..Default::default()
2665 },
2666 Xlsx14Cfvo {
2667 r#type: Some("percent".to_string()),
2668 f: Some("20".to_string()),
2669 ..Default::default()
2670 },
2671 Xlsx14Cfvo {
2672 r#type: Some("percent".to_string()),
2673 f: Some("40".to_string()),
2674 ..Default::default()
2675 },
2676 Xlsx14Cfvo {
2677 r#type: Some("percent".to_string()),
2678 f: Some("60".to_string()),
2679 ..Default::default()
2680 },
2681 Xlsx14Cfvo {
2682 r#type: Some("percent".to_string()),
2683 f: Some("80".to_string()),
2684 ..Default::default()
2685 },
2686 ],
2687 ..Default::default()
2688 };
2689 let mut m = HashMap::new();
2690 m.insert(
2691 "3Stars",
2692 XlsxX14CfRule {
2693 icon_set: Some(cfvo3.clone()),
2694 ..Default::default()
2695 },
2696 );
2697 m.insert(
2698 "3Triangles",
2699 XlsxX14CfRule {
2700 icon_set: Some(cfvo3.clone()),
2701 ..Default::default()
2702 },
2703 );
2704 m.insert(
2705 "5Boxes",
2706 XlsxX14CfRule {
2707 icon_set: Some(cfvo5.clone()),
2708 ..Default::default()
2709 },
2710 );
2711 m
2712});
2713
2714static DRAW_COND_FMT_FUNC: LazyLock<
2715 HashMap<
2716 &'static str,
2717 fn(
2718 i32,
2719 &str,
2720 &str,
2721 &str,
2722 &ConditionalFormatOptions,
2723 ) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>),
2724 >,
2725> = LazyLock::new(|| {
2726 let mut m: HashMap<
2727 &'static str,
2728 fn(
2729 i32,
2730 &str,
2731 &str,
2732 &str,
2733 &ConditionalFormatOptions,
2734 ) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>),
2735 > = HashMap::new();
2736 m.insert("cellIs", draw_cond_fmt_cell_is);
2737 m.insert("timePeriod", draw_cond_fmt_time_period);
2738 m.insert("text", draw_cond_fmt_text);
2739 m.insert("top10", draw_cond_fmt_top10);
2740 m.insert("aboveAverage", draw_cond_fmt_above_average);
2741 m.insert("duplicateValues", draw_cond_fmt_duplicate_unique);
2742 m.insert("uniqueValues", draw_cond_fmt_duplicate_unique);
2743 m.insert("containsBlanks", draw_cond_fmt_blanks);
2744 m.insert("notContainsBlanks", draw_cond_fmt_no_blanks);
2745 m.insert("containsErrors", draw_cond_fmt_errors);
2746 m.insert("notContainsErrors", draw_cond_fmt_no_errors);
2747 m.insert("2_color_scale", draw_cond_fmt_color_scale);
2748 m.insert("3_color_scale", draw_cond_fmt_color_scale);
2749 m.insert("dataBar", draw_cond_fmt_data_bar);
2750 m.insert("expression", draw_cond_fmt_exp);
2751 m.insert("iconSet", draw_cond_fmt_icon_set);
2752 m
2753});
2754
2755static EXTRACT_COND_FMT_FUNC: LazyLock<
2756 HashMap<
2757 &'static str,
2758 fn(&File, &str, &XlsxCfRule, Option<&XlsxExtLst>) -> ConditionalFormatOptions,
2759 >,
2760> = LazyLock::new(|| {
2761 let mut m: HashMap<
2762 &'static str,
2763 fn(&File, &str, &XlsxCfRule, Option<&XlsxExtLst>) -> ConditionalFormatOptions,
2764 > = HashMap::new();
2765 m.insert("cellIs", extract_cond_fmt_cell_is);
2766 m.insert("timePeriod", extract_cond_fmt_time_period);
2767 m.insert("containsText", extract_cond_fmt_text);
2768 m.insert("notContainsText", extract_cond_fmt_text);
2769 m.insert("beginsWith", extract_cond_fmt_text);
2770 m.insert("endsWith", extract_cond_fmt_text);
2771 m.insert("top10", extract_cond_fmt_top10);
2772 m.insert("aboveAverage", extract_cond_fmt_above_average);
2773 m.insert("duplicateValues", extract_cond_fmt_duplicate_unique);
2774 m.insert("uniqueValues", extract_cond_fmt_duplicate_unique);
2775 m.insert("containsBlanks", extract_cond_fmt_blanks);
2776 m.insert("notContainsBlanks", extract_cond_fmt_no_blanks);
2777 m.insert("containsErrors", extract_cond_fmt_errors);
2778 m.insert("notContainsErrors", extract_cond_fmt_no_errors);
2779 m.insert("colorScale", extract_cond_fmt_color_scale);
2780 m.insert("dataBar", extract_cond_fmt_data_bar);
2781 m.insert("expression", extract_cond_fmt_exp);
2782 m.insert("iconSet", extract_cond_fmt_icon_set);
2783 m
2784});
2785
2786fn prepare_conditional_format_range(range_ref: &str) -> Result<(String, String)> {
2791 if range_ref.is_empty() {
2792 return Err(Box::new(ErrParameterRequired));
2793 }
2794 let range_ref = range_ref.replace(',', " ");
2795 let mut sqref_parts = Vec::new();
2796 let mut mast_cell = String::new();
2797 for (i, cell_range) in range_ref.split_whitespace().enumerate() {
2798 let mut cell_names = Vec::new();
2799 for (j, ref_str) in cell_range.split(':').enumerate() {
2800 if j > 1 {
2801 return Err(Box::new(ErrParameterInvalid));
2802 }
2803 let (mut col, mut row) = parse_conditional_ref(ref_str)?;
2804 if col == -1 {
2805 col = if j == 0 { 1 } else { MAX_COLUMNS };
2806 }
2807 if row == -1 {
2808 row = if j == 0 { 1 } else { TOTAL_ROWS };
2809 }
2810 let cell_name = coordinates_to_cell_name(col, row, false)?;
2811 cell_names.push(cell_name.clone());
2812 if i == 0 && j == 0 {
2813 mast_cell = cell_name;
2814 }
2815 }
2816 sqref_parts.push(cell_names.join(":"));
2817 }
2818 Ok((sqref_parts.join(" "), mast_cell))
2819}
2820
2821fn parse_conditional_ref(ref_str: &str) -> Result<(i32, i32)> {
2822 let cell_ref = ref_str.rsplit_once('!').map(|(_, r)| r).unwrap_or(ref_str);
2824 if let Ok((col, row)) = cell_name_to_coordinates(cell_ref) {
2825 return Ok((col, row));
2826 }
2827 if let Ok(col) = column_name_to_number(cell_ref) {
2828 return Ok((col, -1));
2829 }
2830 if let Ok(row) = cell_ref.parse::<i32>() {
2831 if row < 1 || row > TOTAL_ROWS {
2832 return Err(Box::new(ErrParameterInvalid));
2833 }
2834 return Ok((-1, row));
2835 }
2836 Err(Box::new(ErrParameterInvalid))
2837}
2838
2839fn append_cf_rule(ws: &mut XlsxWorksheet, rule: &XlsxX14CfRule, sqref: &str) -> Result<()> {
2844 let cond_fmt = XlsxX14ConditionalFormatting {
2845 xmlns_xm: Some(NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN.to_string()),
2846 sqref: Some(sqref.to_string()),
2847 cf_rule: vec![rule.clone()],
2848 ..Default::default()
2849 };
2850 let mut rule_xml = xml_to_string(&cond_fmt)?;
2851 if let Some(pos) = rule_xml.find("?>") {
2852 rule_xml = rule_xml[pos + 2..].to_string();
2853 }
2854
2855 let ext_lst = ws.ext_lst.get_or_insert_with(XlsxExtLst::default);
2856 let closing = "</x14:conditionalFormattings>";
2857 let mut append_mode = false;
2858 for ext in &mut ext_lst.ext {
2859 if ext.uri.as_deref() == Some(EXT_URI_CONDITIONAL_FORMATTINGS) {
2860 if ext.content.is_empty() {
2861 ext.content =
2862 format!("<x14:conditionalFormattings>{rule_xml}</x14:conditionalFormattings>");
2863 } else if let Some(pos) = ext.content.rfind(closing) {
2864 ext.content.insert_str(pos, &rule_xml);
2865 } else {
2866 ext.content.push_str(&rule_xml);
2867 }
2868 append_mode = true;
2869 break;
2870 }
2871 }
2872 if !append_mode {
2873 ext_lst.ext.push(XlsxExt {
2874 uri: Some(EXT_URI_CONDITIONAL_FORMATTINGS.to_string()),
2875 xmlns_x14: Some(NAMESPACE_SPREADSHEET_X14.to_string()),
2876 content: format!("<x14:conditionalFormattings>{rule_xml}</x14:conditionalFormattings>"),
2877 ..Default::default()
2878 });
2879 }
2880 sort_ext_lst(ext_lst);
2881 Ok(())
2882}
2883
2884fn sort_ext_lst(ext_lst: &mut XlsxExtLst) {
2885 ext_lst.ext.sort_by(|a, b| {
2886 let ai = in_str_slice(
2887 WORKSHEET_EXT_URI_PRIORITY,
2888 a.uri.as_deref().unwrap_or(""),
2889 false,
2890 );
2891 let bi = in_str_slice(
2892 WORKSHEET_EXT_URI_PRIORITY,
2893 b.uri.as_deref().unwrap_or(""),
2894 false,
2895 );
2896 ai.cmp(&bi)
2897 });
2898}
2899
2900fn parse_x14_conditional_formattings(content: &str) -> Result<DecodeX14ConditionalFormattingRules> {
2901 let stripped = content
2902 .replace("<x14:", "<")
2903 .replace("</x14:", "</")
2904 .replace("<xm:", "<")
2905 .replace("</xm:", "</");
2906 Ok(xml_from_reader(stripped.as_bytes())?)
2907}
2908
2909fn delete_x14_cf_rule(
2910 rules: &DecodeX14ConditionalFormattingRules,
2911 del_cells: &HashMap<i32, Vec<Vec<i32>>>,
2912) -> Result<String> {
2913 let mut inner = String::new();
2914 for cond_fmt in &rules.cond_fmt {
2915 let sqref = cond_fmt.sqref.as_deref().unwrap_or("").to_string();
2916 let new_sqref = delete_cells_from_sqref(&sqref, del_cells)?;
2917 if new_sqref.is_empty() {
2918 continue;
2919 }
2920 for rule in &cond_fmt.cf_rule {
2921 let x14_rule = build_x14_cf_rule(rule);
2922 let x14_cond_fmt = XlsxX14ConditionalFormatting {
2923 xmlns_xm: Some(NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN.to_string()),
2924 pivot: cond_fmt.pivot,
2925 sqref: Some(new_sqref.clone()),
2926 cf_rule: vec![x14_rule],
2927 ext_lst: cond_fmt.ext_lst.clone(),
2928 };
2929 let mut bytes = xml_to_string(&x14_cond_fmt)?;
2930 if let Some(pos) = bytes.find("?>") {
2931 bytes = bytes[pos + 2..].to_string();
2932 }
2933 inner.push_str(&bytes);
2934 }
2935 }
2936 Ok(format!(
2937 "<x14:conditionalFormattings>{inner}</x14:conditionalFormattings>"
2938 ))
2939}
2940
2941fn build_x14_cf_rule(rule: &DecodeX14CfRule) -> XlsxX14CfRule {
2942 XlsxX14CfRule {
2943 r#type: rule.r#type.clone(),
2944 priority: rule.priority,
2945 stop_if_true: rule.stop_if_true,
2946 above_average: rule.above_average,
2947 percent: rule.percent,
2948 bottom: rule.bottom,
2949 operator: rule.operator.clone(),
2950 text: rule.text.clone(),
2951 time_period: rule.time_period.clone(),
2952 rank: rule.rank,
2953 std_dev: rule.std_dev,
2954 equal_average: rule.equal_average,
2955 active_present: rule.active_present,
2956 id: rule.id.clone(),
2957 f: rule.f.clone(),
2958 color_scale: rule.color_scale.clone(),
2959 dxf: rule.dxf.clone(),
2960 ext_lst: rule.ext_lst.clone(),
2961 data_bar: rule.data_bar.as_ref().map(build_x14_data_bar),
2962 icon_set: rule.icon_set.as_ref().map(build_x14_icon_set),
2963 }
2964}
2965
2966fn build_x14_data_bar(db: &DecodeX14DataBar) -> Xlsx14DataBar {
2967 Xlsx14DataBar {
2968 max_length: db.max_length,
2969 min_length: db.min_length,
2970 border: db.border,
2971 gradient: db.gradient,
2972 show_value: db.show_value,
2973 direction: db.direction.clone(),
2974 cfvo: db.cfvo.iter().map(build_x14_cfvo).collect(),
2975 border_color: db.border_color.clone(),
2976 negative_fill_color: db.negative_fill_color.clone(),
2977 axis_color: db.axis_color.clone(),
2978 }
2979}
2980
2981fn build_x14_icon_set(icon_set: &DecodeX14IconSet) -> Xlsx14IconSet {
2982 Xlsx14IconSet {
2983 icon_set: icon_set.icon_set.clone(),
2984 show_value: icon_set.show_value,
2985 percent: icon_set.percent,
2986 reverse: icon_set.reverse,
2987 custom: icon_set.custom,
2988 cfvo: icon_set.cfvo.iter().map(build_x14_cfvo).collect(),
2989 cf_icon: icon_set.cf_icon.clone(),
2990 }
2991}
2992
2993fn build_x14_cfvo(cfvo: &DecodeX14Cfvo) -> Xlsx14Cfvo {
2994 Xlsx14Cfvo {
2995 r#type: cfvo.r#type.clone(),
2996 gte: cfvo.gte,
2997 f: cfvo.f.clone(),
2998 ext_lst: cfvo.ext_lst.clone(),
2999 }
3000}
3001
3002fn draw_cond_fmt_cell_is(
3007 p: i32,
3008 ct: &str,
3009 _ref: &str,
3010 _guid: &str,
3011 format: &ConditionalFormatOptions,
3012) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3013 let mut c = XlsxCfRule {
3014 priority: Some(p as i64 + 1),
3015 stop_if_true: Some(format.stop_if_true),
3016 r#type: Some("cellIs".to_string()),
3017 operator: Some(ct.to_string()),
3018 dxf_id: format.format,
3019 ..Default::default()
3020 };
3021 if ct == "between" || ct == "notBetween" {
3022 c.formula.push(format.min_value.clone());
3023 c.formula.push(format.max_value.clone());
3024 }
3025 if in_str_slice(CELL_IS_CRITERIA_TYPE, ct, true) != -1 {
3026 c.formula.push(format.value.clone());
3027 }
3028 (Some(c), None)
3029}
3030
3031fn draw_cond_fmt_time_period(
3032 p: i32,
3033 ct: &str,
3034 ref_str: &str,
3035 _guid: &str,
3036 format: &ConditionalFormatOptions,
3037) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3038 let formula = match ct {
3039 "yesterday" => format!("FLOOR({ref_str},1)=TODAY()-1"),
3040 "today" => format!("FLOOR({ref_str},1)=TODAY()"),
3041 "tomorrow" => format!("FLOOR({ref_str},1)=TODAY()+1"),
3042 "last7Days" => format!("AND(TODAY()-FLOOR({ref_str},1)<=6,FLOOR({ref_str},1)<=TODAY())"),
3043 "lastWeek" => format!(
3044 "AND(TODAY()-ROUNDDOWN({ref_str},0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN({ref_str},0)<(WEEKDAY(TODAY())+7))"
3045 ),
3046 "thisWeek" => format!(
3047 "AND(TODAY()-ROUNDDOWN({ref_str},0)<=WEEKDAY(TODAY())-1,ROUNDDOWN({ref_str},0)-TODAY()>=7-WEEKDAY(TODAY()))"
3048 ),
3049 "nextWeek" => format!(
3050 "AND(ROUNDDOWN({ref_str},0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN({ref_str},0)-TODAY()<(15-WEEKDAY(TODAY())))"
3051 ),
3052 "lastMonth" => format!(
3053 "AND(MONTH({ref_str})=MONTH(TODAY())-1,OR(YEAR({ref_str})=YEAR(TODAY()),AND(MONTH({ref_str})=1,YEAR({ref_str})=YEAR(TODAY())-1)))"
3054 ),
3055 "thisMonth" => {
3056 format!("AND(MONTH({ref_str})=MONTH(TODAY()),YEAR({ref_str})=YEAR(TODAY()))")
3057 }
3058 "nextMonth" => format!(
3059 "AND(MONTH({ref_str})=MONTH(TODAY())+1,OR(YEAR({ref_str})=YEAR(TODAY()),AND(MONTH({ref_str})=12,YEAR({ref_str})=YEAR(TODAY())+1)))"
3060 ),
3061 _ => String::new(),
3062 };
3063 let c = XlsxCfRule {
3064 priority: Some(p as i64 + 1),
3065 stop_if_true: Some(format.stop_if_true),
3066 r#type: Some("timePeriod".to_string()),
3067 formula: vec![formula],
3068 dxf_id: format.format,
3069 ..Default::default()
3070 };
3071 (Some(c), None)
3072}
3073
3074fn draw_cond_fmt_text(
3075 p: i32,
3076 ct: &str,
3077 ref_str: &str,
3078 _guid: &str,
3079 format: &ConditionalFormatOptions,
3080) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3081 let rule_type = match ct {
3082 "containsText" => "containsText",
3083 "notContains" => "notContainsText",
3084 "beginsWith" => "beginsWith",
3085 "endsWith" => "endsWith",
3086 _ => "",
3087 };
3088 let escaped = format.value.replace('"', "\"\"");
3089 let formula = match ct {
3090 "containsText" => format!("NOT(ISERROR(SEARCH(\"{escaped}\",{ref_str})))"),
3091 "notContains" => format!("ISERROR(SEARCH(\"{escaped}\",{ref_str}))"),
3092 "beginsWith" => format!("LEFT({ref_str},LEN(\"{escaped}\"))=\"{escaped}\""),
3093 "endsWith" => format!("RIGHT({ref_str},LEN(\"{escaped}\"))=\"{escaped}\""),
3094 _ => String::new(),
3095 };
3096 let c = XlsxCfRule {
3097 priority: Some(p as i64 + 1),
3098 stop_if_true: Some(format.stop_if_true),
3099 r#type: Some(rule_type.to_string()),
3100 operator: Some(ct.to_string()),
3101 text: Some(format.value.clone()),
3102 formula: vec![formula],
3103 dxf_id: format.format,
3104 ..Default::default()
3105 };
3106 (Some(c), None)
3107}
3108
3109fn draw_cond_fmt_top10(
3110 p: i32,
3111 _ct: &str,
3112 _ref: &str,
3113 _guid: &str,
3114 format: &ConditionalFormatOptions,
3115) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3116 let rank = format.value.parse::<i64>().unwrap_or(10);
3117 let c = XlsxCfRule {
3118 priority: Some(p as i64 + 1),
3119 stop_if_true: Some(format.stop_if_true),
3120 r#type: Some("top10".to_string()),
3121 bottom: Some(format.r#type == "bottom"),
3122 rank: Some(rank),
3123 percent: Some(format.percent),
3124 dxf_id: format.format,
3125 ..Default::default()
3126 };
3127 (Some(c), None)
3128}
3129
3130fn draw_cond_fmt_above_average(
3131 p: i32,
3132 _ct: &str,
3133 _ref: &str,
3134 _guid: &str,
3135 format: &ConditionalFormatOptions,
3136) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3137 let c = XlsxCfRule {
3138 priority: Some(p as i64 + 1),
3139 stop_if_true: Some(format.stop_if_true),
3140 r#type: Some("aboveAverage".to_string()),
3141 above_average: Some(format.above_average),
3142 dxf_id: format.format,
3143 ..Default::default()
3144 };
3145 (Some(c), None)
3146}
3147
3148fn draw_cond_fmt_duplicate_unique(
3149 p: i32,
3150 _ct: &str,
3151 _ref: &str,
3152 _guid: &str,
3153 format: &ConditionalFormatOptions,
3154) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3155 let c = XlsxCfRule {
3156 priority: Some(p as i64 + 1),
3157 stop_if_true: Some(format.stop_if_true),
3158 r#type: Some(
3159 VALID_TYPE
3160 .get(format.r#type.as_str())
3161 .copied()
3162 .unwrap_or("duplicateValues")
3163 .to_string(),
3164 ),
3165 dxf_id: format.format,
3166 ..Default::default()
3167 };
3168 (Some(c), None)
3169}
3170
3171fn draw_cond_fmt_color_scale(
3172 p: i32,
3173 _ct: &str,
3174 _ref: &str,
3175 _guid: &str,
3176 format: &ConditionalFormatOptions,
3177) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3178 let min_value = if format.min_value.is_empty() {
3179 "0"
3180 } else {
3181 &format.min_value
3182 };
3183 let max_value = if format.max_value.is_empty() {
3184 "0"
3185 } else {
3186 &format.max_value
3187 };
3188 let mid_value = if format.mid_value.is_empty() {
3189 "50"
3190 } else {
3191 &format.mid_value
3192 };
3193 let mut cfvo = vec![XlsxCfvo {
3194 r#type: Some(format.min_type.clone()),
3195 val: Some(min_value.to_string()),
3196 ..Default::default()
3197 }];
3198 let mut color = vec![XlsxColor {
3199 rgb: Some(get_palette_color(&format.min_color)),
3200 ..Default::default()
3201 }];
3202 if VALID_TYPE.get(format.r#type.as_str()).copied() == Some("3_color_scale") {
3203 cfvo.push(XlsxCfvo {
3204 r#type: Some(format.mid_type.clone()),
3205 val: Some(mid_value.to_string()),
3206 ..Default::default()
3207 });
3208 color.push(XlsxColor {
3209 rgb: Some(get_palette_color(&format.mid_color)),
3210 ..Default::default()
3211 });
3212 }
3213 cfvo.push(XlsxCfvo {
3214 r#type: Some(format.max_type.clone()),
3215 val: Some(max_value.to_string()),
3216 ..Default::default()
3217 });
3218 color.push(XlsxColor {
3219 rgb: Some(get_palette_color(&format.max_color)),
3220 ..Default::default()
3221 });
3222 let c = XlsxCfRule {
3223 priority: Some(p as i64 + 1),
3224 stop_if_true: Some(format.stop_if_true),
3225 r#type: Some("colorScale".to_string()),
3226 color_scale: Some(XlsxColorScale { cfvo, color }),
3227 ..Default::default()
3228 };
3229 (Some(c), None)
3230}
3231
3232fn draw_cond_fmt_data_bar(
3233 p: i32,
3234 _ct: &str,
3235 _ref: &str,
3236 guid: &str,
3237 format: &ConditionalFormatOptions,
3238) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3239 let mut x14_rule: Option<XlsxX14CfRule> = None;
3240 if format.bar_solid
3241 || format.bar_direction == "leftToRight"
3242 || format.bar_direction == "rightToLeft"
3243 || !format.bar_border_color.is_empty()
3244 {
3245 let mut data_bar = Xlsx14DataBar {
3246 max_length: Some(100),
3247 border: Some(!format.bar_border_color.is_empty()),
3248 gradient: Some(!format.bar_solid),
3249 direction: Some(format.bar_direction.clone()).filter(|s| !s.is_empty()),
3250 cfvo: vec![
3251 Xlsx14Cfvo {
3252 r#type: Some("autoMin".to_string()),
3253 ..Default::default()
3254 },
3255 Xlsx14Cfvo {
3256 r#type: Some("autoMax".to_string()),
3257 ..Default::default()
3258 },
3259 ],
3260 negative_fill_color: Some(XlsxColor {
3261 rgb: Some("FFFF0000".to_string()),
3262 ..Default::default()
3263 }),
3264 axis_color: Some(XlsxColor {
3265 rgb: Some("FFFF0000".to_string()),
3266 ..Default::default()
3267 }),
3268 ..Default::default()
3269 };
3270 if data_bar.border == Some(true) {
3271 data_bar.border_color = Some(XlsxColor {
3272 rgb: Some(get_palette_color(&format.bar_border_color)),
3273 ..Default::default()
3274 });
3275 }
3276 x14_rule = Some(XlsxX14CfRule {
3277 r#type: Some("dataBar".to_string()),
3278 id: Some(guid.to_string()),
3279 data_bar: Some(data_bar),
3280 ..Default::default()
3281 });
3282 }
3283 let ext_lst = x14_rule.as_ref().map(|_| XlsxExtLst {
3284 ext: vec![XlsxExt {
3285 uri: Some(EXT_URI_CONDITIONAL_FORMATTING_RULE_ID.to_string()),
3286 xmlns_x14: Some(NAMESPACE_SPREADSHEET_X14.to_string()),
3287 content: format!("<x14:id>{guid}</x14:id>"),
3288 ..Default::default()
3289 }],
3290 });
3291 let c = XlsxCfRule {
3292 priority: Some(p as i64 + 1),
3293 stop_if_true: Some(format.stop_if_true),
3294 r#type: Some("dataBar".to_string()),
3295 data_bar: Some(XlsxDataBar {
3296 show_value: Some(!format.bar_only),
3297 cfvo: vec![
3298 XlsxCfvo {
3299 r#type: Some(format.min_type.clone()).filter(|s| !s.is_empty()),
3300 val: Some(format.min_value.clone()).filter(|s| !s.is_empty()),
3301 ..Default::default()
3302 },
3303 XlsxCfvo {
3304 r#type: Some(format.max_type.clone()).filter(|s| !s.is_empty()),
3305 val: Some(format.max_value.clone()).filter(|s| !s.is_empty()),
3306 ..Default::default()
3307 },
3308 ],
3309 color: vec![XlsxColor {
3310 rgb: Some(get_palette_color(&format.bar_color)),
3311 ..Default::default()
3312 }],
3313 ..Default::default()
3314 }),
3315 ext_lst,
3316 ..Default::default()
3317 };
3318 (Some(c), x14_rule)
3319}
3320
3321fn draw_cond_fmt_exp(
3322 p: i32,
3323 _ct: &str,
3324 _ref: &str,
3325 _guid: &str,
3326 format: &ConditionalFormatOptions,
3327) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3328 let c = XlsxCfRule {
3329 priority: Some(p as i64 + 1),
3330 stop_if_true: Some(format.stop_if_true),
3331 r#type: Some("expression".to_string()),
3332 formula: vec![format.criteria.clone()],
3333 dxf_id: format.format,
3334 ..Default::default()
3335 };
3336 (Some(c), None)
3337}
3338
3339fn draw_cond_fmt_errors(
3340 p: i32,
3341 _ct: &str,
3342 ref_str: &str,
3343 _guid: &str,
3344 format: &ConditionalFormatOptions,
3345) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3346 let c = XlsxCfRule {
3347 priority: Some(p as i64 + 1),
3348 stop_if_true: Some(format.stop_if_true),
3349 r#type: Some("containsErrors".to_string()),
3350 formula: vec![format!("ISERROR({ref_str})")],
3351 dxf_id: format.format,
3352 ..Default::default()
3353 };
3354 (Some(c), None)
3355}
3356
3357fn draw_cond_fmt_no_errors(
3358 p: i32,
3359 _ct: &str,
3360 ref_str: &str,
3361 _guid: &str,
3362 format: &ConditionalFormatOptions,
3363) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3364 let c = XlsxCfRule {
3365 priority: Some(p as i64 + 1),
3366 stop_if_true: Some(format.stop_if_true),
3367 r#type: Some("notContainsErrors".to_string()),
3368 formula: vec![format!("NOT(ISERROR({ref_str}))")],
3369 dxf_id: format.format,
3370 ..Default::default()
3371 };
3372 (Some(c), None)
3373}
3374
3375fn draw_cond_fmt_blanks(
3376 p: i32,
3377 _ct: &str,
3378 ref_str: &str,
3379 _guid: &str,
3380 format: &ConditionalFormatOptions,
3381) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3382 let c = XlsxCfRule {
3383 priority: Some(p as i64 + 1),
3384 stop_if_true: Some(format.stop_if_true),
3385 r#type: Some("containsBlanks".to_string()),
3386 formula: vec![format!("LEN(TRIM({ref_str}))=0")],
3387 dxf_id: format.format,
3388 ..Default::default()
3389 };
3390 (Some(c), None)
3391}
3392
3393fn draw_cond_fmt_no_blanks(
3394 p: i32,
3395 _ct: &str,
3396 ref_str: &str,
3397 _guid: &str,
3398 format: &ConditionalFormatOptions,
3399) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3400 let c = XlsxCfRule {
3401 priority: Some(p as i64 + 1),
3402 stop_if_true: Some(format.stop_if_true),
3403 r#type: Some("notContainsBlanks".to_string()),
3404 formula: vec![format!("LEN(TRIM({ref_str}))>0")],
3405 dxf_id: format.format,
3406 ..Default::default()
3407 };
3408 (Some(c), None)
3409}
3410
3411fn draw_cond_fmt_icon_set(
3412 p: i32,
3413 _ct: &str,
3414 _ref: &str,
3415 guid: &str,
3416 format: &ConditionalFormatOptions,
3417) -> (Option<XlsxCfRule>, Option<XlsxX14CfRule>) {
3418 if let Some(preset) = ICON_SET_PRESETS.get(format.icon_style.as_str()) {
3419 let mut c = preset.clone();
3420 c.priority = Some(p as i64 + 1);
3421 c.r#type = Some("iconSet".to_string());
3422 if let Some(ref mut icon_set) = c.icon_set {
3423 icon_set.icon_set = Some(format.icon_style.clone());
3424 icon_set.reverse = Some(format.reverse_icons);
3425 icon_set.show_value = Some(!format.icons_only);
3426 }
3427 return (Some(c), None);
3428 }
3429 if let Some(preset) = X14_ICON_SET_PRESETS.get(format.icon_style.as_str()) {
3430 let mut x14 = preset.clone();
3431 x14.r#type = Some("iconSet".to_string());
3432 x14.priority = Some(p as i64 + 1);
3433 x14.stop_if_true = Some(format.stop_if_true);
3434 x14.above_average = Some(format.above_average);
3435 x14.percent = Some(format.percent);
3436 x14.id = Some(guid.to_string());
3437 if let Some(ref mut icon_set) = x14.icon_set {
3438 icon_set.icon_set = Some(format.icon_style.clone());
3439 icon_set.reverse = Some(format.reverse_icons);
3440 icon_set.show_value = Some(!format.icons_only);
3441 }
3442 return (None, Some(x14));
3443 }
3444 (None, None)
3445}
3446
3447fn extract_cond_fmt_cell_is(
3452 _f: &File,
3453 _ref: &str,
3454 c: &XlsxCfRule,
3455 _ext_lst: Option<&XlsxExtLst>,
3456) -> ConditionalFormatOptions {
3457 let mut format = ConditionalFormatOptions {
3458 format: c.dxf_id,
3459 stop_if_true: c.stop_if_true.unwrap_or(false),
3460 r#type: "cell".to_string(),
3461 criteria: OPERATOR_TYPE
3462 .get(c.operator.as_deref().unwrap_or(""))
3463 .copied()
3464 .unwrap_or("")
3465 .to_string(),
3466 ..Default::default()
3467 };
3468 if c.formula.len() == 2 {
3469 format.min_value = c.formula[0].clone();
3470 format.max_value = c.formula[1].clone();
3471 } else if !c.formula.is_empty() {
3472 format.value = c.formula[0].clone();
3473 }
3474 format
3475}
3476
3477fn time_period_criteria(ref_str: &str, formula: &str) -> Option<&'static str> {
3478 if formula == format!("FLOOR({ref_str},1)=TODAY()-1") {
3479 return Some("yesterday");
3480 }
3481 if formula == format!("FLOOR({ref_str},1)=TODAY()") {
3482 return Some("today");
3483 }
3484 if formula == format!("FLOOR({ref_str},1)=TODAY()+1") {
3485 return Some("tomorrow");
3486 }
3487 if formula == format!("AND(TODAY()-FLOOR({ref_str},1)<=6,FLOOR({ref_str},1)<=TODAY())") {
3488 return Some("last 7 days");
3489 }
3490 if formula
3491 == format!(
3492 "AND(TODAY()-ROUNDDOWN({ref_str},0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN({ref_str},0)<(WEEKDAY(TODAY())+7))"
3493 )
3494 {
3495 return Some("last week");
3496 }
3497 if formula
3498 == format!(
3499 "AND(TODAY()-ROUNDDOWN({ref_str},0)<=WEEKDAY(TODAY())-1,ROUNDDOWN({ref_str},0)-TODAY()>=7-WEEKDAY(TODAY()))"
3500 )
3501 {
3502 return Some("this week");
3503 }
3504 if formula
3505 == format!(
3506 "AND(ROUNDDOWN({ref_str},0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN({ref_str},0)-TODAY()<(15-WEEKDAY(TODAY())))"
3507 )
3508 {
3509 return Some("continue week");
3510 }
3511 if formula
3512 == format!(
3513 "AND(MONTH({ref_str})=MONTH(TODAY())-1,OR(YEAR({ref_str})=YEAR(TODAY()),AND(MONTH({ref_str})=1,YEAR({ref_str})=YEAR(TODAY())-1)))"
3514 )
3515 {
3516 return Some("last month");
3517 }
3518 if formula == format!("AND(MONTH({ref_str})=MONTH(TODAY()),YEAR({ref_str})=YEAR(TODAY()))") {
3519 return Some("this month");
3520 }
3521 if formula
3522 == format!(
3523 "AND(MONTH({ref_str})=MONTH(TODAY())+1,OR(YEAR({ref_str})=YEAR(TODAY()),AND(MONTH({ref_str})=12,YEAR({ref_str})=YEAR(TODAY())+1)))"
3524 )
3525 {
3526 return Some("continue month");
3527 }
3528 None
3529}
3530
3531fn extract_cond_fmt_time_period(
3532 _f: &File,
3533 ref_str: &str,
3534 c: &XlsxCfRule,
3535 _ext_lst: Option<&XlsxExtLst>,
3536) -> ConditionalFormatOptions {
3537 let mut criteria = String::new();
3538 for formula in &c.formula {
3539 if let Some(cr) = time_period_criteria(ref_str, formula) {
3540 criteria = cr.to_string();
3541 break;
3542 }
3543 }
3544 ConditionalFormatOptions {
3545 format: c.dxf_id,
3546 stop_if_true: c.stop_if_true.unwrap_or(false),
3547 r#type: "time_period".to_string(),
3548 criteria,
3549 ..Default::default()
3550 }
3551}
3552
3553fn extract_cond_fmt_text(
3554 _f: &File,
3555 _ref: &str,
3556 c: &XlsxCfRule,
3557 _ext_lst: Option<&XlsxExtLst>,
3558) -> ConditionalFormatOptions {
3559 ConditionalFormatOptions {
3560 format: c.dxf_id,
3561 stop_if_true: c.stop_if_true.unwrap_or(false),
3562 r#type: "text".to_string(),
3563 criteria: OPERATOR_TYPE
3564 .get(c.operator.as_deref().unwrap_or(""))
3565 .copied()
3566 .unwrap_or("")
3567 .to_string(),
3568 value: c.text.clone().unwrap_or_default(),
3569 ..Default::default()
3570 }
3571}
3572
3573fn extract_cond_fmt_top10(
3574 _f: &File,
3575 _ref: &str,
3576 c: &XlsxCfRule,
3577 _ext_lst: Option<&XlsxExtLst>,
3578) -> ConditionalFormatOptions {
3579 let mut format = ConditionalFormatOptions {
3580 format: c.dxf_id,
3581 stop_if_true: c.stop_if_true.unwrap_or(false),
3582 r#type: "top".to_string(),
3583 criteria: "=".to_string(),
3584 percent: c.percent.unwrap_or(false),
3585 value: c.rank.unwrap_or(10).to_string(),
3586 ..Default::default()
3587 };
3588 if c.bottom == Some(true) {
3589 format.r#type = "bottom".to_string();
3590 }
3591 format
3592}
3593
3594fn extract_cond_fmt_above_average(
3595 _f: &File,
3596 _ref: &str,
3597 c: &XlsxCfRule,
3598 _ext_lst: Option<&XlsxExtLst>,
3599) -> ConditionalFormatOptions {
3600 ConditionalFormatOptions {
3601 format: c.dxf_id,
3602 stop_if_true: c.stop_if_true.unwrap_or(false),
3603 r#type: "average".to_string(),
3604 criteria: "=".to_string(),
3605 above_average: c.above_average.unwrap_or(false),
3606 ..Default::default()
3607 }
3608}
3609
3610fn extract_cond_fmt_duplicate_unique(
3611 _f: &File,
3612 _ref: &str,
3613 c: &XlsxCfRule,
3614 _ext_lst: Option<&XlsxExtLst>,
3615) -> ConditionalFormatOptions {
3616 let t = match c.r#type.as_deref() {
3617 Some("duplicateValues") => "duplicate",
3618 Some("uniqueValues") => "unique",
3619 _ => "",
3620 };
3621 ConditionalFormatOptions {
3622 format: c.dxf_id,
3623 stop_if_true: c.stop_if_true.unwrap_or(false),
3624 r#type: t.to_string(),
3625 criteria: "=".to_string(),
3626 ..Default::default()
3627 }
3628}
3629
3630fn extract_cond_fmt_blanks(
3631 _f: &File,
3632 _ref: &str,
3633 c: &XlsxCfRule,
3634 _ext_lst: Option<&XlsxExtLst>,
3635) -> ConditionalFormatOptions {
3636 ConditionalFormatOptions {
3637 format: c.dxf_id,
3638 stop_if_true: c.stop_if_true.unwrap_or(false),
3639 r#type: "blanks".to_string(),
3640 ..Default::default()
3641 }
3642}
3643
3644fn extract_cond_fmt_no_blanks(
3645 _f: &File,
3646 _ref: &str,
3647 c: &XlsxCfRule,
3648 _ext_lst: Option<&XlsxExtLst>,
3649) -> ConditionalFormatOptions {
3650 ConditionalFormatOptions {
3651 format: c.dxf_id,
3652 stop_if_true: c.stop_if_true.unwrap_or(false),
3653 r#type: "no_blanks".to_string(),
3654 ..Default::default()
3655 }
3656}
3657
3658fn extract_cond_fmt_errors(
3659 _f: &File,
3660 _ref: &str,
3661 c: &XlsxCfRule,
3662 _ext_lst: Option<&XlsxExtLst>,
3663) -> ConditionalFormatOptions {
3664 ConditionalFormatOptions {
3665 format: c.dxf_id,
3666 stop_if_true: c.stop_if_true.unwrap_or(false),
3667 r#type: "errors".to_string(),
3668 ..Default::default()
3669 }
3670}
3671
3672fn extract_cond_fmt_no_errors(
3673 _f: &File,
3674 _ref: &str,
3675 c: &XlsxCfRule,
3676 _ext_lst: Option<&XlsxExtLst>,
3677) -> ConditionalFormatOptions {
3678 ConditionalFormatOptions {
3679 format: c.dxf_id,
3680 stop_if_true: c.stop_if_true.unwrap_or(false),
3681 r#type: "no_errors".to_string(),
3682 ..Default::default()
3683 }
3684}
3685
3686fn extract_cond_fmt_color_scale(
3687 f: &File,
3688 _ref: &str,
3689 c: &XlsxCfRule,
3690 _ext_lst: Option<&XlsxExtLst>,
3691) -> ConditionalFormatOptions {
3692 let mut format = ConditionalFormatOptions {
3693 stop_if_true: c.stop_if_true.unwrap_or(false),
3694 r#type: "2_color_scale".to_string(),
3695 criteria: "=".to_string(),
3696 ..Default::default()
3697 };
3698 if let Some(ref cs) = c.color_scale {
3699 let values = cs.cfvo.len();
3700 let colors = cs.color.len();
3701 if colors > 1 && values > 1 {
3702 format.min_type = cs.cfvo[0].r#type.clone().unwrap_or_default();
3703 if cs.cfvo[0].val.as_deref() != Some("0") {
3704 format.min_value = cs.cfvo[0].val.clone().unwrap_or_default();
3705 }
3706 format.min_color = theme_color_to_hex(f, Some(&cs.color[0]));
3707 format.max_type = cs.cfvo[1].r#type.clone().unwrap_or_default();
3708 if cs.cfvo[1].val.as_deref() != Some("0") {
3709 format.max_value = cs.cfvo[1].val.clone().unwrap_or_default();
3710 }
3711 format.max_color = theme_color_to_hex(f, Some(&cs.color[1]));
3712 }
3713 if colors == 3 {
3714 format.r#type = "3_color_scale".to_string();
3715 format.mid_type = cs.cfvo[1].r#type.clone().unwrap_or_default();
3716 if cs.cfvo[1].val.as_deref() != Some("0") {
3717 format.mid_value = cs.cfvo[1].val.clone().unwrap_or_default();
3718 }
3719 format.mid_color = theme_color_to_hex(f, Some(&cs.color[1]));
3720 format.max_type = cs.cfvo[2].r#type.clone().unwrap_or_default();
3721 if cs.cfvo[2].val.as_deref() != Some("0") {
3722 format.max_value = cs.cfvo[2].val.clone().unwrap_or_default();
3723 }
3724 format.max_color = theme_color_to_hex(f, Some(&cs.color[2]));
3725 }
3726 }
3727 format
3728}
3729
3730fn theme_color_to_hex(f: &File, color: Option<&XlsxColor>) -> String {
3731 let Some(color) = color else {
3732 return String::new();
3733 };
3734 let rgb = f.get_base_color(
3735 color.rgb.as_deref().unwrap_or(""),
3736 color.indexed.unwrap_or(0),
3737 color.theme,
3738 );
3739 if rgb.is_empty() {
3740 return String::new();
3741 }
3742 let tint = color.tint.unwrap_or(0.0);
3743 let hex = if tint != 0.0 {
3744 if let Some(s) = theme_color(&rgb, tint).strip_prefix("FF") {
3745 s.to_string()
3746 } else {
3747 rgb
3748 }
3749 } else {
3750 rgb
3751 };
3752 format!("#{}", hex)
3753}
3754
3755fn extract_cond_fmt_data_bar(
3756 f: &File,
3757 _ref: &str,
3758 c: &XlsxCfRule,
3759 ext_lst: Option<&XlsxExtLst>,
3760) -> ConditionalFormatOptions {
3761 let mut format = ConditionalFormatOptions {
3762 r#type: "data_bar".to_string(),
3763 criteria: "=".to_string(),
3764 stop_if_true: c.stop_if_true.unwrap_or(false),
3765 ..Default::default()
3766 };
3767 if let Some(ref db) = c.data_bar {
3768 if !db.cfvo.is_empty() {
3769 format.min_type = db.cfvo[0].r#type.clone().unwrap_or_default();
3770 format.min_value = db.cfvo[0].val.clone().unwrap_or_default();
3771 }
3772 if db.cfvo.len() > 1 {
3773 format.max_type = db.cfvo[1].r#type.clone().unwrap_or_default();
3774 format.max_value = db.cfvo[1].val.clone().unwrap_or_default();
3775 }
3776 if let Some(ref color) = db.color.first() {
3777 format.bar_color = theme_color_to_hex(f, Some(color));
3778 }
3779 if let Some(show) = db.show_value {
3780 format.bar_only = !show;
3781 }
3782 }
3783 if let Some(ref ext) = c.ext_lst {
3784 if let Some(id_ext) = ext.ext.first() {
3785 let id = x14_rule_id_from_ext(&id_ext.content);
3786 if let Some(ext_lst) = ext_lst {
3787 extract_cond_fmt_data_bar_rule(f, &id, &mut format, ext_lst);
3788 }
3789 }
3790 }
3791 format
3792}
3793
3794fn x14_rule_id_from_ext(content: &str) -> String {
3795 content
3796 .trim_start_matches("<x14:id>")
3797 .trim_end_matches("</x14:id>")
3798 .to_string()
3799}
3800
3801fn extract_cond_fmt_data_bar_rule(
3802 f: &File,
3803 id: &str,
3804 format: &mut ConditionalFormatOptions,
3805 ext_lst: &XlsxExtLst,
3806) {
3807 for ext in &ext_lst.ext {
3808 if ext.uri.as_deref() == Some(EXT_URI_CONDITIONAL_FORMATTINGS) {
3809 if let Ok(decoded) = parse_x14_conditional_formattings(&ext.content) {
3810 for cond_fmt in &decoded.cond_fmt {
3811 for rule in &cond_fmt.cf_rule {
3812 if rule.data_bar.is_some() && rule.id.as_deref() == Some(id) {
3813 if let Some(ref db) = rule.data_bar {
3814 format.bar_direction = db.direction.clone().unwrap_or_default();
3815 if let Some(gradient) = db.gradient {
3816 if !gradient {
3817 format.bar_solid = true;
3818 }
3819 }
3820 if let Some(ref bc) = db.border_color {
3821 format.bar_border_color = theme_color_to_hex(f, Some(bc));
3822 }
3823 }
3824 }
3825 }
3826 }
3827 }
3828 }
3829 }
3830}
3831
3832fn extract_cond_fmt_exp(
3833 _f: &File,
3834 _ref: &str,
3835 c: &XlsxCfRule,
3836 _ext_lst: Option<&XlsxExtLst>,
3837) -> ConditionalFormatOptions {
3838 let mut format = ConditionalFormatOptions {
3839 format: c.dxf_id,
3840 stop_if_true: c.stop_if_true.unwrap_or(false),
3841 r#type: "formula".to_string(),
3842 ..Default::default()
3843 };
3844 if !c.formula.is_empty() {
3845 format.criteria = c.formula[0].clone();
3846 }
3847 format
3848}
3849
3850fn extract_cond_fmt_icon_set(
3851 _f: &File,
3852 _ref: &str,
3853 c: &XlsxCfRule,
3854 _ext_lst: Option<&XlsxExtLst>,
3855) -> ConditionalFormatOptions {
3856 let mut format = ConditionalFormatOptions {
3857 r#type: "icon_set".to_string(),
3858 ..Default::default()
3859 };
3860 if let Some(ref icon_set) = c.icon_set {
3861 if let Some(show) = icon_set.show_value {
3862 format.icons_only = !show;
3863 }
3864 format.icon_style = icon_set.icon_set.clone().unwrap_or_default();
3865 format.reverse_icons = icon_set.reverse.unwrap_or(false);
3866 }
3867 format
3868}
3869
3870fn extract_x14_cond_fmt_icon_set(c: &DecodeX14CfRule) -> ConditionalFormatOptions {
3871 let mut format = ConditionalFormatOptions {
3872 r#type: "icon_set".to_string(),
3873 ..Default::default()
3874 };
3875 if let Some(ref icon_set) = c.icon_set {
3876 if let Some(show) = icon_set.show_value {
3877 format.icons_only = !show;
3878 }
3879 format.icon_style = icon_set.icon_set.clone().unwrap_or_default();
3880 format.reverse_icons = icon_set.reverse.unwrap_or(false);
3881 }
3882 format
3883}
3884
3885#[cfg(test)]
3890mod tests {
3891 use super::*;
3892 use crate::options::Options;
3893
3894 #[test]
3895 fn create_and_read_style() {
3896 let f = File::new_with_options(Options::default());
3897
3898 let style = Style {
3899 font: Some(Font {
3900 name: Some("Arial".to_string()),
3901 size: Some(12.0),
3902 bold: Some(true),
3903 color: Some("FF0000".to_string()),
3904 ..Default::default()
3905 }),
3906 fill: Fill {
3907 r#type: "pattern".to_string(),
3908 pattern: 1,
3909 color: vec!["FFFF00".to_string()],
3910 ..Default::default()
3911 },
3912 alignment: Some(Alignment {
3913 horizontal: "center".to_string(),
3914 vertical: "middle".to_string(),
3915 ..Default::default()
3916 }),
3917 ..Default::default()
3918 };
3919
3920 let id = f.new_style(&style).unwrap();
3921 assert!(id > 0);
3922
3923 let read = f.get_style(id).unwrap();
3924 assert_eq!(read.font.as_ref().unwrap().name, Some("Arial".to_string()));
3925 assert_eq!(read.font.as_ref().unwrap().size, Some(12.0));
3926 assert_eq!(read.font.as_ref().unwrap().bold, Some(true));
3927 assert_eq!(
3928 read.font.as_ref().unwrap().color,
3929 Some("FF0000".to_string())
3930 );
3931 assert_eq!(read.fill.r#type, "pattern");
3932 assert_eq!(read.fill.pattern, 1);
3933 assert_eq!(read.fill.color, vec!["FFFF00".to_string()]);
3934 assert_eq!(read.alignment.as_ref().unwrap().horizontal, "center");
3935 assert_eq!(read.alignment.as_ref().unwrap().vertical, "middle");
3936 }
3937
3938 #[test]
3939 fn font_color_rgb_strip_alpha() {
3940 let fnt = XlsxFont {
3942 color: Some(XlsxColor {
3943 rgb: Some("FFFF0000".to_string()),
3944 ..Default::default()
3945 }),
3946 ..Default::default()
3947 };
3948 assert_eq!(extract_font(&fnt).color, Some("FF0000".to_string()));
3949
3950 let fnt6 = XlsxFont {
3952 color: Some(XlsxColor {
3953 rgb: Some("FF0000".to_string()),
3954 ..Default::default()
3955 }),
3956 ..Default::default()
3957 };
3958 assert_eq!(extract_font(&fnt6).color, Some("0000".to_string()));
3959
3960 let fnt_green = XlsxFont {
3962 color: Some(XlsxColor {
3963 rgb: Some("00FF00".to_string()),
3964 ..Default::default()
3965 }),
3966 ..Default::default()
3967 };
3968 assert_eq!(extract_font(&fnt_green).color, Some("00FF00".to_string()));
3969 }
3970
3971 #[test]
3972 fn font_color_indexed_only_when_set() {
3973 let font = Font {
3974 color: Some("FF0000".to_string()),
3975 ..Default::default()
3976 };
3977 let color = new_font_color(&font).unwrap();
3978 assert!(color.indexed.is_none());
3979
3980 let font_indexed = Font {
3981 color: Some("FF0000".to_string()),
3982 color_indexed: Some(0),
3983 ..Default::default()
3984 };
3985 let color_indexed = new_font_color(&font_indexed).unwrap();
3986 assert_eq!(color_indexed.indexed, Some(0));
3987 }
3988
3989 #[test]
3990 fn default_font_round_trip() {
3991 let f = File::new_with_options(Options::default());
3992 assert_eq!(f.get_default_font().unwrap(), "Calibri");
3993 f.set_default_font("Arial").unwrap();
3994 assert_eq!(f.get_default_font().unwrap(), "Arial");
3995 }
3996
3997 #[test]
3998 fn set_and_get_conditional_format_data_bar() {
3999 let f = File::new_with_options(Options::default());
4000 f.set_conditional_format(
4001 "Sheet1",
4002 "A1:A10",
4003 &[ConditionalFormatOptions {
4004 r#type: "data_bar".to_string(),
4005 criteria: "=".to_string(),
4006 min_type: "min".to_string(),
4007 max_type: "max".to_string(),
4008 bar_color: "#638EC6".to_string(),
4009 bar_solid: true,
4010 bar_direction: "leftToRight".to_string(),
4011 bar_border_color: "#000000".to_string(),
4012 ..Default::default()
4013 }],
4014 )
4015 .unwrap();
4016
4017 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4018 let rules = cfs.get("A1:A10").unwrap();
4019 eprintln!("rules[0] = {:?}", rules[0]);
4020 assert_eq!(rules[0].r#type, "data_bar");
4021 assert_eq!(rules[0].bar_color, "#638EC6");
4022 assert!(rules[0].bar_solid, "bar_solid should be true");
4023 assert_eq!(rules[0].bar_direction, "leftToRight");
4024 assert_eq!(rules[0].bar_border_color, "#000000");
4025 }
4026
4027 #[test]
4028 fn conditional_format_round_trip() {
4029 let tmp = std::env::temp_dir().join("excelize_rust_conditional_format_round_trip.xlsx");
4030 let _ = std::fs::remove_file(&tmp);
4031
4032 let mut f = File::new_with_options(Options::default());
4033 f.set_conditional_format(
4034 "Sheet1",
4035 "A1:A10",
4036 &[ConditionalFormatOptions {
4037 r#type: "duplicate".to_string(),
4038 criteria: "=".to_string(),
4039 ..Default::default()
4040 }],
4041 )
4042 .unwrap();
4043
4044 f.save_as(tmp.to_str().unwrap()).unwrap();
4045 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
4046 let cfs = f2.get_conditional_formats("Sheet1").unwrap();
4047 assert_eq!(cfs.len(), 1);
4048 let rules = cfs.get("A1:A10").unwrap();
4049 assert_eq!(rules[0].r#type, "duplicate");
4050 assert_eq!(rules[0].criteria, "=");
4051 let _ = std::fs::remove_file(&tmp);
4052 }
4053
4054 #[test]
4055 fn conditional_style_round_trip() {
4056 let f = File::new_with_options(Options::default());
4057 let style = Style {
4058 font: Some(Font {
4059 color: Some("9A0511".to_string()),
4060 ..Default::default()
4061 }),
4062 fill: Fill {
4063 r#type: "pattern".to_string(),
4064 pattern: 1,
4065 color: vec!["FEC7CE".to_string()],
4066 ..Default::default()
4067 },
4068 ..Default::default()
4069 };
4070 let id = f.new_conditional_style(&style).unwrap();
4071 let read = f.get_conditional_style(id).unwrap();
4072 assert_eq!(read.fill.r#type, "pattern");
4073 assert_eq!(read.fill.pattern, 1);
4074 assert_eq!(read.fill.color, vec!["FEC7CE".to_string()]);
4075 assert_eq!(
4076 read.font.as_ref().unwrap().color,
4077 Some("9A0511".to_string())
4078 );
4079 }
4080
4081 #[test]
4082 fn set_and_get_conditional_format_cell() {
4083 let f = File::new_with_options(Options::default());
4084 let style = Style {
4085 font: Some(Font {
4086 color: Some("9A0511".to_string()),
4087 ..Default::default()
4088 }),
4089 fill: Fill {
4090 r#type: "pattern".to_string(),
4091 pattern: 1,
4092 color: vec!["FEC7CE".to_string()],
4093 ..Default::default()
4094 },
4095 ..Default::default()
4096 };
4097 let format = f.new_conditional_style(&style).unwrap();
4098 f.set_conditional_format(
4099 "Sheet1",
4100 "A1:A10",
4101 &[ConditionalFormatOptions {
4102 r#type: "cell".to_string(),
4103 criteria: ">".to_string(),
4104 value: "5".to_string(),
4105 format: Some(format as i64),
4106 ..Default::default()
4107 }],
4108 )
4109 .unwrap();
4110
4111 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4112 assert!(cfs.contains_key("A1:A10"));
4113 let rules = cfs.get("A1:A10").unwrap();
4114 assert_eq!(rules.len(), 1);
4115 assert_eq!(rules[0].r#type, "cell");
4116 assert_eq!(rules[0].criteria, "greater than");
4117 assert_eq!(rules[0].value, "5");
4118 assert_eq!(rules[0].format, Some(format as i64));
4119 }
4120
4121 #[test]
4122 fn set_and_get_conditional_format_color_scale() {
4123 let f = File::new_with_options(Options::default());
4124 f.set_conditional_format(
4125 "Sheet1",
4126 "A1:A10",
4127 &[ConditionalFormatOptions {
4128 r#type: "2_color_scale".to_string(),
4129 criteria: "=".to_string(),
4130 min_type: "min".to_string(),
4131 max_type: "max".to_string(),
4132 min_color: "#F8696B".to_string(),
4133 max_color: "#63BE7B".to_string(),
4134 ..Default::default()
4135 }],
4136 )
4137 .unwrap();
4138
4139 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4140 let rules = cfs.get("A1:A10").unwrap();
4141 assert_eq!(rules[0].r#type, "2_color_scale");
4142 assert_eq!(rules[0].min_type, "min");
4143 assert_eq!(rules[0].max_type, "max");
4144 assert_eq!(rules[0].min_color, "#F8696B");
4145 assert_eq!(rules[0].max_color, "#63BE7B");
4146 }
4147
4148 #[test]
4149 fn unset_conditional_format() {
4150 let f = File::new_with_options(Options::default());
4151 f.set_conditional_format(
4152 "Sheet1",
4153 "A1:A10",
4154 &[ConditionalFormatOptions {
4155 r#type: "duplicate".to_string(),
4156 criteria: "=".to_string(),
4157 ..Default::default()
4158 }],
4159 )
4160 .unwrap();
4161 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4162 assert_eq!(cfs.len(), 1);
4163
4164 f.unset_conditional_format("Sheet1", "A1:A5").unwrap();
4165 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4166 assert_eq!(cfs.len(), 1);
4167
4168 f.unset_conditional_format("Sheet1", "A6:A10").unwrap();
4169 let cfs = f.get_conditional_formats("Sheet1").unwrap();
4170 assert!(cfs.is_empty());
4171 }
4172
4173 #[test]
4174 fn get_base_color_basic() {
4175 let f = File::new_with_options(Options::default());
4176 assert_eq!(f.get_base_color("FF0000", 0, None), "FF0000");
4177 assert_eq!(f.get_base_color("FFFF0000", 0, None), "FF0000");
4178 assert_eq!(f.get_base_color("", 3, None), INDEXED_COLOR_MAPPING[3]);
4179 assert_eq!(f.get_base_color("unknown", 99, None), "unknown");
4180 }
4181}