Skip to main content

sheetkit_xml/
worksheet.rs

1//! Worksheet XML schema structures.
2//!
3//! Represents `xl/worksheets/sheet*.xml` in the OOXML package.
4
5use serde::{Deserialize, Serialize};
6
7use crate::namespaces;
8
9/// Worksheet root element.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename = "worksheet")]
12pub struct WorksheetXml {
13    #[serde(rename = "@xmlns")]
14    pub xmlns: String,
15
16    #[serde(rename = "@xmlns:r")]
17    pub xmlns_r: String,
18
19    #[serde(rename = "sheetPr", skip_serializing_if = "Option::is_none")]
20    pub sheet_pr: Option<SheetPr>,
21
22    #[serde(rename = "dimension", skip_serializing_if = "Option::is_none")]
23    pub dimension: Option<Dimension>,
24
25    #[serde(rename = "sheetViews", skip_serializing_if = "Option::is_none")]
26    pub sheet_views: Option<SheetViews>,
27
28    #[serde(rename = "sheetFormatPr", skip_serializing_if = "Option::is_none")]
29    pub sheet_format_pr: Option<SheetFormatPr>,
30
31    #[serde(rename = "cols", skip_serializing_if = "Option::is_none")]
32    pub cols: Option<Cols>,
33
34    #[serde(rename = "sheetData")]
35    pub sheet_data: SheetData,
36
37    #[serde(rename = "sheetProtection", skip_serializing_if = "Option::is_none")]
38    pub sheet_protection: Option<SheetProtection>,
39
40    #[serde(rename = "autoFilter", skip_serializing_if = "Option::is_none")]
41    pub auto_filter: Option<AutoFilter>,
42
43    #[serde(rename = "mergeCells", skip_serializing_if = "Option::is_none")]
44    pub merge_cells: Option<MergeCells>,
45
46    #[serde(
47        rename = "conditionalFormatting",
48        default,
49        skip_serializing_if = "Vec::is_empty"
50    )]
51    pub conditional_formatting: Vec<ConditionalFormatting>,
52
53    #[serde(rename = "dataValidations", skip_serializing_if = "Option::is_none")]
54    pub data_validations: Option<DataValidations>,
55
56    #[serde(rename = "hyperlinks", skip_serializing_if = "Option::is_none")]
57    pub hyperlinks: Option<Hyperlinks>,
58
59    #[serde(rename = "printOptions", skip_serializing_if = "Option::is_none")]
60    pub print_options: Option<PrintOptions>,
61
62    #[serde(rename = "pageMargins", skip_serializing_if = "Option::is_none")]
63    pub page_margins: Option<PageMargins>,
64
65    #[serde(rename = "pageSetup", skip_serializing_if = "Option::is_none")]
66    pub page_setup: Option<PageSetup>,
67
68    #[serde(rename = "headerFooter", skip_serializing_if = "Option::is_none")]
69    pub header_footer: Option<HeaderFooter>,
70
71    #[serde(rename = "rowBreaks", skip_serializing_if = "Option::is_none")]
72    pub row_breaks: Option<RowBreaks>,
73
74    #[serde(rename = "drawing", skip_serializing_if = "Option::is_none")]
75    pub drawing: Option<DrawingRef>,
76
77    #[serde(rename = "legacyDrawing", skip_serializing_if = "Option::is_none")]
78    pub legacy_drawing: Option<LegacyDrawingRef>,
79
80    #[serde(rename = "tableParts", skip_serializing_if = "Option::is_none")]
81    pub table_parts: Option<TableParts>,
82}
83
84/// Sheet dimension reference.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct Dimension {
87    #[serde(rename = "@ref")]
88    pub reference: String,
89}
90
91/// Sheet views container.
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct SheetViews {
94    #[serde(rename = "sheetView")]
95    pub sheet_views: Vec<SheetView>,
96}
97
98/// Individual sheet view.
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub struct SheetView {
101    #[serde(rename = "@tabSelected", skip_serializing_if = "Option::is_none")]
102    pub tab_selected: Option<bool>,
103
104    #[serde(rename = "@zoomScale", skip_serializing_if = "Option::is_none")]
105    pub zoom_scale: Option<u32>,
106
107    #[serde(rename = "@workbookViewId")]
108    pub workbook_view_id: u32,
109
110    #[serde(rename = "pane", skip_serializing_if = "Option::is_none")]
111    pub pane: Option<Pane>,
112
113    #[serde(rename = "selection", default)]
114    pub selection: Vec<Selection>,
115}
116
117/// Pane definition for split or frozen panes.
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct Pane {
120    #[serde(rename = "@xSplit", skip_serializing_if = "Option::is_none")]
121    pub x_split: Option<u32>,
122
123    #[serde(rename = "@ySplit", skip_serializing_if = "Option::is_none")]
124    pub y_split: Option<u32>,
125
126    #[serde(rename = "@topLeftCell", skip_serializing_if = "Option::is_none")]
127    pub top_left_cell: Option<String>,
128
129    #[serde(rename = "@activePane", skip_serializing_if = "Option::is_none")]
130    pub active_pane: Option<String>,
131
132    #[serde(rename = "@state", skip_serializing_if = "Option::is_none")]
133    pub state: Option<String>,
134}
135
136/// Cell selection.
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct Selection {
139    #[serde(rename = "@pane", skip_serializing_if = "Option::is_none")]
140    pub pane: Option<String>,
141
142    #[serde(rename = "@activeCell", skip_serializing_if = "Option::is_none")]
143    pub active_cell: Option<String>,
144
145    #[serde(rename = "@sqref", skip_serializing_if = "Option::is_none")]
146    pub sqref: Option<String>,
147}
148
149/// Sheet properties.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
151pub struct SheetPr {
152    #[serde(rename = "@codeName", skip_serializing_if = "Option::is_none")]
153    pub code_name: Option<String>,
154
155    #[serde(rename = "@filterMode", skip_serializing_if = "Option::is_none")]
156    pub filter_mode: Option<bool>,
157
158    #[serde(rename = "tabColor", skip_serializing_if = "Option::is_none")]
159    pub tab_color: Option<TabColor>,
160
161    #[serde(rename = "outlinePr", skip_serializing_if = "Option::is_none")]
162    pub outline_pr: Option<OutlinePr>,
163}
164
165/// Tab color specification.
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct TabColor {
168    #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
169    pub rgb: Option<String>,
170
171    #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
172    pub theme: Option<u32>,
173
174    #[serde(rename = "@indexed", skip_serializing_if = "Option::is_none")]
175    pub indexed: Option<u32>,
176}
177
178/// Outline properties for grouping.
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
180pub struct OutlinePr {
181    #[serde(rename = "@summaryBelow", skip_serializing_if = "Option::is_none")]
182    pub summary_below: Option<bool>,
183
184    #[serde(rename = "@summaryRight", skip_serializing_if = "Option::is_none")]
185    pub summary_right: Option<bool>,
186}
187
188/// Sheet format properties.
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub struct SheetFormatPr {
191    #[serde(rename = "@defaultRowHeight")]
192    pub default_row_height: f64,
193
194    #[serde(rename = "@defaultColWidth", skip_serializing_if = "Option::is_none")]
195    pub default_col_width: Option<f64>,
196
197    #[serde(rename = "@customHeight", skip_serializing_if = "Option::is_none")]
198    pub custom_height: Option<bool>,
199
200    #[serde(rename = "@outlineLevelRow", skip_serializing_if = "Option::is_none")]
201    pub outline_level_row: Option<u8>,
202
203    #[serde(rename = "@outlineLevelCol", skip_serializing_if = "Option::is_none")]
204    pub outline_level_col: Option<u8>,
205}
206
207/// Sheet protection settings.
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
209pub struct SheetProtection {
210    #[serde(rename = "@password", skip_serializing_if = "Option::is_none")]
211    pub password: Option<String>,
212
213    #[serde(rename = "@sheet", skip_serializing_if = "Option::is_none")]
214    pub sheet: Option<bool>,
215
216    #[serde(rename = "@objects", skip_serializing_if = "Option::is_none")]
217    pub objects: Option<bool>,
218
219    #[serde(rename = "@scenarios", skip_serializing_if = "Option::is_none")]
220    pub scenarios: Option<bool>,
221
222    #[serde(rename = "@selectLockedCells", skip_serializing_if = "Option::is_none")]
223    pub select_locked_cells: Option<bool>,
224
225    #[serde(
226        rename = "@selectUnlockedCells",
227        skip_serializing_if = "Option::is_none"
228    )]
229    pub select_unlocked_cells: Option<bool>,
230
231    #[serde(rename = "@formatCells", skip_serializing_if = "Option::is_none")]
232    pub format_cells: Option<bool>,
233
234    #[serde(rename = "@formatColumns", skip_serializing_if = "Option::is_none")]
235    pub format_columns: Option<bool>,
236
237    #[serde(rename = "@formatRows", skip_serializing_if = "Option::is_none")]
238    pub format_rows: Option<bool>,
239
240    #[serde(rename = "@insertColumns", skip_serializing_if = "Option::is_none")]
241    pub insert_columns: Option<bool>,
242
243    #[serde(rename = "@insertRows", skip_serializing_if = "Option::is_none")]
244    pub insert_rows: Option<bool>,
245
246    #[serde(rename = "@insertHyperlinks", skip_serializing_if = "Option::is_none")]
247    pub insert_hyperlinks: Option<bool>,
248
249    #[serde(rename = "@deleteColumns", skip_serializing_if = "Option::is_none")]
250    pub delete_columns: Option<bool>,
251
252    #[serde(rename = "@deleteRows", skip_serializing_if = "Option::is_none")]
253    pub delete_rows: Option<bool>,
254
255    #[serde(rename = "@sort", skip_serializing_if = "Option::is_none")]
256    pub sort: Option<bool>,
257
258    #[serde(rename = "@autoFilter", skip_serializing_if = "Option::is_none")]
259    pub auto_filter: Option<bool>,
260
261    #[serde(rename = "@pivotTables", skip_serializing_if = "Option::is_none")]
262    pub pivot_tables: Option<bool>,
263}
264
265/// Columns container.
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267pub struct Cols {
268    #[serde(rename = "col")]
269    pub cols: Vec<Col>,
270}
271
272/// Individual column definition.
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub struct Col {
275    #[serde(rename = "@min")]
276    pub min: u32,
277
278    #[serde(rename = "@max")]
279    pub max: u32,
280
281    #[serde(rename = "@width", skip_serializing_if = "Option::is_none")]
282    pub width: Option<f64>,
283
284    #[serde(rename = "@style", skip_serializing_if = "Option::is_none")]
285    pub style: Option<u32>,
286
287    #[serde(rename = "@hidden", skip_serializing_if = "Option::is_none")]
288    pub hidden: Option<bool>,
289
290    #[serde(rename = "@customWidth", skip_serializing_if = "Option::is_none")]
291    pub custom_width: Option<bool>,
292
293    #[serde(rename = "@outlineLevel", skip_serializing_if = "Option::is_none")]
294    pub outline_level: Option<u8>,
295}
296
297/// Sheet data container holding all rows.
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub struct SheetData {
300    #[serde(rename = "row", default)]
301    pub rows: Vec<Row>,
302}
303
304/// A single row of cells.
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
306pub struct Row {
307    /// 1-based row number.
308    #[serde(rename = "@r")]
309    pub r: u32,
310
311    #[serde(rename = "@spans", skip_serializing_if = "Option::is_none")]
312    pub spans: Option<String>,
313
314    #[serde(rename = "@s", skip_serializing_if = "Option::is_none")]
315    pub s: Option<u32>,
316
317    #[serde(rename = "@customFormat", skip_serializing_if = "Option::is_none")]
318    pub custom_format: Option<bool>,
319
320    #[serde(rename = "@ht", skip_serializing_if = "Option::is_none")]
321    pub ht: Option<f64>,
322
323    #[serde(rename = "@hidden", skip_serializing_if = "Option::is_none")]
324    pub hidden: Option<bool>,
325
326    #[serde(rename = "@customHeight", skip_serializing_if = "Option::is_none")]
327    pub custom_height: Option<bool>,
328
329    #[serde(rename = "@outlineLevel", skip_serializing_if = "Option::is_none")]
330    pub outline_level: Option<u8>,
331
332    #[serde(rename = "c", default)]
333    pub cells: Vec<Cell>,
334}
335
336/// A single cell.
337#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
338pub struct Cell {
339    /// Cell reference (e.g., "A1").
340    #[serde(rename = "@r")]
341    pub r: String,
342
343    /// Style index.
344    #[serde(rename = "@s", skip_serializing_if = "Option::is_none")]
345    pub s: Option<u32>,
346
347    /// Cell type: "b", "d", "e", "inlineStr", "n", "s", "str".
348    #[serde(rename = "@t", skip_serializing_if = "Option::is_none")]
349    pub t: Option<String>,
350
351    /// Cell value.
352    #[serde(rename = "v", skip_serializing_if = "Option::is_none")]
353    pub v: Option<String>,
354
355    /// Cell formula.
356    #[serde(rename = "f", skip_serializing_if = "Option::is_none")]
357    pub f: Option<CellFormula>,
358
359    /// Inline string.
360    #[serde(rename = "is", skip_serializing_if = "Option::is_none")]
361    pub is: Option<InlineString>,
362}
363
364/// Cell type constants.
365pub mod cell_types {
366    pub const BOOLEAN: &str = "b";
367    pub const DATE: &str = "d";
368    pub const ERROR: &str = "e";
369    pub const INLINE_STRING: &str = "inlineStr";
370    pub const NUMBER: &str = "n";
371    pub const SHARED_STRING: &str = "s";
372    pub const FORMULA_STRING: &str = "str";
373}
374
375/// Cell formula.
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377pub struct CellFormula {
378    #[serde(rename = "@t", skip_serializing_if = "Option::is_none")]
379    pub t: Option<String>,
380
381    #[serde(rename = "@ref", skip_serializing_if = "Option::is_none")]
382    pub reference: Option<String>,
383
384    #[serde(rename = "@si", skip_serializing_if = "Option::is_none")]
385    pub si: Option<u32>,
386
387    #[serde(rename = "$value", skip_serializing_if = "Option::is_none")]
388    pub value: Option<String>,
389}
390
391/// Inline string within a cell.
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub struct InlineString {
394    #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
395    pub t: Option<String>,
396}
397
398/// Auto filter.
399#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
400pub struct AutoFilter {
401    #[serde(rename = "@ref")]
402    pub reference: String,
403}
404
405/// Data validations container.
406#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
407pub struct DataValidations {
408    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
409    pub count: Option<u32>,
410
411    #[serde(
412        rename = "@disablePrompts",
413        skip_serializing_if = "Option::is_none",
414        default
415    )]
416    pub disable_prompts: Option<bool>,
417
418    #[serde(rename = "@xWindow", skip_serializing_if = "Option::is_none", default)]
419    pub x_window: Option<u32>,
420
421    #[serde(rename = "@yWindow", skip_serializing_if = "Option::is_none", default)]
422    pub y_window: Option<u32>,
423
424    #[serde(rename = "dataValidation", default)]
425    pub data_validations: Vec<DataValidation>,
426}
427
428/// Individual data validation rule.
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct DataValidation {
431    #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
432    pub validation_type: Option<String>,
433
434    #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
435    pub operator: Option<String>,
436
437    #[serde(rename = "@allowBlank", skip_serializing_if = "Option::is_none")]
438    pub allow_blank: Option<bool>,
439
440    #[serde(
441        rename = "@showDropDown",
442        skip_serializing_if = "Option::is_none",
443        default
444    )]
445    pub show_drop_down: Option<bool>,
446
447    #[serde(rename = "@showInputMessage", skip_serializing_if = "Option::is_none")]
448    pub show_input_message: Option<bool>,
449
450    #[serde(rename = "@showErrorMessage", skip_serializing_if = "Option::is_none")]
451    pub show_error_message: Option<bool>,
452
453    #[serde(rename = "@errorStyle", skip_serializing_if = "Option::is_none")]
454    pub error_style: Option<String>,
455
456    #[serde(rename = "@imeMode", skip_serializing_if = "Option::is_none", default)]
457    pub ime_mode: Option<String>,
458
459    #[serde(rename = "@errorTitle", skip_serializing_if = "Option::is_none")]
460    pub error_title: Option<String>,
461
462    #[serde(rename = "@error", skip_serializing_if = "Option::is_none")]
463    pub error: Option<String>,
464
465    #[serde(rename = "@promptTitle", skip_serializing_if = "Option::is_none")]
466    pub prompt_title: Option<String>,
467
468    #[serde(rename = "@prompt", skip_serializing_if = "Option::is_none")]
469    pub prompt: Option<String>,
470
471    #[serde(rename = "@sqref")]
472    pub sqref: String,
473
474    #[serde(rename = "formula1", skip_serializing_if = "Option::is_none")]
475    pub formula1: Option<String>,
476
477    #[serde(rename = "formula2", skip_serializing_if = "Option::is_none")]
478    pub formula2: Option<String>,
479}
480
481/// Merge cells container.
482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub struct MergeCells {
484    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
485    pub count: Option<u32>,
486
487    #[serde(rename = "mergeCell", default)]
488    pub merge_cells: Vec<MergeCell>,
489}
490
491/// Individual merge cell reference.
492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
493pub struct MergeCell {
494    #[serde(rename = "@ref")]
495    pub reference: String,
496}
497
498/// Hyperlinks container.
499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub struct Hyperlinks {
501    #[serde(rename = "hyperlink", default)]
502    pub hyperlinks: Vec<Hyperlink>,
503}
504
505/// Individual hyperlink.
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
507pub struct Hyperlink {
508    #[serde(rename = "@ref")]
509    pub reference: String,
510
511    #[serde(
512        rename = "@r:id",
513        alias = "@id",
514        skip_serializing_if = "Option::is_none"
515    )]
516    pub r_id: Option<String>,
517
518    #[serde(rename = "@location", skip_serializing_if = "Option::is_none")]
519    pub location: Option<String>,
520
521    #[serde(rename = "@display", skip_serializing_if = "Option::is_none")]
522    pub display: Option<String>,
523
524    #[serde(rename = "@tooltip", skip_serializing_if = "Option::is_none")]
525    pub tooltip: Option<String>,
526}
527
528/// Page margins.
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530pub struct PageMargins {
531    #[serde(rename = "@left")]
532    pub left: f64,
533
534    #[serde(rename = "@right")]
535    pub right: f64,
536
537    #[serde(rename = "@top")]
538    pub top: f64,
539
540    #[serde(rename = "@bottom")]
541    pub bottom: f64,
542
543    #[serde(rename = "@header")]
544    pub header: f64,
545
546    #[serde(rename = "@footer")]
547    pub footer: f64,
548}
549
550/// Page setup.
551#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
552pub struct PageSetup {
553    #[serde(rename = "@paperSize", skip_serializing_if = "Option::is_none")]
554    pub paper_size: Option<u32>,
555
556    #[serde(rename = "@orientation", skip_serializing_if = "Option::is_none")]
557    pub orientation: Option<String>,
558
559    #[serde(rename = "@scale", skip_serializing_if = "Option::is_none")]
560    pub scale: Option<u32>,
561
562    #[serde(rename = "@fitToWidth", skip_serializing_if = "Option::is_none")]
563    pub fit_to_width: Option<u32>,
564
565    #[serde(rename = "@fitToHeight", skip_serializing_if = "Option::is_none")]
566    pub fit_to_height: Option<u32>,
567
568    #[serde(rename = "@firstPageNumber", skip_serializing_if = "Option::is_none")]
569    pub first_page_number: Option<u32>,
570
571    #[serde(rename = "@horizontalDpi", skip_serializing_if = "Option::is_none")]
572    pub horizontal_dpi: Option<u32>,
573
574    #[serde(rename = "@verticalDpi", skip_serializing_if = "Option::is_none")]
575    pub vertical_dpi: Option<u32>,
576
577    #[serde(
578        rename = "@r:id",
579        alias = "@id",
580        skip_serializing_if = "Option::is_none"
581    )]
582    pub r_id: Option<String>,
583}
584
585/// Header and footer for printing.
586#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
587pub struct HeaderFooter {
588    #[serde(rename = "oddHeader", skip_serializing_if = "Option::is_none")]
589    pub odd_header: Option<String>,
590
591    #[serde(rename = "oddFooter", skip_serializing_if = "Option::is_none")]
592    pub odd_footer: Option<String>,
593}
594
595/// Print options.
596#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
597pub struct PrintOptions {
598    #[serde(rename = "@gridLines", skip_serializing_if = "Option::is_none")]
599    pub grid_lines: Option<bool>,
600
601    #[serde(rename = "@headings", skip_serializing_if = "Option::is_none")]
602    pub headings: Option<bool>,
603
604    #[serde(
605        rename = "@horizontalCentered",
606        skip_serializing_if = "Option::is_none"
607    )]
608    pub horizontal_centered: Option<bool>,
609
610    #[serde(rename = "@verticalCentered", skip_serializing_if = "Option::is_none")]
611    pub vertical_centered: Option<bool>,
612}
613
614/// Row page breaks container.
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct RowBreaks {
617    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
618    pub count: Option<u32>,
619
620    #[serde(rename = "@manualBreakCount", skip_serializing_if = "Option::is_none")]
621    pub manual_break_count: Option<u32>,
622
623    #[serde(rename = "brk", default)]
624    pub brk: Vec<Break>,
625}
626
627/// Individual page break entry.
628#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
629pub struct Break {
630    #[serde(rename = "@id")]
631    pub id: u32,
632
633    #[serde(rename = "@max", skip_serializing_if = "Option::is_none")]
634    pub max: Option<u32>,
635
636    #[serde(rename = "@man", skip_serializing_if = "Option::is_none")]
637    pub man: Option<bool>,
638}
639
640/// Drawing reference.
641#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
642pub struct DrawingRef {
643    #[serde(rename = "@r:id", alias = "@id")]
644    pub r_id: String,
645}
646
647/// Legacy drawing reference (VML).
648#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
649pub struct LegacyDrawingRef {
650    #[serde(rename = "@r:id", alias = "@id")]
651    pub r_id: String,
652}
653
654/// Table parts container.
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656pub struct TableParts {
657    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
658    pub count: Option<u32>,
659
660    #[serde(rename = "tablePart", default)]
661    pub table_parts: Vec<TablePart>,
662}
663
664/// Individual table part reference.
665#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
666pub struct TablePart {
667    #[serde(rename = "@r:id", alias = "@id")]
668    pub r_id: String,
669}
670
671/// Conditional formatting container.
672#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
673pub struct ConditionalFormatting {
674    #[serde(rename = "@sqref")]
675    pub sqref: String,
676
677    #[serde(rename = "cfRule", default)]
678    pub cf_rules: Vec<CfRule>,
679}
680
681/// Conditional formatting rule.
682#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
683pub struct CfRule {
684    #[serde(rename = "@type")]
685    pub rule_type: String,
686
687    #[serde(rename = "@dxfId", skip_serializing_if = "Option::is_none")]
688    pub dxf_id: Option<u32>,
689
690    #[serde(rename = "@priority")]
691    pub priority: u32,
692
693    #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
694    pub operator: Option<String>,
695
696    #[serde(rename = "@text", skip_serializing_if = "Option::is_none")]
697    pub text: Option<String>,
698
699    #[serde(rename = "@stopIfTrue", skip_serializing_if = "Option::is_none")]
700    pub stop_if_true: Option<bool>,
701
702    #[serde(rename = "@aboveAverage", skip_serializing_if = "Option::is_none")]
703    pub above_average: Option<bool>,
704
705    #[serde(rename = "@equalAverage", skip_serializing_if = "Option::is_none")]
706    pub equal_average: Option<bool>,
707
708    #[serde(rename = "@percent", skip_serializing_if = "Option::is_none")]
709    pub percent: Option<bool>,
710
711    #[serde(rename = "@rank", skip_serializing_if = "Option::is_none")]
712    pub rank: Option<u32>,
713
714    #[serde(rename = "@bottom", skip_serializing_if = "Option::is_none")]
715    pub bottom: Option<bool>,
716
717    #[serde(rename = "formula", default, skip_serializing_if = "Vec::is_empty")]
718    pub formulas: Vec<String>,
719
720    #[serde(rename = "colorScale", skip_serializing_if = "Option::is_none")]
721    pub color_scale: Option<CfColorScale>,
722
723    #[serde(rename = "dataBar", skip_serializing_if = "Option::is_none")]
724    pub data_bar: Option<CfDataBar>,
725
726    #[serde(rename = "iconSet", skip_serializing_if = "Option::is_none")]
727    pub icon_set: Option<CfIconSet>,
728}
729
730/// Color scale definition for conditional formatting.
731#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
732pub struct CfColorScale {
733    #[serde(rename = "cfvo", default)]
734    pub cfvos: Vec<CfVo>,
735
736    #[serde(rename = "color", default)]
737    pub colors: Vec<CfColor>,
738}
739
740/// Data bar definition for conditional formatting.
741#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
742pub struct CfDataBar {
743    #[serde(rename = "@showValue", skip_serializing_if = "Option::is_none")]
744    pub show_value: Option<bool>,
745
746    #[serde(rename = "cfvo", default)]
747    pub cfvos: Vec<CfVo>,
748
749    #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
750    pub color: Option<CfColor>,
751}
752
753/// Icon set definition for conditional formatting.
754#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
755pub struct CfIconSet {
756    #[serde(rename = "@iconSet", skip_serializing_if = "Option::is_none")]
757    pub icon_set: Option<String>,
758
759    #[serde(rename = "cfvo", default)]
760    pub cfvos: Vec<CfVo>,
761}
762
763/// Conditional formatting value object.
764#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
765pub struct CfVo {
766    #[serde(rename = "@type")]
767    pub value_type: String,
768
769    #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
770    pub val: Option<String>,
771}
772
773/// Color reference for conditional formatting.
774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
775pub struct CfColor {
776    #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
777    pub rgb: Option<String>,
778
779    #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
780    pub theme: Option<u32>,
781
782    #[serde(rename = "@tint", skip_serializing_if = "Option::is_none")]
783    pub tint: Option<f64>,
784}
785
786impl Default for WorksheetXml {
787    fn default() -> Self {
788        Self {
789            xmlns: namespaces::SPREADSHEET_ML.to_string(),
790            xmlns_r: namespaces::RELATIONSHIPS.to_string(),
791            sheet_pr: None,
792            dimension: None,
793            sheet_views: None,
794            sheet_format_pr: None,
795            cols: None,
796            sheet_data: SheetData { rows: vec![] },
797            sheet_protection: None,
798            auto_filter: None,
799            merge_cells: None,
800            conditional_formatting: vec![],
801            data_validations: None,
802            hyperlinks: None,
803            print_options: None,
804            page_margins: None,
805            page_setup: None,
806            header_footer: None,
807            row_breaks: None,
808            drawing: None,
809            legacy_drawing: None,
810            table_parts: None,
811        }
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818
819    #[test]
820    fn test_worksheet_default() {
821        let ws = WorksheetXml::default();
822        assert_eq!(ws.xmlns, namespaces::SPREADSHEET_ML);
823        assert_eq!(ws.xmlns_r, namespaces::RELATIONSHIPS);
824        assert!(ws.sheet_data.rows.is_empty());
825        assert!(ws.dimension.is_none());
826        assert!(ws.sheet_views.is_none());
827        assert!(ws.cols.is_none());
828        assert!(ws.merge_cells.is_none());
829        assert!(ws.page_margins.is_none());
830        assert!(ws.sheet_pr.is_none());
831        assert!(ws.sheet_protection.is_none());
832    }
833
834    #[test]
835    fn test_worksheet_roundtrip() {
836        let ws = WorksheetXml::default();
837        let xml = quick_xml::se::to_string(&ws).unwrap();
838        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
839        assert_eq!(ws.xmlns, parsed.xmlns);
840        assert_eq!(ws.xmlns_r, parsed.xmlns_r);
841        assert_eq!(ws.sheet_data.rows.len(), parsed.sheet_data.rows.len());
842    }
843
844    #[test]
845    fn test_worksheet_with_data() {
846        let ws = WorksheetXml {
847            sheet_data: SheetData {
848                rows: vec![Row {
849                    r: 1,
850                    spans: Some("1:3".to_string()),
851                    s: None,
852                    custom_format: None,
853                    ht: None,
854                    hidden: None,
855                    custom_height: None,
856                    outline_level: None,
857                    cells: vec![
858                        Cell {
859                            r: "A1".to_string(),
860                            s: None,
861                            t: Some(cell_types::SHARED_STRING.to_string()),
862                            v: Some("0".to_string()),
863                            f: None,
864                            is: None,
865                        },
866                        Cell {
867                            r: "B1".to_string(),
868                            s: None,
869                            t: None,
870                            v: Some("42".to_string()),
871                            f: None,
872                            is: None,
873                        },
874                    ],
875                }],
876            },
877            ..WorksheetXml::default()
878        };
879
880        let xml = quick_xml::se::to_string(&ws).unwrap();
881        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
882        assert_eq!(parsed.sheet_data.rows.len(), 1);
883        assert_eq!(parsed.sheet_data.rows[0].r, 1);
884        assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
885        assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
886        assert_eq!(parsed.sheet_data.rows[0].cells[0].t, Some("s".to_string()));
887        assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
888        assert_eq!(parsed.sheet_data.rows[0].cells[1].r, "B1");
889        assert_eq!(parsed.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
890    }
891
892    #[test]
893    fn test_cell_with_formula() {
894        let cell = Cell {
895            r: "C1".to_string(),
896            s: None,
897            t: None,
898            v: Some("84".to_string()),
899            f: Some(CellFormula {
900                t: None,
901                reference: None,
902                si: None,
903                value: Some("A1+B1".to_string()),
904            }),
905            is: None,
906        };
907        let xml = quick_xml::se::to_string(&cell).unwrap();
908        assert!(xml.contains("A1+B1"));
909        let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
910        assert!(parsed.f.is_some());
911        assert_eq!(parsed.f.unwrap().value, Some("A1+B1".to_string()));
912    }
913
914    #[test]
915    fn test_cell_with_inline_string() {
916        let cell = Cell {
917            r: "A1".to_string(),
918            s: None,
919            t: Some(cell_types::INLINE_STRING.to_string()),
920            v: None,
921            f: None,
922            is: Some(InlineString {
923                t: Some("Hello World".to_string()),
924            }),
925        };
926        let xml = quick_xml::se::to_string(&cell).unwrap();
927        assert!(xml.contains("Hello World"));
928        let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
929        assert_eq!(parsed.t, Some("inlineStr".to_string()));
930        assert!(parsed.is.is_some());
931        assert_eq!(parsed.is.unwrap().t, Some("Hello World".to_string()));
932    }
933
934    #[test]
935    fn test_parse_real_excel_worksheet() {
936        let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
937<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
938  <dimension ref="A1:B2"/>
939  <sheetData>
940    <row r="1" spans="1:2">
941      <c r="A1" t="s"><v>0</v></c>
942      <c r="B1" t="s"><v>1</v></c>
943    </row>
944    <row r="2" spans="1:2">
945      <c r="A2"><v>100</v></c>
946      <c r="B2"><v>200</v></c>
947    </row>
948  </sheetData>
949</worksheet>"#;
950
951        let parsed: WorksheetXml = quick_xml::de::from_str(xml).unwrap();
952        assert_eq!(parsed.dimension.as_ref().unwrap().reference, "A1:B2");
953        assert_eq!(parsed.sheet_data.rows.len(), 2);
954        assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
955        assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
956        assert_eq!(parsed.sheet_data.rows[0].cells[0].t, Some("s".to_string()));
957        assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
958        assert_eq!(parsed.sheet_data.rows[1].cells[0].r, "A2");
959        assert_eq!(
960            parsed.sheet_data.rows[1].cells[0].v,
961            Some("100".to_string())
962        );
963    }
964
965    #[test]
966    fn test_worksheet_with_merge_cells() {
967        let ws = WorksheetXml {
968            merge_cells: Some(MergeCells {
969                count: Some(1),
970                merge_cells: vec![MergeCell {
971                    reference: "A1:B2".to_string(),
972                }],
973            }),
974            ..WorksheetXml::default()
975        };
976        let xml = quick_xml::se::to_string(&ws).unwrap();
977        assert!(xml.contains("mergeCells"));
978        assert!(xml.contains("A1:B2"));
979        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
980        assert!(parsed.merge_cells.is_some());
981        assert_eq!(parsed.merge_cells.as_ref().unwrap().merge_cells.len(), 1);
982    }
983
984    #[test]
985    fn test_empty_sheet_data_serialization() {
986        let sd = SheetData { rows: vec![] };
987        let xml = quick_xml::se::to_string(&sd).unwrap();
988        // Empty SheetData should still be serializable
989        let parsed: SheetData = quick_xml::de::from_str(&xml).unwrap();
990        assert!(parsed.rows.is_empty());
991    }
992
993    #[test]
994    fn test_row_optional_fields_not_serialized() {
995        let row = Row {
996            r: 1,
997            spans: None,
998            s: None,
999            custom_format: None,
1000            ht: None,
1001            hidden: None,
1002            custom_height: None,
1003            outline_level: None,
1004            cells: vec![],
1005        };
1006        let xml = quick_xml::se::to_string(&row).unwrap();
1007        assert!(!xml.contains("spans"));
1008        assert!(!xml.contains("ht"));
1009        assert!(!xml.contains("hidden"));
1010    }
1011
1012    #[test]
1013    fn test_cell_types_constants() {
1014        assert_eq!(cell_types::BOOLEAN, "b");
1015        assert_eq!(cell_types::DATE, "d");
1016        assert_eq!(cell_types::ERROR, "e");
1017        assert_eq!(cell_types::INLINE_STRING, "inlineStr");
1018        assert_eq!(cell_types::NUMBER, "n");
1019        assert_eq!(cell_types::SHARED_STRING, "s");
1020        assert_eq!(cell_types::FORMULA_STRING, "str");
1021    }
1022
1023    #[test]
1024    fn test_worksheet_with_cols() {
1025        let ws = WorksheetXml {
1026            cols: Some(Cols {
1027                cols: vec![Col {
1028                    min: 1,
1029                    max: 1,
1030                    width: Some(15.0),
1031                    style: None,
1032                    hidden: None,
1033                    custom_width: Some(true),
1034                    outline_level: None,
1035                }],
1036            }),
1037            ..WorksheetXml::default()
1038        };
1039        let xml = quick_xml::se::to_string(&ws).unwrap();
1040        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1041        assert!(parsed.cols.is_some());
1042        let cols = parsed.cols.unwrap();
1043        assert_eq!(cols.cols.len(), 1);
1044        assert_eq!(cols.cols[0].min, 1);
1045        assert_eq!(cols.cols[0].width, Some(15.0));
1046        assert_eq!(cols.cols[0].custom_width, Some(true));
1047    }
1048
1049    #[test]
1050    fn test_sheet_protection_roundtrip() {
1051        let prot = SheetProtection {
1052            password: Some("ABCD".to_string()),
1053            sheet: Some(true),
1054            objects: Some(true),
1055            scenarios: Some(true),
1056            format_cells: Some(false),
1057            ..SheetProtection::default()
1058        };
1059        let xml = quick_xml::se::to_string(&prot).unwrap();
1060        let parsed: SheetProtection = quick_xml::de::from_str(&xml).unwrap();
1061        assert_eq!(parsed.password, Some("ABCD".to_string()));
1062        assert_eq!(parsed.sheet, Some(true));
1063        assert_eq!(parsed.objects, Some(true));
1064        assert_eq!(parsed.scenarios, Some(true));
1065        assert_eq!(parsed.format_cells, Some(false));
1066        assert!(parsed.sort.is_none());
1067    }
1068
1069    #[test]
1070    fn test_sheet_pr_roundtrip() {
1071        let pr = SheetPr {
1072            code_name: Some("Sheet1".to_string()),
1073            tab_color: Some(TabColor {
1074                rgb: Some("FF0000".to_string()),
1075                theme: None,
1076                indexed: None,
1077            }),
1078            ..SheetPr::default()
1079        };
1080        let xml = quick_xml::se::to_string(&pr).unwrap();
1081        let parsed: SheetPr = quick_xml::de::from_str(&xml).unwrap();
1082        assert_eq!(parsed.code_name, Some("Sheet1".to_string()));
1083        assert!(parsed.tab_color.is_some());
1084        assert_eq!(parsed.tab_color.unwrap().rgb, Some("FF0000".to_string()));
1085    }
1086
1087    #[test]
1088    fn test_sheet_format_pr_extended_fields() {
1089        let fmt = SheetFormatPr {
1090            default_row_height: 15.0,
1091            default_col_width: Some(10.0),
1092            custom_height: Some(true),
1093            outline_level_row: Some(2),
1094            outline_level_col: Some(1),
1095        };
1096        let xml = quick_xml::se::to_string(&fmt).unwrap();
1097        let parsed: SheetFormatPr = quick_xml::de::from_str(&xml).unwrap();
1098        assert_eq!(parsed.default_row_height, 15.0);
1099        assert_eq!(parsed.default_col_width, Some(10.0));
1100        assert_eq!(parsed.custom_height, Some(true));
1101        assert_eq!(parsed.outline_level_row, Some(2));
1102        assert_eq!(parsed.outline_level_col, Some(1));
1103    }
1104}