1use std::collections::HashMap;
6
7use quick_xml::de::from_reader as xml_from_reader;
8use quick_xml::se::to_string as xml_to_string;
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#[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 pub name: String,
63 pub cell: String,
65 pub table_sheet: String,
67 pub table_name: String,
69 pub caption: String,
71 pub macro_name: String,
73 pub width: i32,
75 pub height: i32,
77 pub display_header: Option<bool>,
79 pub item_desc: bool,
81 pub format: GraphicOptions,
83 pub selected_items: Vec<String>,
85}
86
87impl File {
88 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 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 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
167fn 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
190impl 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
278impl 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
361impl 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
420impl 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
497impl 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
829impl 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
1014impl 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
1095impl 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
1365impl 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
1441fn 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 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#[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 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}