From 8d77373873fe401d73501c177fd93865264218a8 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 12 Dec 2025 17:16:13 -0800 Subject: [PATCH] work --- rust/pspp/src/output/pivot/value.rs | 197 +++++++++++---------------- rust/pspp/src/spv/read/legacy_xml.rs | 28 ++-- rust/pspp/src/spv/read/light.rs | 12 +- rust/pspp/src/spv/write.rs | 21 +-- 4 files changed, 100 insertions(+), 158 deletions(-) diff --git a/rust/pspp/src/output/pivot/value.rs b/rust/pspp/src/output/pivot/value.rs index bb90851701..f71f2c733f 100644 --- a/rust/pspp/src/output/pivot/value.rs +++ b/rust/pspp/src/output/pivot/value.rs @@ -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) -> Self { - Self::new(ValueInner::Number(NumberValue::new_number(number))) + Self::new(ValueInner::Datum(DatumValue::new_number(number))) } pub fn new_integer(x: Option) -> Self { @@ -226,13 +225,8 @@ impl Value { footnotes.sort_by_key(|f| f.index); } pub fn with_show_value_label(mut self, show: Option) -> 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) -> 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) -> 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) -> 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>, @@ -523,7 +498,7 @@ pub struct NumberValue { pub value_label: Option, } -impl Serialize for NumberValue { +impl Serialize for DatumValue { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -551,7 +526,7 @@ impl Serialize for NumberValue { } } -impl NumberValue { +impl DatumValue { pub fn new_number(number: Option) -> 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, - - /// The name of the variable that `s` came from, if any. - pub var_name: Option, - /// The value label associated with `s`, if any. - pub value_label: Option, -} -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, } +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>> { - match self { - ValueInner::Number(datum_value) => Some(&datum_value.datum), - _ => None, - } + self.as_datum_value().map(|d| &d.datum) } fn show(&self) -> Option { 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, - pub font_style: Option, - pub subscripts: Vec, - pub footnotes: Vec>, -} + 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, + pub font_style: Option, + pub subscripts: Vec, + pub footnotes: Vec>, +} + +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 { diff --git a/rust/pspp/src/spv/read/legacy_xml.rs b/rust/pspp/src/spv/read/legacy_xml.rs index 65391c9dba..b3bef464a6 100644 --- a/rust/pspp/src/spv/read/legacy_xml.rs +++ b/rust/pspp/src/spv/read/legacy_xml.rs @@ -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::() { + } else if let Ok(number) = string.as_str().parse::() { value.inner = Value::new_number(Some(number)).with_format(format).inner; } } - _ => (), } } } diff --git a/rust/pspp/src/spv/read/light.rs b/rust/pspp/src/spv/read/light.rs index 5521b208e4..21f549f9db 100644 --- a/rust/pspp/src/spv/read/light.rs +++ b/rust/pspp/src/spv/read/light.rs @@ -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)) diff --git a/rust/pspp/src/spv/write.rs b/rust/pspp/src/spv/write.rs index 98cfcb65fa..042db87885 100644 --- a/rust/pspp/src/spv/write.rs +++ b/rust/pspp/src/spv/write.rs @@ -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, -- 2.30.2