From 1f3b8529a59fd3989c0b34615419536918c85542 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 29 Oct 2025 07:57:10 -0700 Subject: [PATCH] work --- rust/pspp/src/output/pivot.rs | 21 +++- rust/pspp/src/output/pivot/look_xml.rs | 2 +- rust/pspp/src/output/spv/legacy_xml.rs | 139 +++++++++++++++++++++---- 3 files changed, 136 insertions(+), 26 deletions(-) diff --git a/rust/pspp/src/output/pivot.rs b/rust/pspp/src/output/pivot.rs index 33820af281..c2a5dc0b87 100644 --- a/rust/pspp/src/output/pivot.rs +++ b/rust/pspp/src/output/pivot.rs @@ -156,7 +156,7 @@ impl Serialize for Area { } impl Area { - fn default_cell_style(self) -> CellStyle { + pub fn default_cell_style(self) -> CellStyle { use HorzAlign::*; use VertAlign::*; let (horz_align, vert_align, hmargins, vmargins) = match self { @@ -176,11 +176,11 @@ impl Area { } } - fn default_font_style(self) -> FontStyle { + pub fn default_font_style(self) -> FontStyle { FontStyle::default().with_bold(self == Area::Title) } - fn default_area_style(self) -> AreaStyle { + pub fn default_area_style(self) -> AreaStyle { AreaStyle { cell_style: self.default_cell_style(), font_style: self.default_font_style(), @@ -1373,6 +1373,21 @@ impl Not for Axis2 { } } +/// Can't convert `Axis3::Z` to `Axis2`. +pub struct ZAxis; + +impl TryFrom for Axis2 { + type Error = ZAxis; + + fn try_from(value: Axis3) -> Result { + match value { + Axis3::X => Ok(Axis2::X), + Axis3::Y => Ok(Axis2::Y), + Axis3::Z => Err(ZAxis), + } + } +} + /// A 2-dimensional `(x,y)` pair. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Coord2(pub EnumMap); diff --git a/rust/pspp/src/output/pivot/look_xml.rs b/rust/pspp/src/output/pivot/look_xml.rs index 0a15f042e7..2d338f6deb 100644 --- a/rust/pspp/src/output/pivot/look_xml.rs +++ b/rust/pspp/src/output/pivot/look_xml.rs @@ -348,7 +348,7 @@ struct PrintingProperties { } #[derive(Copy, Clone, Default, PartialEq)] -struct Dimension( +pub struct Dimension( /// In inches. f64, ); diff --git a/rust/pspp/src/output/spv/legacy_xml.rs b/rust/pspp/src/output/spv/legacy_xml.rs index b8bf8a6b6d..f0ca5803b8 100644 --- a/rust/pspp/src/output/spv/legacy_xml.rs +++ b/rust/pspp/src/output/spv/legacy_xml.rs @@ -23,6 +23,7 @@ use std::{ }; use enum_map::{Enum, EnumMap}; +use itertools::Itertools; use ordered_float::OrderedFloat; use serde::{Deserialize, de::Error as _}; @@ -31,11 +32,12 @@ use crate::{ format::{Decimal::Dot, F8_0, Type, UncheckedFormat}, output::{ pivot::{ - Area, AreaStyle, Color, HeadingRegion, HorzAlign, Look, PivotTable, RowParity, Value, - VertAlign, + self, Area, AreaStyle, Axis2, Axis3, Color, HeadingRegion, HorzAlign, Look, PivotTable, + RowParity, Value, VertAlign, }, spv::legacy_bin::DataValue, }, + variable, }; #[derive(Debug)] @@ -44,6 +46,12 @@ struct Ref { _phantom: PhantomData, } +impl Ref { + fn get<'a>(&self, table: &HashMap<&str, &'a T>) -> Option<&'a T> { + table.get(self.references.as_str()).map(|v| &**v) + } +} + impl<'de, T> Deserialize<'de> for Ref { fn deserialize(deserializer: D) -> Result where @@ -224,6 +232,13 @@ impl Visualization { todo!() }; + let mut axes = HashMap::new(); + for child in &graph.facet_layout.children { + if let FacetLayoutChild::FacetLevel(facet_level) = child { + axes.insert(facet_level.level, &facet_level.axis); + } + } + // Footnotes. // // Any pivot_value might refer to footnotes, so it's important to @@ -273,9 +288,7 @@ impl Visualization { { Style::decode( Some(*style), - styles - .get(graph.cell_style.references.as_str()) - .map(|v| &**v), + graph.cell_style.get(&styles), &mut look.areas[Area::Data(RowParity::Even)], ); look.areas[Area::Data(RowParity::Odd)] = @@ -332,6 +345,87 @@ impl Visualization { } } + fn decode_dimension( + variables: &[(&Series, usize)], + axes: &HashMap, + styles: &HashMap<&str, &Style>, + a: Axis3, + look: &mut Look, + ) { + let base_level = variables[0].1; + if let Ok(a) = Axis2::try_from(a) + && let Some(axis) = axes.get(&(base_level + variables.len())) + && let Some(label) = &axis.label + { + let out = &mut look.areas[Area::Labels(a)]; + *out = Area::Labels(a).default_area_style(); + Style::decode( + label.style.get(&styles), + label.text_frame_style.as_ref().and_then(|r| r.get(styles)), + out, + ); + } + if a == Axis3::Y + && let Some(axis) = axes.get(&(base_level + variables.len() - 1)) + {} + + todo!() + } + + fn decode_dimensions( + variables: &[VariableReference], + series: &HashMap<&str, Series>, + axes: &HashMap, + styles: &HashMap<&str, &Style>, + a: Axis3, + look: &mut Look, + level_ofs: usize, + ) -> Vec { + let variables = variables + .into_iter() + .zip(level_ofs..) + .map(|(vr, level)| { + series + .get(vr.reference.as_str()) + .filter(|s| !s.values.is_empty()) + .map(|s| (s, level)) + }) + .collect::>(); + let mut dim_vars = Vec::new(); + for var in variables { + if let Some((var, level)) = var { + dim_vars.push((var, level)); + } else if !dim_vars.is_empty() { + decode_dimension(&dim_vars, axes, styles, a, look); + dim_vars.clear(); + } + } + if !dim_vars.is_empty() { + decode_dimension(&dim_vars, axes, styles, a, look); + } + todo!() + } + + let cross = &graph.faceting.cross.children; + let columns = cross + .first() + .map(|child| child.variables()) + .unwrap_or_default(); + decode_dimensions(columns, &series, &axes, &styles, Axis3::X, &mut look, 1); + let rows = cross + .get(1) + .map(|child| child.variables()) + .unwrap_or_default(); + decode_dimensions( + rows, + &series, + &axes, + &styles, + Axis3::Y, + &mut look, + 1 + columns.len(), + ); + todo!() } } @@ -539,7 +633,7 @@ struct CategoricalDomain { #[serde(rename_all = "camelCase")] struct VariableReference { #[serde(rename = "@ref")] - reference: Option, + reference: String, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] @@ -1422,6 +1516,15 @@ enum CrossChild { Nest(Nest), } +impl CrossChild { + fn variables(&self) -> &[VariableReference] { + match self { + CrossChild::Unity => &[], + CrossChild::Nest(nest) => &nest.variable_references, + } + } +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Nest { @@ -1766,14 +1869,8 @@ impl Label { } fn decode_style(&self, area_style: &mut AreaStyle, styles: &HashMap<&str, &Style>) { - let fg = styles.get(self.style.references.as_str()).map(|v| &**v); - let bg = if let Some(text_frame_style) = &self.text_frame_style { - styles - .get(text_frame_style.references.as_str()) - .map(|v| &**v) - } else { - None - }; + let fg = self.style.get(styles); + let bg = self.text_frame_style.as_ref().and_then(|r| r.get(styles)); Style::decode(fg, bg, area_style); } } @@ -1864,7 +1961,7 @@ struct LabelFrame { } impl LabelFrame { - fn decode(&self, look: &mut Look, styles: &HashMap<&String, &Style>) { + fn decode(&self, look: &mut Look, styles: &HashMap<&str, &Style>) { let Some(label) = &self.label else { return }; let Some(purpose) = label.purpose else { return }; let area = match purpose { @@ -1874,13 +1971,11 @@ impl LabelFrame { Purpose::Layer => Area::Layers, Purpose::Footnote => Area::Footer, }; - let fg = styles.get(&label.style.references).map(|v| &**v); - let bg = if let Some(text_frame_style) = &label.text_frame_style { - styles.get(&&text_frame_style.references).map(|v| &**v) - } else { - None - }; - Style::decode(fg, bg, &mut look.areas[area]); + Style::decode( + label.style.get(styles), + label.text_frame_style.as_ref().and_then(|r| r.get(styles)), + &mut look.areas[area], + ); todo!() } } -- 2.30.2