1use serde::{Deserialize, Serialize};
7
8use crate::namespaces;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12#[serde(rename = "styleSheet")]
13pub struct StyleSheet {
14 #[serde(rename = "@xmlns")]
15 pub xmlns: String,
16
17 #[serde(rename = "numFmts", skip_serializing_if = "Option::is_none")]
18 pub num_fmts: Option<NumFmts>,
19
20 #[serde(rename = "fonts")]
21 pub fonts: Fonts,
22
23 #[serde(rename = "fills")]
24 pub fills: Fills,
25
26 #[serde(rename = "borders")]
27 pub borders: Borders,
28
29 #[serde(rename = "cellStyleXfs", skip_serializing_if = "Option::is_none")]
30 pub cell_style_xfs: Option<CellStyleXfs>,
31
32 #[serde(rename = "cellXfs")]
33 pub cell_xfs: CellXfs,
34
35 #[serde(rename = "cellStyles", skip_serializing_if = "Option::is_none")]
36 pub cell_styles: Option<CellStyles>,
37
38 #[serde(rename = "dxfs", skip_serializing_if = "Option::is_none")]
39 pub dxfs: Option<Dxfs>,
40
41 #[serde(rename = "tableStyles", skip_serializing_if = "Option::is_none")]
42 pub table_styles: Option<TableStyles>,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct NumFmts {
48 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
49 pub count: Option<u32>,
50
51 #[serde(rename = "numFmt", default)]
52 pub num_fmts: Vec<NumFmt>,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct NumFmt {
58 #[serde(rename = "@numFmtId")]
59 pub num_fmt_id: u32,
60
61 #[serde(rename = "@formatCode")]
62 pub format_code: String,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct Fonts {
68 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
69 pub count: Option<u32>,
70
71 #[serde(rename = "font", default)]
72 pub fonts: Vec<Font>,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct Font {
78 #[serde(rename = "b", skip_serializing_if = "Option::is_none")]
79 pub b: Option<BoolVal>,
80
81 #[serde(rename = "i", skip_serializing_if = "Option::is_none")]
82 pub i: Option<BoolVal>,
83
84 #[serde(rename = "strike", skip_serializing_if = "Option::is_none")]
85 pub strike: Option<BoolVal>,
86
87 #[serde(rename = "u", skip_serializing_if = "Option::is_none")]
88 pub u: Option<Underline>,
89
90 #[serde(rename = "sz", skip_serializing_if = "Option::is_none")]
91 pub sz: Option<FontSize>,
92
93 #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
94 pub color: Option<Color>,
95
96 #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
97 pub name: Option<FontName>,
98
99 #[serde(rename = "family", skip_serializing_if = "Option::is_none")]
100 pub family: Option<FontFamily>,
101
102 #[serde(rename = "scheme", skip_serializing_if = "Option::is_none")]
103 pub scheme: Option<FontScheme>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct Fills {
109 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
110 pub count: Option<u32>,
111
112 #[serde(rename = "fill", default)]
113 pub fills: Vec<Fill>,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct Fill {
119 #[serde(rename = "patternFill", skip_serializing_if = "Option::is_none")]
120 pub pattern_fill: Option<PatternFill>,
121
122 #[serde(rename = "gradientFill", skip_serializing_if = "Option::is_none")]
123 pub gradient_fill: Option<GradientFill>,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct PatternFill {
129 #[serde(rename = "@patternType", skip_serializing_if = "Option::is_none")]
130 pub pattern_type: Option<String>,
131
132 #[serde(rename = "fgColor", skip_serializing_if = "Option::is_none")]
133 pub fg_color: Option<Color>,
134
135 #[serde(rename = "bgColor", skip_serializing_if = "Option::is_none")]
136 pub bg_color: Option<Color>,
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct GradientFill {
142 #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
144 pub gradient_type: Option<String>,
145
146 #[serde(rename = "@degree", skip_serializing_if = "Option::is_none")]
148 pub degree: Option<f64>,
149
150 #[serde(rename = "@left", skip_serializing_if = "Option::is_none")]
152 pub left: Option<f64>,
153
154 #[serde(rename = "@right", skip_serializing_if = "Option::is_none")]
156 pub right: Option<f64>,
157
158 #[serde(rename = "@top", skip_serializing_if = "Option::is_none")]
160 pub top: Option<f64>,
161
162 #[serde(rename = "@bottom", skip_serializing_if = "Option::is_none")]
164 pub bottom: Option<f64>,
165
166 #[serde(rename = "stop", default)]
168 pub stops: Vec<GradientStop>,
169}
170
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct GradientStop {
174 #[serde(rename = "@position")]
176 pub position: f64,
177
178 pub color: Color,
180}
181
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct Borders {
185 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
186 pub count: Option<u32>,
187
188 #[serde(rename = "border", default)]
189 pub borders: Vec<Border>,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct Border {
195 #[serde(rename = "@diagonalUp", skip_serializing_if = "Option::is_none")]
196 pub diagonal_up: Option<bool>,
197
198 #[serde(rename = "@diagonalDown", skip_serializing_if = "Option::is_none")]
199 pub diagonal_down: Option<bool>,
200
201 #[serde(rename = "left", skip_serializing_if = "Option::is_none")]
202 pub left: Option<BorderSide>,
203
204 #[serde(rename = "right", skip_serializing_if = "Option::is_none")]
205 pub right: Option<BorderSide>,
206
207 #[serde(rename = "top", skip_serializing_if = "Option::is_none")]
208 pub top: Option<BorderSide>,
209
210 #[serde(rename = "bottom", skip_serializing_if = "Option::is_none")]
211 pub bottom: Option<BorderSide>,
212
213 #[serde(rename = "diagonal", skip_serializing_if = "Option::is_none")]
214 pub diagonal: Option<BorderSide>,
215}
216
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219pub struct BorderSide {
220 #[serde(rename = "@style", skip_serializing_if = "Option::is_none")]
221 pub style: Option<String>,
222
223 #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
224 pub color: Option<Color>,
225}
226
227#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct CellStyleXfs {
230 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
231 pub count: Option<u32>,
232
233 #[serde(rename = "xf", default)]
234 pub xfs: Vec<Xf>,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239pub struct CellXfs {
240 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
241 pub count: Option<u32>,
242
243 #[serde(rename = "xf", default)]
244 pub xfs: Vec<Xf>,
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct Xf {
250 #[serde(rename = "@numFmtId", skip_serializing_if = "Option::is_none")]
251 pub num_fmt_id: Option<u32>,
252
253 #[serde(rename = "@fontId", skip_serializing_if = "Option::is_none")]
254 pub font_id: Option<u32>,
255
256 #[serde(rename = "@fillId", skip_serializing_if = "Option::is_none")]
257 pub fill_id: Option<u32>,
258
259 #[serde(rename = "@borderId", skip_serializing_if = "Option::is_none")]
260 pub border_id: Option<u32>,
261
262 #[serde(rename = "@xfId", skip_serializing_if = "Option::is_none")]
263 pub xf_id: Option<u32>,
264
265 #[serde(rename = "@applyNumberFormat", skip_serializing_if = "Option::is_none")]
266 pub apply_number_format: Option<bool>,
267
268 #[serde(rename = "@applyFont", skip_serializing_if = "Option::is_none")]
269 pub apply_font: Option<bool>,
270
271 #[serde(rename = "@applyFill", skip_serializing_if = "Option::is_none")]
272 pub apply_fill: Option<bool>,
273
274 #[serde(rename = "@applyBorder", skip_serializing_if = "Option::is_none")]
275 pub apply_border: Option<bool>,
276
277 #[serde(rename = "@applyAlignment", skip_serializing_if = "Option::is_none")]
278 pub apply_alignment: Option<bool>,
279
280 #[serde(rename = "alignment", skip_serializing_if = "Option::is_none")]
281 pub alignment: Option<Alignment>,
282
283 #[serde(rename = "protection", skip_serializing_if = "Option::is_none")]
284 pub protection: Option<Protection>,
285}
286
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct Alignment {
290 #[serde(rename = "@horizontal", skip_serializing_if = "Option::is_none")]
291 pub horizontal: Option<String>,
292
293 #[serde(rename = "@vertical", skip_serializing_if = "Option::is_none")]
294 pub vertical: Option<String>,
295
296 #[serde(rename = "@wrapText", skip_serializing_if = "Option::is_none")]
297 pub wrap_text: Option<bool>,
298
299 #[serde(rename = "@textRotation", skip_serializing_if = "Option::is_none")]
300 pub text_rotation: Option<u32>,
301
302 #[serde(rename = "@indent", skip_serializing_if = "Option::is_none")]
303 pub indent: Option<u32>,
304
305 #[serde(rename = "@shrinkToFit", skip_serializing_if = "Option::is_none")]
306 pub shrink_to_fit: Option<bool>,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub struct Protection {
312 #[serde(rename = "@locked", skip_serializing_if = "Option::is_none")]
313 pub locked: Option<bool>,
314
315 #[serde(rename = "@hidden", skip_serializing_if = "Option::is_none")]
316 pub hidden: Option<bool>,
317}
318
319#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
321pub struct CellStyles {
322 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
323 pub count: Option<u32>,
324
325 #[serde(rename = "cellStyle", default)]
326 pub cell_styles: Vec<CellStyle>,
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub struct CellStyle {
332 #[serde(rename = "@name")]
333 pub name: String,
334
335 #[serde(rename = "@xfId")]
336 pub xf_id: u32,
337
338 #[serde(rename = "@builtinId", skip_serializing_if = "Option::is_none")]
339 pub builtin_id: Option<u32>,
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344pub struct Dxfs {
345 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
346 pub count: Option<u32>,
347
348 #[serde(rename = "dxf", default)]
349 pub dxfs: Vec<Dxf>,
350}
351
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
354pub struct Dxf {
355 #[serde(rename = "font", skip_serializing_if = "Option::is_none")]
356 pub font: Option<Font>,
357
358 #[serde(rename = "numFmt", skip_serializing_if = "Option::is_none")]
359 pub num_fmt: Option<NumFmt>,
360
361 #[serde(rename = "fill", skip_serializing_if = "Option::is_none")]
362 pub fill: Option<Fill>,
363
364 #[serde(rename = "border", skip_serializing_if = "Option::is_none")]
365 pub border: Option<Border>,
366}
367
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
370pub struct TableStyles {
371 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
372 pub count: Option<u32>,
373
374 #[serde(rename = "@defaultTableStyle", skip_serializing_if = "Option::is_none")]
375 pub default_table_style: Option<String>,
376
377 #[serde(rename = "@defaultPivotStyle", skip_serializing_if = "Option::is_none")]
378 pub default_pivot_style: Option<String>,
379}
380
381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
383pub struct Color {
384 #[serde(rename = "@auto", skip_serializing_if = "Option::is_none")]
385 pub auto: Option<bool>,
386
387 #[serde(rename = "@indexed", skip_serializing_if = "Option::is_none")]
388 pub indexed: Option<u32>,
389
390 #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
391 pub rgb: Option<String>,
392
393 #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
394 pub theme: Option<u32>,
395
396 #[serde(rename = "@tint", skip_serializing_if = "Option::is_none")]
397 pub tint: Option<f64>,
398}
399
400#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub struct BoolVal {
403 #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
404 pub val: Option<bool>,
405}
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct Underline {
410 #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
411 pub val: Option<String>,
412}
413
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
416pub struct FontSize {
417 #[serde(rename = "@val")]
418 pub val: f64,
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
423pub struct FontName {
424 #[serde(rename = "@val")]
425 pub val: String,
426}
427
428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct FontFamily {
431 #[serde(rename = "@val")]
432 pub val: u32,
433}
434
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
437pub struct FontScheme {
438 #[serde(rename = "@val")]
439 pub val: String,
440}
441
442impl Default for StyleSheet {
443 fn default() -> Self {
453 Self {
454 xmlns: namespaces::SPREADSHEET_ML.to_string(),
455 num_fmts: None,
456 fonts: Fonts {
457 count: Some(1),
458 fonts: vec![Font {
459 b: None,
460 i: None,
461 strike: None,
462 u: None,
463 sz: Some(FontSize { val: 11.0 }),
464 color: Some(Color {
465 auto: None,
466 indexed: None,
467 rgb: None,
468 theme: Some(1),
469 tint: None,
470 }),
471 name: Some(FontName {
472 val: "Calibri".to_string(),
473 }),
474 family: Some(FontFamily { val: 2 }),
475 scheme: Some(FontScheme {
476 val: "minor".to_string(),
477 }),
478 }],
479 },
480 fills: Fills {
481 count: Some(2),
482 fills: vec![
483 Fill {
484 pattern_fill: Some(PatternFill {
485 pattern_type: Some("none".to_string()),
486 fg_color: None,
487 bg_color: None,
488 }),
489 gradient_fill: None,
490 },
491 Fill {
492 pattern_fill: Some(PatternFill {
493 pattern_type: Some("gray125".to_string()),
494 fg_color: None,
495 bg_color: None,
496 }),
497 gradient_fill: None,
498 },
499 ],
500 },
501 borders: Borders {
502 count: Some(1),
503 borders: vec![Border {
504 diagonal_up: None,
505 diagonal_down: None,
506 left: Some(BorderSide {
507 style: None,
508 color: None,
509 }),
510 right: Some(BorderSide {
511 style: None,
512 color: None,
513 }),
514 top: Some(BorderSide {
515 style: None,
516 color: None,
517 }),
518 bottom: Some(BorderSide {
519 style: None,
520 color: None,
521 }),
522 diagonal: Some(BorderSide {
523 style: None,
524 color: None,
525 }),
526 }],
527 },
528 cell_style_xfs: Some(CellStyleXfs {
529 count: Some(1),
530 xfs: vec![Xf {
531 num_fmt_id: Some(0),
532 font_id: Some(0),
533 fill_id: Some(0),
534 border_id: Some(0),
535 xf_id: None,
536 apply_number_format: None,
537 apply_font: None,
538 apply_fill: None,
539 apply_border: None,
540 apply_alignment: None,
541 alignment: None,
542 protection: None,
543 }],
544 }),
545 cell_xfs: CellXfs {
546 count: Some(1),
547 xfs: vec![Xf {
548 num_fmt_id: Some(0),
549 font_id: Some(0),
550 fill_id: Some(0),
551 border_id: Some(0),
552 xf_id: Some(0),
553 apply_number_format: None,
554 apply_font: None,
555 apply_fill: None,
556 apply_border: None,
557 apply_alignment: None,
558 alignment: None,
559 protection: None,
560 }],
561 },
562 cell_styles: Some(CellStyles {
563 count: Some(1),
564 cell_styles: vec![CellStyle {
565 name: "Normal".to_string(),
566 xf_id: 0,
567 builtin_id: Some(0),
568 }],
569 }),
570 dxfs: None,
571 table_styles: None,
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_stylesheet_default() {
582 let ss = StyleSheet::default();
583 assert_eq!(ss.xmlns, namespaces::SPREADSHEET_ML);
584
585 assert_eq!(ss.fonts.fonts.len(), 1);
587 assert_eq!(ss.fonts.count, Some(1));
588 let font = &ss.fonts.fonts[0];
589 assert_eq!(font.sz.as_ref().unwrap().val, 11.0);
590 assert_eq!(font.name.as_ref().unwrap().val, "Calibri");
591 assert_eq!(font.family.as_ref().unwrap().val, 2);
592 assert_eq!(font.scheme.as_ref().unwrap().val, "minor");
593
594 assert_eq!(ss.fills.fills.len(), 2);
596 assert_eq!(ss.fills.count, Some(2));
597 assert_eq!(
598 ss.fills.fills[0]
599 .pattern_fill
600 .as_ref()
601 .unwrap()
602 .pattern_type,
603 Some("none".to_string())
604 );
605 assert_eq!(
606 ss.fills.fills[1]
607 .pattern_fill
608 .as_ref()
609 .unwrap()
610 .pattern_type,
611 Some("gray125".to_string())
612 );
613
614 assert_eq!(ss.borders.borders.len(), 1);
616 assert_eq!(ss.borders.count, Some(1));
617
618 assert!(ss.cell_style_xfs.is_some());
620 assert_eq!(ss.cell_style_xfs.as_ref().unwrap().xfs.len(), 1);
621
622 assert_eq!(ss.cell_xfs.xfs.len(), 1);
624 assert_eq!(ss.cell_xfs.count, Some(1));
625 let xf = &ss.cell_xfs.xfs[0];
626 assert_eq!(xf.num_fmt_id, Some(0));
627 assert_eq!(xf.font_id, Some(0));
628 assert_eq!(xf.fill_id, Some(0));
629 assert_eq!(xf.border_id, Some(0));
630
631 assert!(ss.cell_styles.is_some());
633 let styles = ss.cell_styles.as_ref().unwrap();
634 assert_eq!(styles.cell_styles.len(), 1);
635 assert_eq!(styles.cell_styles[0].name, "Normal");
636 assert_eq!(styles.cell_styles[0].xf_id, 0);
637 assert_eq!(styles.cell_styles[0].builtin_id, Some(0));
638 }
639
640 #[test]
641 fn test_stylesheet_roundtrip() {
642 let ss = StyleSheet::default();
643 let xml = quick_xml::se::to_string(&ss).unwrap();
644 let parsed: StyleSheet = quick_xml::de::from_str(&xml).unwrap();
645
646 assert_eq!(ss.xmlns, parsed.xmlns);
647 assert_eq!(ss.fonts.fonts.len(), parsed.fonts.fonts.len());
648 assert_eq!(ss.fills.fills.len(), parsed.fills.fills.len());
649 assert_eq!(ss.borders.borders.len(), parsed.borders.borders.len());
650 assert_eq!(ss.cell_xfs.xfs.len(), parsed.cell_xfs.xfs.len());
651 }
652
653 #[test]
654 fn test_stylesheet_serialize_structure() {
655 let ss = StyleSheet::default();
656 let xml = quick_xml::se::to_string(&ss).unwrap();
657 assert!(xml.contains("<styleSheet"));
658 assert!(xml.contains("<fonts"));
659 assert!(xml.contains("<font>"));
660 assert!(xml.contains("<fills"));
661 assert!(xml.contains("<fill>"));
662 assert!(xml.contains("<borders"));
663 assert!(xml.contains("<border>"));
664 assert!(xml.contains("<cellXfs"));
665 assert!(xml.contains("<xf "));
666 }
667
668 #[test]
669 fn test_parse_real_excel_styles_minimal() {
670 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
671<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
672 <fonts count="1">
673 <font>
674 <sz val="11"/>
675 <color theme="1"/>
676 <name val="Calibri"/>
677 <family val="2"/>
678 <scheme val="minor"/>
679 </font>
680 </fonts>
681 <fills count="2">
682 <fill><patternFill patternType="none"/></fill>
683 <fill><patternFill patternType="gray125"/></fill>
684 </fills>
685 <borders count="1">
686 <border>
687 <left/>
688 <right/>
689 <top/>
690 <bottom/>
691 <diagonal/>
692 </border>
693 </borders>
694 <cellStyleXfs count="1">
695 <xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
696 </cellStyleXfs>
697 <cellXfs count="1">
698 <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
699 </cellXfs>
700 <cellStyles count="1">
701 <cellStyle name="Normal" xfId="0" builtinId="0"/>
702 </cellStyles>
703</styleSheet>"#;
704
705 let parsed: StyleSheet = quick_xml::de::from_str(xml).unwrap();
706 assert_eq!(parsed.fonts.fonts.len(), 1);
707 assert_eq!(parsed.fonts.fonts[0].sz.as_ref().unwrap().val, 11.0);
708 assert_eq!(parsed.fonts.fonts[0].name.as_ref().unwrap().val, "Calibri");
709 assert_eq!(parsed.fills.fills.len(), 2);
710 assert_eq!(parsed.borders.borders.len(), 1);
711 assert_eq!(parsed.cell_xfs.xfs.len(), 1);
712 assert_eq!(parsed.cell_xfs.xfs[0].num_fmt_id, Some(0));
713 }
714
715 #[test]
716 fn test_font_with_bold_italic() {
717 let font = Font {
718 b: Some(BoolVal { val: None }),
719 i: Some(BoolVal { val: None }),
720 strike: None,
721 u: None,
722 sz: Some(FontSize { val: 12.0 }),
723 color: None,
724 name: Some(FontName {
725 val: "Arial".to_string(),
726 }),
727 family: None,
728 scheme: None,
729 };
730 let xml = quick_xml::se::to_string(&font).unwrap();
731 assert!(xml.contains("<b"));
732 assert!(xml.contains("<i"));
733 let parsed: Font = quick_xml::de::from_str(&xml).unwrap();
734 assert!(parsed.b.is_some());
735 assert!(parsed.i.is_some());
736 assert_eq!(parsed.sz.unwrap().val, 12.0);
737 }
738
739 #[test]
740 fn test_color_rgb() {
741 let color = Color {
742 auto: None,
743 indexed: None,
744 rgb: Some("FF0000FF".to_string()),
745 theme: None,
746 tint: None,
747 };
748 let xml = quick_xml::se::to_string(&color).unwrap();
749 assert!(xml.contains("rgb=\"FF0000FF\""));
750 let parsed: Color = quick_xml::de::from_str(&xml).unwrap();
751 assert_eq!(parsed.rgb, Some("FF0000FF".to_string()));
752 }
753
754 #[test]
755 fn test_color_theme_with_tint() {
756 let color = Color {
757 auto: None,
758 indexed: None,
759 rgb: None,
760 theme: Some(4),
761 tint: Some(0.399_975_585_192_419_2),
762 };
763 let xml = quick_xml::se::to_string(&color).unwrap();
764 let parsed: Color = quick_xml::de::from_str(&xml).unwrap();
765 assert_eq!(parsed.theme, Some(4));
766 assert!(parsed.tint.is_some());
767 }
768
769 #[test]
770 fn test_border_with_style() {
771 let border = Border {
772 diagonal_up: None,
773 diagonal_down: None,
774 left: Some(BorderSide {
775 style: Some("thin".to_string()),
776 color: Some(Color {
777 auto: Some(true),
778 indexed: None,
779 rgb: None,
780 theme: None,
781 tint: None,
782 }),
783 }),
784 right: None,
785 top: None,
786 bottom: None,
787 diagonal: None,
788 };
789 let xml = quick_xml::se::to_string(&border).unwrap();
790 assert!(xml.contains("style=\"thin\""));
791 let parsed: Border = quick_xml::de::from_str(&xml).unwrap();
792 assert_eq!(
793 parsed.left.as_ref().unwrap().style,
794 Some("thin".to_string())
795 );
796 assert_eq!(
797 parsed.left.as_ref().unwrap().color.as_ref().unwrap().auto,
798 Some(true)
799 );
800 }
801
802 #[test]
803 fn test_xf_with_alignment() {
804 let xf = Xf {
805 num_fmt_id: Some(0),
806 font_id: Some(0),
807 fill_id: Some(0),
808 border_id: Some(0),
809 xf_id: Some(0),
810 apply_number_format: None,
811 apply_font: None,
812 apply_fill: None,
813 apply_border: None,
814 apply_alignment: Some(true),
815 alignment: Some(Alignment {
816 horizontal: Some("center".to_string()),
817 vertical: Some("center".to_string()),
818 wrap_text: Some(true),
819 text_rotation: None,
820 indent: None,
821 shrink_to_fit: None,
822 }),
823 protection: None,
824 };
825 let xml = quick_xml::se::to_string(&xf).unwrap();
826 assert!(xml.contains("alignment"));
827 assert!(xml.contains("horizontal=\"center\""));
828 let parsed: Xf = quick_xml::de::from_str(&xml).unwrap();
829 assert!(parsed.alignment.is_some());
830 let align = parsed.alignment.unwrap();
831 assert_eq!(align.horizontal, Some("center".to_string()));
832 assert_eq!(align.vertical, Some("center".to_string()));
833 assert_eq!(align.wrap_text, Some(true));
834 }
835
836 #[test]
837 fn test_num_fmt() {
838 let nf = NumFmt {
839 num_fmt_id: 164,
840 format_code: "#,##0.00_ ".to_string(),
841 };
842 let xml = quick_xml::se::to_string(&nf).unwrap();
843 let parsed: NumFmt = quick_xml::de::from_str(&xml).unwrap();
844 assert_eq!(parsed.num_fmt_id, 164);
845 assert_eq!(parsed.format_code, "#,##0.00_ ");
846 }
847
848 #[test]
849 fn test_optional_fields_not_serialized() {
850 let ss = StyleSheet::default();
851 let xml = quick_xml::se::to_string(&ss).unwrap();
852 assert!(!xml.contains("numFmts"));
854 assert!(!xml.contains("dxfs"));
856 assert!(!xml.contains("tableStyles"));
858 }
859
860 #[test]
861 fn test_cell_style_roundtrip() {
862 let style = CellStyle {
863 name: "Heading 1".to_string(),
864 xf_id: 1,
865 builtin_id: Some(16),
866 };
867 let xml = quick_xml::se::to_string(&style).unwrap();
868 let parsed: CellStyle = quick_xml::de::from_str(&xml).unwrap();
869 assert_eq!(parsed.name, "Heading 1");
870 assert_eq!(parsed.xf_id, 1);
871 assert_eq!(parsed.builtin_id, Some(16));
872 }
873}