Skip to main content

excelize_rs/
pivot_table.rs

1//! PivotTable support.
2//!
3//! Ported from Go `pivotTable.go`.
4
5use 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// ------------------------------------------------------------------
46// Public types
47// ------------------------------------------------------------------
48
49/// Pivot table show-values-as type.
50#[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/// Pivot table show-values-as settings.
74#[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/// Pivot table field options.
82#[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/// Pivot table creation options.
98#[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    // Internal state used while building the pivot table parts.
126    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
135// ------------------------------------------------------------------
136// Internal helpers
137// ------------------------------------------------------------------
138
139const 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
256// ------------------------------------------------------------------
257// File implementation
258// ------------------------------------------------------------------
259
260impl File {
261    /// Add a pivot table.
262    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    /// Get pivot tables on a worksheet.
306    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    /// Delete a pivot table.
340    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    // ------------------------------------------------------------------
388    // Build helpers
389    // ------------------------------------------------------------------
390
391    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        // Preserve data field extension lists as raw XML so that x14 elements
952        // are not escaped by serde.
953        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    // ------------------------------------------------------------------
1437    // Read helpers
1438    // ------------------------------------------------------------------
1439
1440    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
1713// ------------------------------------------------------------------
1714// Extension trait for XlsxPivotCacheDefinition
1715// ------------------------------------------------------------------
1716
1717trait 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
1768// ------------------------------------------------------------------
1769// Free functions
1770// ------------------------------------------------------------------
1771
1772fn 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
1789/// Serialize the extension list that belongs to a data field as raw XML that
1790/// can be injected into the serialized pivot table definition.
1791fn 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
1798/// Inject raw `<extLst>` blocks into self-closing `<dataField/>` tags.
1799/// `ext_xmls` must be in the same order as the `<dataField/>` tags in `xml`.
1800fn 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
1828/// Extract the raw inner XML of each `<extLst>` inside a `<dataField>` element
1829/// and remove the `<extLst>` block so that serde can deserialize the remainder.
1830/// The returned vector is in data field order.
1831fn 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// ------------------------------------------------------------------
1888// Tests
1889// ------------------------------------------------------------------
1890
1891#[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        // Fields without an explicit selection and without a default subtotal item
1994        // are read back with a placeholder selected item to match Go's behavior.
1995        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        // Classic layout and compact data conflict.
2090        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        // Invalid data range.
2101        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        // Same field in filter and rows.
2119        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        // Selected item does not exist.
2147        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        // Unsupported show value as type.
2174        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        // Missing base field for show value as.
2206        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        // Fields without an explicit selection and without a default subtotal item
2292        // are read back with a placeholder selected item to match Go's behavior.
2293        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        // Different coordinate order should be normalized.
2300        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        // Show values as with base field and base item.
2339        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        // x14 show values as types.
2424        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        // Empty subtotal field name and specified style.
2488        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        // Cross-worksheet data range.
2524        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        // Selected items in rows, columns and filters.
2569        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}