//! a category for each dimension to a value, which is commonly a number but
//! could also be a variable name or an arbitrary text string.
+// Warn about missing docs, but not for items declared with `#[cfg(test)]`.
+#![cfg_attr(not(test), warn(missing_docs))]
+
use std::{
collections::HashMap,
fmt::{Debug, Display},
pub dimensions: Vec<usize>,
}
-pub struct AxisIterator {
+/// Iterator over one of the [Axis3] axes in a [PivotTable].
+///
+/// The items for this iterator are the index values for each of the dimensions
+/// along the axis, each along `0..n` where `n` is the number of leaves in the
+/// dimension.
+///
+/// Use [PivotTable::axis_values] to construct an `AxisIterator`.
+struct AxisIterator {
indexes: SmallVec<[usize; 4]>,
lengths: SmallVec<[usize; 4]>,
done: bool,
}
impl PivotTable {
+ /// Constructs a new `PivotTable` with the given `dimensions` along the
+ /// specified axes.
+ ///
+ /// The caller should add a title to the pivot table using [with_title] and
+ /// add data with [with_data] or [insert].
+ ///
+ /// [with_title]: Self::with_title
+ /// [with_data]: Self::with_data
+ /// [insert]: Self::insert
+ pub fn new(dimensions: impl IntoIterator<Item = (Axis3, Dimension)>) -> Self {
+ let mut dims = Vec::new();
+ let mut axes = EnumMap::<Axis3, Axis>::default();
+ for (axis, dimension) in dimensions {
+ axes[axis].dimensions.push(dims.len());
+ dims.push(dimension);
+ }
+ Self {
+ style: PivotTableStyle::default().with_look(Settings::global().look.clone()),
+ layer: repeat_n(0, axes[Axis3::Z].dimensions.len()).collect(),
+ axes,
+ dimensions: dims,
+ ..Self::default()
+ }
+ }
+
+ /// Returns this pivot table with the given `title`.
+ ///
+ /// The title is displayed above the pivot table. Every pivot table should
+ /// have a title.
+ pub fn with_title(mut self, title: impl Into<Value>) -> Self {
+ self.metadata.title = Some(Box::new(title.into()));
+ self.style.show_title = true;
+ self
+ }
+
+ /// Returns this pivot table with the given `caption`.
+ ///
+ /// The caption is displayed below the pivot table. Captions are optional.
+ pub fn with_caption(mut self, caption: impl Into<Value>) -> Self {
+ self.metadata.caption = Some(Box::new(caption.into()));
+ self.style.show_caption = true;
+ self
+ }
+
+ /// Returns this pivot table with the given `corner_text`.
+ ///
+ /// The corner text is displayed in the top-left corner of the pivot table,
+ /// above the row headings and to the left of the column headings. The
+ /// space used by corner text can also be used for [Dimension] titles.
+ pub fn with_corner_text(mut self, corner_text: impl Into<Value>) -> Self {
+ self.metadata.corner_text = Some(Box::new(corner_text.into()));
+ self
+ }
+
+ /// Returns this pivot table with the given `footnotes`.
+ pub fn with_footnotes(mut self, footnotes: Footnotes) -> Self {
+ debug_assert!(self.footnotes.is_empty());
+ self.footnotes = footnotes;
+ self
+ }
+
+ /// Returns this pivot table with the given `look`.
pub fn with_look(self, look: Arc<Look>) -> Self {
Self {
style: self.style.with_look(look),
..self
}
}
+
+ /// Returns this pivot table with the given `style`.
+ pub fn with_style(self, style: PivotTableStyle) -> Self {
+ Self { style, ..self }
+ }
+
+ /// Returns this pivot table with the given `metadata`.
+ pub fn with_metadata(self, metadata: PivotTableMetadata) -> Self {
+ Self { metadata, ..self }
+ }
+
+ /// Returns this pivot table with the given `subtype`.
+ ///
+ /// A subtype is a locale-invariant command ID for the particular kind of
+ /// output that this table represents in the procedure. This can be the
+ /// same as the command name, e.g. `Frequencies`, or different, e.g. `Case
+ /// Processing Summary`.
+ ///
+ /// `Notes` and `Warnings` are common generic subtypes.
+ pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
+ Self {
+ metadata: self.metadata.with_subtype(subtype),
+ ..self
+ }
+ }
+
+ /// Returns this pivot table with the given `show_values`.
+ pub fn with_show_values(self, show_values: Option<Show>) -> Self {
+ Self {
+ style: self.style.with_show_values(show_values),
+ ..self
+ }
+ }
+
+ /// Returns this pivot table with the given `show_variables`.
+ pub fn with_show_variables(self, show_variables: Option<Show>) -> Self {
+ Self {
+ style: self.style.with_show_variables(show_variables),
+ ..self
+ }
+ }
+
+ /// Returns this pivot table with the given `show_title`.
+ pub fn with_show_title(self, show_title: bool) -> Self {
+ Self {
+ style: self.style.with_show_title(show_title),
+ ..self
+ }
+ }
+
+ /// Returns this pivot table with the given `show_caption`.
+ pub fn with_show_caption(self, show_caption: bool) -> Self {
+ Self {
+ style: self.style.with_show_caption(show_caption),
+ ..self
+ }
+ }
+
+ /// Returns this pivot table with its [Look] modified to show empty rows and
+ /// columns.
+ pub fn with_show_empty(mut self) -> Self {
+ if self.style.look.hide_empty {
+ self.look_mut().hide_empty = false;
+ }
+ self
+ }
+
+ /// Returns this pivot table with its [Look] modified to hide empty rows and
+ /// columns.
+ pub fn with_hide_empty(mut self) -> Self {
+ if !self.style.look.hide_empty {
+ self.look_mut().hide_empty = true;
+ }
+ self
+ }
+
+ /// Returns this pivot table with the current layer set to `layer` and its
+ /// look modified (if necessary) to print just a single layer.
+ pub fn with_layer(mut self, layer: &[usize]) -> Self {
+ // XXX verify that `layer` is valid
+ debug_assert_eq!(layer.len(), self.layer.len());
+ if self.style.look.print_all_layers {
+ self.style.look_mut().print_all_layers = false;
+ }
+ self.layer.clear();
+ self.layer.extend_from_slice(layer);
+ self
+ }
+
+ /// Returns this pivot table set to print all layers.
+ pub fn with_all_layers(mut self) -> Self {
+ if !self.style.look.print_all_layers {
+ self.look_mut().print_all_layers = true;
+ }
+ self
+ }
+
+ /// Inserts `number` into the cell with the given `data_indexes`, drawing
+ /// its format from `class`.
pub fn insert_number(&mut self, data_indexes: &[usize], number: Option<f64>, class: Class) {
let format = match class {
Class::Other => Settings::global().default_format,
self.insert(data_indexes, value);
}
- pub fn with_footnotes(mut self, footnotes: Footnotes) -> Self {
- debug_assert!(self.footnotes.is_empty());
- self.footnotes = footnotes;
- self
- }
+ /// Returns an iterator for all the values along `axis`.
fn axis_values(&self, axis: Axis3) -> AxisIterator {
AxisIterator {
indexes: repeat_n(0, self.axes[axis].dimensions.len()).collect(),
fn axis_extent(&self, axis: Axis3) -> usize {
self.axis_dimensions(axis).map(|d| d.len()).product()
}
-}
-
-/// A dimension.
-///
-/// A [Dimension] identifies the categories associated with a single dimension
-/// within a multidimensional pivot table.
-///
-/// A dimension contains a collection of categories, which are the leaves in a
-/// tree of groups.
-///
-/// (A dimension or a group can contain zero categories, but this is unusual.
-/// If a dimension contains no categories, then its table cannot contain any
-/// data.)
-#[derive(Clone, Debug, Serialize)]
-pub struct Dimension {
- /// Hierarchy of categories within the dimension. The groups and categories
- /// are sorted in the order that should be used for display. This might be
- /// different from the original order produced for output if the user
- /// adjusted it.
- ///
- /// The root must always be a group, although it is allowed to have no
- /// subcategories.
- pub root: Group,
-
- /// Ordering of leaves for presentation.
- ///
- /// This is a permutation of `0..n` where `n` is the number of leaves. It
- /// maps from an index in presentation order to an index in data order.
- pub presentation_order: Vec<usize>,
-
- /// Display.
- pub hide_all_labels: bool,
-}
-
-pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>;
-pub struct Path<'a> {
- groups: GroupVec<'a>,
- leaf: &'a Leaf,
-}
-
-pub type IndexVec = SmallVec<[usize; 4]>;
-impl Dimension {
- pub fn new(root: Group) -> Self {
- Dimension {
- presentation_order: (0..root.len()).collect(),
- root,
- hide_all_labels: false,
- }
+ /// Returns the indexes for the layer to be printed or display on-screen.
+ pub fn layer(&self) -> &[usize] {
+ &self.layer
}
- pub fn is_empty(&self) -> bool {
- self.len() == 0
+ /// Returns the pivot table's dimensions.
+ pub fn dimensions(&self) -> &[Dimension] {
+ &self.dimensions
}
- /// Returns the number of (leaf) categories in this dimension.
- pub fn len(&self) -> usize {
- self.root.len()
+ /// Returns the pivot table's axes.
+ pub fn axes(&self) -> &EnumMap<Axis3, Axis> {
+ &self.axes
}
- pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
- self.root.nth_leaf(index)
+ /// Returns the pivot table's [Look], for modification.
+ pub fn look_mut(&mut self) -> &mut Look {
+ self.style.look_mut()
}
- pub fn leaf_path(&self, index: usize) -> Option<Path<'_>> {
- self.root.leaf_path(index, SmallVec::new())
+ /// Returns a label for the table, which is either its title or a default
+ /// string.
+ pub fn label(&self) -> String {
+ match &self.metadata.title {
+ Some(title) => title.display(self).to_string(),
+ None => String::from("Table"),
+ }
}
- pub fn index_path(&self, index: usize) -> Option<IndexVec> {
- self.root.index_path(index, SmallVec::new())
+ /// Returns the table's title, or an empty value if it doesn't have one.
+ pub fn title(&self) -> &Value {
+ match &self.metadata.title {
+ Some(title) => title,
+ None => Value::static_empty(),
+ }
}
- pub fn with_all_labels_hidden(self) -> Self {
- Self {
- hide_all_labels: true,
- ..self
+ /// Returns the table's subtype, or an empty value if it doesn't have one.
+ pub fn subtype(&self) -> &Value {
+ match &self.metadata.subtype {
+ Some(subtype) => subtype,
+ None => Value::static_empty(),
}
}
-}
-
-/// Specifies a [Category] within a [Group].
-#[derive(Copy, Clone, Debug)]
-pub struct CategoryLocator {
- /// The index of the leaf to start from.
- pub leaf_index: usize,
- /// The number of times to go up a level from the leaf. If this category is
- /// a leaf, this is 0, otherwise it is positive.
- pub level: usize,
-}
+ /// Returns the `HashMap` for cells. Indexes in the map can be computed
+ /// with [cell_index](Self::cell_index).
+ pub fn cells(&self) -> &HashMap<usize, Value> {
+ &self.cells
+ }
-impl CategoryLocator {
- pub fn new_leaf(leaf_index: usize) -> Self {
- Self {
- leaf_index,
- level: 0,
- }
+ /// Computes an index into the `cells` `HashMap` for `cell_index`.
+ pub fn cell_index<C>(&self, cell_index: C) -> usize
+ where
+ C: CellIndex,
+ {
+ cell_index.cell_index(self.dimensions.iter().map(|d| d.len()))
}
- pub fn parent(&self) -> Self {
- Self {
- leaf_index: self.leaf_index,
- level: self.level + 1,
- }
+ /// Inserts a cell with the given `value` and `cell_index`.
+ pub fn insert<C>(&mut self, cell_index: C, value: impl Into<Value>)
+ where
+ C: CellIndex,
+ {
+ self.cells.insert(self.cell_index(cell_index), value.into());
}
- pub fn as_leaf(&self) -> Option<usize> {
- (self.level == 0).then_some(self.leaf_index)
+ /// Returns the cell with the given `cell_index`, if there is one.
+ pub fn get<C>(&self, cell_index: C) -> Option<&Value>
+ where
+ C: CellIndex,
+ {
+ self.cells.get(&self.cell_index(cell_index))
}
-}
-#[derive(Clone, Debug, Serialize)]
-pub struct Group {
- #[serde(skip)]
- len: usize,
- pub name: Box<Value>,
+ /// Returns the pivot table with cell indexes and values from `iter`
+ /// inserted as data.
+ pub fn with_data<C>(mut self, iter: impl IntoIterator<Item = (C, Value)>) -> Self
+ where
+ C: CellIndex,
+ {
+ self.extend(iter);
+ self
+ }
- /// The child categories.
- ///
+ /// Converts per-axis presentation-order indexes in `presentation_indexes`,
+ /// into data indexes for each dimension.
+ fn convert_indexes_ptod(
+ &self,
+ presentation_indexes: EnumMap<Axis3, &[usize]>,
+ ) -> SmallVec<[usize; 4]> {
+ let mut data_indexes = SmallVec::from_elem(0, self.dimensions.len());
+ for (axis, presentation_indexes) in presentation_indexes {
+ for (&dim_index, &pindex) in self.axes[axis]
+ .dimensions
+ .iter()
+ .zip(presentation_indexes.iter())
+ {
+ data_indexes[dim_index] = self.dimensions[dim_index].presentation_order[pindex];
+ }
+ }
+ data_indexes
+ }
+
+ /// Returns an iterator for the layer axis:
+ ///
+ /// - If `print` is true and `self.look.print_all_layers`, then the iterator
+ /// will visit all values of the layer axis.
+ ///
+ /// - Otherwise, the iterator will just visit `self.current_layer`.
+ pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>>> {
+ if print && self.style.look.print_all_layers {
+ Box::new(self.axis_values(Axis3::Z))
+ } else {
+ Box::new(once(SmallVec::from_slice(&self.layer)))
+ }
+ }
+
+ pub fn transpose(&mut self) {
+ self.axes.swap(Axis3::X, Axis3::Y);
+ }
+
+ pub fn axis_dimensions(
+ &self,
+ axis: Axis3,
+ ) -> impl DoubleEndedIterator<Item = &Dimension> + ExactSizeIterator {
+ self.axes[axis]
+ .dimensions
+ .iter()
+ .copied()
+ .map(|index| &self.dimensions[index])
+ }
+
+ fn find_dimension(&self, dim_index: usize) -> Option<(Axis3, usize)> {
+ debug_assert!(dim_index < self.dimensions.len());
+ for axis in enum_iterator::all::<Axis3>() {
+ for (position, dimension) in self.axes[axis].dimensions.iter().copied().enumerate() {
+ if dimension == dim_index {
+ return Some((axis, position));
+ }
+ }
+ }
+ None
+ }
+ pub fn move_dimension(&mut self, dim_index: usize, new_axis: Axis3, new_position: usize) {
+ let (old_axis, old_position) = self.find_dimension(dim_index).unwrap();
+ if old_axis == new_axis && old_position == new_position {
+ return;
+ }
+
+ // Update the current layer, if necessary. If we're moving within the
+ // layer axis, preserve the current layer.
+ match (old_axis, new_axis) {
+ (Axis3::Z, Axis3::Z) => {
+ // Rearrange the layer axis.
+ if old_position < new_position {
+ self.layer[old_position..=new_position].rotate_left(1);
+ } else {
+ self.layer[new_position..=old_position].rotate_right(1);
+ }
+ }
+ (Axis3::Z, _) => {
+ // A layer is becoming a row or column.
+ self.layer.remove(old_position);
+ }
+ (_, Axis3::Z) => {
+ // A row or column is becoming a layer.
+ self.layer.insert(new_position, 0);
+ }
+ _ => (),
+ }
+
+ self.axes[old_axis].dimensions.remove(old_position);
+ self.axes[new_axis]
+ .dimensions
+ .insert(new_position, dim_index);
+ }
+}
+
+impl IntoValueOptions for &PivotTable {
+ fn into_value_options(self) -> ValueOptions {
+ ValueOptions {
+ show_values: self.style.show_values,
+ show_variables: self.style.show_variables,
+ small: self.style.small,
+ footnote_marker_type: self.style.look.footnote_marker_type,
+ }
+ }
+}
+
+/// A dimension.
+///
+/// A [Dimension] identifies the categories associated with a single dimension
+/// within a multidimensional pivot table.
+///
+/// A dimension contains a collection of categories, which are the leaves in a
+/// tree of groups.
+///
+/// (A dimension or a group can contain zero categories, but this is unusual.
+/// If a dimension contains no categories, then its table cannot contain any
+/// data.)
+#[derive(Clone, Debug, Serialize)]
+pub struct Dimension {
+ /// Hierarchy of categories within the dimension. The groups and categories
+ /// are sorted in the order that should be used for display. This might be
+ /// different from the original order produced for output if the user
+ /// adjusted it.
+ ///
+ /// The root must always be a group, although it is allowed to have no
+ /// subcategories.
+ pub root: Group,
+
+ /// Ordering of leaves for presentation.
+ ///
+ /// This is a permutation of `0..n` where `n` is the number of leaves. It
+ /// maps from an index in presentation order to an index in data order.
+ pub presentation_order: Vec<usize>,
+
+ /// Display.
+ pub hide_all_labels: bool,
+}
+
+pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>;
+pub struct Path<'a> {
+ groups: GroupVec<'a>,
+ leaf: &'a Leaf,
+}
+
+pub type IndexVec = SmallVec<[usize; 4]>;
+
+impl Dimension {
+ pub fn new(root: Group) -> Self {
+ Dimension {
+ presentation_order: (0..root.len()).collect(),
+ root,
+ hide_all_labels: false,
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Returns the number of (leaf) categories in this dimension.
+ pub fn len(&self) -> usize {
+ self.root.len()
+ }
+
+ pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
+ self.root.nth_leaf(index)
+ }
+
+ pub fn leaf_path(&self, index: usize) -> Option<Path<'_>> {
+ self.root.leaf_path(index, SmallVec::new())
+ }
+
+ pub fn index_path(&self, index: usize) -> Option<IndexVec> {
+ self.root.index_path(index, SmallVec::new())
+ }
+
+ pub fn with_all_labels_hidden(self) -> Self {
+ Self {
+ hide_all_labels: true,
+ ..self
+ }
+ }
+}
+
+/// Specifies a [Category] within a [Group].
+#[derive(Copy, Clone, Debug)]
+pub struct CategoryLocator {
+ /// The index of the leaf to start from.
+ pub leaf_index: usize,
+
+ /// The number of times to go up a level from the leaf. If this category is
+ /// a leaf, this is 0, otherwise it is positive.
+ pub level: usize,
+}
+
+impl CategoryLocator {
+ pub fn new_leaf(leaf_index: usize) -> Self {
+ Self {
+ leaf_index,
+ level: 0,
+ }
+ }
+
+ pub fn parent(&self) -> Self {
+ Self {
+ leaf_index: self.leaf_index,
+ level: self.level + 1,
+ }
+ }
+
+ pub fn as_leaf(&self) -> Option<usize> {
+ (self.level == 0).then_some(self.leaf_index)
+ }
+}
+
+#[derive(Clone, Debug, Serialize)]
+pub struct Group {
+ #[serde(skip)]
+ len: usize,
+ pub name: Box<Value>,
+
+ /// The child categories.
+ ///
/// A group usually has multiple children, but it is allowed to have
/// only one or even (pathologically) none.
pub children: Vec<Category>,
}
}
+/// A collection of [Footnote]s for a [PivotTable].
+///
+/// Any [Value] in a pivot table can refer to a footnote. All of the footnotes
+/// used in any [Value] within a given pivot table must be collected into the
+/// [Footnotes] attached to the pivot table. (Footnotes used but not collected
+/// might not display as intended, but it's not a safety issue.)
#[derive(Clone, Debug, Default, Serialize)]
pub struct Footnotes(Vec<Arc<Footnote>>);
}
/// A [Look] and other styling for a [PivotTable].
-///
-/// The division between [Look] and the rest of the styling in this structure is
-/// fairly arbitrary. The ultimate reason for the division is simply because
-/// that's how SPSS documentation and file formats do it.
#[derive(Clone, Debug, Serialize)]
pub struct PivotTableStyle {
/// The [Look].
+ ///
+ /// The division between [Look] and the rest of the styling in this
+ /// structure is fairly arbitrary. The ultimate reason for the division is
+ /// simply because that's how SPSS documentation and file formats do it.
pub look: Arc<Look>,
+ /// Display inner column labels as vertical text?
pub rotate_inner_column_labels: bool,
+ /// Display outer row labels as vertical text?
pub rotate_outer_row_labels: bool,
+ /// Show grid lines between data cells?
pub show_grid_lines: bool,
+ /// Display the title?
pub show_title: bool,
+ /// Display the caption?
pub show_caption: bool,
+ /// Default [Show] value for showing values in [Value]s that don't specify
+ /// their own.
+ ///
+ /// If this is `None` then a global default is used.
pub show_values: Option<Show>,
+ /// Default [Show] value for showing variables in [Value]s that don't
+ /// specify their own.
+ ///
+ /// If this is `None` then a global default is used.
pub show_variables: Option<Show>,
+
/// Column and row sizing and page breaks:
///
/// - `sizing[Axis2::X]` is sizes for columns.
/// Numeric grouping character (usually `.` or `,`).
pub grouping: Option<char>,
+ /// The threshold for [DatumValue::honor_small].
+ ///
+ /// [DatumValue::honor_small]: value::DatumValue::honor_small
pub small: f64,
pub weight_format: Format,
}
}
+/// Metadata for a [PivotTable].
#[derive(Clone, Debug, Default, Serialize)]
pub struct PivotTableMetadata {
+ /// Title.
+ ///
+ /// The title is displayed above the table. Every table should have a
+ /// title.
+ pub title: Option<Box<Value>>,
+
+ /// Caption.
+ ///
+ /// The caption is displayed below the table. Captions are optional.
+ pub caption: Option<Box<Value>>,
+
+ /// Corner text, displayed in the top-left corner of the table. Corner text
+ /// is optional.
+ pub corner_text: Option<Box<Value>>,
+
+ /// User-specified optional notes, with special variables expanded into
+ /// their values.
+ pub notes: Option<String>,
+
+ /// User-specified optional notes, with special variables left in their
+ /// original forms.
+ ///
+ /// This allows the notes to be edited in their original form and then
+ /// expanded for display.
+ pub notes_unexpanded: Option<String>,
+
+ /// The localized name of the command that produced this pivot table,
+ /// e.g. `Frequencies` translated into the local language.
pub command_local: Option<String>,
+
+ /// The locale-invariant name of the command that produced this pivot table,
+ /// e.g. `Frequencies`.
pub command_c: Option<String>,
+
+ /// The locale-invariant command ID for the particular kind of output that
+ /// this table represents in the procedure. This can be the same as
+ /// `command_c`, e.g. `Frequencies`, or different, e.g. `Case Processing
+ /// Summary`.
+ ///
+ /// `Notes` and `Warnings` are common generic subtypes.
+ pub subtype: Option<Box<Value>>,
+
+ /// The language used in output.
pub language: Option<String>,
+
+ /// A locale, including an encoding, such as `en_US.windows-1252` or
+ /// `it_IT.windows-1252`.
pub locale: Option<String>,
+
+ /// Name of the dataset analyzed to produce the output, e.g. `DataSet1`.
pub dataset: Option<String>,
+
+ /// Name of the file that the dataset is from, e.g. `C:\Users\foo\bar.sav`.
pub datafile: Option<String>,
+
+ /// Creation date for the table.
pub date: Option<NaiveDateTime>,
- pub title: Option<Box<Value>>,
- pub subtype: Option<Box<Value>>,
- pub corner_text: Option<Box<Value>>,
- pub caption: Option<Box<Value>>,
- pub notes: Option<String>,
-}
-
-#[derive(Clone, Debug, Serialize)]
-pub struct PivotTable {
- pub style: PivotTableStyle,
-
- /// Current layer indexes, with `axes[Axis3::Z].dimensions.len()` elements.
- /// `current_layer[i]` is an offset into
- /// `axes[Axis3::Z].dimensions[i].data_leaves[]`, except that a dimension
- /// can have zero leaves, in which case `current_layer[i]` is zero and
- /// there's no corresponding leaf.
- pub current_layer: Vec<usize>,
-
- pub metadata: PivotTableMetadata,
- pub footnotes: Footnotes,
- dimensions: Vec<Dimension>,
- axes: EnumMap<Axis3, Axis>,
- cells: HashMap<usize, Value>,
}
impl PivotTableMetadata {
}
}
-impl PivotTable {
- pub fn cells(&self) -> &HashMap<usize, Value> {
- &self.cells
- }
- pub fn dimensions(&self) -> &[Dimension] {
- &self.dimensions
- }
- pub fn axes(&self) -> &EnumMap<Axis3, Axis> {
- &self.axes
- }
-
- pub fn with_title(mut self, title: impl Into<Value>) -> Self {
- self.metadata.title = Some(Box::new(title.into()));
- self.style.show_title = true;
- self
- }
-
- pub fn with_caption(mut self, caption: impl Into<Value>) -> Self {
- self.metadata.caption = Some(Box::new(caption.into()));
- self.style.show_caption = true;
- self
- }
-
- pub fn with_corner_text(mut self, corner_text: impl Into<Value>) -> Self {
- self.metadata.corner_text = Some(Box::new(corner_text.into()));
- self
- }
-
- pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
- Self {
- metadata: self.metadata.with_subtype(subtype),
- ..self
- }
- }
-
- pub fn with_show_values(self, show_values: Option<Show>) -> Self {
- Self {
- style: self.style.with_show_values(show_values),
- ..self
- }
- }
-
- pub fn with_show_variables(self, show_variables: Option<Show>) -> Self {
- Self {
- style: self.style.with_show_variables(show_variables),
- ..self
- }
- }
-
- pub fn with_show_title(self, show_title: bool) -> Self {
- Self {
- style: self.style.with_show_title(show_title),
- ..self
- }
- }
-
- pub fn with_show_caption(self, show_caption: bool) -> Self {
- Self {
- style: self.style.with_show_caption(show_caption),
- ..self
- }
- }
-
- pub fn with_layer(mut self, layer: &[usize]) -> Self {
- debug_assert_eq!(layer.len(), self.current_layer.len());
- if self.style.look.print_all_layers {
- self.style.look_mut().print_all_layers = false;
- }
- self.current_layer.clear();
- self.current_layer.extend_from_slice(layer);
- self
- }
-
- pub fn with_all_layers(mut self) -> Self {
- if !self.style.look.print_all_layers {
- self.look_mut().print_all_layers = true;
- }
- self
- }
-
- pub fn look_mut(&mut self) -> &mut Look {
- self.style.look_mut()
- }
-
- pub fn with_show_empty(mut self) -> Self {
- if self.style.look.hide_empty {
- self.look_mut().hide_empty = false;
- }
- self
- }
-
- pub fn with_hide_empty(mut self) -> Self {
- if !self.style.look.hide_empty {
- self.look_mut().hide_empty = true;
- }
- self
- }
-
- pub fn label(&self) -> String {
- match &self.metadata.title {
- Some(title) => title.display(self).to_string(),
- None => String::from("Table"),
- }
- }
+#[derive(Clone, Debug, Serialize)]
+pub struct PivotTable {
+ pub style: PivotTableStyle,
- pub fn title(&self) -> &Value {
- match &self.metadata.title {
- Some(title) => title,
- None => {
- static EMPTY: Value = Value::empty();
- &EMPTY
- }
- }
- }
+ /// Current layer indexes, with `axes[Axis3::Z].dimensions.len()` elements.
+ /// `layer[i]` is an offset into
+ /// `axes[Axis3::Z].dimensions[i].data_leaves[]`, except that a dimension
+ /// can have zero leaves, in which case `layer[i]` is zero and there's no
+ /// corresponding leaf.
+ layer: Vec<usize>,
- pub fn subtype(&self) -> &Value {
- match &self.metadata.subtype {
- Some(subtype) => subtype,
- None => {
- static EMPTY: Value = Value::empty();
- &EMPTY
- }
- }
- }
+ pub metadata: PivotTableMetadata,
+ pub footnotes: Footnotes,
+ dimensions: Vec<Dimension>,
+ axes: EnumMap<Axis3, Axis>,
+ cells: HashMap<usize, Value>,
}
impl Default for PivotTable {
Self {
style: PivotTableStyle::default(),
metadata: PivotTableMetadata::default(),
- current_layer: Vec::new(),
+ layer: Vec::new(),
footnotes: Footnotes::new(),
dimensions: Vec::new(),
axes: EnumMap::default(),
}
}
-impl PivotTable {
- pub fn new(axes_and_dimensions: impl IntoIterator<Item = (Axis3, Dimension)>) -> Self {
- let mut dimensions = Vec::new();
- let mut axes = EnumMap::<Axis3, Axis>::default();
- for (axis, dimension) in axes_and_dimensions {
- axes[axis].dimensions.push(dimensions.len());
- dimensions.push(dimension);
- }
- Self {
- style: PivotTableStyle::default().with_look(Settings::global().look.clone()),
- current_layer: repeat_n(0, axes[Axis3::Z].dimensions.len()).collect(),
- axes,
- dimensions,
- ..Self::default()
- }
- }
- fn cell_index<C>(&self, cell_index: C) -> usize
- where
- C: CellIndex,
- {
- cell_index.cell_index(self.dimensions.iter().map(|d| d.len()))
- }
-
- pub fn insert<C>(&mut self, cell_index: C, value: impl Into<Value>)
- where
- C: CellIndex,
- {
- self.cells.insert(self.cell_index(cell_index), value.into());
- }
-
- pub fn get<C>(&self, cell_index: C) -> Option<&Value>
- where
- C: CellIndex,
- {
- self.cells.get(&self.cell_index(cell_index))
- }
-
- pub fn with_data<C>(mut self, iter: impl IntoIterator<Item = (C, Value)>) -> Self
- where
- C: CellIndex,
- {
- self.extend(iter);
- self
- }
-
- pub fn with_style(self, style: PivotTableStyle) -> Self {
- Self { style, ..self }
- }
- pub fn with_metadata(self, metadata: PivotTableMetadata) -> Self {
- Self { metadata, ..self }
- }
-
- /// Converts per-axis presentation-order indexes in `presentation_indexes`,
- /// into data indexes for each dimension.
- fn convert_indexes_ptod(
- &self,
- presentation_indexes: EnumMap<Axis3, &[usize]>,
- ) -> SmallVec<[usize; 4]> {
- let mut data_indexes = SmallVec::from_elem(0, self.dimensions.len());
- for (axis, presentation_indexes) in presentation_indexes {
- for (&dim_index, &pindex) in self.axes[axis]
- .dimensions
- .iter()
- .zip(presentation_indexes.iter())
- {
- data_indexes[dim_index] = self.dimensions[dim_index].presentation_order[pindex];
- }
- }
- data_indexes
- }
-
- /// Returns an iterator for the layer axis:
- ///
- /// - If `print` is true and `self.look.print_all_layers`, then the iterator
- /// will visit all values of the layer axis.
- ///
- /// - Otherwise, the iterator will just visit `self.current_layer`.
- pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>>> {
- if print && self.style.look.print_all_layers {
- Box::new(self.axis_values(Axis3::Z))
- } else {
- Box::new(once(SmallVec::from_slice(&self.current_layer)))
- }
- }
-
- pub fn value_options(&self) -> ValueOptions {
- ValueOptions {
- show_values: self.style.show_values,
- show_variables: self.style.show_variables,
- small: self.style.small,
- footnote_marker_type: self.style.look.footnote_marker_type,
- }
- }
-
- pub fn transpose(&mut self) {
- self.axes.swap(Axis3::X, Axis3::Y);
- }
-
- pub fn axis_dimensions(
- &self,
- axis: Axis3,
- ) -> impl DoubleEndedIterator<Item = &Dimension> + ExactSizeIterator {
- self.axes[axis]
- .dimensions
- .iter()
- .copied()
- .map(|index| &self.dimensions[index])
- }
-
- fn find_dimension(&self, dim_index: usize) -> Option<(Axis3, usize)> {
- debug_assert!(dim_index < self.dimensions.len());
- for axis in enum_iterator::all::<Axis3>() {
- for (position, dimension) in self.axes[axis].dimensions.iter().copied().enumerate() {
- if dimension == dim_index {
- return Some((axis, position));
- }
- }
- }
- None
- }
- pub fn move_dimension(&mut self, dim_index: usize, new_axis: Axis3, new_position: usize) {
- let (old_axis, old_position) = self.find_dimension(dim_index).unwrap();
- if old_axis == new_axis && old_position == new_position {
- return;
- }
-
- // Update the current layer, if necessary. If we're moving within the
- // layer axis, preserve the current layer.
- match (old_axis, new_axis) {
- (Axis3::Z, Axis3::Z) => {
- // Rearrange the layer axis.
- if old_position < new_position {
- self.current_layer[old_position..=new_position].rotate_left(1);
- } else {
- self.current_layer[new_position..=old_position].rotate_right(1);
- }
- }
- (Axis3::Z, _) => {
- // A layer is becoming a row or column.
- self.current_layer.remove(old_position);
- }
- (_, Axis3::Z) => {
- // A row or column is becoming a layer.
- self.current_layer.insert(new_position, 0);
- }
- _ => (),
- }
-
- self.axes[old_axis].dimensions.remove(old_position);
- self.axes[new_axis]
- .dimensions
- .insert(new_position, dim_index);
- }
-}
-
impl<C> Extend<(C, Value)> for PivotTable
where
C: CellIndex,
// Warn about missing docs, but not for items declared with `#[cfg(test)]`.
#![cfg_attr(not(test), warn(missing_docs))]
+#![warn(dead_code)]
use crate::{
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,
+ DisplayMarker, Footnote, FootnoteMarkerType,
look::{CellStyle, FontStyle},
},
settings::{Settings, Show},
}
}
- /// Constructs a new `Value` as a number whose value is `date_time`
- /// converted to the [PSPP date representation](crate::calendar).
+ /// Constructs a new `Value` from `number` with a default [F8_2] 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 shortcut 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_number(number: Option<f64>) -> Self {
+ Self::new(ValueInner::Datum(DatumValue::new_number(number)))
+ }
+
+ /// Construct a new `Value` from `number` with format [F8_0].
+ ///
+ /// [F8_0]: crate::format::F8_0
+ pub fn new_integer(x: Option<f64>) -> Self {
+ Self::new_number(x).with_format(F40)
+ }
+
+ /// Constructs a new `Value` as a number whose value is `date_time`, which
+ /// is converted to the [PSPP date representation](crate::calendar), with
+ /// format [DATETIME40_0].
pub fn new_date(date_time: NaiveDateTime) -> Self {
Self::new_number(Some(date_time_to_pspp(date_time))).with_format(DATETIME40_0)
}
- /// Constructs a new `Value` as a number whose value is `time`
- /// converted to the [PSPP time representation](crate::calendar).
+ /// Constructs a new `Value` as a number whose value is `time`, which is
+ /// converted to the [PSPP time representation](crate::calendar), with
+ /// format [TIME40_0].
pub fn new_time(time: NaiveTime) -> Self {
Self::new_number(Some(time_to_pspp(time))).with_format(TIME40_0)
}
+ /// Constructs a new `Value` from localizable text string `s`.
+ ///
+ /// PSPP doesn't support internationalization yet, so this does the same
+ /// thing as [new_user_text] for now.
+ ///
+ /// [new_user_text]: Self::new_user_text
+ pub fn new_text(s: impl Into<String>) -> Self {
+ Self::new_user_text(s)
+ }
+
+ /// Constructs a new `Value` from localizable text string `localized`,
+ /// English string `c`, and identifier `id`. If the string came from the
+ /// user, `user_provided` should be true.
+ pub fn new_general_text(localized: String, c: String, id: String, user_provided: bool) -> Self {
+ Self::new(ValueInner::Text(TextValue {
+ user_provided,
+ c: (c != localized).then_some(c),
+ id: (id != localized).then_some(id),
+ localized,
+ }))
+ }
+
+ /// Constructs a new `Value` from `markup`.
+ pub fn new_markup(markup: Markup) -> Self {
+ Self::new(ValueInner::Markup(markup))
+ }
+
+ /// Constructs a new text `Value` from `s`, which should have been provided
+ /// by the user.
+ pub fn new_user_text(s: impl Into<String>) -> Self {
+ let s: String = s.into();
+ if s.is_empty() {
+ Self::default()
+ } else {
+ Self::new(ValueInner::Text(TextValue {
+ user_provided: true,
+ localized: s,
+ c: None,
+ id: None,
+ }))
+ }
+ }
+
/// Constructs a new `Value` from `variable`.
pub fn new_variable(variable: &Variable) -> Self {
Self::new(ValueInner::Variable(VariableValue {
}))
}
- /// Construct a new `Value` from `datum` with a default format. Some
+ /// Constructs 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
+ /// datum came from (or use [new_datum_from_variable] as a shortcut to
/// combine both).
///
/// - [with_format] to override the default format.
Self::new(ValueInner::Datum(DatumValue::new(datum)))
}
- /// 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),
- ..self
+ /// Construct a new, empty `Value`.
+ pub const fn new_empty() -> Self {
+ // Can't use `Self::default()` because that is non-const.
+ Value {
+ inner: ValueInner::Empty,
+ styling: None,
}
}
- pub fn with_honor_small(self, honor_small: bool) -> Self {
- Self {
- inner: self.inner.with_honor_small(honor_small),
- ..self
- }
+ /// Returns a reference to a statically allocated empty `Value`.
+ pub const fn static_empty() -> &'static Self {
+ static EMPTY: Value = Value::new_empty();
+ &EMPTY
+ }
+
+ /// Returns true if this `Value` is empty and unstyled.
+ pub const fn is_empty(&self) -> bool {
+ self.inner.is_empty() && self.styling.is_none()
}
+ /// Returns this value with its value label, format, and variable name from
+ /// `variable`.
pub fn with_source_variable(self, variable: &Variable) -> Self {
let value_label = self
.datum()
.with_variable_name(Some(variable.name.as_str().into()))
}
+ /// Returns this value with its display format set to `format`, if it is a
+ /// [DatumValue].
+ pub fn with_format(self, format: Format) -> 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)
}
+ /// Returns the inner [Datum], if this value is a [DatumValue].
pub fn datum(&self) -> Option<&Datum<WithEncoding<ByteString>>> {
self.inner.datum()
}
- pub fn new_number(number: Option<f64>) -> Self {
- Self::new(ValueInner::Datum(DatumValue::new_number(number)))
- }
-
- pub fn new_integer(x: Option<f64>) -> Self {
- Self::new_number(x).with_format(F40)
- }
- pub fn new_text(s: impl Into<String>) -> Self {
- Self::new_user_text(s)
- }
- pub fn new_general_text(localized: String, c: String, id: String, user_provided: bool) -> Self {
- Self::new(ValueInner::Text(TextValue {
- user_provided,
- c: (c != localized).then_some(c),
- id: (id != localized).then_some(id),
- localized,
- }))
- }
- pub fn new_markup(markup: Markup) -> Self {
- Self::new(ValueInner::Markup(markup))
- }
- pub fn new_user_text(s: impl Into<String>) -> Self {
- let s: String = s.into();
- if s.is_empty() {
- Self::default()
- } else {
- Self::new(ValueInner::Text(TextValue {
- user_provided: true,
- localized: s,
- c: None,
- id: None,
- }))
- }
- }
+ /// Returns this `Value` with the added `footnote`.
pub fn with_footnote(mut self, footnote: &Arc<Footnote>) -> Self {
self.add_footnote(footnote);
self
}
+
+ /// Adds `footnote` to this `Value`.
pub fn add_footnote(&mut self, footnote: &Arc<Footnote>) {
let footnotes = &mut self.styling_mut().footnotes;
footnotes.push(footnote.clone());
footnotes.sort_by_key(|f| f.index);
}
+
+ /// Returns this `Value` with `show` as the [Show] setting for value labels,
+ /// if this is a [DatumValue].
pub fn with_show_value_label(mut self, show: Option<Show>) -> Self {
if let Some(datum_value) = self.inner.as_datum_value_mut() {
datum_value.show = show;
}
self
}
- pub fn with_show_variable_label(mut self, show: Option<Show>) -> Self {
- if let ValueInner::Variable(variable_value) = &mut self.inner {
- variable_value.show = show;
- }
- self
- }
+
+ /// Returns this `Value` with `value_label` as the value label, if this is a
+ /// [DatumValue].
+ ///
+ /// Use [with_source_variable], instead, to automatically add a value label
+ /// and other information from a source variable.
+ ///
+ /// [with_source_variable]: Self::with_source_variable
pub fn with_value_label(mut self, value_label: Option<String>) -> Self {
if let Some(datum_value) = self.inner.as_datum_value_mut() {
datum_value.value_label = value_label.clone()
}
self
}
+
+ /// Returns this `Value` with `variable_name` as the variable's name, if
+ /// this is a [DatumValue].
+ ///
+ /// Use [with_source_variable], instead, to automatically add a variable
+ /// name and other information from a source variable.
+ ///
+ /// [with_source_variable]: Self::with_source_variable
pub fn with_variable_name(mut self, variable_name: Option<String>) -> Self {
- match &mut self.inner {
- ValueInner::Datum(DatumValue { variable, .. }) => *variable = variable_name,
- ValueInner::Variable(VariableValue {
- var_name: variable, ..
- }) => {
- if let Some(name) = variable_name {
- *variable = name;
- }
- }
- _ => (),
+ if let Some(datum_value) = self.inner.as_datum_value_mut() {
+ datum_value.variable = variable_name.clone()
}
self
}
- pub fn styling_mut(&mut self) -> &mut ValueStyle {
- self.styling.get_or_insert_default()
+
+ /// Returns this `Value` with `show` as the [Show] setting for variable
+ /// labels, if this is a [VariableValue].
+ pub fn with_show_variable_label(mut self, show: Option<Show>) -> Self {
+ if let ValueInner::Variable(variable_value) = &mut self.inner {
+ variable_value.show = show;
+ }
+ self
}
+
+ /// Returns this `Value` with the specified `font_style`.
pub fn with_font_style(mut self, font_style: FontStyle) -> Self {
self.styling_mut().font_style = Some(font_style);
self
}
+
+ /// Returns this `Value` with the specified `cell_style`.
pub fn with_cell_style(mut self, cell_style: CellStyle) -> Self {
self.styling_mut().cell_style = Some(cell_style);
self
}
+
+ /// Returns this `Value` with the specified `styling`.
pub fn with_styling(self, styling: Option<Box<ValueStyle>>) -> Self {
Self { styling, ..self }
}
+
+ /// Returns the styling for this `Value` for modification.
+ ///
+ /// If this `Value` doesn't have styling yet, this creates it.
+ pub fn styling_mut(&mut self) -> &mut ValueStyle {
+ self.styling.get_or_insert_default()
+ }
+
+ /// Returns this `Value`'s font style, if it has one.
pub fn font_style(&self) -> Option<&FontStyle> {
self.styling
.as_ref()
.map(|styling| styling.font_style.as_ref())
.flatten()
}
+
+ /// Returns this `Value`'s cell style, if it has one.
pub fn cell_style(&self) -> Option<&CellStyle> {
self.styling
.as_ref()
.map(|styling| styling.cell_style.as_ref())
.flatten()
}
+
+ /// Returns this `Value`'s subscripts.
pub fn subscripts(&self) -> &[String] {
self.styling
.as_ref()
.map_or(&[], |styling| &styling.subscripts)
}
+
+ /// Returns this `Value`'s footnotes.
pub fn footnotes(&self) -> &[Arc<Footnote>] {
self.styling
.as_ref()
.map_or(&[], |styling| &styling.footnotes)
}
- pub const fn empty() -> Self {
- Value {
- inner: ValueInner::Empty,
- styling: None,
+
+ /// Returns an object that will format this value, including subscripts and
+ /// superscripts and footnotes. `options` controls whether variable and
+ /// value labels are included.
+ pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
+ let display = self.inner.display(options.into_value_options());
+ match &self.styling {
+ Some(styling) => display.with_styling(styling),
+ None => display,
}
}
- 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)]`.
}
}
+/// Helper struct for printing a [Value] with `format!` and `{}`.
+///
+/// Create this struct with [Value::display].
+#[derive(Clone, Debug)]
pub struct DisplayValue<'a> {
inner: &'a ValueInner,
subscripts: &'a [String],
}
impl<'a> DisplayValue<'a> {
- pub fn subscripts(&self) -> impl Iterator<Item = &str> {
+ /// Returns the subscripts to be displayed, as an iterator of `&str`.
+ pub fn subscripts(&self) -> impl Iterator<Item = &str> + ExactSizeIterator + Clone {
self.subscripts.iter().map(String::as_str)
}
+ /// Returns true if the value to be displayed includes subscripts.
pub fn has_subscripts(&self) -> bool {
!self.subscripts.is_empty()
}
- pub fn footnotes(&self) -> impl Iterator<Item = DisplayMarker<'_>> {
+ /// Returns the footnotes to be displayed, as an iterator of
+ /// [DisplayMarker].
+ pub fn footnotes(&self) -> impl Iterator<Item = DisplayMarker<'_>> + Clone {
self.footnotes
.iter()
.filter(|f| f.show)
.map(|f| f.display_marker(self.options))
}
+ /// Returns true if the value to be displayed includes footnotes.
pub fn has_footnotes(&self) -> bool {
self.footnotes().next().is_some()
}
+ /// Returns this [DisplayValue] modified so that it won't show any
+ /// subscripts or footnotes.
pub fn without_suffixes(self) -> Self {
Self {
subscripts: &[],
}
}
+ /// Returns this [DisplayValue] modified so that it will only show the
+ /// suffixes and footnotes, not the body.
+ pub fn without_body(self) -> Self {
+ Self {
+ inner: &ValueInner::Empty,
+ ..self
+ }
+ }
+
+ /// Returns the [Markup] to be formatted, if any.
pub fn markup(&self) -> Option<&Markup> {
- self.inner.markup()
+ self.inner.as_markup()
}
/// Returns this display split into `(body, suffixes)` where `suffixes` is
/// subscripts and footnotes and `body` is everything else.
pub fn split_suffixes(self) -> (Self, Self) {
- let suffixes = Self {
- inner: &ValueInner::Empty,
- ..self
- };
- (self.without_suffixes(), suffixes)
+ (self.clone().without_suffixes(), self.without_body())
}
+ /// Returns this display with subscripts and footnotes taken from `styling`.
+ ///
+ /// (This display can't use the other parts of `styling`, since we're just
+ /// formatting plain text.)
pub fn with_styling(mut self, styling: &'a ValueStyle) -> Self {
self.subscripts = styling.subscripts.as_slice();
self.footnotes = styling.footnotes.as_slice();
self
}
+ /// Returns this display with the given `subscripts.`
pub fn with_subscripts(self, subscripts: &'a [String]) -> Self {
Self { subscripts, ..self }
}
+ /// Returns this display with the given `footnotes.`
pub fn with_footnotes(self, footnotes: &'a [Arc<Footnote>]) -> Self {
Self { footnotes, ..self }
}
+ /// Returns true if this display will format to the empty string.
pub fn is_empty(&self) -> bool {
self.inner.is_empty() && self.subscripts.is_empty() && self.footnotes.is_empty()
}
self.options.small
}
+ /// Returns a variable type for the value to be displayed.
+ ///
+ /// We consider a numeric value displayed by itself to be numeric, but if
+ /// the value label is displayed then it is considered to be a string.
+ /// Anything else is also a string.
+ ///
+ /// This is useful for passing to [HorzAlign::for_mixed], although maybe
+ /// this method should just return [HorzAlign] directly.
+ ///
+ /// [HorzAlign]: crate::output::pivot::look::HorzAlign
+ /// [HorzAlign::for_mixed]: crate::output::pivot::look::HorzAlign::for_mixed
pub fn var_type(&self) -> VarType {
if let Some(datum_value) = self.inner.as_datum_value()
&& datum_value.datum.is_number()
}
}
-impl Value {
- // Returns an object that will format this value, including subscripts and
- // superscripts and footnotes. `options` controls whether variable and
- // value labels are included.
- pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
- let display = self.inner.display(options.into_value_options());
- match &self.styling {
- Some(styling) => display.with_styling(styling),
- None => display,
- }
- }
-}
-
impl Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match &self.inner {
ValueInner::Empty => "Empty",
};
write!(f, "{name}:{:?}", self.display(()).to_string())?;
- if let Some(markup) = self.inner.markup() {
+ if let Some(markup) = self.inner.as_markup() {
write!(f, " (markup: {markup:?})")?;
}
if let Some(styling) = &self.styling {
/// 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`.
+ /// 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.
}
impl DatumValue {
- pub fn new_number(number: Option<f64>) -> Self {
- Self::new(&Datum::<&str>::Number(number))
- }
+ /// Constructs a new `DatumValue` for `datum`.
pub fn new<B>(datum: &Datum<B>) -> Self
where
B: EncodedString,
value_label: None,
}
}
+
+ /// Constructs a new `DatumValue` for `number`.
+ pub fn new_number(number: Option<f64>) -> Self {
+ Self::new(&Datum::<&str>::Number(number))
+ }
+
+ /// Returns this `DatumValue` with the given `format`.
pub fn with_format(self, format: Format) -> 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,
display: &DisplayValue<'a>,
Ok(())
}
+ /// Serializes this value to `serializer` in the "bare" manner described for
+ /// [BareValue].
pub fn serialize_bare<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
/// 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.
+/// A `TextValue` is used for text within a table, such as a title, a column or
+/// row heading, or a footnote. (String data values are better represented as
+/// [DatumValue].)
#[derive(Clone, Debug, PartialEq)]
pub struct TextValue {
/// Whether the text came from the user.
}
impl TextValue {
+ /// Returns the localized version of this `TextValue`.
pub fn localized(&self) -> &str {
self.localized.as_str()
}
+
+ /// Returns the English version of this `TextValue`.
pub fn c(&self) -> &str {
self.c.as_ref().unwrap_or(&self.localized).as_str()
}
+
+ /// Returns an identifier for this `TextValue`.
pub fn id(&self) -> &str {
self.id.as_ref().unwrap_or(&self.localized).as_str()
}
&& let Some(arg) = self.args.get(index)
&& let Some(arg) = arg.first()
{
- arg.display(display.options).fmt(f)?;
+ write!(f, "{}", arg.display(display.options))?;
}
iter = rest.chars();
}
}
impl ValueInner {
+ /// Returns true if this is a [ValueInner::Empty].
pub const fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
+
+ /// 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 {
if let Some(datum_value) = self.as_datum_value_mut() {
datum_value.honor_small = honor_small;
self
}
+ /// Returns the [Datum] inside this value, if it is a [DatumValue].
pub fn datum(&self) -> Option<&Datum<WithEncoding<ByteString>>> {
self.as_datum_value().map(|d| &d.datum)
}
+ /// Returns the [Show] value inside this value, if it has one, or [None]
+ /// otherwise.
fn show(&self) -> Option<Show> {
match self {
ValueInner::Datum(DatumValue { show, .. })
}
}
- fn label(&self) -> Option<&str> {
+ /// Returns the value label or variable label inside this value, if it has
+ /// one.
+ pub fn label(&self) -> Option<&str> {
self.value_label().or_else(|| self.variable_label())
}
+ /// Returns the value label inside this value, if it has one.
fn value_label(&self) -> Option<&str> {
self.as_datum_value()
.and_then(|d| d.value_label.as_ref().map(String::as_str))
}
+ /// Returns the variable label inside this value, if it has one.
fn variable_label(&self) -> Option<&str> {
+ self.as_variable_value()
+ .and_then(|d| d.variable_label.as_ref().map(String::as_str))
+ }
+
+ /// Returns the [DatumValue] inside this value, if it is
+ /// [ValueInner::Datum].
+ pub fn as_datum_value(&self) -> Option<&DatumValue> {
match self {
- ValueInner::Variable(VariableValue { variable_label, .. }) => {
- variable_label.as_ref().map(String::as_str)
- }
+ ValueInner::Datum(datum) => Some(datum),
_ => None,
}
}
- fn markup(&self) -> Option<&Markup> {
+ /// Returns the [DatumValue] inside this value, mutably, if it is
+ /// [ValueInner::Datum].
+ pub fn as_datum_value_mut(&mut self) -> Option<&mut DatumValue> {
match self {
- ValueInner::Markup(markup) => Some(markup),
+ ValueInner::Datum(datum) => Some(datum),
_ => None,
}
}
- pub fn as_datum_value(&self) -> Option<&DatumValue> {
+ /// Returns the [VariableValue] inside this value, if it is
+ /// [ValueInner::Variable].
+ pub fn as_variable_value(&self) -> Option<&VariableValue> {
match self {
- ValueInner::Datum(datum) => Some(datum),
+ ValueInner::Variable(variable) => Some(variable),
_ => None,
}
}
- pub fn as_datum_value_mut(&mut self) -> Option<&mut DatumValue> {
+ /// Returns the [VariableValue] inside this value, mutably, if it is
+ /// [ValueInner::Variable].
+ pub fn as_variable_value_mut(&mut self) -> Option<&mut VariableValue> {
match self {
- ValueInner::Datum(datum) => Some(datum),
+ ValueInner::Variable(variable) => Some(variable),
_ => None,
}
}
- // Returns an object that will format this value. Settings on `options`
- // control whether variable and value labels are included.
+ /// Returns the [Markup] inside this value, if it is [ValueInner::Markup].
+ fn as_markup(&self) -> Option<&Markup> {
+ match self {
+ ValueInner::Markup(markup) => Some(markup),
+ _ => None,
+ }
+ }
+
+ /// 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<'_> {
fn interpret_show(
global_show: impl Fn() -> Show,
}
}
+/// Styling inside a [Value].
+///
+/// Most [Value]s use a default style, so this is a separate [Box]ed structure
+/// to save memory.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ValueStyle {
+ /// Cell style.
pub cell_style: Option<CellStyle>,
+
+ /// Font style.
pub font_style: Option<FontStyle>,
+
+ /// Subscripts.
pub subscripts: Vec<String>,
+
+ /// Footnotes.
pub footnotes: Vec<Arc<Footnote>>,
}
impl ValueStyle {
+ /// Returns true if this [ValueStyle] is empty.
+ ///
+ /// This will return false if the font style exists but is the default font
+ /// style, and similarly for the cell style.
pub fn is_empty(&self) -> bool {
self.font_style.is_none()
&& self.cell_style.is_none()
}
}
-/// Extracts [ValueOptions] from a pivot table.
-impl IntoValueOptions for &PivotTable {
- fn into_value_options(self) -> ValueOptions {
- self.value_options()
- }
-}
-
/// Copies [ValueOptions] by reference.
impl IntoValueOptions for &ValueOptions {
fn into_value_options(self) -> ValueOptions {