1use std::fmt;
6
7use serde::de::Deserializer;
8use serde::ser::Serializer;
9use serde::{Deserialize, Serialize};
10
11use crate::namespaces;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(rename = "worksheet")]
16pub struct WorksheetXml {
17 #[serde(rename = "@xmlns")]
18 pub xmlns: String,
19
20 #[serde(rename = "@xmlns:r")]
21 pub xmlns_r: String,
22
23 #[serde(rename = "sheetPr", skip_serializing_if = "Option::is_none")]
24 pub sheet_pr: Option<SheetPr>,
25
26 #[serde(rename = "dimension", skip_serializing_if = "Option::is_none")]
27 pub dimension: Option<Dimension>,
28
29 #[serde(rename = "sheetViews", skip_serializing_if = "Option::is_none")]
30 pub sheet_views: Option<SheetViews>,
31
32 #[serde(rename = "sheetFormatPr", skip_serializing_if = "Option::is_none")]
33 pub sheet_format_pr: Option<SheetFormatPr>,
34
35 #[serde(rename = "cols", skip_serializing_if = "Option::is_none")]
36 pub cols: Option<Cols>,
37
38 #[serde(rename = "sheetData")]
39 pub sheet_data: SheetData,
40
41 #[serde(rename = "sheetProtection", skip_serializing_if = "Option::is_none")]
42 pub sheet_protection: Option<SheetProtection>,
43
44 #[serde(rename = "autoFilter", skip_serializing_if = "Option::is_none")]
45 pub auto_filter: Option<AutoFilter>,
46
47 #[serde(rename = "mergeCells", skip_serializing_if = "Option::is_none")]
48 pub merge_cells: Option<MergeCells>,
49
50 #[serde(
51 rename = "conditionalFormatting",
52 default,
53 skip_serializing_if = "Vec::is_empty"
54 )]
55 pub conditional_formatting: Vec<ConditionalFormatting>,
56
57 #[serde(rename = "dataValidations", skip_serializing_if = "Option::is_none")]
58 pub data_validations: Option<DataValidations>,
59
60 #[serde(rename = "hyperlinks", skip_serializing_if = "Option::is_none")]
61 pub hyperlinks: Option<Hyperlinks>,
62
63 #[serde(rename = "printOptions", skip_serializing_if = "Option::is_none")]
64 pub print_options: Option<PrintOptions>,
65
66 #[serde(rename = "pageMargins", skip_serializing_if = "Option::is_none")]
67 pub page_margins: Option<PageMargins>,
68
69 #[serde(rename = "pageSetup", skip_serializing_if = "Option::is_none")]
70 pub page_setup: Option<PageSetup>,
71
72 #[serde(rename = "headerFooter", skip_serializing_if = "Option::is_none")]
73 pub header_footer: Option<HeaderFooter>,
74
75 #[serde(rename = "rowBreaks", skip_serializing_if = "Option::is_none")]
76 pub row_breaks: Option<RowBreaks>,
77
78 #[serde(rename = "drawing", skip_serializing_if = "Option::is_none")]
79 pub drawing: Option<DrawingRef>,
80
81 #[serde(rename = "legacyDrawing", skip_serializing_if = "Option::is_none")]
82 pub legacy_drawing: Option<LegacyDrawingRef>,
83
84 #[serde(rename = "tableParts", skip_serializing_if = "Option::is_none")]
85 pub table_parts: Option<TableParts>,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct Dimension {
91 #[serde(rename = "@ref")]
92 pub reference: String,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct SheetViews {
98 #[serde(rename = "sheetView")]
99 pub sheet_views: Vec<SheetView>,
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct SheetView {
105 #[serde(rename = "@tabSelected", skip_serializing_if = "Option::is_none")]
106 pub tab_selected: Option<bool>,
107
108 #[serde(rename = "@showGridLines", skip_serializing_if = "Option::is_none")]
109 pub show_grid_lines: Option<bool>,
110
111 #[serde(rename = "@showFormulas", skip_serializing_if = "Option::is_none")]
112 pub show_formulas: Option<bool>,
113
114 #[serde(rename = "@showRowColHeaders", skip_serializing_if = "Option::is_none")]
115 pub show_row_col_headers: Option<bool>,
116
117 #[serde(rename = "@zoomScale", skip_serializing_if = "Option::is_none")]
118 pub zoom_scale: Option<u32>,
119
120 #[serde(rename = "@view", skip_serializing_if = "Option::is_none")]
121 pub view: Option<String>,
122
123 #[serde(rename = "@topLeftCell", skip_serializing_if = "Option::is_none")]
124 pub top_left_cell: Option<String>,
125
126 #[serde(rename = "@workbookViewId")]
127 pub workbook_view_id: u32,
128
129 #[serde(rename = "pane", skip_serializing_if = "Option::is_none")]
130 pub pane: Option<Pane>,
131
132 #[serde(rename = "selection", default)]
133 pub selection: Vec<Selection>,
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct Pane {
139 #[serde(rename = "@xSplit", skip_serializing_if = "Option::is_none")]
140 pub x_split: Option<u32>,
141
142 #[serde(rename = "@ySplit", skip_serializing_if = "Option::is_none")]
143 pub y_split: Option<u32>,
144
145 #[serde(rename = "@topLeftCell", skip_serializing_if = "Option::is_none")]
146 pub top_left_cell: Option<String>,
147
148 #[serde(rename = "@activePane", skip_serializing_if = "Option::is_none")]
149 pub active_pane: Option<String>,
150
151 #[serde(rename = "@state", skip_serializing_if = "Option::is_none")]
152 pub state: Option<String>,
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub struct Selection {
158 #[serde(rename = "@pane", skip_serializing_if = "Option::is_none")]
159 pub pane: Option<String>,
160
161 #[serde(rename = "@activeCell", skip_serializing_if = "Option::is_none")]
162 pub active_cell: Option<String>,
163
164 #[serde(rename = "@sqref", skip_serializing_if = "Option::is_none")]
165 pub sqref: Option<String>,
166}
167
168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
170pub struct SheetPr {
171 #[serde(rename = "@codeName", skip_serializing_if = "Option::is_none")]
172 pub code_name: Option<String>,
173
174 #[serde(rename = "@filterMode", skip_serializing_if = "Option::is_none")]
175 pub filter_mode: Option<bool>,
176
177 #[serde(rename = "tabColor", skip_serializing_if = "Option::is_none")]
178 pub tab_color: Option<TabColor>,
179
180 #[serde(rename = "outlinePr", skip_serializing_if = "Option::is_none")]
181 pub outline_pr: Option<OutlinePr>,
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct TabColor {
187 #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
188 pub rgb: Option<String>,
189
190 #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
191 pub theme: Option<u32>,
192
193 #[serde(rename = "@indexed", skip_serializing_if = "Option::is_none")]
194 pub indexed: Option<u32>,
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
199pub struct OutlinePr {
200 #[serde(rename = "@summaryBelow", skip_serializing_if = "Option::is_none")]
201 pub summary_below: Option<bool>,
202
203 #[serde(rename = "@summaryRight", skip_serializing_if = "Option::is_none")]
204 pub summary_right: Option<bool>,
205}
206
207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub struct SheetFormatPr {
210 #[serde(rename = "@defaultRowHeight")]
211 pub default_row_height: f64,
212
213 #[serde(rename = "@defaultColWidth", skip_serializing_if = "Option::is_none")]
214 pub default_col_width: Option<f64>,
215
216 #[serde(rename = "@customHeight", skip_serializing_if = "Option::is_none")]
217 pub custom_height: Option<bool>,
218
219 #[serde(rename = "@outlineLevelRow", skip_serializing_if = "Option::is_none")]
220 pub outline_level_row: Option<u8>,
221
222 #[serde(rename = "@outlineLevelCol", skip_serializing_if = "Option::is_none")]
223 pub outline_level_col: Option<u8>,
224}
225
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
228pub struct SheetProtection {
229 #[serde(rename = "@password", skip_serializing_if = "Option::is_none")]
230 pub password: Option<String>,
231
232 #[serde(rename = "@sheet", skip_serializing_if = "Option::is_none")]
233 pub sheet: Option<bool>,
234
235 #[serde(rename = "@objects", skip_serializing_if = "Option::is_none")]
236 pub objects: Option<bool>,
237
238 #[serde(rename = "@scenarios", skip_serializing_if = "Option::is_none")]
239 pub scenarios: Option<bool>,
240
241 #[serde(rename = "@selectLockedCells", skip_serializing_if = "Option::is_none")]
242 pub select_locked_cells: Option<bool>,
243
244 #[serde(
245 rename = "@selectUnlockedCells",
246 skip_serializing_if = "Option::is_none"
247 )]
248 pub select_unlocked_cells: Option<bool>,
249
250 #[serde(rename = "@formatCells", skip_serializing_if = "Option::is_none")]
251 pub format_cells: Option<bool>,
252
253 #[serde(rename = "@formatColumns", skip_serializing_if = "Option::is_none")]
254 pub format_columns: Option<bool>,
255
256 #[serde(rename = "@formatRows", skip_serializing_if = "Option::is_none")]
257 pub format_rows: Option<bool>,
258
259 #[serde(rename = "@insertColumns", skip_serializing_if = "Option::is_none")]
260 pub insert_columns: Option<bool>,
261
262 #[serde(rename = "@insertRows", skip_serializing_if = "Option::is_none")]
263 pub insert_rows: Option<bool>,
264
265 #[serde(rename = "@insertHyperlinks", skip_serializing_if = "Option::is_none")]
266 pub insert_hyperlinks: Option<bool>,
267
268 #[serde(rename = "@deleteColumns", skip_serializing_if = "Option::is_none")]
269 pub delete_columns: Option<bool>,
270
271 #[serde(rename = "@deleteRows", skip_serializing_if = "Option::is_none")]
272 pub delete_rows: Option<bool>,
273
274 #[serde(rename = "@sort", skip_serializing_if = "Option::is_none")]
275 pub sort: Option<bool>,
276
277 #[serde(rename = "@autoFilter", skip_serializing_if = "Option::is_none")]
278 pub auto_filter: Option<bool>,
279
280 #[serde(rename = "@pivotTables", skip_serializing_if = "Option::is_none")]
281 pub pivot_tables: Option<bool>,
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct Cols {
287 #[serde(rename = "col")]
288 pub cols: Vec<Col>,
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct Col {
294 #[serde(rename = "@min")]
295 pub min: u32,
296
297 #[serde(rename = "@max")]
298 pub max: u32,
299
300 #[serde(rename = "@width", skip_serializing_if = "Option::is_none")]
301 pub width: Option<f64>,
302
303 #[serde(rename = "@style", skip_serializing_if = "Option::is_none")]
304 pub style: Option<u32>,
305
306 #[serde(rename = "@hidden", skip_serializing_if = "Option::is_none")]
307 pub hidden: Option<bool>,
308
309 #[serde(rename = "@customWidth", skip_serializing_if = "Option::is_none")]
310 pub custom_width: Option<bool>,
311
312 #[serde(rename = "@outlineLevel", skip_serializing_if = "Option::is_none")]
313 pub outline_level: Option<u8>,
314}
315
316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct SheetData {
319 #[serde(rename = "row", default)]
320 pub rows: Vec<Row>,
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
325pub struct Row {
326 #[serde(rename = "@r")]
328 pub r: u32,
329
330 #[serde(skip)]
331 pub spans: Option<String>,
332
333 #[serde(rename = "@s", skip_serializing_if = "Option::is_none")]
334 pub s: Option<u32>,
335
336 #[serde(rename = "@customFormat", skip_serializing_if = "Option::is_none")]
337 pub custom_format: Option<bool>,
338
339 #[serde(rename = "@ht", skip_serializing_if = "Option::is_none")]
340 pub ht: Option<f64>,
341
342 #[serde(rename = "@hidden", skip_serializing_if = "Option::is_none")]
343 pub hidden: Option<bool>,
344
345 #[serde(rename = "@customHeight", skip_serializing_if = "Option::is_none")]
346 pub custom_height: Option<bool>,
347
348 #[serde(rename = "@outlineLevel", skip_serializing_if = "Option::is_none")]
349 pub outline_level: Option<u8>,
350
351 #[serde(rename = "c", default)]
352 pub cells: Vec<Cell>,
353}
354
355#[derive(Clone, Copy, Default, PartialEq, Eq)]
358pub struct CompactCellRef {
359 buf: [u8; 10],
360 len: u8,
361}
362
363impl CompactCellRef {
364 pub fn new(s: &str) -> Self {
366 assert!(
367 s.len() <= 10,
368 "cell reference too long ({} bytes): {s}",
369 s.len()
370 );
371 let mut buf = [0u8; 10];
372 buf[..s.len()].copy_from_slice(s.as_bytes());
373 Self {
374 buf,
375 len: s.len() as u8,
376 }
377 }
378
379 pub fn as_str(&self) -> &str {
381 unsafe { std::str::from_utf8_unchecked(&self.buf[..self.len as usize]) }
383 }
384
385 pub fn from_coordinates(col: u32, row: u32) -> Self {
387 let mut buf = [0u8; 10];
388 let mut pos = 0;
389
390 let mut col_buf = [0u8; 3];
392 let mut col_len = 0;
393 let mut c = col;
394 while c > 0 {
395 c -= 1;
396 col_buf[col_len] = b'A' + (c % 26) as u8;
397 col_len += 1;
398 c /= 26;
399 }
400 for i in (0..col_len).rev() {
402 buf[pos] = col_buf[i];
403 pos += 1;
404 }
405
406 let mut row_buf = [0u8; 7];
408 let mut row_len = 0;
409 let mut r = row;
410 while r > 0 {
411 row_buf[row_len] = b'0' + (r % 10) as u8;
412 row_len += 1;
413 r /= 10;
414 }
415 for i in (0..row_len).rev() {
417 buf[pos] = row_buf[i];
418 pos += 1;
419 }
420
421 Self {
422 buf,
423 len: pos as u8,
424 }
425 }
426}
427
428impl fmt::Display for CompactCellRef {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 f.write_str(self.as_str())
431 }
432}
433
434impl fmt::Debug for CompactCellRef {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(f, "CompactCellRef(\"{}\")", self.as_str())
437 }
438}
439
440impl From<&str> for CompactCellRef {
441 fn from(s: &str) -> Self {
442 Self::new(s)
443 }
444}
445
446impl From<String> for CompactCellRef {
447 fn from(s: String) -> Self {
448 Self::new(&s)
449 }
450}
451
452impl AsRef<str> for CompactCellRef {
453 fn as_ref(&self) -> &str {
454 self.as_str()
455 }
456}
457
458impl PartialEq<&str> for CompactCellRef {
459 fn eq(&self, other: &&str) -> bool {
460 self.as_str() == *other
461 }
462}
463
464impl PartialEq<str> for CompactCellRef {
465 fn eq(&self, other: &str) -> bool {
466 self.as_str() == other
467 }
468}
469
470impl Serialize for CompactCellRef {
471 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
472 serializer.serialize_str(self.as_str())
473 }
474}
475
476impl<'de> Deserialize<'de> for CompactCellRef {
477 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
478 struct CompactCellRefVisitor;
479
480 impl serde::de::Visitor<'_> for CompactCellRefVisitor {
481 type Value = CompactCellRef;
482
483 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
484 formatter.write_str("a cell reference string (e.g. \"A1\")")
485 }
486
487 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<CompactCellRef, E> {
488 if v.len() > 10 {
489 return Err(E::custom(format!(
490 "cell reference too long ({} bytes): {v}",
491 v.len()
492 )));
493 }
494 Ok(CompactCellRef::new(v))
495 }
496
497 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<CompactCellRef, E> {
498 self.visit_str(&v)
499 }
500 }
501
502 deserializer.deserialize_str(CompactCellRefVisitor)
503 }
504}
505
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
508pub struct Cell {
509 #[serde(rename = "@r")]
511 pub r: CompactCellRef,
512
513 #[serde(skip)]
516 pub col: u32,
517
518 #[serde(rename = "@s", skip_serializing_if = "Option::is_none")]
520 pub s: Option<u32>,
521
522 #[serde(rename = "@t", default, skip_serializing_if = "CellTypeTag::is_none")]
524 pub t: CellTypeTag,
525
526 #[serde(rename = "v", skip_serializing_if = "Option::is_none")]
528 pub v: Option<String>,
529
530 #[serde(rename = "f", skip_serializing_if = "Option::is_none")]
532 pub f: Option<Box<CellFormula>>,
533
534 #[serde(rename = "is", skip_serializing_if = "Option::is_none")]
536 pub is: Option<Box<InlineString>>,
537}
538
539#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
541pub enum CellTypeTag {
542 #[default]
544 None,
545 SharedString,
547 Number,
549 Boolean,
551 Error,
553 InlineString,
555 FormulaString,
557 Date,
559}
560
561impl CellTypeTag {
562 pub fn is_none(&self) -> bool {
564 matches!(self, CellTypeTag::None)
565 }
566
567 pub fn as_str(&self) -> Option<&'static str> {
569 match self {
570 CellTypeTag::None => Option::None,
571 CellTypeTag::SharedString => Some("s"),
572 CellTypeTag::Number => Some("n"),
573 CellTypeTag::Boolean => Some("b"),
574 CellTypeTag::Error => Some("e"),
575 CellTypeTag::InlineString => Some("inlineStr"),
576 CellTypeTag::FormulaString => Some("str"),
577 CellTypeTag::Date => Some("d"),
578 }
579 }
580}
581
582impl Serialize for CellTypeTag {
583 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
584 match self.as_str() {
585 Some(s) => serializer.serialize_str(s),
586 Option::None => serializer.serialize_none(),
587 }
588 }
589}
590
591impl<'de> Deserialize<'de> for CellTypeTag {
592 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
593 struct CellTypeTagVisitor;
594
595 impl serde::de::Visitor<'_> for CellTypeTagVisitor {
596 type Value = CellTypeTag;
597
598 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
599 formatter.write_str("a cell type string (e.g. \"s\", \"n\", \"b\")")
600 }
601
602 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<CellTypeTag, E> {
603 Ok(match v {
604 "s" => CellTypeTag::SharedString,
605 "n" => CellTypeTag::Number,
606 "b" => CellTypeTag::Boolean,
607 "e" => CellTypeTag::Error,
608 "inlineStr" => CellTypeTag::InlineString,
609 "str" => CellTypeTag::FormulaString,
610 "d" => CellTypeTag::Date,
611 _ => CellTypeTag::None,
612 })
613 }
614
615 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<CellTypeTag, E> {
616 self.visit_str(&v)
617 }
618 }
619
620 deserializer.deserialize_str(CellTypeTagVisitor)
621 }
622}
623
624#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
626pub struct CellFormula {
627 #[serde(rename = "@t", skip_serializing_if = "Option::is_none")]
628 pub t: Option<String>,
629
630 #[serde(rename = "@ref", skip_serializing_if = "Option::is_none")]
631 pub reference: Option<String>,
632
633 #[serde(rename = "@si", skip_serializing_if = "Option::is_none")]
634 pub si: Option<u32>,
635
636 #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
637 pub value: Option<String>,
638}
639
640#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
642pub struct InlineString {
643 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
644 pub t: Option<String>,
645}
646
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
649pub struct AutoFilter {
650 #[serde(rename = "@ref")]
651 pub reference: String,
652}
653
654#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656pub struct DataValidations {
657 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
658 pub count: Option<u32>,
659
660 #[serde(
661 rename = "@disablePrompts",
662 skip_serializing_if = "Option::is_none",
663 default
664 )]
665 pub disable_prompts: Option<bool>,
666
667 #[serde(rename = "@xWindow", skip_serializing_if = "Option::is_none", default)]
668 pub x_window: Option<u32>,
669
670 #[serde(rename = "@yWindow", skip_serializing_if = "Option::is_none", default)]
671 pub y_window: Option<u32>,
672
673 #[serde(rename = "dataValidation", default)]
674 pub data_validations: Vec<DataValidation>,
675}
676
677#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
679pub struct DataValidation {
680 #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
681 pub validation_type: Option<String>,
682
683 #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
684 pub operator: Option<String>,
685
686 #[serde(rename = "@allowBlank", skip_serializing_if = "Option::is_none")]
687 pub allow_blank: Option<bool>,
688
689 #[serde(
690 rename = "@showDropDown",
691 skip_serializing_if = "Option::is_none",
692 default
693 )]
694 pub show_drop_down: Option<bool>,
695
696 #[serde(rename = "@showInputMessage", skip_serializing_if = "Option::is_none")]
697 pub show_input_message: Option<bool>,
698
699 #[serde(rename = "@showErrorMessage", skip_serializing_if = "Option::is_none")]
700 pub show_error_message: Option<bool>,
701
702 #[serde(rename = "@errorStyle", skip_serializing_if = "Option::is_none")]
703 pub error_style: Option<String>,
704
705 #[serde(rename = "@imeMode", skip_serializing_if = "Option::is_none", default)]
706 pub ime_mode: Option<String>,
707
708 #[serde(rename = "@errorTitle", skip_serializing_if = "Option::is_none")]
709 pub error_title: Option<String>,
710
711 #[serde(rename = "@error", skip_serializing_if = "Option::is_none")]
712 pub error: Option<String>,
713
714 #[serde(rename = "@promptTitle", skip_serializing_if = "Option::is_none")]
715 pub prompt_title: Option<String>,
716
717 #[serde(rename = "@prompt", skip_serializing_if = "Option::is_none")]
718 pub prompt: Option<String>,
719
720 #[serde(rename = "@sqref")]
721 pub sqref: String,
722
723 #[serde(rename = "formula1", skip_serializing_if = "Option::is_none")]
724 pub formula1: Option<String>,
725
726 #[serde(rename = "formula2", skip_serializing_if = "Option::is_none")]
727 pub formula2: Option<String>,
728}
729
730#[derive(Debug, Clone, Serialize, Deserialize)]
736pub struct MergeCells {
737 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
738 pub count: Option<u32>,
739
740 #[serde(rename = "mergeCell", default)]
741 pub merge_cells: Vec<MergeCell>,
742
743 #[serde(skip)]
747 pub cached_coords: Vec<(u32, u32, u32, u32)>,
748}
749
750impl PartialEq for MergeCells {
751 fn eq(&self, other: &Self) -> bool {
752 self.count == other.count && self.merge_cells == other.merge_cells
753 }
754}
755
756#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
758pub struct MergeCell {
759 #[serde(rename = "@ref")]
760 pub reference: String,
761}
762
763#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
765pub struct Hyperlinks {
766 #[serde(rename = "hyperlink", default)]
767 pub hyperlinks: Vec<Hyperlink>,
768}
769
770#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
772pub struct Hyperlink {
773 #[serde(rename = "@ref")]
774 pub reference: String,
775
776 #[serde(
777 rename = "@r:id",
778 alias = "@id",
779 skip_serializing_if = "Option::is_none"
780 )]
781 pub r_id: Option<String>,
782
783 #[serde(rename = "@location", skip_serializing_if = "Option::is_none")]
784 pub location: Option<String>,
785
786 #[serde(rename = "@display", skip_serializing_if = "Option::is_none")]
787 pub display: Option<String>,
788
789 #[serde(rename = "@tooltip", skip_serializing_if = "Option::is_none")]
790 pub tooltip: Option<String>,
791}
792
793#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
795pub struct PageMargins {
796 #[serde(rename = "@left")]
797 pub left: f64,
798
799 #[serde(rename = "@right")]
800 pub right: f64,
801
802 #[serde(rename = "@top")]
803 pub top: f64,
804
805 #[serde(rename = "@bottom")]
806 pub bottom: f64,
807
808 #[serde(rename = "@header")]
809 pub header: f64,
810
811 #[serde(rename = "@footer")]
812 pub footer: f64,
813}
814
815#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
817pub struct PageSetup {
818 #[serde(rename = "@paperSize", skip_serializing_if = "Option::is_none")]
819 pub paper_size: Option<u32>,
820
821 #[serde(rename = "@orientation", skip_serializing_if = "Option::is_none")]
822 pub orientation: Option<String>,
823
824 #[serde(rename = "@scale", skip_serializing_if = "Option::is_none")]
825 pub scale: Option<u32>,
826
827 #[serde(rename = "@fitToWidth", skip_serializing_if = "Option::is_none")]
828 pub fit_to_width: Option<u32>,
829
830 #[serde(rename = "@fitToHeight", skip_serializing_if = "Option::is_none")]
831 pub fit_to_height: Option<u32>,
832
833 #[serde(rename = "@firstPageNumber", skip_serializing_if = "Option::is_none")]
834 pub first_page_number: Option<u32>,
835
836 #[serde(rename = "@horizontalDpi", skip_serializing_if = "Option::is_none")]
837 pub horizontal_dpi: Option<u32>,
838
839 #[serde(rename = "@verticalDpi", skip_serializing_if = "Option::is_none")]
840 pub vertical_dpi: Option<u32>,
841
842 #[serde(
843 rename = "@r:id",
844 alias = "@id",
845 skip_serializing_if = "Option::is_none"
846 )]
847 pub r_id: Option<String>,
848}
849
850#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
852pub struct HeaderFooter {
853 #[serde(rename = "oddHeader", skip_serializing_if = "Option::is_none")]
854 pub odd_header: Option<String>,
855
856 #[serde(rename = "oddFooter", skip_serializing_if = "Option::is_none")]
857 pub odd_footer: Option<String>,
858}
859
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
862pub struct PrintOptions {
863 #[serde(rename = "@gridLines", skip_serializing_if = "Option::is_none")]
864 pub grid_lines: Option<bool>,
865
866 #[serde(rename = "@headings", skip_serializing_if = "Option::is_none")]
867 pub headings: Option<bool>,
868
869 #[serde(
870 rename = "@horizontalCentered",
871 skip_serializing_if = "Option::is_none"
872 )]
873 pub horizontal_centered: Option<bool>,
874
875 #[serde(rename = "@verticalCentered", skip_serializing_if = "Option::is_none")]
876 pub vertical_centered: Option<bool>,
877}
878
879#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
881pub struct RowBreaks {
882 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
883 pub count: Option<u32>,
884
885 #[serde(rename = "@manualBreakCount", skip_serializing_if = "Option::is_none")]
886 pub manual_break_count: Option<u32>,
887
888 #[serde(rename = "brk", default)]
889 pub brk: Vec<Break>,
890}
891
892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
894pub struct Break {
895 #[serde(rename = "@id")]
896 pub id: u32,
897
898 #[serde(rename = "@max", skip_serializing_if = "Option::is_none")]
899 pub max: Option<u32>,
900
901 #[serde(rename = "@man", skip_serializing_if = "Option::is_none")]
902 pub man: Option<bool>,
903}
904
905#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
907pub struct DrawingRef {
908 #[serde(rename = "@r:id", alias = "@id")]
909 pub r_id: String,
910}
911
912#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
914pub struct LegacyDrawingRef {
915 #[serde(rename = "@r:id", alias = "@id")]
916 pub r_id: String,
917}
918
919#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
921pub struct TableParts {
922 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
923 pub count: Option<u32>,
924
925 #[serde(rename = "tablePart", default)]
926 pub table_parts: Vec<TablePart>,
927}
928
929#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
931pub struct TablePart {
932 #[serde(rename = "@r:id", alias = "@id")]
933 pub r_id: String,
934}
935
936#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
938pub struct ConditionalFormatting {
939 #[serde(rename = "@sqref")]
940 pub sqref: String,
941
942 #[serde(rename = "cfRule", default)]
943 pub cf_rules: Vec<CfRule>,
944}
945
946#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
948pub struct CfRule {
949 #[serde(rename = "@type")]
950 pub rule_type: String,
951
952 #[serde(rename = "@dxfId", skip_serializing_if = "Option::is_none")]
953 pub dxf_id: Option<u32>,
954
955 #[serde(rename = "@priority")]
956 pub priority: u32,
957
958 #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
959 pub operator: Option<String>,
960
961 #[serde(rename = "@text", skip_serializing_if = "Option::is_none")]
962 pub text: Option<String>,
963
964 #[serde(rename = "@stopIfTrue", skip_serializing_if = "Option::is_none")]
965 pub stop_if_true: Option<bool>,
966
967 #[serde(rename = "@aboveAverage", skip_serializing_if = "Option::is_none")]
968 pub above_average: Option<bool>,
969
970 #[serde(rename = "@equalAverage", skip_serializing_if = "Option::is_none")]
971 pub equal_average: Option<bool>,
972
973 #[serde(rename = "@percent", skip_serializing_if = "Option::is_none")]
974 pub percent: Option<bool>,
975
976 #[serde(rename = "@rank", skip_serializing_if = "Option::is_none")]
977 pub rank: Option<u32>,
978
979 #[serde(rename = "@bottom", skip_serializing_if = "Option::is_none")]
980 pub bottom: Option<bool>,
981
982 #[serde(rename = "formula", default, skip_serializing_if = "Vec::is_empty")]
983 pub formulas: Vec<String>,
984
985 #[serde(rename = "colorScale", skip_serializing_if = "Option::is_none")]
986 pub color_scale: Option<CfColorScale>,
987
988 #[serde(rename = "dataBar", skip_serializing_if = "Option::is_none")]
989 pub data_bar: Option<CfDataBar>,
990
991 #[serde(rename = "iconSet", skip_serializing_if = "Option::is_none")]
992 pub icon_set: Option<CfIconSet>,
993}
994
995#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
997pub struct CfColorScale {
998 #[serde(rename = "cfvo", default)]
999 pub cfvos: Vec<CfVo>,
1000
1001 #[serde(rename = "color", default)]
1002 pub colors: Vec<CfColor>,
1003}
1004
1005#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1007pub struct CfDataBar {
1008 #[serde(rename = "@showValue", skip_serializing_if = "Option::is_none")]
1009 pub show_value: Option<bool>,
1010
1011 #[serde(rename = "cfvo", default)]
1012 pub cfvos: Vec<CfVo>,
1013
1014 #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
1015 pub color: Option<CfColor>,
1016}
1017
1018#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1020pub struct CfIconSet {
1021 #[serde(rename = "@iconSet", skip_serializing_if = "Option::is_none")]
1022 pub icon_set: Option<String>,
1023
1024 #[serde(rename = "cfvo", default)]
1025 pub cfvos: Vec<CfVo>,
1026}
1027
1028#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1030pub struct CfVo {
1031 #[serde(rename = "@type")]
1032 pub value_type: String,
1033
1034 #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
1035 pub val: Option<String>,
1036}
1037
1038#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1040pub struct CfColor {
1041 #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
1042 pub rgb: Option<String>,
1043
1044 #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
1045 pub theme: Option<u32>,
1046
1047 #[serde(rename = "@tint", skip_serializing_if = "Option::is_none")]
1048 pub tint: Option<f64>,
1049}
1050
1051impl Default for WorksheetXml {
1052 fn default() -> Self {
1053 Self {
1054 xmlns: namespaces::SPREADSHEET_ML.to_string(),
1055 xmlns_r: namespaces::RELATIONSHIPS.to_string(),
1056 sheet_pr: None,
1057 dimension: None,
1058 sheet_views: None,
1059 sheet_format_pr: None,
1060 cols: None,
1061 sheet_data: SheetData { rows: vec![] },
1062 sheet_protection: None,
1063 auto_filter: None,
1064 merge_cells: None,
1065 conditional_formatting: vec![],
1066 data_validations: None,
1067 hyperlinks: None,
1068 print_options: None,
1069 page_margins: None,
1070 page_setup: None,
1071 header_footer: None,
1072 row_breaks: None,
1073 drawing: None,
1074 legacy_drawing: None,
1075 table_parts: None,
1076 }
1077 }
1078}
1079
1080#[cfg(test)]
1081mod tests {
1082 use super::*;
1083
1084 #[test]
1085 fn test_worksheet_default() {
1086 let ws = WorksheetXml::default();
1087 assert_eq!(ws.xmlns, namespaces::SPREADSHEET_ML);
1088 assert_eq!(ws.xmlns_r, namespaces::RELATIONSHIPS);
1089 assert!(ws.sheet_data.rows.is_empty());
1090 assert!(ws.dimension.is_none());
1091 assert!(ws.sheet_views.is_none());
1092 assert!(ws.cols.is_none());
1093 assert!(ws.merge_cells.is_none());
1094 assert!(ws.page_margins.is_none());
1095 assert!(ws.sheet_pr.is_none());
1096 assert!(ws.sheet_protection.is_none());
1097 }
1098
1099 #[test]
1100 fn test_worksheet_roundtrip() {
1101 let ws = WorksheetXml::default();
1102 let xml = quick_xml::se::to_string(&ws).unwrap();
1103 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1104 assert_eq!(ws.xmlns, parsed.xmlns);
1105 assert_eq!(ws.xmlns_r, parsed.xmlns_r);
1106 assert_eq!(ws.sheet_data.rows.len(), parsed.sheet_data.rows.len());
1107 }
1108
1109 #[test]
1110 fn test_worksheet_with_data() {
1111 let ws = WorksheetXml {
1112 sheet_data: SheetData {
1113 rows: vec![Row {
1114 r: 1,
1115 spans: Some("1:3".to_string()),
1116 s: None,
1117 custom_format: None,
1118 ht: None,
1119 hidden: None,
1120 custom_height: None,
1121 outline_level: None,
1122 cells: vec![
1123 Cell {
1124 r: CompactCellRef::new("A1"),
1125 col: 1,
1126 s: None,
1127 t: CellTypeTag::SharedString,
1128 v: Some("0".to_string()),
1129 f: None,
1130 is: None,
1131 },
1132 Cell {
1133 r: CompactCellRef::new("B1"),
1134 col: 2,
1135 s: None,
1136 t: CellTypeTag::None,
1137 v: Some("42".to_string()),
1138 f: None,
1139 is: None,
1140 },
1141 ],
1142 }],
1143 },
1144 ..WorksheetXml::default()
1145 };
1146
1147 let xml = quick_xml::se::to_string(&ws).unwrap();
1148 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1149 assert_eq!(parsed.sheet_data.rows.len(), 1);
1150 assert_eq!(parsed.sheet_data.rows[0].r, 1);
1151 assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
1152 assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
1153 assert_eq!(
1154 parsed.sheet_data.rows[0].cells[0].t,
1155 CellTypeTag::SharedString
1156 );
1157 assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
1158 assert_eq!(parsed.sheet_data.rows[0].cells[1].r, "B1");
1159 assert_eq!(parsed.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
1160 }
1161
1162 #[test]
1163 fn test_cell_with_formula() {
1164 let cell = Cell {
1165 r: CompactCellRef::new("C1"),
1166 col: 3,
1167 s: None,
1168 t: CellTypeTag::None,
1169 v: Some("84".to_string()),
1170 f: Some(Box::new(CellFormula {
1171 t: None,
1172 reference: None,
1173 si: None,
1174 value: Some("A1+B1".to_string()),
1175 })),
1176 is: None,
1177 };
1178 let xml = quick_xml::se::to_string(&cell).unwrap();
1179 assert!(xml.contains("A1+B1"));
1180 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1181 assert!(parsed.f.is_some());
1182 assert_eq!(parsed.f.unwrap().value, Some("A1+B1".to_string()));
1183 }
1184
1185 #[test]
1186 fn test_cell_with_inline_string() {
1187 let cell = Cell {
1188 r: CompactCellRef::new("A1"),
1189 col: 1,
1190 s: None,
1191 t: CellTypeTag::InlineString,
1192 v: None,
1193 f: None,
1194 is: Some(Box::new(InlineString {
1195 t: Some("Hello World".to_string()),
1196 })),
1197 };
1198 let xml = quick_xml::se::to_string(&cell).unwrap();
1199 assert!(xml.contains("Hello World"));
1200 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1201 assert_eq!(parsed.t, CellTypeTag::InlineString);
1202 assert!(parsed.is.is_some());
1203 assert_eq!(parsed.is.unwrap().t, Some("Hello World".to_string()));
1204 }
1205
1206 #[test]
1207 fn test_parse_real_excel_worksheet() {
1208 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1209<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
1210 <dimension ref="A1:B2"/>
1211 <sheetData>
1212 <row r="1" spans="1:2">
1213 <c r="A1" t="s"><v>0</v></c>
1214 <c r="B1" t="s"><v>1</v></c>
1215 </row>
1216 <row r="2" spans="1:2">
1217 <c r="A2"><v>100</v></c>
1218 <c r="B2"><v>200</v></c>
1219 </row>
1220 </sheetData>
1221</worksheet>"#;
1222
1223 let parsed: WorksheetXml = quick_xml::de::from_str(xml).unwrap();
1224 assert_eq!(parsed.dimension.as_ref().unwrap().reference, "A1:B2");
1225 assert_eq!(parsed.sheet_data.rows.len(), 2);
1226 assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
1227 assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
1228 assert_eq!(
1229 parsed.sheet_data.rows[0].cells[0].t,
1230 CellTypeTag::SharedString
1231 );
1232 assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
1233 assert_eq!(parsed.sheet_data.rows[1].cells[0].r, "A2");
1234 assert_eq!(
1235 parsed.sheet_data.rows[1].cells[0].v,
1236 Some("100".to_string())
1237 );
1238 }
1239
1240 #[test]
1241 fn test_worksheet_with_merge_cells() {
1242 let ws = WorksheetXml {
1243 merge_cells: Some(MergeCells {
1244 count: Some(1),
1245 merge_cells: vec![MergeCell {
1246 reference: "A1:B2".to_string(),
1247 }],
1248 cached_coords: Vec::new(),
1249 }),
1250 ..WorksheetXml::default()
1251 };
1252 let xml = quick_xml::se::to_string(&ws).unwrap();
1253 assert!(xml.contains("mergeCells"));
1254 assert!(xml.contains("A1:B2"));
1255 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1256 assert!(parsed.merge_cells.is_some());
1257 assert_eq!(parsed.merge_cells.as_ref().unwrap().merge_cells.len(), 1);
1258 }
1259
1260 #[test]
1261 fn test_empty_sheet_data_serialization() {
1262 let sd = SheetData { rows: vec![] };
1263 let xml = quick_xml::se::to_string(&sd).unwrap();
1264 let parsed: SheetData = quick_xml::de::from_str(&xml).unwrap();
1266 assert!(parsed.rows.is_empty());
1267 }
1268
1269 #[test]
1270 fn test_row_optional_fields_not_serialized() {
1271 let row = Row {
1272 r: 1,
1273 spans: None,
1274 s: None,
1275 custom_format: None,
1276 ht: None,
1277 hidden: None,
1278 custom_height: None,
1279 outline_level: None,
1280 cells: vec![],
1281 };
1282 let xml = quick_xml::se::to_string(&row).unwrap();
1283 assert!(!xml.contains("spans"));
1284 assert!(!xml.contains("ht"));
1285 assert!(!xml.contains("hidden"));
1286 }
1287
1288 #[test]
1289 fn test_cell_type_tag_as_str() {
1290 assert_eq!(CellTypeTag::Boolean.as_str(), Some("b"));
1291 assert_eq!(CellTypeTag::Date.as_str(), Some("d"));
1292 assert_eq!(CellTypeTag::Error.as_str(), Some("e"));
1293 assert_eq!(CellTypeTag::InlineString.as_str(), Some("inlineStr"));
1294 assert_eq!(CellTypeTag::Number.as_str(), Some("n"));
1295 assert_eq!(CellTypeTag::SharedString.as_str(), Some("s"));
1296 assert_eq!(CellTypeTag::FormulaString.as_str(), Some("str"));
1297 assert_eq!(CellTypeTag::None.as_str(), Option::None);
1298 }
1299
1300 #[test]
1301 fn test_cell_type_tag_default_is_none() {
1302 assert_eq!(CellTypeTag::default(), CellTypeTag::None);
1303 assert!(CellTypeTag::None.is_none());
1304 assert!(!CellTypeTag::SharedString.is_none());
1305 }
1306
1307 #[test]
1308 fn test_cell_type_tag_serde_round_trip() {
1309 let variants = [
1310 (CellTypeTag::SharedString, "s"),
1311 (CellTypeTag::Number, "n"),
1312 (CellTypeTag::Boolean, "b"),
1313 (CellTypeTag::Error, "e"),
1314 (CellTypeTag::InlineString, "inlineStr"),
1315 (CellTypeTag::FormulaString, "str"),
1316 (CellTypeTag::Date, "d"),
1317 ];
1318 for (tag, expected_str) in &variants {
1319 let cell = Cell {
1320 r: CompactCellRef::new("A1"),
1321 col: 1,
1322 s: None,
1323 t: *tag,
1324 v: Some("0".to_string()),
1325 f: None,
1326 is: None,
1327 };
1328 let xml = quick_xml::se::to_string(&cell).unwrap();
1329 assert!(
1330 xml.contains(&format!("t=\"{expected_str}\"")),
1331 "expected t=\"{expected_str}\" in: {xml}"
1332 );
1333 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1334 assert_eq!(parsed.t, *tag);
1335 }
1336
1337 let cell_none = Cell {
1338 r: CompactCellRef::new("A1"),
1339 col: 1,
1340 s: None,
1341 t: CellTypeTag::None,
1342 v: Some("42".to_string()),
1343 f: None,
1344 is: None,
1345 };
1346 let xml = quick_xml::se::to_string(&cell_none).unwrap();
1347 assert!(
1348 !xml.contains("t="),
1349 "None variant should not emit t attribute: {xml}"
1350 );
1351 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1352 assert_eq!(parsed.t, CellTypeTag::None);
1353 }
1354
1355 #[test]
1356 fn test_worksheet_with_cols() {
1357 let ws = WorksheetXml {
1358 cols: Some(Cols {
1359 cols: vec![Col {
1360 min: 1,
1361 max: 1,
1362 width: Some(15.0),
1363 style: None,
1364 hidden: None,
1365 custom_width: Some(true),
1366 outline_level: None,
1367 }],
1368 }),
1369 ..WorksheetXml::default()
1370 };
1371 let xml = quick_xml::se::to_string(&ws).unwrap();
1372 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1373 assert!(parsed.cols.is_some());
1374 let cols = parsed.cols.unwrap();
1375 assert_eq!(cols.cols.len(), 1);
1376 assert_eq!(cols.cols[0].min, 1);
1377 assert_eq!(cols.cols[0].width, Some(15.0));
1378 assert_eq!(cols.cols[0].custom_width, Some(true));
1379 }
1380
1381 #[test]
1382 fn test_sheet_protection_roundtrip() {
1383 let prot = SheetProtection {
1384 password: Some("ABCD".to_string()),
1385 sheet: Some(true),
1386 objects: Some(true),
1387 scenarios: Some(true),
1388 format_cells: Some(false),
1389 ..SheetProtection::default()
1390 };
1391 let xml = quick_xml::se::to_string(&prot).unwrap();
1392 let parsed: SheetProtection = quick_xml::de::from_str(&xml).unwrap();
1393 assert_eq!(parsed.password, Some("ABCD".to_string()));
1394 assert_eq!(parsed.sheet, Some(true));
1395 assert_eq!(parsed.objects, Some(true));
1396 assert_eq!(parsed.scenarios, Some(true));
1397 assert_eq!(parsed.format_cells, Some(false));
1398 assert!(parsed.sort.is_none());
1399 }
1400
1401 #[test]
1402 fn test_sheet_pr_roundtrip() {
1403 let pr = SheetPr {
1404 code_name: Some("Sheet1".to_string()),
1405 tab_color: Some(TabColor {
1406 rgb: Some("FF0000".to_string()),
1407 theme: None,
1408 indexed: None,
1409 }),
1410 ..SheetPr::default()
1411 };
1412 let xml = quick_xml::se::to_string(&pr).unwrap();
1413 let parsed: SheetPr = quick_xml::de::from_str(&xml).unwrap();
1414 assert_eq!(parsed.code_name, Some("Sheet1".to_string()));
1415 assert!(parsed.tab_color.is_some());
1416 assert_eq!(parsed.tab_color.unwrap().rgb, Some("FF0000".to_string()));
1417 }
1418
1419 #[test]
1420 fn test_sheet_format_pr_extended_fields() {
1421 let fmt = SheetFormatPr {
1422 default_row_height: 15.0,
1423 default_col_width: Some(10.0),
1424 custom_height: Some(true),
1425 outline_level_row: Some(2),
1426 outline_level_col: Some(1),
1427 };
1428 let xml = quick_xml::se::to_string(&fmt).unwrap();
1429 let parsed: SheetFormatPr = quick_xml::de::from_str(&xml).unwrap();
1430 assert_eq!(parsed.default_row_height, 15.0);
1431 assert_eq!(parsed.default_col_width, Some(10.0));
1432 assert_eq!(parsed.custom_height, Some(true));
1433 assert_eq!(parsed.outline_level_row, Some(2));
1434 assert_eq!(parsed.outline_level_col, Some(1));
1435 }
1436
1437 #[test]
1438 fn test_compact_cell_ref_basic() {
1439 let r = CompactCellRef::new("A1");
1440 assert_eq!(r.as_str(), "A1");
1441 assert_eq!(r.len, 2);
1442 }
1443
1444 #[test]
1445 fn test_compact_cell_ref_max_length() {
1446 let r = CompactCellRef::new("XFD1048576");
1447 assert_eq!(r.as_str(), "XFD1048576");
1448 assert_eq!(r.len, 10);
1449 }
1450
1451 #[test]
1452 fn test_compact_cell_ref_various_lengths() {
1453 for s in &["A1", "B5", "Z99", "AA100", "XFD1048576"] {
1454 let r = CompactCellRef::new(s);
1455 assert_eq!(r.as_str(), *s);
1456 }
1457 }
1458
1459 #[test]
1460 fn test_compact_cell_ref_display() {
1461 let r = CompactCellRef::new("C3");
1462 assert_eq!(format!("{r}"), "C3");
1463 }
1464
1465 #[test]
1466 fn test_compact_cell_ref_debug() {
1467 let r = CompactCellRef::new("C3");
1468 let dbg = format!("{r:?}");
1469 assert!(dbg.contains("CompactCellRef"));
1470 assert!(dbg.contains("C3"));
1471 }
1472
1473 #[test]
1474 fn test_compact_cell_ref_default() {
1475 let r = CompactCellRef::default();
1476 assert_eq!(r.as_str(), "");
1477 assert_eq!(r.len, 0);
1478 }
1479
1480 #[test]
1481 fn test_compact_cell_ref_from_str() {
1482 let r: CompactCellRef = "D4".into();
1483 assert_eq!(r.as_str(), "D4");
1484 }
1485
1486 #[test]
1487 fn test_compact_cell_ref_from_string() {
1488 let r: CompactCellRef = String::from("E5").into();
1489 assert_eq!(r.as_str(), "E5");
1490 }
1491
1492 #[test]
1493 fn test_compact_cell_ref_as_ref_str() {
1494 let r = CompactCellRef::new("F6");
1495 let s: &str = r.as_ref();
1496 assert_eq!(s, "F6");
1497 }
1498
1499 #[test]
1500 fn test_compact_cell_ref_partial_eq_str() {
1501 let r = CompactCellRef::new("G7");
1502 assert_eq!(r, "G7");
1503 assert!(r == "G7");
1504 assert!(r != "H8");
1505 }
1506
1507 #[test]
1508 fn test_compact_cell_ref_copy() {
1509 let r1 = CompactCellRef::new("A1");
1510 let r2 = r1;
1511 assert_eq!(r1.as_str(), "A1");
1512 assert_eq!(r2.as_str(), "A1");
1513 }
1514
1515 #[test]
1516 fn test_compact_cell_ref_serde_roundtrip() {
1517 let cell = Cell {
1518 r: CompactCellRef::new("XFD1048576"),
1519 col: 16384,
1520 s: None,
1521 t: CellTypeTag::None,
1522 v: Some("42".to_string()),
1523 f: None,
1524 is: None,
1525 };
1526 let xml = quick_xml::se::to_string(&cell).unwrap();
1527 assert!(xml.contains("XFD1048576"));
1528 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1529 assert_eq!(parsed.r, "XFD1048576");
1530 assert_eq!(parsed.v, Some("42".to_string()));
1531 }
1532
1533 #[test]
1534 #[should_panic(expected = "cell reference too long")]
1535 fn test_compact_cell_ref_panics_on_overflow() {
1536 CompactCellRef::new("ABCDEFGHIJK");
1537 }
1538
1539 #[test]
1540 fn test_compact_cell_ref_from_coordinates_a1() {
1541 let r = CompactCellRef::from_coordinates(1, 1);
1542 assert_eq!(r.as_str(), "A1");
1543 }
1544
1545 #[test]
1546 fn test_compact_cell_ref_from_coordinates_z26() {
1547 let r = CompactCellRef::from_coordinates(26, 26);
1548 assert_eq!(r.as_str(), "Z26");
1549 }
1550
1551 #[test]
1552 fn test_compact_cell_ref_from_coordinates_aa1() {
1553 let r = CompactCellRef::from_coordinates(27, 1);
1554 assert_eq!(r.as_str(), "AA1");
1555 }
1556
1557 #[test]
1558 fn test_compact_cell_ref_from_coordinates_max() {
1559 let r = CompactCellRef::from_coordinates(16384, 1048576);
1560 assert_eq!(r.as_str(), "XFD1048576");
1561 }
1562
1563 #[test]
1564 fn test_compact_cell_ref_from_coordinates_zz1() {
1565 let r = CompactCellRef::from_coordinates(702, 1);
1566 assert_eq!(r.as_str(), "ZZ1");
1567 }
1568
1569 #[test]
1570 fn test_compact_cell_ref_from_coordinates_roundtrip() {
1571 fn col_to_name(mut col: u32) -> String {
1572 let mut result = String::new();
1573 while col > 0 {
1574 col -= 1;
1575 result.insert(0, (b'A' + (col % 26) as u8) as char);
1576 col /= 26;
1577 }
1578 result
1579 }
1580 for c in [1, 2, 26, 27, 52, 100, 256, 702, 703, 16384] {
1581 let r = CompactCellRef::from_coordinates(c, 1);
1582 let expected = format!("{}1", col_to_name(c));
1583 assert_eq!(r.as_str(), expected, "mismatch for col={c}");
1584 }
1585 }
1586}