-use std::{
- fmt::{Debug, Display, Write},
- iter::{once, repeat},
- sync::Arc,
-};
-
-use chrono::NaiveDateTime;
-use serde::{
- Serialize, Serializer,
- ser::{SerializeMap, SerializeStruct},
-};
+// PSPP - a program for statistical analysis.
+// Copyright (C) 2025 Free Software Foundation, Inc.
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Data cell contents.
+//!
+//! This module contains [Value], which is the contents of a single pivot table
+//! cell, plus what it in turn contains.
+
+// Warn about missing docs, but not for items declared with `#[cfg(test)]`.
+#![cfg_attr(not(test), warn(missing_docs))]
use crate::{
- calendar::date_time_to_pspp,
- data::{Datum, EncodedString},
- format::{DATETIME40_0, F8_2, F40, Format, Type, UncheckedFormat},
+ calendar::{date_time_to_pspp, time_to_pspp},
+ data::{ByteString, Datum, EncodedString, WithEncoding},
+ format::{DATETIME40_0, F8_2, F40, Format, TIME40_0, Type, UncheckedFormat},
output::pivot::{
DisplayMarker, Footnote, FootnoteMarkerType, PivotTable,
look::{CellStyle, FontStyle},
spv::html::Markup,
variable::{VarType, Variable},
};
+use chrono::{NaiveDateTime, NaiveTime};
+use serde::{
+ Serialize, Serializer,
+ ser::{SerializeMap, SerializeStruct},
+};
+use std::{
+ borrow::Borrow,
+ fmt::{Debug, Display, Write},
+ iter::{once, repeat},
+ sync::Arc,
+};
/// The content of a single pivot table cell.
///
self.inner.serialize(serializer)
}
}
-
-/// Wrapper for [Value] that uses [Value::serialize_bare] for serialization.
-#[derive(Serialize)]
-pub struct BareValue<'a>(#[serde(serialize_with = "Value::serialize_bare")] pub &'a Value);
-
-impl Value {
- pub fn serialize_bare<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+/// Wrapper for [Value] that serializes in a plain way:
+///
+/// - Numbers: The number.
+/// - Strings: The string.
+/// - Variables: The variable name.
+/// - Text: The localized text string.
+/// - Markup: A string containing HTML for the markup.
+/// - Template: The formatted template string.
+/// - Empty: `()`.
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
+pub struct BareValue<T>(pub T);
+impl<T> Serialize for BareValue<T>
+where
+ T: Borrow<Value>,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
- match &self.inner {
+ 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::Variable(variable_value) => variable_value.var_name.serialize(serializer),
ValueInner::Text(text_value) => text_value.localized.serialize(serializer),
ValueInner::Markup(markup) => markup.serialize(serializer),
- ValueInner::Template(template_value) => template_value.localized.serialize(serializer),
- ValueInner::Empty => serializer.serialize_none(),
+ ValueInner::Template(_) => value.display(()).to_string().serialize(serializer),
+ ValueInner::Empty => ().serialize(serializer),
}
}
-
+}
+impl Value {
+ /// Constructs a new `Value`, initially with no styling.
+ ///
+ /// Usually one of the other constructors is more convenient.
pub fn new(inner: ValueInner) -> Self {
Self {
inner,
styling: None,
}
}
- pub fn new_date_time(date_time: NaiveDateTime) -> Self {
- Self::new_number_with_format(Some(date_time_to_pspp(date_time)), DATETIME40_0)
+
+ /// Constructs a new `Value` as a number whose value is `date_time`
+ /// converted to the [PSPP date representation](crate::calendar).
+ pub fn new_date(date_time: NaiveDateTime) -> Self {
+ Self::new_number(Some(date_time_to_pspp(date_time))).with_format(DATETIME40_0)
}
- pub fn new_number_with_format(x: Option<f64>, format: Format) -> Self {
- Self::new(ValueInner::Number(NumberValue {
- show: None,
- format,
- honor_small: false,
- value: x,
- variable: None,
- value_label: None,
- }))
+
+ /// Constructs a new `Value` as a number whose value is `time`
+ /// converted to the [PSPP time representation](crate::calendar).
+ pub fn new_time(time: NaiveTime) -> Self {
+ Self::new_number(Some(time_to_pspp(time))).with_format(TIME40_0)
}
+
+ /// Constructs a new `Value` from `variable`.
pub fn new_variable(variable: &Variable) -> Self {
Self::new(ValueInner::Variable(VariableValue {
show: None,
variable_label: variable.label.clone(),
}))
}
- pub fn new_datum<B>(value: &Datum<B>) -> Self
+
+ /// Construct a new `Value` from `datum` with a default format. Some
+ /// related useful methods are:
+ ///
+ /// - [with_source_variable], to add information about the variable that the
+ /// datum came from (or use [new_datum_from_variable] as a convenience to
+ /// combine both).
+ ///
+ /// - [with_format] to override the default format.
+ ///
+ /// [with_source_variable]: Self::with_source_variable
+ /// [new_datum_from_variable]: Self::new_datum_from_variable
+ /// [with_format]: Self::with_format
+ pub fn new_datum<B>(datum: &Datum<B>) -> Self
where
B: EncodedString,
{
- match value {
+ match datum {
Datum::Number(number) => Self::new_number(*number),
- Datum::String(string) => Self::new_user_text(string.as_str()),
- }
- }
- pub fn new_datum_with_format<B>(value: &Datum<B>, format: Format) -> Self
- where
- B: EncodedString,
- {
- match value {
- Datum::Number(number) => Self::new(ValueInner::Number(NumberValue {
- show: None,
- format: match format.var_type() {
- VarType::Numeric => format,
- VarType::String => {
- #[cfg(debug_assertions)]
- panic!("cannot create numeric pivot value with string format");
-
- #[cfg(not(debug_assertions))]
- F8_2
- }
- },
- honor_small: false,
- value: *number,
- variable: None,
- value_label: None,
- })),
Datum::String(string) => Self::new(ValueInner::String(StringValue {
show: None,
- hex: format.type_() == Type::AHex,
+ hex: false,
s: string.as_str().into_owned(),
var_name: None,
value_label: None,
})),
}
}
- pub fn new_variable_value(variable: &Variable, value: &Datum<crate::data::ByteString>) -> Self {
- Self::new_datum_with_format(
- &value.as_encoded(variable.encoding()),
- variable.print_format,
- )
- .with_variable_name(Some(variable.name.as_str().into()))
- .with_value_label(variable.value_labels.get(value).map(String::from))
+
+ /// Returns this value with its display format set to `format`.
+ pub fn with_format(self, format: Format) -> Self {
+ Self {
+ inner: self.inner.with_format(format),
+ styling: self.styling,
+ }
+ }
+
+ pub fn with_source_variable(self, variable: &Variable) -> Self {
+ let value_label = self
+ .datum()
+ .and_then(|datum| variable.value_labels.get(&datum).map(String::from));
+ self.with_value_label(value_label)
+ .with_format(variable.print_format)
+ .with_variable_name(Some(variable.name.as_str().into()))
+ }
+
+ /// Construct a new `Value` from `datum`, which is a value of `variable`.
+ pub fn new_datum_from_variable(datum: &Datum<ByteString>, variable: &Variable) -> Self {
+ Self::new_datum(&datum.as_encoded(variable.encoding())).with_source_variable(variable)
}
- pub fn new_number(x: Option<f64>) -> Self {
- Self::new_number_with_format(x, F8_2)
+
+ pub fn datum(&self) -> Option<Datum<&str>> {
+ self.inner.datum()
}
+
+ pub fn new_number(number: Option<f64>) -> Self {
+ Self::new(ValueInner::Number(NumberValue::new(number)))
+ }
+
pub fn new_integer(x: Option<f64>) -> Self {
- Self::new_number_with_format(x, F40)
+ Self::new_number(x).with_format(F40)
}
pub fn new_text(s: impl Into<String>) -> Self {
Self::new_user_text(s)
pub const fn is_empty(&self) -> bool {
self.inner.is_empty() && self.styling.is_none()
}
+ /// Serializes this value in a plain way, like [BareValue]. This function
+ /// can be used on a field as `#[serde(serialize_with =
+ /// Value::serialize_bare)]`.
+ pub fn serialize_bare<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ BareValue(self).serialize(serializer)
+ }
}
impl From<&str> for Value {
}
impl NumberValue {
+ pub fn new(value: Option<f64>) -> Self {
+ Self {
+ value,
+ format: F8_2,
+ show: None,
+ honor_small: false,
+ variable: None,
+ value_label: None,
+ }
+ }
+ pub fn with_format(self, format: Format) -> Self {
+ Self { format, ..self }
+ }
pub fn display<'a>(
&self,
display: &DisplayValue<'a>,
&& number >= -(1i64 << 53) as f64
&& number <= (1i64 << 53) as f64
{
- (number as u64).serialize(serializer)
+ Some(number as u64).serialize(serializer)
} else {
self.value.serialize(serializer)
}
}
}
-
-#[derive(Serialize)]
-pub struct BareNumberValue<'a>(
- #[serde(serialize_with = "NumberValue::serialize_bare")] pub &'a NumberValue,
-);
-
/// A string value and how to display it.
#[derive(Clone, Debug, Serialize, PartialEq)]
pub struct StringValue {
/// 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 {
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 datum(&self) -> Option<Datum<&str>> {
+ match self {
+ ValueInner::Number(number_value) => Some(Datum::Number(number_value.value)),
+ ValueInner::String(string_value) => Some(Datum::String(&string_value.s)),
+ _ => None,
+ }
+ }
+
fn show(&self) -> Option<Show> {
match self {
ValueInner::Number(NumberValue { show, .. })