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