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 let s = String::deserialize(deserializer)?;
479 Ok(CompactCellRef::new(&s))
480 }
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
485pub struct Cell {
486 #[serde(rename = "@r")]
488 pub r: CompactCellRef,
489
490 #[serde(skip)]
493 pub col: u32,
494
495 #[serde(rename = "@s", skip_serializing_if = "Option::is_none")]
497 pub s: Option<u32>,
498
499 #[serde(rename = "@t", default, skip_serializing_if = "CellTypeTag::is_none")]
501 pub t: CellTypeTag,
502
503 #[serde(rename = "v", skip_serializing_if = "Option::is_none")]
505 pub v: Option<String>,
506
507 #[serde(rename = "f", skip_serializing_if = "Option::is_none")]
509 pub f: Option<Box<CellFormula>>,
510
511 #[serde(rename = "is", skip_serializing_if = "Option::is_none")]
513 pub is: Option<Box<InlineString>>,
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
518pub enum CellTypeTag {
519 #[default]
521 None,
522 SharedString,
524 Number,
526 Boolean,
528 Error,
530 InlineString,
532 FormulaString,
534 Date,
536}
537
538impl CellTypeTag {
539 pub fn is_none(&self) -> bool {
541 matches!(self, CellTypeTag::None)
542 }
543
544 pub fn as_str(&self) -> Option<&'static str> {
546 match self {
547 CellTypeTag::None => Option::None,
548 CellTypeTag::SharedString => Some("s"),
549 CellTypeTag::Number => Some("n"),
550 CellTypeTag::Boolean => Some("b"),
551 CellTypeTag::Error => Some("e"),
552 CellTypeTag::InlineString => Some("inlineStr"),
553 CellTypeTag::FormulaString => Some("str"),
554 CellTypeTag::Date => Some("d"),
555 }
556 }
557}
558
559impl Serialize for CellTypeTag {
560 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
561 match self.as_str() {
562 Some(s) => serializer.serialize_str(s),
563 Option::None => serializer.serialize_none(),
564 }
565 }
566}
567
568impl<'de> Deserialize<'de> for CellTypeTag {
569 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
570 let s = String::deserialize(deserializer)?;
571 match s.as_str() {
572 "s" => Ok(CellTypeTag::SharedString),
573 "n" => Ok(CellTypeTag::Number),
574 "b" => Ok(CellTypeTag::Boolean),
575 "e" => Ok(CellTypeTag::Error),
576 "inlineStr" => Ok(CellTypeTag::InlineString),
577 "str" => Ok(CellTypeTag::FormulaString),
578 "d" => Ok(CellTypeTag::Date),
579 _ => Ok(CellTypeTag::None),
580 }
581 }
582}
583
584#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
586pub struct CellFormula {
587 #[serde(rename = "@t", skip_serializing_if = "Option::is_none")]
588 pub t: Option<String>,
589
590 #[serde(rename = "@ref", skip_serializing_if = "Option::is_none")]
591 pub reference: Option<String>,
592
593 #[serde(rename = "@si", skip_serializing_if = "Option::is_none")]
594 pub si: Option<u32>,
595
596 #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
597 pub value: Option<String>,
598}
599
600#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
602pub struct InlineString {
603 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
604 pub t: Option<String>,
605}
606
607#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
609pub struct AutoFilter {
610 #[serde(rename = "@ref")]
611 pub reference: String,
612}
613
614#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct DataValidations {
617 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
618 pub count: Option<u32>,
619
620 #[serde(
621 rename = "@disablePrompts",
622 skip_serializing_if = "Option::is_none",
623 default
624 )]
625 pub disable_prompts: Option<bool>,
626
627 #[serde(rename = "@xWindow", skip_serializing_if = "Option::is_none", default)]
628 pub x_window: Option<u32>,
629
630 #[serde(rename = "@yWindow", skip_serializing_if = "Option::is_none", default)]
631 pub y_window: Option<u32>,
632
633 #[serde(rename = "dataValidation", default)]
634 pub data_validations: Vec<DataValidation>,
635}
636
637#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
639pub struct DataValidation {
640 #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
641 pub validation_type: Option<String>,
642
643 #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
644 pub operator: Option<String>,
645
646 #[serde(rename = "@allowBlank", skip_serializing_if = "Option::is_none")]
647 pub allow_blank: Option<bool>,
648
649 #[serde(
650 rename = "@showDropDown",
651 skip_serializing_if = "Option::is_none",
652 default
653 )]
654 pub show_drop_down: Option<bool>,
655
656 #[serde(rename = "@showInputMessage", skip_serializing_if = "Option::is_none")]
657 pub show_input_message: Option<bool>,
658
659 #[serde(rename = "@showErrorMessage", skip_serializing_if = "Option::is_none")]
660 pub show_error_message: Option<bool>,
661
662 #[serde(rename = "@errorStyle", skip_serializing_if = "Option::is_none")]
663 pub error_style: Option<String>,
664
665 #[serde(rename = "@imeMode", skip_serializing_if = "Option::is_none", default)]
666 pub ime_mode: Option<String>,
667
668 #[serde(rename = "@errorTitle", skip_serializing_if = "Option::is_none")]
669 pub error_title: Option<String>,
670
671 #[serde(rename = "@error", skip_serializing_if = "Option::is_none")]
672 pub error: Option<String>,
673
674 #[serde(rename = "@promptTitle", skip_serializing_if = "Option::is_none")]
675 pub prompt_title: Option<String>,
676
677 #[serde(rename = "@prompt", skip_serializing_if = "Option::is_none")]
678 pub prompt: Option<String>,
679
680 #[serde(rename = "@sqref")]
681 pub sqref: String,
682
683 #[serde(rename = "formula1", skip_serializing_if = "Option::is_none")]
684 pub formula1: Option<String>,
685
686 #[serde(rename = "formula2", skip_serializing_if = "Option::is_none")]
687 pub formula2: Option<String>,
688}
689
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
692pub struct MergeCells {
693 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
694 pub count: Option<u32>,
695
696 #[serde(rename = "mergeCell", default)]
697 pub merge_cells: Vec<MergeCell>,
698}
699
700#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
702pub struct MergeCell {
703 #[serde(rename = "@ref")]
704 pub reference: String,
705}
706
707#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
709pub struct Hyperlinks {
710 #[serde(rename = "hyperlink", default)]
711 pub hyperlinks: Vec<Hyperlink>,
712}
713
714#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
716pub struct Hyperlink {
717 #[serde(rename = "@ref")]
718 pub reference: String,
719
720 #[serde(
721 rename = "@r:id",
722 alias = "@id",
723 skip_serializing_if = "Option::is_none"
724 )]
725 pub r_id: Option<String>,
726
727 #[serde(rename = "@location", skip_serializing_if = "Option::is_none")]
728 pub location: Option<String>,
729
730 #[serde(rename = "@display", skip_serializing_if = "Option::is_none")]
731 pub display: Option<String>,
732
733 #[serde(rename = "@tooltip", skip_serializing_if = "Option::is_none")]
734 pub tooltip: Option<String>,
735}
736
737#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
739pub struct PageMargins {
740 #[serde(rename = "@left")]
741 pub left: f64,
742
743 #[serde(rename = "@right")]
744 pub right: f64,
745
746 #[serde(rename = "@top")]
747 pub top: f64,
748
749 #[serde(rename = "@bottom")]
750 pub bottom: f64,
751
752 #[serde(rename = "@header")]
753 pub header: f64,
754
755 #[serde(rename = "@footer")]
756 pub footer: f64,
757}
758
759#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
761pub struct PageSetup {
762 #[serde(rename = "@paperSize", skip_serializing_if = "Option::is_none")]
763 pub paper_size: Option<u32>,
764
765 #[serde(rename = "@orientation", skip_serializing_if = "Option::is_none")]
766 pub orientation: Option<String>,
767
768 #[serde(rename = "@scale", skip_serializing_if = "Option::is_none")]
769 pub scale: Option<u32>,
770
771 #[serde(rename = "@fitToWidth", skip_serializing_if = "Option::is_none")]
772 pub fit_to_width: Option<u32>,
773
774 #[serde(rename = "@fitToHeight", skip_serializing_if = "Option::is_none")]
775 pub fit_to_height: Option<u32>,
776
777 #[serde(rename = "@firstPageNumber", skip_serializing_if = "Option::is_none")]
778 pub first_page_number: Option<u32>,
779
780 #[serde(rename = "@horizontalDpi", skip_serializing_if = "Option::is_none")]
781 pub horizontal_dpi: Option<u32>,
782
783 #[serde(rename = "@verticalDpi", skip_serializing_if = "Option::is_none")]
784 pub vertical_dpi: Option<u32>,
785
786 #[serde(
787 rename = "@r:id",
788 alias = "@id",
789 skip_serializing_if = "Option::is_none"
790 )]
791 pub r_id: Option<String>,
792}
793
794#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
796pub struct HeaderFooter {
797 #[serde(rename = "oddHeader", skip_serializing_if = "Option::is_none")]
798 pub odd_header: Option<String>,
799
800 #[serde(rename = "oddFooter", skip_serializing_if = "Option::is_none")]
801 pub odd_footer: Option<String>,
802}
803
804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
806pub struct PrintOptions {
807 #[serde(rename = "@gridLines", skip_serializing_if = "Option::is_none")]
808 pub grid_lines: Option<bool>,
809
810 #[serde(rename = "@headings", skip_serializing_if = "Option::is_none")]
811 pub headings: Option<bool>,
812
813 #[serde(
814 rename = "@horizontalCentered",
815 skip_serializing_if = "Option::is_none"
816 )]
817 pub horizontal_centered: Option<bool>,
818
819 #[serde(rename = "@verticalCentered", skip_serializing_if = "Option::is_none")]
820 pub vertical_centered: Option<bool>,
821}
822
823#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
825pub struct RowBreaks {
826 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
827 pub count: Option<u32>,
828
829 #[serde(rename = "@manualBreakCount", skip_serializing_if = "Option::is_none")]
830 pub manual_break_count: Option<u32>,
831
832 #[serde(rename = "brk", default)]
833 pub brk: Vec<Break>,
834}
835
836#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
838pub struct Break {
839 #[serde(rename = "@id")]
840 pub id: u32,
841
842 #[serde(rename = "@max", skip_serializing_if = "Option::is_none")]
843 pub max: Option<u32>,
844
845 #[serde(rename = "@man", skip_serializing_if = "Option::is_none")]
846 pub man: Option<bool>,
847}
848
849#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
851pub struct DrawingRef {
852 #[serde(rename = "@r:id", alias = "@id")]
853 pub r_id: String,
854}
855
856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
858pub struct LegacyDrawingRef {
859 #[serde(rename = "@r:id", alias = "@id")]
860 pub r_id: String,
861}
862
863#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
865pub struct TableParts {
866 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
867 pub count: Option<u32>,
868
869 #[serde(rename = "tablePart", default)]
870 pub table_parts: Vec<TablePart>,
871}
872
873#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
875pub struct TablePart {
876 #[serde(rename = "@r:id", alias = "@id")]
877 pub r_id: String,
878}
879
880#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
882pub struct ConditionalFormatting {
883 #[serde(rename = "@sqref")]
884 pub sqref: String,
885
886 #[serde(rename = "cfRule", default)]
887 pub cf_rules: Vec<CfRule>,
888}
889
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
892pub struct CfRule {
893 #[serde(rename = "@type")]
894 pub rule_type: String,
895
896 #[serde(rename = "@dxfId", skip_serializing_if = "Option::is_none")]
897 pub dxf_id: Option<u32>,
898
899 #[serde(rename = "@priority")]
900 pub priority: u32,
901
902 #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
903 pub operator: Option<String>,
904
905 #[serde(rename = "@text", skip_serializing_if = "Option::is_none")]
906 pub text: Option<String>,
907
908 #[serde(rename = "@stopIfTrue", skip_serializing_if = "Option::is_none")]
909 pub stop_if_true: Option<bool>,
910
911 #[serde(rename = "@aboveAverage", skip_serializing_if = "Option::is_none")]
912 pub above_average: Option<bool>,
913
914 #[serde(rename = "@equalAverage", skip_serializing_if = "Option::is_none")]
915 pub equal_average: Option<bool>,
916
917 #[serde(rename = "@percent", skip_serializing_if = "Option::is_none")]
918 pub percent: Option<bool>,
919
920 #[serde(rename = "@rank", skip_serializing_if = "Option::is_none")]
921 pub rank: Option<u32>,
922
923 #[serde(rename = "@bottom", skip_serializing_if = "Option::is_none")]
924 pub bottom: Option<bool>,
925
926 #[serde(rename = "formula", default, skip_serializing_if = "Vec::is_empty")]
927 pub formulas: Vec<String>,
928
929 #[serde(rename = "colorScale", skip_serializing_if = "Option::is_none")]
930 pub color_scale: Option<CfColorScale>,
931
932 #[serde(rename = "dataBar", skip_serializing_if = "Option::is_none")]
933 pub data_bar: Option<CfDataBar>,
934
935 #[serde(rename = "iconSet", skip_serializing_if = "Option::is_none")]
936 pub icon_set: Option<CfIconSet>,
937}
938
939#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
941pub struct CfColorScale {
942 #[serde(rename = "cfvo", default)]
943 pub cfvos: Vec<CfVo>,
944
945 #[serde(rename = "color", default)]
946 pub colors: Vec<CfColor>,
947}
948
949#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
951pub struct CfDataBar {
952 #[serde(rename = "@showValue", skip_serializing_if = "Option::is_none")]
953 pub show_value: Option<bool>,
954
955 #[serde(rename = "cfvo", default)]
956 pub cfvos: Vec<CfVo>,
957
958 #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
959 pub color: Option<CfColor>,
960}
961
962#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
964pub struct CfIconSet {
965 #[serde(rename = "@iconSet", skip_serializing_if = "Option::is_none")]
966 pub icon_set: Option<String>,
967
968 #[serde(rename = "cfvo", default)]
969 pub cfvos: Vec<CfVo>,
970}
971
972#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
974pub struct CfVo {
975 #[serde(rename = "@type")]
976 pub value_type: String,
977
978 #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
979 pub val: Option<String>,
980}
981
982#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
984pub struct CfColor {
985 #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
986 pub rgb: Option<String>,
987
988 #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
989 pub theme: Option<u32>,
990
991 #[serde(rename = "@tint", skip_serializing_if = "Option::is_none")]
992 pub tint: Option<f64>,
993}
994
995impl Default for WorksheetXml {
996 fn default() -> Self {
997 Self {
998 xmlns: namespaces::SPREADSHEET_ML.to_string(),
999 xmlns_r: namespaces::RELATIONSHIPS.to_string(),
1000 sheet_pr: None,
1001 dimension: None,
1002 sheet_views: None,
1003 sheet_format_pr: None,
1004 cols: None,
1005 sheet_data: SheetData { rows: vec![] },
1006 sheet_protection: None,
1007 auto_filter: None,
1008 merge_cells: None,
1009 conditional_formatting: vec![],
1010 data_validations: None,
1011 hyperlinks: None,
1012 print_options: None,
1013 page_margins: None,
1014 page_setup: None,
1015 header_footer: None,
1016 row_breaks: None,
1017 drawing: None,
1018 legacy_drawing: None,
1019 table_parts: None,
1020 }
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_worksheet_default() {
1030 let ws = WorksheetXml::default();
1031 assert_eq!(ws.xmlns, namespaces::SPREADSHEET_ML);
1032 assert_eq!(ws.xmlns_r, namespaces::RELATIONSHIPS);
1033 assert!(ws.sheet_data.rows.is_empty());
1034 assert!(ws.dimension.is_none());
1035 assert!(ws.sheet_views.is_none());
1036 assert!(ws.cols.is_none());
1037 assert!(ws.merge_cells.is_none());
1038 assert!(ws.page_margins.is_none());
1039 assert!(ws.sheet_pr.is_none());
1040 assert!(ws.sheet_protection.is_none());
1041 }
1042
1043 #[test]
1044 fn test_worksheet_roundtrip() {
1045 let ws = WorksheetXml::default();
1046 let xml = quick_xml::se::to_string(&ws).unwrap();
1047 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1048 assert_eq!(ws.xmlns, parsed.xmlns);
1049 assert_eq!(ws.xmlns_r, parsed.xmlns_r);
1050 assert_eq!(ws.sheet_data.rows.len(), parsed.sheet_data.rows.len());
1051 }
1052
1053 #[test]
1054 fn test_worksheet_with_data() {
1055 let ws = WorksheetXml {
1056 sheet_data: SheetData {
1057 rows: vec![Row {
1058 r: 1,
1059 spans: Some("1:3".to_string()),
1060 s: None,
1061 custom_format: None,
1062 ht: None,
1063 hidden: None,
1064 custom_height: None,
1065 outline_level: None,
1066 cells: vec![
1067 Cell {
1068 r: CompactCellRef::new("A1"),
1069 col: 1,
1070 s: None,
1071 t: CellTypeTag::SharedString,
1072 v: Some("0".to_string()),
1073 f: None,
1074 is: None,
1075 },
1076 Cell {
1077 r: CompactCellRef::new("B1"),
1078 col: 2,
1079 s: None,
1080 t: CellTypeTag::None,
1081 v: Some("42".to_string()),
1082 f: None,
1083 is: None,
1084 },
1085 ],
1086 }],
1087 },
1088 ..WorksheetXml::default()
1089 };
1090
1091 let xml = quick_xml::se::to_string(&ws).unwrap();
1092 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1093 assert_eq!(parsed.sheet_data.rows.len(), 1);
1094 assert_eq!(parsed.sheet_data.rows[0].r, 1);
1095 assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
1096 assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
1097 assert_eq!(
1098 parsed.sheet_data.rows[0].cells[0].t,
1099 CellTypeTag::SharedString
1100 );
1101 assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
1102 assert_eq!(parsed.sheet_data.rows[0].cells[1].r, "B1");
1103 assert_eq!(parsed.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
1104 }
1105
1106 #[test]
1107 fn test_cell_with_formula() {
1108 let cell = Cell {
1109 r: CompactCellRef::new("C1"),
1110 col: 3,
1111 s: None,
1112 t: CellTypeTag::None,
1113 v: Some("84".to_string()),
1114 f: Some(Box::new(CellFormula {
1115 t: None,
1116 reference: None,
1117 si: None,
1118 value: Some("A1+B1".to_string()),
1119 })),
1120 is: None,
1121 };
1122 let xml = quick_xml::se::to_string(&cell).unwrap();
1123 assert!(xml.contains("A1+B1"));
1124 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1125 assert!(parsed.f.is_some());
1126 assert_eq!(parsed.f.unwrap().value, Some("A1+B1".to_string()));
1127 }
1128
1129 #[test]
1130 fn test_cell_with_inline_string() {
1131 let cell = Cell {
1132 r: CompactCellRef::new("A1"),
1133 col: 1,
1134 s: None,
1135 t: CellTypeTag::InlineString,
1136 v: None,
1137 f: None,
1138 is: Some(Box::new(InlineString {
1139 t: Some("Hello World".to_string()),
1140 })),
1141 };
1142 let xml = quick_xml::se::to_string(&cell).unwrap();
1143 assert!(xml.contains("Hello World"));
1144 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1145 assert_eq!(parsed.t, CellTypeTag::InlineString);
1146 assert!(parsed.is.is_some());
1147 assert_eq!(parsed.is.unwrap().t, Some("Hello World".to_string()));
1148 }
1149
1150 #[test]
1151 fn test_parse_real_excel_worksheet() {
1152 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1153<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
1154 <dimension ref="A1:B2"/>
1155 <sheetData>
1156 <row r="1" spans="1:2">
1157 <c r="A1" t="s"><v>0</v></c>
1158 <c r="B1" t="s"><v>1</v></c>
1159 </row>
1160 <row r="2" spans="1:2">
1161 <c r="A2"><v>100</v></c>
1162 <c r="B2"><v>200</v></c>
1163 </row>
1164 </sheetData>
1165</worksheet>"#;
1166
1167 let parsed: WorksheetXml = quick_xml::de::from_str(xml).unwrap();
1168 assert_eq!(parsed.dimension.as_ref().unwrap().reference, "A1:B2");
1169 assert_eq!(parsed.sheet_data.rows.len(), 2);
1170 assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
1171 assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
1172 assert_eq!(
1173 parsed.sheet_data.rows[0].cells[0].t,
1174 CellTypeTag::SharedString
1175 );
1176 assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
1177 assert_eq!(parsed.sheet_data.rows[1].cells[0].r, "A2");
1178 assert_eq!(
1179 parsed.sheet_data.rows[1].cells[0].v,
1180 Some("100".to_string())
1181 );
1182 }
1183
1184 #[test]
1185 fn test_worksheet_with_merge_cells() {
1186 let ws = WorksheetXml {
1187 merge_cells: Some(MergeCells {
1188 count: Some(1),
1189 merge_cells: vec![MergeCell {
1190 reference: "A1:B2".to_string(),
1191 }],
1192 }),
1193 ..WorksheetXml::default()
1194 };
1195 let xml = quick_xml::se::to_string(&ws).unwrap();
1196 assert!(xml.contains("mergeCells"));
1197 assert!(xml.contains("A1:B2"));
1198 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1199 assert!(parsed.merge_cells.is_some());
1200 assert_eq!(parsed.merge_cells.as_ref().unwrap().merge_cells.len(), 1);
1201 }
1202
1203 #[test]
1204 fn test_empty_sheet_data_serialization() {
1205 let sd = SheetData { rows: vec![] };
1206 let xml = quick_xml::se::to_string(&sd).unwrap();
1207 let parsed: SheetData = quick_xml::de::from_str(&xml).unwrap();
1209 assert!(parsed.rows.is_empty());
1210 }
1211
1212 #[test]
1213 fn test_row_optional_fields_not_serialized() {
1214 let row = Row {
1215 r: 1,
1216 spans: None,
1217 s: None,
1218 custom_format: None,
1219 ht: None,
1220 hidden: None,
1221 custom_height: None,
1222 outline_level: None,
1223 cells: vec![],
1224 };
1225 let xml = quick_xml::se::to_string(&row).unwrap();
1226 assert!(!xml.contains("spans"));
1227 assert!(!xml.contains("ht"));
1228 assert!(!xml.contains("hidden"));
1229 }
1230
1231 #[test]
1232 fn test_cell_type_tag_as_str() {
1233 assert_eq!(CellTypeTag::Boolean.as_str(), Some("b"));
1234 assert_eq!(CellTypeTag::Date.as_str(), Some("d"));
1235 assert_eq!(CellTypeTag::Error.as_str(), Some("e"));
1236 assert_eq!(CellTypeTag::InlineString.as_str(), Some("inlineStr"));
1237 assert_eq!(CellTypeTag::Number.as_str(), Some("n"));
1238 assert_eq!(CellTypeTag::SharedString.as_str(), Some("s"));
1239 assert_eq!(CellTypeTag::FormulaString.as_str(), Some("str"));
1240 assert_eq!(CellTypeTag::None.as_str(), Option::None);
1241 }
1242
1243 #[test]
1244 fn test_cell_type_tag_default_is_none() {
1245 assert_eq!(CellTypeTag::default(), CellTypeTag::None);
1246 assert!(CellTypeTag::None.is_none());
1247 assert!(!CellTypeTag::SharedString.is_none());
1248 }
1249
1250 #[test]
1251 fn test_cell_type_tag_serde_round_trip() {
1252 let variants = [
1253 (CellTypeTag::SharedString, "s"),
1254 (CellTypeTag::Number, "n"),
1255 (CellTypeTag::Boolean, "b"),
1256 (CellTypeTag::Error, "e"),
1257 (CellTypeTag::InlineString, "inlineStr"),
1258 (CellTypeTag::FormulaString, "str"),
1259 (CellTypeTag::Date, "d"),
1260 ];
1261 for (tag, expected_str) in &variants {
1262 let cell = Cell {
1263 r: CompactCellRef::new("A1"),
1264 col: 1,
1265 s: None,
1266 t: *tag,
1267 v: Some("0".to_string()),
1268 f: None,
1269 is: None,
1270 };
1271 let xml = quick_xml::se::to_string(&cell).unwrap();
1272 assert!(
1273 xml.contains(&format!("t=\"{expected_str}\"")),
1274 "expected t=\"{expected_str}\" in: {xml}"
1275 );
1276 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1277 assert_eq!(parsed.t, *tag);
1278 }
1279
1280 let cell_none = Cell {
1281 r: CompactCellRef::new("A1"),
1282 col: 1,
1283 s: None,
1284 t: CellTypeTag::None,
1285 v: Some("42".to_string()),
1286 f: None,
1287 is: None,
1288 };
1289 let xml = quick_xml::se::to_string(&cell_none).unwrap();
1290 assert!(
1291 !xml.contains("t="),
1292 "None variant should not emit t attribute: {xml}"
1293 );
1294 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1295 assert_eq!(parsed.t, CellTypeTag::None);
1296 }
1297
1298 #[test]
1299 fn test_worksheet_with_cols() {
1300 let ws = WorksheetXml {
1301 cols: Some(Cols {
1302 cols: vec![Col {
1303 min: 1,
1304 max: 1,
1305 width: Some(15.0),
1306 style: None,
1307 hidden: None,
1308 custom_width: Some(true),
1309 outline_level: None,
1310 }],
1311 }),
1312 ..WorksheetXml::default()
1313 };
1314 let xml = quick_xml::se::to_string(&ws).unwrap();
1315 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1316 assert!(parsed.cols.is_some());
1317 let cols = parsed.cols.unwrap();
1318 assert_eq!(cols.cols.len(), 1);
1319 assert_eq!(cols.cols[0].min, 1);
1320 assert_eq!(cols.cols[0].width, Some(15.0));
1321 assert_eq!(cols.cols[0].custom_width, Some(true));
1322 }
1323
1324 #[test]
1325 fn test_sheet_protection_roundtrip() {
1326 let prot = SheetProtection {
1327 password: Some("ABCD".to_string()),
1328 sheet: Some(true),
1329 objects: Some(true),
1330 scenarios: Some(true),
1331 format_cells: Some(false),
1332 ..SheetProtection::default()
1333 };
1334 let xml = quick_xml::se::to_string(&prot).unwrap();
1335 let parsed: SheetProtection = quick_xml::de::from_str(&xml).unwrap();
1336 assert_eq!(parsed.password, Some("ABCD".to_string()));
1337 assert_eq!(parsed.sheet, Some(true));
1338 assert_eq!(parsed.objects, Some(true));
1339 assert_eq!(parsed.scenarios, Some(true));
1340 assert_eq!(parsed.format_cells, Some(false));
1341 assert!(parsed.sort.is_none());
1342 }
1343
1344 #[test]
1345 fn test_sheet_pr_roundtrip() {
1346 let pr = SheetPr {
1347 code_name: Some("Sheet1".to_string()),
1348 tab_color: Some(TabColor {
1349 rgb: Some("FF0000".to_string()),
1350 theme: None,
1351 indexed: None,
1352 }),
1353 ..SheetPr::default()
1354 };
1355 let xml = quick_xml::se::to_string(&pr).unwrap();
1356 let parsed: SheetPr = quick_xml::de::from_str(&xml).unwrap();
1357 assert_eq!(parsed.code_name, Some("Sheet1".to_string()));
1358 assert!(parsed.tab_color.is_some());
1359 assert_eq!(parsed.tab_color.unwrap().rgb, Some("FF0000".to_string()));
1360 }
1361
1362 #[test]
1363 fn test_sheet_format_pr_extended_fields() {
1364 let fmt = SheetFormatPr {
1365 default_row_height: 15.0,
1366 default_col_width: Some(10.0),
1367 custom_height: Some(true),
1368 outline_level_row: Some(2),
1369 outline_level_col: Some(1),
1370 };
1371 let xml = quick_xml::se::to_string(&fmt).unwrap();
1372 let parsed: SheetFormatPr = quick_xml::de::from_str(&xml).unwrap();
1373 assert_eq!(parsed.default_row_height, 15.0);
1374 assert_eq!(parsed.default_col_width, Some(10.0));
1375 assert_eq!(parsed.custom_height, Some(true));
1376 assert_eq!(parsed.outline_level_row, Some(2));
1377 assert_eq!(parsed.outline_level_col, Some(1));
1378 }
1379
1380 #[test]
1381 fn test_compact_cell_ref_basic() {
1382 let r = CompactCellRef::new("A1");
1383 assert_eq!(r.as_str(), "A1");
1384 assert_eq!(r.len, 2);
1385 }
1386
1387 #[test]
1388 fn test_compact_cell_ref_max_length() {
1389 let r = CompactCellRef::new("XFD1048576");
1390 assert_eq!(r.as_str(), "XFD1048576");
1391 assert_eq!(r.len, 10);
1392 }
1393
1394 #[test]
1395 fn test_compact_cell_ref_various_lengths() {
1396 for s in &["A1", "B5", "Z99", "AA100", "XFD1048576"] {
1397 let r = CompactCellRef::new(s);
1398 assert_eq!(r.as_str(), *s);
1399 }
1400 }
1401
1402 #[test]
1403 fn test_compact_cell_ref_display() {
1404 let r = CompactCellRef::new("C3");
1405 assert_eq!(format!("{r}"), "C3");
1406 }
1407
1408 #[test]
1409 fn test_compact_cell_ref_debug() {
1410 let r = CompactCellRef::new("C3");
1411 let dbg = format!("{r:?}");
1412 assert!(dbg.contains("CompactCellRef"));
1413 assert!(dbg.contains("C3"));
1414 }
1415
1416 #[test]
1417 fn test_compact_cell_ref_default() {
1418 let r = CompactCellRef::default();
1419 assert_eq!(r.as_str(), "");
1420 assert_eq!(r.len, 0);
1421 }
1422
1423 #[test]
1424 fn test_compact_cell_ref_from_str() {
1425 let r: CompactCellRef = "D4".into();
1426 assert_eq!(r.as_str(), "D4");
1427 }
1428
1429 #[test]
1430 fn test_compact_cell_ref_from_string() {
1431 let r: CompactCellRef = String::from("E5").into();
1432 assert_eq!(r.as_str(), "E5");
1433 }
1434
1435 #[test]
1436 fn test_compact_cell_ref_as_ref_str() {
1437 let r = CompactCellRef::new("F6");
1438 let s: &str = r.as_ref();
1439 assert_eq!(s, "F6");
1440 }
1441
1442 #[test]
1443 fn test_compact_cell_ref_partial_eq_str() {
1444 let r = CompactCellRef::new("G7");
1445 assert_eq!(r, "G7");
1446 assert!(r == "G7");
1447 assert!(r != "H8");
1448 }
1449
1450 #[test]
1451 fn test_compact_cell_ref_copy() {
1452 let r1 = CompactCellRef::new("A1");
1453 let r2 = r1;
1454 assert_eq!(r1.as_str(), "A1");
1455 assert_eq!(r2.as_str(), "A1");
1456 }
1457
1458 #[test]
1459 fn test_compact_cell_ref_serde_roundtrip() {
1460 let cell = Cell {
1461 r: CompactCellRef::new("XFD1048576"),
1462 col: 16384,
1463 s: None,
1464 t: CellTypeTag::None,
1465 v: Some("42".to_string()),
1466 f: None,
1467 is: None,
1468 };
1469 let xml = quick_xml::se::to_string(&cell).unwrap();
1470 assert!(xml.contains("XFD1048576"));
1471 let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
1472 assert_eq!(parsed.r, "XFD1048576");
1473 assert_eq!(parsed.v, Some("42".to_string()));
1474 }
1475
1476 #[test]
1477 #[should_panic(expected = "cell reference too long")]
1478 fn test_compact_cell_ref_panics_on_overflow() {
1479 CompactCellRef::new("ABCDEFGHIJK");
1480 }
1481
1482 #[test]
1483 fn test_compact_cell_ref_from_coordinates_a1() {
1484 let r = CompactCellRef::from_coordinates(1, 1);
1485 assert_eq!(r.as_str(), "A1");
1486 }
1487
1488 #[test]
1489 fn test_compact_cell_ref_from_coordinates_z26() {
1490 let r = CompactCellRef::from_coordinates(26, 26);
1491 assert_eq!(r.as_str(), "Z26");
1492 }
1493
1494 #[test]
1495 fn test_compact_cell_ref_from_coordinates_aa1() {
1496 let r = CompactCellRef::from_coordinates(27, 1);
1497 assert_eq!(r.as_str(), "AA1");
1498 }
1499
1500 #[test]
1501 fn test_compact_cell_ref_from_coordinates_max() {
1502 let r = CompactCellRef::from_coordinates(16384, 1048576);
1503 assert_eq!(r.as_str(), "XFD1048576");
1504 }
1505
1506 #[test]
1507 fn test_compact_cell_ref_from_coordinates_zz1() {
1508 let r = CompactCellRef::from_coordinates(702, 1);
1509 assert_eq!(r.as_str(), "ZZ1");
1510 }
1511
1512 #[test]
1513 fn test_compact_cell_ref_from_coordinates_roundtrip() {
1514 fn col_to_name(mut col: u32) -> String {
1515 let mut result = String::new();
1516 while col > 0 {
1517 col -= 1;
1518 result.insert(0, (b'A' + (col % 26) as u8) as char);
1519 col /= 26;
1520 }
1521 result
1522 }
1523 for c in [1, 2, 26, 27, 52, 100, 256, 702, 703, 16384] {
1524 let r = CompactCellRef::from_coordinates(c, 1);
1525 let expected = format!("{}1", col_to_name(c));
1526 assert_eq!(r.as_str(), expected, "mismatch for col={c}");
1527 }
1528 }
1529}