work rust
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Dec 2025 01:16:13 +0000 (17:16 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 13 Dec 2025 01:16:13 +0000 (17:16 -0800)
rust/pspp/src/output/pivot/value.rs
rust/pspp/src/spv/read/legacy_xml.rs
rust/pspp/src/spv/read/light.rs
rust/pspp/src/spv/write.rs

index bb908517011b5559ed97a07e7d1a0c4625a5d385..f71f2c733fbb59065763c0e675d33167af853ac3 100644 (file)
@@ -88,8 +88,7 @@ where
     {
         let value = self.0.borrow();
         match &value.inner {
-            ValueInner::Number(number_value) => number_value.serialize_bare(serializer),
-            ValueInner::String(string_value) => string_value.s.serialize(serializer),
+            ValueInner::Datum(datum_value) => datum_value.serialize_bare(serializer),
             ValueInner::Variable(variable_value) => variable_value.var_name.serialize(serializer),
             ValueInner::Text(text_value) => text_value.localized.serialize(serializer),
             ValueInner::Markup(markup) => markup.serialize(serializer),
@@ -146,7 +145,7 @@ impl Value {
     where
         B: EncodedString,
     {
-        Self::new(ValueInner::Number(NumberValue::new(datum)))
+        Self::new(ValueInner::Datum(DatumValue::new(datum)))
     }
 
     /// Returns this value with its display format set to `format`.
@@ -183,7 +182,7 @@ impl Value {
     }
 
     pub fn new_number(number: Option<f64>) -> Self {
-        Self::new(ValueInner::Number(NumberValue::new_number(number)))
+        Self::new(ValueInner::Datum(DatumValue::new_number(number)))
     }
 
     pub fn new_integer(x: Option<f64>) -> Self {
@@ -226,13 +225,8 @@ impl Value {
         footnotes.sort_by_key(|f| f.index);
     }
     pub fn with_show_value_label(mut self, show: Option<Show>) -> Self {
-        let new_show = show;
-        match &mut self.inner {
-            ValueInner::Number(NumberValue { show, .. })
-            | ValueInner::String(StringValue { show, .. }) => {
-                *show = new_show;
-            }
-            _ => (),
+        if let Some(datum_value) = self.inner.as_datum_value_mut() {
+            datum_value.show = show;
         }
         self
     }
@@ -242,20 +236,15 @@ impl Value {
         }
         self
     }
-    pub fn with_value_label(mut self, label: Option<String>) -> Self {
-        match &mut self.inner {
-            ValueInner::Number(NumberValue { value_label, .. })
-            | ValueInner::String(StringValue { value_label, .. }) => *value_label = label.clone(),
-            _ => (),
+    pub fn with_value_label(mut self, value_label: Option<String>) -> Self {
+        if let Some(datum_value) = self.inner.as_datum_value_mut() {
+            datum_value.value_label = value_label.clone()
         }
         self
     }
     pub fn with_variable_name(mut self, variable_name: Option<String>) -> Self {
         match &mut self.inner {
-            ValueInner::Number(NumberValue { variable, .. })
-            | ValueInner::String(StringValue {
-                var_name: variable, ..
-            }) => *variable = variable_name,
+            ValueInner::Datum(DatumValue { variable, .. }) => *variable = variable_name,
             ValueInner::Variable(VariableValue {
                 var_name: variable, ..
             }) => {
@@ -415,8 +404,8 @@ impl<'a> DisplayValue<'a> {
     }
 
     pub fn var_type(&self) -> VarType {
-        if let ValueInner::Number(NumberValue { datum, .. }) = &self.inner
-            && datum.is_number()
+        if let Some(datum_value) = self.inner.as_datum_value()
+            && datum_value.datum.is_number()
             && self.show_label.is_none()
         {
             VarType::Numeric
@@ -429,24 +418,11 @@ impl<'a> DisplayValue<'a> {
 impl Display for DisplayValue<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self.inner {
-            ValueInner::Number(number_value) => number_value.display(self, f),
-
-            ValueInner::String(StringValue { s, .. })
-            | ValueInner::Variable(VariableValue { var_name: s, .. }) => {
-                match (self.show_value, self.show_label) {
-                    (true, None) => write!(f, "{s}"),
-                    (false, Some(label)) => write!(f, "{label}"),
-                    (true, Some(label)) => write!(f, "{s} {label}"),
-                    (false, None) => unreachable!(),
-                }
-            }
-
+            ValueInner::Datum(datum_value) => datum_value.display(self, f),
+            ValueInner::Variable(variable_value) => variable_value.display(self, f),
             ValueInner::Markup(markup) => write!(f, "{markup}"),
-
             ValueInner::Text(text_value) => write!(f, "{text_value}"),
-
             ValueInner::Template(template_value) => template_value.display(self, f),
-
             ValueInner::Empty => Ok(()),
         }?;
 
@@ -478,8 +454,7 @@ impl Value {
 impl Debug for Value {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let name = match &self.inner {
-            ValueInner::Number(_) => "Number",
-            ValueInner::String(_) => "String",
+            ValueInner::Datum(_) => "Datum",
             ValueInner::Variable(_) => "Variable",
             ValueInner::Text(_) => "Text",
             ValueInner::Markup(_) => "Markup",
@@ -499,7 +474,7 @@ impl Debug for Value {
 
 /// A datum and how to display it.
 #[derive(Clone, Debug, PartialEq)]
-pub struct NumberValue {
+pub struct DatumValue {
     /// The datum.
     pub datum: Datum<WithEncoding<ByteString>>,
 
@@ -523,7 +498,7 @@ pub struct NumberValue {
     pub value_label: Option<String>,
 }
 
-impl Serialize for NumberValue {
+impl Serialize for DatumValue {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: serde::Serializer,
@@ -551,7 +526,7 @@ impl Serialize for NumberValue {
     }
 }
 
-impl NumberValue {
+impl DatumValue {
     pub fn new_number(number: Option<f64>) -> Self {
         Self::new(&Datum::<&str>::Number(number))
     }
@@ -631,37 +606,7 @@ impl NumberValue {
         }
     }
 }
-/// A string value and how to display it.
-#[derive(Clone, Debug, Serialize, PartialEq)]
-pub struct StringValue {
-    /// The string value.
-    ///
-    /// If `hex` is true, this should contain hex digits, not raw binary data
-    /// (otherwise it would be impossible to encode non-UTF-8 data).
-    pub s: String,
-
-    /// True if `s` is hex digits.
-    pub hex: bool,
-
-    /// Whether to show `s` or `value_label` or both.
-    ///
-    /// If this is unset, then a higher-level default is used.
-    pub show: Option<Show>,
-
-    /// The name of the variable that `s` came from, if any.
-    pub var_name: Option<String>,
 
-    /// The value label associated with `s`, if any.
-    pub value_label: Option<String>,
-}
-impl StringValue {
-    pub fn with_format(self, format: Format) -> Self {
-        Self {
-            hex: format.type_() == Type::AHex,
-            ..self
-        }
-    }
-}
 /// A variable name.
 #[derive(Clone, Debug, Serialize, PartialEq)]
 pub struct VariableValue {
@@ -677,6 +622,21 @@ pub struct VariableValue {
     pub show: Option<Show>,
 }
 
+impl VariableValue {
+    fn display(&self, display: &DisplayValue<'_>, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        if display.show_value {
+            f.write_str(&self.var_name)?;
+        }
+        if let Some(label) = display.show_label {
+            if display.show_value {
+                f.write_char(' ')?;
+            }
+            f.write_str(label)?;
+        }
+        Ok(())
+    }
+}
+
 /// A text string.
 ///
 /// Whereas a [StringValue] is usually related to data, a `TextValue` is used
@@ -886,15 +846,10 @@ impl TemplateValue {
 #[derive(Clone, Debug, Default, Serialize, PartialEq)]
 #[serde(rename_all = "snake_case")]
 pub enum ValueInner {
-    /// A numeric data value.
-    Number(
-        /// The number.
-        NumberValue,
-    ),
-    /// A string data value.
-    String(
-        /// The string.
-        StringValue,
+    /// A [Datum] value.
+    Datum(
+        /// The datum.
+        DatumValue,
     ),
     /// A variable name.
     Variable(
@@ -925,34 +880,27 @@ impl ValueInner {
     pub const fn is_empty(&self) -> bool {
         matches!(self, Self::Empty)
     }
-    pub fn with_format(self, format: Format) -> Self {
-        match self {
-            ValueInner::Number(number_value) => Self::Number(number_value.with_format(format)),
-            ValueInner::String(string_value) => Self::String(string_value.with_format(format)),
-            _ => self,
+    pub fn with_format(mut self, format: Format) -> Self {
+        if let Some(datum_value) = self.as_datum_value_mut() {
+            datum_value.format = format;
         }
+        self
     }
 
-    pub fn with_honor_small(self, honor_small: bool) -> Self {
-        match self {
-            ValueInner::Number(number_value) => {
-                Self::Number(number_value.with_honor_small(honor_small))
-            }
-            _ => self,
+    pub fn with_honor_small(mut self, honor_small: bool) -> Self {
+        if let Some(datum_value) = self.as_datum_value_mut() {
+            datum_value.honor_small = honor_small;
         }
+        self
     }
 
     pub fn datum(&self) -> Option<&Datum<WithEncoding<ByteString>>> {
-        match self {
-            ValueInner::Number(datum_value) => Some(&datum_value.datum),
-            _ => None,
-        }
+        self.as_datum_value().map(|d| &d.datum)
     }
 
     fn show(&self) -> Option<Show> {
         match self {
-            ValueInner::Number(NumberValue { show, .. })
-            | ValueInner::String(StringValue { show, .. })
+            ValueInner::Datum(DatumValue { show, .. })
             | ValueInner::Variable(VariableValue { show, .. }) => *show,
             _ => None,
         }
@@ -963,13 +911,8 @@ impl ValueInner {
     }
 
     fn value_label(&self) -> Option<&str> {
-        match self {
-            ValueInner::Number(NumberValue { value_label, .. })
-            | ValueInner::String(StringValue { value_label, .. }) => {
-                value_label.as_ref().map(String::as_str)
-            }
-            _ => None,
-        }
+        self.as_datum_value()
+            .and_then(|d| d.value_label.as_ref().map(String::as_str))
     }
 
     fn variable_label(&self) -> Option<&str> {
@@ -987,26 +930,21 @@ impl ValueInner {
             _ => None,
         }
     }
-}
 
-#[derive(Clone, Debug, Default, PartialEq)]
-pub struct ValueStyle {
-    pub cell_style: Option<CellStyle>,
-    pub font_style: Option<FontStyle>,
-    pub subscripts: Vec<String>,
-    pub footnotes: Vec<Arc<Footnote>>,
-}
+    pub fn as_datum_value(&self) -> Option<&DatumValue> {
+        match self {
+            ValueInner::Datum(datum) => Some(datum),
+            _ => None,
+        }
+    }
 
-impl ValueStyle {
-    pub fn is_empty(&self) -> bool {
-        self.font_style.is_none()
-            && self.cell_style.is_none()
-            && self.subscripts.is_empty()
-            && self.footnotes.is_empty()
+    pub fn as_datum_value_mut(&mut self) -> Option<&mut DatumValue> {
+        match self {
+            ValueInner::Datum(datum) => Some(datum),
+            _ => None,
+        }
     }
-}
 
-impl ValueInner {
     // Returns an object that will format this value.  Settings on `options`
     // control whether variable and value labels are included.
     pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
@@ -1052,6 +990,23 @@ impl ValueInner {
     }
 }
 
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct ValueStyle {
+    pub cell_style: Option<CellStyle>,
+    pub font_style: Option<FontStyle>,
+    pub subscripts: Vec<String>,
+    pub footnotes: Vec<Arc<Footnote>>,
+}
+
+impl ValueStyle {
+    pub fn is_empty(&self) -> bool {
+        self.font_style.is_none()
+            && self.cell_style.is_none()
+            && self.subscripts.is_empty()
+            && self.footnotes.is_empty()
+    }
+}
+
 /// Options for displaying a [Value].
 #[derive(Copy, Clone, Debug)]
 pub struct ValueOptions {
index 65391c9dba1f3675f2caf0bc7071fc6fa1b31ffd..b3bef464a66fa3f6b0f1801a5f50bc178789a6a9 100644 (file)
@@ -31,7 +31,7 @@ use ordered_float::OrderedFloat;
 use serde::Deserialize;
 
 use crate::{
-    data::Datum,
+    data::{Datum, EncodedString},
     format::{self, Decimal::Dot, F8_0, F40_2, Type, UncheckedFormat},
     output::pivot::{
         self, Axis2, Axis3, Category, CategoryLocator, Dimension, Group, Leaf, Length, PivotTable,
@@ -39,7 +39,7 @@ use crate::{
             self, Area, AreaStyle, CellStyle, Color, HeadingRegion, HorzAlign, Look, RowParity,
             VertAlign,
         },
-        value::{Value, ValueInner},
+        value::Value,
     },
     spv::read::legacy_bin::DataValue,
 };
@@ -1851,26 +1851,30 @@ impl Style {
                 Some(SetFormatChild::ElapsedTimeFormat(format)) => Some(format.decode()),
                 None => None,
             };
-            if let Some(format) = format {
-                match &mut value.inner {
-                    ValueInner::Number(number) => {
-                        number.format = format;
+            if let Some(format) = format
+                && let Some(datum_value) = value.inner.as_datum_value_mut()
+            {
+                match &datum_value.datum {
+                    Datum::Number(_) => {
+                        datum_value.format = format;
                     }
-                    ValueInner::String(string) => {
+                    Datum::String(string) => {
                         if format.type_().category() == format::Category::Date
-                            && let Ok(date_time) =
-                                NaiveDateTime::parse_from_str(&string.s, "%Y-%m-%dT%H:%M:%S%.3f")
+                            && let Ok(date_time) = NaiveDateTime::parse_from_str(
+                                &string.as_str(),
+                                "%Y-%m-%dT%H:%M:%S%.3f",
+                            )
                         {
                             value.inner = Value::new_date(date_time).with_format(format).inner;
                         } else if format.type_().category() == format::Category::Time
-                            && let Ok(time) = NaiveTime::parse_from_str(&string.s, "%H:%M:%S%.3f")
+                            && let Ok(time) =
+                                NaiveTime::parse_from_str(&string.as_str(), "%H:%M:%S%.3f")
                         {
                             value.inner = Value::new_time(time).with_format(format).inner;
-                        } else if let Ok(number) = string.s.parse::<f64>() {
+                        } else if let Ok(number) = string.as_str().parse::<f64>() {
                             value.inner = Value::new_number(Some(number)).with_format(format).inner;
                         }
                     }
-                    _ => (),
                 }
             }
         }
index 5521b208e4db585c21f46b11dc8cbdd0910fb393..21f549f9db81a6943b18bd2635a6e4f2e9241b71 100644 (file)
@@ -16,6 +16,7 @@ use encoding_rs::{Encoding, WINDOWS_1252};
 use enum_map::{EnumMap, enum_map};
 
 use crate::{
+    data::Datum,
     format::{
         CC, Decimal, Decimals, Epoch, F40, Format, NumberStyle, Settings, Type, UncheckedFormat,
         Width,
@@ -28,7 +29,7 @@ use crate::{
             RowColBorder, RowParity, Stroke, VertAlign,
         },
         parse_bool,
-        value::{self, StringValue, TemplateValue, ValueStyle, VariableValue},
+        value::{self, DatumValue, TemplateValue, ValueStyle, VariableValue},
     },
     settings::Show,
 };
@@ -1232,11 +1233,12 @@ impl ValueText {
 
 impl ValueString {
     fn decode(&self, encoding: &'static Encoding, footnotes: &pivot::Footnotes) -> value::Value {
-        value::Value::new(pivot::value::ValueInner::String(StringValue {
-            s: self.s.decode(encoding),
-            hex: self.format.type_() == Type::AHex,
+        value::Value::new(pivot::value::ValueInner::Datum(DatumValue {
+            datum: Datum::new_utf8(self.s.decode(encoding)),
+            format: self.format,
             show: self.show,
-            var_name: self.var_name.decode_optional(encoding),
+            honor_small: false,
+            variable: self.var_name.decode_optional(encoding),
             value_label: self.value_label.decode_optional(encoding),
         }))
         .with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
index 98cfcb65fa90cb904169734132d315fde92aca1b..042db878856d56324a36758ca25c3d2b4dc9cf85 100644 (file)
@@ -1262,7 +1262,7 @@ impl BinWrite for Value {
         args: Self::Args<'_>,
     ) -> binrw::BinResult<()> {
         match &self.inner {
-            ValueInner::Number(number_value) => match &number_value.datum {
+            ValueInner::Datum(number_value) => match &number_value.datum {
                 Datum::Number(number) => {
                     let format = SpvFormat {
                         format: number_value.format,
@@ -1315,25 +1315,6 @@ impl BinWrite for Value {
                         .write_options(writer, endian, args)?;
                 }
             },
-            ValueInner::String(string) => {
-                (
-                    4u8,
-                    ValueMod::new(self),
-                    SpvFormat {
-                        format: if string.hex {
-                            Format::new(Type::AHex, (string.s.len() * 2) as u16, 0).unwrap()
-                        } else {
-                            Format::new(Type::A, (string.s.len()) as u16, 0).unwrap()
-                        },
-                        honor_small: false,
-                    },
-                    SpvString::optional(&string.value_label),
-                    SpvString::optional(&string.var_name),
-                    Show::as_spv(&string.show),
-                    SpvString(&string.s),
-                )
-                    .write_options(writer, endian, args)?;
-            }
             ValueInner::Variable(variable) => {
                 (
                     5u8,