fixes
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 25 Dec 2025 01:25:14 +0000 (17:25 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 25 Dec 2025 01:25:14 +0000 (17:25 -0800)
rust/pspp/src/output/pivot.rs
rust/pspp/src/output/pivot/look_xml.rs
rust/pspp/src/output/table.rs

index 9b099e71d3d9bda4d5d4ae0b73e388de8f679d9d..0f32575344aa6c0fc254aecb9d8e9abe45a5f760 100644 (file)
@@ -1402,10 +1402,10 @@ pub enum FootnoteMarkerType {
 #[serde(rename_all = "camelCase")]
 pub enum FootnoteMarkerPosition {
     /// Subscripts.
-    #[default]
     Subscript,
 
     /// Superscripts.
+    #[default]
     Superscript,
 }
 
index dfbb93e5f4331a64acaec49fd393c2f027884d7c..ecaf2c96f86fe038a12529a8312757c5b4a1ee5c 100644 (file)
 
 use std::{fmt::Debug, num::ParseFloatError, str::FromStr};
 
-use enum_map::enum_map;
+use enum_map::{EnumMap, enum_map};
 use serde::{Deserialize, de::Visitor};
 
 use crate::output::pivot::{
     Axis2, FootnoteMarkerPosition, FootnoteMarkerType,
     look::{
-        self, Area, AreaStyle, Border, BorderStyle, BoxBorder, Color, HeadingRegion, HorzAlign,
-        LabelPosition, Look, RowColBorder, RowParity, VertAlign,
+        self, Area, AreaStyle, Border, BoxBorder, Color, HeadingRegion, HorzAlign, LabelPosition,
+        Look, RowColBorder, RowParity, Stroke, VertAlign,
     },
 };
 use thiserror::Error as ThisError;
@@ -63,27 +63,7 @@ impl From<TableProperties> for Look {
                     Area::Data(row) => table_properties.cell_format_properties.data.style.as_area_style(row),
                     Area::Layers => table_properties.cell_format_properties.layers.style.as_area_style(RowParity::Even),
                 },
-                borders: enum_map!  {
-                    Border::Title => table_properties.border_properties.title_layer_separator,
-                    Border::OuterFrame(BoxBorder::Left) => table_properties.border_properties.left_outer_frame,
-                    Border::OuterFrame(BoxBorder::Top) => table_properties.border_properties.top_outer_frame,
-                    Border::OuterFrame(BoxBorder::Right) => table_properties.border_properties.right_outer_frame,
-                    Border::OuterFrame(BoxBorder::Bottom) => table_properties.border_properties.bottom_outer_frame,
-                    Border::InnerFrame(BoxBorder::Left) => table_properties.border_properties.left_inner_frame,
-                    Border::InnerFrame(BoxBorder::Top) => table_properties.border_properties.top_inner_frame,
-                    Border::InnerFrame(BoxBorder::Right) => table_properties.border_properties.right_inner_frame,
-                    Border::InnerFrame(BoxBorder::Bottom) => table_properties.border_properties.bottom_inner_frame,
-                    Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::X)) => table_properties.border_properties.horizontal_dimension_border_columns,
-                    Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => table_properties.border_properties.vertical_category_border_columns,
-                    Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X)) => table_properties.border_properties.horizontal_dimension_border_rows,
-                    Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::Y)) => table_properties.border_properties.vertical_category_border_rows,
-                    Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::X)) => table_properties.border_properties.horizontal_category_border_columns,
-                    Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => table_properties.border_properties.vertical_category_border_columns,
-                    Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::X)) => table_properties.border_properties.horizontal_category_border_rows,
-                    Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::Y)) => table_properties.border_properties.vertical_category_border_rows,
-                    Border::DataLeft => table_properties.border_properties.data_area_left,
-                    Border::DataTop => table_properties.border_properties.data_area_top,
-                },
+            borders: table_properties.border_properties.decode(),
                 print_all_layers: table_properties.printing_properties.print_all_layers,
                 paginate_layers: table_properties
                     .printing_properties
@@ -116,6 +96,7 @@ impl From<TableProperties> for Look {
 }
 
 #[derive(Clone, Debug, Deserialize)]
