1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename = "pivotCacheDefinition")]
11pub struct PivotCacheDefinition {
12 #[serde(rename = "@xmlns")]
13 pub xmlns: String,
14
15 #[serde(rename = "@xmlns:r")]
16 pub xmlns_r: String,
17
18 #[serde(
19 rename = "@r:id",
20 alias = "@id",
21 skip_serializing_if = "Option::is_none"
22 )]
23 pub r_id: Option<String>,
24
25 #[serde(rename = "@recordCount", skip_serializing_if = "Option::is_none")]
26 pub record_count: Option<u32>,
27
28 #[serde(rename = "cacheSource")]
29 pub cache_source: CacheSource,
30
31 #[serde(rename = "cacheFields")]
32 pub cache_fields: CacheFields,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct CacheSource {
38 #[serde(rename = "@type")]
39 pub source_type: String,
40
41 #[serde(rename = "worksheetSource", skip_serializing_if = "Option::is_none")]
42 pub worksheet_source: Option<WorksheetSource>,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct WorksheetSource {
48 #[serde(rename = "@ref")]
49 pub reference: String,
50
51 #[serde(rename = "@sheet")]
52 pub sheet: String,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct CacheFields {
58 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
59 pub count: Option<u32>,
60
61 #[serde(rename = "cacheField", default)]
62 pub fields: Vec<CacheField>,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct CacheField {
68 #[serde(rename = "@name")]
69 pub name: String,
70
71 #[serde(rename = "@numFmtId", skip_serializing_if = "Option::is_none")]
72 pub num_fmt_id: Option<u32>,
73
74 #[serde(rename = "sharedItems", skip_serializing_if = "Option::is_none")]
75 pub shared_items: Option<SharedItems>,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct SharedItems {
81 #[serde(
82 rename = "@containsSemiMixedTypes",
83 skip_serializing_if = "Option::is_none"
84 )]
85 pub contains_semi_mixed_types: Option<bool>,
86
87 #[serde(rename = "@containsString", skip_serializing_if = "Option::is_none")]
88 pub contains_string: Option<bool>,
89
90 #[serde(rename = "@containsNumber", skip_serializing_if = "Option::is_none")]
91 pub contains_number: Option<bool>,
92
93 #[serde(rename = "@containsBlank", skip_serializing_if = "Option::is_none")]
94 pub contains_blank: Option<bool>,
95
96 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
97 pub count: Option<u32>,
98
99 #[serde(rename = "s", default)]
100 pub string_items: Vec<StringItem>,
101
102 #[serde(rename = "n", default)]
103 pub number_items: Vec<NumberItem>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct StringItem {
109 #[serde(rename = "@v")]
110 pub value: String,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct NumberItem {
116 #[serde(rename = "@v")]
117 pub value: f64,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(rename = "pivotCacheRecords")]
123pub struct PivotCacheRecords {
124 #[serde(rename = "@xmlns")]
125 pub xmlns: String,
126
127 #[serde(rename = "@xmlns:r")]
128 pub xmlns_r: String,
129
130 #[serde(rename = "@count", skip_serializing_if = "Option::is_none")]
131 pub count: Option<u32>,
132
133 #[serde(rename = "r", default)]
134 pub records: Vec<CacheRecord>,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct CacheRecord {
145 #[serde(rename = "x", default)]
146 pub index_fields: Vec<IndexField>,
147
148 #[serde(rename = "n", default)]
149 pub number_fields: Vec<NumberField>,
150
151 #[serde(rename = "s", default)]
152 pub string_fields: Vec<StringField>,
153
154 #[serde(rename = "b", default)]
155 pub bool_fields: Vec<BoolField>,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct IndexField {
161 #[serde(rename = "@v")]
162 pub v: u32,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct NumberField {
168 #[serde(rename = "@v")]
169 pub v: f64,
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct StringField {
175 #[serde(rename = "@v")]
176 pub v: String,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct BoolField {
182 #[serde(rename = "@v")]
183 pub v: bool,
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_worksheet_source_roundtrip() {
192 let src = WorksheetSource {
193 reference: "A1:D10".to_string(),
194 sheet: "Sheet1".to_string(),
195 };
196 let xml = quick_xml::se::to_string(&src).unwrap();
197 let parsed: WorksheetSource = quick_xml::de::from_str(&xml).unwrap();
198 assert_eq!(src, parsed);
199 }
200
201 #[test]
202 fn test_cache_source_roundtrip() {
203 let src = CacheSource {
204 source_type: "worksheet".to_string(),
205 worksheet_source: Some(WorksheetSource {
206 reference: "A1:D10".to_string(),
207 sheet: "Data".to_string(),
208 }),
209 };
210 let xml = quick_xml::se::to_string(&src).unwrap();
211 let parsed: CacheSource = quick_xml::de::from_str(&xml).unwrap();
212 assert_eq!(src, parsed);
213 }
214
215 #[test]
216 fn test_cache_source_without_worksheet() {
217 let src = CacheSource {
218 source_type: "external".to_string(),
219 worksheet_source: None,
220 };
221 let xml = quick_xml::se::to_string(&src).unwrap();
222 assert!(!xml.contains("worksheetSource"));
223 let parsed: CacheSource = quick_xml::de::from_str(&xml).unwrap();
224 assert_eq!(src, parsed);
225 }
226
227 #[test]
228 fn test_string_item_roundtrip() {
229 let item = StringItem {
230 value: "North".to_string(),
231 };
232 let xml = quick_xml::se::to_string(&item).unwrap();
233 let parsed: StringItem = quick_xml::de::from_str(&xml).unwrap();
234 assert_eq!(item, parsed);
235 }
236
237 #[test]
238 fn test_number_item_roundtrip() {
239 let item = NumberItem { value: 42.5 };
240 let xml = quick_xml::se::to_string(&item).unwrap();
241 let parsed: NumberItem = quick_xml::de::from_str(&xml).unwrap();
242 assert_eq!(item, parsed);
243 }
244
245 #[test]
246 fn test_shared_items_with_strings_roundtrip() {
247 let items = SharedItems {
248 contains_semi_mixed_types: Some(false),
249 contains_string: Some(true),
250 contains_number: Some(false),
251 contains_blank: None,
252 count: Some(3),
253 string_items: vec![
254 StringItem {
255 value: "North".to_string(),
256 },
257 StringItem {
258 value: "South".to_string(),
259 },
260 StringItem {
261 value: "East".to_string(),
262 },
263 ],
264 number_items: vec![],
265 };
266 let xml = quick_xml::se::to_string(&items).unwrap();
267 let parsed: SharedItems = quick_xml::de::from_str(&xml).unwrap();
268 assert_eq!(items, parsed);
269 }
270
271 #[test]
272 fn test_shared_items_with_numbers_roundtrip() {
273 let items = SharedItems {
274 contains_semi_mixed_types: None,
275 contains_string: Some(false),
276 contains_number: Some(true),
277 contains_blank: None,
278 count: Some(2),
279 string_items: vec![],
280 number_items: vec![NumberItem { value: 100.0 }, NumberItem { value: 200.0 }],
281 };
282 let xml = quick_xml::se::to_string(&items).unwrap();
283 let parsed: SharedItems = quick_xml::de::from_str(&xml).unwrap();
284 assert_eq!(items, parsed);
285 }
286
287 #[test]
288 fn test_shared_items_empty_roundtrip() {
289 let items = SharedItems {
290 contains_semi_mixed_types: None,
291 contains_string: None,
292 contains_number: None,
293 contains_blank: None,
294 count: Some(0),
295 string_items: vec![],
296 number_items: vec![],
297 };
298 let xml = quick_xml::se::to_string(&items).unwrap();
299 let parsed: SharedItems = quick_xml::de::from_str(&xml).unwrap();
300 assert_eq!(items, parsed);
301 }
302
303 #[test]
304 fn test_cache_field_roundtrip() {
305 let field = CacheField {
306 name: "Region".to_string(),
307 num_fmt_id: Some(0),
308 shared_items: Some(SharedItems {
309 contains_semi_mixed_types: None,
310 contains_string: Some(true),
311 contains_number: None,
312 contains_blank: None,
313 count: Some(2),
314 string_items: vec![
315 StringItem {
316 value: "North".to_string(),
317 },
318 StringItem {
319 value: "South".to_string(),
320 },
321 ],
322 number_items: vec![],
323 }),
324 };
325 let xml = quick_xml::se::to_string(&field).unwrap();
326 let parsed: CacheField = quick_xml::de::from_str(&xml).unwrap();
327 assert_eq!(field, parsed);
328 }
329
330 #[test]
331 fn test_cache_field_no_shared_items() {
332 let field = CacheField {
333 name: "Amount".to_string(),
334 num_fmt_id: None,
335 shared_items: None,
336 };
337 let xml = quick_xml::se::to_string(&field).unwrap();
338 assert!(!xml.contains("sharedItems"));
339 assert!(!xml.contains("numFmtId"));
340 let parsed: CacheField = quick_xml::de::from_str(&xml).unwrap();
341 assert_eq!(field, parsed);
342 }
343
344 #[test]
345 fn test_cache_fields_roundtrip() {
346 let fields = CacheFields {
347 count: Some(2),
348 fields: vec![
349 CacheField {
350 name: "Region".to_string(),
351 num_fmt_id: Some(0),
352 shared_items: None,
353 },
354 CacheField {
355 name: "Sales".to_string(),
356 num_fmt_id: Some(0),
357 shared_items: None,
358 },
359 ],
360 };
361 let xml = quick_xml::se::to_string(&fields).unwrap();
362 let parsed: CacheFields = quick_xml::de::from_str(&xml).unwrap();
363 assert_eq!(fields, parsed);
364 }
365
366 #[test]
367 fn test_pivot_cache_definition_roundtrip() {
368 let def = PivotCacheDefinition {
369 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
370 xmlns_r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
371 .to_string(),
372 r_id: None,
373 record_count: Some(5),
374 cache_source: CacheSource {
375 source_type: "worksheet".to_string(),
376 worksheet_source: Some(WorksheetSource {
377 reference: "A1:C6".to_string(),
378 sheet: "Data".to_string(),
379 }),
380 },
381 cache_fields: CacheFields {
382 count: Some(3),
383 fields: vec![
384 CacheField {
385 name: "Name".to_string(),
386 num_fmt_id: Some(0),
387 shared_items: None,
388 },
389 CacheField {
390 name: "Region".to_string(),
391 num_fmt_id: Some(0),
392 shared_items: None,
393 },
394 CacheField {
395 name: "Sales".to_string(),
396 num_fmt_id: Some(0),
397 shared_items: None,
398 },
399 ],
400 },
401 };
402 let xml = quick_xml::se::to_string(&def).unwrap();
403 let parsed: PivotCacheDefinition = quick_xml::de::from_str(&xml).unwrap();
404 assert_eq!(def, parsed);
405 }
406
407 #[test]
408 fn test_pivot_cache_definition_structure() {
409 let def = PivotCacheDefinition {
410 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
411 xmlns_r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
412 .to_string(),
413 r_id: Some("rId1".to_string()),
414 record_count: Some(10),
415 cache_source: CacheSource {
416 source_type: "worksheet".to_string(),
417 worksheet_source: Some(WorksheetSource {
418 reference: "A1:D11".to_string(),
419 sheet: "Sheet1".to_string(),
420 }),
421 },
422 cache_fields: CacheFields {
423 count: Some(1),
424 fields: vec![CacheField {
425 name: "Col1".to_string(),
426 num_fmt_id: None,
427 shared_items: None,
428 }],
429 },
430 };
431 let xml = quick_xml::se::to_string(&def).unwrap();
432 assert!(xml.contains("<pivotCacheDefinition"));
433 assert!(xml.contains("recordCount=\"10\""));
434 assert!(xml.contains("<cacheSource"));
435 assert!(xml.contains("type=\"worksheet\""));
436 assert!(xml.contains("<worksheetSource"));
437 assert!(xml.contains("<cacheFields"));
438 }
439
440 #[test]
441 fn test_index_field_roundtrip() {
442 let field = IndexField { v: 3 };
443 let xml = quick_xml::se::to_string(&field).unwrap();
444 let parsed: IndexField = quick_xml::de::from_str(&xml).unwrap();
445 assert_eq!(field, parsed);
446 }
447
448 #[test]
449 fn test_number_field_roundtrip() {
450 let field = NumberField { v: 99.5 };
451 let xml = quick_xml::se::to_string(&field).unwrap();
452 let parsed: NumberField = quick_xml::de::from_str(&xml).unwrap();
453 assert_eq!(field, parsed);
454 }
455
456 #[test]
457 fn test_string_field_roundtrip() {
458 let field = StringField {
459 v: "hello".to_string(),
460 };
461 let xml = quick_xml::se::to_string(&field).unwrap();
462 let parsed: StringField = quick_xml::de::from_str(&xml).unwrap();
463 assert_eq!(field, parsed);
464 }
465
466 #[test]
467 fn test_bool_field_roundtrip() {
468 let field = BoolField { v: true };
469 let xml = quick_xml::se::to_string(&field).unwrap();
470 let parsed: BoolField = quick_xml::de::from_str(&xml).unwrap();
471 assert_eq!(field, parsed);
472 }
473
474 #[test]
475 fn test_cache_record_roundtrip() {
476 let record = CacheRecord {
477 index_fields: vec![IndexField { v: 0 }, IndexField { v: 1 }],
478 number_fields: vec![NumberField { v: 150.0 }],
479 string_fields: vec![],
480 bool_fields: vec![],
481 };
482 let xml = quick_xml::se::to_string(&record).unwrap();
483 let parsed: CacheRecord = quick_xml::de::from_str(&xml).unwrap();
484 assert_eq!(record, parsed);
485 }
486
487 #[test]
488 fn test_cache_record_with_strings() {
489 let record = CacheRecord {
490 index_fields: vec![],
491 number_fields: vec![],
492 string_fields: vec![
493 StringField {
494 v: "alpha".to_string(),
495 },
496 StringField {
497 v: "beta".to_string(),
498 },
499 ],
500 bool_fields: vec![BoolField { v: false }],
501 };
502 let xml = quick_xml::se::to_string(&record).unwrap();
503 let parsed: CacheRecord = quick_xml::de::from_str(&xml).unwrap();
504 assert_eq!(record, parsed);
505 }
506
507 #[test]
508 fn test_pivot_cache_records_roundtrip() {
509 let records = PivotCacheRecords {
510 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
511 xmlns_r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
512 .to_string(),
513 count: Some(2),
514 records: vec![
515 CacheRecord {
516 index_fields: vec![IndexField { v: 0 }],
517 number_fields: vec![NumberField { v: 100.0 }],
518 string_fields: vec![],
519 bool_fields: vec![],
520 },
521 CacheRecord {
522 index_fields: vec![IndexField { v: 1 }],
523 number_fields: vec![NumberField { v: 200.0 }],
524 string_fields: vec![],
525 bool_fields: vec![],
526 },
527 ],
528 };
529 let xml = quick_xml::se::to_string(&records).unwrap();
530 let parsed: PivotCacheRecords = quick_xml::de::from_str(&xml).unwrap();
531 assert_eq!(records, parsed);
532 }
533
534 #[test]
535 fn test_pivot_cache_records_empty() {
536 let records = PivotCacheRecords {
537 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
538 xmlns_r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
539 .to_string(),
540 count: Some(0),
541 records: vec![],
542 };
543 let xml = quick_xml::se::to_string(&records).unwrap();
544 let parsed: PivotCacheRecords = quick_xml::de::from_str(&xml).unwrap();
545 assert_eq!(records, parsed);
546 }
547
548 #[test]
549 fn test_pivot_cache_records_structure() {
550 let records = PivotCacheRecords {
551 xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main".to_string(),
552 xmlns_r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
553 .to_string(),
554 count: Some(1),
555 records: vec![CacheRecord {
556 index_fields: vec![IndexField { v: 0 }],
557 number_fields: vec![],
558 string_fields: vec![],
559 bool_fields: vec![],
560 }],
561 };
562 let xml = quick_xml::se::to_string(&records).unwrap();
563 assert!(xml.contains("<pivotCacheRecords"));
564 assert!(xml.contains("count=\"1\""));
565 }
566}