1use quick_xml::se::to_string as xml_to_string;
6
7use crate::File;
8use crate::constants::{
9 EXT_URI_SPARKLINE_GROUPS, NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN, NAMESPACE_SPREADSHEET_X14,
10 WORKSHEET_EXT_URI_PRIORITY,
11};
12use crate::errors::{
13 ErrSparkline, ErrSparklineLocation, ErrSparklineRange, ErrSparklineStyle, ErrSparklineType,
14 Result,
15};
16use crate::lib_util::{in_str_slice, int_ptr};
17use crate::xml::common::{XlsxColor, XlsxExt, XlsxExtLst};
18use crate::xml::worksheet::{
19 SparklineOptions, XlsxWorksheet, XlsxX14Sparkline, XlsxX14SparklineGroup,
20 XlsxX14SparklineGroups,
21};
22
23impl File {
24 pub fn add_sparkline(&self, sheet: &str, opts: &SparklineOptions) -> Result<()> {
30 let mut ws = self.parse_format_add_sparkline_set(sheet, opts)?;
31
32 let spark_types = [
33 ("line", "line"),
34 ("column", "column"),
35 ("win_loss", "stacked"),
36 ]
37 .iter()
38 .cloned()
39 .collect::<std::collections::HashMap<_, _>>();
40 let mut spark_type = "line".to_string();
41 if !opts.r#type.is_empty() {
42 spark_type = spark_types
43 .get(opts.r#type.as_str())
44 .ok_or_else(|| ErrSparklineType)?
45 .to_string();
46 }
47
48 let presets = get_sparkline_group_presets();
49 let mut group = presets
50 .get(opts.style as usize)
51 .cloned()
52 .unwrap_or_else(|| presets[0].clone());
53 group.r#type = Some(spark_type);
54 group.color_axis = Some(XlsxColor {
55 rgb: Some("FF000000".to_string()),
56 ..Default::default()
57 });
58 group.display_empty_cells_as = Some("gap".to_string());
59 group.high = Some(opts.high).filter(|&v| v);
60 group.low = Some(opts.low).filter(|&v| v);
61 group.first = Some(opts.first).filter(|&v| v);
62 group.last = Some(opts.last).filter(|&v| v);
63 group.negative = Some(opts.negative).filter(|&v| v);
64 group.display_x_axis = Some(opts.axis).filter(|&v| v);
65 group.markers = Some(opts.markers).filter(|&v| v);
66 if !opts.series_color.is_empty() {
67 group.color_series = Some(XlsxColor {
68 rgb: Some(crate::styles::get_palette_color(&opts.series_color)),
69 ..Default::default()
70 });
71 }
72 if opts.reverse {
73 group.right_to_left = Some(opts.reverse);
74 }
75
76 add_sparkline_items(opts, &mut group);
77 self.append_sparkline_group(&mut ws, &group)?;
78 self.add_sheet_name_space(sheet, NAMESPACE_SPREADSHEET_X14);
79
80 let path =
81 self.get_sheet_xml_path(sheet)
82 .ok_or_else(|| crate::errors::ErrSheetNotExist {
83 sheet_name: sheet.to_string(),
84 })?;
85 self.sheet.insert(path, ws);
86 Ok(())
87 }
88
89 fn parse_format_add_sparkline_set(
91 &self,
92 sheet: &str,
93 opts: &SparklineOptions,
94 ) -> Result<XlsxWorksheet> {
95 let mut ws = self.work_sheet_reader(sheet)?;
96 if opts.location.is_empty() {
97 return Err(Box::new(ErrSparklineLocation));
98 }
99 if opts.range.is_empty() {
100 return Err(Box::new(ErrSparklineRange));
101 }
102 if opts.location.len() != opts.range.len() {
103 return Err(Box::new(ErrSparkline));
104 }
105 if opts.style < 0 || opts.style > 35 {
106 return Err(Box::new(ErrSparklineStyle));
107 }
108 if ws.ext_lst.is_none() {
109 ws.ext_lst = Some(XlsxExtLst::default());
110 }
111 Ok(ws)
112 }
113
114 fn append_sparkline_group(
116 &self,
117 ws: &mut XlsxWorksheet,
118 group: &XlsxX14SparklineGroup,
119 ) -> Result<()> {
120 let new_group_xml = xml_to_string(group)?;
121 let mut append_mode = false;
122
123 let ext_lst = ws.ext_lst.as_mut().unwrap();
124 for ext in &mut ext_lst.ext {
125 if ext.uri.as_deref() == Some(EXT_URI_SPARKLINE_GROUPS) {
126 let new_content = if ext.content.contains("</x14:sparklineGroups>") {
127 ext.content.replacen(
128 "</x14:sparklineGroups>",
129 &format!("{new_group_xml}</x14:sparklineGroups>"),
130 1,
131 )
132 } else {
133 format!(
134 r#"<x14:sparklineGroups xmlns:xm="{}">{new_group_xml}</x14:sparklineGroups>"#,
135 NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN
136 )
137 };
138 ext.content = new_content;
139 append_mode = true;
140 }
141 }
142
143 if !append_mode {
144 let groups = XlsxX14SparklineGroups {
145 xmlns_xm: Some(NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN.to_string()),
146 sparkline_groups: vec![group.clone()],
147 ..Default::default()
148 };
149 let groups_xml = xml_to_string(&groups)?;
150 ext_lst.ext.push(XlsxExt {
151 uri: Some(EXT_URI_SPARKLINE_GROUPS.to_string()),
152 content: groups_xml,
153 ..Default::default()
154 });
155 }
156
157 ext_lst.ext.sort_by(|a, b| {
158 let ia = in_str_slice(
159 WORKSHEET_EXT_URI_PRIORITY,
160 a.uri.as_deref().unwrap_or(""),
161 false,
162 );
163 let ib = in_str_slice(
164 WORKSHEET_EXT_URI_PRIORITY,
165 b.uri.as_deref().unwrap_or(""),
166 false,
167 );
168 ia.cmp(&ib)
169 });
170
171 Ok(())
172 }
173}
174
175fn add_sparkline_items(opts: &SparklineOptions, group: &mut XlsxX14SparklineGroup) {
177 for (location, range) in opts.location.iter().zip(opts.range.iter()) {
178 group.sparklines.sparkline.push(XlsxX14Sparkline {
179 f: range.clone(),
180 sqref: location.clone(),
181 });
182 }
183}
184
185fn theme_color(theme: i64, tint: Option<f64>) -> Option<XlsxColor> {
187 Some(XlsxColor {
188 theme: int_ptr(theme),
189 tint,
190 ..Default::default()
191 })
192}
193
194fn rgb_color(rgb: &str) -> Option<XlsxColor> {
196 Some(XlsxColor {
197 rgb: Some(rgb.to_string()),
198 ..Default::default()
199 })
200}
201
202#[allow(clippy::too_many_lines)]
204fn get_sparkline_group_presets() -> Vec<XlsxX14SparklineGroup> {
205 vec![
206 XlsxX14SparklineGroup {
207 color_series: theme_color(4, Some(-0.499984740745262)),
208 color_negative: theme_color(5, None),
209 color_markers: theme_color(4, Some(-0.499984740745262)),
210 color_first: theme_color(4, Some(0.39997558519241921)),
211 color_last: theme_color(4, Some(0.39997558519241921)),
212 color_high: theme_color(4, None),
213 color_low: theme_color(4, None),
214 ..Default::default()
215 }, XlsxX14SparklineGroup {
217 color_series: theme_color(4, Some(-0.499984740745262)),
218 color_negative: theme_color(5, None),
219 color_markers: theme_color(4, Some(-0.499984740745262)),
220 color_first: theme_color(4, Some(0.39997558519241921)),
221 color_last: theme_color(4, Some(0.39997558519241921)),
222 color_high: theme_color(4, None),
223 color_low: theme_color(4, None),
224 ..Default::default()
225 }, XlsxX14SparklineGroup {
227 color_series: theme_color(5, Some(-0.499984740745262)),
228 color_negative: theme_color(6, None),
229 color_markers: theme_color(5, Some(-0.499984740745262)),
230 color_first: theme_color(5, Some(0.39997558519241921)),
231 color_last: theme_color(5, Some(0.39997558519241921)),
232 color_high: theme_color(5, None),
233 color_low: theme_color(5, None),
234 ..Default::default()
235 }, XlsxX14SparklineGroup {
237 color_series: theme_color(6, Some(-0.499984740745262)),
238 color_negative: theme_color(7, None),
239 color_markers: theme_color(6, Some(-0.499984740745262)),
240 color_first: theme_color(6, Some(0.39997558519241921)),
241 color_last: theme_color(6, Some(0.39997558519241921)),
242 color_high: theme_color(6, None),
243 color_low: theme_color(6, None),
244 ..Default::default()
245 }, XlsxX14SparklineGroup {
247 color_series: theme_color(7, Some(-0.499984740745262)),
248 color_negative: theme_color(8, None),
249 color_markers: theme_color(7, Some(-0.499984740745262)),
250 color_first: theme_color(7, Some(0.39997558519241921)),
251 color_last: theme_color(7, Some(0.39997558519241921)),
252 color_high: theme_color(7, None),
253 color_low: theme_color(7, None),
254 ..Default::default()
255 }, XlsxX14SparklineGroup {
257 color_series: theme_color(8, Some(-0.499984740745262)),
258 color_negative: theme_color(9, None),
259 color_markers: theme_color(8, Some(-0.499984740745262)),
260 color_first: theme_color(8, Some(0.39997558519241921)),
261 color_last: theme_color(8, Some(0.39997558519241921)),
262 color_high: theme_color(8, None),
263 color_low: theme_color(8, None),
264 ..Default::default()
265 }, XlsxX14SparklineGroup {
267 color_series: theme_color(9, Some(-0.499984740745262)),
268 color_negative: theme_color(4, None),
269 color_markers: theme_color(9, Some(-0.499984740745262)),
270 color_first: theme_color(9, Some(0.39997558519241921)),
271 color_last: theme_color(9, Some(0.39997558519241921)),
272 color_high: theme_color(9, None),
273 color_low: theme_color(9, None),
274 ..Default::default()
275 }, XlsxX14SparklineGroup {
277 color_series: theme_color(4, Some(-0.249977111117893)),
278 color_negative: theme_color(5, None),
279 color_markers: theme_color(5, Some(-0.249977111117893)),
280 color_first: theme_color(5, Some(-0.249977111117893)),
281 color_last: theme_color(5, Some(-0.249977111117893)),
282 color_high: theme_color(5, None),
283 color_low: theme_color(5, None),
284 ..Default::default()
285 }, XlsxX14SparklineGroup {
287 color_series: theme_color(5, Some(-0.249977111117893)),
288 color_negative: theme_color(6, None),
289 color_markers: theme_color(6, Some(-0.249977111117893)),
290 color_first: theme_color(6, Some(-0.249977111117893)),
291 color_last: theme_color(6, Some(-0.249977111117893)),
292 color_high: theme_color(6, Some(-0.249977111117893)),
293 color_low: theme_color(6, Some(-0.249977111117893)),
294 ..Default::default()
295 }, XlsxX14SparklineGroup {
297 color_series: theme_color(6, Some(-0.249977111117893)),
298 color_negative: theme_color(7, None),
299 color_markers: theme_color(7, Some(-0.249977111117893)),
300 color_first: theme_color(7, Some(-0.249977111117893)),
301 color_last: theme_color(7, Some(-0.249977111117893)),
302 color_high: theme_color(7, Some(-0.249977111117893)),
303 color_low: theme_color(7, Some(-0.249977111117893)),
304 ..Default::default()
305 }, XlsxX14SparklineGroup {
307 color_series: theme_color(7, Some(-0.249977111117893)),
308 color_negative: theme_color(8, None),
309 color_markers: theme_color(8, Some(-0.249977111117893)),
310 color_first: theme_color(8, Some(-0.249977111117893)),
311 color_last: theme_color(8, Some(-0.249977111117893)),
312 color_high: theme_color(8, Some(-0.249977111117893)),
313 color_low: theme_color(8, Some(-0.249977111117893)),
314 ..Default::default()
315 }, XlsxX14SparklineGroup {
317 color_series: theme_color(8, Some(-0.249977111117893)),
318 color_negative: theme_color(9, None),
319 color_markers: theme_color(9, Some(-0.249977111117893)),
320 color_first: theme_color(9, Some(-0.249977111117893)),
321 color_last: theme_color(9, Some(-0.249977111117893)),
322 color_high: theme_color(9, Some(-0.249977111117893)),
323 color_low: theme_color(9, Some(-0.249977111117893)),
324 ..Default::default()
325 }, XlsxX14SparklineGroup {
327 color_series: theme_color(9, Some(-0.249977111117893)),
328 color_negative: theme_color(4, None),
329 color_markers: theme_color(4, Some(-0.249977111117893)),
330 color_first: theme_color(4, Some(-0.249977111117893)),
331 color_last: theme_color(4, Some(-0.249977111117893)),
332 color_high: theme_color(4, Some(-0.249977111117893)),
333 color_low: theme_color(4, Some(-0.249977111117893)),
334 ..Default::default()
335 }, XlsxX14SparklineGroup {
337 color_series: theme_color(4, None),
338 color_negative: theme_color(5, None),
339 color_markers: theme_color(4, Some(-0.249977111117893)),
340 color_first: theme_color(4, Some(-0.249977111117893)),
341 color_last: theme_color(4, Some(-0.249977111117893)),
342 color_high: theme_color(4, Some(-0.249977111117893)),
343 color_low: theme_color(4, Some(-0.249977111117893)),
344 ..Default::default()
345 }, XlsxX14SparklineGroup {
347 color_series: theme_color(5, None),
348 color_negative: theme_color(6, None),
349 color_markers: theme_color(5, Some(-0.249977111117893)),
350 color_first: theme_color(5, Some(-0.249977111117893)),
351 color_last: theme_color(5, Some(-0.249977111117893)),
352 color_high: theme_color(5, Some(-0.249977111117893)),
353 color_low: theme_color(5, Some(-0.249977111117893)),
354 ..Default::default()
355 }, XlsxX14SparklineGroup {
357 color_series: theme_color(6, None),
358 color_negative: theme_color(7, None),
359 color_markers: theme_color(6, Some(-0.249977111117893)),
360 color_first: theme_color(6, Some(-0.249977111117893)),
361 color_last: theme_color(6, Some(-0.249977111117893)),
362 color_high: theme_color(6, Some(-0.249977111117893)),
363 color_low: theme_color(6, Some(-0.249977111117893)),
364 ..Default::default()
365 }, XlsxX14SparklineGroup {
367 color_series: theme_color(7, None),
368 color_negative: theme_color(8, None),
369 color_markers: theme_color(7, Some(-0.249977111117893)),
370 color_first: theme_color(7, Some(-0.249977111117893)),
371 color_last: theme_color(7, Some(-0.249977111117893)),
372 color_high: theme_color(7, Some(-0.249977111117893)),
373 color_low: theme_color(7, Some(-0.249977111117893)),
374 ..Default::default()
375 }, XlsxX14SparklineGroup {
377 color_series: theme_color(8, None),
378 color_negative: theme_color(9, None),
379 color_markers: theme_color(8, Some(-0.249977111117893)),
380 color_first: theme_color(8, Some(-0.249977111117893)),
381 color_last: theme_color(8, Some(-0.249977111117893)),
382 color_high: theme_color(8, Some(-0.249977111117893)),
383 color_low: theme_color(8, Some(-0.249977111117893)),
384 ..Default::default()
385 }, XlsxX14SparklineGroup {
387 color_series: theme_color(9, None),
388 color_negative: theme_color(4, None),
389 color_markers: theme_color(9, Some(-0.249977111117893)),
390 color_first: theme_color(9, Some(-0.249977111117893)),
391 color_last: theme_color(9, Some(-0.249977111117893)),
392 color_high: theme_color(9, Some(-0.249977111117893)),
393 color_low: theme_color(9, Some(-0.249977111117893)),
394 ..Default::default()
395 }, XlsxX14SparklineGroup {
397 color_series: theme_color(4, Some(0.39997558519241921)),
398 color_negative: theme_color(0, Some(-0.499984740745262)),
399 color_markers: theme_color(4, Some(0.79998168889431442)),
400 color_first: theme_color(4, Some(-0.249977111117893)),
401 color_last: theme_color(4, Some(-0.249977111117893)),
402 color_high: theme_color(4, Some(-0.499984740745262)),
403 color_low: theme_color(4, Some(-0.499984740745262)),
404 ..Default::default()
405 }, XlsxX14SparklineGroup {
407 color_series: theme_color(5, Some(0.39997558519241921)),
408 color_negative: theme_color(0, Some(-0.499984740745262)),
409 color_markers: theme_color(5, Some(0.79998168889431442)),
410 color_first: theme_color(5, Some(-0.249977111117893)),
411 color_last: theme_color(5, Some(-0.249977111117893)),
412 color_high: theme_color(5, Some(-0.499984740745262)),
413 color_low: theme_color(5, Some(-0.499984740745262)),
414 ..Default::default()
415 }, XlsxX14SparklineGroup {
417 color_series: theme_color(6, Some(0.39997558519241921)),
418 color_negative: theme_color(0, Some(-0.499984740745262)),
419 color_markers: theme_color(6, Some(0.79998168889431442)),
420 color_first: theme_color(6, Some(-0.249977111117893)),
421 color_last: theme_color(6, Some(-0.249977111117893)),
422 color_high: theme_color(6, Some(-0.499984740745262)),
423 color_low: theme_color(6, Some(-0.499984740745262)),
424 ..Default::default()
425 }, XlsxX14SparklineGroup {
427 color_series: theme_color(7, Some(0.39997558519241921)),
428 color_negative: theme_color(0, Some(-0.499984740745262)),
429 color_markers: theme_color(7, Some(0.79998168889431442)),
430 color_first: theme_color(7, Some(-0.249977111117893)),
431 color_last: theme_color(7, Some(-0.249977111117893)),
432 color_high: theme_color(7, Some(-0.499984740745262)),
433 color_low: theme_color(7, Some(-0.499984740745262)),
434 ..Default::default()
435 }, XlsxX14SparklineGroup {
437 color_series: theme_color(8, Some(0.39997558519241921)),
438 color_negative: theme_color(0, Some(-0.499984740745262)),
439 color_markers: theme_color(8, Some(0.79998168889431442)),
440 color_first: theme_color(8, Some(-0.249977111117893)),
441 color_last: theme_color(8, Some(-0.249977111117893)),
442 color_high: theme_color(8, Some(-0.499984740745262)),
443 color_low: theme_color(8, Some(-0.499984740745262)),
444 ..Default::default()
445 }, XlsxX14SparklineGroup {
447 color_series: theme_color(9, Some(0.39997558519241921)),
448 color_negative: theme_color(0, Some(-0.499984740745262)),
449 color_markers: theme_color(9, Some(0.79998168889431442)),
450 color_first: theme_color(9, Some(-0.249977111117893)),
451 color_last: theme_color(9, Some(-0.249977111117893)),
452 color_high: theme_color(9, Some(-0.499984740745262)),
453 color_low: theme_color(9, Some(-0.499984740745262)),
454 ..Default::default()
455 }, XlsxX14SparklineGroup {
457 color_series: theme_color(1, Some(0.499984740745262)),
458 color_negative: theme_color(1, Some(0.249977111117893)),
459 color_markers: theme_color(1, Some(0.249977111117893)),
460 color_first: theme_color(1, Some(0.249977111117893)),
461 color_last: theme_color(1, Some(0.249977111117893)),
462 color_high: theme_color(1, Some(0.249977111117893)),
463 color_low: theme_color(1, Some(0.249977111117893)),
464 ..Default::default()
465 }, XlsxX14SparklineGroup {
467 color_series: theme_color(1, Some(0.34998626667073579)),
468 color_negative: theme_color(0, Some(0.249977111117893)),
469 color_markers: theme_color(0, Some(0.249977111117893)),
470 color_first: theme_color(0, Some(0.249977111117893)),
471 color_last: theme_color(0, Some(0.249977111117893)),
472 color_high: theme_color(0, Some(0.249977111117893)),
473 color_low: theme_color(0, Some(0.249977111117893)),
474 ..Default::default()
475 }, XlsxX14SparklineGroup {
477 color_series: rgb_color("FF323232"),
478 color_negative: rgb_color("FFD00000"),
479 color_markers: rgb_color("FFD00000"),
480 color_first: rgb_color("FFD00000"),
481 color_last: rgb_color("FFD00000"),
482 color_high: rgb_color("FFD00000"),
483 color_low: rgb_color("FFD00000"),
484 ..Default::default()
485 }, XlsxX14SparklineGroup {
487 color_series: rgb_color("FF000000"),
488 color_negative: rgb_color("FF0070C0"),
489 color_markers: rgb_color("FF0070C0"),
490 color_first: rgb_color("FF0070C0"),
491 color_last: rgb_color("FF0070C0"),
492 color_high: rgb_color("FF0070C0"),
493 color_low: rgb_color("FF0070C0"),
494 ..Default::default()
495 }, XlsxX14SparklineGroup {
497 color_series: rgb_color("FF376092"),
498 color_negative: rgb_color("FFD00000"),
499 color_markers: rgb_color("FFD00000"),
500 color_first: rgb_color("FFD00000"),
501 color_last: rgb_color("FFD00000"),
502 color_high: rgb_color("FFD00000"),
503 color_low: rgb_color("FFD00000"),
504 ..Default::default()
505 }, XlsxX14SparklineGroup {
507 color_series: rgb_color("FF0070C0"),
508 color_negative: rgb_color("FF000000"),
509 color_markers: rgb_color("FF000000"),
510 color_first: rgb_color("FF000000"),
511 color_last: rgb_color("FF000000"),
512 color_high: rgb_color("FF000000"),
513 color_low: rgb_color("FF000000"),
514 ..Default::default()
515 }, XlsxX14SparklineGroup {
517 color_series: rgb_color("FF5F5F5F"),
518 color_negative: rgb_color("FFFFB620"),
519 color_markers: rgb_color("FFD70077"),
520 color_first: rgb_color("FF5687C2"),
521 color_last: rgb_color("FF359CEB"),
522 color_high: rgb_color("FF56BE79"),
523 color_low: rgb_color("FFFF5055"),
524 ..Default::default()
525 }, XlsxX14SparklineGroup {
527 color_series: rgb_color("FF5687C2"),
528 color_negative: rgb_color("FFFFB620"),
529 color_markers: rgb_color("FFD70077"),
530 color_first: rgb_color("FF777777"),
531 color_last: rgb_color("FF359CEB"),
532 color_high: rgb_color("FF56BE79"),
533 color_low: rgb_color("FFFF5055"),
534 ..Default::default()
535 }, XlsxX14SparklineGroup {
537 color_series: rgb_color("FFC6EFCE"),
538 color_negative: rgb_color("FFFFC7CE"),
539 color_markers: rgb_color("FF8CADD6"),
540 color_first: rgb_color("FFFFDC47"),
541 color_last: rgb_color("FFFFEB9C"),
542 color_high: rgb_color("FF60D276"),
543 color_low: rgb_color("FFFF5367"),
544 ..Default::default()
545 }, XlsxX14SparklineGroup {
547 color_series: rgb_color("FF00B050"),
548 color_negative: rgb_color("FFFF0000"),
549 color_markers: rgb_color("FF0070C0"),
550 color_first: rgb_color("FFFFC000"),
551 color_last: rgb_color("FFFFC000"),
552 color_high: rgb_color("FF00B050"),
553 color_low: rgb_color("FFFF0000"),
554 ..Default::default()
555 }, XlsxX14SparklineGroup {
557 color_series: theme_color(3, None),
558 color_negative: theme_color(9, None),
559 color_markers: theme_color(8, None),
560 color_first: theme_color(4, None),
561 color_last: theme_color(5, None),
562 color_high: theme_color(6, None),
563 color_low: theme_color(7, None),
564 ..Default::default()
565 }, XlsxX14SparklineGroup {
567 color_series: theme_color(1, None),
568 color_negative: theme_color(9, None),
569 color_markers: theme_color(8, None),
570 color_first: theme_color(4, None),
571 color_last: theme_color(5, None),
572 color_high: theme_color(6, None),
573 color_low: theme_color(7, None),
574 ..Default::default()
575 }, ]
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use crate::options::Options;
583 use crate::xml::common::serialize_ext_lst;
584
585 #[test]
586 fn add_sparkline_basic() {
587 let f = File::new_with_options(Options::default());
588 f.add_sparkline(
589 "Sheet1",
590 &SparklineOptions {
591 location: vec!["A2".to_string()],
592 range: vec!["Sheet2!A1:J1".to_string()],
593 ..Default::default()
594 },
595 )
596 .unwrap();
597
598 let ws = f.work_sheet_reader("Sheet1").unwrap();
599 let ext_lst = ws.ext_lst.as_ref().unwrap();
600 assert_eq!(ext_lst.ext.len(), 1);
601 assert_eq!(
602 ext_lst.ext[0].uri.as_deref(),
603 Some(EXT_URI_SPARKLINE_GROUPS)
604 );
605 assert!(ext_lst.ext[0].content.contains("x14:sparklineGroup"));
606 assert!(ext_lst.ext[0].content.contains("Sheet2!A1:J1"));
607 assert!(ext_lst.ext[0].content.contains("A2"));
608 }
609
610 #[test]
611 fn add_sparkline_grouped() {
612 let f = File::new_with_options(Options::default());
613 f.add_sparkline(
614 "Sheet1",
615 &SparklineOptions {
616 location: vec!["A27".to_string(), "A28".to_string(), "A29".to_string()],
617 range: vec![
618 "Sheet3!A5:J5".to_string(),
619 "Sheet3!A6:J6".to_string(),
620 "Sheet3!A7:J7".to_string(),
621 ],
622 markers: true,
623 ..Default::default()
624 },
625 )
626 .unwrap();
627
628 let ws = f.work_sheet_reader("Sheet1").unwrap();
629 let ext_lst = ws.ext_lst.as_ref().unwrap();
630 assert_eq!(ext_lst.ext.len(), 1);
631 let content = &ext_lst.ext[0].content;
632 assert_eq!(content.matches("</x14:sparklineGroup>").count(), 1);
633 assert_eq!(content.matches("<x14:sparkline>").count(), 3);
634 }
635
636 #[test]
637 fn add_sparkline_invalid_type() {
638 let f = File::new_with_options(Options::default());
639 let err = f
640 .add_sparkline(
641 "Sheet1",
642 &SparklineOptions {
643 location: vec!["A1".to_string()],
644 range: vec!["Sheet2!A1:J1".to_string()],
645 r#type: "unknown_type".to_string(),
646 ..Default::default()
647 },
648 )
649 .unwrap_err();
650 assert!(err.to_string().contains("'Type' value must be one of"));
651 }
652
653 #[test]
654 fn add_sparkline_invalid_style() {
655 let f = File::new_with_options(Options::default());
656 let err = f
657 .add_sparkline(
658 "Sheet1",
659 &SparklineOptions {
660 location: vec!["A1".to_string()],
661 range: vec!["Sheet2!A1:J1".to_string()],
662 style: -1,
663 ..Default::default()
664 },
665 )
666 .unwrap_err();
667 assert!(err.to_string().contains("'Style' value must be an integer"));
668 }
669
670 #[test]
671 fn add_sparkline_mismatched_location_range() {
672 let f = File::new_with_options(Options::default());
673 let err = f
674 .add_sparkline(
675 "Sheet1",
676 &SparklineOptions {
677 location: vec!["A1".to_string(), "A2".to_string()],
678 range: vec!["Sheet2!A1:J1".to_string()],
679 ..Default::default()
680 },
681 )
682 .unwrap_err();
683 assert!(err.to_string().contains("Location") && err.to_string().contains("Range"));
684 }
685
686 #[test]
687 fn add_sparkline_append_mode() {
688 let f = File::new_with_options(Options::default());
689 f.add_sparkline(
690 "Sheet1",
691 &SparklineOptions {
692 location: vec!["A2".to_string()],
693 range: vec!["Sheet2!A1:J1".to_string()],
694 ..Default::default()
695 },
696 )
697 .unwrap();
698 f.add_sparkline(
699 "Sheet1",
700 &SparklineOptions {
701 location: vec!["A3".to_string()],
702 range: vec!["Sheet2!A2:J2".to_string()],
703 r#type: "column".to_string(),
704 ..Default::default()
705 },
706 )
707 .unwrap();
708
709 let ws = f.work_sheet_reader("Sheet1").unwrap();
710 let ext_lst = ws.ext_lst.as_ref().unwrap();
711 assert_eq!(ext_lst.ext.len(), 1);
712 let content = &ext_lst.ext[0].content;
713 assert_eq!(content.matches("</x14:sparklineGroup>").count(), 2);
714 }
715
716 #[test]
717 fn add_sparkline_with_existing_ext() {
718 let f = File::new_with_options(Options::default());
719 let path = f.get_sheet_xml_path("Sheet1").unwrap();
720 let mut ws = f.work_sheet_reader("Sheet1").unwrap();
721 ws.ext_lst = Some(XlsxExtLst {
722 ext: vec![
723 XlsxExt {
724 uri: Some("{A8765BA9-456A-4dab-B4F3-ACF838C121DE}".to_string()),
725 content: "<x14:slicerList />".to_string(),
726 ..Default::default()
727 },
728 XlsxExt {
729 uri: Some(EXT_URI_SPARKLINE_GROUPS.to_string()),
730 content: format!(
731 r#"<x14:sparklineGroups xmlns:xm="{}"></x14:sparklineGroups>"#,
732 NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN
733 ),
734 ..Default::default()
735 },
736 ],
737 });
738 f.sheet.insert(path, ws);
739
740 f.add_sparkline(
741 "Sheet1",
742 &SparklineOptions {
743 location: vec!["A3".to_string()],
744 range: vec!["Sheet2!A2:J2".to_string()],
745 r#type: "column".to_string(),
746 ..Default::default()
747 },
748 )
749 .unwrap();
750
751 let ws = f.work_sheet_reader("Sheet1").unwrap();
752 let ext_lst = ws.ext_lst.as_ref().unwrap();
753 assert_eq!(ext_lst.ext.len(), 2);
754 assert_eq!(
756 ext_lst.ext[0].uri.as_deref(),
757 Some(EXT_URI_SPARKLINE_GROUPS)
758 );
759 assert_eq!(
760 ext_lst.ext[1].uri.as_deref(),
761 Some("{A8765BA9-456A-4dab-B4F3-ACF838C121DE}")
762 );
763 assert!(ext_lst.ext[0].content.contains("x14:sparklineGroup"));
764 }
765
766 #[test]
767 fn sparkline_presets_count() {
768 assert_eq!(get_sparkline_group_presets().len(), 37);
771 }
772
773 #[test]
774 fn serialize_ext_lst_round_trip() {
775 let ext_lst = XlsxExtLst {
776 ext: vec![XlsxExt {
777 uri: Some(EXT_URI_SPARKLINE_GROUPS.to_string()),
778 content: format!(
779 r#"<x14:sparklineGroups xmlns:xm="{}"><x14:sparklineGroup type="line"><x14:sparklines><x14:sparkline><xm:f>Sheet2!A1:J1</xm:f><xm:sqref>A2</xm:sqref></x14:sparkline></x14:sparklines></x14:sparklineGroup></x14:sparklineGroups>"#,
780 NAMESPACE_SPREADSHEET_EXCEL_2006_MAIN
781 ),
782 ..Default::default()
783 }],
784 };
785 let xml = serialize_ext_lst(&ext_lst);
786 assert!(xml.contains("x14:sparklineGroup"));
787 assert!(xml.contains("Sheet2!A1:J1"));
788 }
789
790 #[test]
791 fn save_sparkline_writes_valid_xml() {
792 let mut f = File::new_with_options(Options::default());
793 f.add_sparkline(
794 "Sheet1",
795 &SparklineOptions {
796 location: vec!["A2".to_string()],
797 range: vec!["Sheet2!A1:J1".to_string()],
798 markers: true,
799 ..Default::default()
800 },
801 )
802 .unwrap();
803
804 let tmp = std::env::temp_dir().join("excelize_rust_sparkline_test.xlsx");
805 f.save_as(tmp.to_str().unwrap()).unwrap();
806
807 let file = std::fs::File::open(&tmp).unwrap();
808 let mut archive = zip::ZipArchive::new(file).unwrap();
809 let mut sheet = archive.by_name("xl/worksheets/sheet1.xml").unwrap();
810 let mut xml = String::new();
811 std::io::Read::read_to_string(&mut sheet, &mut xml).unwrap();
812 drop(sheet);
813 drop(archive);
814 let _ = std::fs::remove_file(&tmp);
815
816 assert!(xml.contains("x14:sparklineGroups"));
817 assert!(xml.contains("x14:sparklineGroup"));
818 assert!(xml.contains("Sheet2!A1:J1"));
819 assert!(xml.contains("A2"));
820 assert!(xml.contains("xmlns:x14"));
821 }
822}