/// instead of `.5`)?
pub leading_zero: bool,
+ /// Format `PCT` and `DOLLAR` with leading zero (e.g. `$0.5` instead of
+ /// `$.5`)?
+ pub leading_zero_pct: bool,
+
/// Custom currency styles.
pub ccs: EnumMap<CC, Option<Box<NumberStyle>>>,
}
struct StyleParams {
decimal: Decimal,
leading_zero: bool,
+ leading_zero_pct: bool,
}
impl From<&Settings> for StyleParams {
fn from(value: &Settings) -> Self {
Self {
decimal: value.decimal,
leading_zero: value.leading_zero,
+ leading_zero_pct: value.leading_zero_pct,
}
}
}
}
}
+struct NumberStyles {
+ f: StyleSet,
+ comma: StyleSet,
+ dot: StyleSet,
+ dollar: StyleSet,
+ pct: StyleSet,
+ default: NumberStyle,
+}
+impl NumberStyles {
+ fn new() -> Self {
+ Self {
+ f: StyleSet::new(|p| NumberStyle::new(p.decimal, p.leading_zero)),
+ comma: StyleSet::new(|p| {
+ NumberStyle::new(p.decimal, p.leading_zero).with_grouping(true)
+ }),
+ dot: StyleSet::new(|p| {
+ NumberStyle::new(!p.decimal, p.leading_zero).with_grouping(true)
+ }),
+ dollar: StyleSet::new(|p| {
+ NumberStyle::new(p.decimal, p.leading_zero_pct)
+ .with_grouping(true)
+ .with_prefix("$")
+ }),
+ pct: StyleSet::new(|p| {
+ NumberStyle::new(p.decimal, p.leading_zero_pct).with_suffix("%")
+ }),
+ default: NumberStyle::new(Decimal::Dot, false),
+ }
+ }
+ fn get<'a>(&'a self, settings: &'a Settings, type_: Type) -> &'a NumberStyle {
+ match type_ {
+ Type::F | Type::E => self.f.get(settings),
+ Type::Comma => self.comma.get(settings),
+ Type::Dot => self.dot.get(settings),
+ Type::Dollar => self.dollar.get(settings),
+ Type::Pct => self.pct.get(settings),
+ Type::CC(cc) => settings.ccs[cc].as_deref().unwrap_or(&self.default),
+ _ => &self.default,
+ }
+ }
+}
+
impl Settings {
pub fn with_cc(mut self, cc: CC, style: NumberStyle) -> Self {
self.ccs[cc] = Some(Box::new(style));
..self
}
}
+ pub fn with_leading_zero_pct(self, leading_zero_pct: bool) -> Self {
+ Self {
+ leading_zero_pct,
+ ..self
+ }
+ }
pub fn with_epoch(self, epoch: Epoch) -> Self {
Self { epoch, ..self }
}
pub fn number_style(&self, type_: Type) -> &NumberStyle {
- static DEFAULT: LazyLock<NumberStyle> =
- LazyLock::new(|| NumberStyle::new("", "", Decimal::Dot, None, false));
-
- match type_ {
- Type::F | Type::E => {
- static F: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("", "", p.decimal, None, p.leading_zero))
- });
- F.get(self)
- }
- Type::Comma => {
- static COMMA: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| {
- NumberStyle::new("", "", p.decimal, Some(!p.decimal), p.leading_zero)
- })
- });
- COMMA.get(self)
- }
- Type::Dot => {
- static DOT: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| {
- NumberStyle::new("", "", !p.decimal, Some(p.decimal), p.leading_zero)
- })
- });
- DOT.get(self)
- }
- Type::Dollar => {
- static DOLLAR: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("$", "", p.decimal, Some(!p.decimal), false))
- });
- DOLLAR.get(self)
- }
- Type::Pct => {
- static PCT: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("", "%", p.decimal, None, false))
- });
- PCT.get(self)
- }
- Type::CC(cc) => self.ccs[cc].as_deref().unwrap_or(&DEFAULT),
- Type::N
- | Type::Z
- | Type::P
- | Type::PK
- | Type::IB
- | Type::PIB
- | Type::PIBHex
- | Type::RB
- | Type::RBHex
- | Type::Date
- | Type::ADate
- | Type::EDate
- | Type::JDate
- | Type::SDate
- | Type::QYr
- | Type::MoYr
- | Type::WkYr
- | Type::DateTime
- | Type::YmdHms
- | Type::MTime
- | Type::Time
- | Type::DTime
- | Type::WkDay
- | Type::Month
- | Type::A
- | Type::AHex => &DEFAULT,
- }
+ static NUMBER_STYLES: LazyLock<NumberStyles> = LazyLock::new(|| NumberStyles::new());
+ NUMBER_STYLES.get(self, type_)
}
}
}
impl NumberStyle {
- fn new(
- prefix: &str,
- suffix: &str,
- decimal: Decimal,
- grouping: Option<Decimal>,
- leading_zero: bool,
- ) -> Self {
- // These assertions ensure that zero is correct for `extra_bytes`.
- debug_assert!(prefix.is_ascii());
- debug_assert!(suffix.is_ascii());
-
+ fn new(decimal: Decimal, leading_zero: bool) -> Self {
Self {
- neg_prefix: Affix::new("-"),
- prefix: Affix::new(prefix),
- suffix: Affix::new(suffix),
- neg_suffix: Affix::new(""),
+ neg_prefix: Affix::from("-"),
+ prefix: Affix::new(),
+ suffix: Affix::new(),
+ neg_suffix: Affix::new(),
decimal,
- grouping,
+ grouping: None,
leading_zero,
extra_bytes: 0,
}
}
+ fn with_grouping(self, grouping: bool) -> Self {
+ Self {
+ grouping: grouping.then_some(!self.decimal),
+ ..self
+ }
+ }
+
+ fn with_prefix(self, prefix: impl Into<String>) -> Self {
+ let prefix = Affix::from(prefix);
+ Self {
+ extra_bytes: self.extra_bytes - self.prefix.extra_bytes() + prefix.extra_bytes(),
+ prefix,
+ ..self
+ }
+ }
+
+ fn with_neg_prefix(self, neg_prefix: impl Into<String>) -> Self {
+ let neg_prefix = Affix::from(neg_prefix);
+ Self {
+ extra_bytes: self.extra_bytes - self.neg_prefix.extra_bytes()
+ + neg_prefix.extra_bytes(),
+ neg_prefix,
+ ..self
+ }
+ }
+
+ fn with_neg_suffix(self, neg_suffix: impl Into<String>) -> Self {
+ let neg_suffix = Affix::from(neg_suffix);
+ Self {
+ extra_bytes: self.extra_bytes - self.neg_suffix.extra_bytes()
+ + neg_suffix.extra_bytes(),
+ neg_suffix,
+ ..self
+ }
+ }
+
+ fn with_suffix(self, suffix: impl Into<String>) -> Self {
+ let suffix = Affix::from(suffix);
+ Self {
+ extra_bytes: self.extra_bytes - self.suffix.extra_bytes() + suffix.extra_bytes(),
+ suffix,
+ ..self
+ }
+ }
+
fn affix_width(&self) -> usize {
self.prefix.width + self.suffix.width
}
pub width: usize,
}
-impl Affix {
- fn new(s: impl Into<String>) -> Self {
- let s = s.into();
+impl<T> From<T> for Affix
+where
+ T: Into<String>,
+{
+ fn from(value: T) -> Self {
+ let s = value.into();
Self {
width: s.width(),
s,
}
}
+}
+
+impl Affix {
+ fn new() -> Self {
+ Self::from("")
+ }
fn extra_bytes(&self) -> usize {
self.s.len().checked_sub(self.width).unwrap()
}
}
- fn take_cc_token(iter: &mut Chars<'_>, grouping: char) -> Affix {
+ fn take_cc_token(iter: &mut Chars<'_>, grouping: char) -> String {
let mut s = String::new();
let mut quote = false;
for c in iter {
quote = false;
}
}
- Affix::new(s)
+ s
}
let Some(grouping) = find_separator(s) else {
let neg_suffix = take_cc_token(&mut iter, grouping);
let grouping: Decimal = grouping.try_into().unwrap();
let decimal = !grouping;
- let extra_bytes = neg_prefix.extra_bytes()
- + prefix.extra_bytes()
- + suffix.extra_bytes()
- + neg_suffix.extra_bytes();
- Ok(Self {
- neg_prefix,
- prefix,
- suffix,
- neg_suffix,
- decimal,
- grouping: Some(grouping),
- leading_zero: false,
- extra_bytes,
- })
+ Ok(Self::new(decimal, false)
+ .with_grouping(true)
+ .with_prefix(prefix)
+ .with_neg_prefix(neg_prefix)
+ .with_neg_suffix(neg_suffix)
+ .with_suffix(suffix))
}
}
.cells
.iter()
.map(|cell| {
- (PrecomputedIndex(cell.index as usize), {
- let value = cell.value.decode(encoding, &footnotes, warn);
- dbg!(value.inner.as_datum_value());
- value
- })
+ (
+ PrecomputedIndex(cell.index as usize),
+ cell.value.decode(encoding, &footnotes, warn),
+ )
})
.collect::<Vec<_>>();
let dimensions = self
settings: Arc::new(Settings {
epoch: self.formats.y0.epoch(),
decimal: self.formats.y0.decimal(warn),
- leading_zero: y1.map_or(false, |y1| y1.include_leading_zero),
+ leading_zero: if let Some(y1) = y1 {
+ y1.include_leading_zero
+ } else {
+ false
+ },
+ leading_zero_pct: true,
ccs: self.formats.custom_currency.decode(encoding, warn),
}),
grouping: {
impl Formats {
fn y1(&self) -> Option<&Y1> {
- self.v1
- .as_ref()
- .map(|n0| &n0.y1)
- .or_else(|| self.v3.as_ref().map(|v3| &v3.n3.y1))
+ if let Some(n0) = &**self.v1 {
+ Some(&n0.y1)
+ } else if let Some(v3) = &self.v3 {
+ Some(&v3.n3.y1)
+ } else {
+ None
+ }
}
fn n1(&self) -> Option<&N1> {
footnotes: &pivot::Footnotes,
warn: &mut dyn FnMut(LightWarning),
) -> value::Value {
- let format = dbg!(self.format.decode(warn));
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
- .with_format(format)
+ .with_format(self.format.decode(warn))
.with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes))
}
}
footnotes: &pivot::Footnotes,
warn: &mut dyn FnMut(LightWarning),
) -> value::Value {
- let format = self.format.decode(warn);
value::Value::new_number((self.x != -f64::MAX).then_some(self.x))
- .with_format(format)
+ .with_format(self.format.decode(warn))
.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 = self.format.decode(warn);
value::Value::new(pivot::value::ValueInner::Datum(DatumValue {
datum: Datum::new_utf8(self.s.decode(encoding)),
- format,
+ format: self.format.decode(warn),
show: self.show.decode(warn),
variable: self.var_name.decode_optional(encoding),
value_label: self.value_label.decode_optional(encoding),