use std::{
collections::HashMap,
fmt::{Debug, Display, Write},
io::Read,
iter::{once, repeat, repeat_n, FusedIterator},
ops::{Index, IndexMut, Not, Range, RangeInclusive},
str::{from_utf8, FromStr, Utf8Error},
sync::{Arc, OnceLock},
};
use binrw::Error as BinError;
use chrono::NaiveDateTime;
pub use color::ParseError as ParseColorError;
use color::{palette::css::TRANSPARENT, AlphaColor, Rgba8, Srgb};
use enum_iterator::Sequence;
use enum_map::{enum_map, Enum, EnumMap};
use look_xml::TableProperties;
use quick_xml::{de::from_str, DeError};
use serde::{
de::Visitor,
ser::{SerializeMap, SerializeStruct},
Deserialize, Serialize, Serializer,
};
use smallstr::SmallString;
use smallvec::SmallVec;
use thiserror::Error as ThisError;
use tlo::parse_tlo;
use crate::{
calendar::date_time_to_pspp,
data::{ByteString, Datum, EncodedString, RawString},
format::{Decimal, Format, Settings as FormatSettings, Type, UncheckedFormat},
settings::{Settings, Show},
util::ToSmallString,
variable::{VarType, Variable},
};
pub mod output;
mod look_xml;
#[cfg(test)]
pub mod tests;
mod tlo;
#[derive(Copy, Clone, Debug, Default, Enum, PartialEq, Eq)]
pub enum Area {
Title,
Caption,
Footer,
Corner,
Labels(Axis2),
#[default]
Data,
Layers,
}
impl Display for Area {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Area::Title => write!(f, "title"),
Area::Caption => write!(f, "caption"),
Area::Footer => write!(f, "footer"),
Area::Corner => write!(f, "corner"),
Area::Labels(axis2) => write!(f, "labels({axis2})"),
Area::Data => write!(f, "data"),
Area::Layers => write!(f, "layers"),
}
}
}
impl Serialize for Area {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_small_string::<16>())
}
}
impl Area {
fn default_cell_style(self) -> CellStyle {
use HorzAlign::*;
use VertAlign::*;
let (horz_align, vert_align, hmargins, vmargins) = match self {
Area::Title => (Some(Center), Middle, [8, 11], [1, 8]),
Area::Caption => (Some(Left), Top, [8, 11], [1, 1]),
Area::Footer => (Some(Left), Top, [11, 8], [2, 3]),
Area::Corner => (Some(Left), Bottom, [8, 11], [1, 1]),
Area::Labels(Axis2::X) => (Some(Center), Top, [8, 11], [1, 3]),
Area::Labels(Axis2::Y) => (Some(Left), Top, [8, 11], [1, 3]),
Area::Data => (None, Top, [8, 11], [1, 1]),
Area::Layers => (Some(Left), Bottom, [8, 11], [1, 3]),
};
CellStyle {
horz_align,
vert_align,
margins: enum_map! { Axis2::X => hmargins, Axis2::Y => vmargins },
}
}
fn default_font_style(self) -> FontStyle {
FontStyle {
bold: self == Area::Title,
italic: false,
underline: false,
markup: false,
font: String::from("Sans Serif"),
fg: [Color::BLACK; 2],
bg: [Color::WHITE; 2],
size: 9,
}
}
fn default_area_style(self) -> AreaStyle {
AreaStyle {
cell_style: self.default_cell_style(),
font_style: self.default_font_style(),
}
}
}
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq)]
pub enum Border {
Title,
OuterFrame(BoxBorder),
InnerFrame(BoxBorder),
Dimension(RowColBorder),
Category(RowColBorder),
DataLeft,
DataTop,
}
impl Border {
pub fn default_stroke(self) -> Stroke {
match self {
Self::InnerFrame(_) | Self::DataLeft | Self::DataTop => Stroke::Thick,
Self::Dimension(
RowColBorder(HeadingRegion::Columns, _) | RowColBorder(_, Axis2::X),
)
| Self::Category(RowColBorder(HeadingRegion::Columns, _)) => Stroke::Solid,
_ => Stroke::None,
}
}
pub fn default_border_style(self) -> BorderStyle {
BorderStyle {
stroke: self.default_stroke(),
color: Color::BLACK,
}
}
fn fallback(self) -> Self {
match self {
Self::Title
| Self::OuterFrame(_)
| Self::InnerFrame(_)
| Self::DataLeft
| Self::DataTop
| Self::Category(_) => self,
Self::Dimension(row_col_border) => Self::Category(row_col_border),
}
}
}
impl Display for Border {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Border::Title => write!(f, "title"),
Border::OuterFrame(box_border) => write!(f, "outer_frame({box_border})"),
Border::InnerFrame(box_border) => write!(f, "inner_frame({box_border})"),
Border::Dimension(row_col_border) => write!(f, "dimension({row_col_border})"),
Border::Category(row_col_border) => write!(f, "category({row_col_border})"),
Border::DataLeft => write!(f, "data(left)"),
Border::DataTop => write!(f, "data(top)"),
}
}
}
impl Serialize for Border {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_small_string::<32>())
}
}
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BoxBorder {
Left,
Top,
Right,
Bottom,
}
impl BoxBorder {
fn as_str(&self) -> &'static str {
match self {
BoxBorder::Left => "left",
BoxBorder::Top => "top",
BoxBorder::Right => "right",
BoxBorder::Bottom => "bottom",
}
}
}
impl Display for BoxBorder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct RowColBorder(
pub HeadingRegion,
) or vertical ([Axis2::Y]) borders.
pub Axis2,
);
impl Display for RowColBorder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.0, self.1)
}
}
#[derive(Default, Clone, Debug, Serialize)]
pub struct Sizing {
widths: Vec<i32>,
breaks: Vec<usize>,
keeps: Vec<Range<usize>>,
}
#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Sequence, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Axis3 {
X,
Y,
Z,
}
impl Axis3 {
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>,
}
pub 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 with_look(mut self, look: Arc<Look>) -> Self {
self.look = look;
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 => Format::F40,
Class::Correlations => Format::F40_3,
Class::Significance => Format::F40_3,
Class::Percent => Format::PCT40_1,
Class::Residual => Format::F40_2,
Class::Count => Format::F40, };
let value = Value::new(ValueInner::Number(NumberValue {
show: None,
format,
honor_small: class == Class::Other,
value: number,
variable: None,
value_label: None,
}));
self.insert(data_indexes, value);
}
pub fn with_footnotes(mut self, footnotes: Footnotes) -> Self {
debug_assert!(self.footnotes.is_empty());
self.footnotes = footnotes;
self
}
fn axis_values(&self, axis: Axis3) -> AxisIterator {
AxisIterator {
indexes: repeat_n(0, self.axes[axis].dimensions.len()).collect(),
lengths: self.axis_dimensions(axis).map(|d| d.len()).collect(),
done: self.axis_extent(axis) == 0,
}
}
fn axis_extent(&self, axis: Axis3) -> usize {
self.axis_dimensions(axis).map(|d| d.len()).product()
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Dimension {
pub root: Group,
pub presentation_order: Vec<usize>,
pub hide_all_labels: bool,
}
pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>;
pub struct Path<'a> {
groups: GroupVec<'a>,
leaf: &'a Leaf,
}
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
}
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 with_all_labels_hidden(self) -> Self {
Self {
hide_all_labels: true,
..self
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Group {
#[serde(skip)]
len: usize,
pub name: Box<Value>,
pub children: Vec<Category>,
pub show_label: bool,
}
impl Group {
pub fn new(name: impl Into<Value>) -> Self {
Self::with_capacity(name, 0)
}
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 Category::Group(group) = &mut child {
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
}
pub 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
}
pub 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
}
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(pub Vec<Arc<Footnote>>);
impl Footnotes {
pub fn new() -> Self {
Self::default()
}
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 is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Debug)]
pub struct Leaf {
name: Box<Value>,
}
impl Leaf {
pub fn new(name: Value) -> Self {
Self {
name: Box::new(name),
}
}
pub fn name(&self) -> &Value {
&self.name
}
}
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 {
Other,
Integer,
Correlations,
Significance,
Percent,
Residual,
Count,
}
#[derive(Clone, Debug, Serialize)]
pub enum Category {
Group(Group),
Leaf(Leaf),
}
impl Category {
pub fn name(&self) -> &Value {
match self {
Category::Group(group) => &group.name,
Category::Leaf(leaf) => &leaf.name,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
match self {
Category::Group(group) => group.len,
Category::Leaf(_) => 1,
}
}
pub 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)
} else {
None
}
}
}
}
pub 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 })
} else {
None
}
}
}
}
pub fn show_label(&self) -> bool {
match self {
Category::Group(group) => group.show_label,
Category::Leaf(_) => true,
}
}
}
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(Clone, Debug, Serialize)]
pub struct Look {
pub name: Option<String>,
pub hide_empty: bool,
pub row_label_position: LabelPosition,
pub heading_widths: EnumMap<HeadingRegion, RangeInclusive<usize>>,
pub footnote_marker_type: FootnoteMarkerType,
pub footnote_marker_position: FootnoteMarkerPosition,
pub areas: EnumMap<Area, AreaStyle>,
pub borders: EnumMap<Border, BorderStyle>,
pub print_all_layers: bool,
pub paginate_layers: bool,
pub shrink_to_fit: EnumMap<Axis2, bool>,
pub top_continuation: bool,
pub bottom_continuation: bool,
pub continuation: Option<String>,
pub n_orphan_lines: usize,
}
impl Look {
pub fn with_omit_empty(mut self, omit_empty: bool) -> Self {
self.hide_empty = omit_empty;
self
}
pub fn with_row_label_position(mut self, row_label_position: LabelPosition) -> Self {
self.row_label_position = row_label_position;
self
}
pub fn with_borders(mut self, borders: EnumMap<Border, BorderStyle>) -> Self {
self.borders = borders;
self
}
}
impl Default for Look {
fn default() -> Self {
Self {
name: None,
hide_empty: true,
row_label_position: LabelPosition::default(),
heading_widths: EnumMap::from_fn(|region| match region {
HeadingRegion::Rows => 36..=72,
HeadingRegion::Columns => 36..=120,
}),
footnote_marker_type: FootnoteMarkerType::default(),
footnote_marker_position: FootnoteMarkerPosition::default(),
areas: EnumMap::from_fn(Area::default_area_style),
borders: EnumMap::from_fn(Border::default_border_style),
print_all_layers: false,
paginate_layers: false,
shrink_to_fit: EnumMap::from_fn(|_| false),
top_continuation: false,
bottom_continuation: false,
continuation: None,
n_orphan_lines: 0,
}
}
}
#[derive(ThisError, Debug)]
pub enum ParseLookError {
#[error(transparent)]
XmlError(#[from] DeError),
#[error(transparent)]
Utf8Error(#[from] Utf8Error),
#[error(transparent)]
BinError(#[from] BinError),
#[error(transparent)]
IoError(#[from] std::io::Error),
}
impl Look {
pub fn shared_default() -> Arc<Look> {
static LOOK: OnceLock<Arc<Look>> = OnceLock::new();
LOOK.get_or_init(|| Arc::new(Look::default())).clone()
}
pub fn from_xml(xml: &str) -> Result<Self, ParseLookError> {
Ok(from_str::<TableProperties>(xml)
.map_err(ParseLookError::from)?
.into())
}
pub fn from_binary(tlo: &[u8]) -> Result<Self, ParseLookError> {
parse_tlo(tlo).map_err(ParseLookError::from)
}
pub fn from_data(data: &[u8]) -> Result<Self, ParseLookError> {
if data.starts_with(b"\xff\xff\0\0") {
Self::from_binary(data)
} else {
Self::from_xml(from_utf8(data).map_err(ParseLookError::from)?)
}
}
pub fn from_reader<R>(mut reader: R) -> Result<Self, ParseLookError>
where
R: Read,
{
let mut buffer = Vec::new();
reader
.read_to_end(&mut buffer)
.map_err(ParseLookError::from)?;
Self::from_data(&buffer)
}
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub enum LabelPosition {
#[serde(rename = "nested")]
Nested,
#[default]
#[serde(rename = "inCorner")]
Corner,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Enum, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HeadingRegion {
Rows,
Columns,
}
impl HeadingRegion {
pub fn as_str(&self) -> &'static str {
match self {
HeadingRegion::Rows => "rows",
HeadingRegion::Columns => "columns",
}
}
}
impl Display for HeadingRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<Axis2> for HeadingRegion {
fn from(axis: Axis2) -> Self {
match axis {
Axis2::X => HeadingRegion::Columns,
Axis2::Y => HeadingRegion::Rows,
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct AreaStyle {
pub cell_style: CellStyle,
pub font_style: FontStyle,
}
#[derive(Clone, Debug, Serialize)]
pub struct CellStyle {
pub horz_align: Option<HorzAlign>,
pub vert_align: VertAlign,
pub margins: EnumMap<Axis2, [i32; 2]>,
}
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HorzAlign {
Right,
Left,
Center,
Decimal {
offset: f64,
decimal: Decimal,
},
}
impl HorzAlign {
pub fn for_mixed(var_type: VarType) -> Self {
match var_type {
VarType::Numeric => Self::Right,
VarType::String => Self::Left,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum VertAlign {
Top,
Middle,
Bottom,
}
#[derive(Clone, Debug, Serialize)]
pub struct FontStyle {
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub markup: bool,
pub font: String,
pub fg: [Color; 2],
pub bg: [Color; 2],
pub size: i32,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Color {
pub alpha: u8,
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Color {
pub const BLACK: Color = Color::new(0, 0, 0);
pub const WHITE: Color = Color::new(255, 255, 255);
pub const RED: Color = Color::new(255, 0, 0);
pub const BLUE: Color = Color::new(0, 0, 255);
pub const TRANSPARENT: Color = Color::new(0, 0, 0).with_alpha(0);
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self {
alpha: 255,
r,
g,
b,
}
}
pub const fn with_alpha(self, alpha: u8) -> Self {
Self { alpha, ..self }
}
pub const fn without_alpha(self) -> Self {
self.with_alpha(255)
}
pub fn display_css(&self) -> DisplayCss {
DisplayCss(*self)
}
}
impl Debug for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_css())
}
}
impl From<Rgba8> for Color {
fn from(Rgba8 { r, g, b, a }: Rgba8) -> Self {
Self::new(r, g, b).with_alpha(a)
}
}
impl FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn is_bare_hex(s: &str) -> bool {
let s = s.trim();
s.chars().count() == 6 && s.chars().all(|c| c.is_ascii_hexdigit())
}
let color: AlphaColor<Srgb> = match s.parse() {
Err(ParseColorError::UnknownColorSyntax) if is_bare_hex(s) => {
("#".to_owned() + s).parse()
}
Err(ParseColorError::UnknownColorSyntax)
if s.trim().eq_ignore_ascii_case("transparent") =>
{
Ok(TRANSPARENT)
}
other => other,
}?;
Ok(color.to_rgba8().into())
}
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.display_css().to_small_string::<32>())
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("\"#rrggbb\" or \"rrggbb\" or web color name")
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(E::custom)
}
}
deserializer.deserialize_str(ColorVisitor)
}
}
pub struct DisplayCss(Color);
impl Display for DisplayCss {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Color { alpha, r, g, b } = self.0;
match alpha {
255 => write!(f, "#{r:02x}{g:02x}{b:02x}"),
_ => write!(f, "rgb({r}, {g}, {b}, {:.2})", alpha as f64 / 255.0),
}
}
}
#[derive(Copy, Clone, Debug, Deserialize)]
pub struct BorderStyle {
#[serde(rename = "@borderStyleType")]
pub stroke: Stroke,
#[serde(rename = "@color")]
pub color: Color,
}
impl Serialize for BorderStyle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = serializer.serialize_struct("BorderStyle", 2)?;
s.serialize_field("stroke", &self.stroke)?;
s.serialize_field("color", &self.color)?;
s.end()
}
}
impl BorderStyle {
pub const fn none() -> Self {
Self {
stroke: Stroke::None,
color: Color::BLACK,
}
}
pub fn is_none(&self) -> bool {
self.stroke.is_none()
}
pub fn combine(self, other: BorderStyle) -> Self {
Self {
stroke: self.stroke.combine(other.stroke),
color: self.color,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Enum, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Stroke {
None,
Solid,
Dashed,
Thick,
Thin,
Double,
}
impl Stroke {
pub fn is_none(&self) -> bool {
self == &Self::None
}
pub fn combine(self, other: Stroke) -> Self {
self.max(other)
}
}
#[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, Default, PartialEq, Eq, Hash)]
pub struct Coord2(pub EnumMap<Axis2, usize>);
impl Coord2 {
pub fn new(x: usize, y: usize) -> Self {
use Axis2::*;
Self(enum_map! {
X => x,
Y => y
})
}
pub fn for_axis((a, az): (Axis2, usize), bz: usize) -> 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) -> usize,
{
Self(EnumMap::from_fn(f))
}
pub fn x(&self) -> usize {
self.0[Axis2::X]
}
pub fn y(&self) -> usize {
self.0[Axis2::Y]
}
pub fn get(&self, axis: Axis2) -> usize {
self.0[axis]
}
}
impl From<EnumMap<Axis2, usize>> for Coord2 {
fn from(value: EnumMap<Axis2, usize>) -> Self {
Self(value)
}
}
impl Index<Axis2> for Coord2 {
type Output = usize;
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<usize>>);
impl Rect2 {
pub fn new(x_range: Range<usize>, y_range: Range<usize>) -> Self {
Self(enum_map! {
Axis2::X => x_range.clone(),
Axis2::Y => y_range.clone(),
})
}
pub fn for_cell(cell: Coord2) -> Self {
Self::new(cell.x()..cell.x() + 1, cell.y()..cell.y() + 1)
}
pub fn for_ranges((a, a_range): (Axis2, Range<usize>), b_range: Range<usize>) -> 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<usize>,
{
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<usize>>> for Rect2 {
fn from(value: EnumMap<Axis2, Range<usize>>) -> Self {
Self(value)
}
}
impl Index<Axis2> for Rect2 {
type Output = Range<usize>;
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 {
#[default]
Subscript,
Superscript,
}
#[derive(Copy, Clone, Debug)]
pub struct ValueOptions {
pub show_values: Option<Show>,
pub show_variables: Option<Show>,
pub small: f64,
pub footnote_marker_type: FootnoteMarkerType,
}
impl Default for ValueOptions {
fn default() -> Self {
Self {
show_values: None,
show_variables: None,
small: 0.0001,
footnote_marker_type: FootnoteMarkerType::default(),
}
}
}
pub trait IntoValueOptions {
fn into_value_options(self) -> ValueOptions;
}
impl IntoValueOptions for () {
fn into_value_options(self) -> ValueOptions {
ValueOptions::default()
}
}
impl IntoValueOptions for &PivotTable {
fn into_value_options(self) -> ValueOptions {
self.value_options()
}
}
impl IntoValueOptions for &ValueOptions {
fn into_value_options(self) -> ValueOptions {
*self
}
}
impl IntoValueOptions for ValueOptions {
fn into_value_options(self) -> ValueOptions {
self
}
}
#[derive(Clone, Debug, Serialize)]
pub struct PivotTable {
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 weight_format: Format,
pub current_layer: Vec<usize>,
pub sizing: EnumMap<Axis2, Option<Box<Sizing>>>,
pub settings: FormatSettings,
pub grouping: Option<char>,
pub small: f64,
pub command_local: Option<String>,
pub command_c: Option<String>,
pub language: Option<String>,
pub locale: Option<String>,
pub dataset: Option<String>,
pub datafile: Option<String>,
pub date: Option<NaiveDateTime>,
pub footnotes: Footnotes,
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>,
pub dimensions: Vec<Dimension>,
pub axes: EnumMap<Axis3, Axis>,
pub cells: HashMap<usize, Value>,
}
impl PivotTable {
pub fn with_title(mut self, title: impl Into<Value>) -> Self {
self.title = Some(Box::new(title.into()));
self.show_title = true;
self
}
pub fn with_caption(mut self, caption: impl Into<Value>) -> Self {
self.caption = Some(Box::new(caption.into()));
self.show_caption = true;
self
}
pub fn with_corner_text(mut self, corner_text: impl Into<Value>) -> Self {
self.corner_text = Some(Box::new(corner_text.into()));
self
}
pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
Self {
subtype: Some(Box::new(subtype.into())),
..self
}
}
pub fn with_show_title(mut self, show_title: bool) -> Self {
self.show_title = show_title;
self
}
pub fn with_show_caption(mut self, show_caption: bool) -> Self {
self.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.look.print_all_layers {
self.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.look.print_all_layers {
self.look_mut().print_all_layers = true;
}
self
}
pub fn look_mut(&mut self) -> &mut Look {
Arc::make_mut(&mut self.look)
}
pub fn with_show_empty(mut self) -> Self {
if self.look.hide_empty {
self.look_mut().hide_empty = false;
}
self
}
pub fn with_hide_empty(mut self) -> Self {
if !self.look.hide_empty {
self.look_mut().hide_empty = true;
}
self
}
pub fn label(&self) -> String {
match &self.title {
Some(title) => title.display(self).to_string(),
None => String::from("Table"),
}
}
pub fn title(&self) -> &Value {
match &self.title {
Some(title) => title,
None => {
static EMPTY: Value = Value::empty();
&EMPTY
}
}
}
pub fn subtype(&self) -> &Value {
match &self.subtype {
Some(subtype) => subtype,
None => {
static EMPTY: Value = Value::empty();
&EMPTY
}
}
}
}
impl Default for PivotTable {
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,
weight_format: Format::F40,
current_layer: Vec::new(),
sizing: EnumMap::default(),
settings: FormatSettings::default(), grouping: None,
small: 0.0001, command_local: None,
command_c: None, language: None,
locale: None,
dataset: None,
datafile: None,
date: None,
footnotes: Footnotes::new(),
subtype: None,
title: None,
corner_text: None,
caption: None,
notes: None,
dimensions: Vec::new(),
axes: EnumMap::default(),
cells: HashMap::new(),
}
}
}
fn cell_index<I>(data_indexes: &[usize], dimensions: I) -> usize
where
I: ExactSizeIterator<Item = usize>,
{
debug_assert_eq!(data_indexes.len(), dimensions.len());
let mut index = 0;
for (dimension, data_index) in dimensions.zip(data_indexes.iter()) {
debug_assert!(*data_index < dimension);
index = dimension * index + data_index;
}
index
}
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 {
look: Settings::global().look.clone(),
current_layer: repeat_n(0, axes[Axis3::Z].dimensions.len()).collect(),
axes,
dimensions,
..Self::default()
}
}
fn cell_index(&self, data_indexes: &[usize]) -> usize {
cell_index(data_indexes, self.dimensions.iter().map(|d| d.len()))
}
pub fn insert(&mut self, data_indexes: &[usize], value: impl Into<Value>) {
self.cells
.insert(self.cell_index(data_indexes), value.into());
}
pub fn get(&self, data_indexes: &[usize]) -> Option<&Value> {
self.cells.get(&self.cell_index(data_indexes))
}
pub fn with_data<I>(mut self, iter: impl IntoIterator<Item = (I, Value)>) -> Self
where
I: AsRef<[usize]>,
{
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.axes[axis]
.dimensions
.iter()
.zip(presentation_indexes.iter())
{
data_indexes[dim_index] = self.dimensions[dim_index].presentation_order[pindex];
}
}
data_indexes
}
pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>>> {
if print && self.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.show_values,
show_variables: self.show_variables,
small: self.small,
footnote_marker_type: self.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;
}
match (old_axis, new_axis) {
(Axis3::Z, Axis3::Z) => {
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, _) => {
self.current_layer.remove(old_position);
}
(_, Axis3::Z) => {
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<I> Extend<(I, Value)> for PivotTable
where
I: AsRef<[usize]>,
{
fn extend<T: IntoIterator<Item = (I, Value)>>(&mut self, iter: T) {
for (data_indexes, value) in iter {
self.insert(data_indexes.as_ref(), value);
}
}
}
#[derive(Clone, Debug, Serialize)]
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(mut self, marker: impl Into<Value>) -> Self {
self.marker = Some(Box::new(marker.into()));
self
}
pub fn with_show(mut self, show: bool) -> Self {
self.show = show;
self
}
pub fn with_index(mut self, index: usize) -> Self {
self.index = index;
self
}
pub fn display_marker(&self, options: impl IntoValueOptions) -> DisplayMarker<'_> {
DisplayMarker {
footnote: self,
options: options.into_value_options(),
}
}
pub fn display_content(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
self.content.display(options)
}
pub fn index(&self) -> usize {
self.index
}
}
pub 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, "{}", from_utf8(&output).unwrap())
}
}
#[derive(Clone, Default)]
pub struct Value {
pub inner: ValueInner,
pub styling: Option<Box<ValueStyle>>,
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.inner.serialize(serializer)
}
}
#[derive(Serialize)]
struct BareValue<'a>(#[serde(serialize_with = "Value::serialize_bare")] pub &'a Value);
impl Value {
pub fn serialize_bare<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.inner {
ValueInner::Number(number_value) => number_value.serialize_bare(serializer),
ValueInner::String(string_value) => string_value.s.serialize(serializer),
ValueInner::Variable(variable_value) => variable_value.var_name.serialize(serializer),
ValueInner::Text(text_value) => text_value.localized.serialize(serializer),
ValueInner::Template(template_value) => template_value.localized.serialize(serializer),
ValueInner::Empty => serializer.serialize_none(),
}
}
fn new(inner: ValueInner) -> Self {
Self {
inner,
styling: None,
}
}
pub fn new_date_time(date_time: NaiveDateTime) -> Self {
Self::new_number_with_format(Some(date_time_to_pspp(date_time)), Format::DATETIME40_0)
}
pub fn new_number_with_format(x: Option<f64>, format: Format) -> Self {
Self::new(ValueInner::Number(NumberValue {
show: None,
format,
honor_small: false,
value: x,
variable: None,
value_label: None,
}))
}
pub fn new_variable(variable: &Variable) -> Self {
Self::new(ValueInner::Variable(VariableValue {
show: None,
var_name: String::from(variable.name.as_str()),
variable_label: variable.label.clone(),
}))
}
pub fn new_datum<B>(value: &Datum<B>) -> Self
where
B: EncodedString,
{
match value {
Datum::Number(number) => Self::new_number(*number),
Datum::String(string) => Self::new_user_text(string.as_str()),
}
}
pub fn new_variable_value(variable: &Variable, value: &Datum<ByteString>) -> Self {
let var_name = Some(variable.name.as_str().into());
let value_label = variable.value_labels.get(value).map(String::from);
match value {
Datum::Number(number) => Self::new(ValueInner::Number(NumberValue {
show: None,
format: match variable.print_format.var_type() {
VarType::Numeric => variable.print_format,
VarType::String => {
#[cfg(debug_assertions)]
panic!("cannot create numeric pivot value with string format");
#[cfg(not(debug_assertions))]
Format::F8_2
}
},
honor_small: false,
value: *number,
variable: var_name,
value_label,
})),
Datum::String(string) => Self::new(ValueInner::String(StringValue {
show: None,
hex: variable.print_format.type_() == Type::AHex,
s: string
.as_ref()
.with_encoding(variable.encoding())
.into_string(),
var_name,
value_label,
})),
}
}
pub fn new_number(x: Option<f64>) -> Self {
Self::new_number_with_format(x, Format::F8_2)
}
pub fn new_integer(x: Option<f64>) -> Self {
Self::new_number_with_format(x, Format::F40)
}
pub fn new_text(s: impl Into<String>) -> Self {
Self::new_user_text(s)
}
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.clone(),
c: None,
id: None,
}))
}
}
pub fn with_footnote(mut self, footnote: &Arc<Footnote>) -> Self {
self.add_footnote(footnote);
self
}
pub fn add_footnote(&mut self, footnote: &Arc<Footnote>) {
let footnotes = &mut self.styling.get_or_insert_default().footnotes;
footnotes.push(footnote.clone());
footnotes.sort_by_key(|f| f.index);
}
pub fn with_show_value_label(mut self, show: Option<Show>) -> Self {
let new_show = show;
match &mut self.inner {
ValueInner::Number(NumberValue { show, .. })
| ValueInner::String(StringValue { show, .. }) => {
*show = new_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
}
pub fn with_value_label(mut self, label: Option<String>) -> Self {
match &mut self.inner {
ValueInner::Number(NumberValue { value_label, .. })
| ValueInner::String(StringValue { value_label, .. }) => *value_label = label.clone(),
_ => (),
}
self
}
pub const fn empty() -> Self {
Value {
inner: ValueInner::Empty,
styling: None,
}
}
pub const fn is_empty(&self) -> bool {
self.inner.is_empty() && self.styling.is_none()
}
}
impl From<&str> for Value {
fn from(value: &str) -> Self {
Self::new_text(value)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::new_text(value)
}
}
impl From<&Variable> for Value {
fn from(variable: &Variable) -> Self {
Self::new_variable(variable)
}
}
pub struct DisplayValue<'a> {
inner: &'a ValueInner,
markup: bool,
subscripts: &'a [String],
footnotes: &'a [Arc<Footnote>],
options: ValueOptions,
show_value: bool,
show_label: Option<&'a str>,
}
impl<'a> DisplayValue<'a> {
pub fn subscripts(&self) -> impl Iterator<Item = &str> {
self.subscripts.iter().map(String::as_str)
}
pub fn has_subscripts(&self) -> bool {
!self.subscripts.is_empty()
}
pub fn footnotes(&self) -> impl Iterator<Item = DisplayMarker<'_>> {
self.footnotes
.iter()
.filter(|f| f.show)
.map(|f| f.display_marker(self.options))
}
pub fn has_footnotes(&self) -> bool {
self.footnotes().next().is_some()
}
pub fn without_suffixes(self) -> Self {
Self {
subscripts: &[],
footnotes: &[],
..self
}
}
pub fn split_suffixes(self) -> (Self, Self) {
let suffixes = Self {
inner: &ValueInner::Empty,
..self
};
(self.without_suffixes(), suffixes)
}
pub fn with_styling(mut self, styling: &'a ValueStyle) -> Self {
if let Some(area_style) = &styling.style {
self.markup = area_style.font_style.markup;
}
self.subscripts = styling.subscripts.as_slice();
self.footnotes = styling.footnotes.as_slice();
self
}
pub fn with_font_style(self, font_style: &FontStyle) -> Self {
Self {
markup: font_style.markup,
..self
}
}
pub fn with_subscripts(self, subscripts: &'a [String]) -> Self {
Self { subscripts, ..self }
}
pub fn with_footnotes(self, footnotes: &'a [Arc<Footnote>]) -> Self {
Self { footnotes, ..self }
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty() && self.subscripts.is_empty() && self.footnotes.is_empty()
}
fn small(&self) -> f64 {
self.options.small
}
pub fn var_type(&self) -> VarType {
match self.inner {
ValueInner::Number(NumberValue { .. }) if self.show_label.is_none() => VarType::Numeric,
_ => VarType::String,
}
}
fn template(
&self,
f: &mut std::fmt::Formatter<'_>,
template: &str,
args: &[Vec<Value>],
) -> std::fmt::Result {
let mut iter = template.as_bytes().iter();
while let Some(c) = iter.next() {
match c {
b'\\' => {
let c = *iter.next().unwrap_or(&b'\\') as char;
let c = if c == 'n' { '\n' } else { c };
write!(f, "{c}")?;
}
b'^' => {
let (index, rest) = consume_int(iter.as_slice());
iter = rest.iter();
let Some(arg) = args.get(index.wrapping_sub(1)) else {
continue;
};
if let Some(arg) = arg.first() {
write!(f, "{}", arg.display(self.options))?;
}
}
b'[' => {
let (a, rest) = extract_inner_template(iter.as_slice());
let (b, rest) = extract_inner_template(rest);
let rest = rest.strip_prefix(b"]").unwrap_or(rest);
let (index, rest) = consume_int(rest);
iter = rest.iter();
let Some(mut args) = args.get(index.wrapping_sub(1)).map(|vec| vec.as_slice())
else {
continue;
};
let (mut template, mut escape) =
if !a.is_empty() { (a, b'%') } else { (b, b'^') };
while !args.is_empty() {
let n_consumed = self.inner_template(f, template, escape, args)?;
if n_consumed == 0 {
break;
}
args = &args[n_consumed..];
template = b;
escape = b'^';
}
}
c => write!(f, "{c}")?,
}
}
Ok(())
}
fn inner_template(
&self,
f: &mut std::fmt::Formatter<'_>,
template: &[u8],
escape: u8,
args: &[Value],
) -> Result<usize, std::fmt::Error> {
let mut iter = template.iter();
let mut args_consumed = 0;
while let Some(c) = iter.next() {
match c {
b'\\' => {
let c = *iter.next().unwrap_or(&b'\\') as char;
let c = if c == 'n' { '\n' } else { c };
write!(f, "{c}")?;
}
c if *c == escape => {
let (index, rest) = consume_int(iter.as_slice());
iter = rest.iter();
let Some(arg) = args.get(index.wrapping_sub(1)) else {
continue;
};
args_consumed = args_consumed.max(index);
write!(f, "{}", arg.display(self.options))?;
}
c => write!(f, "{c}")?,
}
}
Ok(args_consumed)
}
}
fn consume_int(input: &[u8]) -> (usize, &[u8]) {
let mut n = 0;
for (index, c) in input.iter().enumerate() {
if !c.is_ascii_digit() {
return (n, &input[index..]);
}
n = n * 10 + (c - b'0') as usize;
}
(n, &[])
}
fn extract_inner_template(input: &[u8]) -> (&[u8], &[u8]) {
for (index, c) in input.iter().copied().enumerate() {
if c == b':' && (index == 0 || input[index - 1] != b'\\') {
return input.split_at(index);
}
}
(input, &[])
}
fn interpret_show(
global_show: impl Fn() -> Show,
table_show: Option<Show>,
value_show: Option<Show>,
label: &str,
) -> (bool, Option<&str>) {
match value_show.or(table_show).unwrap_or_else(global_show) {
Show::Value => (true, None),
Show::Label => (false, Some(label)),
Show::Both => (true, Some(label)),
}
}
impl Display for DisplayValue<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inner {
ValueInner::Number(NumberValue {
format,
honor_small,
value,
..
}) => {
if self.show_value {
let format = if format.type_() == Type::F
&& *honor_small
&& value.is_some_and(|value| value != 0.0 && value.abs() < self.small())
{
UncheckedFormat::new(Type::E, 40, format.d() as u8).fix()
} else {
*format
};
let mut buf = SmallString::<[u8; 40]>::new();
write!(
&mut buf,
"{}",
Datum::<&str>::Number(*value).display(format)
)
.unwrap();
write!(f, "{}", buf.trim_start_matches(' '))?;
}
if let Some(label) = self.show_label {
if self.show_value {
write!(f, " ")?;
}
f.write_str(label)?;
}
Ok(())
}
ValueInner::String(StringValue { s, .. })
| ValueInner::Variable(VariableValue { var_name: s, .. }) => {
match (self.show_value, self.show_label) {
(true, None) => write!(f, "{s}"),
(false, Some(label)) => write!(f, "{label}"),
(true, Some(label)) => write!(f, "{s} {label}"),
(false, None) => unreachable!(),
}
}
ValueInner::Text(TextValue {
localized: local, ..
}) => {
f.write_str(local)
}
ValueInner::Template(TemplateValue {
args,
localized: local,
..
}) => self.template(f, local, args),
ValueInner::Empty => Ok(()),
}?;
for (subscript, delimiter) in self.subscripts.iter().zip(once('_').chain(repeat(','))) {
write!(f, "{delimiter}{subscript}")?;
}
for footnote in self.footnotes {
write!(f, "[{}]", footnote.display_marker(self.options))?;
}
Ok(())
}
}
impl Value {
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 {
write!(f, "{:?}", self.display(()).to_string())
}
}
#[derive(Clone, Debug)]
pub struct NumberValue {
pub value: Option<f64>,
pub format: Format,
pub show: Option<Show>,
pub honor_small: bool,
pub variable: Option<String>,
pub value_label: Option<String>,
}
impl Serialize for NumberValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if self.format.type_() == Type::F && self.variable.is_none() && self.value_label.is_none() {
self.value.serialize(serializer)
} else {
let mut s = serializer.serialize_map(None)?;
s.serialize_entry("value", &self.value)?;
s.serialize_entry("format", &self.format)?;
if let Some(show) = self.show {
s.serialize_entry("show", &show)?;
}
if self.honor_small {
s.serialize_entry("honor_small", &self.honor_small)?;
}
if let Some(variable) = &self.variable {
s.serialize_entry("variable", variable)?;
}
if let Some(value_label) = &self.value_label {
s.serialize_entry("value_label", value_label)?;
}
s.end()
}
}
}
impl NumberValue {
pub fn serialize_bare<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(number) = self.value
&& number.trunc() == number
&& number >= -(1i64 << 53) as f64
&& number <= (1i64 << 53) as f64
{
(number as u64).serialize(serializer)
} else {
self.value.serialize(serializer)
}
}
}
#[derive(Serialize)]
pub struct BareNumberValue<'a>(
#[serde(serialize_with = "NumberValue::serialize_bare")] pub &'a NumberValue,
);
#[derive(Clone, Debug, Serialize)]
pub struct StringValue {
pub s: String,
pub hex: bool,
pub show: Option<Show>,
pub var_name: Option<String>,
pub value_label: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
pub struct VariableValue {
pub show: Option<Show>,
pub var_name: String,
pub variable_label: Option<String>,
}
#[derive(Clone, Debug)]
pub struct TextValue {
pub user_provided: bool,
pub localized: String,
pub c: Option<String>,
pub id: Option<String>,
}
impl Serialize for TextValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if self.user_provided && self.c.is_none() && self.id.is_none() {
serializer.serialize_str(&self.localized)
} else {
let mut s = serializer.serialize_struct(
"TextValue",
2 + self.c.is_some() as usize + self.id.is_some() as usize,
)?;
s.serialize_field("user_provided", &self.user_provided)?;
s.serialize_field("localized", &self.localized)?;
if let Some(c) = &self.c {
s.serialize_field("c", &c)?;
}
if let Some(id) = &self.id {
s.serialize_field("id", &id)?;
}
s.end()
}
}
}
impl TextValue {
pub fn localized(&self) -> &str {
self.localized.as_str()
}
pub fn c(&self) -> &str {
self.c.as_ref().unwrap_or(&self.localized).as_str()
}
pub fn id(&self) -> &str {
self.id.as_ref().unwrap_or(&self.localized).as_str()
}
}
#[derive(Clone, Debug, Serialize)]
pub struct TemplateValue {
pub args: Vec<Vec<Value>>,
pub localized: String,
pub id: String,
}
#[derive(Clone, Debug, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ValueInner {
Number(NumberValue),
String(StringValue),
Variable(VariableValue),
Text(TextValue),
Template(TemplateValue),
#[default]
Empty,
}
impl ValueInner {
pub const fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
fn show(&self) -> Option<Show> {
match self {
ValueInner::Number(NumberValue { show, .. })
| ValueInner::String(StringValue { show, .. })
| ValueInner::Variable(VariableValue { show, .. }) => *show,
_ => None,
}
}
fn label(&self) -> Option<&str> {
self.value_label().or_else(|| self.variable_label())
}
fn value_label(&self) -> Option<&str> {
match self {
ValueInner::Number(NumberValue { value_label, .. })
| ValueInner::String(StringValue { value_label, .. }) => {
value_label.as_ref().map(String::as_str)
}
_ => None,
}
}
fn variable_label(&self) -> Option<&str> {
match self {
ValueInner::Variable(VariableValue { variable_label, .. }) => {
variable_label.as_ref().map(String::as_str)
}
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ValueStyle {
pub style: Option<AreaStyle>,
pub subscripts: Vec<String>,
pub footnotes: Vec<Arc<Footnote>>,
}
impl ValueStyle {
pub fn is_empty(&self) -> bool {
self.style.is_none() && self.subscripts.is_empty() && self.footnotes.is_empty()
}
}
impl ValueInner {
pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
let options = options.into_value_options();
let (show_value, show_label) = if let Some(value_label) = self.value_label() {
interpret_show(
|| Settings::global().show_values,
options.show_values,
self.show(),
value_label,
)
} else if let Some(variable_label) = self.variable_label() {
interpret_show(
|| Settings::global().show_variables,
options.show_variables,
self.show(),
variable_label,
)
} else {
(true, None)
};
DisplayValue {
inner: self,
markup: false,
subscripts: &[],
footnotes: &[],
options,
show_value,
show_label,
}
}
}
pub struct MetadataEntry {
pub name: Value,
pub value: MetadataValue,
}
pub enum MetadataValue {
Leaf(Value),
Group(Vec<MetadataEntry>),
}
impl MetadataEntry {
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(),
}
}
}
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 crate::output::pivot::{Display26Adic, MetadataEntry, MetadataValue, Value};
#[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 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_eq!(
tree.into_pivot_table().to_string(),
r#"╭────────────────────┬──────────╮
│ Name 1 │Value 1 │
├────────────────────┼──────────┤
│Subgroup 1 Subname 1│Subvalue 1│
│ Subname 2│Subvalue 2│
│ Subname 3│ 3│
├────────────────────┼──────────┤
│ Name 2 │Value 2 │
╰────────────────────┴──────────╯
"#
);
}
}