use enum_map::{enum_map, Enum, EnumMap};
use look_xml::TableProperties;
use quick_xml::{de::from_str, DeError};
-use serde::{de::Visitor, Deserialize, Serialize};
+use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Serialize};
use smallstr::SmallString;
use smallvec::SmallVec;
use thiserror::Error as ThisError;
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,
+ {
+ let mut s = SmallString::<[u8; 16]>::new();
+ write!(&mut s, "{}", self).unwrap();
+ serializer.serialize_str(&s)
+ }
+}
+
impl Area {
fn default_cell_style(self) -> CellStyle {
use HorzAlign::*;
}
}
+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,
+ {
+ let mut s = SmallString::<[u8; 32]>::new();
+ write!(&mut s, "{}", self).unwrap();
+ serializer.serialize_str(&s)
+ }
+}
+
/// The borders on a box.
-#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "snake_case")]
pub enum BoxBorder {
Left,
Top,
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())
+ }
+}
+
/// Borders between rows and columns.
-#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "snake_case")]
pub struct RowColBorder(
/// Row or column headings.
pub HeadingRegion,
pub Axis2,
);
+impl Display for RowColBorder {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}:{}", self.0, self.1)
+ }
+}
+
/// Sizing for rows or columns of a rendered table.
///
/// The comments below talk about columns and their widths but they apply
/// equally to rows and their heights.
-#[derive(Default, Clone, Debug)]
+#[derive(Default, Clone, Debug, Serialize)]
pub struct Sizing {
/// Specific column widths, in 1/96" units.
widths: Vec<i32>,
keeps: Vec<Range<usize>>,
}
-#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Sequence)]
+#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Sequence, Serialize)]
+#[serde(rename_all = "snake_case")]
pub enum Axis3 {
X,
Y,
}
/// An axis within a pivot table.
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Serialize)]
pub struct Axis {
/// `dimensions[0]` is the innermost dimension.
pub dimensions: Vec<usize>,
/// (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)]
+#[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
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct Group {
+ #[serde(skip)]
len: usize,
pub name: Box<Value>,
}
}
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Serialize)]
pub struct Footnotes(pub Vec<Arc<Footnote>>);
impl Footnotes {
}
}
+impl Serialize for Leaf {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ self.name.serialize(serializer)
+ }
+}
+
/// Pivot result classes.
///
/// These are used to mark [Leaf] categories as having particular types of data,
}
/// A pivot_category is a leaf (a category) or a group.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub enum Category {
Group(Group),
Leaf(Leaf),
/// The division between this and the style information in [PivotTable] seems
/// fairly arbitrary. The ultimate reason for the division is simply because
/// that's how SPSS documentation and file formats do it.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct Look {
pub name: Option<String>,
}
/// Position for group labels.
-#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub enum LabelPosition {
/// Hierarachically enclosing the categories.
///
/// │ │ │
/// └──────────────────┴─────────────────────────────────────────────────┘
/// ```
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Enum)]
+#[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 {
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct AreaStyle {
pub cell_style: CellStyle,
pub font_style: FontStyle,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct CellStyle {
/// `None` means "mixed" alignment: align strings to the left, numbers to
/// the right.
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "snake_case")]
pub enum VertAlign {
/// Top alignment.
Top,
Bottom,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct FontStyle {
pub bold: bool,
pub italic: bool,
}
}
+impl Serialize for Color {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let mut s = SmallString::<[u8; 32]>::new();
+ write!(&mut s, "{}", self.display_css()).unwrap();
+ serializer.serialize_str(&s)
+ }
+}
+
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
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 {
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Enum, Deserialize)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Enum, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Stroke {
None,
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 {
}
}
-#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum FootnoteMarkerType {
/// a, b, c, ...
Numeric,
}
-#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum FootnoteMarkerPosition {
/// Subscripts.
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct PivotTable {
pub look: Arc<Look>,
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct Footnote {
+ #[serde(skip)]
index: usize,
pub content: Box<Value>,
pub marker: Option<Box<Value>>,
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)
+ }
+}
+
impl Value {
fn new(inner: ValueInner) -> Self {
Self {
} else {
Self::new(ValueInner::Text(TextValue {
user_provided: true,
- local: s.clone(),
- c: s.clone(),
- id: s.clone(),
+ localized: s.clone(),
+ c: None,
+ id: None,
}))
}
}
}
}
- ValueInner::Text(TextValue { local, .. }) => {
+ ValueInner::Text(TextValue {
+ localized: local, ..
+ }) => {
/*
if self
.inner
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct NumberValue {
pub show: Option<Show>,
pub format: Format,
pub value_label: Option<String>,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct StringValue {
pub show: Option<Show>,
pub hex: bool,
pub value_label: Option<String>,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize)]
pub struct VariableValue {
pub show: Option<Show>,
pub var_name: String,
pub struct TextValue {
pub user_provided: bool,
/// Localized.
- pub local: String,
+ pub localized: String,
/// English.
- pub c: String,
+ pub c: Option<String>,
/// Identifier.
- pub id: String,
+ pub id: Option<String>,
}
-#[derive(Clone, Debug)]
+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 local: String,
pub id: String,
}
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Serialize)]
+#[serde(rename_all = "snake_case")]
pub enum ValueInner {
Number(NumberValue),
String(StringValue),