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(rename = "dataValidation", default)]
412    pub data_validations: Vec<DataValidation>,
413}
414
415/// Individual data validation rule.
416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
417pub struct DataValidation {
418    #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
419    pub validation_type: Option<String>,
420
421    #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
422    pub operator: Option<String>,
423
424    #[serde(rename = "@allowBlank", skip_serializing_if = "Option::is_none")]
425    pub allow_blank: Option<bool>,
426
427    #[serde(rename = "@showInputMessage", skip_serializing_if = "Option::is_none")]
428    pub show_input_message: Option<bool>,
429
430    #[serde(rename = "@showErrorMessage", skip_serializing_if = "Option::is_none")]
431    pub show_error_message: Option<bool>,
432
433    #[serde(rename = "@errorStyle", skip_serializing_if = "Option::is_none")]
434    pub error_style: Option<String>,
435
436    #[serde(rename = "@errorTitle", skip_serializing_if = "Option::is_none")]
437    pub error_title: Option<String>,
438
439    #[serde(rename = "@error", skip_serializing_if = "Option::is_none")]
440    pub error: Option<String>,
441
442    #[serde(rename = "@promptTitle", skip_serializing_if = "Option::is_none")]
443    pub prompt_title: Option<String>,
444
445    #[serde(rename = "@prompt", skip_serializing_if = "Option::is_none")]
446    pub prompt: Option<String>,
447
448    #[serde(rename = "@sqref")]
449    pub sqref: String,
450
451    #[serde(rename = "formula1", skip_serializing_if = "Option::is_none")]
452    pub formula1: Option<String>,
453
454    #[serde(rename = "formula2", skip_serializing_if = "Option::is_none")]
455    pub formula2: Option<String>,
456}
457
458/// Merge cells container.
459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
460pub struct MergeCells {
461    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
462    pub count: Option<u32>,
463
464    #[serde(rename = "mergeCell", default)]
465    pub merge_cells: Vec<MergeCell>,
466}
467
468/// Individual merge cell reference.
469#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
470pub struct MergeCell {
471    #[serde(rename = "@ref")]
472    pub reference: String,
473}
474
475/// Hyperlinks container.
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
477pub struct Hyperlinks {
478    #[serde(rename = "hyperlink", default)]
479    pub hyperlinks: Vec<Hyperlink>,
480}
481
482/// Individual hyperlink.
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
484pub struct Hyperlink {
485    #[serde(rename = "@ref")]
486    pub reference: String,
487
488    #[serde(
489        rename = "@r:id",
490        alias = "@id",
491        skip_serializing_if = "Option::is_none"
492    )]
493    pub r_id: Option<String>,
494
495    #[serde(rename = "@location", skip_serializing_if = "Option::is_none")]
496    pub location: Option<String>,
497
498    #[serde(rename = "@display", skip_serializing_if = "Option::is_none")]
499    pub display: Option<String>,
500
501    #[serde(rename = "@tooltip", skip_serializing_if = "Option::is_none")]
502    pub tooltip: Option<String>,
503}
504
505/// Page margins.
506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
507pub struct PageMargins {
508    #[serde(rename = "@left")]
509    pub left: f64,
510
511    #[serde(rename = "@right")]
512    pub right: f64,
513
514    #[serde(rename = "@top")]
515    pub top: f64,
516
517    #[serde(rename = "@bottom")]
518    pub bottom: f64,
519
520    #[serde(rename = "@header")]
521    pub header: f64,
522
523    #[serde(rename = "@footer")]
524    pub footer: f64,
525}
526
527/// Page setup.
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529pub struct PageSetup {
530    #[serde(rename = "@paperSize", skip_serializing_if = "Option::is_none")]
531    pub paper_size: Option<u32>,
532
533    #[serde(rename = "@orientation", skip_serializing_if = "Option::is_none")]
534    pub orientation: Option<String>,
535
536    #[serde(rename = "@scale", skip_serializing_if = "Option::is_none")]
537    pub scale: Option<u32>,
538
539    #[serde(rename = "@fitToWidth", skip_serializing_if = "Option::is_none")]
540    pub fit_to_width: Option<u32>,
541
542    #[serde(rename = "@fitToHeight", skip_serializing_if = "Option::is_none")]
543    pub fit_to_height: Option<u32>,
544
545    #[serde(rename = "@firstPageNumber", skip_serializing_if = "Option::is_none")]
546    pub first_page_number: Option<u32>,
547
548    #[serde(rename = "@horizontalDpi", skip_serializing_if = "Option::is_none")]
549    pub horizontal_dpi: Option<u32>,
550
551    #[serde(rename = "@verticalDpi", skip_serializing_if = "Option::is_none")]
552    pub vertical_dpi: Option<u32>,
553
554    #[serde(
555        rename = "@r:id",
556        alias = "@id",
557        skip_serializing_if = "Option::is_none"
558    )]
559    pub r_id: Option<String>,
560}
561
562/// Header and footer for printing.
563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
564pub struct HeaderFooter {
565    #[serde(rename = "oddHeader", skip_serializing_if = "Option::is_none")]
566    pub odd_header: Option<String>,
567
568    #[serde(rename = "oddFooter", skip_serializing_if = "Option::is_none")]
569    pub odd_footer: Option<String>,
570}
571
572/// Print options.
573#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
574pub struct PrintOptions {
575    #[serde(rename = "@gridLines", skip_serializing_if = "Option::is_none")]
576    pub grid_lines: Option<bool>,
577
578    #[serde(rename = "@headings", skip_serializing_if = "Option::is_none")]
579    pub headings: Option<bool>,
580
581    #[serde(
582        rename = "@horizontalCentered",
583        skip_serializing_if = "Option::is_none"
584    )]
585    pub horizontal_centered: Option<bool>,
586
587    #[serde(rename = "@verticalCentered", skip_serializing_if = "Option::is_none")]
588    pub vertical_centered: Option<bool>,
589}
590
591/// Row page breaks container.
592#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
593pub struct RowBreaks {
594    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
595    pub count: Option<u32>,
596
597    #[serde(rename = "@manualBreakCount", skip_serializing_if = "Option::is_none")]
598    pub manual_break_count: Option<u32>,
599
600    #[serde(rename = "brk", default)]
601    pub brk: Vec<Break>,
602}
603
604/// Individual page break entry.
605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
606pub struct Break {
607    #[serde(rename = "@id")]
608    pub id: u32,
609
610    #[serde(rename = "@max", skip_serializing_if = "Option::is_none")]
611    pub max: Option<u32>,
612
613    #[serde(rename = "@man", skip_serializing_if = "Option::is_none")]
614    pub man: Option<bool>,
615}
616
617/// Drawing reference.
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
619pub struct DrawingRef {
620    #[serde(rename = "@r:id", alias = "@id")]
621    pub r_id: String,
622}
623
624/// Legacy drawing reference (VML).
625#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
626pub struct LegacyDrawingRef {
627    #[serde(rename = "@r:id", alias = "@id")]
628    pub r_id: String,
629}
630
631/// Table parts container.
632#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
633pub struct TableParts {
634    #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
635    pub count: Option<u32>,
636
637    #[serde(rename = "tablePart", default)]
638    pub table_parts: Vec<TablePart>,
639}
640
641/// Individual table part reference.
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643pub struct TablePart {
644    #[serde(rename = "@r:id", alias = "@id")]
645    pub r_id: String,
646}
647
648/// Conditional formatting container.
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
650pub struct ConditionalFormatting {
651    #[serde(rename = "@sqref")]
652    pub sqref: String,
653
654    #[serde(rename = "cfRule", default)]
655    pub cf_rules: Vec<CfRule>,
656}
657
658/// Conditional formatting rule.
659#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
660pub struct CfRule {
661    #[serde(rename = "@type")]
662    pub rule_type: String,
663
664    #[serde(rename = "@dxfId", skip_serializing_if = "Option::is_none")]
665    pub dxf_id: Option<u32>,
666
667    #[serde(rename = "@priority")]
668    pub priority: u32,
669
670    #[serde(rename = "@operator", skip_serializing_if = "Option::is_none")]
671    pub operator: Option<String>,
672
673    #[serde(rename = "@text", skip_serializing_if = "Option::is_none")]
674    pub text: Option<String>,
675
676    #[serde(rename = "@stopIfTrue", skip_serializing_if = "Option::is_none")]
677    pub stop_if_true: Option<bool>,
678
679    #[serde(rename = "@aboveAverage", skip_serializing_if = "Option::is_none")]
680    pub above_average: Option<bool>,
681
682    #[serde(rename = "@equalAverage", skip_serializing_if = "Option::is_none")]
683    pub equal_average: Option<bool>,
684
685    #[serde(rename = "@percent", skip_serializing_if = "Option::is_none")]
686    pub percent: Option<bool>,
687
688    #[serde(rename = "@rank", skip_serializing_if = "Option::is_none")]
689    pub rank: Option<u32>,
690
691    #[serde(rename = "@bottom", skip_serializing_if = "Option::is_none")]
692    pub bottom: Option<bool>,
693
694    #[serde(rename = "formula", default, skip_serializing_if = "Vec::is_empty")]
695    pub formulas: Vec<String>,
696
697    #[serde(rename = "colorScale", skip_serializing_if = "Option::is_none")]
698    pub color_scale: Option<CfColorScale>,
699
700    #[serde(rename = "dataBar", skip_serializing_if = "Option::is_none")]
701    pub data_bar: Option<CfDataBar>,
702
703    #[serde(rename = "iconSet", skip_serializing_if = "Option::is_none")]
704    pub icon_set: Option<CfIconSet>,
705}
706
707/// Color scale definition for conditional formatting.
708#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
709pub struct CfColorScale {
710    #[serde(rename = "cfvo", default)]
711    pub cfvos: Vec<CfVo>,
712
713    #[serde(rename = "color", default)]
714    pub colors: Vec<CfColor>,
715}
716
717/// Data bar definition for conditional formatting.
718#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
719pub struct CfDataBar {
720    #[serde(rename = "@showValue", skip_serializing_if = "Option::is_none")]
721    pub show_value: Option<bool>,
722
723    #[serde(rename = "cfvo", default)]
724    pub cfvos: Vec<CfVo>,
725
726    #[serde(rename = "color", skip_serializing_if = "Option::is_none")]
727    pub color: Option<CfColor>,
728}
729
730/// Icon set definition for conditional formatting.
731#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
732pub struct CfIconSet {
733    #[serde(rename = "@iconSet", skip_serializing_if = "Option::is_none")]
734    pub icon_set: Option<String>,
735
736    #[serde(rename = "cfvo", default)]
737    pub cfvos: Vec<CfVo>,
738}
739
740/// Conditional formatting value object.
741#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
742pub struct CfVo {
743    #[serde(rename = "@type")]
744    pub value_type: String,
745
746    #[serde(rename = "@val", skip_serializing_if = "Option::is_none")]
747    pub val: Option<String>,
748}
749
750/// Color reference for conditional formatting.
751#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
752pub struct CfColor {
753    #[serde(rename = "@rgb", skip_serializing_if = "Option::is_none")]
754    pub rgb: Option<String>,
755
756    #[serde(rename = "@theme", skip_serializing_if = "Option::is_none")]
757    pub theme: Option<u32>,
758
759    #[serde(rename = "@tint", skip_serializing_if = "Option::is_none")]
760    pub tint: Option<f64>,
761}
762
763impl Default for WorksheetXml {
764    fn default() -> Self {
765        Self {
766            xmlns: namespaces::SPREADSHEET_ML.to_string(),
767            xmlns_r: namespaces::RELATIONSHIPS.to_string(),
768            sheet_pr: None,
769            dimension: None,
770            sheet_views: None,
771            sheet_format_pr: None,
772            cols: None,
773            sheet_data: SheetData { rows: vec![] },
774            sheet_protection: None,
775            auto_filter: None,
776            merge_cells: None,
777            conditional_formatting: vec![],
778            data_validations: None,
779            hyperlinks: None,
780            print_options: None,
781            page_margins: None,
782            page_setup: None,
783            header_footer: None,
784            row_breaks: None,
785            drawing: None,
786            legacy_drawing: None,
787            table_parts: None,
788        }
789    }
790}
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795
796    #[test]
797    fn test_worksheet_default() {
798        let ws = WorksheetXml::default();
799        assert_eq!(ws.xmlns, namespaces::SPREADSHEET_ML);
800        assert_eq!(ws.xmlns_r, namespaces::RELATIONSHIPS);
801        assert!(ws.sheet_data.rows.is_empty());
802        assert!(ws.dimension.is_none());
803        assert!(ws.sheet_views.is_none());
804        assert!(ws.cols.is_none());
805        assert!(ws.merge_cells.is_none());
806        assert!(ws.page_margins.is_none());
807        assert!(ws.sheet_pr.is_none());
808        assert!(ws.sheet_protection.is_none());
809    }
810
811    #[test]
812    fn test_worksheet_roundtrip() {
813        let ws = WorksheetXml::default();
814        let xml = quick_xml::se::to_string(&ws).unwrap();
815        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
816        assert_eq!(ws.xmlns, parsed.xmlns);
817        assert_eq!(ws.xmlns_r, parsed.xmlns_r);
818        assert_eq!(ws.sheet_data.rows.len(), parsed.sheet_data.rows.len());
819    }
820
821    #[test]
822    fn test_worksheet_with_data() {
823        let ws = WorksheetXml {
824            sheet_data: SheetData {
825                rows: vec![Row {
826                    r: 1,
827                    spans: Some("1:3".to_string()),
828                    s: None,
829                    custom_format: None,
830                    ht: None,
831                    hidden: None,
832                    custom_height: None,
833                    outline_level: None,
834                    cells: vec![
835                        Cell {
836                            r: "A1".to_string(),
837                            s: None,
838                            t: Some(cell_types::SHARED_STRING.to_string()),
839                            v: Some("0".to_string()),
840                            f: None,
841                            is: None,
842                        },
843                        Cell {
844                            r: "B1".to_string(),
845                            s: None,
846                            t: None,
847                            v: Some("42".to_string()),
848                            f: None,
849                            is: None,
850                        },
851                    ],
852                }],
853            },
854            ..WorksheetXml::default()
855        };
856
857        let xml = quick_xml::se::to_string(&ws).unwrap();
858        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
859        assert_eq!(parsed.sheet_data.rows.len(), 1);
860        assert_eq!(parsed.sheet_data.rows[0].r, 1);
861        assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
862        assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
863        assert_eq!(parsed.sheet_data.rows[0].cells[0].t, Some("s".to_string()));
864        assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
865        assert_eq!(parsed.sheet_data.rows[0].cells[1].r, "B1");
866        assert_eq!(parsed.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
867    }
868
869    #[test]
870    fn test_cell_with_formula() {
871        let cell = Cell {
872            r: "C1".to_string(),
873            s: None,
874            t: None,
875            v: Some("84".to_string()),
876            f: Some(CellFormula {
877                t: None,
878                reference: None,
879                si: None,
880                value: Some("A1+B1".to_string()),
881            }),
882            is: None,
883        };
884        let xml = quick_xml::se::to_string(&cell).unwrap();
885        assert!(xml.contains("A1+B1"));
886        let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
887        assert!(parsed.f.is_some());
888        assert_eq!(parsed.f.unwrap().value, Some("A1+B1".to_string()));
889    }
890
891    #[test]
892    fn test_cell_with_inline_string() {
893        let cell = Cell {
894            r: "A1".to_string(),
895            s: None,
896            t: Some(cell_types::INLINE_STRING.to_string()),
897            v: None,
898            f: None,
899            is: Some(InlineString {
900                t: Some("Hello World".to_string()),
901            }),
902        };
903        let xml = quick_xml::se::to_string(&cell).unwrap();
904        assert!(xml.contains("Hello World"));
905        let parsed: Cell = quick_xml::de::from_str(&xml).unwrap();
906        assert_eq!(parsed.t, Some("inlineStr".to_string()));
907        assert!(parsed.is.is_some());
908        assert_eq!(parsed.is.unwrap().t, Some("Hello World".to_string()));
909    }
910
911    #[test]
912    fn test_parse_real_excel_worksheet() {
913        let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
914<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
915  <dimension ref="A1:B2"/>
916  <sheetData>
917    <row r="1" spans="1:2">
918      <c r="A1" t="s"><v>0</v></c>
919      <c r="B1" t="s"><v>1</v></c>
920    </row>
921    <row r="2" spans="1:2">
922      <c r="A2"><v>100</v></c>
923      <c r="B2"><v>200</v></c>
924    </row>
925  </sheetData>
926</worksheet>"#;
927
928        let parsed: WorksheetXml = quick_xml::de::from_str(xml).unwrap();
929        assert_eq!(parsed.dimension.as_ref().unwrap().reference, "A1:B2");
930        assert_eq!(parsed.sheet_data.rows.len(), 2);
931        assert_eq!(parsed.sheet_data.rows[0].cells.len(), 2);
932        assert_eq!(parsed.sheet_data.rows[0].cells[0].r, "A1");
933        assert_eq!(parsed.sheet_data.rows[0].cells[0].t, Some("s".to_string()));
934        assert_eq!(parsed.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
935        assert_eq!(parsed.sheet_data.rows[1].cells[0].r, "A2");
936        assert_eq!(
937            parsed.sheet_data.rows[1].cells[0].v,
938            Some("100".to_string())
939        );
940    }
941
942    #[test]
943    fn test_worksheet_with_merge_cells() {
944        let ws = WorksheetXml {
945            merge_cells: Some(MergeCells {
946                count: Some(1),
947                merge_cells: vec![MergeCell {
948                    reference: "A1:B2".to_string(),
949                }],
950            }),
951            ..WorksheetXml::default()
952        };
953        let xml = quick_xml::se::to_string(&ws).unwrap();
954        assert!(xml.contains("mergeCells"));
955        assert!(xml.contains("A1:B2"));
956        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
957        assert!(parsed.merge_cells.is_some());
958        assert_eq!(parsed.merge_cells.as_ref().unwrap().merge_cells.len(), 1);
959    }
960
961    #[test]
962    fn test_empty_sheet_data_serialization() {
963        let sd = SheetData { rows: vec![] };
964        let xml = quick_xml::se::to_string(&sd).unwrap();
965        // Empty SheetData should still be serializable
966        let parsed: SheetData = quick_xml::de::from_str(&xml).unwrap();
967        assert!(parsed.rows.is_empty());
968    }
969
970    #[test]
971    fn test_row_optional_fields_not_serialized() {
972        let row = Row {
973            r: 1,
974            spans: None,
975            s: None,
976            custom_format: None,
977            ht: None,
978            hidden: None,
979            custom_height: None,
980            outline_level: None,
981            cells: vec![],
982        };
983        let xml = quick_xml::se::to_string(&row).unwrap();
984        assert!(!xml.contains("spans"));
985        assert!(!xml.contains("ht"));
986        assert!(!xml.contains("hidden"));
987    }
988
989    #[test]
990    fn test_cell_types_constants() {
991        assert_eq!(cell_types::BOOLEAN, "b");
992        assert_eq!(cell_types::DATE, "d");
993        assert_eq!(cell_types::ERROR, "e");
994        assert_eq!(cell_types::INLINE_STRING, "inlineStr");
995        assert_eq!(cell_types::NUMBER, "n");
996        assert_eq!(cell_types::SHARED_STRING, "s");
997        assert_eq!(cell_types::FORMULA_STRING, "str");
998    }
999
1000    #[test]
1001    fn test_worksheet_with_cols() {
1002        let ws = WorksheetXml {
1003            cols: Some(Cols {
1004                cols: vec![Col {
1005                    min: 1,
1006                    max: 1,
1007                    width: Some(15.0),
1008                    style: None,
1009                    hidden: None,
1010                    custom_width: Some(true),
1011                    outline_level: None,
1012                }],
1013            }),
1014            ..WorksheetXml::default()
1015        };
1016        let xml = quick_xml::se::to_string(&ws).unwrap();
1017        let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1018        assert!(parsed.cols.is_some());
1019        let cols = parsed.cols.unwrap();
1020        assert_eq!(cols.cols.len(), 1);
1021        assert_eq!(cols.cols[0].min, 1);
1022        assert_eq!(cols.cols[0].width, Some(15.0));
1023        assert_eq!(cols.cols[0].custom_width, Some(true));
1024    }
1025
1026    #[test]
1027    fn test_sheet_protection_roundtrip() {
1028        let prot = SheetProtection {
1029            password: Some("ABCD".to_string()),
1030            sheet: Some(true),
1031            objects: Some(true),
1032            scenarios: Some(true),
1033            format_cells: Some(false),
1034            ..SheetProtection::default()
1035        };
1036        let xml = quick_xml::se::to_string(&prot).unwrap();
1037        let parsed: SheetProtection = quick_xml::de::from_str(&xml).unwrap();
1038        assert_eq!(parsed.password, Some("ABCD".to_string()));
1039        assert_eq!(parsed.sheet, Some(true));
1040        assert_eq!(parsed.objects, Some(true));
1041        assert_eq!(parsed.scenarios, Some(true));
1042        assert_eq!(parsed.format_cells, Some(false));
1043        assert!(parsed.sort.is_none());
1044    }
1045
1046    #[test]
1047    fn test_sheet_pr_roundtrip() {
1048        let pr = SheetPr {
1049            code_name: Some("Sheet1".to_string()),
1050            tab_color: Some(TabColor {
1051                rgb: Some("FF0000".to_string()),
1052                theme: None,
1053                indexed: None,
1054            }),
1055            ..SheetPr::default()
1056        };
1057        let xml = quick_xml::se::to_string(&pr).unwrap();
1058        let parsed: SheetPr = quick_xml::de::from_str(&xml).unwrap();
1059        assert_eq!(parsed.code_name, Some("Sheet1".to_string()));
1060        assert!(parsed.tab_color.is_some());
1061        assert_eq!(parsed.tab_color.unwrap().rgb, Some("FF0000".to_string()));
1062    }
1063
1064    #[test]
1065    fn test_sheet_format_pr_extended_fields() {
1066        let fmt = SheetFormatPr {
1067            default_row_height: 15.0,
1068            default_col_width: Some(10.0),
1069            custom_height: Some(true),
1070            outline_level_row: Some(2),
1071            outline_level_col: Some(1),
1072        };
1073        let xml = quick_xml::se::to_string(&fmt).unwrap();
1074        let parsed: SheetFormatPr = quick_xml::de::from_str(&xml).unwrap();
1075        assert_eq!(parsed.default_row_height, 15.0);
1076        assert_eq!(parsed.default_col_width, Some(10.0));
1077        assert_eq!(parsed.custom_height, Some(true));
1078        assert_eq!(parsed.outline_level_row, Some(2));
1079        assert_eq!(parsed.outline_level_col, Some(1));
1080    }
1081}