From: Ben Pfaff Date: Thu, 15 May 2025 00:07:32 +0000 (-0700) Subject: spv progress X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4f686090a795654923ae586ff83d796bf7bc8799;p=pspp spv progress --- diff --git a/rust/pspp/src/format/mod.rs b/rust/pspp/src/format/mod.rs index e03fd714e9..7094dec8a4 100644 --- a/rust/pspp/src/format/mod.rs +++ b/rust/pspp/src/format/mod.rs @@ -1,5 +1,5 @@ use std::{ - fmt::{Debug, Display, Formatter, Result as FmtResult}, + fmt::{Debug, Display, Formatter, Result as FmtResult, Write}, ops::{Not, RangeInclusive}, str::{Chars, FromStr}, sync::LazyLock, @@ -938,7 +938,7 @@ impl Settings { pub fn with_epoch(self, epoch: Epoch) -> Self { Self { epoch, ..self } } - fn number_style(&self, type_: Type) -> &NumberStyle { + pub fn number_style(&self, type_: Type) -> &NumberStyle { static DEFAULT: LazyLock = LazyLock::new(|| NumberStyle::new("", "", Decimal::Dot, None, false)); @@ -1036,6 +1036,28 @@ pub struct NumberStyle { pub extra_bytes: usize, } +impl Display for NumberStyle { + /// Display this number style in the format used for custom currency. + /// + /// This format can only accurately represent number styles that include a + /// grouping character. If this number style doesn't, it will pretend that + /// the grouping character is the opposite of the decimal point character. + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + let grouping = char::from(!self.decimal); + write!( + f, + "{}{}{}{}{}{}{}", + self.neg_prefix.display(grouping), + grouping, + self.prefix.display(grouping), + grouping, + self.suffix.display(grouping), + grouping, + self.neg_suffix.display(grouping), + ) + } +} + impl NumberStyle { fn new( prefix: &str, @@ -1086,6 +1108,30 @@ impl Affix { fn extra_bytes(&self) -> usize { self.s.len().checked_sub(self.width).unwrap() } + + fn display(&self, escape: char) -> DisplayAffix<'_> { + DisplayAffix { + affix: self.s.as_str(), + escape, + } + } +} + +pub struct DisplayAffix<'a> { + affix: &'a str, + escape: char, +} + +impl Display for DisplayAffix<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + for c in self.affix.chars() { + if c == self.escape { + f.write_char('\'')?; + } + f.write_char(c)?; + } + Ok(()) + } } impl FromStr for NumberStyle { diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index 1f5ffea12e..0160885211 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -336,11 +336,12 @@ pub struct Dimension { /// Ordering of leaves for presentation. /// - /// This is a permutation of `0..n` where `n` is the number of leaves. - presentation_order: Vec, + /// This is a permutation of `0..n` where `n` is the number of leaves. It + /// maps from an index in presentation order to an index in data order. + pub presentation_order: Vec, /// Display. - hide_all_labels: bool, + pub hide_all_labels: bool, } pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>; @@ -379,13 +380,13 @@ impl Dimension { #[derive(Clone, Debug)] pub struct Group { len: usize, - name: Box, + pub name: Box, /// The child categories. /// /// A group usually has multiple children, but it is allowed to have /// only one or even (pathologically) none. - children: Vec, + pub children: Vec, /// Whether to show the group's label. pub show_label: bool, @@ -454,6 +455,10 @@ impl Group { pub fn is_empty(&self) -> bool { self.len() == 0 } + + pub fn name(&self) -> &Value { + &self.name + } } #[derive(Clone, Debug, Default)] @@ -486,6 +491,9 @@ impl Leaf { name: Box::new(name), } } + pub fn name(&self) -> &Value { + &self.name + } } /// Pivot result classes. @@ -511,14 +519,21 @@ pub enum Category { } impl Category { - fn len(&self) -> usize { + pub fn name(&self) -> &Value { + match self { + Category::Group(group) => &group.name, + Category::Leaf(leaf) => &leaf.name, + } + } + + pub fn len(&self) -> usize { match self { Category::Group(group) => group.len, Category::Leaf(_) => 1, } } - fn nth_leaf(&self, index: usize) -> Option<&Leaf> { + pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> { match self { Category::Group(group) => group.nth_leaf(index), Category::Leaf(leaf) => { @@ -531,7 +546,7 @@ impl Category { } } - fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option> { + pub fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option> { match self { Category::Group(group) => group.leaf_path(index, groups), Category::Leaf(leaf) => { @@ -544,7 +559,7 @@ impl Category { } } - fn show_label(&self) -> bool { + pub fn show_label(&self) -> bool { match self { Category::Group(group) => group.show_label, Category::Leaf(_) => true, diff --git a/rust/pspp/src/output/spv.rs b/rust/pspp/src/output/spv.rs index eea552b2de..df39a7126c 100644 --- a/rust/pspp/src/output/spv.rs +++ b/rust/pspp/src/output/spv.rs @@ -3,10 +3,12 @@ use std::{ 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, @@ -21,9 +23,10 @@ use crate::{ 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, }, @@ -97,6 +100,27 @@ where }), } } + + fn write_item(&mut self, item: &Item) -> Option { + 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::>(); + } + 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 { @@ -105,11 +129,24 @@ impl BinWrite for PivotTable { fn write_options( &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. ( @@ -170,8 +207,7 @@ impl BinWrite for PivotTable { 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), @@ -181,14 +217,157 @@ impl BinWrite for PivotTable { 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 = ()> { + ( + pivot_table.settings.epoch.0 as u32, + u8::from(pivot_table.settings.decimal), + b',', + ) + } + + fn custom_currency(pivot_table: &PivotTable) -> impl for<'a> BinWrite = ()> { + ( + 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 = ()> { + ( + 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 = ()> { + Counted::new(( + 0u32, // n-row-heights + 0u32, // n-style-maps + 0u32, // n-styles, + 0u32, + )) + } + + fn y1(pivot_table: &PivotTable) -> impl for<'a> BinWrite = ()> + 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 = ()> { + (custom_currency(pivot_table), b'.', SpvBool(false)) + } + + fn x3(pivot_table: &PivotTable) -> impl for<'a> BinWrite = ()> + 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(()) } @@ -217,19 +396,7 @@ where } fn write(&mut self, item: &Arc) { - 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) {} } } @@ -395,6 +562,88 @@ impl Table { #[derive(Serialize)] struct TableStructure; +impl BinWrite for Dimension { + type Args<'a> = (usize, u8); + + fn write_options( + &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(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()> + where + W: Write + Seek, + D: Iterator, + { + 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(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()> + where + W: Write + Seek, + D: Iterator, + { + ( + self.name(), + 0u8, + 0u8, + 0u8, + 2u32, + data_indexes.next().unwrap() as u32, + 0u32, + ) + .write_le(writer) + } +} + +impl Group { + fn write_le(&self, writer: &mut W, data_indexes: &mut D) -> binrw::BinResult<()> + where + W: Write + Seek, + D: Iterator, + { + ( + 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> = (); @@ -531,13 +780,16 @@ impl BinWrite for SpvBool { } } -struct SpvString<'a>(&'a str); -impl<'a> SpvString<'a> { +struct SpvString(T); +impl<'a> SpvString<&'a str> { fn optional(s: &'a Option) -> Self { Self(s.as_ref().map_or("", |s| s.as_str())) } } -impl BinWrite for SpvString<'_> { +impl BinWrite for SpvString +where + T: AsRef, +{ type Args<'a> = (); fn write_options( @@ -546,48 +798,9 @@ impl BinWrite for SpvString<'_> { 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) } } @@ -640,6 +853,63 @@ impl Count { } } +struct Counted { + inner: T, + endian: Option, +} + +impl Counted { + fn new(inner: T) -> Self { + Self { + inner, + endian: None, + } + } + fn with_endian(self, endian: Endian) -> Self { + Self { + inner: self.inner, + endian: Some(endian), + } + } +} + +impl BinWrite for Counted +where + T: BinWrite, + for<'a> T: BinWrite = ()>, +{ + type Args<'a> = T::Args<'a>; + + fn write_options( + &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( + &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>,