borrow::Cow,
fmt::Write as _,
io::{Cursor, Result as IoResult, Seek, Write},
+ iter::{repeat, repeat_n},
sync::Arc,
};
use binrw::{BinWrite, Endian};
+use enum_map::EnumMap;
use quick_xml::{
events::{attributes::Attribute, BytesText},
writer::Writer as XmlWriter,
output::{
driver::Driver,
pivot::{
- Area, AreaStyle, Axis2, Axis3, Border, BorderStyle, BoxBorder, CellStyle, Color,
- FontStyle, Footnote, Footnotes, HeadingRegion, HorzAlign, PivotTable, RowColBorder,
- Stroke, Value, ValueInner, ValueStyle, VertAlign,
+ Area, AreaStyle, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category, CellStyle,
+ Color, Dimension, FontStyle, Footnote, FootnoteMarkerPosition, FootnoteMarkerType,
+ Footnotes, Group, HeadingRegion, HorzAlign, LabelPosition, Leaf, PivotTable,
+ RowColBorder, Stroke, Value, ValueInner, ValueStyle, VertAlign,
},
Item,
},
}),
}
}
+
+ fn write_item(&mut self, item: &Item) -> Option<Container> {
+ match &item.details {
+ super::Details::Chart => todo!(),
+ super::Details::Image => todo!(),
+ super::Details::Group(children) => {
+ let containers = children
+ .iter()
+ .map(|child| self.write_item(child))
+ .flatten()
+ .collect::<Vec<_>>();
+ }
+ super::Details::Message(_diagnostic) => todo!(),
+ super::Details::PageBreak => {
+ self.needs_page_break = true;
+ None
+ }
+ super::Details::Table(pivot_table) => Some(self.write_table(&*item, pivot_table)),
+ super::Details::Text(_text) => todo!(),
+ }
+ }
}
impl BinWrite for PivotTable {
fn write_options<W: Write + Seek>(
&self,
writer: &mut W,
- _endian: Endian,
+ endian: Endian,
_args: (),
) -> binrw::BinResult<()> {
// Header.
- Header::new(self).write_le(writer)?;
+ (
+ 1u16,
+ SpvBool(true), // x0
+ SpvBool(false), // x1
+ SpvBool(self.rotate_inner_column_labels),
+ SpvBool(self.rotate_outer_row_labels),
+ SpvBool(true),
+ 0x15u32,
+ *self.look.heading_widths[HeadingRegion::Columns].start() as i32,
+ *self.look.heading_widths[HeadingRegion::Columns].end() as i32,
+ *self.look.heading_widths[HeadingRegion::Rows].start() as i32,
+ *self.look.heading_widths[HeadingRegion::Rows].end() as i32,
+ )
+ .write_le(writer)?;
// Titles.
(
borders_start.finish_le32(writer)?;
// Print Settings.
- let ps_start = Count::new(writer)?;
- (
+ Counted::new((
1u32,
SpvBool(self.look.print_all_layers),
SpvBool(self.look.paginate_layers),
SpvBool(self.look.bottom_continuation),
self.look.n_orphan_lines as u32,
SpvString(self.look.continuation.as_ref().map_or("", |s| s.as_str())),
- )
- .write_be(writer)?;
- ps_start.finish_le32(writer)?;
+ ))
+ .with_endian(Endian::Little)
+ .write_be(writer)?;
// Table Settings.
- let ts_start = Count::new(writer)?;
- (1u32, 4u32, self.spv_layer() as u32).write_be(writer)?;
- ts_start.finish_le32(writer)?;
+ Counted::new((
+ 1u32,
+ 4u32,
+ self.spv_layer() as u32,
+ SpvBool(self.look.omit_empty),
+ SpvBool(self.look.row_label_position == LabelPosition::Corner),
+ SpvBool(self.look.footnote_marker_type == FootnoteMarkerType::Alphabetic),
+ SpvBool(self.look.footnote_marker_position == FootnoteMarkerPosition::Superscript),
+ 0u8,
+ Counted::new((
+ 0u32, // n-row-breaks
+ 0u32, // n-column-breaks
+ 0u32, // n-row-keeps
+ 0u32, // n-column-keeps
+ 0u32, // n-row-point-keeps
+ 0u32, // n-column-point-keeps
+ )),
+ SpvString::optional(&self.notes),
+ SpvString::optional(&self.look.name),
+ Zeros(82),
+ ))
+ .with_endian(Endian::Little)
+ .write_be(writer)?;
+
+ fn y0(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> {
+ (
+ pivot_table.settings.epoch.0 as u32,
+ u8::from(pivot_table.settings.decimal),
+ b',',
+ )
+ }
+
+ fn custom_currency(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> {
+ (
+ 5,
+ EnumMap::from_fn(|cc| {
+ SpvString(pivot_table.settings.number_style(Type::CC(cc)).to_string())
+ })
+ .into_array(),
+ )
+ }
+
+ fn x1(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> {
+ (
+ 0u8, // x14
+ if pivot_table.show_title { 1u8 } else { 10u8 },
+ 0u8, // x16
+ 0u8, // lang
+ Show::as_spv(&pivot_table.show_variables),
+ Show::as_spv(&pivot_table.show_values),
+ -1i32, // x18
+ -1i32, // x19
+ Zeros(17),
+ SpvBool(false), // x20
+ SpvBool(pivot_table.show_caption),
+ )
+ }
+
+ fn x2() -> impl for<'a> BinWrite<Args<'a> = ()> {
+ Counted::new((
+ 0u32, // n-row-heights
+ 0u32, // n-style-maps
+ 0u32, // n-styles,
+ 0u32,
+ ))
+ }
+
+ fn y1(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> + use<'_> {
+ (
+ SpvString::optional(&pivot_table.command_c),
+ SpvString::optional(&pivot_table.command_local),
+ SpvString::optional(&pivot_table.language),
+ SpvString("UTF-8"),
+ SpvString::optional(&pivot_table.locale),
+ SpvBool(false), // x10
+ SpvBool(pivot_table.settings.leading_zero),
+ SpvBool(true), // x12
+ SpvBool(true), // x13
+ y0(pivot_table),
+ )
+ }
+
+ fn y2(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> {
+ (custom_currency(pivot_table), b'.', SpvBool(false))
+ }
+
+ fn x3(pivot_table: &PivotTable) -> impl for<'a> BinWrite<Args<'a> = ()> + use<'_> {
+ Counted::new((
+ 1u8,
+ 0u8,
+ 4u8, // x21
+ 0u8,
+ 0u8,
+ 0u8,
+ y1(pivot_table),
+ pivot_table.small,
+ 1u8,
+ SpvString::optional(&pivot_table.dataset),
+ SpvString::optional(&pivot_table.datafile),
+ 0u32,
+ pivot_table
+ .date
+ .map_or(0i64, |date| date.and_utc().timestamp()),
+ y2(pivot_table),
+ ))
+ }
+
+ // Formats.
+ (
+ 0u32,
+ SpvString("en_US.ISO_8859-1:1987"),
+ 0u32,
+ SpvBool(false),
+ SpvBool(false),
+ SpvBool(true),
+ y0(self),
+ custom_currency(self),
+ Counted::new(Counted::new(((x1(self), x2()), x3(self)))),
+ )
+ .write_le(writer)?;
+
+ // Dimensions.
+ (self.dimensions.len() as u32).write_le(writer)?;
+
+ let x2 = repeat_n(2, self.axes[Axis3::Z].dimensions.len())
+ .chain(repeat_n(0, self.axes[Axis3::Y].dimensions.len()))
+ .chain(repeat(1));
+ for ((index, dimension), x2) in self.dimensions.iter().enumerate().zip(x2) {
+ dimension.write_options(writer, endian, (index, x2))?;
+ }
+
+ // Axes.
+ for axis in [Axis3::Z, Axis3::Y, Axis3::X] {
+ (self.axes[axis].dimensions.len() as u32).write_le(writer)?;
+ }
+ for axis in [Axis3::Z, Axis3::Y, Axis3::X] {
+ for index in self.axes[axis].dimensions.iter().copied() {
+ (index as u32).write_le(writer)?;
+ }
+ }
+
+ // Cells.
+ (self.cells.len() as u32).write_le(writer)?;
+ for (index, value) in &self.cells {
+ (*index as u64, value).write_le(writer)?;
+ }
Ok(())
}
}
fn write(&mut self, item: &Arc<Item>) {
- match &item.details {
- super::Details::Chart => todo!(),
- super::Details::Image => todo!(),
- super::Details::Group(_items) => todo!(),
- super::Details::Message(_diagnostic) => todo!(),
- super::Details::PageBreak => {
- self.needs_page_break = true;
- return;
- }
- super::Details::Table(pivot_table) => self.write_table(&*item, pivot_table),
- super::Details::Text(_text) => todo!(),
- };
- todo!()
+ if let Some(container) = self.write_item(item) {}
}
}
#[derive(Serialize)]
struct TableStructure;
+impl BinWrite for Dimension {
+ type Args<'a> = (usize, u8);
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ (index, x2): (usize, u8),
+ ) -> binrw::BinResult<()> {
+ (
+ &self.root.name,
+ 0u8, // x1
+ x2,
+ 2u32, // x3
+ SpvBool(self.root.show_label),
+ SpvBool(self.hide_all_labels),
+ SpvBool(true),
+ index as u32,
+ self.root.children.len() as u32,
+ )
+ .write_options(writer, endian, ())?;
+
+ let mut data_indexes = self.presentation_order.iter().copied();
+ self.root.write_le(writer, &mut data_indexes)
+ }
+}
+
+impl Category {
+ fn write_le<D, W>(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ D: Iterator<Item = usize>,
+ {
+ match self {
+ Category::Group(group) => group.write_le(writer, data_indexes),
+ Category::Leaf(leaf) => leaf.write_le(writer, data_indexes),
+ }
+ }
+}
+
+impl Leaf {
+ fn write_le<D, W>(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ D: Iterator<Item = usize>,
+ {
+ (
+ self.name(),
+ 0u8,
+ 0u8,
+ 0u8,
+ 2u32,
+ data_indexes.next().unwrap() as u32,
+ 0u32,
+ )
+ .write_le(writer)
+ }
+}
+
+impl Group {
+ fn write_le<D, W>(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ D: Iterator<Item = usize>,
+ {
+ (
+ self.name(),
+ 0u8,
+ 0u8,
+ 1u8,
+ 0u32, // x23
+ -1i32,
+ )
+ .write_le(writer)?;
+
+ for child in &self.children {
+ child.write_le(writer, data_indexes)?;
+ }
+ Ok(())
+ }
+}
+
impl BinWrite for Footnote {
type Args<'a> = ();
}
}
-struct SpvString<'a>(&'a str);
-impl<'a> SpvString<'a> {
+struct SpvString<T>(T);
+impl<'a> SpvString<&'a str> {
fn optional(s: &'a Option<String>) -> Self {
Self(s.as_ref().map_or("", |s| s.as_str()))
}
}
-impl BinWrite for SpvString<'_> {
+impl<T> BinWrite for SpvString<T>
+where
+ T: AsRef<str>,
+{
type Args<'a> = ();
fn write_options<W: Write + Seek>(
endian: binrw::Endian,
args: Self::Args<'_>,
) -> binrw::BinResult<()> {
- let length = self.0.len() as u32;
- (length, self.0.as_bytes()).write_options(writer, endian, args)
- }
-}
-
-#[derive(BinWrite)]
-#[bw(little)]
-struct Header {
- #[bw(magic(1u16))]
- version: u8,
-
- x0: SpvBool,
- x1: SpvBool,
- rotate_inner_column_labels: SpvBool,
- rotate_outer_row_labels: SpvBool,
- x2: SpvBool,
- x3: u32,
- min_col_heading_width: i32,
- max_col_heading_width: i32,
- min_row_heading_width: i32,
- max_row_heading_width: i32,
-}
-
-impl Header {
- fn new(pivot_table: &PivotTable) -> Self {
- Self {
- version: 3,
- x0: SpvBool(true),
- x1: SpvBool(false),
- rotate_inner_column_labels: SpvBool(pivot_table.rotate_inner_column_labels),
- rotate_outer_row_labels: SpvBool(pivot_table.rotate_outer_row_labels),
- x2: SpvBool(true),
- x3: 0x15,
- min_col_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Columns].start()
- as i32,
- max_col_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Columns].end()
- as i32,
- min_row_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Rows].start()
- as i32,
- max_row_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Rows].end()
- as i32,
- }
+ let s = self.0.as_ref();
+ let length = s.len() as u32;
+ (length, s.as_bytes()).write_options(writer, endian, args)
}
}
}
}
+struct Counted<T> {
+ inner: T,
+ endian: Option<Endian>,
+}
+
+impl<T> Counted<T> {
+ fn new(inner: T) -> Self {
+ Self {
+ inner,
+ endian: None,
+ }
+ }
+ fn with_endian(self, endian: Endian) -> Self {
+ Self {
+ inner: self.inner,
+ endian: Some(endian),
+ }
+ }
+}
+
+impl<T> BinWrite for Counted<T>
+where
+ T: BinWrite,
+ for<'a> T: BinWrite<Args<'a> = ()>,
+{
+ type Args<'a> = T::Args<'a>;
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ let start = Count::new(writer)?;
+ self.inner.write_options(writer, endian, args)?;
+ start.finish(writer, self.endian.unwrap_or(endian))
+ }
+}
+
+struct Zeros(usize);
+
+impl BinWrite for Zeros {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ _endian: Endian,
+ _args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ for _ in 0..self.0 {
+ writer.write_all(&[0u8])?;
+ }
+ Ok(())
+ }
+}
+
#[derive(Default)]
struct StylePair<'a> {
font_style: Option<&'a FontStyle>,