cleanup
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 3 Jan 2026 00:19:03 +0000 (16:19 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 3 Jan 2026 00:19:03 +0000 (16:19 -0800)
rust/pspp/src/output/pivot.rs
rust/pspp/src/spv/read.rs
rust/pspp/src/spv/read/legacy_xml.rs

index 0f32575344aa6c0fc254aecb9d8e9abe45a5f760..ad9ccaf1c0edb9a845b1fba05f6868d1e008688b 100644 (file)
@@ -53,7 +53,7 @@ use std::{
     sync::Arc,
 };
 
-use chrono::NaiveDateTime;
+use chrono::{NaiveDateTime, Utc};
 use enum_iterator::Sequence;
 use enum_map::{Enum, EnumMap, enum_map};
 use itertools::Itertools;
@@ -62,7 +62,7 @@ use serde::{Deserialize, Serialize, ser::SerializeMap};
 use smallvec::SmallVec;
 
 use crate::{
-    format::{F40, F40_2, F40_3, Format, PCT40_1, Settings as FormatSettings},
+    format::{Decimal, F40, F40_2, F40_3, Format, PCT40_1, Settings as FormatSettings},
     output::pivot::{
         look::{Look, Sizing},
         value::{BareValue, Value, ValueOptions},
@@ -324,6 +324,20 @@ impl PivotTable {
         self
     }
 
+    /// Returns this pivot table with the specified decimal point.
+    pub fn with_decimal(mut self, decimal: Decimal) -> Self {
+        if self.style.settings.decimal != decimal {
+            Arc::make_mut(&mut self.style.settings).decimal = decimal;
+        }
+        self
+    }
+
+    /// Returns this pivot table with the date set as specified.
+    pub fn with_date(mut self, date: Option<NaiveDateTime>) -> Self {
+        self.metadata.date = date;
+        self
+    }
+
     /// Inserts `number` into the cell with the given `data_indexes`, drawing
     /// its format from `class`.
     pub fn insert_number(&mut self, data_indexes: &[usize], number: Option<f64>, class: Class) {
@@ -1531,7 +1545,7 @@ impl PivotTableStyle {
 }
 
 /// Metadata for a [PivotTable].
-#[derive(Clone, Debug, Default, Serialize)]
+#[derive(Clone, Debug, Serialize)]
 pub struct PivotTableMetadata {
     /// Title.
     ///
@@ -1592,6 +1606,26 @@ pub struct PivotTableMetadata {
     pub date: Option<NaiveDateTime>,
 }
 
+impl Default for PivotTableMetadata {
+    fn default() -> Self {
+        Self {
+            title: None,
+            caption: None,
+            corner_text: None,
+            notes: None,
+            notes_unexpanded: None,
+            command_local: None,
+            command_c: None,
+            subtype: None,
+            language: None,
+            locale: None,
+            dataset: None,
+            datafile: None,
+            date: Some(Utc::now().naive_local()),
+        }
+    }
+}
+
 impl PivotTableMetadata {
     /// Return this metadata with the given `subtype`.
     pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
index 0bceac883397c85bc289be25e455ff209c5e1fe5..8cb5987df4cfd8faec1aa00de52857a6339e236f 100644 (file)
@@ -251,6 +251,9 @@ pub enum Error {
         error: serde_path_to_error::Error<quick_xml::DeError>,
     },
 
+    /// Legacy table missing `graph` element.
+    LegacyMissingGraph,
+
     /// Graphs not yet implemented.
     GraphTodo,
 
index 325afa44adf63fd340fbc7a372d08b4c8eb6fc5e..d4af8374adc4d0d80eb1b0e86a76cfff4a467ee6 100644 (file)
@@ -25,7 +25,7 @@ use std::{
     sync::Arc,
 };
 
-use chrono::{NaiveDateTime, NaiveTime};
+use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
 use displaydoc::Display;
 use enum_map::{Enum, EnumMap};
 use hashbrown::HashSet;
@@ -114,7 +114,6 @@ impl Map {
                 Datum::String(relabel.to.clone())
             };
             self.0.insert(OrderedFloat(relabel.from), value);
-            // XXX warn on duplicate
         }
         (format.unwrap_or(F8_0), affixes)
     }
@@ -158,8 +157,6 @@ impl Map {
                         Datum::String(vme.to.clone())
                     };
                     self.0.insert(OrderedFloat(from), to);
-                } else {
-                    // XXX warn
                 }
             }
         }
@@ -195,15 +192,17 @@ pub enum LegacyXmlWarning {
 
     /// Unsupported applyToConverse.
     UnsupportedApplyToConverse,
+
+    /// Invalid creation date {0:?}.
+    InvalidCreationDate(String),
 }
 
 #[derive(Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 pub struct Visualization {
     /// In format `YYYY-MM-DD`.
-    // XXX parse this
     #[serde(rename = "@date")]
-    _date: String,
+    date: String,
 
     // Locale used for output, e.g. `en-US`.
     #[serde(rename = "@lang")]
@@ -225,6 +224,10 @@ pub struct Visualization {
 }
 
 impl Visualization {
+    fn decode_date(&self) -> Result<NaiveDate, LegacyXmlWarning> {
+        NaiveDate::parse_from_str(&self.date, "%Y-%m-%d")
+            .map_err(|_| LegacyXmlWarning::InvalidCreationDate(self.date.clone()))
+    }
     pub fn decode_series(
         &self,
         data: IndexMap<String, IndexMap<String, Vec<DataValue>>>,
@@ -675,7 +678,9 @@ impl Visualization {
                 | VisChild::Other => (),
             }
         }
-        let Some(graph) = graph else { todo!() }; //XXX
+        let Some(graph) = graph else {
+            return Err(super::Error::LegacyMissingGraph);
+        };
 
         let footnotes = self.decode_footnotes(graph, &labels[Purpose::Footnote]);
 
@@ -707,19 +712,25 @@ impl Visualization {
             warn,
         );
 
-        let dimensions = dims
-            .into_iter()
-            .map(|dim| (dim.axis, dim.dimension))
-            .collect::<Vec<_>>();
-        let mut pivot_table = PivotTable::new(dimensions)
-            .with_look(Arc::new(look))
-            .with_footnotes(footnotes)
-            .with_data(data)
-            .with_layer(&current_layer);
-        let decimal = Decimal::for_lang(&self.lang);
-        if pivot_table.style.settings.decimal != decimal {
-            Arc::make_mut(&mut pivot_table.style.settings).decimal = decimal;
-        }
+        let date = match self.decode_date() {
+            Ok(date) => Some(date.into()),
+            Err(w) => {
+                warn(w);
+                None
+            }
+        };
+        let mut pivot_table = PivotTable::new(
+            dims.into_iter()
+                .map(|dim| (dim.axis, dim.dimension))
+                .collect::<Vec<_>>(),
+        )
+        .with_look(Arc::new(look))
+        .with_footnotes(footnotes)
+        .with_data(data)
+        .with_layer(&current_layer)
+        .with_decimal(Decimal::for_lang(&self.lang))
+        .with_date(date);
+
         if let Some(title) = title {
             pivot_table = pivot_table.with_title(title);
         }
@@ -2095,7 +2106,6 @@ fn decode_dimension<'a>(
     axes: &HashMap<usize, &Axis>,
     styles: &HashMap<&str, &Style>,
     a: Axis3,
-
     footnotes: &pivot::Footnotes,
     dims: &mut Vec<Dim<'a>>,
 ) {