use std::{
collections::HashMap,
- fmt::{Debug, Display},
+ fmt::{Debug, Display, Write},
io::Read,
iter::{FusedIterator, once, repeat, repeat_n},
ops::{Index, IndexMut, Not, Range, RangeInclusive},
ValueInner::Markup(markup) => write!(f, "{markup}"),
- ValueInner::Text(TextValue {
- localized: local, ..
- }) => f.write_str(local),
+ ValueInner::Text(text_value) => write!(f, "{text_value}"),
ValueInner::Template(template_value) => template_value.display(self, f),
}
}
+/// A numeric value and how to display it.
#[derive(Clone, Debug, PartialEq)]
pub struct NumberValue {
/// The numerical value, or `None` if it is a missing value.
pub value: Option<f64>,
+
+ /// The display format (usually [F] or [Pct]).
+ ///
+ /// [F]: crate::format::Type::F
+ /// [Pct]: crate::format::Type::Pct
pub format: Format,
+
+ /// 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 a threshold will be displayed in
+ /// scientific notation. Otherwise, all numbers will be displayed with
+ /// `format`.
pub honor_small: bool,
+
+ /// The name of the variable that `value` came from, if any.
pub variable: Option<String>,
+
+ /// The value label associated with `value`, if any.
pub value_label: Option<String>,
}
#[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 string value.
/// 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>,
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub struct VariableValue {
+ /// Whether to show `var_name` or `variable_label` or both.
+ ///
+ /// If this is unset, then a higher-level default is used.
pub show: Option<Show>,
pub var_name: String,
pub variable_label: Option<String>,
}
+/// A text string.
+///
+/// Whereas a [StringValue] is usually related to data, a `TextValue` is used
+/// for other text within a table, such as a title, a column or row heading, or
+/// a footnote.
#[derive(Clone, Debug, PartialEq)]
pub struct TextValue {
+ /// Whether the text came from the user.
+ ///
+ /// PSPP can localize text that it writes itself, but not text provided by
+ /// the user.
pub user_provided: bool,
+
/// Localized.
+ ///
+ /// This is the main output string.
pub localized: String,
- /// English.
+
+ /// English version of the string.
+ ///
+ /// Only for strings that are not user-provided, and only if it is different
+ /// from `localized`.
pub c: Option<String>,
+
/// Identifier.
+ ///
+ /// Only for strings that are not user-provided, and only if it is different
+ /// from `localized`.
pub id: Option<String>,
}
}
}
+impl Display for TextValue {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(&self.localized)
+ }
+}
+
impl TextValue {
pub fn localized(&self) -> &str {
self.localized.as_str()
while let Some(c) = iter.next() {
match c {
'\\' => {
- let c = iter.next().unwrap_or('\\') as char;
- let c = if c == 'n' { '\n' } else { c };
- write!(f, "{c}")?;
+ let c = match iter.next() {
+ None => '\\',
+ Some('n') => '\n',
+ Some(c) => c,
+ };
+ f.write_char(c)?;
}
'^' => {
let (index, rest) = Self::consume_int(iter.as_str());
- iter = rest.chars();
- let Some(arg) = self.args.get(index.wrapping_sub(1)) else {
- continue;
- };
- if let Some(arg) = arg.first() {
- write!(f, "{}", arg.display(display.options))?;
+ if let Some(index) = index.checked_sub(1)
+ && let Some(arg) = self.args.get(index)
+ && let Some(arg) = arg.first()
+ {
+ arg.display(display.options).fmt(f)?;
}
+ iter = rest.chars();
}
'[' => {
let (a, rest) = extract_inner_template(iter.as_str());
let (index, rest) = Self::consume_int(rest);
iter = rest.chars();
- dbg!((a, b, index));
- let Some(mut args) = self
- .args
- .get(index.wrapping_sub(1))
- .map(|vec| vec.as_slice())
- else {
- continue;
- };
- let (mut template, mut escape) =
- if !a.is_empty() { (a, '%') } else { (b, '^') };
- while !args.is_empty() {
- let n_consumed = self.inner_template(display, f, template, escape, args)?;
- if n_consumed == 0 {
- break;
+ if let Some(index) = index.checked_sub(1)
+ && let Some(args) = self.args.get(index)
+ {
+ let mut args = args.as_slice();
+ let (mut template, mut escape) =
+ if !a.is_empty() { (a, '%') } else { (b, '^') };
+ while !args.is_empty()
+ && let n_consumed =
+ self.inner_template(display, f, template, escape, args)?
+ && n_consumed > 0
+ {
+ args = &args[n_consumed..];
+ template = b;
+ escape = '^';
}
- args = &args[n_consumed..];
-
- template = b;
- escape = '^';
}
}
- c => write!(f, "{c}")?,
+ c => f.write_char(c)?,
}
}
Ok(())
c if c == escape => {
let (index, rest) = Self::consume_int(iter.as_str());
iter = rest.chars();
- let Some(arg) = args.get(index.wrapping_sub(1)) else {
- continue;
- };
- args_consumed = args_consumed.max(index);
- write!(f, "{}", arg.display(display.options))?;
+ if let Some(index) = index.checked_sub(1)
+ && let Some(arg) = args.get(index)
+ {
+ args_consumed = args_consumed.max(index);
+ write!(f, "{}", arg.display(display.options))?;
+ }
}
c => write!(f, "{c}")?,
}