#![cfg_attr(not(test), warn(missing_docs))]
use std::{
collections::HashMap,
fmt::{Debug, Display},
iter::{FusedIterator, once, repeat_n, zip},
ops::{Index, IndexMut, Not, Range},
sync::Arc,
};
use chrono::{NaiveDateTime, Utc};
use enum_iterator::Sequence;
use enum_map::{Enum, EnumMap, enum_map};
use itertools::Itertools;
pub use look_xml::{Length, TableProperties};
use serde::{Deserialize, Serialize, ser::SerializeMap};
use smallvec::{SmallVec, smallvec};
use crate::{
format::{Decimal, F40, F40_2, F40_3, Format, PCT40_1, Settings as FormatSettings},
output::pivot::{
look::{Look, Sizing},
value::{BareValue, Value, ValueFormat, ValueOptions},
},
settings::{Settings, Show},
variable::Variable,
};
pub(crate) use tlo::parse_bool;
mod output;
pub use output::OutputTables;
mod look_xml;
mod tlo;
pub mod value;
#[cfg(test)]
pub mod tests;
pub mod look;
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Sequence, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Axis3 {
X,
Y,
Z,
}
impl Axis3 {
pub fn transpose(&self) -> Option<Self> {
match self {
Axis3::X => Some(Axis3::Y),
Axis3::Y => Some(Axis3::X),
Axis3::Z => None,
}
}
}
impl From<Axis2> for Axis3 {
fn from(axis2: Axis2) -> Self {
match axis2 {
Axis2::X => Self::X,
Axis2::Y => Self::Y,
}
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct Axis {
pub dimensions: Vec<usize>,
}
struct AxisIterator {
indexes: SmallVec<[usize; 4]>,
lengths: SmallVec<[usize; 4]>,
done: bool,
}
impl FusedIterator for AxisIterator {}
impl Iterator for AxisIterator {
type Item = SmallVec<[usize; 4]>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
None
} else {
let retval = self.indexes.clone();
for (index, len) in self.indexes.iter_mut().zip(self.lengths.iter().copied()) {
*index += 1;
if *index < len {
return Some(retval);
};
*index = 0;
}
self.done = true;
Some(retval)
}
}
}
impl PivotTable {
pub fn new(structure: impl Into<Structure>) -> Self {
let structure = structure.into();
Self {
style: PivotTableStyle::default().with_look(Settings::global().look.clone()),
layer: repeat_n(0, structure.axes[Axis3::Z].dimensions.len()).collect(),
structure,
..Self::default()
}
}
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_optional_title(self, title: Option<Value>) -> Self {
if let Some(title) = title {
self.with_title(title)
} else {
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_optional_caption(self, caption: Option<Value>) -> Self {
if let Some(caption) = caption {
self.with_caption(caption)
} else {
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_footnotes(mut self, footnotes: Footnotes) -> Self {
debug_assert!(self.footnotes.is_empty());
self.footnotes = footnotes;
self
}
pub fn with_look(self, look: Arc<Look>) -> Self {
Self {
style: self.style.with_look(look),
..self
}
}
pub fn with_style(self, style: PivotTableStyle) -> Self {
Self { style, ..self }
}
pub fn with_metadata(self, metadata: PivotTableMetadata) -> Self {
Self { metadata, ..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_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 with_layer(mut self, layer: &[usize]) -> Self {
assert!(self.is_valid_layer(layer));
self.layer.clear();
self.layer.extend_from_slice(layer);
self
}
pub fn is_valid_layer(&mut self, layer: &[usize]) -> bool {
let layer_dimensions = self.axis_dimensions(Axis3::Z);
layer.len() == layer_dimensions.len()
&& zip(layer, layer_dimensions).all(|(value, dimension)| *value < dimension.len())
}
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 with_decimal(mut self, decimal: Decimal) -> Self {
self.style.settings.decimal = decimal;
self
}
pub fn with_date(mut self, date: Option<NaiveDateTime>) -> Self {
self.metadata.date = date;
self
}
pub fn insert_number(&mut self, data_indexes: &[usize], number: Option<f64>, class: Class) {
let format = match class {
Class::Other => Settings::global().default_format,
Class::Integer => F40,
Class::Correlations => F40_3,
Class::Significance => F40_3,
Class::Percent => PCT40_1,
Class::Residual => F40_2,
Class::Count => F40, };
let value = Value::new_number(number).with_format(match class {
Class::Other => ValueFormat::SmallE(format),
_ => ValueFormat::Other(format),
});
self.insert(data_indexes, value);
}
fn axis_values(&self, axis: Axis3) -> AxisIterator {
self.structure.axis_values(axis)
}
fn axis_extent(&self, axis: Axis3) -> usize {
self.structure.axis_extent(axis)
}
pub fn layer(&self) -> &[usize] {
&self.layer
}
pub fn dimensions(&self) -> &[Dimension] {
&self.structure.dimensions
}
pub fn axes(&self) -> &EnumMap<Axis3, Axis> {
&self.structure.axes
}
pub fn look_mut(&mut self) -> &mut Look {
self.style.look_mut()
}
pub fn label(&self) -> String {
match &self.metadata.title {
Some(title) => title.display(self).to_string(),
None => String::from("Table"),
}
}
pub fn title(&self) -> &Value {
match &self.metadata.title {
Some(title) => title,
None => Value::static_empty(),
}
}
pub fn subtype(&self) -> &Value {
match &self.metadata.subtype {
Some(subtype) => subtype,
None => Value::static_empty(),
}
}
pub fn cells(&self) -> &HashMap<usize, Value> {
&self.cells
}
pub fn n_cells(&self) -> usize {
self.structure.n_cells()
}
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 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
}
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.structure.axes[axis]
.dimensions
.iter()
.zip(presentation_indexes.iter())
{
data_indexes[dim_index] = self.structure.dimensions[dim_index].ptod[pindex];
}
}
data_indexes
}
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.structure.axes.swap(Axis3::X, Axis3::Y);
}
pub fn axis_dimensions(
&self,
axis: Axis3,
) -> impl DoubleEndedIterator<Item = &Dimension> + ExactSizeIterator {
self.structure.axis_dimensions(axis)
}
fn find_dimension(&self, dim_index: usize) -> Option<(Axis3, usize)> {
debug_assert!(dim_index < self.structure.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;
}
match (old_axis, new_axis) {
(Axis3::Z, Axis3::Z) => {
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, _) => {
self.layer.remove(old_position);
}
(_, Axis3::Z) => {
self.layer.insert(new_position, 0);
}
_ => (),
}
self.structure.axes[old_axis]
.dimensions
.remove(old_position);
self.structure.axes[new_axis]
.dimensions
.insert(new_position, dim_index);
}
}
impl From<&PivotTable> for ValueOptions {
fn from(value: &PivotTable) -> Self {
ValueOptions {
show_values: value.style.show_values,
show_variables: value.style.show_variables,
small: value.style.small,
footnote_marker_type: value.style.look.footnote_marker_type,
settings: value.style.settings.clone().with_leading_zero_pct(true),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Dimension {
root: Group,
ptod: Vec<usize>,
pub hide_all_labels: bool,
}
pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>;
pub struct Path<'a> {
pub groups: GroupVec<'a>,
pub leaf: &'a Leaf,
}
impl<'a> Path<'a> {
pub fn into_parts(self) -> (GroupVec<'a>, &'a Leaf) {
(self.groups, self.leaf)
}
}
pub type IndexVec = SmallVec<[usize; 4]>;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct InvalidPermutation;
impl Dimension {
pub fn new(root: Group) -> Self {
Dimension {
ptod: (0..root.len()).collect(),
root,
hide_all_labels: false,
}
}
pub fn root(&self) -> &Group {
&self.root
}
pub fn ptod(&self) -> &[usize] {
&self.ptod
}
pub fn set_ptod(&mut self, ptod: Vec<usize>) -> Result<(), InvalidPermutation> {
if ptod.len() != self.ptod.len() {
return Err(InvalidPermutation);
}
let mut seen = vec![false; ptod.len()];
for element in ptod.iter().copied() {
if element >= ptod.len() || seen[element] {
return Err(InvalidPermutation);
}
seen[element] = true;
}
self.ptod = ptod;
Ok(())
}
pub fn with_all_labels_hidden(self) -> Self {
self.with_hide_all_labels(true)
}
pub fn with_hide_all_labels(self, hide_all_labels: bool) -> Self {
Self {
hide_all_labels,
..self
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
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())
}
}
#[derive(Copy, Clone, Debug)]
pub struct CategoryLocator {
pub leaf_index: usize,
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,
name: Box<Value>,
pub show_label: bool,
children: Vec<Category>,
}
impl Group {
pub fn new(name: impl Into<Value>) -> Self {
Self::with_capacity(name, 0)
}
pub fn children(&self) -> &[Category] {
&self.children
}
pub fn with_capacity(name: impl Into<Value>, capacity: usize) -> Self {
Self {
len: 0,
name: Box::new(name.into()),
children: Vec::with_capacity(capacity),
show_label: false,
}
}
pub fn push(&mut self, child: impl Into<Category>) {
let mut child = child.into();
if let Some(group) = child.as_group_mut() {
group.show_label = true;
}
self.len += child.len();
self.children.push(child);
}
pub fn with(mut self, child: impl Into<Category>) -> Self {
self.push(child);
self
}
pub fn with_multiple<C>(mut self, children: impl IntoIterator<Item = C>) -> Self
where
C: Into<Category>,
{
self.extend(children);
self
}
pub fn with_label_shown(self) -> Self {
self.with_show_label(true)
}
pub fn with_show_label(mut self, show_label: bool) -> Self {
self.show_label = show_label;
self
}
fn nth_leaf(&self, mut index: usize) -> Option<&Leaf> {
for child in &self.children {
let len = child.len();
if index < len {
return child.nth_leaf(index);
}
index -= len;
}
None
}
fn leaf_path<'a>(&'a self, mut index: usize, mut groups: GroupVec<'a>) -> Option<Path<'a>> {
for child in &self.children {
let len = child.len();
if index < len {
groups.push(self);
return child.leaf_path(index, groups);
}
index -= len;
}
None
}
fn index_path(&self, mut index: usize, mut path: IndexVec) -> Option<IndexVec> {
for (i, child) in self.children.iter().enumerate() {
let len = child.len();
if index < len {
path.push(i);
return child.index_path(index, path);
}
index -= len;
}
None
}
fn locator_path(&self, locator: CategoryLocator) -> Option<IndexVec> {
let mut path = self.index_path(locator.leaf_index, IndexVec::new())?;
path.truncate(path.len().checked_sub(locator.level)?);
Some(path)
}
pub fn category(&self, locator: CategoryLocator) -> Option<&Category> {
let path = self.locator_path(locator)?;
let mut this = &self.children[*path.get(0)?];
for index in path[1..].iter().copied() {
this = &this.as_group().unwrap().children[index];
}
Some(this)
}
pub fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
let path = self.locator_path(locator)?;
let mut this = &mut self.children[*path.get(0)?];
for index in path[1..].iter().copied() {
this = &mut this.as_group_mut().unwrap().children[index];
}
Some(this)
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn name(&self) -> &Value {
&self.name
}
}
impl<C> Extend<C> for Group
where
C: Into<Category>,
{
fn extend<T: IntoIterator<Item = C>>(&mut self, children: T) {
let children = children.into_iter();
self.children.reserve(children.size_hint().0);
for child in children {
self.push(child);
}
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct Footnotes(Vec<Arc<Footnote>>);
impl Footnotes {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn push(&mut self, footnote: Footnote) -> Arc<Footnote> {
let footnote = Arc::new(footnote.with_index(self.0.len()));
self.0.push(footnote.clone());
footnote
}
pub fn get(&self, index: usize) -> Option<&Arc<Footnote>> {
self.0.get(index)
}
}
impl Index<usize> for Footnotes {
type Output = Arc<Footnote>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<'a> IntoIterator for &'a Footnotes {
type Item = &'a Arc<Footnote>;
type IntoIter = std::slice::Iter<'a, Arc<Footnote>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl FromIterator<Footnote> for Footnotes {
fn from_iter<T: IntoIterator<Item = Footnote>>(iter: T) -> Self {
Self(
iter.into_iter()
.enumerate()
.map(|(index, footnote)| Arc::new(footnote.with_index(index)))
.collect(),
)
}
}
#[derive(Clone, Debug)]
pub struct Leaf {
data_index: usize,
name: Box<Value>,
}
impl Leaf {
pub fn new(name: Value) -> Self {
Self {
data_index: 0,
name: Box::new(name),
}
}
pub fn name(&self) -> &Value {
&self.name
}
pub fn numbers(range: Range<usize>) -> impl Iterator<Item = Leaf> {
range.map(|i| Self::new(Value::new_integer(Some(i as f64))))
}
}
impl Serialize for Leaf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.name.serialize(serializer)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Class {
Integer,
Correlations,
Significance,
Percent,
Residual,
Count,
Other,
}
#[derive(Clone, Debug, Serialize)]
pub enum Category {
Group(
Group,
),
Leaf(
Leaf,
),
}
impl Category {
pub fn as_group(&self) -> Option<&Group> {
match self {
Category::Group(group) => Some(group),
Category::Leaf(_) => None,
}
}
pub fn as_group_mut(&mut self) -> Option<&mut Group> {
match self {
Category::Group(group) => Some(group),
Category::Leaf(_) => None,
}
}
pub fn as_leaf(&self) -> Option<&Leaf> {
match self {
Category::Leaf(leaf) => Some(leaf),
Category::Group(_) => None,
}
}
pub fn as_leaf_mut(&mut self) -> Option<&mut Leaf> {
match self {
Category::Leaf(leaf) => Some(leaf),
Category::Group(_) => None,
}
}
pub fn name(&self) -> &Value {
match self {
Category::Group(group) => &group.name,
Category::Leaf(leaf) => &leaf.name,
}
}
pub fn name_mut(&mut self) -> &mut Value {
match self {
Category::Group(group) => &mut group.name,
Category::Leaf(leaf) => &mut leaf.name,
}
}
pub fn show_label(&self) -> bool {
match self {
Category::Group(group) => group.show_label,
Category::Leaf(_) => true,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
match self {
Category::Group(group) => group.len,
Category::Leaf(_) => 1,
}
}
fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
match self {
Category::Group(group) => group.nth_leaf(index),
Category::Leaf(leaf) if index == 0 => Some(leaf),
_ => None,
}
}
fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option<Path<'a>> {
match self {
Category::Group(group) => group.leaf_path(index, groups),
Category::Leaf(leaf) if index == 0 => Some(Path { groups, leaf }),
_ => None,
}
}
fn index_path(&self, index: usize, path: IndexVec) -> Option<IndexVec> {
match self {
Category::Group(group) => group.index_path(index, path),
Category::Leaf(_) if index == 0 => Some(path),
_ => None,
}
}
fn locator_path(&self, locator: CategoryLocator) -> Option<IndexVec> {
let mut path = self.index_path(locator.leaf_index, IndexVec::new())?;
path.truncate(path.len().checked_sub(locator.level)?);
Some(path)
}
fn category(&self, locator: CategoryLocator) -> Option<&Category> {
let mut this = self;
for index in this.locator_path(locator)? {
this = &this.as_group().unwrap().children[index];
}
Some(this)
}
fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
let mut this = self;
for index in this.locator_path(locator)? {
this = &mut this.as_group_mut().unwrap().children[index];
}
Some(this)
}
}
impl From<Group> for Category {
fn from(group: Group) -> Self {
Self::Group(group)
}
}
impl From<Leaf> for Category {
fn from(group: Leaf) -> Self {
Self::Leaf(group)
}
}
impl From<Value> for Category {
fn from(name: Value) -> Self {
Leaf::new(name).into()
}
}
impl From<&Variable> for Category {
fn from(variable: &Variable) -> Self {
Value::new_variable(variable).into()
}
}
impl From<&str> for Category {
fn from(name: &str) -> Self {
Self::Leaf(Leaf::new(Value::new_text(name)))
}
}
impl From<String> for Category {
fn from(name: String) -> Self {
Self::Leaf(Leaf::new(Value::new_text(name)))
}
}
impl From<&String> for Category {
fn from(name: &String) -> Self {
Self::Leaf(Leaf::new(Value::new_text(name)))
}
}
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Axis2 {
X,
Y,
}
impl Axis2 {
pub fn new_enum<T>(x: T, y: T) -> EnumMap<Axis2, T> {
EnumMap::from_array([x, y])
}
pub fn as_str(&self) -> &'static str {
match self {
Axis2::X => "x",
Axis2::Y => "y",
}
}
}
impl Display for Axis2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Not for Axis2 {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Self::X => Self::Y,
Self::Y => Self::X,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("Can't convert `Axis3::Z` to `Axis2`.")]
pub struct ZAxis;
impl TryFrom<Axis3> for Axis2 {
type Error = ZAxis;
fn try_from(value: Axis3) -> Result<Self, Self::Error> {
match value {
Axis3::X => Ok(Axis2::X),
Axis3::Y => Ok(Axis2::Y),
Axis3::Z => Err(ZAxis),
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Coord2(pub EnumMap<Axis2, isize>);
impl Coord2 {
pub fn new(x: isize, y: isize) -> Self {
use Axis2::*;
Self(enum_map! {
X => x,
Y => y
})
}
pub fn for_axis((a, az): (Axis2, isize), bz: isize) -> Self {
let mut coord = Self::default();
coord[a] = az;
coord[!a] = bz;
coord
}
pub fn from_fn<F>(f: F) -> Self
where
F: FnMut(Axis2) -> isize,
{
Self(EnumMap::from_fn(f))
}
pub fn x(&self) -> isize {
self.0[Axis2::X]
}
pub fn y(&self) -> isize {
self.0[Axis2::Y]
}
pub fn get(&self, axis: Axis2) -> isize {
self.0[axis]
}
}
impl From<EnumMap<Axis2, isize>> for Coord2 {
fn from(value: EnumMap<Axis2, isize>) -> Self {
Self(value)
}
}
impl Index<Axis2> for Coord2 {
type Output = isize;
fn index(&self, index: Axis2) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<Axis2> for Coord2 {
fn index_mut(&mut self, index: Axis2) -> &mut Self::Output {
&mut self.0[index]
}
}
#[derive(Clone, Debug, Default)]
pub struct Rect2(
pub EnumMap<Axis2, Range<isize>>,
);
impl Rect2 {
pub fn new(x_range: Range<isize>, y_range: Range<isize>) -> Self {
Self(enum_map! {
Axis2::X => x_range.clone(),
Axis2::Y => y_range.clone(),
})
}
pub fn for_ranges((a, a_range): (Axis2, Range<isize>), b_range: Range<isize>) -> Self {
let b = !a;
let mut ranges = EnumMap::default();
ranges[a] = a_range;
ranges[b] = b_range;
Self(ranges)
}
pub fn top_left(&self) -> Coord2 {
use Axis2::*;
Coord2::new(self[X].start, self[Y].start)
}
pub fn from_fn<F>(f: F) -> Self
where
F: FnMut(Axis2) -> Range<isize>,
{
Self(EnumMap::from_fn(f))
}
pub fn translate(self, offset: Coord2) -> Rect2 {
Self::from_fn(|axis| self[axis].start + offset[axis]..self[axis].end + offset[axis])
}
pub fn is_empty(&self) -> bool {
self[Axis2::X].is_empty() || self[Axis2::Y].is_empty()
}
}
impl From<EnumMap<Axis2, Range<isize>>> for Rect2 {
fn from(value: EnumMap<Axis2, Range<isize>>) -> Self {
Self(value)
}
}
impl Index<Axis2> for Rect2 {
type Output = Range<isize>;
fn index(&self, index: Axis2) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<Axis2> for Rect2 {
fn index_mut(&mut self, index: Axis2) -> &mut Self::Output {
&mut self.0[index]
}
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum FootnoteMarkerType {
#[default]
Alphabetic,
Numeric,
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum FootnoteMarkerPosition {
Subscript,
#[default]
Superscript,
}
#[derive(Clone, Debug, Serialize)]
pub struct PivotTableStyle {
pub look: Arc<Look>,
pub rotate_inner_column_labels: bool,
pub rotate_outer_row_labels: bool,
pub show_grid_lines: bool,
pub show_title: bool,
pub show_caption: bool,
pub show_values: Option<Show>,
pub show_variables: Option<Show>,
pub sizing: EnumMap<Axis2, Option<Box<Sizing>>>,
pub settings: FormatSettings,
pub grouping: Option<char>,
pub small: f64,
pub weight_format: Format,
}
impl Default for PivotTableStyle {
fn default() -> Self {
Self {
look: Look::shared_default(),
rotate_inner_column_labels: false,
rotate_outer_row_labels: false,
show_grid_lines: false,
show_title: true,
show_caption: true,
show_values: None,
show_variables: None,
sizing: EnumMap::default(),
settings: Default::default(), grouping: None,
small: 0.0001, weight_format: F40,
}
}
}
impl PivotTableStyle {
pub fn with_look(self, look: Arc<Look>) -> Self {
Self { look, ..self }
}
pub fn with_show_values(self, show_values: Option<Show>) -> Self {
Self {
show_values,
..self
}
}
pub fn with_show_variables(self, show_variables: Option<Show>) -> Self {
Self {
show_variables,
..self
}
}
pub fn with_show_title(self, show_title: bool) -> Self {
Self { show_title, ..self }
}
pub fn with_show_caption(self, show_caption: bool) -> Self {
Self {
show_caption,
..self
}
}
pub fn look_mut(&mut self) -> &mut Look {
Arc::make_mut(&mut self.look)
}
}
#[derive(Clone, Debug, Serialize)]
pub struct PivotTableMetadata {
pub title: Option<Box<Value>>,
pub caption: Option<Box<Value>>,
pub corner_text: Option<Box<Value>>,
pub notes: Option<String>,
pub notes_unexpanded: Option<String>,
pub command_local: Option<String>,
pub command_c: Option<String>,
pub subtype: Option<Box<Value>>,
pub language: Option<String>,
pub locale: Option<String>,
pub dataset: Option<String>,
pub datafile: Option<String>,
pub date: Option<NaiveDateTime>,
}
impl Default for PivotTableMetadata {
fn default() -> Self {
Self {
title: None,
caption: None,
corner_text: None,
notes: None,
notes_unexpanded: None,
command_local: None,
command_c: None,
subtype: None,
language: None,
locale: None,
dataset: None,
datafile: None,
date: Some(Utc::now().naive_local()),
}
}
}
impl PivotTableMetadata {
pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
Self {
subtype: Some(Box::new(subtype.into())),
..self
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct PivotTable {
pub style: PivotTableStyle,
layer: Vec<usize>,
pub metadata: PivotTableMetadata,
pub footnotes: Footnotes,
#[serde(flatten)]
structure: Structure,
cells: HashMap<usize, Value>,
}
impl Default for PivotTable {
fn default() -> Self {
Self {
style: PivotTableStyle::default(),
metadata: PivotTableMetadata::default(),
layer: Vec::new(),
footnotes: Footnotes::new(),
structure: Structure::default(),
cells: HashMap::new(),
}
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct Structure {
dimensions: Vec<Dimension>,
axes: EnumMap<Axis3, Axis>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct InvalidAxes;
impl Structure {
pub fn new(
dimensions: Vec<Dimension>,
axes: EnumMap<Axis3, Axis>,
) -> Result<Self, InvalidAxes> {
if dimensions.len()
!= axes
.values()
.map(|axis| axis.dimensions.len())
.sum::<usize>()
{
return Err(InvalidAxes);
}
let mut seen = vec![false; dimensions.len()];
for d in axes
.values()
.flat_map(|axis| axis.dimensions.iter())
.copied()
{
if let Some(seen) = seen.get_mut(d)
&& !*seen
{
*seen = true;
} else {
return Err(InvalidAxes);
}
}
Ok(Self { dimensions, axes })
}
pub fn n_cells(&self) -> usize {
if self.dimensions.is_empty() {
0
} else {
self.dimensions.iter().map(|d| d.len()).product()
}
}
pub fn axis_dimensions(
&self,
axis: Axis3,
) -> impl DoubleEndedIterator<Item = &Dimension> + ExactSizeIterator {
self.axes[axis]
.dimensions
.iter()
.copied()
.map(|index| &self.dimensions[index])
}
fn axis_values(&self, axis: Axis3) -> AxisIterator {
AxisIterator {
indexes: smallvec![0; self.axes[axis].dimensions.len()],
lengths: self.axis_dimensions(axis).map(|d| d.len()).collect(),
done: self.axis_extent(axis) == 0,
}
}
pub fn axis_extent(&self, axis: Axis3) -> usize {
self.axis_dimensions(axis).map(|d| d.len()).product()
}
}
impl<T> From<T> for Structure
where
T: IntoIterator<Item = (Axis3, Dimension)>,
{
fn from(value: T) -> Self {
let mut structure = Structure::default();
for (axis, dimension) in value {
structure.axes[axis]
.dimensions
.push(structure.dimensions.len());
structure.dimensions.push(dimension);
}
structure
}
}
pub trait CellIndex {
fn cell_index<I>(self, dimensions: I) -> usize
where
I: ExactSizeIterator<Item = usize>;
}
impl<T> CellIndex for T
where
T: AsRef<[usize]>,
{
fn cell_index<I>(self, dimensions: I) -> usize
where
I: ExactSizeIterator<Item = usize>,
{
let data_indexes = self.as_ref();
let mut index = 0;
for (dimension, data_index) in dimensions.zip_eq(data_indexes.iter()) {
debug_assert!(*data_index < dimension);
index = dimension * index + data_index;
}
index
}
}
pub struct PrecomputedIndex(
pub usize,
);
impl CellIndex for PrecomputedIndex {
fn cell_index<I>(self, _dimensions: I) -> usize
where
I: ExactSizeIterator<Item = usize>,
{
self.0
}
}
impl<C> Extend<(C, Value)> for PivotTable
where
C: CellIndex,
{
fn extend<T: IntoIterator<Item = (C, Value)>>(&mut self, iter: T) {
for (cell_index, value) in iter {
self.insert(cell_index, value);
}
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub struct Footnote {
#[serde(skip)]
index: usize,
pub content: Box<Value>,
pub marker: Option<Box<Value>>,
pub show: bool,
}
impl Footnote {
pub fn new(content: impl Into<Value>) -> Self {
Self {
index: 0,
content: Box::new(content.into()),
marker: None,
show: true,
}
}
pub fn with_marker(self, marker: Option<Value>) -> Self {
Self {
marker: marker.map(Box::new),
..self
}
}
pub fn with_some_marker(self, marker: impl Into<Value>) -> Self {
Self::with_marker(self, Some(marker.into()))
}
pub fn with_show(self, show: bool) -> Self {
Self { show, ..self }
}
pub fn with_index(self, index: usize) -> Self {
Self { index, ..self }
}
pub fn display_marker(&self, options: impl Into<ValueOptions>) -> impl Display {
DisplayMarker {
footnote: self,
options: options.into(),
}
}
pub fn display_content(&self, options: impl Into<ValueOptions>) -> impl Display {
self.content.display(options)
}
pub fn index(&self) -> usize {
self.index
}
}
impl Default for Footnote {
fn default() -> Self {
Footnote::new(Value::default())
}
}
struct DisplayMarker<'a> {
footnote: &'a Footnote,
options: ValueOptions,
}
impl Display for DisplayMarker<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(marker) = &self.footnote.marker {
write!(f, "{}", marker.display(&self.options).without_suffixes())
} else {
let i = self.footnote.index + 1;
match self.options.footnote_marker_type {
FootnoteMarkerType::Alphabetic => write!(f, "{}", Display26Adic::new_lowercase(i)),
FootnoteMarkerType::Numeric => write!(f, "{i}"),
}
}
}
}
pub struct Display26Adic {
value: usize,
base: u8,
}
impl Display26Adic {
pub fn new_lowercase(value: usize) -> Self {
Self { value, base: b'a' }
}
pub fn new_uppercase(value: usize) -> Self {
Self { value, base: b'A' }
}
}
impl Display for Display26Adic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut output = SmallVec::<[u8; 16]>::new();
let mut number = self.value;
while number > 0 {
number -= 1;
let digit = (number % 26) as u8;
output.push(digit + self.base);
number /= 26;
}
output.reverse();
write!(f, "{}", str::from_utf8(&output).unwrap())
}
}
pub struct MetadataEntry {
pub name: Value,
pub value: MetadataValue,
}
impl MetadataEntry {
pub fn new(name: impl Into<Value>, value: MetadataValue) -> Self {
Self {
name: name.into(),
value,
}
}
pub fn into_pivot_table(self) -> PivotTable {
let mut data = Vec::new();
let group = match self.visit(&mut data) {
Category::Group(group) => group,
Category::Leaf(leaf) => Group::new("Metadata").with(leaf).with_label_shown(),
};
PivotTable::new([(Axis3::Y, Dimension::new(group))]).with_data(
data.into_iter()
.enumerate()
.filter(|(_row, value)| !value.is_empty())
.map(|(row, value)| ([row], value)),
)
}
fn visit(self, data: &mut Vec<Value>) -> Category {
match self.value {
MetadataValue::Leaf(value) => {
data.push(value);
Leaf::new(self.name).into()
}
MetadataValue::Group(items) => Group::with_capacity(self.name, items.len())
.with_multiple(items.into_iter().map(|item| item.visit(data)))
.into(),
}
}
}
pub enum MetadataValue {
Leaf(
Value,
),
Group(
Vec<MetadataEntry>,
),
}
impl MetadataValue {
pub fn new_leaf(value: impl Into<Value>) -> Self {
Self::Leaf(value.into())
}
}
impl Serialize for MetadataValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MetadataValue::Leaf(value) => value.serialize_bare(serializer),
MetadataValue::Group(items) => {
let mut map = serializer.serialize_map(Some(items.len()))?;
for item in items {
let name = item.name.display(()).to_string();
map.serialize_entry(&name, &item.value)?;
}
map.end()
}
}
}
}
impl Serialize for MetadataEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match &self.value {
MetadataValue::Leaf(value) => {
let mut map = serializer.serialize_map(Some(1))?;
let name = self.name.display(()).to_string();
map.serialize_entry(&name, &BareValue(value))?;
map.end()
}
MetadataValue::Group(items) => {
let mut map = serializer.serialize_map(Some(items.len()))?;
for item in items {
let name = item.name.display(()).to_string();
map.serialize_entry(&name, &item.value)?;
}
map.end()
}
}
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use crate::output::pivot::{
Display26Adic, MetadataEntry, MetadataValue,
look::Color,
tests::assert_rendering,
value::{TemplateValue, Value, ValueInner},
};
#[test]
fn parse_color() {
assert_eq!(Color::from_str("red"), Ok(Color::new(255, 0, 0)));
assert_eq!(Color::from_str("transparent"), Ok(Color::TRANSPARENT));
assert_eq!(Color::from_str("rgb(12,34,56)"), Ok(Color::new(12, 34, 56)));
assert_eq!(Color::from_str("#abcdef"), Ok(Color::new(0xab, 0xcd, 0xef)));
assert_eq!(Color::from_str("abcdef"), Ok(Color::new(0xab, 0xcd, 0xef)));
assert_eq!(Color::from_str("transparent"), Ok(Color::TRANSPARENT));
}
#[test]
fn display_26adic() {
for (number, lowercase, uppercase) in [
(0, "", ""),
(1, "a", "A"),
(2, "b", "B"),
(26, "z", "Z"),
(27, "aa", "AA"),
(28, "ab", "AB"),
(29, "ac", "AC"),
(18278, "zzz", "ZZZ"),
(18279, "aaaa", "AAAA"),
(19010, "abcd", "ABCD"),
] {
assert_eq!(Display26Adic::new_lowercase(number).to_string(), lowercase);
assert_eq!(Display26Adic::new_uppercase(number).to_string(), uppercase);
}
}
#[test]
fn template() {
for (template, expected) in [
(
"1: [:^1,:]1; [:^1,:]2",
"1: First,1.00,Second,2,; Third,3.00,Fourth,4,",
),
(r#"2: [:^1\n:]1"#, "2: First\n1.00\nSecond\n2\n"),
(r#"3: [:^1 = ^2\n:]1"#, "3: First = 1.00\nSecond = 2\n"),
("4: [%1:, ^1:]1", "4: First, 1.00, Second, 2"),
("5: [%1 = %2:, ^1 = ^2:]1", "5: First = 1.00, Second = 2"),
("6: [%1:, ^1:]1", "6: First, 1.00, Second, 2"),
("7: [%1:, ^1: and $1]1", "7: First, 1.00, Second and 2"),
("8: [%1:, ^1: and $1]3", "8: One and two"),
("9: [%1:, ^1: and $1]4", "9: Just one"),
] {
let value = Value::new(ValueInner::Template(TemplateValue {
args: vec![
vec![
Value::new_user_text("First"),
Value::new_number(Some(1.0)),
Value::new_user_text("Second"),
Value::new_integer(Some(2.0)),
],
vec![
Value::new_user_text("Third"),
Value::new_number(Some(3.0)),
Value::new_user_text("Fourth"),
Value::new_integer(Some(4.0)),
],
vec![Value::new_user_text("One"), Value::new_user_text("two")],
vec![Value::new_user_text("Just one")],
],
localized: String::from(template),
id: None,
}));
assert_eq!(value.display(()).to_string(), expected);
}
}
#[test]
fn metadata_entry() {
let tree = MetadataEntry {
name: Value::from("Group"),
value: MetadataValue::Group(vec![
MetadataEntry {
name: Value::from("Name 1"),
value: MetadataValue::Leaf(Value::from("Value 1")),
},
MetadataEntry {
name: Value::from("Subgroup 1"),
value: MetadataValue::Group(vec![
MetadataEntry {
name: Value::from("Subname 1"),
value: MetadataValue::Leaf(Value::from("Subvalue 1")),
},
MetadataEntry {
name: Value::from("Subname 2"),
value: MetadataValue::Leaf(Value::from("Subvalue 2")),
},
MetadataEntry {
name: Value::from("Subname 3"),
value: MetadataValue::Leaf(Value::new_integer(Some(3.0))),
},
]),
},
MetadataEntry {
name: Value::from("Name 2"),
value: MetadataValue::Leaf(Value::from("Value 2")),
},
]),
};
assert_eq!(
serde_json::to_string_pretty(&tree).unwrap(),
r#"{
"Name 1": "Value 1",
"Subgroup 1": {
"Subname 1": "Subvalue 1",
"Subname 2": "Subvalue 2",
"Subname 3": 3
},
"Name 2": "Value 2"
}"#
);
assert_rendering("metadata_entry", &tree.into_pivot_table());
}
}