diff --git a/crates/pptx/src/read/chart.rs b/crates/pptx/src/read/chart.rs index 7be64997..ecd43cba 100644 --- a/crates/pptx/src/read/chart.rs +++ b/crates/pptx/src/read/chart.rs @@ -272,6 +272,10 @@ fn chart_within(root: &El, mut budget: usize) -> Option { c.series.push(ser); } } + // `autoTitleDeleted val="0"` without `c:title`: PowerPoint and LibreOffice draw the series name as the title of a single-series chart. + if ch.child("title").is_none() && ch.child("autoTitleDeleted").and_then(|a| a.bool("val")) == Some(false) && c.series.len() == 1 { + c.title = c.series.first().map(|s| s.name.clone()).filter(|n| !n.is_empty()); + } let first = *kinds.first()?; c.kind = if kinds.iter().any(|k| *k != first) { ChartType::Combo } else { first }; if c.kind != ChartType::Combo { @@ -585,4 +589,33 @@ mod tests { assert_eq!((t[1].kind, t[1].order), (TrendlineType::Poly, Some(6))); assert_eq!((t[2].kind, t[2].period), (TrendlineType::MovingAvg, Some(3))); } + + const NS_C: &str = "http://schemas.openxmlformats.org/drawingml/2006/chart"; + const NS_A: &str = "http://schemas.openxmlformats.org/drawingml/2006/main"; + + fn chart(auto_deleted: &str, title: &str, series: usize) -> Chart { + let val = r#"1"#; + let ser: String = (0..series).map(|i| format!(r#"S{i}{val}"#)).collect(); + let plot = format!(r#"{ser}"#); + let xml = format!(r#"{title}{auto_deleted}{plot}"#); + read_chart_xml(xml.as_bytes()).unwrap() + } + + #[test] + fn single_series_gets_automatic_title() { + assert_eq!(chart(r#""#, "", 1).title.as_deref(), Some("S0")); + } + + #[test] + fn no_automatic_title_when_deleted_or_multi_series() { + assert_eq!(chart(r#""#, "", 1).title, None); + assert_eq!(chart("", "", 1).title, None); + assert_eq!(chart(r#""#, "", 2).title, None); + } + + #[test] + fn explicit_title_wins() { + let t = "Mine"; + assert_eq!(chart(r#""#, t, 1).title.as_deref(), Some("Mine")); + } }