Skip to main content

chart/
reader.rs

1//! Reading chart XML back into this crate's model.
2//!
3//! [`read_chart_space`] takes the **entire** chart part's XML text (the same shape of input
4//! `word-ooxml`/`excel-ooxml`'s own top-level `parse_document_xml`/`parse_workbook_xml`-style
5//! functions take) and returns a [`crate::ChartSpace`] — the counterpart to
6//! [`crate::write_chart_space`] writing the whole part. Every other function here parses one piece
7//! of that tree from a reader already positioned right after the relevant element's own opening tag
8//! was consumed by its caller, the same fragment-reading contract `drawing` uses.
9//!
10//! Unrecognized children (every deferred chart type/axis/feature listed in `model.rs`'s doc
11//! comments) are skipped by depth-tracking their subtree rather than causing an error — the same
12//! forgiving-reader posture used throughout this workspace's other crates.
13
14use xml_core::{BytesStart, Event, Reader};
15
16use crate::error::{Error, Result};
17use crate::model::{
18    Area3DChart, AreaChart, AreaSeries, Axis, AxisDataSource, AxisPosition, Bar3DChart, Bar3DShape,
19    BarChart, BarDirection, BarGrouping, BarSeries, CategoryAxis, Chart, ChartSpace, ChartType,
20    CrossBetween, DisplayBlanksAs, DoughnutChart, Legend, LegendPosition, Line3DChart, LineChart,
21    LineGrouping, LineSeries, Marker, MarkerSymbol, NumericData, NumericDataSource, NumericPoint,
22    Pie3DChart, PieChart, PieSeries, PlotArea, ScatterChart, ScatterSeries, ScatterStyle,
23    SeriesTitle, StringData, StringDataSource, StringPoint, Surface, Surface3DChart, SurfaceSeries,
24    Title, TitleText, ValueAxis, View3D,
25};
26
27/// Reads a whole chart part's XML text and returns its [`ChartSpace`].
28pub fn read_chart_space(xml: &str) -> Result<ChartSpace> {
29    let mut reader = Reader::from_xml_str(xml);
30    loop {
31        match reader.read_event()? {
32            Event::Start(start) if start.local_name().as_ref() == b"chartSpace" => {
33                return parse_chart_space(&mut reader);
34            }
35            Event::Eof => return Err(Error::MissingChartSpace),
36            _ => {}
37        }
38    }
39}
40
41fn parse_chart_space(reader: &mut Reader) -> Result<ChartSpace> {
42    let mut chart = None;
43    let mut shape_properties = None;
44
45    loop {
46        match reader.read_event()? {
47            Event::Start(start) if start.local_name().as_ref() == b"chart" => {
48                chart = Some(parse_chart(reader)?);
49            }
50            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
51                shape_properties = Some(drawing::read_shape_properties(reader)?);
52            }
53            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
54                shape_properties = Some(drawing::ShapeProperties::new());
55            }
56            // `date1904`, `lang`, `roundedCorners`, `style`, `clrMapOvr`, `pivotSource`,
57            // `protection`, `txPr`, `externalData`, `printSettings`, `userShapes`, `extLst` — not
58            // modeled.
59            Event::Start(_) => skip_subtree(reader)?,
60            Event::Empty(_) => {}
61            Event::End(end) if end.local_name().as_ref() == b"chartSpace" => break,
62            Event::Eof => break,
63            _ => {}
64        }
65    }
66
67    let chart = chart.unwrap_or_else(|| Chart::new(PlotArea::new()));
68    let mut chart_space = ChartSpace::new(chart);
69    chart_space.shape_properties = shape_properties;
70    Ok(chart_space)
71}
72
73/// Skips an already-opened element's entire subtree, including its own closing tag.
74fn skip_subtree(reader: &mut Reader) -> Result<()> {
75    let mut depth: u32 = 0;
76    loop {
77        match reader.read_event()? {
78            Event::Start(_) => depth += 1,
79            Event::End(_) => {
80                if depth == 0 {
81                    return Ok(());
82                }
83                depth -= 1;
84            }
85            Event::Eof => return Ok(()),
86            _ => {}
87        }
88    }
89}
90
91fn raw_attribute(start: &BytesStart, name: &[u8]) -> Option<String> {
92    start
93        .attributes()
94        .flatten()
95        .find(|attribute| attribute.key.local_name().as_ref() == name)
96        .and_then(|attribute| {
97            xml_core::decode_attribute_value(attribute.value.as_ref())
98                .ok()
99                .flatten()
100        })
101}
102
103fn parse_bool_val(start: &BytesStart) -> Option<bool> {
104    raw_attribute(start, b"val").map(|value| value == "1" || value == "true")
105}
106
107/// Reads the text content of a simple leaf element (`<c:f>..</c:f>`, `<c:v>..</c:v>`,
108/// `<c:formatCode>..</c:formatCode>`) — assumes the element's own `Start` was just consumed by the
109/// caller and consumes up to and including its matching `End`.
110fn read_text_content(reader: &mut Reader, end_local_name: &[u8]) -> Result<String> {
111    let mut text = String::new();
112    loop {
113        match reader.read_event()? {
114            Event::Text(content) => text.push_str(&xml_core::decode_text(&content)?),
115            Event::GeneralRef(reference) => {
116                if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
117                    text.push_str(&resolved);
118                }
119            }
120            Event::End(end) if end.local_name().as_ref() == end_local_name => break,
121            Event::Eof => break,
122            _ => {}
123        }
124    }
125    Ok(text)
126}
127
128/// Parses a percentage attribute value (`"150%"`, `"-10%"`) into its integer value, tolerating a
129/// missing `%` suffix too.
130fn parse_percent(value: &str) -> Option<i32> {
131    value.strip_suffix('%').unwrap_or(value).parse().ok()
132}
133
134fn parse_chart(reader: &mut Reader) -> Result<Chart> {
135    let mut title = None;
136    let mut auto_title_deleted = None;
137    let mut view_3d = None;
138    let mut floor = None;
139    let mut side_wall = None;
140    let mut back_wall = None;
141    let mut plot_area = None;
142    let mut legend = None;
143    let mut plot_visible_only = None;
144    let mut display_blanks_as = None;
145
146    loop {
147        match reader.read_event()? {
148            Event::Start(start) if start.local_name().as_ref() == b"title" => {
149                title = Some(parse_title(reader)?);
150            }
151            Event::Empty(start) if start.local_name().as_ref() == b"title" => {
152                title = Some(Title::new());
153            }
154
155            Event::Empty(child) if child.local_name().as_ref() == b"autoTitleDeleted" => {
156                auto_title_deleted = parse_bool_val(&child);
157            }
158            Event::Start(child) if child.local_name().as_ref() == b"autoTitleDeleted" => {
159                auto_title_deleted = parse_bool_val(&child);
160                skip_subtree(reader)?;
161            }
162
163            Event::Start(start) if start.local_name().as_ref() == b"view3D" => {
164                view_3d = Some(parse_view_3d(reader)?);
165            }
166            Event::Empty(start) if start.local_name().as_ref() == b"view3D" => {
167                view_3d = Some(View3D::new());
168            }
169
170            Event::Start(start) if start.local_name().as_ref() == b"floor" => {
171                floor = Some(parse_surface(reader)?);
172            }
173            Event::Empty(start) if start.local_name().as_ref() == b"floor" => {
174                floor = Some(Surface::new());
175            }
176            Event::Start(start) if start.local_name().as_ref() == b"sideWall" => {
177                side_wall = Some(parse_surface(reader)?);
178            }
179            Event::Empty(start) if start.local_name().as_ref() == b"sideWall" => {
180                side_wall = Some(Surface::new());
181            }
182            Event::Start(start) if start.local_name().as_ref() == b"backWall" => {
183                back_wall = Some(parse_surface(reader)?);
184            }
185            Event::Empty(start) if start.local_name().as_ref() == b"backWall" => {
186                back_wall = Some(Surface::new());
187            }
188
189            Event::Start(start) if start.local_name().as_ref() == b"plotArea" => {
190                plot_area = Some(parse_plot_area(reader)?);
191            }
192
193            Event::Start(start) if start.local_name().as_ref() == b"legend" => {
194                legend = Some(parse_legend(reader)?);
195            }
196            Event::Empty(start) if start.local_name().as_ref() == b"legend" => {
197                legend = Some(Legend::new());
198            }
199
200            Event::Empty(child) if child.local_name().as_ref() == b"plotVisOnly" => {
201                plot_visible_only = parse_bool_val(&child);
202            }
203            Event::Start(child) if child.local_name().as_ref() == b"plotVisOnly" => {
204                plot_visible_only = parse_bool_val(&child);
205                skip_subtree(reader)?;
206            }
207
208            Event::Empty(child) if child.local_name().as_ref() == b"dispBlanksAs" => {
209                display_blanks_as =
210                    raw_attribute(&child, b"val").and_then(|v| DisplayBlanksAs::from_xml_token(&v));
211            }
212            Event::Start(child) if child.local_name().as_ref() == b"dispBlanksAs" => {
213                display_blanks_as =
214                    raw_attribute(&child, b"val").and_then(|v| DisplayBlanksAs::from_xml_token(&v));
215                skip_subtree(reader)?;
216            }
217
218            // `pivotFmts`, `showDLblsOverMax`, `extLst` — not modeled.
219            Event::Start(_) => skip_subtree(reader)?,
220            Event::Empty(_) => {}
221            Event::End(end) if end.local_name().as_ref() == b"chart" => break,
222            Event::Eof => break,
223            _ => {}
224        }
225    }
226
227    let mut chart = Chart::new(plot_area.unwrap_or_else(PlotArea::new));
228    chart.title = title;
229    chart.auto_title_deleted = auto_title_deleted;
230    chart.view_3d = view_3d;
231    chart.floor = floor;
232    chart.side_wall = side_wall;
233    chart.back_wall = back_wall;
234    chart.legend = legend;
235    chart.plot_visible_only = plot_visible_only;
236    chart.display_blanks_as = display_blanks_as;
237    Ok(chart)
238}
239
240fn parse_title(reader: &mut Reader) -> Result<Title> {
241    let mut title = Title::new();
242
243    loop {
244        match reader.read_event()? {
245            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
246                title.text = Some(parse_title_text(reader)?);
247            }
248
249            Event::Empty(child) if child.local_name().as_ref() == b"overlay" => {
250                title.overlay = parse_bool_val(&child);
251            }
252            Event::Start(child) if child.local_name().as_ref() == b"overlay" => {
253                title.overlay = parse_bool_val(&child);
254                skip_subtree(reader)?;
255            }
256
257            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
258                title.shape_properties = Some(drawing::read_shape_properties(reader)?);
259            }
260            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
261                title.shape_properties = Some(drawing::ShapeProperties::new());
262            }
263
264            // `<c:layout>`, `<c:txPr>`, `<c:extLst>` — not modeled (see `Title`'s doc comment for
265            // why `txPr` specifically is deferred everywhere in this crate).
266            Event::Start(_) => skip_subtree(reader)?,
267            Event::Empty(_) => {}
268            Event::End(_) | Event::Eof => break,
269            _ => {}
270        }
271    }
272
273    Ok(title)
274}
275
276fn parse_title_text(reader: &mut Reader) -> Result<TitleText> {
277    let mut result = None;
278
279    loop {
280        match reader.read_event()? {
281            Event::Start(start) if start.local_name().as_ref() == b"strRef" => {
282                let (formula, cache) = parse_simple_string_reference(reader)?;
283                result = Some(TitleText::Reference { formula, cache });
284            }
285            Event::Start(start) if start.local_name().as_ref() == b"rich" => {
286                result = Some(TitleText::Rich(drawing::read_text_body(reader)?));
287            }
288            Event::Start(_) => skip_subtree(reader)?,
289            Event::End(end) if end.local_name().as_ref() == b"tx" => break,
290            Event::Eof => break,
291            _ => {}
292        }
293    }
294
295    Ok(result.unwrap_or_else(|| TitleText::Rich(drawing::TextBody::new())))
296}
297
298/// Parses `<c:strRef><c:f>..</c:f>[<c:strCache>..</c:strCache>]</c:strRef>`, simplified to a single
299/// cached value (see [`crate::model::SeriesTitle`]'s doc comment) — assumes `<c:strRef>`'s own
300/// `Start` was just consumed by the caller.
301fn parse_simple_string_reference(reader: &mut Reader) -> Result<(String, Option<String>)> {
302    let mut formula = String::new();
303    let mut cache = None;
304
305    loop {
306        match reader.read_event()? {
307            Event::Start(start) if start.local_name().as_ref() == b"f" => {
308                formula = read_text_content(reader, b"f")?;
309            }
310            Event::Start(start) if start.local_name().as_ref() == b"strCache" => {
311                cache = parse_string_data(reader)?
312                    .points
313                    .into_iter()
314                    .next()
315                    .map(|point| point.value);
316            }
317            Event::Start(_) => skip_subtree(reader)?,
318            Event::End(end) if end.local_name().as_ref() == b"strRef" => break,
319            Event::Eof => break,
320            _ => {}
321        }
322    }
323
324    Ok((formula, cache))
325}
326
327fn parse_series_title(reader: &mut Reader) -> Result<SeriesTitle> {
328    let mut result = None;
329
330    loop {
331        match reader.read_event()? {
332            Event::Start(start) if start.local_name().as_ref() == b"strRef" => {
333                let (formula, cache) = parse_simple_string_reference(reader)?;
334                result = Some(SeriesTitle::Reference { formula, cache });
335            }
336            Event::Start(start) if start.local_name().as_ref() == b"v" => {
337                result = Some(SeriesTitle::Literal(read_text_content(reader, b"v")?));
338            }
339            Event::Start(_) => skip_subtree(reader)?,
340            Event::End(end) if end.local_name().as_ref() == b"tx" => break,
341            Event::Eof => break,
342            _ => {}
343        }
344    }
345
346    Ok(result.unwrap_or_else(|| SeriesTitle::Literal(String::new())))
347}
348
349fn parse_legend(reader: &mut Reader) -> Result<Legend> {
350    let mut legend = Legend::new();
351
352    loop {
353        match reader.read_event()? {
354            Event::Empty(child) if child.local_name().as_ref() == b"legendPos" => {
355                legend.position =
356                    raw_attribute(&child, b"val").and_then(|v| LegendPosition::from_xml_token(&v));
357            }
358            Event::Start(child) if child.local_name().as_ref() == b"legendPos" => {
359                legend.position =
360                    raw_attribute(&child, b"val").and_then(|v| LegendPosition::from_xml_token(&v));
361                skip_subtree(reader)?;
362            }
363
364            Event::Empty(child) if child.local_name().as_ref() == b"overlay" => {
365                legend.overlay = parse_bool_val(&child);
366            }
367            Event::Start(child) if child.local_name().as_ref() == b"overlay" => {
368                legend.overlay = parse_bool_val(&child);
369                skip_subtree(reader)?;
370            }
371
372            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
373                legend.shape_properties = Some(drawing::read_shape_properties(reader)?);
374            }
375            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
376                legend.shape_properties = Some(drawing::ShapeProperties::new());
377            }
378
379            // `<c:legendEntry>`, `<c:layout>`, `<c:txPr>` — not modeled.
380            Event::Start(_) => skip_subtree(reader)?,
381            Event::Empty(_) => {}
382            Event::End(_) | Event::Eof => break,
383            _ => {}
384        }
385    }
386
387    Ok(legend)
388}
389
390fn parse_plot_area(reader: &mut Reader) -> Result<PlotArea> {
391    let mut plot_area = PlotArea::new();
392
393    loop {
394        match reader.read_event()? {
395            Event::Start(start) if start.local_name().as_ref() == b"barChart" => {
396                plot_area
397                    .charts
398                    .push(ChartType::Bar(parse_bar_chart(reader)?));
399            }
400            Event::Start(start) if start.local_name().as_ref() == b"lineChart" => {
401                plot_area
402                    .charts
403                    .push(ChartType::Line(parse_line_chart(reader)?));
404            }
405            Event::Start(start) if start.local_name().as_ref() == b"pieChart" => {
406                plot_area
407                    .charts
408                    .push(ChartType::Pie(parse_pie_chart(reader)?));
409            }
410            Event::Start(start) if start.local_name().as_ref() == b"doughnutChart" => {
411                plot_area
412                    .charts
413                    .push(ChartType::Doughnut(parse_doughnut_chart(reader)?));
414            }
415            Event::Start(start) if start.local_name().as_ref() == b"areaChart" => {
416                plot_area
417                    .charts
418                    .push(ChartType::Area(parse_area_chart(reader)?));
419            }
420            Event::Start(start) if start.local_name().as_ref() == b"scatterChart" => {
421                plot_area
422                    .charts
423                    .push(ChartType::Scatter(parse_scatter_chart(reader)?));
424            }
425            Event::Start(start) if start.local_name().as_ref() == b"bar3DChart" => {
426                plot_area
427                    .charts
428                    .push(ChartType::Bar3D(parse_bar3d_chart(reader)?));
429            }
430            Event::Start(start) if start.local_name().as_ref() == b"line3DChart" => {
431                plot_area
432                    .charts
433                    .push(ChartType::Line3D(parse_line3d_chart(reader)?));
434            }
435            Event::Start(start) if start.local_name().as_ref() == b"pie3DChart" => {
436                plot_area
437                    .charts
438                    .push(ChartType::Pie3D(parse_pie3d_chart(reader)?));
439            }
440            Event::Start(start) if start.local_name().as_ref() == b"area3DChart" => {
441                plot_area
442                    .charts
443                    .push(ChartType::Area3D(parse_area3d_chart(reader)?));
444            }
445            Event::Start(start) if start.local_name().as_ref() == b"surface3DChart" => {
446                plot_area
447                    .charts
448                    .push(ChartType::Surface3D(parse_surface3d_chart(reader)?));
449            }
450
451            Event::Start(start) if start.local_name().as_ref() == b"catAx" => {
452                plot_area
453                    .axes
454                    .push(Axis::Category(parse_category_axis(reader)?));
455            }
456            Event::Start(start) if start.local_name().as_ref() == b"valAx" => {
457                plot_area.axes.push(Axis::Value(parse_value_axis(reader)?));
458            }
459
460            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
461                plot_area.shape_properties = Some(drawing::read_shape_properties(reader)?);
462            }
463            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
464                plot_area.shape_properties = Some(drawing::ShapeProperties::new());
465            }
466
467            // `<c:layout>`, `<c:dTable>`, and every still-unmodeled chart type (radar/bubble/the
468            // non-3D `surfaceChart`/ofPie/stock) or axis (`serAx`/`dateAx`) — skipped.
469            Event::Start(_) => skip_subtree(reader)?,
470            Event::Empty(_) => {}
471            Event::End(_) | Event::Eof => break,
472            _ => {}
473        }
474    }
475
476    Ok(plot_area)
477}
478
479fn parse_bar_chart(reader: &mut Reader) -> Result<BarChart> {
480    let mut direction = BarDirection::Column;
481    let mut grouping = None;
482    let mut vary_colors = None;
483    let mut series = Vec::new();
484    let mut gap_width_percent = None;
485    let mut overlap_percent = None;
486    let mut axis_ids: Vec<u32> = Vec::new();
487
488    loop {
489        match reader.read_event()? {
490            Event::Empty(child) if child.local_name().as_ref() == b"barDir" => {
491                direction = raw_attribute(&child, b"val")
492                    .and_then(|v| BarDirection::from_xml_token(&v))
493                    .unwrap_or(BarDirection::Column);
494            }
495            Event::Start(child) if child.local_name().as_ref() == b"barDir" => {
496                direction = raw_attribute(&child, b"val")
497                    .and_then(|v| BarDirection::from_xml_token(&v))
498                    .unwrap_or(BarDirection::Column);
499                skip_subtree(reader)?;
500            }
501
502            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
503                grouping =
504                    raw_attribute(&child, b"val").and_then(|v| BarGrouping::from_xml_token(&v));
505            }
506            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
507                grouping =
508                    raw_attribute(&child, b"val").and_then(|v| BarGrouping::from_xml_token(&v));
509                skip_subtree(reader)?;
510            }
511
512            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
513                vary_colors = parse_bool_val(&child);
514            }
515            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
516                vary_colors = parse_bool_val(&child);
517                skip_subtree(reader)?;
518            }
519
520            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
521                series.push(parse_bar_series(reader)?);
522            }
523
524            Event::Empty(child) if child.local_name().as_ref() == b"gapWidth" => {
525                gap_width_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
526            }
527            Event::Start(child) if child.local_name().as_ref() == b"gapWidth" => {
528                gap_width_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
529                skip_subtree(reader)?;
530            }
531
532            Event::Empty(child) if child.local_name().as_ref() == b"overlap" => {
533                overlap_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
534            }
535            Event::Start(child) if child.local_name().as_ref() == b"overlap" => {
536                overlap_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
537                skip_subtree(reader)?;
538            }
539
540            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
541                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
542                    axis_ids.push(id);
543                }
544            }
545            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
546                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
547                    axis_ids.push(id);
548                }
549                skip_subtree(reader)?;
550            }
551
552            // `serLines`, 3D-only children, `dLbls`, `extLst` — not modeled.
553            Event::Start(_) => skip_subtree(reader)?,
554            Event::Empty(_) => {}
555            Event::End(_) | Event::Eof => break,
556            _ => {}
557        }
558    }
559
560    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
561    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
562
563    let mut chart = BarChart::new(direction, category_axis_id, value_axis_id);
564    chart.grouping = grouping;
565    chart.vary_colors = vary_colors;
566    chart.series = series;
567    chart.gap_width_percent = gap_width_percent;
568    chart.overlap_percent = overlap_percent;
569    Ok(chart)
570}
571
572fn parse_line_chart(reader: &mut Reader) -> Result<LineChart> {
573    let mut grouping = LineGrouping::default();
574    let mut vary_colors = None;
575    let mut series = Vec::new();
576    let mut marker = None;
577    let mut smooth = None;
578    let mut axis_ids: Vec<u32> = Vec::new();
579
580    loop {
581        match reader.read_event()? {
582            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
583                grouping = raw_attribute(&child, b"val")
584                    .and_then(|v| LineGrouping::from_xml_token(&v))
585                    .unwrap_or_default();
586            }
587            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
588                grouping = raw_attribute(&child, b"val")
589                    .and_then(|v| LineGrouping::from_xml_token(&v))
590                    .unwrap_or_default();
591                skip_subtree(reader)?;
592            }
593
594            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
595                vary_colors = parse_bool_val(&child);
596            }
597            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
598                vary_colors = parse_bool_val(&child);
599                skip_subtree(reader)?;
600            }
601
602            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
603                series.push(parse_line_series(reader)?);
604            }
605
606            Event::Empty(child) if child.local_name().as_ref() == b"marker" => {
607                marker = parse_bool_val(&child);
608            }
609            Event::Start(child) if child.local_name().as_ref() == b"marker" => {
610                marker = parse_bool_val(&child);
611                skip_subtree(reader)?;
612            }
613
614            Event::Empty(child) if child.local_name().as_ref() == b"smooth" => {
615                smooth = parse_bool_val(&child);
616            }
617            Event::Start(child) if child.local_name().as_ref() == b"smooth" => {
618                smooth = parse_bool_val(&child);
619                skip_subtree(reader)?;
620            }
621
622            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
623                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
624                    axis_ids.push(id);
625                }
626            }
627            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
628                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
629                    axis_ids.push(id);
630                }
631                skip_subtree(reader)?;
632            }
633
634            // `hiLowLines`, `upDownBars`, `dropLines`, 3D-only children, `dLbls`, `extLst` — not
635            // modeled.
636            Event::Start(_) => skip_subtree(reader)?,
637            Event::Empty(_) => {}
638            Event::End(_) | Event::Eof => break,
639            _ => {}
640        }
641    }
642
643    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
644    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
645
646    let mut chart = LineChart::new(category_axis_id, value_axis_id);
647    chart.grouping = grouping;
648    chart.vary_colors = vary_colors;
649    chart.series = series;
650    chart.marker = marker;
651    chart.smooth = smooth;
652    Ok(chart)
653}
654
655fn parse_pie_chart(reader: &mut Reader) -> Result<PieChart> {
656    let mut chart = PieChart::new();
657
658    loop {
659        match reader.read_event()? {
660            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
661                chart.vary_colors = parse_bool_val(&child);
662            }
663            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
664                chart.vary_colors = parse_bool_val(&child);
665                skip_subtree(reader)?;
666            }
667
668            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
669                chart.series.push(parse_pie_series(reader)?);
670            }
671
672            Event::Empty(child) if child.local_name().as_ref() == b"firstSliceAng" => {
673                chart.first_slice_angle_degrees =
674                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
675            }
676            Event::Start(child) if child.local_name().as_ref() == b"firstSliceAng" => {
677                chart.first_slice_angle_degrees =
678                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
679                skip_subtree(reader)?;
680            }
681
682            // `dLbls`, `extLst` — not modeled.
683            Event::Start(_) => skip_subtree(reader)?,
684            Event::Empty(_) => {}
685            Event::End(_) | Event::Eof => break,
686            _ => {}
687        }
688    }
689
690    Ok(chart)
691}
692
693fn parse_bar_series(reader: &mut Reader) -> Result<BarSeries> {
694    let mut series = BarSeries::default();
695
696    loop {
697        match reader.read_event()? {
698            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
699                series.index = raw_attribute(&child, b"val")
700                    .and_then(|v| v.parse().ok())
701                    .unwrap_or(0);
702            }
703            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
704                series.index = raw_attribute(&child, b"val")
705                    .and_then(|v| v.parse().ok())
706                    .unwrap_or(0);
707                skip_subtree(reader)?;
708            }
709
710            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
711                series.order = raw_attribute(&child, b"val")
712                    .and_then(|v| v.parse().ok())
713                    .unwrap_or(0);
714            }
715            Event::Start(child) if child.local_name().as_ref() == b"order" => {
716                series.order = raw_attribute(&child, b"val")
717                    .and_then(|v| v.parse().ok())
718                    .unwrap_or(0);
719                skip_subtree(reader)?;
720            }
721
722            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
723                series.title = Some(parse_series_title(reader)?);
724            }
725
726            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
727                series.shape_properties = Some(drawing::read_shape_properties(reader)?);
728            }
729            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
730                series.shape_properties = Some(drawing::ShapeProperties::new());
731            }
732
733            Event::Start(start) if start.local_name().as_ref() == b"cat" => {
734                series.categories = parse_axis_data_source(reader)?;
735            }
736            Event::Start(start) if start.local_name().as_ref() == b"val" => {
737                series.values = parse_numeric_data_source(reader)?;
738            }
739
740            // `invertIfNegative`, `pictureOptions`, `dPt`, `dLbls`, `trendline`, `errBars`,
741            // `shape`, `extLst` — not modeled.
742            Event::Start(_) => skip_subtree(reader)?,
743            Event::Empty(_) => {}
744            Event::End(_) | Event::Eof => break,
745            _ => {}
746        }
747    }
748
749    Ok(series)
750}
751
752fn parse_line_series(reader: &mut Reader) -> Result<LineSeries> {
753    let mut series = LineSeries::default();
754
755    loop {
756        match reader.read_event()? {
757            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
758                series.index = raw_attribute(&child, b"val")
759                    .and_then(|v| v.parse().ok())
760                    .unwrap_or(0);
761            }
762            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
763                series.index = raw_attribute(&child, b"val")
764                    .and_then(|v| v.parse().ok())
765                    .unwrap_or(0);
766                skip_subtree(reader)?;
767            }
768
769            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
770                series.order = raw_attribute(&child, b"val")
771                    .and_then(|v| v.parse().ok())
772                    .unwrap_or(0);
773            }
774            Event::Start(child) if child.local_name().as_ref() == b"order" => {
775                series.order = raw_attribute(&child, b"val")
776                    .and_then(|v| v.parse().ok())
777                    .unwrap_or(0);
778                skip_subtree(reader)?;
779            }
780
781            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
782                series.title = Some(parse_series_title(reader)?);
783            }
784
785            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
786                series.shape_properties = Some(drawing::read_shape_properties(reader)?);
787            }
788            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
789                series.shape_properties = Some(drawing::ShapeProperties::new());
790            }
791
792            Event::Start(start) if start.local_name().as_ref() == b"marker" => {
793                series.marker = Some(parse_marker(reader, true)?);
794            }
795            Event::Empty(start) if start.local_name().as_ref() == b"marker" => {
796                series.marker = Some(parse_marker(reader, false)?);
797            }
798
799            Event::Start(start) if start.local_name().as_ref() == b"cat" => {
800                series.categories = parse_axis_data_source(reader)?;
801            }
802            Event::Start(start) if start.local_name().as_ref() == b"val" => {
803                series.values = parse_numeric_data_source(reader)?;
804            }
805
806            Event::Empty(child) if child.local_name().as_ref() == b"smooth" => {
807                series.smooth = parse_bool_val(&child);
808            }
809            Event::Start(child) if child.local_name().as_ref() == b"smooth" => {
810                series.smooth = parse_bool_val(&child);
811                skip_subtree(reader)?;
812            }
813
814            // `dPt`, `dLbls`, `trendline`, `errBars`, `extLst` — not modeled.
815            Event::Start(_) => skip_subtree(reader)?,
816            Event::Empty(_) => {}
817            Event::End(_) | Event::Eof => break,
818            _ => {}
819        }
820    }
821
822    Ok(series)
823}
824
825fn parse_pie_series(reader: &mut Reader) -> Result<PieSeries> {
826    let mut series = PieSeries::default();
827
828    loop {
829        match reader.read_event()? {
830            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
831                series.index = raw_attribute(&child, b"val")
832                    .and_then(|v| v.parse().ok())
833                    .unwrap_or(0);
834            }
835            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
836                series.index = raw_attribute(&child, b"val")
837                    .and_then(|v| v.parse().ok())
838                    .unwrap_or(0);
839                skip_subtree(reader)?;
840            }
841
842            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
843                series.order = raw_attribute(&child, b"val")
844                    .and_then(|v| v.parse().ok())
845                    .unwrap_or(0);
846            }
847            Event::Start(child) if child.local_name().as_ref() == b"order" => {
848                series.order = raw_attribute(&child, b"val")
849                    .and_then(|v| v.parse().ok())
850                    .unwrap_or(0);
851                skip_subtree(reader)?;
852            }
853
854            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
855                series.title = Some(parse_series_title(reader)?);
856            }
857
858            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
859                series.shape_properties = Some(drawing::read_shape_properties(reader)?);
860            }
861            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
862                series.shape_properties = Some(drawing::ShapeProperties::new());
863            }
864
865            Event::Start(start) if start.local_name().as_ref() == b"cat" => {
866                series.categories = parse_axis_data_source(reader)?;
867            }
868            Event::Start(start) if start.local_name().as_ref() == b"val" => {
869                series.values = parse_numeric_data_source(reader)?;
870            }
871
872            // `explosion`, `dPt`, `dLbls`, `extLst` — not modeled.
873            Event::Start(_) => skip_subtree(reader)?,
874            Event::Empty(_) => {}
875            Event::End(_) | Event::Eof => break,
876            _ => {}
877        }
878    }
879
880    Ok(series)
881}
882
883// ================================================================================================
884// Doughnut, area, and scatter charts
885// ================================================================================================
886// ============================================================================
887
888fn parse_doughnut_chart(reader: &mut Reader) -> Result<DoughnutChart> {
889    let mut chart = DoughnutChart::new();
890
891    loop {
892        match reader.read_event()? {
893            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
894                chart.vary_colors = parse_bool_val(&child);
895            }
896            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
897                chart.vary_colors = parse_bool_val(&child);
898                skip_subtree(reader)?;
899            }
900
901            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
902                chart.series.push(parse_pie_series(reader)?);
903            }
904
905            Event::Empty(child) if child.local_name().as_ref() == b"firstSliceAng" => {
906                chart.first_slice_angle_degrees =
907                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
908            }
909            Event::Start(child) if child.local_name().as_ref() == b"firstSliceAng" => {
910                chart.first_slice_angle_degrees =
911                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
912                skip_subtree(reader)?;
913            }
914
915            Event::Empty(child) if child.local_name().as_ref() == b"holeSize" => {
916                chart.hole_size_percent = raw_attribute(&child, b"val")
917                    .and_then(|v| parse_percent(&v))
918                    .and_then(|v| u8::try_from(v).ok());
919            }
920            Event::Start(child) if child.local_name().as_ref() == b"holeSize" => {
921                chart.hole_size_percent = raw_attribute(&child, b"val")
922                    .and_then(|v| parse_percent(&v))
923                    .and_then(|v| u8::try_from(v).ok());
924                skip_subtree(reader)?;
925            }
926
927            // `dLbls`, `extLst` — not modeled.
928            Event::Start(_) => skip_subtree(reader)?,
929            Event::Empty(_) => {}
930            Event::End(_) | Event::Eof => break,
931            _ => {}
932        }
933    }
934
935    Ok(chart)
936}
937
938fn parse_area_chart(reader: &mut Reader) -> Result<AreaChart> {
939    let mut grouping = None;
940    let mut vary_colors = None;
941    let mut series = Vec::new();
942    let mut axis_ids: Vec<u32> = Vec::new();
943
944    loop {
945        match reader.read_event()? {
946            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
947                grouping =
948                    raw_attribute(&child, b"val").and_then(|v| LineGrouping::from_xml_token(&v));
949            }
950            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
951                grouping =
952                    raw_attribute(&child, b"val").and_then(|v| LineGrouping::from_xml_token(&v));
953                skip_subtree(reader)?;
954            }
955
956            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
957                vary_colors = parse_bool_val(&child);
958            }
959            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
960                vary_colors = parse_bool_val(&child);
961                skip_subtree(reader)?;
962            }
963
964            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
965                series.push(parse_area_series(reader)?);
966            }
967
968            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
969                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
970                    axis_ids.push(id);
971                }
972            }
973            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
974                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
975                    axis_ids.push(id);
976                }
977                skip_subtree(reader)?;
978            }
979
980            // `dLbls`, `dropLines`, 3D-only children, `extLst` — not modeled.
981            Event::Start(_) => skip_subtree(reader)?,
982            Event::Empty(_) => {}
983            Event::End(_) | Event::Eof => break,
984            _ => {}
985        }
986    }
987
988    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
989    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
990
991    let mut chart = AreaChart::new(category_axis_id, value_axis_id);
992    chart.grouping = grouping;
993    chart.vary_colors = vary_colors;
994    chart.series = series;
995    Ok(chart)
996}
997
998fn parse_area_series(reader: &mut Reader) -> Result<AreaSeries> {
999    let mut series = AreaSeries::default();
1000
1001    loop {
1002        match reader.read_event()? {
1003            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
1004                series.index = raw_attribute(&child, b"val")
1005                    .and_then(|v| v.parse().ok())
1006                    .unwrap_or(0);
1007            }
1008            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
1009                series.index = raw_attribute(&child, b"val")
1010                    .and_then(|v| v.parse().ok())
1011                    .unwrap_or(0);
1012                skip_subtree(reader)?;
1013            }
1014
1015            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
1016                series.order = raw_attribute(&child, b"val")
1017                    .and_then(|v| v.parse().ok())
1018                    .unwrap_or(0);
1019            }
1020            Event::Start(child) if child.local_name().as_ref() == b"order" => {
1021                series.order = raw_attribute(&child, b"val")
1022                    .and_then(|v| v.parse().ok())
1023                    .unwrap_or(0);
1024                skip_subtree(reader)?;
1025            }
1026
1027            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
1028                series.title = Some(parse_series_title(reader)?);
1029            }
1030
1031            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
1032                series.shape_properties = Some(drawing::read_shape_properties(reader)?);
1033            }
1034            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
1035                series.shape_properties = Some(drawing::ShapeProperties::new());
1036            }
1037
1038            Event::Start(start) if start.local_name().as_ref() == b"cat" => {
1039                series.categories = parse_axis_data_source(reader)?;
1040            }
1041            Event::Start(start) if start.local_name().as_ref() == b"val" => {
1042                series.values = parse_numeric_data_source(reader)?;
1043            }
1044
1045            // `dPt`, `dLbls`, `trendline`, `errBars`, `extLst` — not modeled.
1046            Event::Start(_) => skip_subtree(reader)?,
1047            Event::Empty(_) => {}
1048            Event::End(_) | Event::Eof => break,
1049            _ => {}
1050        }
1051    }
1052
1053    Ok(series)
1054}
1055
1056fn parse_scatter_chart(reader: &mut Reader) -> Result<ScatterChart> {
1057    let mut style = ScatterStyle::default();
1058    let mut vary_colors = None;
1059    let mut series = Vec::new();
1060    let mut axis_ids: Vec<u32> = Vec::new();
1061
1062    loop {
1063        match reader.read_event()? {
1064            Event::Empty(child) if child.local_name().as_ref() == b"scatterStyle" => {
1065                style = raw_attribute(&child, b"val")
1066                    .and_then(|v| ScatterStyle::from_xml_token(&v))
1067                    .unwrap_or_default();
1068            }
1069            Event::Start(child) if child.local_name().as_ref() == b"scatterStyle" => {
1070                style = raw_attribute(&child, b"val")
1071                    .and_then(|v| ScatterStyle::from_xml_token(&v))
1072                    .unwrap_or_default();
1073                skip_subtree(reader)?;
1074            }
1075
1076            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
1077                vary_colors = parse_bool_val(&child);
1078            }
1079            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
1080                vary_colors = parse_bool_val(&child);
1081                skip_subtree(reader)?;
1082            }
1083
1084            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1085                series.push(parse_scatter_series(reader)?);
1086            }
1087
1088            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1089                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1090                    axis_ids.push(id);
1091                }
1092            }
1093            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1094                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1095                    axis_ids.push(id);
1096                }
1097                skip_subtree(reader)?;
1098            }
1099
1100            // `dLbls`, `extLst` — not modeled.
1101            Event::Start(_) => skip_subtree(reader)?,
1102            Event::Empty(_) => {}
1103            Event::End(_) | Event::Eof => break,
1104            _ => {}
1105        }
1106    }
1107
1108    let x_axis_id = axis_ids.first().copied().unwrap_or(0);
1109    let y_axis_id = axis_ids.get(1).copied().unwrap_or(0);
1110
1111    let mut chart = ScatterChart::new(x_axis_id, y_axis_id);
1112    chart.style = style;
1113    chart.vary_colors = vary_colors;
1114    chart.series = series;
1115    Ok(chart)
1116}
1117
1118fn parse_scatter_series(reader: &mut Reader) -> Result<ScatterSeries> {
1119    let mut series = ScatterSeries::default();
1120
1121    loop {
1122        match reader.read_event()? {
1123            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
1124                series.index = raw_attribute(&child, b"val")
1125                    .and_then(|v| v.parse().ok())
1126                    .unwrap_or(0);
1127            }
1128            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
1129                series.index = raw_attribute(&child, b"val")
1130                    .and_then(|v| v.parse().ok())
1131                    .unwrap_or(0);
1132                skip_subtree(reader)?;
1133            }
1134
1135            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
1136                series.order = raw_attribute(&child, b"val")
1137                    .and_then(|v| v.parse().ok())
1138                    .unwrap_or(0);
1139            }
1140            Event::Start(child) if child.local_name().as_ref() == b"order" => {
1141                series.order = raw_attribute(&child, b"val")
1142                    .and_then(|v| v.parse().ok())
1143                    .unwrap_or(0);
1144                skip_subtree(reader)?;
1145            }
1146
1147            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
1148                series.title = Some(parse_series_title(reader)?);
1149            }
1150
1151            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
1152                series.shape_properties = Some(drawing::read_shape_properties(reader)?);
1153            }
1154            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
1155                series.shape_properties = Some(drawing::ShapeProperties::new());
1156            }
1157
1158            Event::Start(start) if start.local_name().as_ref() == b"marker" => {
1159                series.marker = Some(parse_marker(reader, true)?);
1160            }
1161            Event::Empty(start) if start.local_name().as_ref() == b"marker" => {
1162                series.marker = Some(parse_marker(reader, false)?);
1163            }
1164
1165            Event::Start(start) if start.local_name().as_ref() == b"xVal" => {
1166                series.x_values = parse_axis_data_source(reader)?;
1167            }
1168            Event::Start(start) if start.local_name().as_ref() == b"yVal" => {
1169                series.y_values = parse_numeric_data_source(reader)?;
1170            }
1171
1172            Event::Empty(child) if child.local_name().as_ref() == b"smooth" => {
1173                series.smooth = parse_bool_val(&child);
1174            }
1175            Event::Start(child) if child.local_name().as_ref() == b"smooth" => {
1176                series.smooth = parse_bool_val(&child);
1177                skip_subtree(reader)?;
1178            }
1179
1180            // `dPt`, `dLbls`, `trendline`, `errBars`, `extLst` — not modeled.
1181            Event::Start(_) => skip_subtree(reader)?,
1182            Event::Empty(_) => {}
1183            Event::End(_) | Event::Eof => break,
1184            _ => {}
1185        }
1186    }
1187
1188    Ok(series)
1189}
1190
1191// ================================================================================================
1192// 3D chart variants, View3D, and wall dressing
1193// ================================================================================================
1194
1195fn parse_view_3d(reader: &mut Reader) -> Result<View3D> {
1196    let mut view = View3D::new();
1197
1198    loop {
1199        match reader.read_event()? {
1200            Event::Empty(child) if child.local_name().as_ref() == b"rotX" => {
1201                view.rotation_x_degrees =
1202                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1203            }
1204            Event::Start(child) if child.local_name().as_ref() == b"rotX" => {
1205                view.rotation_x_degrees =
1206                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1207                skip_subtree(reader)?;
1208            }
1209
1210            Event::Empty(child) if child.local_name().as_ref() == b"hPercent" => {
1211                view.height_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1212            }
1213            Event::Start(child) if child.local_name().as_ref() == b"hPercent" => {
1214                view.height_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1215                skip_subtree(reader)?;
1216            }
1217
1218            Event::Empty(child) if child.local_name().as_ref() == b"rotY" => {
1219                view.rotation_y_degrees =
1220                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1221            }
1222            Event::Start(child) if child.local_name().as_ref() == b"rotY" => {
1223                view.rotation_y_degrees =
1224                    raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1225                skip_subtree(reader)?;
1226            }
1227
1228            Event::Empty(child) if child.local_name().as_ref() == b"depthPercent" => {
1229                view.depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1230            }
1231            Event::Start(child) if child.local_name().as_ref() == b"depthPercent" => {
1232                view.depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1233                skip_subtree(reader)?;
1234            }
1235
1236            Event::Empty(child) if child.local_name().as_ref() == b"rAngAx" => {
1237                view.right_angle_axes = parse_bool_val(&child);
1238            }
1239            Event::Start(child) if child.local_name().as_ref() == b"rAngAx" => {
1240                view.right_angle_axes = parse_bool_val(&child);
1241                skip_subtree(reader)?;
1242            }
1243
1244            Event::Empty(child) if child.local_name().as_ref() == b"perspective" => {
1245                view.perspective = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1246            }
1247            Event::Start(child) if child.local_name().as_ref() == b"perspective" => {
1248                view.perspective = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1249                skip_subtree(reader)?;
1250            }
1251
1252            // `extLst` — not modeled.
1253            Event::Start(_) => skip_subtree(reader)?,
1254            Event::Empty(_) => {}
1255            Event::End(_) | Event::Eof => break,
1256            _ => {}
1257        }
1258    }
1259
1260    Ok(view)
1261}
1262
1263fn parse_surface(reader: &mut Reader) -> Result<Surface> {
1264    let mut surface = Surface::new();
1265
1266    loop {
1267        match reader.read_event()? {
1268            Event::Empty(child) if child.local_name().as_ref() == b"thickness" => {
1269                surface.thickness_percent =
1270                    raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1271            }
1272            Event::Start(child) if child.local_name().as_ref() == b"thickness" => {
1273                surface.thickness_percent =
1274                    raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1275                skip_subtree(reader)?;
1276            }
1277
1278            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
1279                surface.shape_properties = Some(drawing::read_shape_properties(reader)?);
1280            }
1281            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
1282                surface.shape_properties = Some(drawing::ShapeProperties::new());
1283            }
1284
1285            // `pictureOptions`, `extLst` — not modeled.
1286            Event::Start(_) => skip_subtree(reader)?,
1287            Event::Empty(_) => {}
1288            Event::End(_) | Event::Eof => break,
1289            _ => {}
1290        }
1291    }
1292
1293    Ok(surface)
1294}
1295
1296fn parse_bar3d_chart(reader: &mut Reader) -> Result<Bar3DChart> {
1297    let mut direction = BarDirection::Column;
1298    let mut grouping = None;
1299    let mut vary_colors = None;
1300    let mut series = Vec::new();
1301    let mut gap_width_percent = None;
1302    let mut gap_depth_percent = None;
1303    let mut shape = None;
1304    let mut axis_ids: Vec<u32> = Vec::new();
1305
1306    loop {
1307        match reader.read_event()? {
1308            Event::Empty(child) if child.local_name().as_ref() == b"barDir" => {
1309                direction = raw_attribute(&child, b"val")
1310                    .and_then(|v| BarDirection::from_xml_token(&v))
1311                    .unwrap_or(BarDirection::Column);
1312            }
1313            Event::Start(child) if child.local_name().as_ref() == b"barDir" => {
1314                direction = raw_attribute(&child, b"val")
1315                    .and_then(|v| BarDirection::from_xml_token(&v))
1316                    .unwrap_or(BarDirection::Column);
1317                skip_subtree(reader)?;
1318            }
1319
1320            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
1321                grouping =
1322                    raw_attribute(&child, b"val").and_then(|v| BarGrouping::from_xml_token(&v));
1323            }
1324            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
1325                grouping =
1326                    raw_attribute(&child, b"val").and_then(|v| BarGrouping::from_xml_token(&v));
1327                skip_subtree(reader)?;
1328            }
1329
1330            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
1331                vary_colors = parse_bool_val(&child);
1332            }
1333            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
1334                vary_colors = parse_bool_val(&child);
1335                skip_subtree(reader)?;
1336            }
1337
1338            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1339                series.push(parse_bar_series(reader)?);
1340            }
1341
1342            Event::Empty(child) if child.local_name().as_ref() == b"gapWidth" => {
1343                gap_width_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1344            }
1345            Event::Start(child) if child.local_name().as_ref() == b"gapWidth" => {
1346                gap_width_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1347                skip_subtree(reader)?;
1348            }
1349
1350            Event::Empty(child) if child.local_name().as_ref() == b"gapDepth" => {
1351                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1352            }
1353            Event::Start(child) if child.local_name().as_ref() == b"gapDepth" => {
1354                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1355                skip_subtree(reader)?;
1356            }
1357
1358            Event::Empty(child) if child.local_name().as_ref() == b"shape" => {
1359                shape = raw_attribute(&child, b"val").and_then(|v| Bar3DShape::from_xml_token(&v));
1360            }
1361            Event::Start(child) if child.local_name().as_ref() == b"shape" => {
1362                shape = raw_attribute(&child, b"val").and_then(|v| Bar3DShape::from_xml_token(&v));
1363                skip_subtree(reader)?;
1364            }
1365
1366            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1367                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1368                    axis_ids.push(id);
1369                }
1370            }
1371            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1372                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1373                    axis_ids.push(id);
1374                }
1375                skip_subtree(reader)?;
1376            }
1377
1378            // `dLbls`, `extLst` — not modeled.
1379            Event::Start(_) => skip_subtree(reader)?,
1380            Event::Empty(_) => {}
1381            Event::End(_) | Event::Eof => break,
1382            _ => {}
1383        }
1384    }
1385
1386    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
1387    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
1388    let series_axis_id = axis_ids.get(2).copied();
1389
1390    let mut chart = Bar3DChart::new(direction, category_axis_id, value_axis_id);
1391    chart.grouping = grouping;
1392    chart.vary_colors = vary_colors;
1393    chart.series = series;
1394    chart.gap_width_percent = gap_width_percent;
1395    chart.gap_depth_percent = gap_depth_percent;
1396    chart.shape = shape;
1397    chart.series_axis_id = series_axis_id;
1398    Ok(chart)
1399}
1400
1401fn parse_line3d_chart(reader: &mut Reader) -> Result<Line3DChart> {
1402    let mut grouping = LineGrouping::default();
1403    let mut vary_colors = None;
1404    let mut series = Vec::new();
1405    let mut gap_depth_percent = None;
1406    let mut axis_ids: Vec<u32> = Vec::new();
1407
1408    loop {
1409        match reader.read_event()? {
1410            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
1411                grouping = raw_attribute(&child, b"val")
1412                    .and_then(|v| LineGrouping::from_xml_token(&v))
1413                    .unwrap_or_default();
1414            }
1415            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
1416                grouping = raw_attribute(&child, b"val")
1417                    .and_then(|v| LineGrouping::from_xml_token(&v))
1418                    .unwrap_or_default();
1419                skip_subtree(reader)?;
1420            }
1421
1422            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
1423                vary_colors = parse_bool_val(&child);
1424            }
1425            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
1426                vary_colors = parse_bool_val(&child);
1427                skip_subtree(reader)?;
1428            }
1429
1430            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1431                series.push(parse_line_series(reader)?);
1432            }
1433
1434            Event::Empty(child) if child.local_name().as_ref() == b"gapDepth" => {
1435                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1436            }
1437            Event::Start(child) if child.local_name().as_ref() == b"gapDepth" => {
1438                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1439                skip_subtree(reader)?;
1440            }
1441
1442            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1443                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1444                    axis_ids.push(id);
1445                }
1446            }
1447            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1448                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1449                    axis_ids.push(id);
1450                }
1451                skip_subtree(reader)?;
1452            }
1453
1454            // `dLbls`, `extLst` — not modeled.
1455            Event::Start(_) => skip_subtree(reader)?,
1456            Event::Empty(_) => {}
1457            Event::End(_) | Event::Eof => break,
1458            _ => {}
1459        }
1460    }
1461
1462    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
1463    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
1464    let series_axis_id = axis_ids.get(2).copied().unwrap_or(0);
1465
1466    let mut chart = Line3DChart::new(category_axis_id, value_axis_id, series_axis_id);
1467    chart.grouping = grouping;
1468    chart.vary_colors = vary_colors;
1469    chart.series = series;
1470    chart.gap_depth_percent = gap_depth_percent;
1471    Ok(chart)
1472}
1473
1474fn parse_pie3d_chart(reader: &mut Reader) -> Result<Pie3DChart> {
1475    let mut chart = Pie3DChart::new();
1476
1477    loop {
1478        match reader.read_event()? {
1479            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
1480                chart.vary_colors = parse_bool_val(&child);
1481            }
1482            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
1483                chart.vary_colors = parse_bool_val(&child);
1484                skip_subtree(reader)?;
1485            }
1486
1487            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1488                chart.series.push(parse_pie_series(reader)?);
1489            }
1490
1491            // `dLbls`, `extLst` — not modeled.
1492            Event::Start(_) => skip_subtree(reader)?,
1493            Event::Empty(_) => {}
1494            Event::End(_) | Event::Eof => break,
1495            _ => {}
1496        }
1497    }
1498
1499    Ok(chart)
1500}
1501
1502fn parse_area3d_chart(reader: &mut Reader) -> Result<Area3DChart> {
1503    let mut grouping = None;
1504    let mut vary_colors = None;
1505    let mut series = Vec::new();
1506    let mut gap_depth_percent = None;
1507    let mut axis_ids: Vec<u32> = Vec::new();
1508
1509    loop {
1510        match reader.read_event()? {
1511            Event::Empty(child) if child.local_name().as_ref() == b"grouping" => {
1512                grouping =
1513                    raw_attribute(&child, b"val").and_then(|v| LineGrouping::from_xml_token(&v));
1514            }
1515            Event::Start(child) if child.local_name().as_ref() == b"grouping" => {
1516                grouping =
1517                    raw_attribute(&child, b"val").and_then(|v| LineGrouping::from_xml_token(&v));
1518                skip_subtree(reader)?;
1519            }
1520
1521            Event::Empty(child) if child.local_name().as_ref() == b"varyColors" => {
1522                vary_colors = parse_bool_val(&child);
1523            }
1524            Event::Start(child) if child.local_name().as_ref() == b"varyColors" => {
1525                vary_colors = parse_bool_val(&child);
1526                skip_subtree(reader)?;
1527            }
1528
1529            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1530                series.push(parse_area_series(reader)?);
1531            }
1532
1533            Event::Empty(child) if child.local_name().as_ref() == b"gapDepth" => {
1534                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1535            }
1536            Event::Start(child) if child.local_name().as_ref() == b"gapDepth" => {
1537                gap_depth_percent = raw_attribute(&child, b"val").and_then(|v| parse_percent(&v));
1538                skip_subtree(reader)?;
1539            }
1540
1541            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1542                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1543                    axis_ids.push(id);
1544                }
1545            }
1546            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1547                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1548                    axis_ids.push(id);
1549                }
1550                skip_subtree(reader)?;
1551            }
1552
1553            // `dLbls`, `dropLines`, `extLst` — not modeled.
1554            Event::Start(_) => skip_subtree(reader)?,
1555            Event::Empty(_) => {}
1556            Event::End(_) | Event::Eof => break,
1557            _ => {}
1558        }
1559    }
1560
1561    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
1562    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
1563    let series_axis_id = axis_ids.get(2).copied();
1564
1565    let mut chart = Area3DChart::new(category_axis_id, value_axis_id);
1566    chart.grouping = grouping;
1567    chart.vary_colors = vary_colors;
1568    chart.series = series;
1569    chart.gap_depth_percent = gap_depth_percent;
1570    chart.series_axis_id = series_axis_id;
1571    Ok(chart)
1572}
1573
1574fn parse_surface_series(reader: &mut Reader) -> Result<SurfaceSeries> {
1575    let mut series = SurfaceSeries::default();
1576
1577    loop {
1578        match reader.read_event()? {
1579            Event::Empty(child) if child.local_name().as_ref() == b"idx" => {
1580                series.index = raw_attribute(&child, b"val")
1581                    .and_then(|v| v.parse().ok())
1582                    .unwrap_or(0);
1583            }
1584            Event::Start(child) if child.local_name().as_ref() == b"idx" => {
1585                series.index = raw_attribute(&child, b"val")
1586                    .and_then(|v| v.parse().ok())
1587                    .unwrap_or(0);
1588                skip_subtree(reader)?;
1589            }
1590
1591            Event::Empty(child) if child.local_name().as_ref() == b"order" => {
1592                series.order = raw_attribute(&child, b"val")
1593                    .and_then(|v| v.parse().ok())
1594                    .unwrap_or(0);
1595            }
1596            Event::Start(child) if child.local_name().as_ref() == b"order" => {
1597                series.order = raw_attribute(&child, b"val")
1598                    .and_then(|v| v.parse().ok())
1599                    .unwrap_or(0);
1600                skip_subtree(reader)?;
1601            }
1602
1603            Event::Start(start) if start.local_name().as_ref() == b"tx" => {
1604                series.title = Some(parse_series_title(reader)?);
1605            }
1606
1607            Event::Start(start) if start.local_name().as_ref() == b"cat" => {
1608                series.categories = parse_axis_data_source(reader)?;
1609            }
1610            Event::Start(start) if start.local_name().as_ref() == b"val" => {
1611                series.values = parse_numeric_data_source(reader)?;
1612            }
1613
1614            // `extLst` — not modeled.
1615            Event::Start(_) => skip_subtree(reader)?,
1616            Event::Empty(_) => {}
1617            Event::End(_) | Event::Eof => break,
1618            _ => {}
1619        }
1620    }
1621
1622    Ok(series)
1623}
1624
1625fn parse_surface3d_chart(reader: &mut Reader) -> Result<Surface3DChart> {
1626    let mut wireframe = None;
1627    let mut series = Vec::new();
1628    let mut axis_ids: Vec<u32> = Vec::new();
1629
1630    loop {
1631        match reader.read_event()? {
1632            Event::Empty(child) if child.local_name().as_ref() == b"wireframe" => {
1633                wireframe = parse_bool_val(&child);
1634            }
1635            Event::Start(child) if child.local_name().as_ref() == b"wireframe" => {
1636                wireframe = parse_bool_val(&child);
1637                skip_subtree(reader)?;
1638            }
1639
1640            Event::Start(start) if start.local_name().as_ref() == b"ser" => {
1641                series.push(parse_surface_series(reader)?);
1642            }
1643
1644            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1645                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1646                    axis_ids.push(id);
1647                }
1648            }
1649            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1650                if let Some(id) = raw_attribute(&child, b"val").and_then(|v| v.parse().ok()) {
1651                    axis_ids.push(id);
1652                }
1653                skip_subtree(reader)?;
1654            }
1655
1656            // `bandFmts`, `extLst` — not modeled.
1657            Event::Start(_) => skip_subtree(reader)?,
1658            Event::Empty(_) => {}
1659            Event::End(_) | Event::Eof => break,
1660            _ => {}
1661        }
1662    }
1663
1664    let category_axis_id = axis_ids.first().copied().unwrap_or(0);
1665    let value_axis_id = axis_ids.get(1).copied().unwrap_or(0);
1666    let series_axis_id = axis_ids.get(2).copied().unwrap_or(0);
1667
1668    let mut chart = Surface3DChart::new(category_axis_id, value_axis_id, series_axis_id);
1669    chart.wireframe = wireframe;
1670    chart.series = series;
1671    Ok(chart)
1672}
1673
1674fn parse_marker(reader: &mut Reader, is_start: bool) -> Result<Marker> {
1675    let mut marker = Marker::new();
1676
1677    if !is_start {
1678        return Ok(marker);
1679    }
1680
1681    loop {
1682        match reader.read_event()? {
1683            Event::Empty(child) if child.local_name().as_ref() == b"symbol" => {
1684                marker.symbol =
1685                    raw_attribute(&child, b"val").and_then(|v| MarkerSymbol::from_xml_token(&v));
1686            }
1687            Event::Start(child) if child.local_name().as_ref() == b"symbol" => {
1688                marker.symbol =
1689                    raw_attribute(&child, b"val").and_then(|v| MarkerSymbol::from_xml_token(&v));
1690                skip_subtree(reader)?;
1691            }
1692
1693            Event::Empty(child) if child.local_name().as_ref() == b"size" => {
1694                marker.size = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1695            }
1696            Event::Start(child) if child.local_name().as_ref() == b"size" => {
1697                marker.size = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1698                skip_subtree(reader)?;
1699            }
1700
1701            Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
1702                marker.shape_properties = Some(drawing::read_shape_properties(reader)?);
1703            }
1704            Event::Empty(start) if start.local_name().as_ref() == b"spPr" => {
1705                marker.shape_properties = Some(drawing::ShapeProperties::new());
1706            }
1707
1708            Event::Start(_) => skip_subtree(reader)?,
1709            Event::Empty(_) => {}
1710            Event::End(_) | Event::Eof => break,
1711            _ => {}
1712        }
1713    }
1714
1715    Ok(marker)
1716}
1717
1718/// The four `c:scaling` child values, grouped so `parse_scaling` doesn't have to return a bare
1719/// 4-tuple (clippy's `type_complexity` flags that as hard to read at the call site — a named field
1720/// beats a tuple position).
1721struct AxisScaling {
1722    log_base: Option<f64>,
1723    reversed: bool,
1724    max: Option<f64>,
1725    min: Option<f64>,
1726}
1727
1728fn parse_scaling(reader: &mut Reader) -> Result<AxisScaling> {
1729    let mut log_base = None;
1730    let mut reversed = false;
1731    let mut max = None;
1732    let mut min = None;
1733
1734    loop {
1735        match reader.read_event()? {
1736            Event::Empty(child) if child.local_name().as_ref() == b"logBase" => {
1737                log_base = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1738            }
1739            Event::Start(child) if child.local_name().as_ref() == b"logBase" => {
1740                log_base = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1741                skip_subtree(reader)?;
1742            }
1743
1744            Event::Empty(child) if child.local_name().as_ref() == b"orientation" => {
1745                reversed = raw_attribute(&child, b"val").as_deref() == Some("maxMin");
1746            }
1747            Event::Start(child) if child.local_name().as_ref() == b"orientation" => {
1748                reversed = raw_attribute(&child, b"val").as_deref() == Some("maxMin");
1749                skip_subtree(reader)?;
1750            }
1751
1752            Event::Empty(child) if child.local_name().as_ref() == b"max" => {
1753                max = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1754            }
1755            Event::Start(child) if child.local_name().as_ref() == b"max" => {
1756                max = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1757                skip_subtree(reader)?;
1758            }
1759
1760            Event::Empty(child) if child.local_name().as_ref() == b"min" => {
1761                min = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1762            }
1763            Event::Start(child) if child.local_name().as_ref() == b"min" => {
1764                min = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
1765                skip_subtree(reader)?;
1766            }
1767
1768            Event::Start(_) => skip_subtree(reader)?,
1769            Event::Empty(_) => {}
1770            Event::End(_) | Event::Eof => break,
1771            _ => {}
1772        }
1773    }
1774
1775    Ok(AxisScaling {
1776        log_base,
1777        reversed,
1778        max,
1779        min,
1780    })
1781}
1782
1783fn parse_category_axis(reader: &mut Reader) -> Result<CategoryAxis> {
1784    let mut id = 0u32;
1785    let mut position = AxisPosition::Bottom;
1786    let mut cross_axis_id = 0u32;
1787    let mut log_base = None;
1788    let mut min = None;
1789    let mut max = None;
1790    let mut reversed = false;
1791    let mut major_gridlines = false;
1792    let mut minor_gridlines = false;
1793    let mut title = None;
1794
1795    loop {
1796        match reader.read_event()? {
1797            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1798                id = raw_attribute(&child, b"val")
1799                    .and_then(|v| v.parse().ok())
1800                    .unwrap_or(0);
1801            }
1802            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1803                id = raw_attribute(&child, b"val")
1804                    .and_then(|v| v.parse().ok())
1805                    .unwrap_or(0);
1806                skip_subtree(reader)?;
1807            }
1808
1809            Event::Start(start) if start.local_name().as_ref() == b"scaling" => {
1810                let scaling = parse_scaling(reader)?;
1811                log_base = scaling.log_base;
1812                reversed = scaling.reversed;
1813                max = scaling.max;
1814                min = scaling.min;
1815            }
1816
1817            Event::Empty(child) if child.local_name().as_ref() == b"axPos" => {
1818                position = raw_attribute(&child, b"val")
1819                    .and_then(|v| AxisPosition::from_xml_token(&v))
1820                    .unwrap_or(AxisPosition::Bottom);
1821            }
1822            Event::Start(child) if child.local_name().as_ref() == b"axPos" => {
1823                position = raw_attribute(&child, b"val")
1824                    .and_then(|v| AxisPosition::from_xml_token(&v))
1825                    .unwrap_or(AxisPosition::Bottom);
1826                skip_subtree(reader)?;
1827            }
1828
1829            Event::Empty(child) if child.local_name().as_ref() == b"majorGridlines" => {
1830                major_gridlines = true;
1831            }
1832            Event::Start(child) if child.local_name().as_ref() == b"majorGridlines" => {
1833                major_gridlines = true;
1834                skip_subtree(reader)?;
1835            }
1836
1837            Event::Empty(child) if child.local_name().as_ref() == b"minorGridlines" => {
1838                minor_gridlines = true;
1839            }
1840            Event::Start(child) if child.local_name().as_ref() == b"minorGridlines" => {
1841                minor_gridlines = true;
1842                skip_subtree(reader)?;
1843            }
1844
1845            Event::Start(start) if start.local_name().as_ref() == b"title" => {
1846                title = Some(parse_title(reader)?);
1847            }
1848            Event::Empty(start) if start.local_name().as_ref() == b"title" => {
1849                title = Some(Title::new());
1850            }
1851
1852            Event::Empty(child) if child.local_name().as_ref() == b"crossAx" => {
1853                cross_axis_id = raw_attribute(&child, b"val")
1854                    .and_then(|v| v.parse().ok())
1855                    .unwrap_or(0);
1856            }
1857            Event::Start(child) if child.local_name().as_ref() == b"crossAx" => {
1858                cross_axis_id = raw_attribute(&child, b"val")
1859                    .and_then(|v| v.parse().ok())
1860                    .unwrap_or(0);
1861                skip_subtree(reader)?;
1862            }
1863
1864            // `delete`, `crosses`/`crossesAt`, `numFmt`, `majorTickMark`/
1865            // `minorTickMark`/`tickLblPos`, `spPr`/`txPr`, `auto`,
1866            // `lblAlgn`/`lblOffset`/`tickLblSkip`/`tickMarkSkip`/ `noMultiLvlLbl`, `extLst` — not
1867            // modeled.
1868            Event::Start(_) => skip_subtree(reader)?,
1869            Event::Empty(_) => {}
1870            Event::End(_) | Event::Eof => break,
1871            _ => {}
1872        }
1873    }
1874
1875    let mut axis = CategoryAxis::new(id, position, cross_axis_id);
1876    axis.log_base = log_base;
1877    axis.min = min;
1878    axis.max = max;
1879    axis.reversed = reversed;
1880    axis.major_gridlines = major_gridlines;
1881    axis.minor_gridlines = minor_gridlines;
1882    axis.title = title;
1883    Ok(axis)
1884}
1885
1886fn parse_value_axis(reader: &mut Reader) -> Result<ValueAxis> {
1887    let mut id = 0u32;
1888    let mut position = AxisPosition::Left;
1889    let mut cross_axis_id = 0u32;
1890    let mut log_base = None;
1891    let mut min = None;
1892    let mut max = None;
1893    let mut reversed = false;
1894    let mut major_gridlines = false;
1895    let mut minor_gridlines = false;
1896    let mut title = None;
1897    let mut cross_between = None;
1898
1899    loop {
1900        match reader.read_event()? {
1901            Event::Empty(child) if child.local_name().as_ref() == b"axId" => {
1902                id = raw_attribute(&child, b"val")
1903                    .and_then(|v| v.parse().ok())
1904                    .unwrap_or(0);
1905            }
1906            Event::Start(child) if child.local_name().as_ref() == b"axId" => {
1907                id = raw_attribute(&child, b"val")
1908                    .and_then(|v| v.parse().ok())
1909                    .unwrap_or(0);
1910                skip_subtree(reader)?;
1911            }
1912
1913            Event::Start(start) if start.local_name().as_ref() == b"scaling" => {
1914                let scaling = parse_scaling(reader)?;
1915                log_base = scaling.log_base;
1916                reversed = scaling.reversed;
1917                max = scaling.max;
1918                min = scaling.min;
1919            }
1920
1921            Event::Empty(child) if child.local_name().as_ref() == b"axPos" => {
1922                position = raw_attribute(&child, b"val")
1923                    .and_then(|v| AxisPosition::from_xml_token(&v))
1924                    .unwrap_or(AxisPosition::Left);
1925            }
1926            Event::Start(child) if child.local_name().as_ref() == b"axPos" => {
1927                position = raw_attribute(&child, b"val")
1928                    .and_then(|v| AxisPosition::from_xml_token(&v))
1929                    .unwrap_or(AxisPosition::Left);
1930                skip_subtree(reader)?;
1931            }
1932
1933            Event::Empty(child) if child.local_name().as_ref() == b"majorGridlines" => {
1934                major_gridlines = true;
1935            }
1936            Event::Start(child) if child.local_name().as_ref() == b"majorGridlines" => {
1937                major_gridlines = true;
1938                skip_subtree(reader)?;
1939            }
1940
1941            Event::Empty(child) if child.local_name().as_ref() == b"minorGridlines" => {
1942                minor_gridlines = true;
1943            }
1944            Event::Start(child) if child.local_name().as_ref() == b"minorGridlines" => {
1945                minor_gridlines = true;
1946                skip_subtree(reader)?;
1947            }
1948
1949            Event::Start(start) if start.local_name().as_ref() == b"title" => {
1950                title = Some(parse_title(reader)?);
1951            }
1952            Event::Empty(start) if start.local_name().as_ref() == b"title" => {
1953                title = Some(Title::new());
1954            }
1955
1956            Event::Empty(child) if child.local_name().as_ref() == b"crossAx" => {
1957                cross_axis_id = raw_attribute(&child, b"val")
1958                    .and_then(|v| v.parse().ok())
1959                    .unwrap_or(0);
1960            }
1961            Event::Start(child) if child.local_name().as_ref() == b"crossAx" => {
1962                cross_axis_id = raw_attribute(&child, b"val")
1963                    .and_then(|v| v.parse().ok())
1964                    .unwrap_or(0);
1965                skip_subtree(reader)?;
1966            }
1967
1968            Event::Empty(child) if child.local_name().as_ref() == b"crossBetween" => {
1969                cross_between =
1970                    raw_attribute(&child, b"val").and_then(|v| CrossBetween::from_xml_token(&v));
1971            }
1972            Event::Start(child) if child.local_name().as_ref() == b"crossBetween" => {
1973                cross_between =
1974                    raw_attribute(&child, b"val").and_then(|v| CrossBetween::from_xml_token(&v));
1975                skip_subtree(reader)?;
1976            }
1977
1978            // `delete`, `crosses`/`crossesAt`, `numFmt`, `majorTickMark`/
1979            // `minorTickMark`/`tickLblPos`, `spPr`/`txPr`, `majorUnit`/ `minorUnit`, `dispUnits`,
1980            // `extLst` — not modeled.
1981            Event::Start(_) => skip_subtree(reader)?,
1982            Event::Empty(_) => {}
1983            Event::End(_) | Event::Eof => break,
1984            _ => {}
1985        }
1986    }
1987
1988    let mut axis = ValueAxis::new(id, position, cross_axis_id);
1989    axis.log_base = log_base;
1990    axis.min = min;
1991    axis.max = max;
1992    axis.reversed = reversed;
1993    axis.major_gridlines = major_gridlines;
1994    axis.minor_gridlines = minor_gridlines;
1995    axis.title = title;
1996    axis.cross_between = cross_between;
1997    Ok(axis)
1998}
1999
2000fn parse_axis_data_source(reader: &mut Reader) -> Result<Option<AxisDataSource>> {
2001    let mut result = None;
2002
2003    loop {
2004        match reader.read_event()? {
2005            Event::Start(start) if start.local_name().as_ref() == b"numRef" => {
2006                let (formula, cache) = parse_numeric_reference(reader)?;
2007                result = Some(AxisDataSource::Numeric(NumericDataSource::Reference {
2008                    formula,
2009                    cache,
2010                }));
2011            }
2012            Event::Start(start) if start.local_name().as_ref() == b"numLit" => {
2013                result = Some(AxisDataSource::Numeric(NumericDataSource::Literal(
2014                    parse_numeric_data(reader)?,
2015                )));
2016            }
2017            Event::Start(start) if start.local_name().as_ref() == b"strRef" => {
2018                let (formula, cache) = parse_string_reference(reader)?;
2019                result = Some(AxisDataSource::String(StringDataSource::Reference {
2020                    formula,
2021                    cache,
2022                }));
2023            }
2024            Event::Start(start) if start.local_name().as_ref() == b"strLit" => {
2025                result = Some(AxisDataSource::String(StringDataSource::Literal(
2026                    parse_string_data(reader)?,
2027                )));
2028            }
2029            // `<c:multiLvlStrRef>` — multi-level categories, deferred.
2030            Event::Start(_) => skip_subtree(reader)?,
2031            Event::End(_) | Event::Eof => break,
2032            _ => {}
2033        }
2034    }
2035
2036    Ok(result)
2037}
2038
2039fn parse_numeric_data_source(reader: &mut Reader) -> Result<Option<NumericDataSource>> {
2040    let mut result = None;
2041
2042    loop {
2043        match reader.read_event()? {
2044            Event::Start(start) if start.local_name().as_ref() == b"numRef" => {
2045                let (formula, cache) = parse_numeric_reference(reader)?;
2046                result = Some(NumericDataSource::Reference { formula, cache });
2047            }
2048            Event::Start(start) if start.local_name().as_ref() == b"numLit" => {
2049                result = Some(NumericDataSource::Literal(parse_numeric_data(reader)?));
2050            }
2051            Event::Start(_) => skip_subtree(reader)?,
2052            Event::End(_) | Event::Eof => break,
2053            _ => {}
2054        }
2055    }
2056
2057    Ok(result)
2058}
2059
2060fn parse_numeric_reference(reader: &mut Reader) -> Result<(String, Option<NumericData>)> {
2061    let mut formula = String::new();
2062    let mut cache = None;
2063
2064    loop {
2065        match reader.read_event()? {
2066            Event::Start(start) if start.local_name().as_ref() == b"f" => {
2067                formula = read_text_content(reader, b"f")?;
2068            }
2069            Event::Start(start) if start.local_name().as_ref() == b"numCache" => {
2070                cache = Some(parse_numeric_data(reader)?);
2071            }
2072            Event::Start(_) => skip_subtree(reader)?,
2073            Event::End(end) if end.local_name().as_ref() == b"numRef" => break,
2074            Event::Eof => break,
2075            _ => {}
2076        }
2077    }
2078
2079    Ok((formula, cache))
2080}
2081
2082fn parse_string_reference(reader: &mut Reader) -> Result<(String, Option<StringData>)> {
2083    let mut formula = String::new();
2084    let mut cache = None;
2085
2086    loop {
2087        match reader.read_event()? {
2088            Event::Start(start) if start.local_name().as_ref() == b"f" => {
2089                formula = read_text_content(reader, b"f")?;
2090            }
2091            Event::Start(start) if start.local_name().as_ref() == b"strCache" => {
2092                cache = Some(parse_string_data(reader)?);
2093            }
2094            Event::Start(_) => skip_subtree(reader)?,
2095            Event::End(end) if end.local_name().as_ref() == b"strRef" => break,
2096            Event::Eof => break,
2097            _ => {}
2098        }
2099    }
2100
2101    Ok((formula, cache))
2102}
2103
2104fn parse_numeric_data(reader: &mut Reader) -> Result<NumericData> {
2105    let mut data = NumericData::new();
2106
2107    loop {
2108        match reader.read_event()? {
2109            Event::Start(start) if start.local_name().as_ref() == b"formatCode" => {
2110                data.format_code = Some(read_text_content(reader, b"formatCode")?);
2111            }
2112
2113            Event::Empty(child) if child.local_name().as_ref() == b"ptCount" => {
2114                data.point_count = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
2115            }
2116            Event::Start(child) if child.local_name().as_ref() == b"ptCount" => {
2117                data.point_count = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
2118                skip_subtree(reader)?;
2119            }
2120
2121            Event::Start(start) if start.local_name().as_ref() == b"pt" => {
2122                let index = raw_attribute(&start, b"idx")
2123                    .and_then(|v| v.parse().ok())
2124                    .unwrap_or(0);
2125                let format_code = raw_attribute(&start, b"formatCode");
2126                let value = parse_pt_value(reader)?;
2127                let mut point = NumericPoint::new(index, value);
2128                point.format_code = format_code;
2129                data.points.push(point);
2130            }
2131
2132            Event::Start(_) => skip_subtree(reader)?,
2133            Event::End(_) | Event::Eof => break,
2134            _ => {}
2135        }
2136    }
2137
2138    Ok(data)
2139}
2140
2141fn parse_string_data(reader: &mut Reader) -> Result<StringData> {
2142    let mut data = StringData::new();
2143
2144    loop {
2145        match reader.read_event()? {
2146            Event::Empty(child) if child.local_name().as_ref() == b"ptCount" => {
2147                data.point_count = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
2148            }
2149            Event::Start(child) if child.local_name().as_ref() == b"ptCount" => {
2150                data.point_count = raw_attribute(&child, b"val").and_then(|v| v.parse().ok());
2151                skip_subtree(reader)?;
2152            }
2153
2154            Event::Start(start) if start.local_name().as_ref() == b"pt" => {
2155                let index = raw_attribute(&start, b"idx")
2156                    .and_then(|v| v.parse().ok())
2157                    .unwrap_or(0);
2158                let value = parse_pt_value(reader)?;
2159                data.points.push(StringPoint { index, value });
2160            }
2161
2162            Event::Start(_) => skip_subtree(reader)?,
2163            Event::End(_) | Event::Eof => break,
2164            _ => {}
2165        }
2166    }
2167
2168    Ok(data)
2169}
2170
2171/// Parses a `<c:pt idx="..">`'s content (its single `<c:v>` child), assuming `<c:pt>`'s own `Start`
2172/// was just consumed by the caller.
2173fn parse_pt_value(reader: &mut Reader) -> Result<String> {
2174    let mut value = String::new();
2175
2176    loop {
2177        match reader.read_event()? {
2178            Event::Start(start) if start.local_name().as_ref() == b"v" => {
2179                value = read_text_content(reader, b"v")?;
2180            }
2181            Event::Start(_) => skip_subtree(reader)?,
2182            Event::End(end) if end.local_name().as_ref() == b"pt" => break,
2183            Event::Eof => break,
2184            _ => {}
2185        }
2186    }
2187
2188    Ok(value)
2189}