Skip to main content

excelize_rs/
slicer.rs

1//! Slicer support.
2//!
3//! Ported from Go `slicer.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;
9
10use crate::constants::{
11    CONTENT_TYPE_SLICER, CONTENT_TYPE_SLICER_CACHE, DEFAULT_SLICER_HEIGHT, DEFAULT_SLICER_WIDTH,
12    EMU, EXT_URI_PIVOT_CACHE_DEFINITION, EXT_URI_SLICER_CACHE_DEFINITION,
13    EXT_URI_SLICER_CACHES_X14, EXT_URI_SLICER_CACHES_X15, EXT_URI_SLICER_LIST_X14,
14    EXT_URI_SLICER_LIST_X15, NAMESPACE_DRAWING_ML_A14, NAMESPACE_DRAWING_ML_SLICER,
15    NAMESPACE_DRAWING_ML_SLICER_X15, NAMESPACE_SPREADSHEET, NAMESPACE_SPREADSHEET_X14,
16    NAMESPACE_SPREADSHEET_X15, NAMESPACE_SPREADSHEET_XR10, SOURCE_RELATIONSHIP,
17    SOURCE_RELATIONSHIP_SLICER, SOURCE_RELATIONSHIP_SLICER_CACHE, WORKBOOK_EXT_URI_PRIORITY,
18    WORKSHEET_EXT_URI_PRIORITY,
19};
20use crate::errors::Result;
21use crate::errors::{
22    ErrParameterInvalid, new_invalid_slicer_name_error, new_no_exist_slicer_error,
23    new_no_exist_table_error, new_pivot_table_selected_item_error,
24};
25use crate::lib_util::{cell_name_to_coordinates, coordinates_to_cell_name, in_str_slice};
26use crate::xml::common::{AttrValString, XlsxExt, XlsxExtLst, XlsxInnerXml};
27use crate::xml::decode_drawing::DecodeChoice;
28use crate::xml::drawing::{
29    ABodyPr, ALn, AP, AR, ASolidFill, ASrgbClr, GraphicOptions, XdrCNvSpPr, XdrCellAnchor,
30    XdrClientData, XdrNvSpPr, XdrSp, XdrTxBody, XlsxAlternateContent, XlsxCNvPr, XlsxFrom,
31    XlsxGraphic, XlsxGraphicData, XlsxGraphicFrame, XlsxNvGraphicFramePr, XlsxOff,
32    XlsxPositiveSize2D, XlsxPrstGeom, XlsxSle, XlsxSpPr, XlsxTo, XlsxWsDr, XlsxXfrm,
33};
34use crate::xml::pivot_cache::{
35    DecodeX14PivotCacheDefinition, XlsxPivotCacheDefinition, XlsxSharedItem, XlsxSharedItems,
36    XlsxX14PivotCacheDefinition,
37};
38use crate::xml::slicers::{
39    DecodeSlicerCaches, DecodeSlicerList, DecodeTableSlicerCache, XlsxSlicer as XlsxSlicerEl,
40    XlsxSlicerCacheData, XlsxSlicerCacheDefinition, XlsxSlicerCachePivotTable,
41    XlsxSlicerCachePivotTables, XlsxSlicers, XlsxTableSlicerCache, XlsxTabularSlicerCache,
42    XlsxTabularSlicerCacheItem, XlsxTabularSlicerCacheItems, XlsxTimelines, XlsxX14Slicer,
43    XlsxX14SlicerCache, XlsxX14SlicerCaches, XlsxX14SlicerList, XlsxX15SlicerCaches,
44};
45
46use crate::File;
47use crate::xml::table::Table;
48use crate::xml::workbook::DefinedName;
49
50/// Slicer creation and read-back options.
51#[derive(Debug, Default, Clone, PartialEq)]
52pub struct SlicerOptions {
53    pub(crate) slicer_xml: String,
54    pub(crate) slicer_cache_xml: String,
55    pub(crate) slicer_cache_name: String,
56    pub(crate) slicer_sheet_name: String,
57    pub(crate) slicer_sheet_rid: String,
58    pub(crate) drawing_xml: String,
59
60    /// Name specifies the slicer name, should be an existing field name of the
61    /// given table or pivot table.
62    pub name: String,
63    /// Cell specifies the left top cell coordinates for inserting the slicer.
64    pub cell: String,
65    /// TableSheet specifies the worksheet name of the table or pivot table.
66    pub table_sheet: String,
67    /// TableName specifies the name of the table or pivot table.
68    pub table_name: String,
69    /// Caption specifies the caption of the slicer.
70    pub caption: String,
71    /// Macro used for set macro for the slicer.
72    pub macro_name: String,
73    /// Width specifies the width of the slicer.
74    pub width: i32,
75    /// Height specifies the height of the slicer.
76    pub height: i32,
77    /// DisplayHeader specifies if display header of the slicer.
78    pub display_header: Option<bool>,
79    /// ItemDesc specifies descending (Z-A) item sorting.
80    pub item_desc: bool,
81    /// Format specifies the format of the slicer.
82    pub format: GraphicOptions,
83    /// SelectedItems option is used to specify the default selected items.
84    pub selected_items: Vec<String>,
85}
86
87impl File {
88    /// AddSlicer inserts a slicer by giving the worksheet name and slicer
89    /// settings.
90    pub fn add_slicer(&self, sheet: &str, opts: &SlicerOptions) -> Result<()> {
91        let mut opts = parse_slicer_options(opts)?;
92        let (table, pivot_table, col_idx) = self.get_slicer_source(&opts)?;
93        let (ext_uri, ns) = if table.is_some() {
94            (EXT_URI_SLICER_LIST_X15, NAMESPACE_DRAWING_ML_SLICER_X15)
95        } else {
96            (EXT_URI_SLICER_LIST_X14, NAMESPACE_DRAWING_ML_A14)
97        };
98        let slicer_id = self.add_sheet_slicer(sheet, ext_uri)?;
99        let slicer_cache_name =
100            self.set_slicer_cache(col_idx, &mut opts, table.as_ref(), pivot_table.as_ref())?;
101        let slicer_name = self.gen_slicer_name(&opts.name);
102        self.add_drawing_slicer(sheet, &slicer_name, ns, &opts)?;
103        self.add_slicer_part(
104            slicer_id,
105            XlsxSlicerEl {
106                name: slicer_name,
107                cache: slicer_cache_name,
108                caption: Some(opts.caption.clone()).filter(|s| !s.is_empty()),
109                show_caption: opts.display_header,
110                row_height: 251883,
111                ..Default::default()
112            },
113        )
114    }
115
116    /// GetSlicers provides all slicers in a worksheet by a given worksheet name.
117    pub fn get_slicers(&self, sheet: &str) -> Result<Vec<SlicerOptions>> {
118        let mut slicers = Vec::new();
119        let ws = self.work_sheet_reader(sheet)?;
120        if ws.ext_lst.is_none() {
121            return Ok(slicers);
122        }
123        let drawing_xml = if let Some(drawing) = &ws.drawing {
124            let target = self
125                .get_sheet_relationships_target_by_id(sheet, drawing.rid.as_deref().unwrap_or(""));
126            target
127                .replace("..", "xl")
128                .trim_start_matches('/')
129                .to_string()
130        } else {
131            String::new()
132        };
133        for ext in &ws.ext_lst.as_ref().unwrap().ext {
134            if ext.uri.as_deref() == Some(EXT_URI_SLICER_LIST_X14)
135                || ext.uri.as_deref() == Some(EXT_URI_SLICER_LIST_X15)
136            {
137                let slicer_list: DecodeSlicerList =
138                    xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
139                for slicer in slicer_list.slicer {
140                    if !slicer.rid.is_empty() {
141                        let mut opts =
142                            self.get_slicers_internal(sheet, &slicer.rid, &drawing_xml)?;
143                        slicers.append(&mut opts);
144                    }
145                }
146            }
147        }
148        Ok(slicers)
149    }
150
151    /// DeleteSlicer deletes a slicer by a given slicer name.
152    pub fn delete_slicer(&self, name: &str) -> Result<()> {
153        let all = self.get_all_slicers()?;
154        for (_, slicers) in &all {
155            for slicer in slicers {
156                if slicer.name != name {
157                    continue;
158                }
159                self.delete_slicer_internal(slicer)?;
160                return self.delete_slicer_cache(&all, slicer);
161            }
162        }
163        Err(new_no_exist_slicer_error(name).into())
164    }
165}
166
167// ------------------------------------------------------------------
168// Option parsing
169// ------------------------------------------------------------------
170
171fn parse_slicer_options(opts: &SlicerOptions) -> Result<SlicerOptions> {
172    if opts.name.is_empty()
173        || opts.cell.is_empty()
174        || opts.table_sheet.is_empty()
175        || opts.table_name.is_empty()
176    {
177        return Err(Box::new(ErrParameterInvalid));
178    }
179    let mut out = opts.clone();
180    if out.width == 0 {
181        out.width = DEFAULT_SLICER_WIDTH;
182    }
183    if out.height == 0 {
184        out.height = DEFAULT_SLICER_HEIGHT;
185    }
186    out.format = crate::chart::parse_graphic_options(&out.format)?;
187    Ok(out)
188}
189
190// ------------------------------------------------------------------
191// Slicer source resolution
192// ------------------------------------------------------------------
193
194impl File {
195    fn get_slicer_source(
196        &self,
197        opts: &SlicerOptions,
198    ) -> Result<(
199        Option<Table>,
200        Option<crate::pivot_table::PivotTableOptions>,
201        i32,
202    )> {
203        let mut table: Option<Table> = None;
204        let mut pivot_table: Option<crate::pivot_table::PivotTableOptions> = None;
205        let mut data_range = String::new();
206
207        let tables = self.get_tables(&opts.table_sheet)?;
208        for tbl in tables {
209            if tbl.name == opts.table_name {
210                table = Some(tbl.clone());
211                data_range = format!("{}!{}", opts.table_sheet, tbl.range);
212                break;
213            }
214        }
215
216        if table.is_none() {
217            let pivot_tables = self.get_pivot_tables(&opts.table_sheet)?;
218            for pt in pivot_tables {
219                if pt.data_range.is_empty() {
220                    continue;
221                }
222                if let Some(ref name) = self.find_pivot_table_name(&pt) {
223                    if name == &opts.table_name {
224                        pivot_table = Some(pt.clone());
225                        data_range = pt.data_range.clone();
226                        break;
227                    }
228                }
229            }
230            if pivot_table.is_none() {
231                return Err(new_no_exist_table_error(&opts.table_name).into());
232            }
233        }
234
235        let order = self.get_slicer_table_fields_order(&data_range)?;
236        let col_idx = in_str_slice(&order, &opts.name, true);
237        if col_idx == -1 {
238            return Err(new_invalid_slicer_name_error(&opts.name).into());
239        }
240        Ok((table, pivot_table, col_idx))
241    }
242
243    fn find_pivot_table_name(&self, pt: &crate::pivot_table::PivotTableOptions) -> Option<String> {
244        if pt.name.is_empty() {
245            None
246        } else {
247            Some(pt.name.clone())
248        }
249    }
250
251    fn get_slicer_table_fields_order(&self, data_range: &str) -> Result<Vec<String>> {
252        let (sheet, range) = split_data_range(data_range);
253        let coords = crate::lib_util::range_ref_to_coordinates(&range)?;
254        let mut order = Vec::new();
255        for col in coords[0]..=coords[2] {
256            let cell = coordinates_to_cell_name(col, coords[1], false)?;
257            let name = self.get_cell_value(&sheet, &cell)?;
258            if name.is_empty() {
259                return Err(Box::new(ErrParameterInvalid));
260            }
261            order.push(name);
262        }
263        Ok(order)
264    }
265}
266
267fn split_data_range(data_range: &str) -> (String, String) {
268    if let Some(pos) = data_range.find('!') {
269        (
270            data_range[..pos].to_string(),
271            data_range[pos + 1..].to_string(),
272        )
273    } else {
274        (String::new(), data_range.to_string())
275    }
276}
277
278// ------------------------------------------------------------------
279// Worksheet slicer list
280// ------------------------------------------------------------------
281
282impl File {
283    fn add_sheet_slicer(&self, sheet: &str, ext_uri: &str) -> Result<i32> {
284        let mut slicer_id = self.count_slicers() + 1;
285        let ws = self.work_sheet_reader(sheet)?;
286        if let Some(ext_lst) = &ws.ext_lst {
287            for ext in &ext_lst.ext {
288                if ext.uri.as_deref() == Some(ext_uri) {
289                    let slicer_list: DecodeSlicerList =
290                        xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
291                    for slicer in slicer_list.slicer {
292                        if !slicer.rid.is_empty() {
293                            let target =
294                                self.get_sheet_relationships_target_by_id(sheet, &slicer.rid);
295                            if let Some(id_str) = target
296                                .strip_prefix("../slicers/slicer")
297                                .and_then(|s| s.strip_suffix(".xml"))
298                            {
299                                slicer_id = id_str.parse::<i32>().unwrap_or(slicer_id);
300                            }
301                            return Ok(slicer_id);
302                        }
303                    }
304                }
305            }
306        }
307
308        let sheet_xml_path = self.get_sheet_xml_path(sheet).unwrap_or_default();
309        let sheet_rels = format!(
310            "xl/worksheets/_rels/{}.rels",
311            sheet_xml_path.trim_start_matches("xl/worksheets/")
312        );
313        let sheet_relationships_slicer_xml = format!("../slicers/slicer{slicer_id}.xml");
314        let r_id = self.add_rels(
315            &sheet_rels,
316            SOURCE_RELATIONSHIP_SLICER,
317            &sheet_relationships_slicer_xml,
318            "",
319        );
320        self.add_sheet_name_space(sheet, NAMESPACE_SPREADSHEET_X14);
321        self.add_sheet_table_slicer(sheet, r_id, ext_uri)?;
322        Ok(slicer_id)
323    }
324
325    fn add_sheet_table_slicer(&self, sheet: &str, r_id: i32, ext_uri: &str) -> Result<()> {
326        let mut ws = self.work_sheet_reader(sheet)?;
327        if ws.ext_lst.is_none() {
328            ws.ext_lst = Some(XlsxExtLst::default());
329        }
330        let ext_lst = ws.ext_lst.as_mut().unwrap();
331
332        let slicer_list_bytes = xml_to_string(&XlsxX14SlicerList {
333            slicer: vec![XlsxX14Slicer {
334                rid: format!("rId{r_id}"),
335            }],
336        })?;
337
338        let ns_attr = if ext_uri == EXT_URI_SLICER_LIST_X15 {
339            NAMESPACE_SPREADSHEET_X15
340        } else {
341            NAMESPACE_SPREADSHEET_X14
342        };
343
344        ext_lst.ext.push(XlsxExt {
345            uri: Some(ext_uri.to_string()),
346            content: format!(
347                r#"<x14:slicerList xmlns:x14="{}">{}</x14:slicerList>"#,
348                ns_attr,
349                extract_inner(&slicer_list_bytes)
350            ),
351            ..Default::default()
352        });
353        sort_ext_lst(&mut ext_lst.ext, WORKSHEET_EXT_URI_PRIORITY);
354        if let Some(path) = self.get_sheet_xml_path(sheet) {
355            self.sheet.insert(path, ws);
356        }
357        Ok(())
358    }
359}
360
361// ------------------------------------------------------------------
362// Slicer part
363// ------------------------------------------------------------------
364
365impl File {
366    fn add_slicer_part(&self, slicer_id: i32, slicer: XlsxSlicerEl) -> Result<()> {
367        let slicer_xml = format!("xl/slicers/slicer{slicer_id}.xml");
368        let mut slicers = self.slicer_reader(&slicer_xml)?;
369        self.add_content_type_part(slicer_id, "slicer")?;
370        slicers.slicer.push(slicer);
371        let output = xml_to_string(&slicers)?;
372        self.save_file_list(&slicer_xml, output.as_bytes());
373        Ok(())
374    }
375
376    fn slicer_reader(&self, slicer_xml: &str) -> Result<XlsxSlicers> {
377        let content = self.read_xml(slicer_xml);
378        let mut slicers = XlsxSlicers {
379            xmlns_mc: Some(SOURCE_RELATIONSHIP.to_string()),
380            xmlns_x: Some(NAMESPACE_SPREADSHEET.to_string()),
381            xmlns_xr10: Some(NAMESPACE_SPREADSHEET_XR10.to_string()),
382            ..Default::default()
383        };
384        if !content.is_empty() {
385            slicers = xml_from_reader(
386                crate::file::namespace_strict_to_transitional(&content).as_slice(),
387            )?;
388        }
389        Ok(slicers)
390    }
391
392    fn slicer_cache_reader(&self, slicer_cache_xml: &str) -> Result<XlsxSlicerCacheDefinition> {
393        let content = self.read_xml(slicer_cache_xml);
394        let mut slicer_cache = XlsxSlicerCacheDefinition::default();
395        if !content.is_empty() {
396            slicer_cache = xml_from_reader(
397                crate::file::namespace_strict_to_transitional(&content).as_slice(),
398            )?;
399        }
400        Ok(slicer_cache)
401    }
402
403    fn timeline_reader(&self, timeline_xml: &str) -> Result<XlsxTimelines> {
404        let content = self.read_xml(timeline_xml);
405        let mut timelines = XlsxTimelines {
406            xmlns_mc: Some(SOURCE_RELATIONSHIP.to_string()),
407            xmlns_x: Some(NAMESPACE_SPREADSHEET.to_string()),
408            xmlns_xr10: Some(NAMESPACE_SPREADSHEET_XR10.to_string()),
409            ..Default::default()
410        };
411        if !content.is_empty() {
412            timelines = xml_from_reader(
413                crate::file::namespace_strict_to_transitional(&content).as_slice(),
414            )?;
415        }
416        Ok(timelines)
417    }
418}
419
420// ------------------------------------------------------------------
421// Name generation
422// ------------------------------------------------------------------
423
424impl File {
425    fn gen_slicer_name(&self, name: &str) -> String {
426        let mut names: Vec<String> = Vec::new();
427        for entry in self.pkg.iter() {
428            let k = entry.key();
429            if k.contains("xl/slicers/slicer") {
430                if let Ok(slicers) = self.slicer_reader(k) {
431                    for slicer in slicers.slicer {
432                        names.push(slicer.name);
433                    }
434                }
435            }
436            if k.contains("xl/timelines/timeline") {
437                if let Ok(timelines) = self.timeline_reader(k) {
438                    for timeline in timelines.timeline {
439                        names.push(timeline.name);
440                    }
441                }
442            }
443        }
444
445        let mut cnt = 0;
446        let slicer_name = name.to_string();
447        loop {
448            let tmp = if cnt > 0 {
449                format!("{slicer_name} {cnt}")
450            } else {
451                slicer_name.clone()
452            };
453            if in_str_slice(&names, &tmp, true) == -1 {
454                return tmp;
455            }
456            cnt += 1;
457        }
458    }
459
460    fn gen_slicer_cache_name(&self, name: &str) -> String {
461        let mut defined_names: Vec<String> = Vec::new();
462        if let Ok(dns) = self.get_defined_names() {
463            for dn in dns {
464                if dn.scope == "Workbook" {
465                    defined_names.push(dn.name);
466                }
467            }
468        }
469
470        let mut slicer_cache_name = String::new();
471        for (i, c) in name.chars().enumerate() {
472            if c.is_alphabetic() {
473                slicer_cache_name.push(c);
474            } else if i > 0 && (c.is_ascii_digit() || c == '.') {
475                slicer_cache_name.push(c);
476            } else {
477                slicer_cache_name.push('_');
478            }
479        }
480        slicer_cache_name = format!("Slicer_{slicer_cache_name}");
481
482        let mut cnt = 0;
483        loop {
484            let tmp = if cnt > 0 {
485                format!("{slicer_cache_name}{cnt}")
486            } else {
487                slicer_cache_name.clone()
488            };
489            if in_str_slice(&defined_names, &tmp, true) == -1 {
490                return tmp;
491            }
492            cnt += 1;
493        }
494    }
495}
496
497// ------------------------------------------------------------------
498// Slicer cache
499// ------------------------------------------------------------------
500
501impl File {
502    fn set_slicer_cache(
503        &self,
504        col_idx: i32,
505        opts: &mut SlicerOptions,
506        table: Option<&Table>,
507        pivot_table: Option<&crate::pivot_table::PivotTableOptions>,
508    ) -> Result<String> {
509        let mut ok = false;
510        let mut slicer_cache_name = String::new();
511
512        for entry in self.pkg.iter() {
513            let k = entry.key();
514            if !k.contains("xl/slicerCaches/slicerCache") {
515                continue;
516            }
517            let slicer_cache = self.slicer_cache_reader(k)?;
518            if let Some(ref pts) = slicer_cache.pivot_tables {
519                if let Some(ref pt) = pivot_table {
520                    for tbl in &pts.pivot_table {
521                        if tbl.name == pt.data_range || tbl.name == opts.table_name {
522                            ok = true;
523                            slicer_cache_name = slicer_cache.name.clone();
524                            break;
525                        }
526                    }
527                }
528            }
529            if ok {
530                break;
531            }
532            if table.is_none() || slicer_cache.ext_lst.is_none() {
533                continue;
534            }
535            let ext = decode_first_ext(&slicer_cache.ext_lst.as_ref().unwrap().ext)?;
536            if ext.uri.as_deref() == Some(EXT_URI_SLICER_CACHE_DEFINITION) {
537                let tsc: DecodeTableSlicerCache =
538                    xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
539                if let Some(tbl) = table {
540                    if tsc.table_id == tbl.t_id && tsc.column == col_idx as i64 + 1 {
541                        ok = true;
542                        slicer_cache_name = slicer_cache.name.clone();
543                        break;
544                    }
545                }
546            }
547        }
548
549        if ok {
550            return Ok(slicer_cache_name);
551        }
552        let slicer_cache_name = self.gen_slicer_cache_name(&opts.name);
553        self.add_slicer_cache(&slicer_cache_name, col_idx, opts, table, pivot_table)?;
554        Ok(slicer_cache_name)
555    }
556
557    fn add_slicer_cache(
558        &self,
559        slicer_cache_name: &str,
560        col_idx: i32,
561        opts: &SlicerOptions,
562        table: Option<&Table>,
563        pivot_table: Option<&crate::pivot_table::PivotTableOptions>,
564    ) -> Result<()> {
565        let mut sort_order = String::new();
566        let mut ext_uri = EXT_URI_SLICER_CACHES_X14;
567        let slicer_cache_id = self.count_slicer_cache() + 1;
568
569        if opts.item_desc {
570            sort_order = "descending".to_string();
571        }
572
573        let mut slicer_cache = XlsxSlicerCacheDefinition {
574            xmlns_mc: Some(SOURCE_RELATIONSHIP.to_string()),
575            xmlns_x: Some(NAMESPACE_SPREADSHEET.to_string()),
576            xmlns_x15: Some(NAMESPACE_SPREADSHEET_X15.to_string()),
577            xmlns_xr10: Some(NAMESPACE_SPREADSHEET_XR10.to_string()),
578            name: slicer_cache_name.to_string(),
579            source_name: opts.name.clone(),
580            ..Default::default()
581        };
582
583        if let Some(pt) = pivot_table {
584            let pivot_cache_id = self.add_pivot_cache_slicer(pt)?;
585            slicer_cache.pivot_tables = Some(XlsxSlicerCachePivotTables {
586                pivot_table: vec![XlsxSlicerCachePivotTable {
587                    tab_id: self.get_sheet_id(&opts.table_sheet) as i64,
588                    name: opts.table_name.clone(),
589                }],
590            });
591            let items = self.build_slicer_items(pt, opts)?;
592            slicer_cache.data = Some(XlsxSlicerCacheData {
593                tabular: Some(XlsxTabularSlicerCache {
594                    pivot_cache_id: pivot_cache_id as i64,
595                    sort_order: Some(sort_order.clone()).filter(|s| !s.is_empty()),
596                    show_missing: Some(false),
597                    items: Some(XlsxTabularSlicerCacheItems {
598                        count: Some(items.len() as i64),
599                        i: items,
600                    }),
601                    ..Default::default()
602                }),
603                ..Default::default()
604            });
605        }
606
607        if let Some(tbl) = table {
608            let table_slicer_bytes = xml_to_string(&XlsxTableSlicerCache {
609                table_id: tbl.t_id,
610                column: col_idx as i64 + 1,
611                sort_order: Some(sort_order.clone()).filter(|s| !s.is_empty()),
612                ..Default::default()
613            })?;
614            slicer_cache.ext_lst = Some(XlsxExtLst {
615                ext: vec![XlsxExt {
616                    uri: Some(EXT_URI_SLICER_CACHE_DEFINITION.to_string()),
617                    content: format!(
618                        r#"<x15:tableSlicerCache xmlns:x15="{}">{}</x15:tableSlicerCache>"#,
619                        NAMESPACE_SPREADSHEET_X15,
620                        extract_inner(&table_slicer_bytes)
621                    ),
622                    ..Default::default()
623                }],
624            });
625            ext_uri = EXT_URI_SLICER_CACHES_X15;
626        }
627
628        let slicer_cache_xml = format!("xl/slicerCaches/slicerCache{slicer_cache_id}.xml");
629        let slicer_cache_bytes = xml_to_string(&slicer_cache)?;
630        self.save_file_list(&slicer_cache_xml, slicer_cache_bytes.as_bytes());
631        self.add_content_type_part(slicer_cache_id, "slicerCache")?;
632        self.add_workbook_slicer_cache(slicer_cache_id, ext_uri)?;
633        self.set_defined_name(&DefinedName {
634            name: slicer_cache_name.to_string(),
635            refers_to: "#N/A".to_string(),
636            scope: "Workbook".to_string(),
637            ..Default::default()
638        })
639    }
640
641    fn build_slicer_items(
642        &self,
643        pivot_table: &crate::pivot_table::PivotTableOptions,
644        opts: &SlicerOptions,
645    ) -> Result<Vec<XlsxTabularSlicerCacheItem>> {
646        let mut items: Vec<XlsxTabularSlicerCacheItem> = Vec::new();
647        let pivot_cache_xml = self.find_pivot_cache_xml(pivot_table)?;
648        let pc = self.slicer_pivot_cache_reader(&pivot_cache_xml)?;
649        let mut shared_items: Option<&XlsxSharedItems> = None;
650        if let Some(ref fields) = pc.cache_fields {
651            for field in &fields.cache_field {
652                if field.name == opts.name {
653                    shared_items = field.shared_items.as_ref();
654                    break;
655                }
656            }
657        }
658        let shared_items = match shared_items {
659            Some(si) => si,
660            None => {
661                return Ok(vec![XlsxTabularSlicerCacheItem {
662                    s: Some(true),
663                    ..Default::default()
664                }]);
665            }
666        };
667
668        let mut i = 0;
669        for item in &shared_items.items {
670            let (variant, v) = match item {
671                XlsxSharedItem::M(d) => ("m", d.v.clone().unwrap_or_default()),
672                XlsxSharedItem::B(d) => ("b", d.v.clone().unwrap_or_default()),
673                XlsxSharedItem::N(d) => ("n", d.v.clone().unwrap_or_default()),
674                XlsxSharedItem::E(d) => ("e", d.v.clone().unwrap_or_default()),
675                XlsxSharedItem::S(d) => ("s", d.v.clone().unwrap_or_default()),
676                XlsxSharedItem::D(d) => ("d", d.v.clone().unwrap_or_default()),
677            };
678            let selected = match variant {
679                "m" => in_str_slice(&opts.selected_items, "", true) != -1,
680                "b" => in_str_slice(&opts.selected_items, &v, false) != -1,
681                _ => in_str_slice(&opts.selected_items, &v, true) != -1,
682            };
683            items.push(XlsxTabularSlicerCacheItem {
684                x: i,
685                s: Some(selected),
686                nd: None,
687            });
688            i += 1;
689        }
690        check_selected_items(shared_items, &opts.name, &opts.selected_items)?;
691        if items.is_empty() {
692            items.push(XlsxTabularSlicerCacheItem {
693                s: Some(true),
694                ..Default::default()
695            });
696        }
697        Ok(items)
698    }
699
700    fn find_pivot_cache_xml(
701        &self,
702        _pivot_table: &crate::pivot_table::PivotTableOptions,
703    ) -> Result<String> {
704        for entry in self.pkg.iter() {
705            let k = entry.key();
706            if k.contains("xl/pivotCache/pivotCacheDefinition") {
707                return Ok(k.clone());
708            }
709        }
710        Err(Box::new(ErrParameterInvalid))
711    }
712
713    fn slicer_pivot_cache_reader(&self, pivot_cache_xml: &str) -> Result<XlsxPivotCacheDefinition> {
714        let content = self.read_xml(pivot_cache_xml);
715        let mut pc = XlsxPivotCacheDefinition::default();
716        if !content.is_empty() {
717            pc = xml_from_reader(
718                crate::file::namespace_strict_to_transitional(&content).as_slice(),
719            )?;
720        }
721        Ok(pc)
722    }
723
724    fn gen_slicer_pivot_cache_id(&self) -> i32 {
725        let mut id = 0;
726        for entry in self.pkg.iter() {
727            let k = entry.key();
728            if !k.contains("xl/pivotCache/pivotCacheDefinition") {
729                continue;
730            }
731            if let Ok(pc) = self.slicer_pivot_cache_reader(k) {
732                if let Some(ref ext_lst) = pc.ext_lst {
733                    let decoded = decode_ext_lst(&ext_lst.ext).unwrap_or_default();
734                    for ext in decoded {
735                        if ext.uri.as_deref() == Some(EXT_URI_PIVOT_CACHE_DEFINITION) {
736                            let def: crate::xml::pivot_cache::DecodeX14PivotCacheDefinition =
737                                xml_from_reader(wrap_ext_content(&ext.content).as_bytes())
738                                    .unwrap_or_default();
739                            if id < def.pivot_cache_id {
740                                id = def.pivot_cache_id;
741                            }
742                        }
743                    }
744                }
745            }
746        }
747        id + 1
748    }
749
750    fn add_pivot_cache_slicer(
751        &self,
752        _pivot_table: &crate::pivot_table::PivotTableOptions,
753    ) -> Result<i32> {
754        let pivot_cache_xml = self.find_first_pivot_cache_xml()?;
755        let mut pc = self.slicer_pivot_cache_reader(&pivot_cache_xml)?;
756        let mut decode_ext_lst = XlsxExtLst::default();
757        if let Some(ref ext_lst) = pc.ext_lst {
758            for ext in &ext_lst.ext {
759                if ext.uri.as_deref() == Some(EXT_URI_PIVOT_CACHE_DEFINITION) {
760                    let def: DecodeX14PivotCacheDefinition =
761                        xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
762                    return Ok(def.pivot_cache_id);
763                }
764            }
765        }
766        let pivot_cache_id = self.gen_slicer_pivot_cache_id();
767        let pivot_cache_bytes = xml_to_string(&XlsxX14PivotCacheDefinition {
768            xmlns: Some(NAMESPACE_SPREADSHEET_X14.to_string()),
769            pivot_cache_id,
770        })?;
771        decode_ext_lst.ext.push(XlsxExt {
772            uri: Some(EXT_URI_PIVOT_CACHE_DEFINITION.to_string()),
773            content: format!(
774                r#"<x14:pivotCacheDefinition xmlns:x14="{}">{}</x14:pivotCacheDefinition>"#,
775                NAMESPACE_SPREADSHEET_X14,
776                extract_inner(&pivot_cache_bytes)
777            ),
778            ..Default::default()
779        });
780        pc.ext_lst = Some(decode_ext_lst);
781        let pivot_cache = xml_to_string(&pc)?;
782        self.save_file_list(&pivot_cache_xml, pivot_cache.as_bytes());
783        Ok(pivot_cache_id)
784    }
785
786    fn find_first_pivot_cache_xml(&self) -> Result<String> {
787        for entry in self.pkg.iter() {
788            let k = entry.key();
789            if k.contains("xl/pivotCache/pivotCacheDefinition") {
790                return Ok(k.clone());
791            }
792        }
793        Err(Box::new(ErrParameterInvalid))
794    }
795}
796
797fn check_selected_items(
798    si: &XlsxSharedItems,
799    field: &str,
800    selected_items: &[String],
801) -> Result<()> {
802    for shared_item in selected_items {
803        let mut found = false;
804        for item in &si.items {
805            let (variant, v) = match item {
806                XlsxSharedItem::M(d) => ("m", d.v.clone().unwrap_or_default()),
807                XlsxSharedItem::B(d) => ("b", d.v.clone().unwrap_or_default()),
808                XlsxSharedItem::N(d) => ("n", d.v.clone().unwrap_or_default()),
809                XlsxSharedItem::E(d) => ("e", d.v.clone().unwrap_or_default()),
810                XlsxSharedItem::S(d) => ("s", d.v.clone().unwrap_or_default()),
811                XlsxSharedItem::D(d) => ("d", d.v.clone().unwrap_or_default()),
812            };
813            found = match variant {
814                "m" => shared_item.is_empty(),
815                "b" => shared_item.eq_ignore_ascii_case(&v),
816                _ => shared_item == &v,
817            };
818            if found {
819                break;
820            }
821        }
822        if !found {
823            return Err(new_pivot_table_selected_item_error(shared_item, field).into());
824        }
825    }
826    Ok(())
827}
828
829// ------------------------------------------------------------------
830// Drawing slicer shape
831// ------------------------------------------------------------------
832
833impl File {
834    fn add_drawing_slicer(
835        &self,
836        sheet: &str,
837        slicer_name: &str,
838        ns: &str,
839        opts: &SlicerOptions,
840    ) -> Result<()> {
841        let drawing_id = self.count_drawings() + 1;
842        let drawing_xml = format!("xl/drawings/drawing{drawing_id}.xml");
843        let (drawing_id, drawing_xml) = self.prepare_drawing(sheet, drawing_id, &drawing_xml)?;
844        let (mut content, mut cell_anchor, c_nv_pr_id) = self.cell_anchor_shape(
845            sheet,
846            &drawing_xml,
847            &opts.cell,
848            opts.width,
849            opts.height,
850            &opts.format,
851        )?;
852
853        let graphic_frame = XlsxGraphicFrame {
854            macro_name: opts.macro_name.clone(),
855            nv_graphic_frame_pr: XlsxNvGraphicFramePr {
856                c_nv_pr: Some(XlsxCNvPr {
857                    id: c_nv_pr_id,
858                    name: slicer_name.to_string(),
859                    descr: opts.format.alt_text.clone(),
860                    ..Default::default()
861                }),
862                c_nv_graphic_frame_pr: String::new(),
863            },
864            xfrm: XlsxXfrm {
865                off: XlsxOff { x: 0, y: 0 },
866                ext: XlsxPositiveSize2D { cx: 0, cy: 0 },
867            },
868            graphic: Some(XlsxGraphic {
869                graphic_data: Some(XlsxGraphicData {
870                    uri: NAMESPACE_DRAWING_ML_SLICER.to_string(),
871                    sle: Some(XlsxSle {
872                        xmlns_sle: ns.to_string(),
873                        name: slicer_name.to_string(),
874                    }),
875                    ..Default::default()
876                }),
877            }),
878        };
879        let graphic = xml_to_string(&graphic_frame)?;
880
881        let sp = XdrSp {
882            macro_name: opts.macro_name.clone(),
883            nv_sp_pr: Some(XdrNvSpPr {
884                c_nv_pr: Some(XlsxCNvPr {
885                    id: c_nv_pr_id,
886                    descr: opts.format.alt_text.clone(),
887                    ..Default::default()
888                }),
889                c_nv_sp_pr: Some(XdrCNvSpPr { tx_box: true }),
890            }),
891            sp_pr: Some(XlsxSpPr {
892                xfrm: XlsxXfrm {
893                    off: XlsxOff { x: 2914650, y: 152400 },
894                    ext: XlsxPositiveSize2D { cx: 1828800, cy: 2238375 },
895                },
896                solid_fill: Some(ASolidFill {
897                    srgb_clr: Some(ASrgbClr { val: Some("FFFFFF".to_string()), ..Default::default() }),
898                    ..Default::default()
899                }),
900                prst_geom: XlsxPrstGeom { prst: "rect".to_string() },
901                ln: Some(ALn {
902                    w: Some(1),
903                    solid_fill: Some(ASolidFill {
904                        prst_clr: Some(AttrValString { val: Some("black".to_string()) }),
905                        ..Default::default()
906                    }),
907                    ..Default::default()
908                }),
909            }),
910            tx_body: Some(XdrTxBody {
911                body_pr: Some(ABodyPr {
912                    vert_overflow: Some("clip".to_string()),
913                    horz_overflow: Some("clip".to_string()),
914                    ..Default::default()
915                }),
916                p: vec![
917                    AP {
918                        r: Some(AR { t: Some("This shape represents a table slicer. Table slicers are not supported in this version of Excel.".to_string()), ..Default::default() }),
919                        ..Default::default()
920                    },
921                    AP {
922                        r: Some(AR { t: Some("If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used.".to_string()), ..Default::default() }),
923                        ..Default::default()
924                    },
925                ],
926            }),
927            ..Default::default()
928        };
929        let shape = xml_to_string(&sp)?;
930
931        cell_anchor.client_data = Some(XdrClientData {
932            f_locks_with_sheet: opts.format.locked.unwrap_or(true),
933            f_prints_with_sheet: opts.format.print_object.unwrap_or(true),
934        });
935
936        let prefix = ns_to_prefix(ns);
937        let xmlns_attr = format!(r#"xmlns:{}="{}""#, prefix, ns);
938        let choice = format!(
939            r#"<mc:Choice {} Requires="{}">{}</mc:Choice>"#,
940            xmlns_attr, prefix, graphic
941        );
942        let fallback = format!(r#"<mc:Fallback>{}</mc:Fallback>"#, shape);
943        cell_anchor.alternate_content.push(XlsxAlternateContent {
944            xmlns_mc: Some(SOURCE_RELATIONSHIP.to_string()),
945            content: XlsxInnerXml {
946                content: format!("{choice}{fallback}"),
947            },
948        });
949
950        if opts.format.positioning == "oneCell" {
951            content.one_cell_anchor.push(cell_anchor);
952        } else {
953            content.two_cell_anchor.push(cell_anchor);
954        }
955        self.drawings.insert(drawing_xml, content);
956        self.add_content_type_part(drawing_id, "drawings")
957    }
958
959    fn cell_anchor_shape(
960        &self,
961        sheet: &str,
962        drawing_xml: &str,
963        cell: &str,
964        width: i32,
965        height: i32,
966        opts: &GraphicOptions,
967    ) -> Result<(XlsxWsDr, XdrCellAnchor, i64)> {
968        let (col, row) = cell_name_to_coordinates(cell)?;
969        let width = (width as f64 * opts.scale_x) as i32;
970        let height = (height as f64 * opts.scale_y) as i32;
971        let (col_start, row_start, col_end, row_end, x1, y1, x2, y2) =
972            self.position_object_pixels(sheet, col, row, width, height, opts)?;
973        let (content, c_nv_pr_id) = self.drawing_parser(drawing_xml)?;
974
975        let mut anchor = XdrCellAnchor {
976            edit_as: if opts.positioning.is_empty() {
977                None
978            } else {
979                Some(opts.positioning.clone())
980            },
981            from: Some(XlsxFrom {
982                col: col_start as i64,
983                col_off: x1 as i64 * EMU as i64,
984                row: row_start as i64,
985                row_off: y1 as i64 * EMU as i64,
986            }),
987            ..Default::default()
988        };
989        if opts.positioning == "oneCell" {
990            anchor.ext = Some(XlsxPositiveSize2D {
991                cx: x2 as i64 * EMU as i64,
992                cy: y2 as i64 * EMU as i64,
993            });
994        } else {
995            anchor.to = Some(XlsxTo {
996                col: col_end as i64,
997                col_off: x2 as i64 * EMU as i64,
998                row: row_end as i64,
999                row_off: y2 as i64 * EMU as i64,
1000            });
1001        }
1002        Ok((content, anchor, c_nv_pr_id))
1003    }
1004}
1005
1006fn ns_to_prefix(ns: &str) -> &'static str {
1007    match ns {
1008        NAMESPACE_DRAWING_ML_A14 => "a14",
1009        NAMESPACE_DRAWING_ML_SLICER_X15 => "sle15",
1010        _ => "a14",
1011    }
1012}
1013
1014// ------------------------------------------------------------------
1015// Workbook slicer cache references
1016// ------------------------------------------------------------------
1017
1018impl File {
1019    fn add_workbook_slicer_cache(&self, slicer_cache_id: i32, uri: &str) -> Result<()> {
1020        let mut wb = self.workbook_reader()?;
1021        let r_id = self.add_rels(
1022            &self.get_workbook_rels_path(),
1023            SOURCE_RELATIONSHIP_SLICER_CACHE,
1024            &format!("/xl/slicerCaches/slicerCache{slicer_cache_id}.xml"),
1025            "",
1026        );
1027
1028        if wb.ext_lst.is_none() {
1029            wb.ext_lst = Some(XlsxExtLst::default());
1030        }
1031        let ext_lst = wb.ext_lst.as_mut().unwrap();
1032        let mut append_mode = false;
1033        for ext in &mut ext_lst.ext {
1034            if ext.uri.as_deref() == Some(uri) {
1035                let decode_slicer_caches: DecodeSlicerCaches =
1036                    xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
1037                let slicer_cache = XlsxX14SlicerCache {
1038                    rid: format!("rId{r_id}"),
1039                };
1040                let slicer_cache_bytes = xml_to_string(&slicer_cache)?;
1041                let content = format!(
1042                    "{}{}",
1043                    decode_slicer_caches.content,
1044                    extract_inner(&slicer_cache_bytes)
1045                );
1046                let caches_bytes = if uri == EXT_URI_SLICER_CACHES_X14 {
1047                    xml_to_string(&XlsxX14SlicerCaches {
1048                        xmlns: NAMESPACE_SPREADSHEET_X14.to_string(),
1049                        content,
1050                    })?
1051                } else {
1052                    xml_to_string(&XlsxX15SlicerCaches {
1053                        xmlns: NAMESPACE_SPREADSHEET_X14.to_string(),
1054                        content,
1055                    })?
1056                };
1057                ext.content = extract_inner(&caches_bytes);
1058                append_mode = true;
1059            }
1060        }
1061
1062        if !append_mode {
1063            let slicer_cache = XlsxX14SlicerCache {
1064                rid: format!("rId{r_id}"),
1065            };
1066            let slicer_cache_bytes = xml_to_string(&slicer_cache)?;
1067            let (content, ns) = (
1068                extract_inner(&slicer_cache_bytes),
1069                NAMESPACE_SPREADSHEET_X14,
1070            );
1071            let caches_bytes = if uri == EXT_URI_SLICER_CACHES_X14 {
1072                xml_to_string(&XlsxX14SlicerCaches {
1073                    xmlns: ns.to_string(),
1074                    content,
1075                })?
1076            } else {
1077                xml_to_string(&XlsxX15SlicerCaches {
1078                    xmlns: ns.to_string(),
1079                    content,
1080                })?
1081            };
1082            ext_lst.ext.push(XlsxExt {
1083                uri: Some(uri.to_string()),
1084                content: extract_inner(&caches_bytes),
1085                ..Default::default()
1086            });
1087        }
1088
1089        sort_ext_lst(&mut ext_lst.ext, WORKBOOK_EXT_URI_PRIORITY);
1090        *self.workbook.lock().unwrap() = Some(wb);
1091        Ok(())
1092    }
1093}
1094
1095// ------------------------------------------------------------------
1096// Get slicers
1097// ------------------------------------------------------------------
1098
1099impl File {
1100    fn get_all_slicers(&self) -> Result<HashMap<String, Vec<SlicerOptions>>> {
1101        let mut slicers: HashMap<String, Vec<SlicerOptions>> = HashMap::new();
1102        for sheet_name in self.get_sheet_list() {
1103            match self.get_slicers(&sheet_name) {
1104                Ok(sles) => {
1105                    slicers.insert(sheet_name, sles);
1106                }
1107                Err(_) => {}
1108            }
1109        }
1110        Ok(slicers)
1111    }
1112
1113    fn get_slicer_cache(
1114        &self,
1115        slicer_cache_name: &str,
1116        opt: &mut SlicerOptions,
1117    ) -> Option<XlsxSlicerCacheDefinition> {
1118        let mut result: Option<XlsxSlicerCacheDefinition> = None;
1119        for entry in self.pkg.iter() {
1120            let k = entry.key();
1121            if !k.contains("xl/slicerCaches/slicerCache") {
1122                continue;
1123            }
1124            if let Ok(slicer_cache) = self.slicer_cache_reader(k) {
1125                if slicer_cache.name == slicer_cache_name {
1126                    opt.slicer_cache_xml = k.clone();
1127                    result = Some(slicer_cache);
1128                    break;
1129                }
1130            }
1131        }
1132        result
1133    }
1134
1135    fn get_slicers_internal(
1136        &self,
1137        sheet: &str,
1138        r_id: &str,
1139        drawing_xml: &str,
1140    ) -> Result<Vec<SlicerOptions>> {
1141        let mut opts = Vec::new();
1142        let sheet_relationships_slicer_xml = self.get_sheet_relationships_target_by_id(sheet, r_id);
1143        let slicer_xml = sheet_relationships_slicer_xml.replace("..", "xl");
1144        let slicers = self.slicer_reader(&slicer_xml)?;
1145        for slicer in slicers.slicer {
1146            let mut opt = SlicerOptions {
1147                slicer_xml: slicer_xml.clone(),
1148                slicer_cache_name: slicer.cache.clone(),
1149                slicer_sheet_name: sheet.to_string(),
1150                slicer_sheet_rid: r_id.to_string(),
1151                drawing_xml: drawing_xml.to_string(),
1152                name: slicer.name.clone(),
1153                caption: slicer.caption.clone().unwrap_or_default(),
1154                display_header: slicer.show_caption,
1155                ..Default::default()
1156            };
1157            if let Some(slicer_cache) = self.get_slicer_cache(&slicer.cache, &mut opt) {
1158                self.extract_table_slicer(&slicer_cache, &mut opt)?;
1159                self.extract_pivot_table_slicer(&slicer_cache, &mut opt)?;
1160                self.extract_slicer_cell_anchor(drawing_xml, &mut opt)?;
1161                opts.push(opt);
1162            }
1163        }
1164        Ok(opts)
1165    }
1166
1167    fn extract_table_slicer(
1168        &self,
1169        slicer_cache: &XlsxSlicerCacheDefinition,
1170        opt: &mut SlicerOptions,
1171    ) -> Result<()> {
1172        if let Some(ref ext_lst) = slicer_cache.ext_lst {
1173            let tables = self.get_tables_internal()?;
1174            let ext = decode_first_ext(&ext_lst.ext)?;
1175            if ext.uri.as_deref() == Some(EXT_URI_SLICER_CACHE_DEFINITION) {
1176                let tsc: DecodeTableSlicerCache =
1177                    xml_from_reader(wrap_ext_content(&ext.content).as_bytes())?;
1178                opt.item_desc = tsc.sort_order == "descending";
1179                for (sheet_name, sheet_tables) in tables {
1180                    for table in sheet_tables {
1181                        if tsc.table_id == table.t_id {
1182                            opt.table_name = table.name.clone();
1183                            opt.table_sheet = sheet_name.clone();
1184                        }
1185                    }
1186                }
1187            }
1188        }
1189        Ok(())
1190    }
1191
1192    fn extract_pivot_table_slicer(
1193        &self,
1194        slicer_cache: &XlsxSlicerCacheDefinition,
1195        opt: &mut SlicerOptions,
1196    ) -> Result<()> {
1197        let pivot_tables = self.get_pivot_tables_internal()?;
1198        if let Some(ref pts) = slicer_cache.pivot_tables {
1199            let mut pivot_cache_xml = String::new();
1200            for pt in &pts.pivot_table {
1201                opt.table_name = pt.name.clone();
1202                for (sheet_name, sheet_pts) in &pivot_tables {
1203                    for pivot_table in sheet_pts {
1204                        if opt.table_name == pivot_table.data_range {
1205                            opt.table_sheet = sheet_name.clone();
1206                        }
1207                        if pt.name == pivot_table.data_range {
1208                            pivot_cache_xml =
1209                                self.find_pivot_cache_xml(pivot_table).unwrap_or_default();
1210                        }
1211                    }
1212                }
1213            }
1214            if let Some(ref data) = slicer_cache.data {
1215                if let Some(ref tabular) = data.tabular {
1216                    opt.item_desc = tabular.sort_order.as_deref() == Some("descending");
1217                }
1218            }
1219            self.extract_slicer_selected_items(&pivot_cache_xml, slicer_cache, opt)?;
1220        }
1221        Ok(())
1222    }
1223
1224    fn extract_slicer_selected_items(
1225        &self,
1226        pivot_cache_xml: &str,
1227        slicer_cache: &XlsxSlicerCacheDefinition,
1228        opt: &mut SlicerOptions,
1229    ) -> Result<()> {
1230        if pivot_cache_xml.is_empty() {
1231            return Ok(());
1232        }
1233        let pc = self.slicer_pivot_cache_reader(pivot_cache_xml)?;
1234        if let Some(ref data) = slicer_cache.data {
1235            if let Some(ref tabular) = data.tabular {
1236                if let Some(ref items) = tabular.items {
1237                    for item in &items.i {
1238                        if item.s.unwrap_or(false) {
1239                            if let Some(ref fields) = pc.cache_fields {
1240                                for field in &fields.cache_field {
1241                                    if field.name == slicer_cache.source_name {
1242                                        if let Some(ref shared_items) = field.shared_items {
1243                                            let idx = item.x as usize;
1244                                            if idx < shared_items.items.len() {
1245                                                let val = match &shared_items.items[idx] {
1246                                                    XlsxSharedItem::M(d) => d.v.clone(),
1247                                                    XlsxSharedItem::B(d) => d.v.clone(),
1248                                                    XlsxSharedItem::N(d) => d.v.clone(),
1249                                                    XlsxSharedItem::E(d) => d.v.clone(),
1250                                                    XlsxSharedItem::S(d) => d.v.clone(),
1251                                                    XlsxSharedItem::D(d) => d.v.clone(),
1252                                                };
1253                                                if let Some(v) = val {
1254                                                    opt.selected_items.push(v);
1255                                                }
1256                                            }
1257                                        }
1258                                    }
1259                                }
1260                            }
1261                        }
1262                    }
1263                }
1264            }
1265        }
1266        Ok(())
1267    }
1268
1269    fn extract_slicer_cell_anchor(&self, drawing_xml: &str, opt: &mut SlicerOptions) -> Result<()> {
1270        if drawing_xml.is_empty() {
1271            return Ok(());
1272        }
1273        let (ws_dr, _) = self.drawing_parser(drawing_xml)?;
1274        for anchor in &ws_dr.one_cell_anchor {
1275            self.extract_slicer_from_anchor(anchor, opt)?;
1276            self.extract_slicer_from_decode_anchor(anchor, opt)?;
1277        }
1278        for anchor in &ws_dr.two_cell_anchor {
1279            self.extract_slicer_from_anchor(anchor, opt)?;
1280            self.extract_slicer_from_decode_anchor(anchor, opt)?;
1281        }
1282        Ok(())
1283    }
1284
1285    fn extract_slicer_from_anchor(
1286        &self,
1287        anchor: &XdrCellAnchor,
1288        opt: &mut SlicerOptions,
1289    ) -> Result<()> {
1290        for ac in &anchor.alternate_content {
1291            let de_choice: DecodeChoice =
1292                xml_from_reader(ac.content.content.as_bytes()).unwrap_or_default();
1293            if de_choice.xmlns_sle15.as_deref() == Some(NAMESPACE_DRAWING_ML_SLICER_X15)
1294                || de_choice.xmlns_a14.as_deref() == Some(NAMESPACE_DRAWING_ML_A14)
1295            {
1296                if de_choice.graphic_frame.nv_graphic_frame_pr.c_nv_pr.name == opt.name {
1297                    opt.macro_name = de_choice.graphic_frame.macro_name.clone();
1298                    if let Some(ref from) = anchor.from {
1299                        opt.cell = coordinates_to_cell_name(
1300                            (from.col + 1) as i32,
1301                            (from.row + 1) as i32,
1302                            false,
1303                        )?;
1304                    }
1305                }
1306            }
1307        }
1308        Ok(())
1309    }
1310
1311    fn extract_slicer_from_decode_anchor(
1312        &self,
1313        anchor: &XdrCellAnchor,
1314        opt: &mut SlicerOptions,
1315    ) -> Result<()> {
1316        if let Some(ref gf) = anchor.graphic_frame {
1317            if let Some(ref graphic) = gf.graphic {
1318                if let Some(ref data) = graphic.graphic_data {
1319                    if data.uri == NAMESPACE_DRAWING_ML_SLICER
1320                        || data.uri == NAMESPACE_DRAWING_ML_SLICER_X15
1321                    {
1322                        if let Some(ref sle) = data.sle {
1323                            if sle.name == opt.name {
1324                                opt.macro_name = gf.macro_name.clone();
1325                                if let Some(ref from) = anchor.from {
1326                                    opt.cell = coordinates_to_cell_name(
1327                                        (from.col + 1) as i32,
1328                                        (from.row + 1) as i32,
1329                                        false,
1330                                    )?;
1331                                }
1332                            }
1333                        }
1334                    }
1335                }
1336            }
1337        }
1338        Ok(())
1339    }
1340
1341    fn get_tables_internal(&self) -> Result<HashMap<String, Vec<Table>>> {
1342        let mut tables: HashMap<String, Vec<Table>> = HashMap::new();
1343        for sheet_name in self.get_sheet_list() {
1344            if let Ok(tbls) = self.get_tables(&sheet_name) {
1345                tables.insert(sheet_name, tbls);
1346            }
1347        }
1348        Ok(tables)
1349    }
1350
1351    fn get_pivot_tables_internal(
1352        &self,
1353    ) -> Result<HashMap<String, Vec<crate::pivot_table::PivotTableOptions>>> {
1354        let mut pivot_tables: HashMap<String, Vec<crate::pivot_table::PivotTableOptions>> =
1355            HashMap::new();
1356        for sheet_name in self.get_sheet_list() {
1357            if let Ok(pts) = self.get_pivot_tables(&sheet_name) {
1358                pivot_tables.insert(sheet_name, pts);
1359            }
1360        }
1361        Ok(pivot_tables)
1362    }
1363}
1364
1365// ------------------------------------------------------------------
1366// Delete slicer
1367// ------------------------------------------------------------------
1368
1369impl File {
1370    fn delete_slicer_internal(&self, opts: &SlicerOptions) -> Result<()> {
1371        let mut slicers = self.slicer_reader(&opts.slicer_xml)?;
1372        slicers.slicer.retain(|s| s.name != opts.name);
1373        if slicers.slicer.is_empty() {
1374            let mut ws = self.work_sheet_reader(&opts.slicer_sheet_name)?;
1375            if let Some(ref mut ext_lst) = ws.ext_lst {
1376                let original_len = ext_lst.ext.len();
1377                ext_lst.ext.retain(|ext| {
1378                    if ext.uri.as_deref() != Some(EXT_URI_SLICER_LIST_X14)
1379                        && ext.uri.as_deref() != Some(EXT_URI_SLICER_LIST_X15)
1380                    {
1381                        return true;
1382                    }
1383                    let slicer_list: DecodeSlicerList =
1384                        xml_from_reader(wrap_ext_content(&ext.content).as_bytes())
1385                            .unwrap_or_default();
1386                    !slicer_list
1387                        .slicer
1388                        .iter()
1389                        .any(|s| s.rid == opts.slicer_sheet_rid)
1390                });
1391                if ext_lst.ext.len() != original_len {
1392                    if ext_lst.ext.is_empty() {
1393                        ws.ext_lst = None;
1394                    }
1395                    if let Some(path) = self.get_sheet_xml_path(&opts.slicer_sheet_name) {
1396                        self.sheet.insert(path, ws);
1397                    }
1398                    self.pkg.remove(&opts.slicer_xml);
1399                    self.remove_content_types_part(
1400                        CONTENT_TYPE_SLICER,
1401                        &format!("/{}", opts.slicer_xml),
1402                    )?;
1403                    self.delete_sheet_relationships(
1404                        &opts.slicer_sheet_name,
1405                        &opts.slicer_sheet_rid,
1406                    );
1407                    return Ok(());
1408                }
1409            }
1410        }
1411        let output = xml_to_string(&slicers)?;
1412        self.save_file_list(&opts.slicer_xml, output.as_bytes());
1413        Ok(())
1414    }
1415
1416    fn delete_slicer_cache(
1417        &self,
1418        all: &HashMap<String, Vec<SlicerOptions>>,
1419        opts: &SlicerOptions,
1420    ) -> Result<()> {
1421        for (_, slicers) in all {
1422            for slicer in slicers {
1423                if slicer.name != opts.name && slicer.slicer_cache_name == opts.slicer_cache_name {
1424                    return Ok(());
1425                }
1426            }
1427        }
1428        self.delete_defined_name(&DefinedName {
1429            name: opts.slicer_cache_name.clone(),
1430            scope: "Workbook".to_string(),
1431            ..Default::default()
1432        })?;
1433        self.pkg.remove(&opts.slicer_cache_xml);
1434        self.remove_content_types_part(
1435            CONTENT_TYPE_SLICER_CACHE,
1436            &format!("/{}", opts.slicer_cache_xml),
1437        )
1438    }
1439}
1440
1441// ------------------------------------------------------------------
1442// Extension list helpers
1443// ------------------------------------------------------------------
1444
1445fn decode_ext_lst(exts: &[XlsxExt]) -> Result<Vec<XlsxExt>> {
1446    let mut out = Vec::new();
1447    for ext in exts {
1448        out.push(XlsxExt {
1449            uri: ext.uri.clone(),
1450            xmlns_x14: ext.xmlns_x14.clone(),
1451            xmlns_xm: ext.xmlns_xm.clone(),
1452            content: ext.content.clone(),
1453        });
1454    }
1455    Ok(out)
1456}
1457
1458fn decode_first_ext(exts: &[XlsxExt]) -> Result<XlsxExt> {
1459    decode_ext_lst(exts)?
1460        .into_iter()
1461        .next()
1462        .ok_or_else(|| ErrParameterInvalid.into())
1463}
1464
1465fn wrap_ext_content(content: &str) -> String {
1466    format!("<ext>{content}</ext>")
1467}
1468
1469fn extract_inner(s: &str) -> String {
1470    // Strip the outermost XML element, leaving only its children/text.
1471    let s = s.trim();
1472    let start = s.find('>').map(|i| i + 1).unwrap_or(0);
1473    let end = s.rfind('<').unwrap_or(s.len());
1474    if end > start {
1475        s[start..end].to_string()
1476    } else {
1477        String::new()
1478    }
1479}
1480
1481fn sort_ext_lst(exts: &mut Vec<XlsxExt>, priority: &[&str]) {
1482    exts.sort_by(|a, b| {
1483        let ai = a
1484            .uri
1485            .as_deref()
1486            .and_then(|u| priority.iter().position(|p| *p == u))
1487            .unwrap_or(usize::MAX);
1488        let bi = b
1489            .uri
1490            .as_deref()
1491            .and_then(|u| priority.iter().position(|p| *p == u))
1492            .unwrap_or(usize::MAX);
1493        ai.cmp(&bi)
1494    });
1495}
1496
1497// ------------------------------------------------------------------
1498// Tests
1499// ------------------------------------------------------------------
1500
1501#[cfg(test)]
1502mod tests {
1503    use super::*;
1504    use crate::Options;
1505
1506    fn sample_opts() -> SlicerOptions {
1507        SlicerOptions {
1508            name: "Column1".to_string(),
1509            cell: "E1".to_string(),
1510            table_sheet: "Sheet1".to_string(),
1511            table_name: "Table1".to_string(),
1512            caption: "Column1".to_string(),
1513            width: 200,
1514            height: 200,
1515            ..Default::default()
1516        }
1517    }
1518
1519    #[test]
1520    fn parse_slicer_options_defaults() {
1521        let opts = sample_opts();
1522        let parsed = parse_slicer_options(&opts).unwrap();
1523        assert_eq!(parsed.width, 200);
1524        assert_eq!(parsed.height, 200);
1525        assert_eq!(parsed.format.print_object, Some(true));
1526        assert_eq!(parsed.format.locked, Some(true));
1527    }
1528
1529    #[test]
1530    fn parse_slicer_options_missing_required() {
1531        let mut opts = sample_opts();
1532        opts.name.clear();
1533        assert!(parse_slicer_options(&opts).is_err());
1534
1535        let mut opts = sample_opts();
1536        opts.cell.clear();
1537        assert!(parse_slicer_options(&opts).is_err());
1538    }
1539
1540    #[test]
1541    fn parse_slicer_options_fills_defaults() {
1542        let mut opts = sample_opts();
1543        opts.width = 0;
1544        opts.height = 0;
1545        let parsed = parse_slicer_options(&opts).unwrap();
1546        assert_eq!(parsed.width, DEFAULT_SLICER_WIDTH);
1547        assert_eq!(parsed.height, DEFAULT_SLICER_HEIGHT);
1548    }
1549
1550    #[test]
1551    fn gen_slicer_cache_name_cleans_invalid_chars() {
1552        let f = File::new_with_options(Options::default());
1553        assert!(
1554            f.gen_slicer_cache_name("Column 1")
1555                .starts_with("Slicer_Column_1")
1556        );
1557        assert!(f.gen_slicer_cache_name("A.B").starts_with("Slicer_A.B"));
1558    }
1559
1560    #[test]
1561    fn get_slicers_empty_workbook() {
1562        let f = File::new_with_options(Options::default());
1563        let slicers = f.get_slicers("Sheet1").unwrap();
1564        assert!(slicers.is_empty());
1565    }
1566
1567    #[test]
1568    fn delete_nonexistent_slicer_fails() {
1569        let f = File::new_with_options(Options::default());
1570        assert!(f.delete_slicer("NoSuchSlicer").is_err());
1571    }
1572
1573    #[test]
1574    fn add_slicer_requires_source_table_or_pivot() {
1575        let f = File::new_with_options(Options::default());
1576        let opts = sample_opts();
1577        // No table/pivot exists in a blank workbook.
1578        assert!(f.add_slicer("Sheet1", &opts).is_err());
1579    }
1580
1581    #[test]
1582    fn count_slicers_starts_at_zero() {
1583        let f = File::new_with_options(Options::default());
1584        assert_eq!(f.count_slicers(), 0);
1585        assert_eq!(f.count_slicer_cache(), 0);
1586    }
1587}