1use std::collections::HashMap;
6
7use quick_xml::de::from_reader as xml_from_reader;
8use quick_xml::se::to_string as xml_to_string;
9use regex::Regex;
10
11use crate::constants::{
12 EXT_URI_PIVOT_DATA_FIELD, MAX_FIELD_LENGTH, NAMESPACE_SPREADSHEET, NAMESPACE_SPREADSHEET_X14,
13 PIVOT_TABLE_REFRESHED_VERSION, PIVOT_TABLE_VERSION, SOURCE_RELATIONSHIP_PIVOT_CACHE,
14 SOURCE_RELATIONSHIP_PIVOT_TABLE,
15};
16use crate::errors::{
17 ErrNameLength, ErrParameterInvalid, ErrParameterRequired, ErrPivotTableClassicLayout,
18 ErrPivotTableShowValuesAsBaseField, ErrPivotTableShowValuesAsBaseItem, ErrSheetNotExist,
19 ErrUnsupportedPivotTableShowValuesAsType, Result, new_no_exist_table_error,
20 new_pivot_table_col_fields_error, new_pivot_table_data_range_error,
21 new_pivot_table_range_error, new_pivot_table_row_fields_error,
22 new_pivot_table_selected_item_error, new_pivot_table_show_values_as_base_field_error,
23 new_unsupported_pivot_cache_source_type_error,
24};
25use crate::file::{File, namespace_strict_to_transitional};
26use crate::lib_util::{
27 coordinates_to_cell_name, count_utf16_string, in_str_slice, is_numeric,
28 range_ref_to_coordinates, truncate_utf16_units,
29};
30use crate::numfmt::built_in_num_fmt_code;
31use crate::xml::common::{XlsxExt, XlsxExtLst};
32use crate::xml::pivot_cache::{
33 DecodeX14PivotCacheDefinition, XlsxCacheField, XlsxCacheFields, XlsxCacheSource,
34 XlsxPivotCacheDefinition, XlsxSharedItem, XlsxSharedItemData, XlsxSharedItems,
35 XlsxWorksheetSource,
36};
37use crate::xml::pivot_table::{
38 XlsxColFields, XlsxColItems, XlsxDataField, XlsxDataFields, XlsxField, XlsxI, XlsxItem,
39 XlsxItems, XlsxLocation, XlsxPageField, XlsxPageFields, XlsxPivotField, XlsxPivotFields,
40 XlsxPivotTableDefinition, XlsxPivotTableStyleInfo, XlsxRowFields, XlsxRowItems, XlsxX,
41 XlsxX14DataField,
42};
43use crate::xml::workbook::{XlsxPivotCache, XlsxPivotCaches};
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct PivotTableShowValuesAsType(pub u8);
52
53impl PivotTableShowValuesAsType {
54 pub const NO_CALCULATION: PivotTableShowValuesAsType = PivotTableShowValuesAsType(0);
55 pub const PERCENT_OF_GRAND_TOTAL: PivotTableShowValuesAsType = PivotTableShowValuesAsType(1);
56 pub const PERCENT_OF_COLUMN_TOTAL: PivotTableShowValuesAsType = PivotTableShowValuesAsType(2);
57 pub const PERCENT_OF_ROW_TOTAL: PivotTableShowValuesAsType = PivotTableShowValuesAsType(3);
58 pub const PERCENT_OF: PivotTableShowValuesAsType = PivotTableShowValuesAsType(4);
59 pub const PERCENT_OF_PARENT_ROW_TOTAL: PivotTableShowValuesAsType =
60 PivotTableShowValuesAsType(5);
61 pub const PERCENT_OF_PARENT_COLUMN_TOTAL: PivotTableShowValuesAsType =
62 PivotTableShowValuesAsType(6);
63 pub const PERCENT_OF_PARENT_TOTAL: PivotTableShowValuesAsType = PivotTableShowValuesAsType(7);
64 pub const DIFFERENCE_FROM: PivotTableShowValuesAsType = PivotTableShowValuesAsType(8);
65 pub const PERCENT_DIFFERENCE_FROM: PivotTableShowValuesAsType = PivotTableShowValuesAsType(9);
66 pub const RUNNING_TOTAL_IN: PivotTableShowValuesAsType = PivotTableShowValuesAsType(10);
67 pub const PERCENT_RUNNING_TOTAL_IN: PivotTableShowValuesAsType = PivotTableShowValuesAsType(11);
68 pub const RANK_SMALLEST_TO_LARGEST: PivotTableShowValuesAsType = PivotTableShowValuesAsType(12);
69 pub const RANK_LARGEST_TO_SMALLEST: PivotTableShowValuesAsType = PivotTableShowValuesAsType(13);
70 pub const INDEX: PivotTableShowValuesAsType = PivotTableShowValuesAsType(14);
71}
72
73#[derive(Debug, Default, Clone, PartialEq, Eq)]
75pub struct PivotTableShowValuesAs {
76 pub r#type: PivotTableShowValuesAsType,
77 pub base_field: String,
78 pub base_item: String,
79}
80
81#[derive(Debug, Default, Clone, PartialEq, Eq)]
83pub struct PivotTableField {
84 pub compact: bool,
85 pub data: String,
86 pub name: String,
87 pub outline: bool,
88 pub show_all: bool,
89 pub insert_blank_row: bool,
90 pub subtotal: String,
91 pub default_subtotal: bool,
92 pub num_fmt: i32,
93 pub selected_items: Vec<String>,
94 pub show_values_as: PivotTableShowValuesAs,
95}
96
97#[derive(Debug, Default, Clone, PartialEq)]
99pub struct PivotTableOptions {
100 pub data_range: String,
101 pub pivot_table_range: String,
102 pub name: String,
103 pub rows: Vec<PivotTableField>,
104 pub columns: Vec<PivotTableField>,
105 pub data: Vec<PivotTableField>,
106 pub filter: Vec<PivotTableField>,
107 pub row_grand_totals: bool,
108 pub col_grand_totals: bool,
109 pub show_drill: bool,
110 pub use_auto_formatting: bool,
111 pub page_over_then_down: bool,
112 pub merge_item: bool,
113 pub classic_layout: bool,
114 pub compact_data: bool,
115 pub show_error: bool,
116 pub show_row_headers: bool,
117 pub show_col_headers: bool,
118 pub show_row_stripes: bool,
119 pub show_col_stripes: bool,
120 pub show_last_column: bool,
121 pub field_print_titles: bool,
122 pub item_print_titles: bool,
123 pub pivot_table_style_name: String,
124
125 items: HashMap<String, Vec<XlsxItem>>,
127 shared_items: HashMap<String, XlsxSharedItems>,
128 pivot_table_xml: String,
129 pivot_cache_xml: String,
130 pivot_sheet_name: String,
131 pivot_data_range: String,
132 named_data_range: bool,
133}
134
135const FORMULA_ERRORS: &[&str] = &[
140 "#DIV/0!",
141 "#NAME?",
142 "#N/A",
143 "#NUM!",
144 "#VALUE!",
145 "#REF!",
146 "#NULL!",
147 "#SPILL!",
148 "#CALC!",
149 "#GETTING_DATA",
150];
151
152fn show_values_as_map() -> HashMap<PivotTableShowValuesAsType, &'static str> {
153 [
154 (
155 PivotTableShowValuesAsType::PERCENT_OF_GRAND_TOTAL,
156 "percentOfTotal",
157 ),
158 (
159 PivotTableShowValuesAsType::PERCENT_OF_COLUMN_TOTAL,
160 "percentOfCol",
161 ),
162 (
163 PivotTableShowValuesAsType::PERCENT_OF_ROW_TOTAL,
164 "percentOfRow",
165 ),
166 (PivotTableShowValuesAsType::PERCENT_OF, "percent"),
167 (
168 PivotTableShowValuesAsType::PERCENT_OF_PARENT_ROW_TOTAL,
169 "percentOfParentRow",
170 ),
171 (
172 PivotTableShowValuesAsType::PERCENT_OF_PARENT_COLUMN_TOTAL,
173 "percentOfParentCol",
174 ),
175 (
176 PivotTableShowValuesAsType::PERCENT_OF_PARENT_TOTAL,
177 "percentOfParent",
178 ),
179 (PivotTableShowValuesAsType::DIFFERENCE_FROM, "difference"),
180 (
181 PivotTableShowValuesAsType::PERCENT_DIFFERENCE_FROM,
182 "percentDiff",
183 ),
184 (PivotTableShowValuesAsType::RUNNING_TOTAL_IN, "runTotal"),
185 (
186 PivotTableShowValuesAsType::PERCENT_RUNNING_TOTAL_IN,
187 "percentOfRunningTotal",
188 ),
189 (
190 PivotTableShowValuesAsType::RANK_SMALLEST_TO_LARGEST,
191 "rankAscending",
192 ),
193 (
194 PivotTableShowValuesAsType::RANK_LARGEST_TO_SMALLEST,
195 "rankDescending",
196 ),
197 (PivotTableShowValuesAsType::INDEX, "index"),
198 ]
199 .into_iter()
200 .collect()
201}
202
203fn x14_show_values_as_types() -> HashMap<PivotTableShowValuesAsType, bool> {
204 [
205 PivotTableShowValuesAsType::PERCENT_OF_PARENT_ROW_TOTAL,
206 PivotTableShowValuesAsType::PERCENT_OF_PARENT_COLUMN_TOTAL,
207 PivotTableShowValuesAsType::PERCENT_OF_PARENT_TOTAL,
208 PivotTableShowValuesAsType::PERCENT_RUNNING_TOTAL_IN,
209 PivotTableShowValuesAsType::RANK_SMALLEST_TO_LARGEST,
210 PivotTableShowValuesAsType::RANK_LARGEST_TO_SMALLEST,
211 PivotTableShowValuesAsType::INDEX,
212 ]
213 .into_iter()
214 .map(|t| (t, true))
215 .collect()
216}
217
218fn base_field_required() -> HashMap<PivotTableShowValuesAsType, bool> {
219 [
220 PivotTableShowValuesAsType::PERCENT_OF,
221 PivotTableShowValuesAsType::PERCENT_OF_PARENT_TOTAL,
222 PivotTableShowValuesAsType::DIFFERENCE_FROM,
223 PivotTableShowValuesAsType::PERCENT_DIFFERENCE_FROM,
224 PivotTableShowValuesAsType::RUNNING_TOTAL_IN,
225 PivotTableShowValuesAsType::PERCENT_RUNNING_TOTAL_IN,
226 PivotTableShowValuesAsType::RANK_SMALLEST_TO_LARGEST,
227 PivotTableShowValuesAsType::RANK_LARGEST_TO_SMALLEST,
228 ]
229 .into_iter()
230 .map(|t| (t, true))
231 .collect()
232}
233
234fn base_item_required() -> HashMap<PivotTableShowValuesAsType, bool> {
235 [
236 PivotTableShowValuesAsType::PERCENT_OF,
237 PivotTableShowValuesAsType::DIFFERENCE_FROM,
238 PivotTableShowValuesAsType::PERCENT_DIFFERENCE_FROM,
239 ]
240 .into_iter()
241 .map(|t| (t, true))
242 .collect()
243}
244
245#[derive(Debug, Clone, Copy, PartialEq)]
246enum CellType {
247 Unset,
248 Number,
249 Bool,
250 Error,
251 Formula,
252 InlineString,
253 SharedString,
254}
255
256impl File {
261 pub fn add_pivot_table(&self, opts: &mut PivotTableOptions) -> Result<()> {
263 let (_, pivot_table_sheet_path) = self.parse_format_pivot_table_set(opts)?;
264 self.clear_calc_cache();
265 let pivot_table_id = self.count_pivot_tables() + 1;
266 let pivot_cache_id = self.count_pivot_cache() + 1;
267
268 let sheet_relationships_pivot_table_xml =
269 format!("../pivotTables/pivotTable{pivot_table_id}.xml");
270 opts.pivot_table_xml = sheet_relationships_pivot_table_xml.replace("..", "xl");
271 opts.pivot_cache_xml = format!("xl/pivotCache/pivotCacheDefinition{pivot_cache_id}.xml");
272 self.add_pivot_cache(opts)?;
273
274 let workbook_pivot_cache_rid = self.add_rels(
275 &self.get_workbook_rels_path(),
276 SOURCE_RELATIONSHIP_PIVOT_CACHE,
277 &opts.pivot_cache_xml.trim_start_matches("xl/"),
278 "",
279 );
280 let cache_id = self.add_workbook_pivot_cache(workbook_pivot_cache_rid);
281
282 let pivot_cache_rels = format!("xl/pivotTables/_rels/pivotTable{pivot_table_id}.xml.rels");
283 let _ = self.add_rels(
284 &pivot_cache_rels,
285 SOURCE_RELATIONSHIP_PIVOT_CACHE,
286 &format!("../pivotCache/pivotCacheDefinition{pivot_cache_id}.xml"),
287 "",
288 );
289 self.add_pivot_table_internal(cache_id, pivot_table_id, opts)?;
290
291 let pivot_table_sheet_rels = format!(
292 "xl/worksheets/_rels/{}.rels",
293 pivot_table_sheet_path.trim_start_matches("xl/worksheets/")
294 );
295 self.add_rels(
296 &pivot_table_sheet_rels,
297 SOURCE_RELATIONSHIP_PIVOT_TABLE,
298 &sheet_relationships_pivot_table_xml,
299 "",
300 );
301 self.add_content_type_part(pivot_table_id, "pivotTable")?;
302 self.add_content_type_part(pivot_cache_id, "pivotCache")
303 }
304
305 pub fn get_pivot_tables(&self, sheet: &str) -> Result<Vec<PivotTableOptions>> {
307 let mut pivot_tables = Vec::new();
308 let Some(name) = self.get_sheet_xml_path(sheet) else {
309 return Err(Box::new(ErrSheetNotExist {
310 sheet_name: sheet.to_string(),
311 }));
312 };
313 let rels = format!(
314 "xl/worksheets/_rels/{}.rels",
315 name.trim_start_matches("xl/worksheets/")
316 );
317 let sheet_rels = match self.rels_reader(&rels)? {
318 Some(r) => r,
319 None => crate::xml::workbook::XlsxRelationships::default(),
320 };
321 for v in &sheet_rels.relationships {
322 if v.r#type == SOURCE_RELATIONSHIP_PIVOT_TABLE {
323 let pivot_table_xml = v.target.replace("..", "xl");
324 let basename = std::path::Path::new(&v.target)
325 .file_name()
326 .and_then(|s| s.to_str())
327 .unwrap_or("");
328 let pivot_cache_rels = format!("xl/pivotTables/_rels/{basename}.rels");
329 pivot_tables.push(self.get_pivot_table(
330 sheet,
331 &pivot_table_xml,
332 &pivot_cache_rels,
333 )?);
334 }
335 }
336 Ok(pivot_tables)
337 }
338
339 pub fn delete_pivot_table(&self, sheet: &str, name: &str) -> Result<()> {
341 let Some(sheet_xml) = self.get_sheet_xml_path(sheet) else {
342 return Err(Box::new(ErrSheetNotExist {
343 sheet_name: sheet.to_string(),
344 }));
345 };
346 let rels = format!(
347 "xl/worksheets/_rels/{}.rels",
348 sheet_xml.trim_start_matches("xl/worksheets/")
349 );
350 let sheet_rels = match self.rels_reader(&rels)? {
351 Some(r) => r,
352 None => crate::xml::workbook::XlsxRelationships::default(),
353 };
354 let opts = self.get_pivot_tables(sheet)?;
355 self.clear_calc_cache();
356 let mut pivot_table_caches: HashMap<String, i32> = HashMap::new();
357 for sheet_name in self.get_sheet_list() {
358 if let Ok(pts) = self.get_pivot_tables(&sheet_name) {
359 for pt in pts {
360 *pivot_table_caches.entry(pt.pivot_cache_xml).or_default() += 1;
361 }
362 }
363 }
364
365 for v in &sheet_rels.relationships.clone() {
366 if v.r#type == SOURCE_RELATIONSHIP_PIVOT_TABLE {
367 let pivot_table_xml = v.target.replace("..", "xl");
368 for opt in &opts {
369 if opt.name == name && opt.pivot_table_xml == pivot_table_xml {
370 if pivot_table_caches
371 .get(&opt.pivot_cache_xml)
372 .copied()
373 .unwrap_or(0)
374 == 1
375 {
376 self.delete_workbook_pivot_cache(opt)?;
377 }
378 self.delete_sheet_relationships(sheet, &v.id);
379 return Ok(());
380 }
381 }
382 }
383 }
384 Err(new_no_exist_table_error(name).into())
385 }
386
387 fn parse_format_pivot_table_set(
392 &self,
393 opts: &mut PivotTableOptions,
394 ) -> Result<(crate::xml::worksheet::XlsxWorksheet, String)> {
395 let (pivot_table_sheet_name, _) =
396 self.adjust_range(&opts.pivot_table_range).map_err(|err| {
397 let err: Box<dyn std::error::Error + Send + Sync> =
398 new_pivot_table_range_error(&err.to_string()).into();
399 err
400 })?;
401 if count_utf16_string(&opts.name) > MAX_FIELD_LENGTH {
402 return Err(Box::new(ErrNameLength));
403 }
404 opts.pivot_sheet_name = pivot_table_sheet_name.clone();
405 self.get_pivot_table_data_range(opts)?;
406 let (data_sheet_name, _) = self.adjust_range(&opts.pivot_data_range).map_err(|err| {
407 let err: Box<dyn std::error::Error + Send + Sync> =
408 new_pivot_table_data_range_error(&err.to_string()).into();
409 err
410 })?;
411 let data_sheet = self.work_sheet_reader(&data_sheet_name)?;
412 let Some(pivot_table_sheet_path) = self.get_sheet_xml_path(&pivot_table_sheet_name) else {
413 return Err(Box::new(ErrSheetNotExist {
414 sheet_name: pivot_table_sheet_name,
415 }));
416 };
417 if opts.compact_data && opts.classic_layout {
418 return Err(Box::new(ErrPivotTableClassicLayout));
419 }
420
421 let mut col_data_fields = Vec::new();
422 let mut row_data_fields = Vec::new();
423 for f in &opts.filter {
424 if in_pivot_table_field(&opts.columns, &f.data) != -1 {
425 col_data_fields.push(f.data.clone());
426 }
427 if in_pivot_table_field(&opts.rows, &f.data) != -1 {
428 row_data_fields.push(f.data.clone());
429 }
430 }
431 if !col_data_fields.is_empty() {
432 return Err(new_pivot_table_col_fields_error(&col_data_fields).into());
433 }
434 if !row_data_fields.is_empty() {
435 return Err(new_pivot_table_row_fields_error(&row_data_fields).into());
436 }
437 Ok((data_sheet, pivot_table_sheet_path))
438 }
439
440 fn adjust_range(&self, range_str: &str) -> Result<(String, Vec<i32>)> {
441 if range_str.is_empty() {
442 return Err(Box::new(ErrParameterRequired));
443 }
444 let rng: Vec<&str> = range_str.split('!').collect();
445 if rng.len() != 2 {
446 return Err(Box::new(ErrParameterInvalid));
447 }
448 let trim_rng = rng[1].replace('$', "");
449 let mut coordinates = range_ref_to_coordinates(&trim_rng)?;
450 let (x1, y1, x2, y2) = (
451 coordinates[0],
452 coordinates[1],
453 coordinates[2],
454 coordinates[3],
455 );
456 if x1 == x2 && y1 == y2 {
457 return Err(Box::new(ErrParameterInvalid));
458 }
459 if x2 < x1 {
460 coordinates.swap(0, 2);
461 }
462 if y2 < y1 {
463 coordinates.swap(1, 3);
464 }
465 Ok((rng[0].to_string(), coordinates))
466 }
467
468 fn get_table_fields_order(&self, opts: &PivotTableOptions) -> Result<Vec<String>> {
469 let mut order = Vec::new();
470 let mut opts = opts.clone();
471 self.get_pivot_table_data_range(&mut opts)?;
472 let (data_sheet, coordinates) = self.adjust_range(&opts.pivot_data_range)?;
473 for col in coordinates[0]..=coordinates[2] {
474 let coordinate = coordinates_to_cell_name(col, coordinates[1], false)?;
475 let name = self.get_cell_value(&data_sheet, &coordinate)?;
476 if name.is_empty() {
477 return Err(ErrParameterInvalid.into());
478 }
479 order.push(name);
480 }
481 Ok(order)
482 }
483
484 fn add_missing_item(shared_items: &mut XlsxSharedItems) {
485 for item in &shared_items.items {
486 if matches!(item, XlsxSharedItem::M(_)) {
487 return;
488 }
489 }
490 shared_items
491 .items
492 .push(XlsxSharedItem::M(XlsxSharedItemData {
493 xmlns: Some("".to_string()),
494 ..Default::default()
495 }));
496 shared_items.contains_blank = Some(true);
497 }
498
499 fn add_number_item(shared_items: &mut XlsxSharedItems, val: &str) {
500 for item in &shared_items.items {
501 if let XlsxSharedItem::N(data) = item {
502 if data.v.as_deref() == Some(val) {
503 return;
504 }
505 }
506 }
507 shared_items
508 .items
509 .push(XlsxSharedItem::N(XlsxSharedItemData {
510 v: Some(val.to_string()),
511 xmlns: Some("".to_string()),
512 ..Default::default()
513 }));
514 shared_items.contains_number = Some(true);
515 }
516
517 fn add_boolean_item(shared_items: &mut XlsxSharedItems, val: &str) {
518 let v = (val.to_uppercase() == "TRUE").to_string();
519 for item in &shared_items.items {
520 if let XlsxSharedItem::B(data) = item {
521 if data.v.as_deref() == Some(&v) {
522 return;
523 }
524 }
525 }
526 shared_items
527 .items
528 .push(XlsxSharedItem::B(XlsxSharedItemData {
529 v: Some(v),
530 xmlns: Some("".to_string()),
531 ..Default::default()
532 }));
533 }
534
535 fn add_error_item(shared_items: &mut XlsxSharedItems, val: &str) {
536 for item in &shared_items.items {
537 if let XlsxSharedItem::E(data) = item {
538 if data.v.as_deref() == Some(val) {
539 return;
540 }
541 }
542 }
543 shared_items
544 .items
545 .push(XlsxSharedItem::E(XlsxSharedItemData {
546 v: Some(val.to_string()),
547 xmlns: Some("".to_string()),
548 ..Default::default()
549 }));
550 }
551
552 fn add_string_item(shared_items: &mut XlsxSharedItems, val: &str) {
553 for item in &shared_items.items {
554 if let XlsxSharedItem::S(data) = item {
555 if data.v.as_deref() == Some(val) {
556 return;
557 }
558 }
559 }
560 shared_items
561 .items
562 .push(XlsxSharedItem::S(XlsxSharedItemData {
563 v: Some(val.to_string()),
564 xmlns: Some("".to_string()),
565 ..Default::default()
566 }));
567 shared_items.contains_string = Some(true);
568 }
569
570 fn check_selected_items(
571 shared_items: &XlsxSharedItems,
572 field: &str,
573 selected_items: &[String],
574 ) -> Result<()> {
575 for shared_item in selected_items {
576 let mut found = false;
577 for item in &shared_items.items {
578 match item {
579 XlsxSharedItem::M(_) => {
580 if shared_item.is_empty() {
581 found = true;
582 }
583 }
584 XlsxSharedItem::B(data) => {
585 if data
586 .v
587 .as_deref()
588 .map(|v| v.eq_ignore_ascii_case(shared_item))
589 .unwrap_or(false)
590 {
591 found = true;
592 }
593 }
594 XlsxSharedItem::N(data)
595 | XlsxSharedItem::E(data)
596 | XlsxSharedItem::S(data)
597 | XlsxSharedItem::D(data) => {
598 if data.v.as_deref() == Some(shared_item.as_str()) {
599 found = true;
600 }
601 }
602 }
603 if found {
604 break;
605 }
606 }
607 if !found {
608 return Err(new_pivot_table_selected_item_error(shared_item, field).into());
609 }
610 }
611 Ok(())
612 }
613
614 fn cell_type(&self, sheet: &str, cell: &str) -> Result<CellType> {
615 let ws = self.work_sheet_reader(sheet)?;
616 let c = match crate::cell::find_cell(&ws, cell) {
617 Some(c) => c,
618 None => return Ok(CellType::Unset),
619 };
620 match c.t.as_deref() {
621 Some("b") => return Ok(CellType::Bool),
622 Some("e") => return Ok(CellType::Error),
623 Some("inlineStr") => return Ok(CellType::InlineString),
624 Some("s") => return Ok(CellType::SharedString),
625 Some("str") => return Ok(CellType::Formula),
626 _ => {}
627 }
628 if c.f.is_some() {
629 return Ok(CellType::Formula);
630 }
631 let val = c.v.as_deref().unwrap_or("");
632 if val.is_empty() {
633 return Ok(CellType::Unset);
634 }
635 if is_numeric(val).0 {
636 return Ok(CellType::Number);
637 }
638 Ok(CellType::SharedString)
639 }
640
641 fn add_shared_items(
642 &self,
643 sheet: &str,
644 col: i32,
645 from_row: i32,
646 to_row: i32,
647 ) -> Result<XlsxSharedItems> {
648 let mut si = XlsxSharedItems::default();
649 for row in from_row..=to_row {
650 let cell = coordinates_to_cell_name(col, row, false)?;
651 let val = match self.calc_cell_value(sheet, &cell) {
652 Ok(v) => v,
653 Err(err) => {
654 let msg = err.to_string();
655 if FORMULA_ERRORS.iter().any(|e| msg.contains(e)) {
656 Self::add_error_item(&mut si, &msg);
657 continue;
658 }
659 return Err(err);
660 }
661 };
662 if val.is_empty() {
663 Self::add_missing_item(&mut si);
664 continue;
665 }
666 let cell_type = self.cell_type(sheet, &cell)?;
667 match cell_type {
668 CellType::Unset | CellType::Number => Self::add_number_item(&mut si, &val),
669 CellType::Bool => Self::add_boolean_item(&mut si, &val),
670 CellType::Error => Self::add_error_item(&mut si, &val),
671 CellType::Formula => {
672 if is_numeric(&val).0 {
673 Self::add_number_item(&mut si, &val);
674 } else {
675 Self::add_string_item(&mut si, &val);
676 }
677 }
678 CellType::InlineString | CellType::SharedString => {
679 Self::add_string_item(&mut si, &val)
680 }
681 }
682 }
683 Ok(si)
684 }
685
686 fn build_pivot_shared_items(
687 &self,
688 opts: &mut PivotTableOptions,
689 idx: i32,
690 coordinates: &[i32],
691 field: &PivotTableField,
692 ) -> Result<()> {
693 let mut items = Vec::new();
694 let mut shared_items = self.add_shared_items(
695 &opts.pivot_sheet_name,
696 coordinates[0] + idx,
697 coordinates[1] + 1,
698 coordinates[3],
699 )?;
700 let mut i = 0i32;
701 for item in &shared_items.items {
702 let hidden = if field.selected_items.is_empty() {
703 false
704 } else {
705 match item {
706 XlsxSharedItem::M(_) => in_str_slice(&field.selected_items, "", true) == -1,
707 XlsxSharedItem::B(data) => {
708 in_str_slice(
709 &field.selected_items,
710 data.v.as_deref().unwrap_or(""),
711 false,
712 ) == -1
713 }
714 XlsxSharedItem::N(data)
715 | XlsxSharedItem::E(data)
716 | XlsxSharedItem::S(data)
717 | XlsxSharedItem::D(data) => {
718 in_str_slice(&field.selected_items, data.v.as_deref().unwrap_or(""), true)
719 == -1
720 }
721 }
722 };
723 items.push(XlsxItem {
724 h: Some(hidden),
725 x: Some(i as i64),
726 ..Default::default()
727 });
728 i += 1;
729 }
730 Self::check_selected_items(&shared_items, &field.data, &field.selected_items)?;
731
732 let mut types_set = HashMap::new();
733 let mut num_count = 0i32;
734 for item in &shared_items.items {
735 let tag = match item {
736 XlsxSharedItem::M(_) => "m",
737 XlsxSharedItem::N(_) => "n",
738 XlsxSharedItem::B(_) => "b",
739 XlsxSharedItem::E(_) => "e",
740 XlsxSharedItem::S(_) => "s",
741 XlsxSharedItem::D(_) => "d",
742 };
743 types_set.insert(tag, true);
744 if tag == "n" {
745 num_count += 1;
746 }
747 }
748 shared_items.contains_mixed_types = Some(types_set.len() > 1);
749 if num_count == i {
750 shared_items.contains_integer = Some(true);
751 shared_items.contains_string = Some(false);
752 shared_items.contains_semi_mixed_types = Some(false);
753 }
754 shared_items.count = i;
755
756 opts.items.insert(field.data.clone(), items);
757 opts.shared_items.insert(field.data.clone(), shared_items);
758 Ok(())
759 }
760
761 fn add_pivot_shared_items(
762 &self,
763 opts: &mut PivotTableOptions,
764 coordinates: &[i32],
765 fields_type: &str,
766 ) -> Result<()> {
767 let fields: Vec<PivotTableField> = match fields_type {
768 "filters" => opts.filter.clone(),
769 "cols" => opts.columns.clone(),
770 "rows" => opts.rows.clone(),
771 _ => Vec::new(),
772 };
773 let mut show_values_as_base_field_required = false;
774 for field in &opts.data {
775 if let Some(t) = show_values_as_map().get(&field.show_values_as.r#type) {
776 let _ = t;
777 if base_field_required()
778 .get(&field.show_values_as.r#type)
779 .copied()
780 .unwrap_or(false)
781 {
782 show_values_as_base_field_required = true;
783 }
784 }
785 }
786 let field_refs: Vec<&PivotTableField> = fields.iter().collect();
787 let fields_index = self.get_pivot_fields_index(&field_refs, opts)?;
788 for (i, field) in fields.iter().enumerate() {
789 if !field.selected_items.is_empty() || show_values_as_base_field_required {
790 self.build_pivot_shared_items(opts, fields_index[i], coordinates, field)?;
791 }
792 }
793 Ok(())
794 }
795
796 fn add_pivot_cache(&self, opts: &mut PivotTableOptions) -> Result<()> {
797 let (data_sheet, coordinates) = self.adjust_range(&opts.pivot_data_range)?;
798 let order = self.get_table_fields_order(opts)?;
799 let top_left_cell = coordinates_to_cell_name(coordinates[0], coordinates[1], false)?;
800 let bottom_right_cell = coordinates_to_cell_name(coordinates[2], coordinates[3], false)?;
801 let mut pc = XlsxPivotCacheDefinition {
802 xmlns: Some(NAMESPACE_SPREADSHEET.to_string()),
803 save_data: false,
804 refresh_on_load: Some(true),
805 created_version: Some(PIVOT_TABLE_VERSION),
806 refreshed_version: Some(PIVOT_TABLE_REFRESHED_VERSION),
807 min_refreshable_version: Some(PIVOT_TABLE_VERSION),
808 cache_source: Some(XlsxCacheSource {
809 r#type: "worksheet".to_string(),
810 worksheet_source: Some(XlsxWorksheetSource {
811 r#ref: Some(format!("{top_left_cell}:{bottom_right_cell}")),
812 sheet: Some(data_sheet),
813 ..Default::default()
814 }),
815 ..Default::default()
816 }),
817 cache_fields: Some(XlsxCacheFields::default()),
818 ..Default::default()
819 };
820 if opts.named_data_range {
821 if let Some(source) = pc.cache_source.as_mut() {
822 source.worksheet_source = Some(XlsxWorksheetSource {
823 name: Some(opts.data_range.clone()),
824 ..Default::default()
825 });
826 }
827 }
828 self.add_pivot_shared_items(opts, &coordinates, "filters")?;
829 self.add_pivot_shared_items(opts, &coordinates, "cols")?;
830 self.add_pivot_shared_items(opts, &coordinates, "rows")?;
831
832 let cache_fields = pc.cache_fields.as_mut().unwrap();
833 for name in &order {
834 let si = opts
835 .shared_items
836 .get(name)
837 .cloned()
838 .unwrap_or_else(|| XlsxSharedItems {
839 contains_blank: Some(true),
840 items: vec![XlsxSharedItem::M(XlsxSharedItemData {
841 xmlns: Some("".to_string()),
842 ..Default::default()
843 })],
844 ..Default::default()
845 });
846 cache_fields.cache_field.push(XlsxCacheField {
847 name: name.clone(),
848 shared_items: Some(si),
849 ..Default::default()
850 });
851 }
852 cache_fields.count = cache_fields.cache_field.len() as i32;
853 let mut pivot_cache = xml_to_string(&pc)?.into_bytes();
854 crate::file::strip_empty_attributes(&mut pivot_cache);
855 self.save_file_list(&opts.pivot_cache_xml, &pivot_cache);
856 Ok(())
857 }
858
859 fn add_pivot_table_internal(
860 &self,
861 cache_id: i64,
862 pivot_table_id: i32,
863 opts: &mut PivotTableOptions,
864 ) -> Result<()> {
865 let (_, coordinates) = self.adjust_range(&opts.pivot_table_range)?;
866 let top_left_cell = coordinates_to_cell_name(coordinates[0], coordinates[1], false)?;
867 let bottom_right_cell = coordinates_to_cell_name(coordinates[2], coordinates[3], false)?;
868
869 let pivot_table_style = if opts.pivot_table_style_name.is_empty() {
870 "PivotStyleLight16".to_string()
871 } else {
872 opts.pivot_table_style_name.clone()
873 };
874 let mut pt = XlsxPivotTableDefinition {
875 xmlns: Some(NAMESPACE_SPREADSHEET.to_string()),
876 name: opts.name.clone(),
877 cache_id,
878 row_grand_totals: Some(opts.row_grand_totals),
879 col_grand_totals: Some(opts.col_grand_totals),
880 updated_version: Some(PIVOT_TABLE_REFRESHED_VERSION as i64),
881 min_refreshable_version: Some(PIVOT_TABLE_VERSION as i64),
882 show_drill: Some(opts.show_drill),
883 use_auto_formatting: Some(opts.use_auto_formatting),
884 page_over_then_down: Some(opts.page_over_then_down),
885 merge_item: Some(opts.merge_item),
886 created_version: Some(PIVOT_TABLE_VERSION as i64),
887 compact_data: Some(opts.compact_data),
888 grid_drop_zones: Some(opts.classic_layout),
889 show_error: Some(opts.show_error),
890 field_print_titles: Some(opts.field_print_titles),
891 item_print_titles: Some(opts.item_print_titles),
892 data_caption: "Values".to_string(),
893 location: Some(XlsxLocation {
894 r#ref: format!("{top_left_cell}:{bottom_right_cell}"),
895 first_data_col: 1,
896 first_data_row: 1,
897 first_header_row: 1,
898 ..Default::default()
899 }),
900 pivot_fields: Some(XlsxPivotFields::default()),
901 row_items: Some(XlsxRowItems {
902 count: 1,
903 i: vec![XlsxI {
904 x: vec![XlsxX::default()],
905 }],
906 }),
907 col_items: Some(XlsxColItems {
908 count: 1,
909 i: vec![XlsxI::default()],
910 }),
911 pivot_table_style_info: Some(XlsxPivotTableStyleInfo {
912 name: pivot_table_style,
913 show_row_headers: opts.show_row_headers,
914 show_col_headers: opts.show_col_headers,
915 show_row_stripes: if opts.show_row_stripes {
916 Some(true)
917 } else {
918 None
919 },
920 show_col_stripes: if opts.show_col_stripes {
921 Some(true)
922 } else {
923 None
924 },
925 show_last_column: if opts.show_last_column {
926 Some(true)
927 } else {
928 None
929 },
930 }),
931 ..Default::default()
932 };
933 if pt.name.is_empty() {
934 pt.name = format!("PivotTable{pivot_table_id}");
935 }
936 opts.name = pt.name.clone();
937 if opts.classic_layout {
938 pt.compact = Some(false);
939 pt.compact_data = Some(false);
940 }
941
942 self.add_pivot_fields(&mut pt, opts)?;
943 if let Some(pivot_fields) = pt.pivot_fields.as_mut() {
944 pivot_fields.count = pivot_fields.pivot_field.len() as i64;
945 }
946 let _ = self.add_pivot_row_fields(&mut pt, opts);
947 let _ = self.add_pivot_col_fields(&mut pt, opts);
948 let _ = self.add_pivot_page_fields(&mut pt, opts);
949 self.add_pivot_data_fields(&mut pt, opts)?;
950
951 let mut data_field_ext_xmls = Vec::new();
954 if let Some(ref mut data_fields) = pt.data_fields {
955 for df in &mut data_fields.data_field {
956 if let Some(ext_lst) = df.ext_lst.take() {
957 data_field_ext_xmls.push(Some(serialize_data_field_ext_lst(&ext_lst)));
958 } else {
959 data_field_ext_xmls.push(None);
960 }
961 }
962 }
963
964 let mut pivot_table = xml_to_string(&pt)?.into_bytes();
965 crate::file::strip_empty_attributes(&mut pivot_table);
966 inject_data_field_ext_lst(&mut pivot_table, &data_field_ext_xmls);
967 self.save_file_list(&opts.pivot_table_xml, &pivot_table);
968 Ok(())
969 }
970
971 fn add_pivot_row_fields(
972 &self,
973 pt: &mut XlsxPivotTableDefinition,
974 opts: &PivotTableOptions,
975 ) -> Result<()> {
976 let row_fields_index =
977 self.get_pivot_fields_index(&opts.rows.iter().collect::<Vec<_>>(), opts)?;
978 for field_idx in row_fields_index {
979 if pt.row_fields.is_none() {
980 pt.row_fields = Some(XlsxRowFields::default());
981 }
982 pt.row_fields.as_mut().unwrap().field.push(XlsxField {
983 x: field_idx as i64,
984 });
985 }
986 if let Some(row_fields) = pt.row_fields.as_mut() {
987 row_fields.count = row_fields.field.len() as i64;
988 }
989 Ok(())
990 }
991
992 fn add_pivot_page_fields(
993 &self,
994 pt: &mut XlsxPivotTableDefinition,
995 opts: &PivotTableOptions,
996 ) -> Result<()> {
997 let page_fields_index =
998 self.get_pivot_fields_index(&opts.filter.iter().collect::<Vec<_>>(), opts)?;
999 let page_fields_name = self.get_pivot_table_fields_name(&opts.filter);
1000 for (idx, page_field) in page_fields_index.iter().enumerate() {
1001 if pt.page_fields.is_none() {
1002 pt.page_fields = Some(XlsxPageFields::default());
1003 }
1004 pt.page_fields
1005 .as_mut()
1006 .unwrap()
1007 .page_field
1008 .push(XlsxPageField {
1009 fld: *page_field as i64,
1010 name: Some(page_fields_name[idx].clone()),
1011 ..Default::default()
1012 });
1013 }
1014 if let Some(page_fields) = pt.page_fields.as_mut() {
1015 page_fields.count = page_fields.page_field.len() as i64;
1016 }
1017 Ok(())
1018 }
1019
1020 fn add_pivot_data_fields(
1021 &self,
1022 pt: &mut XlsxPivotTableDefinition,
1023 opts: &mut PivotTableOptions,
1024 ) -> Result<()> {
1025 let data_fields_index =
1026 self.get_pivot_fields_index(&opts.data.iter().collect::<Vec<_>>(), opts)?;
1027 let order = self.get_table_fields_order(opts)?;
1028 let data_fields_subtotals = self.get_pivot_table_fields_subtotal(&opts.data);
1029 let data_fields_name = self.get_pivot_table_fields_name(&opts.data);
1030 let data_fields_num_fmt_id = self.get_pivot_table_fields_num_fmt_id(&opts.data);
1031 for (idx, data_field) in data_fields_index.iter().enumerate() {
1032 if pt.data_fields.is_none() {
1033 pt.data_fields = Some(XlsxDataFields::default());
1034 }
1035 let mut df = XlsxDataField {
1036 name: Some(data_fields_name[idx].clone()),
1037 fld: *data_field as i64,
1038 subtotal: Some(data_fields_subtotals[idx].clone()),
1039 num_fmt_id: Some(data_fields_num_fmt_id[idx] as i64),
1040 ..Default::default()
1041 };
1042 self.set_pivot_table_show_values_as(&mut df, idx, &order, opts)?;
1043 pt.data_fields.as_mut().unwrap().data_field.push(df);
1044 }
1045 if let Some(data_fields) = pt.data_fields.as_mut() {
1046 data_fields.count = data_fields.data_field.len() as i64;
1047 }
1048 Ok(())
1049 }
1050
1051 fn set_pivot_table_show_values_as(
1052 &self,
1053 df: &mut XlsxDataField,
1054 idx: usize,
1055 order: &[String],
1056 opts: &PivotTableOptions,
1057 ) -> Result<()> {
1058 let show_values_as_type = opts.data[idx].show_values_as.r#type;
1059 if show_values_as_type == PivotTableShowValuesAsType::NO_CALCULATION {
1060 return Ok(());
1061 }
1062 let map = show_values_as_map();
1063 let Some(show_data_as) = map.get(&show_values_as_type).copied() else {
1064 return Err(Box::new(ErrUnsupportedPivotTableShowValuesAsType));
1065 };
1066 df.show_data_as = Some(show_data_as.to_string());
1067 if x14_show_values_as_types()
1068 .get(&show_values_as_type)
1069 .copied()
1070 .unwrap_or(false)
1071 {
1072 df.show_data_as = None;
1073 let x14_df = XlsxX14DataField {
1074 xmlns_x14: Some(NAMESPACE_SPREADSHEET_X14.to_string()),
1075 pivot_show_as: Some(show_data_as.to_string()),
1076 ..Default::default()
1077 };
1078 let data_field_bytes = xml_to_string(&x14_df)?;
1079 let ext = XlsxExt {
1080 uri: Some(EXT_URI_PIVOT_DATA_FIELD.to_string()),
1081 content: data_field_bytes,
1082 ..Default::default()
1083 };
1084 df.ext_lst = Some(XlsxExtLst { ext: vec![ext] });
1085 }
1086 if base_field_required()
1087 .get(&show_values_as_type)
1088 .copied()
1089 .unwrap_or(false)
1090 {
1091 let base_field = &opts.data[idx].show_values_as.base_field;
1092 if base_field.is_empty() {
1093 return Err(ErrPivotTableShowValuesAsBaseField.into());
1094 }
1095 let Some(shared_items) = opts.shared_items.get(base_field) else {
1096 return Err(new_pivot_table_show_values_as_base_field_error(base_field).into());
1097 };
1098 let base_field_index = in_str_slice(order, base_field, true);
1099 df.base_field = Some(base_field_index as i64);
1100 if base_item_required()
1101 .get(&show_values_as_type)
1102 .copied()
1103 .unwrap_or(false)
1104 {
1105 self.set_pivot_table_show_values_as_base_item(
1106 df,
1107 base_field,
1108 &opts.data[idx].show_values_as.base_item,
1109 shared_items,
1110 )?;
1111 }
1112 }
1113 Ok(())
1114 }
1115
1116 fn set_pivot_table_show_values_as_base_item(
1117 &self,
1118 df: &mut XlsxDataField,
1119 base_field: &str,
1120 base_item: &str,
1121 shared_items: &XlsxSharedItems,
1122 ) -> Result<()> {
1123 if base_item.is_empty() {
1124 return Err(Box::new(ErrPivotTableShowValuesAsBaseItem));
1125 }
1126 Self::check_selected_items(shared_items, base_field, &[base_item.to_string()])?;
1127 for (i, item) in shared_items.items.iter().enumerate() {
1128 match item {
1129 XlsxSharedItem::B(data) => {
1130 if data
1131 .v
1132 .as_deref()
1133 .map(|v| v.eq_ignore_ascii_case(base_item))
1134 .unwrap_or(false)
1135 {
1136 df.base_item = Some(i as i64);
1137 }
1138 }
1139 XlsxSharedItem::N(data)
1140 | XlsxSharedItem::E(data)
1141 | XlsxSharedItem::S(data)
1142 | XlsxSharedItem::D(data) => {
1143 if data.v.as_deref() == Some(base_item) {
1144 df.base_item = Some(i as i64);
1145 }
1146 }
1147 _ => {}
1148 }
1149 }
1150 Ok(())
1151 }
1152
1153 fn add_pivot_col_fields(
1154 &self,
1155 pt: &mut XlsxPivotTableDefinition,
1156 opts: &PivotTableOptions,
1157 ) -> Result<()> {
1158 if opts.columns.is_empty() {
1159 if opts.data.len() <= 1 {
1160 return Ok(());
1161 }
1162 pt.col_fields = Some(XlsxColFields {
1163 count: 1,
1164 field: vec![XlsxField { x: -2 }],
1165 });
1166 return Ok(());
1167 }
1168 pt.col_fields = Some(XlsxColFields::default());
1169 let col_fields_index =
1170 self.get_pivot_fields_index(&opts.columns.iter().collect::<Vec<_>>(), opts)?;
1171 for field_idx in col_fields_index {
1172 pt.col_fields.as_mut().unwrap().field.push(XlsxField {
1173 x: field_idx as i64,
1174 });
1175 }
1176 if opts.data.len() > 1 {
1177 pt.col_fields
1178 .as_mut()
1179 .unwrap()
1180 .field
1181 .push(XlsxField { x: -2 });
1182 }
1183 pt.col_fields.as_mut().unwrap().count = pt.col_fields.as_ref().unwrap().field.len() as i64;
1184 Ok(())
1185 }
1186
1187 fn set_classic_layout(fld: &mut XlsxPivotField, classic_layout: bool) {
1188 if classic_layout {
1189 fld.compact = Some(false);
1190 fld.outline = Some(false);
1191 }
1192 }
1193
1194 fn add_pivot_fields(
1195 &self,
1196 pt: &mut XlsxPivotTableDefinition,
1197 opts: &mut PivotTableOptions,
1198 ) -> Result<()> {
1199 let order = self.get_table_fields_order(opts)?;
1200 let x = 0i64;
1201 for name in &order {
1202 if in_pivot_table_field(&opts.rows, name) != -1 {
1203 let (row_options, ok) = self.get_pivot_table_field_options(name, &opts.rows);
1204 let mut items = opts.items.get(name).cloned().unwrap_or_default();
1205 if ok && row_options.default_subtotal {
1206 items.push(XlsxItem {
1207 t: Some("default".to_string()),
1208 ..Default::default()
1209 });
1210 }
1211 if items.is_empty() {
1212 items.push(XlsxItem {
1213 x: Some(x),
1214 ..Default::default()
1215 });
1216 }
1217 let mut fld = XlsxPivotField {
1218 name: Some(self.get_pivot_table_field_name(name, &opts.rows)),
1219 axis: Some("axisRow".to_string()),
1220 data_field: Some(in_pivot_table_field(&opts.data, name) != -1),
1221 compact: Some(row_options.compact),
1222 outline: Some(row_options.outline),
1223 multiple_item_selection_allowed: Some(
1224 !opts.items.get(name).map(|v| v.is_empty()).unwrap_or(true),
1225 ),
1226 show_all: Some(row_options.show_all),
1227 insert_blank_row: Some(row_options.insert_blank_row),
1228 default_subtotal: Some(row_options.default_subtotal),
1229 items: Some(XlsxItems {
1230 count: items.len() as i64,
1231 item: items,
1232 }),
1233 ..Default::default()
1234 };
1235 Self::set_classic_layout(&mut fld, opts.classic_layout);
1236 pt.pivot_fields.as_mut().unwrap().pivot_field.push(fld);
1237 continue;
1238 }
1239 if in_pivot_table_field(&opts.filter, name) != -1 {
1240 let mut items = opts.items.get(name).cloned().unwrap_or_default();
1241 items.push(XlsxItem {
1242 t: Some("default".to_string()),
1243 ..Default::default()
1244 });
1245 let mut fld = XlsxPivotField {
1246 axis: Some("axisPage".to_string()),
1247 data_field: Some(in_pivot_table_field(&opts.data, name) != -1),
1248 multiple_item_selection_allowed: Some(
1249 !opts.items.get(name).map(|v| v.is_empty()).unwrap_or(true),
1250 ),
1251 name: Some(self.get_pivot_table_field_name(name, &opts.columns)),
1252 items: Some(XlsxItems {
1253 count: items.len() as i64,
1254 item: items,
1255 }),
1256 ..Default::default()
1257 };
1258 Self::set_classic_layout(&mut fld, opts.classic_layout);
1259 pt.pivot_fields.as_mut().unwrap().pivot_field.push(fld);
1260 continue;
1261 }
1262 if in_pivot_table_field(&opts.columns, name) != -1 {
1263 let (column_options, ok) = self.get_pivot_table_field_options(name, &opts.columns);
1264 let mut items = opts.items.get(name).cloned().unwrap_or_default();
1265 if ok && column_options.default_subtotal {
1266 items.push(XlsxItem {
1267 t: Some("default".to_string()),
1268 ..Default::default()
1269 });
1270 }
1271 if items.is_empty() {
1272 items.push(XlsxItem {
1273 x: Some(x),
1274 ..Default::default()
1275 });
1276 }
1277 let mut fld = XlsxPivotField {
1278 name: Some(self.get_pivot_table_field_name(name, &opts.columns)),
1279 axis: Some("axisCol".to_string()),
1280 data_field: Some(in_pivot_table_field(&opts.data, name) != -1),
1281 compact: Some(column_options.compact),
1282 outline: Some(column_options.outline),
1283 multiple_item_selection_allowed: Some(
1284 !opts.items.get(name).map(|v| v.is_empty()).unwrap_or(true),
1285 ),
1286 show_all: Some(column_options.show_all),
1287 insert_blank_row: Some(column_options.insert_blank_row),
1288 default_subtotal: Some(column_options.default_subtotal),
1289 items: Some(XlsxItems {
1290 count: items.len() as i64,
1291 item: items,
1292 }),
1293 ..Default::default()
1294 };
1295 Self::set_classic_layout(&mut fld, opts.classic_layout);
1296 pt.pivot_fields.as_mut().unwrap().pivot_field.push(fld);
1297 continue;
1298 }
1299 if in_pivot_table_field(&opts.data, name) != -1 {
1300 let mut fld = XlsxPivotField {
1301 data_field: Some(true),
1302 ..Default::default()
1303 };
1304 Self::set_classic_layout(&mut fld, opts.classic_layout);
1305 pt.pivot_fields.as_mut().unwrap().pivot_field.push(fld);
1306 continue;
1307 }
1308 let mut fld = XlsxPivotField::default();
1309 Self::set_classic_layout(&mut fld, opts.classic_layout);
1310 pt.pivot_fields.as_mut().unwrap().pivot_field.push(fld);
1311 }
1312 Ok(())
1313 }
1314
1315 fn get_pivot_fields_index(
1316 &self,
1317 fields: &[&PivotTableField],
1318 opts: &PivotTableOptions,
1319 ) -> Result<Vec<i32>> {
1320 let mut pivot_fields_index = Vec::new();
1321 let orders = self.get_table_fields_order(opts)?;
1322 for field in fields {
1323 let pos = in_str_slice(&orders, &field.data, true);
1324 if pos != -1 {
1325 pivot_fields_index.push(pos);
1326 }
1327 }
1328 Ok(pivot_fields_index)
1329 }
1330
1331 fn get_pivot_table_fields_subtotal(&self, fields: &[PivotTableField]) -> Vec<String> {
1332 let enums = [
1333 "average",
1334 "count",
1335 "countNums",
1336 "max",
1337 "min",
1338 "product",
1339 "stdDev",
1340 "stdDevp",
1341 "sum",
1342 "var",
1343 "varp",
1344 ];
1345 let mut result = Vec::with_capacity(fields.len());
1346 for fld in fields {
1347 let mut val = "sum".to_string();
1348 for e in &enums {
1349 if e.eq_ignore_ascii_case(&fld.subtotal) {
1350 val = (*e).to_string();
1351 break;
1352 }
1353 }
1354 result.push(val);
1355 }
1356 result
1357 }
1358
1359 fn get_pivot_table_fields_name(&self, fields: &[PivotTableField]) -> Vec<String> {
1360 let mut result = Vec::with_capacity(fields.len());
1361 for fld in fields {
1362 if count_utf16_string(&fld.name) > MAX_FIELD_LENGTH {
1363 result.push(truncate_utf16_units(&fld.name, MAX_FIELD_LENGTH));
1364 } else {
1365 result.push(fld.name.clone());
1366 }
1367 }
1368 result
1369 }
1370
1371 fn get_pivot_table_field_name(&self, name: &str, fields: &[PivotTableField]) -> String {
1372 let fields_name = self.get_pivot_table_fields_name(fields);
1373 for (idx, field) in fields.iter().enumerate() {
1374 if field.data == name {
1375 return fields_name[idx].clone();
1376 }
1377 }
1378 String::new()
1379 }
1380
1381 fn get_pivot_table_fields_num_fmt_id(&self, fields: &[PivotTableField]) -> Vec<i32> {
1382 let mut result = Vec::with_capacity(fields.len());
1383 for fld in fields {
1384 if built_in_num_fmt_code(fld.num_fmt).is_some() {
1385 result.push(fld.num_fmt);
1386 continue;
1387 }
1388 if (27..=36).contains(&fld.num_fmt) || (50..=81).contains(&fld.num_fmt) {
1389 result.push(fld.num_fmt);
1390 continue;
1391 }
1392 result.push(0);
1393 }
1394 result
1395 }
1396
1397 fn get_pivot_table_field_options(
1398 &self,
1399 name: &str,
1400 fields: &[PivotTableField],
1401 ) -> (PivotTableField, bool) {
1402 for field in fields {
1403 if field.data == name {
1404 return (field.clone(), true);
1405 }
1406 }
1407 (PivotTableField::default(), false)
1408 }
1409
1410 fn add_workbook_pivot_cache(&self, rid: i32) -> i64 {
1411 let mut wb = self.workbook_reader().unwrap_or_default();
1412 if wb.pivot_caches.is_none() {
1413 wb.pivot_caches = Some(XlsxPivotCaches::default());
1414 }
1415 let mut cache_id = 1i64;
1416 if let Some(ref caches) = wb.pivot_caches {
1417 for pivot_cache in &caches.pivot_cache {
1418 if pivot_cache.cache_id > cache_id {
1419 cache_id = pivot_cache.cache_id;
1420 }
1421 }
1422 }
1423 cache_id += 1;
1424 wb.pivot_caches
1425 .as_mut()
1426 .unwrap()
1427 .pivot_cache
1428 .push(XlsxPivotCache {
1429 cache_id,
1430 rid: Some(format!("rId{rid}")),
1431 });
1432 *self.workbook.lock().unwrap() = Some(wb);
1433 cache_id
1434 }
1435
1436 fn get_pivot_table_data_range(&self, opts: &mut PivotTableOptions) -> Result<()> {
1441 if opts.data_range.is_empty() {
1442 return Err(new_pivot_table_data_range_error(&ErrParameterRequired.to_string()).into());
1443 }
1444 if !opts.pivot_data_range.is_empty() {
1445 return Ok(());
1446 }
1447 if opts.data_range.contains('!') {
1448 opts.pivot_data_range = opts.data_range.clone();
1449 return Ok(());
1450 }
1451 let tables = self.get_tables_for_workbook()?;
1452 for (sheet_name, sheet_tables) in tables {
1453 for table in sheet_tables {
1454 if table.name == opts.data_range {
1455 opts.pivot_data_range = format!("{}!{}", sheet_name, table.range);
1456 opts.named_data_range = true;
1457 return Ok(());
1458 }
1459 }
1460 }
1461 if !opts.named_data_range {
1462 let refers_to = self.get_defined_name_ref_to(&opts.data_range, &opts.pivot_sheet_name);
1463 if !refers_to.is_empty() {
1464 opts.pivot_data_range = refers_to;
1465 opts.named_data_range = true;
1466 return Ok(());
1467 }
1468 }
1469 Err(new_pivot_table_data_range_error(&ErrParameterInvalid.to_string()).into())
1470 }
1471
1472 fn get_pivot_table(
1473 &self,
1474 sheet: &str,
1475 pivot_table_xml: &str,
1476 pivot_cache_rels: &str,
1477 ) -> Result<PivotTableOptions> {
1478 let rels = match self.rels_reader(pivot_cache_rels)? {
1479 Some(r) => r,
1480 None => crate::xml::workbook::XlsxRelationships::default(),
1481 };
1482 let mut pivot_cache_xml = String::new();
1483 for v in &rels.relationships {
1484 if v.r#type == SOURCE_RELATIONSHIP_PIVOT_CACHE {
1485 pivot_cache_xml = v.target.replace("..", "xl");
1486 break;
1487 }
1488 }
1489 let pc = self.pivot_cache_reader(&pivot_cache_xml)?;
1490 let pt = self.pivot_table_reader(pivot_table_xml)?;
1491 let Some(ref cache_source) = pc.cache_source else {
1492 return Err(new_unsupported_pivot_cache_source_type_error("").into());
1493 };
1494 let Some(ref worksheet_source) = cache_source.worksheet_source else {
1495 return Err(new_unsupported_pivot_cache_source_type_error(&cache_source.r#type).into());
1496 };
1497 let data_range = format!(
1498 "{}!{}",
1499 worksheet_source.sheet.as_deref().unwrap_or(""),
1500 worksheet_source.r#ref.as_deref().unwrap_or("")
1501 );
1502 let location_ref = pt
1503 .location
1504 .as_ref()
1505 .map(|l| l.r#ref.clone())
1506 .unwrap_or_default();
1507 let mut opts = PivotTableOptions {
1508 pivot_table_xml: pivot_table_xml.to_string(),
1509 pivot_cache_xml,
1510 pivot_sheet_name: sheet.to_string(),
1511 data_range,
1512 pivot_table_range: format!("{}!{}", sheet, location_ref),
1513 name: pt.name.clone(),
1514 classic_layout: pt.grid_drop_zones.unwrap_or(false),
1515 field_print_titles: pt.field_print_titles.unwrap_or(false),
1516 item_print_titles: pt.item_print_titles.unwrap_or(false),
1517 ..Default::default()
1518 };
1519 if let Some(ref name) = worksheet_source.name {
1520 opts.data_range = name.clone();
1521 let _ = self.get_pivot_table_data_range(&mut opts);
1522 }
1523 opts.row_grand_totals = pt.row_grand_totals.unwrap_or(false);
1524 opts.col_grand_totals = pt.col_grand_totals.unwrap_or(false);
1525 opts.show_drill = pt.show_drill.unwrap_or(false);
1526 opts.use_auto_formatting = pt.use_auto_formatting.unwrap_or(false);
1527 opts.page_over_then_down = pt.page_over_then_down.unwrap_or(false);
1528 opts.merge_item = pt.merge_item.unwrap_or(false);
1529 opts.compact_data = pt.compact_data.unwrap_or(false);
1530 opts.show_error = pt.show_error.unwrap_or(false);
1531 if let Some(ref si) = pt.pivot_table_style_info {
1532 opts.show_row_headers = si.show_row_headers;
1533 opts.show_col_headers = si.show_col_headers;
1534 opts.show_row_stripes = si.show_row_stripes.unwrap_or(false);
1535 opts.show_col_stripes = si.show_col_stripes.unwrap_or(false);
1536 opts.show_last_column = si.show_last_column.unwrap_or(false);
1537 opts.pivot_table_style_name = si.name.clone();
1538 }
1539 let _ = self.get_pivot_table_data_range(&mut opts);
1540 self.extract_pivot_table_fields(&pt, &pc, &mut opts);
1541 Ok(opts)
1542 }
1543
1544 fn pivot_table_reader(&self, path: &str) -> Result<XlsxPivotTableDefinition> {
1545 let content = namespace_strict_to_transitional(&self.read_xml(path));
1546 let mut pivot_table = XlsxPivotTableDefinition::default();
1547 if !content.is_empty() {
1548 let content_str = String::from_utf8_lossy(&content);
1549 let (content_without_ext, ext_xmls) = extract_data_field_ext_lst(&content_str);
1550 pivot_table = xml_from_reader(content_without_ext.as_bytes())?;
1551 if let Some(ref mut data_fields) = pivot_table.data_fields {
1552 for (i, df) in data_fields.data_field.iter_mut().enumerate() {
1553 if let Some(inner) = ext_xmls.get(i).and_then(|o| o.as_ref()) {
1554 df.ext_lst = Some(crate::xml::common::parse_ext_lst_content(inner)?);
1555 }
1556 }
1557 }
1558 }
1559 Ok(pivot_table)
1560 }
1561
1562 fn pivot_cache_reader(&self, path: &str) -> Result<XlsxPivotCacheDefinition> {
1563 let content = namespace_strict_to_transitional(&self.read_xml(path));
1564 let mut pivot_cache = XlsxPivotCacheDefinition::default();
1565 if !content.is_empty() {
1566 pivot_cache = xml_from_reader(content.as_slice())?;
1567 }
1568 Ok(pivot_cache)
1569 }
1570
1571 fn extract_pivot_table_fields(
1572 &self,
1573 pt: &XlsxPivotTableDefinition,
1574 pc: &XlsxPivotCacheDefinition,
1575 opts: &mut PivotTableOptions,
1576 ) {
1577 let order = pc.get_pivot_cache_fields_name();
1578 if let Some(ref pivot_fields) = pt.pivot_fields {
1579 for (field_idx, field) in pivot_fields.pivot_field.iter().enumerate() {
1580 let name = order.get(field_idx).cloned().unwrap_or_default();
1581 match field.axis.as_deref() {
1582 Some("axisRow") => opts.rows.push(pc.extract_pivot_table_field(&name, field)),
1583 Some("axisCol") => opts
1584 .columns
1585 .push(pc.extract_pivot_table_field(&name, field)),
1586 Some("axisPage") => {
1587 opts.filter.push(pc.extract_pivot_table_field(&name, field))
1588 }
1589 _ => {}
1590 }
1591 }
1592 }
1593 if let Some(ref data_fields) = pt.data_fields {
1594 for field in &data_fields.data_field {
1595 let mut data_field = PivotTableField {
1596 data: order.get(field.fld as usize).cloned().unwrap_or_default(),
1597 name: field.name.clone().unwrap_or_default(),
1598 subtotal: title_case(field.subtotal.as_deref().unwrap_or("sum")),
1599 num_fmt: field.num_fmt_id.unwrap_or(0) as i32,
1600 ..Default::default()
1601 };
1602 if field.show_data_as.is_some() || field.ext_lst.is_some() {
1603 self.extract_pivot_table_show_values_as(pc, field, &mut data_field);
1604 }
1605 opts.data.push(data_field);
1606 }
1607 }
1608 }
1609
1610 fn extract_pivot_table_show_values_as(
1611 &self,
1612 pc: &XlsxPivotCacheDefinition,
1613 df: &XlsxDataField,
1614 data_field: &mut PivotTableField,
1615 ) {
1616 let order = pc.get_pivot_cache_fields_name();
1617 let mut show_data_as = df.show_data_as.clone().unwrap_or_default();
1618 if let Some(ref ext_lst) = df.ext_lst {
1619 for ext in &ext_lst.ext {
1620 if ext.uri.as_deref() == Some(EXT_URI_PIVOT_DATA_FIELD) {
1621 if let Ok(parsed) =
1622 xml_from_reader::<_, XlsxX14DataField>(ext.content.as_bytes())
1623 {
1624 if let Some(ref psa) = parsed.pivot_show_as {
1625 show_data_as = psa.clone();
1626 }
1627 }
1628 }
1629 }
1630 }
1631 for (k, v) in show_values_as_map() {
1632 if v == show_data_as {
1633 data_field.show_values_as.r#type = k;
1634 break;
1635 }
1636 }
1637 if let Some(base_field_idx) = df.base_field {
1638 if base_field_idx < order.len() as i64 {
1639 data_field.show_values_as.base_field = order[base_field_idx as usize].clone();
1640 }
1641 }
1642 if df.base_item.is_none() {
1643 return;
1644 }
1645 let base_item_idx = df.base_item.unwrap() as usize;
1646 for cache_field in pc
1647 .cache_fields
1648 .as_ref()
1649 .map(|f| &f.cache_field)
1650 .into_iter()
1651 .flatten()
1652 {
1653 if cache_field.name == data_field.show_values_as.base_field {
1654 if let Some(ref shared_items) = cache_field.shared_items {
1655 if base_item_idx < shared_items.items.len() {
1656 data_field.show_values_as.base_item =
1657 shared_items.items[base_item_idx].v().unwrap_or_default();
1658 }
1659 }
1660 }
1661 }
1662 }
1663
1664 fn gen_pivot_cache_definition_id(&self) -> i32 {
1665 let mut id = 0i32;
1666 for entry in self.pkg.iter() {
1667 let k = entry.key();
1668 if k.contains("xl/pivotCache/pivotCacheDefinition") {
1669 if let Ok(pc) = self.pivot_cache_reader(k) {
1670 if let Some(ref ext_lst) = pc.ext_lst {
1671 for ext in &ext_lst.ext {
1672 if ext.uri.as_deref()
1673 == Some(crate::constants::EXT_URI_PIVOT_CACHE_DEFINITION)
1674 {
1675 if let Ok(parsed) =
1676 xml_from_reader::<_, DecodeX14PivotCacheDefinition>(
1677 ext.content.as_bytes(),
1678 )
1679 {
1680 if parsed.pivot_cache_id > id {
1681 id = parsed.pivot_cache_id;
1682 }
1683 }
1684 }
1685 }
1686 }
1687 }
1688 }
1689 }
1690 id + 1
1691 }
1692
1693 fn delete_workbook_pivot_cache(&self, opt: &PivotTableOptions) -> Result<()> {
1694 let target = opt
1695 .pivot_cache_xml
1696 .trim_start_matches('/')
1697 .trim_start_matches("xl/");
1698 let r_id = self.delete_workbook_rels(SOURCE_RELATIONSHIP_PIVOT_CACHE, target)?;
1699 let mut wb = self.workbook_reader()?;
1700 if let Some(ref mut caches) = wb.pivot_caches {
1701 caches
1702 .pivot_cache
1703 .retain(|c| c.rid.as_deref() != Some(&r_id));
1704 if caches.pivot_cache.is_empty() {
1705 wb.pivot_caches = None;
1706 }
1707 }
1708 *self.workbook.lock().unwrap() = Some(wb);
1709 Ok(())
1710 }
1711}
1712
1713trait PivotCacheExt {
1718 fn get_pivot_cache_fields_name(&self) -> Vec<String>;
1719 fn extract_pivot_table_field(&self, data: &str, fld: &XlsxPivotField) -> PivotTableField;
1720}
1721
1722impl PivotCacheExt for XlsxPivotCacheDefinition {
1723 fn get_pivot_cache_fields_name(&self) -> Vec<String> {
1724 let mut order = Vec::new();
1725 if let Some(ref cache_fields) = self.cache_fields {
1726 for cf in &cache_fields.cache_field {
1727 order.push(cf.name.clone());
1728 }
1729 }
1730 order
1731 }
1732
1733 fn extract_pivot_table_field(&self, data: &str, fld: &XlsxPivotField) -> PivotTableField {
1734 let mut pivot_table_field = PivotTableField {
1735 data: data.to_string(),
1736 show_all: fld.show_all.unwrap_or(false),
1737 insert_blank_row: fld.insert_blank_row.unwrap_or(false),
1738 ..Default::default()
1739 };
1740 if let Some(ref items) = fld.items {
1741 for item in &items.item {
1742 if item.h.unwrap_or(false) || item.x.is_none() {
1743 continue;
1744 }
1745 let idx = item.x.unwrap() as usize;
1746 if let Some(ref cache_fields) = self.cache_fields {
1747 for field in &cache_fields.cache_field {
1748 if field.name == data {
1749 if let Some(ref shared_items) = field.shared_items {
1750 if !shared_items.items.is_empty() && idx < shared_items.items.len()
1751 {
1752 let value = shared_items.items[idx].v().unwrap_or_default();
1753 pivot_table_field.selected_items.push(value);
1754 }
1755 }
1756 }
1757 }
1758 }
1759 }
1760 }
1761 pivot_table_field.compact = fld.compact.unwrap_or(false);
1762 pivot_table_field.outline = fld.outline.unwrap_or(false);
1763 pivot_table_field.default_subtotal = fld.default_subtotal.unwrap_or(false);
1764 pivot_table_field
1765 }
1766}
1767
1768fn in_pivot_table_field(fields: &[PivotTableField], x: &str) -> i32 {
1773 for (idx, n) in fields.iter().enumerate() {
1774 if n.data == x {
1775 return idx as i32;
1776 }
1777 }
1778 -1
1779}
1780
1781fn title_case(s: &str) -> String {
1782 let mut chars = s.chars();
1783 match chars.next() {
1784 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
1785 None => String::new(),
1786 }
1787}
1788
1789fn serialize_data_field_ext_lst(ext_lst: &XlsxExtLst) -> String {
1792 format!(
1793 "<extLst>{}</extLst>",
1794 crate::xml::common::serialize_ext_lst(ext_lst)
1795 )
1796}
1797
1798fn inject_data_field_ext_lst(xml: &mut Vec<u8>, ext_xmls: &[Option<String>]) {
1801 let s = String::from_utf8_lossy(xml).to_string();
1802 let re = Regex::new(r#"<dataField\b[^>]*?/>"#).unwrap();
1803 let extra = ext_xmls
1804 .iter()
1805 .map(|o| o.as_ref().map_or(0, |x| x.len() + 20))
1806 .sum::<usize>();
1807 let mut out = String::with_capacity(s.len() + extra);
1808 let mut last = 0usize;
1809 let mut idx = 0usize;
1810 for m in re.find_iter(&s) {
1811 out.push_str(&s[last..m.start()]);
1812 let tag = m.as_str();
1813 if let Some(ext) = ext_xmls.get(idx).and_then(|o| o.as_ref()) {
1814 out.push_str(&tag[..tag.len() - 2]);
1815 out.push('>');
1816 out.push_str(ext);
1817 out.push_str("</dataField>");
1818 } else {
1819 out.push_str(tag);
1820 }
1821 last = m.end();
1822 idx += 1;
1823 }
1824 out.push_str(&s[last..]);
1825 *xml = out.into_bytes();
1826}
1827
1828fn extract_data_field_ext_lst(xml: &str) -> (String, Vec<Option<String>>) {
1832 let data_field_re = Regex::new(r#"(?s)<dataField\b([^>]*?)(?:/>|>(.*?)</dataField>)"#).unwrap();
1833 let ext_re = Regex::new(r#"(?s)<extLst>(.*?)</extLst>"#).unwrap();
1834 let mut out = String::with_capacity(xml.len());
1835 let mut ext_xmls = Vec::new();
1836 let mut last = 0usize;
1837 for cap in data_field_re.captures_iter(xml) {
1838 let m = cap.get(0).unwrap();
1839 out.push_str(&xml[last..m.start()]);
1840 let attrs = cap.get(1).unwrap().as_str();
1841 if let Some(content_match) = cap.get(2) {
1842 let content = content_match.as_str();
1843 let mut ext_opt = None;
1844 let content_without_ext = if let Some(ext_cap) = ext_re.captures(content) {
1845 ext_opt = Some(ext_cap.get(1).unwrap().as_str().to_string());
1846 let full_ext = ext_cap.get(0).unwrap();
1847 let mut c = String::with_capacity(content.len());
1848 c.push_str(&content[..full_ext.start()]);
1849 c.push_str(&content[full_ext.end()..]);
1850 c
1851 } else {
1852 content.to_string()
1853 };
1854 ext_xmls.push(ext_opt);
1855 out.push_str("<dataField");
1856 out.push_str(attrs);
1857 out.push('>');
1858 out.push_str(&content_without_ext);
1859 out.push_str("</dataField>");
1860 } else {
1861 ext_xmls.push(None);
1862 out.push_str(m.as_str());
1863 }
1864 last = m.end();
1865 }
1866 out.push_str(&xml[last..]);
1867 (out, ext_xmls)
1868}
1869
1870trait SharedItemValue {
1871 fn v(&self) -> Option<String>;
1872}
1873
1874impl SharedItemValue for XlsxSharedItem {
1875 fn v(&self) -> Option<String> {
1876 match self {
1877 XlsxSharedItem::M(d)
1878 | XlsxSharedItem::N(d)
1879 | XlsxSharedItem::B(d)
1880 | XlsxSharedItem::E(d)
1881 | XlsxSharedItem::S(d)
1882 | XlsxSharedItem::D(d) => d.v.clone(),
1883 }
1884 }
1885}
1886
1887#[cfg(test)]
1892mod tests {
1893 use super::*;
1894 use crate::options::Options;
1895 use crate::slicer::SlicerOptions;
1896 use crate::xml::table::Table;
1897 use crate::xml::workbook::DefinedName;
1898
1899 fn new_file() -> File {
1900 File::new_with_options(Options::default())
1901 }
1902
1903 fn create_sample_data(f: &File) {
1904 let headers = ["Month", "Year", "Type", "Revenue", "Region"];
1905 for (i, h) in headers.iter().enumerate() {
1906 f.set_cell_str("Sheet1", &format!("{}1", (b'A' + i as u8) as char), h)
1907 .unwrap();
1908 }
1909 let months = [
1910 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
1911 ];
1912 let years = [2017i32, 2018, 2019];
1913 let types = ["Meat", "Dairy", "Beverages", "Produce"];
1914 let revenue = [3217, 4512, 3891, 4738, 3054, 4265, 3643, 4901, 3378, 4126];
1915 let regions = ["East", "West", "North", "South"];
1916 for row in 2..32 {
1917 f.set_cell_str(
1918 "Sheet1",
1919 &format!("A{row}"),
1920 months[(row - 2) % months.len()],
1921 )
1922 .unwrap();
1923 f.set_cell_value("Sheet1", &format!("B{row}"), years[(row - 2) % years.len()])
1924 .unwrap();
1925 f.set_cell_str("Sheet1", &format!("C{row}"), types[(row - 2) % types.len()])
1926 .unwrap();
1927 f.set_cell_value(
1928 "Sheet1",
1929 &format!("D{row}"),
1930 revenue[(row - 2) % revenue.len()],
1931 )
1932 .unwrap();
1933 f.set_cell_str(
1934 "Sheet1",
1935 &format!("E{row}"),
1936 regions[(row - 2) % regions.len()],
1937 )
1938 .unwrap();
1939 }
1940 }
1941
1942 #[test]
1943 fn add_and_get_pivot_table() {
1944 let f = new_file();
1945 create_sample_data(&f);
1946 let mut expected = PivotTableOptions {
1947 data_range: "Sheet1!A1:E31".to_string(),
1948 pivot_table_range: "Sheet1!G4:M30".to_string(),
1949 rows: vec![
1950 PivotTableField {
1951 data: "Month".to_string(),
1952 show_all: true,
1953 default_subtotal: true,
1954 ..Default::default()
1955 },
1956 PivotTableField {
1957 data: "Year".to_string(),
1958 ..Default::default()
1959 },
1960 ],
1961 filter: vec![PivotTableField {
1962 data: "Region".to_string(),
1963 ..Default::default()
1964 }],
1965 columns: vec![PivotTableField {
1966 data: "Type".to_string(),
1967 show_all: true,
1968 insert_blank_row: true,
1969 default_subtotal: true,
1970 ..Default::default()
1971 }],
1972 data: vec![PivotTableField {
1973 data: "Revenue".to_string(),
1974 subtotal: "Sum".to_string(),
1975 name: "Summarize".to_string(),
1976 ..Default::default()
1977 }],
1978 row_grand_totals: true,
1979 col_grand_totals: true,
1980 show_drill: true,
1981 classic_layout: true,
1982 show_error: true,
1983 show_row_headers: true,
1984 show_col_headers: true,
1985 show_last_column: true,
1986 field_print_titles: true,
1987 item_print_titles: true,
1988 pivot_table_style_name: "PivotStyleLight16".to_string(),
1989 ..Default::default()
1990 };
1991 f.add_pivot_table(&mut expected).unwrap();
1992
1993 expected.rows[1].selected_items = vec!["".to_string()];
1996
1997 assert!(f.pkg.contains_key("xl/pivotTables/pivotTable1.xml"));
1998 assert!(
1999 f.pkg
2000 .contains_key("xl/pivotCache/pivotCacheDefinition1.xml")
2001 );
2002
2003 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2004 assert_eq!(pivot_tables.len(), 1);
2005 assert_eq!(pivot_tables[0], expected);
2006 }
2007
2008 #[test]
2009 fn pivot_table_show_values_as_round_trip() {
2010 let f = new_file();
2011 create_sample_data(&f);
2012 let mut opts = PivotTableOptions {
2013 data_range: "Sheet1!A1:E31".to_string(),
2014 pivot_table_range: "Sheet1!W2:AC28".to_string(),
2015 rows: vec![PivotTableField {
2016 data: "Month".to_string(),
2017 default_subtotal: true,
2018 ..Default::default()
2019 }],
2020 columns: vec![PivotTableField {
2021 data: "Region".to_string(),
2022 ..Default::default()
2023 }],
2024 data: vec![PivotTableField {
2025 data: "Revenue".to_string(),
2026 subtotal: "Count".to_string(),
2027 name: "Summarize by Count".to_string(),
2028 show_values_as: PivotTableShowValuesAs {
2029 r#type: PivotTableShowValuesAsType::PERCENT_OF,
2030 base_field: "Region".to_string(),
2031 base_item: "East".to_string(),
2032 },
2033 ..Default::default()
2034 }],
2035 row_grand_totals: true,
2036 col_grand_totals: true,
2037 show_drill: true,
2038 show_row_headers: true,
2039 show_col_headers: true,
2040 show_last_column: true,
2041 ..Default::default()
2042 };
2043 f.add_pivot_table(&mut opts).unwrap();
2044
2045 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2046 assert_eq!(pivot_tables.len(), 1);
2047 assert_eq!(pivot_tables[0].data.len(), 1);
2048 let data = &pivot_tables[0].data[0];
2049 assert_eq!(data.data, "Revenue");
2050 assert_eq!(data.subtotal, "Count");
2051 assert_eq!(
2052 data.show_values_as.r#type,
2053 PivotTableShowValuesAsType::PERCENT_OF
2054 );
2055 assert_eq!(data.show_values_as.base_field, "Region");
2056 assert_eq!(data.show_values_as.base_item, "East");
2057 }
2058
2059 #[test]
2060 fn delete_pivot_table() {
2061 let f = new_file();
2062 create_sample_data(&f);
2063 let mut opts = PivotTableOptions {
2064 data_range: "Sheet1!A1:E31".to_string(),
2065 pivot_table_range: "Sheet1!G4:M30".to_string(),
2066 rows: vec![PivotTableField {
2067 data: "Month".to_string(),
2068 default_subtotal: true,
2069 ..Default::default()
2070 }],
2071 data: vec![PivotTableField {
2072 data: "Revenue".to_string(),
2073 subtotal: "Sum".to_string(),
2074 ..Default::default()
2075 }],
2076 ..Default::default()
2077 };
2078 f.add_pivot_table(&mut opts).unwrap();
2079 assert_eq!(f.get_pivot_tables("Sheet1").unwrap().len(), 1);
2080 f.delete_pivot_table("Sheet1", "PivotTable1").unwrap();
2081 assert!(f.get_pivot_tables("Sheet1").unwrap().is_empty());
2082 }
2083
2084 #[test]
2085 fn pivot_table_errors() {
2086 let f = new_file();
2087 create_sample_data(&f);
2088
2089 let mut opts = PivotTableOptions {
2091 data_range: "Sheet1!A1:E31".to_string(),
2092 pivot_table_range: "Sheet1!G2:M34".to_string(),
2093 compact_data: true,
2094 classic_layout: true,
2095 ..Default::default()
2096 };
2097 let err = f.add_pivot_table(&mut opts).unwrap_err();
2098 assert!(err.downcast_ref::<ErrPivotTableClassicLayout>().is_some());
2099
2100 let mut opts = PivotTableOptions {
2102 data_range: "Sheet1!A1:A1".to_string(),
2103 pivot_table_range: "Sheet1!G2:M34".to_string(),
2104 rows: vec![PivotTableField {
2105 data: "Month".to_string(),
2106 ..Default::default()
2107 }],
2108 data: vec![PivotTableField {
2109 data: "Revenue".to_string(),
2110 ..Default::default()
2111 }],
2112 ..Default::default()
2113 };
2114 let err = f.add_pivot_table(&mut opts).unwrap_err();
2115 let msg = err.to_string();
2116 assert!(msg.contains("DataRange"));
2117
2118 let mut opts = PivotTableOptions {
2120 data_range: "Sheet1!A1:E31".to_string(),
2121 pivot_table_range: "Sheet1!G2:M34".to_string(),
2122 rows: vec![PivotTableField {
2123 data: "Month".to_string(),
2124 default_subtotal: true,
2125 ..Default::default()
2126 }],
2127 columns: vec![PivotTableField {
2128 data: "Type".to_string(),
2129 default_subtotal: true,
2130 ..Default::default()
2131 }],
2132 data: vec![PivotTableField {
2133 data: "Revenue".to_string(),
2134 ..Default::default()
2135 }],
2136 filter: vec![PivotTableField {
2137 data: "Month".to_string(),
2138 ..Default::default()
2139 }],
2140 ..Default::default()
2141 };
2142 let err = f.add_pivot_table(&mut opts).unwrap_err();
2143 let msg = err.to_string();
2144 assert!(msg.contains("row fields"));
2145
2146 let mut opts = PivotTableOptions {
2148 data_range: "Sheet1!A1:E31".to_string(),
2149 pivot_table_range: "Sheet1!G2:M34".to_string(),
2150 rows: vec![PivotTableField {
2151 data: "Year".to_string(),
2152 ..Default::default()
2153 }],
2154 columns: vec![PivotTableField {
2155 data: "Type".to_string(),
2156 ..Default::default()
2157 }],
2158 data: vec![PivotTableField {
2159 data: "Revenue".to_string(),
2160 ..Default::default()
2161 }],
2162 filter: vec![PivotTableField {
2163 data: "Month".to_string(),
2164 selected_items: vec!["x".to_string()],
2165 ..Default::default()
2166 }],
2167 ..Default::default()
2168 };
2169 let err = f.add_pivot_table(&mut opts).unwrap_err();
2170 let msg = err.to_string();
2171 assert!(msg.contains("selected item x"));
2172
2173 let mut opts = PivotTableOptions {
2175 data_range: "Sheet1!A1:E31".to_string(),
2176 pivot_table_range: "Sheet1!G2:M34".to_string(),
2177 rows: vec![PivotTableField {
2178 data: "Year".to_string(),
2179 ..Default::default()
2180 }],
2181 columns: vec![PivotTableField {
2182 data: "Type".to_string(),
2183 ..Default::default()
2184 }],
2185 data: vec![PivotTableField {
2186 data: "Revenue".to_string(),
2187 show_values_as: PivotTableShowValuesAs {
2188 r#type: PivotTableShowValuesAsType(15),
2189 ..Default::default()
2190 },
2191 ..Default::default()
2192 }],
2193 filter: vec![PivotTableField {
2194 data: "Month".to_string(),
2195 ..Default::default()
2196 }],
2197 ..Default::default()
2198 };
2199 let err = f.add_pivot_table(&mut opts).unwrap_err();
2200 assert!(
2201 err.downcast_ref::<ErrUnsupportedPivotTableShowValuesAsType>()
2202 .is_some()
2203 );
2204
2205 let mut opts = PivotTableOptions {
2207 data_range: "Sheet1!A1:E31".to_string(),
2208 pivot_table_range: "Sheet1!G2:M34".to_string(),
2209 rows: vec![PivotTableField {
2210 data: "Year".to_string(),
2211 ..Default::default()
2212 }],
2213 columns: vec![PivotTableField {
2214 data: "Type".to_string(),
2215 ..Default::default()
2216 }],
2217 data: vec![PivotTableField {
2218 data: "Revenue".to_string(),
2219 show_values_as: PivotTableShowValuesAs {
2220 r#type: PivotTableShowValuesAsType::RUNNING_TOTAL_IN,
2221 ..Default::default()
2222 },
2223 ..Default::default()
2224 }],
2225 filter: vec![PivotTableField {
2226 data: "Month".to_string(),
2227 ..Default::default()
2228 }],
2229 ..Default::default()
2230 };
2231 let err = f.add_pivot_table(&mut opts).unwrap_err();
2232 assert!(
2233 err.downcast_ref::<ErrPivotTableShowValuesAsBaseField>()
2234 .is_some()
2235 );
2236 }
2237
2238 #[test]
2239 fn pivot_table_comprehensive() {
2240 let f = new_file();
2241 create_sample_data(&f);
2242
2243 let mut expected = PivotTableOptions {
2244 data_range: "Sheet1!A1:E31".to_string(),
2245 pivot_table_range: "Sheet1!G4:M30".to_string(),
2246 rows: vec![
2247 PivotTableField {
2248 data: "Month".to_string(),
2249 show_all: true,
2250 default_subtotal: true,
2251 ..Default::default()
2252 },
2253 PivotTableField {
2254 data: "Year".to_string(),
2255 ..Default::default()
2256 },
2257 ],
2258 filter: vec![PivotTableField {
2259 data: "Region".to_string(),
2260 ..Default::default()
2261 }],
2262 columns: vec![PivotTableField {
2263 data: "Type".to_string(),
2264 show_all: true,
2265 insert_blank_row: true,
2266 default_subtotal: true,
2267 ..Default::default()
2268 }],
2269 data: vec![PivotTableField {
2270 data: "Revenue".to_string(),
2271 subtotal: "Sum".to_string(),
2272 name: "Summarize by Sum".to_string(),
2273 num_fmt: 38,
2274 ..Default::default()
2275 }],
2276 row_grand_totals: true,
2277 col_grand_totals: true,
2278 show_drill: true,
2279 classic_layout: true,
2280 show_error: true,
2281 show_row_headers: true,
2282 show_col_headers: true,
2283 show_last_column: true,
2284 field_print_titles: true,
2285 item_print_titles: true,
2286 pivot_table_style_name: "PivotStyleLight16".to_string(),
2287 ..Default::default()
2288 };
2289 f.add_pivot_table(&mut expected).unwrap();
2290
2291 expected.rows[1].selected_items = vec!["".to_string()];
2294
2295 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2296 assert_eq!(pivot_tables.len(), 1);
2297 assert_eq!(pivot_tables[0], expected);
2298
2299 let mut opts = PivotTableOptions {
2301 data_range: "Sheet1!A1:E31".to_string(),
2302 pivot_table_range: "Sheet1!U29:O2".to_string(),
2303 rows: vec![
2304 PivotTableField {
2305 data: "Month".to_string(),
2306 default_subtotal: true,
2307 ..Default::default()
2308 },
2309 PivotTableField {
2310 data: "Year".to_string(),
2311 ..Default::default()
2312 },
2313 ],
2314 columns: vec![PivotTableField {
2315 data: "Type".to_string(),
2316 default_subtotal: true,
2317 ..Default::default()
2318 }],
2319 data: vec![PivotTableField {
2320 data: "Revenue".to_string(),
2321 subtotal: "Average".to_string(),
2322 name: "Summarize by Average".to_string(),
2323 ..Default::default()
2324 }],
2325 row_grand_totals: true,
2326 col_grand_totals: true,
2327 show_drill: true,
2328 show_row_headers: true,
2329 show_col_headers: true,
2330 show_last_column: true,
2331 ..Default::default()
2332 };
2333 f.add_pivot_table(&mut opts).unwrap();
2334 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2335 assert_eq!(pivot_tables.len(), 2);
2336 assert_eq!(pivot_tables[1].pivot_table_style_name, "PivotStyleLight16");
2337
2338 let mut opts = PivotTableOptions {
2340 data_range: "Sheet1!A1:E31".to_string(),
2341 pivot_table_range: "Sheet1!W2:AC28".to_string(),
2342 rows: vec![PivotTableField {
2343 data: "Month".to_string(),
2344 default_subtotal: true,
2345 ..Default::default()
2346 }],
2347 columns: vec![PivotTableField {
2348 data: "Region".to_string(),
2349 ..Default::default()
2350 }],
2351 data: vec![PivotTableField {
2352 data: "Revenue".to_string(),
2353 subtotal: "Count".to_string(),
2354 name: "Summarize by Count".to_string(),
2355 show_values_as: PivotTableShowValuesAs {
2356 r#type: PivotTableShowValuesAsType::PERCENT_OF,
2357 base_field: "Region".to_string(),
2358 base_item: "East".to_string(),
2359 },
2360 ..Default::default()
2361 }],
2362 row_grand_totals: true,
2363 col_grand_totals: true,
2364 show_drill: true,
2365 show_row_headers: true,
2366 show_col_headers: true,
2367 show_last_column: true,
2368 ..Default::default()
2369 };
2370 f.add_pivot_table(&mut opts).unwrap();
2371
2372 let mut opts = PivotTableOptions {
2373 data_range: "Sheet1!A1:E31".to_string(),
2374 pivot_table_range: "Sheet1!G34:X49".to_string(),
2375 rows: vec![PivotTableField {
2376 data: "Month".to_string(),
2377 ..Default::default()
2378 }],
2379 columns: vec![
2380 PivotTableField {
2381 data: "Region".to_string(),
2382 default_subtotal: true,
2383 ..Default::default()
2384 },
2385 PivotTableField {
2386 data: "Year".to_string(),
2387 ..Default::default()
2388 },
2389 ],
2390 data: vec![PivotTableField {
2391 data: "Revenue".to_string(),
2392 subtotal: "CountNums".to_string(),
2393 name: "Summarize by CountNums".to_string(),
2394 show_values_as: PivotTableShowValuesAs {
2395 r#type: PivotTableShowValuesAsType::PERCENT_OF,
2396 base_field: "Month".to_string(),
2397 base_item: "Jan".to_string(),
2398 },
2399 ..Default::default()
2400 }],
2401 row_grand_totals: true,
2402 col_grand_totals: true,
2403 show_drill: true,
2404 show_row_headers: true,
2405 show_col_headers: true,
2406 show_last_column: true,
2407 ..Default::default()
2408 };
2409 f.add_pivot_table(&mut opts).unwrap();
2410
2411 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2412 assert_eq!(pivot_tables.len(), 4);
2413 assert_eq!(pivot_tables[3].data.len(), 1);
2414 assert_eq!(
2415 pivot_tables[3].data[0].show_values_as,
2416 PivotTableShowValuesAs {
2417 r#type: PivotTableShowValuesAsType::PERCENT_OF,
2418 base_field: "Month".to_string(),
2419 base_item: "Jan".to_string(),
2420 }
2421 );
2422
2423 let mut opts = PivotTableOptions {
2425 data_range: "Sheet1!A1:E31".to_string(),
2426 pivot_table_range: "Sheet1!AE2:AH28".to_string(),
2427 rows: vec![
2428 PivotTableField {
2429 data: "Month".to_string(),
2430 default_subtotal: true,
2431 ..Default::default()
2432 },
2433 PivotTableField {
2434 data: "Year".to_string(),
2435 ..Default::default()
2436 },
2437 ],
2438 data: vec![
2439 PivotTableField {
2440 data: "Revenue".to_string(),
2441 subtotal: "Max".to_string(),
2442 name: "Summarize by Max".to_string(),
2443 show_values_as: PivotTableShowValuesAs {
2444 r#type: PivotTableShowValuesAsType::PERCENT_RUNNING_TOTAL_IN,
2445 base_field: "Year".to_string(),
2446 ..Default::default()
2447 },
2448 ..Default::default()
2449 },
2450 PivotTableField {
2451 data: "Revenue".to_string(),
2452 subtotal: "Average".to_string(),
2453 name: "Average of Sales".to_string(),
2454 show_values_as: PivotTableShowValuesAs {
2455 r#type: PivotTableShowValuesAsType::RUNNING_TOTAL_IN,
2456 base_field: "Year".to_string(),
2457 ..Default::default()
2458 },
2459 ..Default::default()
2460 },
2461 ],
2462 row_grand_totals: true,
2463 col_grand_totals: true,
2464 show_drill: true,
2465 show_row_headers: true,
2466 show_col_headers: true,
2467 show_last_column: true,
2468 ..Default::default()
2469 };
2470 f.add_pivot_table(&mut opts).unwrap();
2471
2472 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2473 let x14_pt = pivot_tables
2474 .iter()
2475 .find(|pt| pt.name == "PivotTable5")
2476 .unwrap();
2477 assert_eq!(x14_pt.data.len(), 2);
2478 assert_eq!(
2479 x14_pt.data[0].show_values_as.r#type,
2480 PivotTableShowValuesAsType::PERCENT_RUNNING_TOTAL_IN
2481 );
2482 assert_eq!(
2483 x14_pt.data[1].show_values_as.r#type,
2484 PivotTableShowValuesAsType::RUNNING_TOTAL_IN
2485 );
2486
2487 let mut opts = PivotTableOptions {
2489 data_range: "Sheet1!A1:E31".to_string(),
2490 pivot_table_range: "Sheet1!AJ4:AK30".to_string(),
2491 rows: vec![
2492 PivotTableField {
2493 data: "Month".to_string(),
2494 default_subtotal: true,
2495 ..Default::default()
2496 },
2497 PivotTableField {
2498 data: "Year".to_string(),
2499 ..Default::default()
2500 },
2501 ],
2502 filter: vec![PivotTableField {
2503 data: "Region".to_string(),
2504 ..Default::default()
2505 }],
2506 columns: vec![],
2507 data: vec![PivotTableField {
2508 subtotal: "Sum".to_string(),
2509 name: "Summarize by Sum".to_string(),
2510 ..Default::default()
2511 }],
2512 row_grand_totals: true,
2513 col_grand_totals: true,
2514 show_drill: true,
2515 show_row_headers: true,
2516 show_col_headers: true,
2517 show_last_column: true,
2518 pivot_table_style_name: "PivotStyleLight19".to_string(),
2519 ..Default::default()
2520 };
2521 f.add_pivot_table(&mut opts).unwrap();
2522
2523 f.new_sheet("Sheet2").unwrap();
2525 let mut opts = PivotTableOptions {
2526 data_range: "Sheet1!A1:E31".to_string(),
2527 pivot_table_range: "Sheet2!A1:AV17".to_string(),
2528 rows: vec![PivotTableField {
2529 data: "Month".to_string(),
2530 ..Default::default()
2531 }],
2532 columns: vec![
2533 PivotTableField {
2534 data: "Region".to_string(),
2535 default_subtotal: true,
2536 ..Default::default()
2537 },
2538 PivotTableField {
2539 data: "Type".to_string(),
2540 default_subtotal: true,
2541 ..Default::default()
2542 },
2543 PivotTableField {
2544 data: "Year".to_string(),
2545 ..Default::default()
2546 },
2547 ],
2548 data: vec![PivotTableField {
2549 data: "Revenue".to_string(),
2550 subtotal: "Min".to_string(),
2551 name: "Summarize by Min".to_string(),
2552 num_fmt: 32,
2553 ..Default::default()
2554 }],
2555 row_grand_totals: true,
2556 col_grand_totals: true,
2557 show_drill: true,
2558 show_row_headers: true,
2559 show_col_headers: true,
2560 show_last_column: true,
2561 ..Default::default()
2562 };
2563 f.add_pivot_table(&mut opts).unwrap();
2564 let pivot_tables = f.get_pivot_tables("Sheet2").unwrap();
2565 assert_eq!(pivot_tables.len(), 1);
2566 assert_eq!(pivot_tables[0].data_range, "Sheet1!A1:E31");
2567
2568 let mut opts = PivotTableOptions {
2570 data_range: "Sheet1!A1:E31".to_string(),
2571 pivot_table_range: "Sheet1!AM4:AQ12".to_string(),
2572 rows: vec![PivotTableField {
2573 data: "Month".to_string(),
2574 selected_items: vec![
2575 "Jan".to_string(),
2576 "Feb".to_string(),
2577 "Mar".to_string(),
2578 "Apr".to_string(),
2579 "May".to_string(),
2580 "Jun".to_string(),
2581 "Jul".to_string(),
2582 "Aug".to_string(),
2583 "Sep".to_string(),
2584 "Oct".to_string(),
2585 "Nov".to_string(),
2586 ],
2587 ..Default::default()
2588 }],
2589 filter: vec![PivotTableField {
2590 data: "Year".to_string(),
2591 selected_items: vec!["2017".to_string(), "2018".to_string()],
2592 ..Default::default()
2593 }],
2594 columns: vec![PivotTableField {
2595 data: "Type".to_string(),
2596 selected_items: vec![
2597 "Meat".to_string(),
2598 "Dairy".to_string(),
2599 "Beverages".to_string(),
2600 ],
2601 ..Default::default()
2602 }],
2603 data: vec![PivotTableField {
2604 data: "Revenue".to_string(),
2605 subtotal: "Sum".to_string(),
2606 name: "Summarize by Sum".to_string(),
2607 ..Default::default()
2608 }],
2609 row_grand_totals: true,
2610 col_grand_totals: true,
2611 show_drill: true,
2612 show_error: true,
2613 show_row_headers: true,
2614 show_col_headers: true,
2615 show_last_column: true,
2616 field_print_titles: true,
2617 item_print_titles: true,
2618 pivot_table_style_name: "PivotStyleLight16".to_string(),
2619 ..Default::default()
2620 };
2621 f.add_pivot_table(&mut opts).unwrap();
2622 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2623 assert_eq!(pivot_tables.len(), 7);
2624
2625 f.delete_pivot_table("Sheet1", "PivotTable1").unwrap();
2626 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2627 assert_eq!(pivot_tables.len(), 6);
2628 }
2629
2630 #[test]
2631 fn pivot_table_data_range_with_table() {
2632 let f = new_file();
2633 let table = Table {
2634 name: "Table1".to_string(),
2635 range: "A1:D5".to_string(),
2636 ..Default::default()
2637 };
2638 f.add_table("Sheet1", Some(&table)).unwrap();
2639 for row in 2..6 {
2640 f.set_cell_value("Sheet1", &format!("A{row}"), 1).unwrap();
2641 f.set_cell_value("Sheet1", &format!("B{row}"), 2).unwrap();
2642 f.set_cell_value("Sheet1", &format!("C{row}"), 3).unwrap();
2643 f.set_cell_value("Sheet1", &format!("D{row}"), 4).unwrap();
2644 }
2645
2646 let mut opts = PivotTableOptions {
2647 data_range: "Table1".to_string(),
2648 pivot_table_range: "Sheet1!G2:K7".to_string(),
2649 rows: vec![PivotTableField {
2650 data: "Column1".to_string(),
2651 ..Default::default()
2652 }],
2653 columns: vec![PivotTableField {
2654 data: "Column2".to_string(),
2655 ..Default::default()
2656 }],
2657 row_grand_totals: true,
2658 col_grand_totals: true,
2659 show_drill: true,
2660 show_row_headers: true,
2661 show_col_headers: true,
2662 show_last_column: true,
2663 show_error: true,
2664 pivot_table_style_name: "PivotStyleLight16".to_string(),
2665 ..Default::default()
2666 };
2667 f.add_pivot_table(&mut opts).unwrap();
2668 f.delete_pivot_table("Sheet1", "PivotTable1").unwrap();
2669 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2670 assert!(pivot_tables.is_empty());
2671 }
2672
2673 #[test]
2674 fn pivot_table_data_range_with_defined_name() {
2675 let f = new_file();
2676 create_sample_data(&f);
2677 f.set_defined_name(&DefinedName {
2678 name: "dataRange".to_string(),
2679 refers_to: "Sheet1!A1:E31".to_string(),
2680 comment: "Pivot Table Data Range".to_string(),
2681 scope: "Sheet1".to_string(),
2682 })
2683 .unwrap();
2684
2685 let mut opts = PivotTableOptions {
2686 data_range: "dataRange".to_string(),
2687 pivot_table_range: "Sheet1!G2:M34".to_string(),
2688 rows: vec![PivotTableField {
2689 data: "Month".to_string(),
2690 default_subtotal: true,
2691 ..Default::default()
2692 }],
2693 data: vec![PivotTableField {
2694 data: "Revenue".to_string(),
2695 subtotal: "Sum".to_string(),
2696 ..Default::default()
2697 }],
2698 ..Default::default()
2699 };
2700 f.add_pivot_table(&mut opts).unwrap();
2701 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2702 assert_eq!(pivot_tables.len(), 1);
2703 assert_eq!(pivot_tables[0].data_range, "dataRange");
2704 }
2705
2706 #[test]
2707 fn pivot_table_shared_items_mixed_types() {
2708 let f = new_file();
2709 for (r, row) in [
2710 vec!["Type", "Value"],
2711 vec!["Blank"],
2712 vec!["Blank"],
2713 vec!["Integer", "100"],
2714 vec!["Integer", "100"],
2715 vec!["Float", "0.01"],
2716 vec!["Float", "0.01"],
2717 vec!["Boolean", "true"],
2718 vec!["Boolean", "true"],
2719 vec!["String", "text"],
2720 vec!["String", "text"],
2721 vec!["Error"],
2722 vec!["Error"],
2723 vec!["Formula1"],
2724 vec!["Formula1"],
2725 vec!["Formula2"],
2726 vec!["Formula2"],
2727 vec!["FormulaError"],
2728 vec!["FormulaError"],
2729 vec!["InlineString"],
2730 vec!["InlineString"],
2731 ]
2732 .iter()
2733 .enumerate()
2734 {
2735 for (c, val) in row.iter().enumerate() {
2736 f.set_cell_str(
2737 "Sheet1",
2738 &format!("{}{}", (b'A' + c as u8) as char, r + 1),
2739 val,
2740 )
2741 .unwrap();
2742 }
2743 }
2744 f.set_cell_formula("Sheet1", "B12", "1/0").unwrap();
2745 f.set_cell_formula("Sheet1", "B13", "1/0").unwrap();
2746 f.set_cell_formula("Sheet1", "B14", "1+1").unwrap();
2747 f.set_cell_formula("Sheet1", "B15", "1+1").unwrap();
2748 f.set_cell_formula("Sheet1", "B16", "_xlfn.TEXTAFTER(\"ab\", \"a\")")
2749 .unwrap();
2750 f.set_cell_formula("Sheet1", "B17", "_xlfn.TEXTAFTER(\"ab\", \"a\")")
2751 .unwrap();
2752
2753 let selected_items = vec![
2754 "".to_string(),
2755 "100".to_string(),
2756 "0.01".to_string(),
2757 "true".to_string(),
2758 "text".to_string(),
2759 "#DIV/0!".to_string(),
2760 "2".to_string(),
2761 ];
2762 let mut opts = PivotTableOptions {
2763 data_range: "Sheet1!A1:B21".to_string(),
2764 pivot_table_range: "Sheet1!D4:E12".to_string(),
2765 rows: vec![PivotTableField {
2766 data: "Type".to_string(),
2767 ..Default::default()
2768 }],
2769 data: vec![PivotTableField {
2770 data: "Type".to_string(),
2771 subtotal: "Count".to_string(),
2772 name: "Count of Type".to_string(),
2773 ..Default::default()
2774 }],
2775 filter: vec![PivotTableField {
2776 data: "Value".to_string(),
2777 selected_items: selected_items.clone(),
2778 ..Default::default()
2779 }],
2780 row_grand_totals: true,
2781 col_grand_totals: true,
2782 show_drill: true,
2783 show_row_headers: true,
2784 show_col_headers: true,
2785 show_last_column: true,
2786 show_error: true,
2787 field_print_titles: true,
2788 item_print_titles: true,
2789 pivot_table_style_name: "PivotStyleLight16".to_string(),
2790 ..Default::default()
2791 };
2792 f.add_pivot_table(&mut opts).unwrap();
2793 let pivot_tables = f.get_pivot_tables("Sheet1").unwrap();
2794 assert_eq!(pivot_tables.len(), 1);
2795 assert_eq!(pivot_tables[0].filter[0].selected_items, selected_items);
2796
2797 f.add_slicer(
2798 "Sheet1",
2799 &SlicerOptions {
2800 name: "Value".to_string(),
2801 cell: "G2".to_string(),
2802 table_sheet: "Sheet1".to_string(),
2803 table_name: "PivotTable1".to_string(),
2804 caption: "Value".to_string(),
2805 selected_items: vec!["true".to_string()],
2806 ..Default::default()
2807 },
2808 )
2809 .unwrap();
2810 }
2811}