format::{Decimal, F40, F40_2, F40_3, Format, PCT40_1, Settings as FormatSettings},
output::pivot::{
look::{Look, Sizing},
- value::{BareValue, Value, ValueOptions},
+ value::{BareValue, Value, ValueFormat, ValueOptions},
},
settings::{Settings, Show},
variable::Variable,
Class::Residual => F40_2,
Class::Count => F40, // XXX
};
- let value = Value::new_number(number)
- .with_format(format)
- .with_honor_small(class == Class::Other);
+ let value = Value::new_number(number).with_format(match class {
+ Class::Other => ValueFormat::SmallE(format),
+ _ => ValueFormat::Other(format),
+ });
self.insert(data_indexes, value);
}
/// Numeric grouping character (usually `.` or `,`).
pub grouping: Option<char>,
- /// The threshold for [DatumValue::honor_small].
- ///
- /// [DatumValue::honor_small]: value::DatumValue::honor_small
+ /// The threshold for [ValueFormat::SmallE].
pub small: f64,
/// The format to use for weight and count variables.
Self::new(ValueInner::Datum(DatumValue::new_number(number)))
}
- /// Construct a new `Value` from `number` with format [F8_0].
- ///
- /// [F8_0]: crate::format::F8_0
+ /// Construct a new `Value` from `number` with format [F40].
pub fn new_integer(x: Option<f64>) -> Self {
Self::new_number(x).with_format(F40)
}
/// Returns this value with its display format set to `format`, if it is a
/// [DatumValue].
- pub fn with_format(self, format: Format) -> Self {
+ pub fn with_format(self, format: impl Into<ValueFormat>) -> Self {
Self {
inner: self.inner.with_format(format),
..self
}
}
- /// Returns this value with `honor_small` set as specified, if it is a
- /// [DatumValue].
- pub fn with_honor_small(self, honor_small: bool) -> Self {
- Self {
- inner: self.inner.with_honor_small(honor_small),
- ..self
- }
- }
-
/// 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)
}
}
+/// A [Format] inside a [Value].
+///
+/// Most `Value`s contain ordinary [Format]s, but occasionally one will have a
+/// special format that is like [Type::F] except that nonzero numbers with
+/// magnitude below a small threshold are instead shown in scientific notation.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ValueFormat {
+ /// Any ordinary format.
+ Other(Format),
+
+ /// Displays numbers smaller than [PivotTableStyle::small] in scientific
+ /// notation, and otherwise in the enclosed format (which should be
+ /// [Type::F] format).
+ ///
+ /// [PivotTableStyle::small]: super::PivotTableStyle::small
+ SmallE(Format),
+}
+
+impl ValueFormat {
+ /// Returns the inner [Format].
+ pub fn inner(&self) -> Format {
+ match self {
+ ValueFormat::Other(format) => *format,
+ ValueFormat::SmallE(format) => *format,
+ }
+ }
+
+ /// Returns this format as applied to the given `number` with `small` as the
+ /// threshold.
+ pub fn apply(&self, number: Option<f64>, small: f64) -> Format {
+ if let ValueFormat::SmallE(format) = self
+ && let Some(number) = number
+ && number != 0.0
+ && number.abs() < small
+ {
+ UncheckedFormat::new(Type::E, 40, format.d() as u8).fix()
+ } else {
+ self.inner()
+ }
+ }
+
+ /// Returns true if this is [ValueFormat::SmallE].
+ pub fn is_small_e(&self) -> bool {
+ matches!(self, ValueFormat::SmallE(_))
+ }
+}
+
+impl From<Format> for ValueFormat {
+ fn from(format: Format) -> Self {
+ Self::Other(format)
+ }
+}
+
+impl Serialize for ValueFormat {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ match self {
+ ValueFormat::Other(format) => format.serialize(serializer),
+ ValueFormat::SmallE(format) => {
+ #[derive(Serialize)]
+ struct SmallE(Format);
+ SmallE(*format).serialize(serializer)
+ }
+ }
+ }
+}
+
/// A datum and how to display it.
#[derive(Clone, Debug, PartialEq)]
pub struct DatumValue {
pub datum: Datum<WithEncoding<ByteString>>,
/// The display format.
- pub format: Format,
+ pub format: ValueFormat,
/// Whether to show `value` or `value_label` or both.
///
/// If this is unset, then a higher-level default is used.
pub show: Option<Show>,
- /// If true, then numbers smaller than [PivotTableStyle::small] will be
- /// displayed in scientific notation. Otherwise, all numbers will be
- /// displayed with `format`.
- ///
- /// [PivotTableStyle::small]: super::PivotTableStyle::small
- pub honor_small: bool,
-
/// The name of the variable that `value` came from, if any.
pub variable: Option<String>,
where
S: serde::Serializer,
{
- if self.format.type_() == Type::F && self.variable.is_none() && self.value_label.is_none() {
+ if let ValueFormat::Other(format) = self.format
+ && format.type_() == Type::F
+ && self.variable.is_none()
+ && self.value_label.is_none()
+ {
self.datum.serialize(serializer)
} else {
let mut s = serializer.serialize_map(None)?;
if let Some(show) = self.show {
s.serialize_entry("show", &show)?;
}
- if self.honor_small {
- s.serialize_entry("honor_small", &self.honor_small)?;
- }
if let Some(variable) = &self.variable {
s.serialize_entry("variable", variable)?;
}
{
Self {
datum: datum.cloned(),
- format: F8_2,
+ format: ValueFormat::Other(F8_2),
show: None,
- honor_small: false,
variable: None,
value_label: None,
}
}
/// Returns this `DatumValue` with the given `format`.
- pub fn with_format(self, format: Format) -> Self {
+ pub fn with_format(self, format: ValueFormat) -> Self {
Self { format, ..self }
}
- /// Returns this `DatumValue` with the given `honor_small`.
- pub fn with_honor_small(self, honor_small: bool) -> Self {
- Self {
- honor_small,
- ..self
- }
- }
-
/// Writes this value to `f` using the settings in `display`.
pub fn display<'a>(
&self,
if display.show_value {
match &self.datum {
Datum::Number(number) => {
- let format = if self.format.type_() == Type::F
- && self.honor_small
- && let Some(number) = *number
- && number != 0.0
- && number.abs() < display.small()
- {
- UncheckedFormat::new(Type::E, 40, self.format.d() as u8).fix()
- } else {
- self.format
- };
+ let format = self.format.apply(*number, display.small());
self.datum
.display(format)
.with_settings(&display.options.settings)
.fmt(f)?;
}
Datum::String(s) => {
- if self.format.type_() == Type::AHex {
+ if self.format.inner().type_() == Type::AHex {
write!(f, "{}", s.inner.display_hex())?;
} else {
f.write_str(&s.as_str())?;
/// Returns the decimal point used in the formatted value, if any.
pub fn decimal(&self) -> Decimal {
- self.datum.display(self.format).decimal()
+ self.datum.display(self.format.inner()).decimal()
}
/// Serializes this value to `serializer` in the "bare" manner described for
/// Returns this value with its display format set to `format`, if it is a
/// [DatumValue].
- pub fn with_format(mut self, format: Format) -> Self {
- if let Some(datum_value) = self.as_datum_value_mut() {
- datum_value.format = format;
- }
- self
- }
-
- /// Returns this value with `honor_small` set as specified, if it is a
- /// [DatumValue].
- pub fn with_honor_small(mut self, honor_small: bool) -> Self {
+ pub fn with_format(mut self, format: impl Into<ValueFormat>) -> Self {
if let Some(datum_value) = self.as_datum_value_mut() {
- datum_value.honor_small = honor_small;
+ datum_value.format = format.into();
}
self
}
output::pivot::{
self, Axis2, Axis3, Category, CategoryLocator, Dimension, Group, Leaf, Length, PivotTable,
look::{self, Area, AreaStyle, CellStyle, Color, HorzAlign, Look, RowParity, VertAlign},
- value::Value,
+ value::{Value, ValueFormat},
},
spv::read::light::decode_format,
};
Datum::Number(None) => 0,
Datum::String(s) => s.parse().unwrap_or_default(),
};
- decode_format(f as u32, &mut |_| () /*XXX*/).0
+ decode_format(f as u32, &mut |_| () /*XXX*/).inner()
}
/// Returns this data value interpreted using `format`.
{
match &datum_value.datum {
Datum::Number(_) => {
- datum_value.format = format;
+ datum_value.format = ValueFormat::Other(format);
}
Datum::String(string) => {
if format.type_().category() == format::Category::Date
&& let Ok(time) =
NaiveTime::parse_from_str(&string.as_str(), "%H:%M:%S%.3f")
{
- value.inner = Value::new_time(time).with_format(format).inner;
+ value.inner = Value::new_time(time)
+ .with_format(ValueFormat::Other(format))
+ .inner;
} else if let Ok(number) = string.as_str().parse::<f64>() {
value.inner = Value::new_number(Some(number)).with_format(format).inner;
}
use crate::{
data::Datum,
format::{
- self, CC, Decimal, Decimals, Epoch, F40, F40_2, NumberStyle, Settings, Type,
- UncheckedFormat, Width,
+ CC, Decimal, Decimals, Epoch, F40, F40_2, NumberStyle, Settings, Type, UncheckedFormat,
+ Width,
},
output::pivot::{
self, Axis, Axis2, Axis3, FootnoteMarkerPosition, FootnoteMarkerType, Footnotes, Group,
RowColBorder, RowParity, Stroke, VertAlign,
},
parse_bool,
- value::{self, DatumValue, TemplateValue, ValueStyle, VariableValue},
+ value::{self, DatumValue, TemplateValue, ValueFormat, ValueStyle, VariableValue},
},
settings,
};
}
pub fn decode(&self, mut warn: &mut dyn FnMut(LightWarning)) -> PivotTable {
+ dbg!(self);
let encoding = self.formats.encoding(warn);
let n1 = self.formats.n1();
.cells
.iter()
.map(|cell| {
- (
- PrecomputedIndex(cell.index as usize),
- cell.value.decode(encoding, &footnotes, warn),
- )
+ (PrecomputedIndex(cell.index as usize), {
+ let value = cell.value.decode(encoding, &footnotes, warn);
+ dbg!(value.inner.as_datum_value());
+ value
+ })
})
.collect::<Vec<_>>();
let dimensions = self
struct Format(u32);
impl Format {
- pub fn decode(&self, warn: &mut dyn FnMut(LightWarning)) -> (format::Format, bool) {
+ pub fn decode(&self, warn: &mut dyn FnMut(LightWarning)) -> ValueFormat {
decode_format(self.0, warn)
}
}
impl Debug for Format {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut warning = false;
- let (format, _honor_small) = self.decode(&mut |_| {
+ let format = self.decode(&mut |_| {
warning = true;
});
if warning {
write!(f, "InvalidFormat({:#x})", self.0)
} else {
- write!(f, "{format}")
+ match format {
+ ValueFormat::Other(format) => write!(f, "{format}"),
+ ValueFormat::SmallE(format) => write!(f, "SmallE({format})"),
+ }
}
}
}
}
}
-pub(super) fn decode_format(
- raw: u32,
- warn: &mut dyn FnMut(LightWarning),
-) -> (format::Format, bool) {
+pub(super) fn decode_format(raw: u32, warn: &mut dyn FnMut(LightWarning)) -> ValueFormat {
if raw == 0 || raw == 0x10000 || raw == 1 {
- return (F40_2, false);
+ return ValueFormat::Other(F40_2);
}
let raw_type = (raw >> 16) as u16;
- let (type_, honor_small) = if raw_type >= 40 {
- (Type::F, true)
+ let type_ = if raw_type >= 40 {
+ Type::F
} else if let Ok(type_) = Type::try_from(raw_type) {
- (type_, false)
+ type_
} else {
warn(LightWarning::InvalidFormat(raw_type));
- (Type::F, false)
+ Type::F
};
+
let w = ((raw >> 8) & 0xff) as Width;
let d = raw as Decimals;
+ let inner = UncheckedFormat::new(type_, w, d).fix();
- (UncheckedFormat::new(type_, w, d).fix(), honor_small)
+ if raw_type >= 40 {
+ ValueFormat::SmallE(inner)
+ } else {
+ ValueFormat::Other(inner)
+ }
}
impl ValueNumber {
footnotes: &pivot::Footnotes,
warn: &mut dyn FnMut(LightWarning),
) -> value::Value {
- let (format, honor_small) = dbg!(self.format.decode(warn));
+ let format = dbg!(self.format.decode(warn));
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
.with_format(format)
- .with_honor_small(honor_small)
.with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
}
}
footnotes: &pivot::Footnotes,
warn: &mut dyn FnMut(LightWarning),
) -> value::Value {
- let (format, honor_small) = self.format.decode(warn);
+ let format = self.format.decode(warn);
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
.with_format(format)
- .with_honor_small(honor_small)
.with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
.with_value_label(self.value_label.decode_optional(encoding))
.with_variable_name(Some(self.var_name.decode(encoding)))
footnotes: &pivot::Footnotes,
warn: &mut dyn FnMut(LightWarning),
) -> value::Value {
- let (format, honor_small) = self.format.decode(warn);
+ let format = self.format.decode(warn);
value::Value::new(pivot::value::ValueInner::Datum(DatumValue {
datum: Datum::new_utf8(self.s.decode(encoding)),
format,
- honor_small,
show: self.show.decode(warn),
variable: self.var_name.decode_optional(encoding),
value_label: self.value_label.decode_optional(encoding),
HeadingRegion, HorzAlign, LabelPosition, RowColBorder, RowParity, Stroke,
VertAlign,
},
- value::{Value, ValueInner, ValueStyle},
+ value::{Value, ValueFormat, ValueInner, ValueStyle},
},
},
settings::Show,
}
}
-struct SpvFormat {
- format: Format,
- honor_small: bool,
-}
-
-impl BinWrite for SpvFormat {
+impl BinWrite for ValueFormat {
type Args<'a> = ();
fn write_options<W: Write + Seek>(
endian: binrw::Endian,
args: Self::Args<'_>,
) -> binrw::BinResult<()> {
- let type_ = if self.format.type_() == Type::F && self.honor_small {
+ let type_ = if let ValueFormat::SmallE(format) = self
+ && format.type_() == Type::F
+ {
40
} else {
- self.format.type_().into()
+ self.inner().type_().into()
};
- (((type_ as u32) << 16) | ((self.format.w() as u32) << 8) | (self.format.d() as u32))
+ let inner = self.inner();
+ (((type_ as u32) << 16) | ((inner.w() as u32) << 8) | (inner.d() as u32))
.write_options(writer, endian, args)
}
}
match &self.inner {
ValueInner::Datum(number_value) => match &number_value.datum {
Datum::Number(number) => {
- let format = SpvFormat {
- format: number_value.format,
- honor_small: number_value.honor_small,
- };
if number_value.variable.is_some() || number_value.value_label.is_some() {
(
2u8,
ValueMod::new(self),
- format,
+ number_value.format,
number.unwrap_or(f64::MIN),
SpvString::optional(&number_value.variable),
SpvString::optional(&number_value.value_label),
)
.write_options(writer, endian, args)?;
} else {
- (1u8, ValueMod::new(self), format, number.unwrap_or(f64::MIN))
+ (
+ 1u8,
+ ValueMod::new(self),
+ number_value.format,
+ number.unwrap_or(f64::MIN),
+ )
.write_options(writer, endian, args)?;
}
}
Datum::String(s) => {
let hex;
let utf8;
- let (s, format) = if number_value.format.type_() == Type::AHex {
+ let (s, format) = if number_value.format.inner().type_() == Type::AHex {
hex = s.inner.to_hex();
(
hex.as_str(),
(
4u8,
ValueMod::new(self),
- SpvFormat {
- format,
- honor_small: false,
- },
+ ValueFormat::Other(format),
SpvString::optional(&number_value.value_label),
SpvString::optional(&number_value.variable),
Show::as_spv(&number_value.show),