1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename = "pivotTableDefinition")]
10pub struct PivotTableDefinition {
11 #[serde(rename = "@xmlns")]
12 pub xmlns: String,
13
14 #[serde(rename = "@name")]
15 pub name: String,
16
17 #[serde(rename = "@cacheId")]
18 pub cache_id: u32,
19
20 #[serde(rename = "@dataOnRows", skip_serializing_if = "Option::is_none")]
21 pub data_on_rows: Option<bool>,
22
23 #[serde(
24 rename = "@applyNumberFormats",
25 skip_serializing_if = "Option::is_none"
26 )]
27 pub apply_number_formats: Option<bool>,
28
29 #[serde(
30 rename = "@applyBorderFormats",
31 skip_serializing_if = "Option::is_none"
32 )]
33 pub apply_border_formats: Option<bool>,
34
35 #[serde(rename = "@applyFontFormats", skip_serializing_if = "Option::is_none")]
36 pub apply_font_formats: Option<bool>,
37
38 #[serde(
39 rename = "@applyPatternFormats",
40 skip_serializing_if = "Option::is_none"
41 )]
42 pub apply_pattern_formats: Option<bool>,
43
44 #[serde(
45 rename = "@applyAlignmentFormats",
46 skip_serializing_if = "Option::is_none"
47 )]
48 pub apply_alignment_formats: Option<bool>,
49
50 #[serde(
51 rename = "@applyWidthHeightFormats",
52 skip_serializing_if = "Option::is_none"
53 )]
54 pub apply_width_height_formats: Option<bool>,
55
56 #[serde(rename = "location")]
57 pub location: PivotLocation,
58
59 #[serde(rename = "pivotFields")]
60 pub pivot_fields: PivotFields,
61
62 #[serde(rename = "rowFields", skip_serializing_if = "Option::is_none")]
63 pub row_fields: Option<FieldList>,
64
65 #[serde(rename = "colFields", skip_serializing_if = "Option::is_none")]
66 pub col_fields: Option<FieldList>,
67
68 #[serde(rename = "dataFields", skip_serializing_if = "Option::is_none")]
69 pub data_fields: Option<DataFields>,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct PivotLocation {
75 #[serde(rename = "@ref")]
76 pub reference: String,
77
78 #[serde(rename = "@firstHeaderRow")]
79 pub first_header_row: u32,
80
81 #[serde(rename = "@firstDataRow")]
82 pub first_data_row: u32,
83
84 #[serde(rename = "@firstDataCol")]
85 pub first_data_col: u32,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct PivotFields {
91 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
92 pub count: Option<u32>,
93
94 #[serde(rename = "pivotField", default)]
95 pub fields: Vec<PivotFieldDef>,
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub struct PivotFieldDef {
101 #[serde(rename = "@axis", skip_serializing_if = "Option::is_none")]
102 pub axis: Option<String>,
103
104 #[serde(rename = "@dataField", skip_serializing_if = "Option::is_none")]
105 pub data_field: Option<bool>,
106
107 #[serde(rename = "@showAll", skip_serializing_if = "Option::is_none")]
108 pub show_all: Option<bool>,
109
110 #[serde(rename = "items", skip_serializing_if = "Option::is_none")]
111 pub items: Option<FieldItems>,
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct FieldItems {
117 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
118 pub count: Option<u32>,
119
120 #[serde(rename = "item", default)]
121 pub items: Vec<FieldItem>,
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub struct FieldItem {
127 #[serde(rename = "@t", skip_serializing_if = "Option::is_none")]
128 pub item_type: Option<String>,
129
130 #[serde(rename = "@x", skip_serializing_if = "Option::is_none")]
131 pub index: Option<u32>,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub struct FieldList {
137 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
138 pub count: Option<u32>,
139
140 #[serde(rename = "field", default)]
141 pub fields: Vec<FieldRef>,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct FieldRef {
147 #[serde(rename = "@x")]
148 pub index: i32,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153pub struct DataFields {
154 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
155 pub count: Option<u32>,
156
157 #[serde(rename = "dataField", default)]
158 pub fields: Vec<DataFieldDef>,
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct DataFieldDef {
164 #[serde(rename = "@name", skip_serializing_if = "Option::is_none")]
165 pub name: Option<String>,
166
167 #[serde(rename = "@fld")]
168 pub field_index: u32,
169
170 #[serde(rename = "@subtotal", skip_serializing_if = "Option::is_none")]
171 pub subtotal: Option<String>,
172
173 #[serde(rename = "@baseField", skip_serializing_if = "Option::is_none")]
174 pub base_field: Option<i32>,
175
176 #[serde(rename = "@baseItem", skip_serializing_if = "Option::is_none")]
177 pub base_item: Option<u32>,
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_pivot_location_roundtrip() {
186 let loc = PivotLocation {
187 reference: "A3:D20".to_string(),
188 first_header_row: 1,
189 first_data_row: 2,
190 first_data_col: 1,
191 };
192 let xml = quick_xml::se::to_string(&loc).unwrap();
193 let parsed: PivotLocation = quick_xml::de::from_str(&xml).unwrap();
194 assert_eq!(loc, parsed);
195 }
196
197 #[test]
198 fn test_field_item_roundtrip() {
199 let item = FieldItem {
200 item_type: Some("default".to_string()),
201 index: Some(0),
202 };
203 let xml = quick_xml::se::to_string(&item).unwrap();
204 let parsed: FieldItem = quick_xml::de::from_str(&xml).unwrap();
205 assert_eq!(item, parsed);
206 }
207
208 #[test]
209 fn test_field_item_optional_fields_skipped() {
210 let item = FieldItem {
211 item_type: None,
212 index: Some(3),
213 };
214 let xml = quick_xml::se::to_string(&item).unwrap();
215 assert!(!xml.contains("t="));
216 assert!(xml.contains("x=\"3\""));
217 }
218
219 #[test]
220 fn test_field_items_roundtrip() {
221 let items = FieldItems {
222 count: Some(2),
223 items: vec![
224 FieldItem {
225 item_type: None,
226 index: Some(0),
227 },
228 FieldItem {
229 item_type: Some("default".to_string()),
230 index: None,
231 },
232 ],
233 };
234 let xml = quick_xml::se::to_string(&items).unwrap();
235 let parsed: FieldItems = quick_xml::de::from_str(&xml).unwrap();
236 assert_eq!(items, parsed);
237 }
238
239 #[test]
240 fn test_pivot_field_def_roundtrip() {
241 let field = PivotFieldDef {
242 axis: Some("axisRow".to_string()),
243 data_field: None,
244 show_all: Some(false),
245 items: Some(FieldItems {
246 count: Some(1),
247 items: vec![FieldItem {
248 item_type: Some("default".to_string()),
249 index: None,
250 }],
251 }),
252 };
253 let xml = quick_xml::se::to_string(&field).unwrap();
254 let parsed: PivotFieldDef = quick_xml::de::from_str(&xml).unwrap();
255 assert_eq!(field, parsed);
256 }
257
258 #[test]
259 fn test_pivot_field_def_no_axis() {
260 let field = PivotFieldDef {
261 axis: None,
262 data_field: Some(true),
263 show_all: Some(false),
264 items: None,
265 };
266 let xml = quick_xml::se::to_string(&field).unwrap();
267 assert!(!xml.contains("axis="));
268 assert!(xml.contains("dataField=\"true\""));
269 }
270
271 #[test]
272 fn test_pivot_fields_roundtrip() {
273 let fields = PivotFields {
274 count: Some(2),
275 fields: vec![
276 PivotFieldDef {
277 axis: Some("axisRow".to_string()),
278 data_field: None,
279 show_all: Some(false),
280 items: None,
281 },
282 PivotFieldDef {
283 axis: None,
284 data_field: Some(true),
285 show_all: Some(false),
286 items: None,
287 },
288 ],
289 };
290 let xml = quick_xml::se::to_string(&fields).unwrap();
291 let parsed: PivotFields = quick_xml::de::from_str(&xml).unwrap();
292 assert_eq!(fields, parsed);
293 }
294
295 #[test]
296 fn test_field_ref_roundtrip() {
297 let field_ref = FieldRef { index: 2 };
298 let xml = quick_xml::se::to_string(&field_ref).unwrap();
299 let parsed: FieldRef = quick_xml::de::from_str(&xml).unwrap();
300 assert_eq!(field_ref, parsed);
301 }
302
303 #[test]
304 fn test_field_ref_negative_index() {
305 let field_ref = FieldRef { index: -2 };
306 let xml = quick_xml::se::to_string(&field_ref).unwrap();
307 let parsed: FieldRef = quick_xml::de::from_str(&xml).unwrap();
308 assert_eq!(parsed.index, -2);
309 }
310
311 #[test]
312 fn test_field_list_roundtrip() {
313 let list = FieldList {
314 count: Some(2),
315 fields: vec![FieldRef { index: 0 }, FieldRef { index: 3 }],
316 };
317 let xml = quick_xml::se::to_string(&list).unwrap();
318 let parsed: FieldList = quick_xml::de::from_str(&xml).unwrap();
319 assert_eq!(list, parsed);
320 }
321
322 #[test]
323 fn test_data_field_def_roundtrip() {
324 let data_field = DataFieldDef {
325 name: Some("Sum of Sales".to_string()),
326 field_index: 2,
327 subtotal: Some("sum".to_string()),
328 base_field: Some(0),
329 base_item: Some(0),
330 };
331 let xml = quick_xml::se::to_string(&data_field).unwrap();
332 let parsed: DataFieldDef = quick_xml::de::from_str(&xml).unwrap();
333 assert_eq!(data_field, parsed);
334 }
335
336 #[test]
337 fn test_data_field_def_optional_fields_skipped() {
338 let data_field = DataFieldDef {
339 name: None,
340 field_index: 1,
341 subtotal: None,
342 base_field: None,
343 base_item: None,
344 };
345 let xml = quick_xml::se::to_string(&data_field).unwrap();
346 assert!(!xml.contains("name="));
347 assert!(!xml.contains("subtotal="));
348 assert!(!xml.contains("baseField="));
349 assert!(!xml.contains("baseItem="));
350 assert!(xml.contains("fld=\"1\""));
351 }
352
353 #[test]
354 fn test_data_fields_roundtrip() {
355 let data_fields = DataFields {
356 count: Some(1),
357 fields: vec![DataFieldDef {
358 name: Some("Count of Items".to_string()),
359 field_index: 0,
360 subtotal: Some("count".to_string()),
361 base_field: Some(0),
362 base_item: Some(0),
363 }],
364 };
365 let xml = quick_xml::se::to_string(&data_fields).unwrap();
366 let parsed: DataFields = quick_xml::de::from_str(&xml).unwrap();
367 assert_eq!(data_fields, parsed);
368 }
369
370 #[test]
371 fn test_pivot_table_definition_minimal_roundtrip() {
372 let def = PivotTableDefinition {
373 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
374 name: "PivotTable1".to_string(),
375 cache_id: 0,
376 data_on_rows: None,
377 apply_number_formats: None,
378 apply_border_formats: None,
379 apply_font_formats: None,
380 apply_pattern_formats: None,
381 apply_alignment_formats: None,
382 apply_width_height_formats: None,
383 location: PivotLocation {
384 reference: "A3:C20".to_string(),
385 first_header_row: 1,
386 first_data_row: 1,
387 first_data_col: 1,
388 },
389 pivot_fields: PivotFields {
390 count: Some(2),
391 fields: vec![
392 PivotFieldDef {
393 axis: Some("axisRow".to_string()),
394 data_field: None,
395 show_all: Some(false),
396 items: None,
397 },
398 PivotFieldDef {
399 axis: None,
400 data_field: Some(true),
401 show_all: Some(false),
402 items: None,
403 },
404 ],
405 },
406 row_fields: Some(FieldList {
407 count: Some(1),
408 fields: vec![FieldRef { index: 0 }],
409 }),
410 col_fields: None,
411 data_fields: Some(DataFields {
412 count: Some(1),
413 fields: vec![DataFieldDef {
414 name: Some("Sum of Amount".to_string()),
415 field_index: 1,
416 subtotal: Some("sum".to_string()),
417 base_field: Some(0),
418 base_item: Some(0),
419 }],
420 }),
421 };
422 let xml = quick_xml::se::to_string(&def).unwrap();
423 let parsed: PivotTableDefinition = quick_xml::de::from_str(&xml).unwrap();
424 assert_eq!(def, parsed);
425 }
426
427 #[test]
428 fn test_pivot_table_definition_full_roundtrip() {
429 let def = PivotTableDefinition {
430 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
431 name: "SalesReport".to_string(),
432 cache_id: 1,
433 data_on_rows: Some(false),
434 apply_number_formats: Some(false),
435 apply_border_formats: Some(false),
436 apply_font_formats: Some(false),
437 apply_pattern_formats: Some(false),
438 apply_alignment_formats: Some(false),
439 apply_width_height_formats: Some(true),
440 location: PivotLocation {
441 reference: "A1:E30".to_string(),
442 first_header_row: 1,
443 first_data_row: 2,
444 first_data_col: 1,
445 },
446 pivot_fields: PivotFields {
447 count: Some(3),
448 fields: vec![
449 PivotFieldDef {
450 axis: Some("axisRow".to_string()),
451 data_field: None,
452 show_all: Some(false),
453 items: Some(FieldItems {
454 count: Some(2),
455 items: vec![
456 FieldItem {
457 item_type: None,
458 index: Some(0),
459 },
460 FieldItem {
461 item_type: Some("default".to_string()),
462 index: None,
463 },
464 ],
465 }),
466 },
467 PivotFieldDef {
468 axis: Some("axisCol".to_string()),
469 data_field: None,
470 show_all: Some(false),
471 items: None,
472 },
473 PivotFieldDef {
474 axis: None,
475 data_field: Some(true),
476 show_all: Some(false),
477 items: None,
478 },
479 ],
480 },
481 row_fields: Some(FieldList {
482 count: Some(1),
483 fields: vec![FieldRef { index: 0 }],
484 }),
485 col_fields: Some(FieldList {
486 count: Some(1),
487 fields: vec![FieldRef { index: 1 }],
488 }),
489 data_fields: Some(DataFields {
490 count: Some(1),
491 fields: vec![DataFieldDef {
492 name: Some("Sum of Revenue".to_string()),
493 field_index: 2,
494 subtotal: Some("sum".to_string()),
495 base_field: Some(0),
496 base_item: Some(0),
497 }],
498 }),
499 };
500 let xml = quick_xml::se::to_string(&def).unwrap();
501 let parsed: PivotTableDefinition = quick_xml::de::from_str(&xml).unwrap();
502 assert_eq!(def, parsed);
503 }
504
505 #[test]
506 fn test_pivot_table_definition_serialization_structure() {
507 let def = PivotTableDefinition {
508 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
509 name: "TestPivot".to_string(),
510 cache_id: 0,
511 data_on_rows: Some(false),
512 apply_number_formats: None,
513 apply_border_formats: None,
514 apply_font_formats: None,
515 apply_pattern_formats: None,
516 apply_alignment_formats: None,
517 apply_width_height_formats: None,
518 location: PivotLocation {
519 reference: "A1".to_string(),
520 first_header_row: 1,
521 first_data_row: 1,
522 first_data_col: 1,
523 },
524 pivot_fields: PivotFields {
525 count: Some(0),
526 fields: vec![],
527 },
528 row_fields: None,
529 col_fields: None,
530 data_fields: None,
531 };
532 let xml = quick_xml::se::to_string(&def).unwrap();
533 assert!(xml.contains("<pivotTableDefinition"));
534 assert!(xml.contains("name=\"TestPivot\""));
535 assert!(xml.contains("cacheId=\"0\""));
536 assert!(xml.contains("<location"));
537 assert!(xml.contains("<pivotFields"));
538 assert!(!xml.contains("<rowFields"));
539 assert!(!xml.contains("<colFields"));
540 assert!(!xml.contains("<dataFields"));
541 }
542
543 #[test]
544 fn test_empty_pivot_fields() {
545 let fields = PivotFields {
546 count: Some(0),
547 fields: vec![],
548 };
549 let xml = quick_xml::se::to_string(&fields).unwrap();
550 let parsed: PivotFields = quick_xml::de::from_str(&xml).unwrap();
551 assert_eq!(parsed.count, Some(0));
552 assert!(parsed.fields.is_empty());
553 }
554
555 #[test]
556 fn test_empty_field_list() {
557 let list = FieldList {
558 count: Some(0),
559 fields: vec![],
560 };
561 let xml = quick_xml::se::to_string(&list).unwrap();
562 let parsed: FieldList = quick_xml::de::from_str(&xml).unwrap();
563 assert_eq!(parsed.count, Some(0));
564 assert!(parsed.fields.is_empty());
565 }
566
567 #[test]
568 fn test_empty_data_fields() {
569 let data_fields = DataFields {
570 count: Some(0),
571 fields: vec![],
572 };
573 let xml = quick_xml::se::to_string(&data_fields).unwrap();
574 let parsed: DataFields = quick_xml::de::from_str(&xml).unwrap();
575 assert_eq!(parsed.count, Some(0));
576 assert!(parsed.fields.is_empty());
577 }
578}