+#[serde(default)]
 struct GeneralProperties {
     #[serde(rename = "@hideEmptyRows")]
     hide_empty_rows: bool,
@@ -136,8 +117,21 @@ struct GeneralProperties {
     row_label_position: LabelPosition,
 }
 
-#[derive(Clone, Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
+impl Default for GeneralProperties {
+    fn default() -> Self {
+        Self {
+            hide_empty_rows: true,
+            maximum_column_width: 120,
+            minimum_column_width: 36,
+            maximum_row_width: 72,
+            minimum_row_width: 36,
+            row_label_position: LabelPosition::Corner,
+        }
+    }
+}
+
+#[derive(Clone, Deserialize, Debug, Default)]
+#[serde(rename_all = "camelCase", default)]
 struct FootnoteProperties {
     #[serde(rename = "@markerPosition")]
     marker_position: FootnoteMarkerPosition,
@@ -293,28 +287,97 @@ enum LabelLocationVertical {
     Center,
 }
 
-#[derive(Clone, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
+#[derive(Clone, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase", default)]
 struct BorderProperties {
-    bottom_inner_frame: BorderStyle,
-    bottom_outer_frame: BorderStyle,
-    data_area_left: BorderStyle,
-    data_area_top: BorderStyle,
-    horizontal_category_border_columns: BorderStyle,
-    horizontal_category_border_rows: BorderStyle,
-    horizontal_dimension_border_columns: BorderStyle,
-    horizontal_dimension_border_rows: BorderStyle,
-    left_inner_frame: BorderStyle,
-    left_outer_frame: BorderStyle,
-    right_inner_frame: BorderStyle,
-    right_outer_frame: BorderStyle,
-    title_layer_separator: BorderStyle,
-    top_inner_frame: BorderStyle,
-    top_outer_frame: BorderStyle,
-    vertical_category_border_columns: BorderStyle,
-    vertical_category_border_rows: BorderStyle,
-    vertical_dimension_border_rows: BorderStyle,
-    vertical_dimension_border_columns: BorderStyle,
+    bottom_inner_frame: Option<BorderStyle>,
+    bottom_outer_frame: Option<BorderStyle>,
+    data_area_left: Option<BorderStyle>,
+    data_area_top: Option<BorderStyle>,
+    horizontal_category_border_columns: Option<BorderStyle>,
+    horizontal_category_border_rows: Option<BorderStyle>,
+    horizontal_dimension_border_columns: Option<BorderStyle>,
+    horizontal_dimension_border_rows: Option<BorderStyle>,
+    left_inner_frame: Option<BorderStyle>,
+    left_outer_frame: Option<BorderStyle>,
+    right_inner_frame: Option<BorderStyle>,
+    right_outer_frame: Option<BorderStyle>,
+    title_layer_separator: Option<BorderStyle>,
+    top_inner_frame: Option<BorderStyle>,
+    top_outer_frame: Option<BorderStyle>,
+    vertical_category_border_columns: Option<BorderStyle>,
+    vertical_category_border_rows: Option<BorderStyle>,
+    vertical_dimension_border_rows: Option<BorderStyle>,
+    vertical_dimension_border_columns: Option<BorderStyle>,
+}
+
+impl BorderProperties {
+    fn get_style(&self, border: Border) -> &Option<BorderStyle> {
+        match border {
+            Border::Title => &self.title_layer_separator,
+            Border::OuterFrame(BoxBorder::Left) => &self.left_outer_frame,
+            Border::OuterFrame(BoxBorder::Top) => &self.top_outer_frame,
+            Border::OuterFrame(BoxBorder::Right) => &self.right_outer_frame,
+            Border::OuterFrame(BoxBorder::Bottom) => &self.bottom_outer_frame,
+            Border::InnerFrame(BoxBorder::Left) => &self.left_inner_frame,
+            Border::InnerFrame(BoxBorder::Top) => &self.top_inner_frame,
+            Border::InnerFrame(BoxBorder::Right) => &self.right_inner_frame,
+            Border::InnerFrame(BoxBorder::Bottom) => &self.bottom_inner_frame,
+            Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::X)) => {
+                &self.horizontal_dimension_border_columns
+            }
+            Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => {
+                &self.vertical_category_border_columns
+            }
+            Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X)) => {
+                &self.horizontal_dimension_border_rows
+            }
+            Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::Y)) => {
+                &self.vertical_category_border_rows
+            }
+            Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::X)) => {
+                &self.horizontal_category_border_columns
+            }
+            Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => {
+                &self.vertical_category_border_columns
+            }
+            Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::X)) => {
+                &self.horizontal_category_border_rows
+            }
+            Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::Y)) => {
+                &self.vertical_category_border_rows
+            }
+            Border::DataLeft => &self.data_area_left,
+            Border::DataTop => &self.data_area_top,
+        }
+    }
+
+    fn decode(&self) -> EnumMap<Border, look::BorderStyle> {
+        EnumMap::from_fn(|border: Border| {
+            let mut base = border.default_border_style();
+            if let Some(style) = self.get_style(border) {
+                if let Some(stroke) = style.stroke {
+                    base.stroke = stroke;
+                }
+                if let Some(color) = style.color {
+                    base.color = color;
+                }
+            }
+            base
+        })
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase", default)]
+struct BorderStyle {
+    /// The kind of line to draw.
+    #[serde(rename = "@borderStyleType")]
+    pub stroke: Option<Stroke>,
+
+    /// Line color.
+    #[serde(rename = "@color")]
+    pub color: Option<Color>,
 }
 
 #[derive(Clone, Debug, Default, Deserialize)]
index 5d63ba8a7c6a007f2e4a22f3270a4527adfe55ce..c9bd0ee2ebbeeaef94f2151ab5843b1506ed3e2d 100644 (file)
@@ -40,6 +40,7 @@ use crate::{
         Axis2, Footnote,
         look::{
             Area, AreaStyle, Border, BorderStyle, CellStyle, FontStyle, HeadingRegion, HorzAlign,
+            RowParity,
         },
         value::{DisplayValue, Value, ValueInner, ValueOptions},
     },
@@ -249,11 +250,8 @@ impl Content {
     pub fn row_span(&self) -> usize {
         self.span(Axis2::Y)
     }
-}
-
-impl Default for Content {
-    fn default() -> Self {
-        Self::Value(CellInner::default())
+    pub fn default_for_area(area: Area) -> Self {
+        Self::Value(CellInner::new(area, Default::default()))
     }
 }
 
@@ -344,7 +342,15 @@ impl Table {
         Self {
             n,
             h: headers,
-            contents: Array::default((n.x, n.y)),
+            contents: Array::from_shape_fn((n.x, n.y), |(x, y)| {
+                let area = match (x < headers.x, y < headers.y) {
+                    (true, true) => Area::Corner,
+                    (true, false) => Area::Labels(Axis2::Y),
+                    (false, true) => Area::Labels(Axis2::X),
+                    (false, false) => Area::Data(RowParity::Even),
+                };
+                Content::default_for_area(area)
+            }),
             areas,
             borders,
             rules: enum_map! {