From 1109965e74e9f2695ae1a09ea24f138b34c2a40a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 25 Dec 2025 13:18:42 -0800 Subject: [PATCH] more legacy --- rust/pspp/src/output/drivers/cairo/fsm.rs | 2 +- rust/pspp/src/output/drivers/csv.rs | 4 +- rust/pspp/src/output/drivers/html.rs | 20 ++-- rust/pspp/src/output/pivot/output.rs | 87 +++++++++--------- .../pivot/testdata/d2_cl-all_layers.expected | 6 +- .../pivot/testdata/d2_cl-layer0.expected | 2 +- .../pivot/testdata/d2_cl-layer1.expected | 2 +- .../pivot/testdata/d2_rl-all_layers.expected | 6 +- .../pivot/testdata/d2_rl-layer0.expected | 2 +- .../pivot/testdata/d2_rl-layer1.expected | 2 +- .../pivot/testdata/d3-layer0_0.expected | 4 +- .../pivot/testdata/d3-layer0_1.expected | 4 +- .../pivot/testdata/d3-layer1_2.expected | 4 +- rust/pspp/src/output/pivot/value.rs | 57 +++++++++--- rust/pspp/src/output/render.rs | 21 +++-- rust/pspp/src/spv/read.rs | 1 - rust/pspp/src/spv/read/legacy_xml.rs | 28 +++--- rust/pspp/src/spv/read/tests.rs | 7 ++ rust/pspp/src/spv/testdata/legacy5.expected | 2 +- rust/pspp/src/spv/testdata/legacy6.expected | 2 +- rust/pspp/src/spv/testdata/legacy9.expected | 18 ++++ rust/pspp/src/spv/testdata/legacy9.spv | Bin 0 -> 10371 bytes 22 files changed, 177 insertions(+), 104 deletions(-) create mode 100644 rust/pspp/src/spv/testdata/legacy9.expected create mode 100644 rust/pspp/src/spv/testdata/legacy9.spv diff --git a/rust/pspp/src/output/drivers/cairo/fsm.rs b/rust/pspp/src/output/drivers/cairo/fsm.rs index 05075ca1f0..786c1a3c9e 100644 --- a/rust/pspp/src/output/drivers/cairo/fsm.rs +++ b/rust/pspp/src/output/drivers/cairo/fsm.rs @@ -332,7 +332,7 @@ impl<'a, 'b> DrawCell<'a, 'b> { }; layout.set_font_description(Some(font)); - let (body_display, suffixes) = self.display().split_suffixes(); + let (body_display, suffixes) = self.display().split(); let horz_align = self.horz_align(&body_display); let (mut body, mut attrs) = if let Some(markup) = body_display.markup() { diff --git a/rust/pspp/src/output/drivers/csv.rs b/rust/pspp/src/output/drivers/csv.rs index 749e50535a..b34214a6cf 100644 --- a/rust/pspp/src/output/drivers/csv.rs +++ b/rust/pspp/src/output/drivers/csv.rs @@ -309,7 +309,9 @@ impl CsvDriver { self.start_item(); self.output_table(pt, output.title.as_ref(), Some("Table"))?; - self.output_table(pt, output.layers.as_ref(), Some("Layer"))?; + for (index, layer) in output.layers.iter().enumerate() { + self.output_table(pt, Some(layer), (index == 0).then_some("Layer"))?; + } self.output_table(pt, Some(&output.body), None)?; self.output_table(pt, output.caption.as_ref(), Some("Caption"))?; self.output_table(pt, output.footnotes.as_ref(), Some("Footnote"))?; diff --git a/rust/pspp/src/output/drivers/html.rs b/rust/pspp/src/output/drivers/html.rs index 6552ebe8ae..77b549963b 100644 --- a/rust/pspp/src/output/drivers/html.rs +++ b/rust/pspp/src/output/drivers/html.rs @@ -98,16 +98,18 @@ where )?; } - if let Some(layers) = output.layers { + if !output.layers.is_empty() { writeln!(&mut self.writer, "")?; - for cell in layers.cells() { + for layer in &output.layers { writeln!(&mut self.writer, "")?; - self.put_cell( - DrawCell::new(cell.inner(), &layers), - CellRect::new(0..output.body.n[Axis2::X], 0..1), - "td", - None, - )?; + for cell in layer.cells() { + self.put_cell( + DrawCell::new(cell.inner(), layer), + CellRect::for_cell(cell.pos), + "td", + None, + )?; + } writeln!(&mut self.writer, "")?; } writeln!(&mut self.writer, "")?; @@ -172,7 +174,7 @@ where table: Option<&Table>, ) -> std::io::Result<()> { write!(&mut self.writer, "<{tag}")?; - let (body, suffixes) = cell.display().split_suffixes(); + let (body, suffixes) = cell.display().split(); let mut style = String::new(); let horz_align = match cell.horz_align(&body) { diff --git a/rust/pspp/src/output/pivot/output.rs b/rust/pspp/src/output/pivot/output.rs index fa07f4f2c2..cf23ef768c 100644 --- a/rust/pspp/src/output/pivot/output.rs +++ b/rust/pspp/src/output/pivot/output.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License along with // this program. If not, see . -use std::{iter::zip, ops::Range, sync::Arc}; +use std::{iter::once, ops::Range, sync::Arc}; use enum_map::{EnumMap, enum_map}; use itertools::Itertools; @@ -138,20 +138,20 @@ impl PivotTable { } } - fn create_aux_table3(&self, area: Area, rows: I) -> Table + fn create_aux_table(&self, area: Area, axis: Axis2, cells: I) -> Table where I: Iterator> + ExactSizeIterator, { let mut table = Table::new( - CellPos::new(1, rows.len()), + CellPos::for_axis((axis, cells.len()), 1), CellPos::new(0, 0), self.style.look.areas.clone(), self.borders(false), self, ); - for (y, row) in rows.enumerate() { + for (z, row) in cells.enumerate() { table.put( - CellRect::for_cell(CellPos::new(0, y)), + CellRect::for_cell(CellPos::for_axis((axis, z), 0)), CellInner::new(area, row), ); } @@ -163,7 +163,7 @@ impl PivotTable { I: Iterator> + ExactSizeIterator, { if rows.len() > 0 { - Some(self.create_aux_table3(area, rows)) + Some(self.create_aux_table(area, Axis2::Y, rows)) } else { None } @@ -274,44 +274,48 @@ impl PivotTable { /// Constructs a [Table] for this `PivotTable`'s title. Returns `None` if /// the table doesn't have a title. pub fn output_title(&self) -> Option { - Some(self.create_aux_table3( + Some(self.create_aux_table( Area::Title, + Axis2::Y, [self.metadata.title.as_ref()?.clone()].into_iter(), )) } /// Constructs a [Table] for this `PivotTable`'s layer values. Returns /// `None` if the table doesn't have layers. - pub fn output_layers(&self, layer_indexes: &[usize]) -> Option
{ - let mut layers = Vec::new(); - for (dimension, &layer_index) in zip( - self.axes[Axis3::Z] - .dimensions - .iter() - .map(|index| &self.dimensions[*index]), - layer_indexes, - ) { - if !dimension.is_empty() { - // `\u{2001}` is an "em quad" space, which looks to me like the - // space that SPSS uses here. - let s = format!( - "{}:\u{2001}{}", - dimension.root.name().display(self), - dimension.nth_leaf(layer_index).unwrap().0.display(self) - ); - layers.push(Box::new(Value::new_user_text(s))); - } - } - layers.reverse(); - - self.create_aux_table_if_nonempty(Area::Layers, layers.into_iter()) + pub fn output_layers(&self, layer_indexes: &[usize]) -> Vec
{ + self.axes[Axis3::Z] + .dimensions + .iter() + .map(|index| &self.dimensions[*index]) + .zip(layer_indexes) + .rev() + .filter(|(dimension, _)| !dimension.is_empty()) + .map(|(dimension, &layer_index)| { + // Append `:` to the name of the dimension, preserving all the styling. + let name = dimension.root.name(); + let text = format!("{}:", name.display(self).without_suffixes()); + let name = Value::new_user_text(text).with_styling(name.styling.clone()); + + self.create_aux_table( + Area::Layers, + Axis2::X, + [ + Box::new(name), + dimension.nth_leaf(layer_index).unwrap().0.clone(), + ] + .into_iter(), + ) + }) + .collect() } /// Constructs a [Table] for this `PivotTable`'s caption. Returns `None` if /// the table doesn't have a caption. pub fn output_caption(&self) -> Option
{ - Some(self.create_aux_table3( + Some(self.create_aux_table( Area::Caption, + Axis2::Y, [self.metadata.caption.as_ref()?.clone()].into_iter(), )) } @@ -345,14 +349,15 @@ impl PivotTable { .flatten(); // Then collect the footnotes from those tables. - let tables = [ - title.as_ref(), - layers.as_ref(), - Some(&body), - caption.as_ref(), - ]; - let footnotes = - self.output_footnotes(&self.collect_footnotes(tables.into_iter().flatten())); + let title_iter = once(title.as_ref()).flatten(); + let layers_iter = layers.iter(); + let body_iter = once(&body); + let caption_iter = once(caption.as_ref()).flatten(); + let tables_iter = title_iter + .chain(layers_iter) + .chain(body_iter) + .chain(caption_iter); + let footnotes = self.output_footnotes(&self.collect_footnotes(tables_iter)); OutputTables { title, @@ -401,8 +406,8 @@ impl PivotTable { pub struct OutputTables { /// Title table, if any. pub title: Option
, - /// Layers table, if any. - pub layers: Option
, + /// Layers tables, if any. + pub layers: Vec
, /// Table body. pub body: Table, /// Table caption, if any. diff --git a/rust/pspp/src/output/pivot/testdata/d2_cl-all_layers.expected b/rust/pspp/src/output/pivot/testdata/d2_cl-all_layers.expected index 842bf2702a..0fac24e5d7 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_cl-all_layers.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_cl-all_layers.expected @@ -1,5 +1,5 @@ Column (All Layers) -b: b1 +b: b1 ╭──┬──┬──╮ │a1│a2│a3│ ├──┼──┼──┤ @@ -7,7 +7,7 @@ b: b1 ╰──┴──┴──╯ Column (All Layers) -b: b2 +b: b2 ╭──┬──┬──╮ │a1│a2│a3│ ├──┼──┼──┤ @@ -15,7 +15,7 @@ b: b2 ╰──┴──┴──╯ Column (All Layers) -b: b3 +b: b3 ╭──┬──┬──╮ │a1│a2│a3│ ├──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/testdata/d2_cl-layer0.expected b/rust/pspp/src/output/pivot/testdata/d2_cl-layer0.expected index 9a3c09891e..8cb69bd2bc 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_cl-layer0.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_cl-layer0.expected @@ -1,5 +1,5 @@ Column x b1 -b: b1 +b: b1 ╭──┬──┬──╮ │a1│a2│a3│ ├──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/testdata/d2_cl-layer1.expected b/rust/pspp/src/output/pivot/testdata/d2_cl-layer1.expected index 6611a174d5..f6c5080f1a 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_cl-layer1.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_cl-layer1.expected @@ -1,5 +1,5 @@ Column x b2 -b: b2 +b: b2 ╭──┬──┬──╮ │a1│a2│a3│ ├──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/testdata/d2_rl-all_layers.expected b/rust/pspp/src/output/pivot/testdata/d2_rl-all_layers.expected index 2a83533b22..72f11c1822 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_rl-all_layers.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_rl-all_layers.expected @@ -1,5 +1,5 @@ Row (All Layers) -b: b1 +b: b1 ╭──┬─╮ │a1│0│ │a2│1│ @@ -7,7 +7,7 @@ b: b1 ╰──┴─╯ Row (All Layers) -b: b2 +b: b2 ╭──┬─╮ │a1│3│ │a2│4│ @@ -15,7 +15,7 @@ b: b2 ╰──┴─╯ Row (All Layers) -b: b3 +b: b3 ╭──┬─╮ │a1│6│ │a2│7│ diff --git a/rust/pspp/src/output/pivot/testdata/d2_rl-layer0.expected b/rust/pspp/src/output/pivot/testdata/d2_rl-layer0.expected index 843d15dbbc..b516020cc7 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_rl-layer0.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_rl-layer0.expected @@ -1,5 +1,5 @@ Row x b1 -b: b1 +b: b1 ╭──┬─╮ │a1│0│ │a2│1│ diff --git a/rust/pspp/src/output/pivot/testdata/d2_rl-layer1.expected b/rust/pspp/src/output/pivot/testdata/d2_rl-layer1.expected index 53ad394014..1ee2079e10 100644 --- a/rust/pspp/src/output/pivot/testdata/d2_rl-layer1.expected +++ b/rust/pspp/src/output/pivot/testdata/d2_rl-layer1.expected @@ -1,5 +1,5 @@ Row x b2 -b: b2 +b: b2 ╭──┬─╮ │a1│3│ │a2│4│ diff --git a/rust/pspp/src/output/pivot/testdata/d3-layer0_0.expected b/rust/pspp/src/output/pivot/testdata/d3-layer0_0.expected index 3ddf5ab679..3db87b5dd9 100644 --- a/rust/pspp/src/output/pivot/testdata/d3-layer0_0.expected +++ b/rust/pspp/src/output/pivot/testdata/d3-layer0_0.expected @@ -1,6 +1,6 @@ Column x b1 x a1 -b: b1 -a: a1 +b: b1 +a: a1 ╭──┬──┬──┬──┬──╮ │c1│c2│c3│c4│c5│ ├──┼──┼──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/testdata/d3-layer0_1.expected b/rust/pspp/src/output/pivot/testdata/d3-layer0_1.expected index 8f4dd26678..e1d4dafc90 100644 --- a/rust/pspp/src/output/pivot/testdata/d3-layer0_1.expected +++ b/rust/pspp/src/output/pivot/testdata/d3-layer0_1.expected @@ -1,6 +1,6 @@ Column x b2 x a1 -b: b2 -a: a1 +b: b2 +a: a1 ╭──┬──┬──┬──┬──╮ │c1│c2│c3│c4│c5│ ├──┼──┼──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/testdata/d3-layer1_2.expected b/rust/pspp/src/output/pivot/testdata/d3-layer1_2.expected index bc679a3ebe..c4248f0a52 100644 --- a/rust/pspp/src/output/pivot/testdata/d3-layer1_2.expected +++ b/rust/pspp/src/output/pivot/testdata/d3-layer1_2.expected @@ -1,6 +1,6 @@ Column x b3 x a2 -b: b3 -a: a2 +b: b3 +a: a2 ╭──┬──┬──┬──┬──╮ │c1│c2│c3│c4│c5│ ├──┼──┼──┼──┼──┤ diff --git a/rust/pspp/src/output/pivot/value.rs b/rust/pspp/src/output/pivot/value.rs index 3af7749c78..9127658e66 100644 --- a/rust/pspp/src/output/pivot/value.rs +++ b/rust/pspp/src/output/pivot/value.rs @@ -176,17 +176,7 @@ impl Value { /// Constructs a new text `Value` from `s`, which should have been provided /// by the user. pub fn new_user_text(s: impl Into) -> Self { - let s: String = s.into(); - if s.is_empty() { - Self::default() - } else { - Self::new(ValueInner::Text(TextValue { - user_provided: true, - localized: s, - c: None, - id: None, - })) - } + Self::new(ValueInner::new_user_text(s)) } /// Constructs a new `Value` from `variable`. @@ -282,6 +272,16 @@ impl Value { self } + pub fn with_footnotes<'a>( + mut self, + footnotes: impl IntoIterator>, + ) -> Self { + for footnote in footnotes { + self.add_footnote(footnote); + } + self + } + /// Adds `footnote` to this `Value`. pub fn add_footnote(&mut self, footnote: &Arc) { let footnotes = &mut self.styling_mut().footnotes; @@ -289,6 +289,25 @@ impl Value { footnotes.sort_by_key(|f| f.index); } + pub fn with_subscripts<'a>( + mut self, + subscripts: impl IntoIterator>, + ) -> Self { + for subscript in subscripts { + self.add_subscript(subscript); + } + self + } + + pub fn with_subscript(mut self, subscript: impl Into) -> Self { + self.add_subscript(subscript); + self + } + + pub fn add_subscript(&mut self, subscript: impl Into) { + self.styling_mut().subscripts.push(subscript.into()); + } + /// Returns this `Value` with `show` as the [Show] setting for value labels, /// if this is a [DatumValue]. pub fn with_show_value_label(mut self, show: Option) -> Self { @@ -498,7 +517,7 @@ impl<'a> DisplayValue<'a> { /// Returns this display split into `(body, suffixes)` where `suffixes` is /// subscripts and footnotes and `body` is everything else. - pub fn split_suffixes(self) -> (Self, Self) { + pub fn split(self) -> (Self, Self) { (self.clone().without_suffixes(), self.without_body()) } @@ -1182,6 +1201,20 @@ impl ValueInner { show_label, } } + + pub fn new_user_text(s: impl Into) -> Self { + let s: String = s.into(); + if !s.is_empty() { + Self::Text(TextValue { + user_provided: true, + localized: s, + c: None, + id: None, + }) + } else { + Self::Empty + } + } } /// Styling inside a [Value]. diff --git a/rust/pspp/src/output/render.rs b/rust/pspp/src/output/render.rs index be194a7dcf..2f0509e055 100644 --- a/rust/pspp/src/output/render.rs +++ b/rust/pspp/src/output/render.rs @@ -219,7 +219,7 @@ impl RenderedTable { /// The new [Page] will be suitable for rendering on a device whose page /// size is `params.size`, but the caller is responsible for actually /// breaking it up to fit on such a device, using the [Break] abstraction. - fn new(table: Table, device: &dyn Device, min_width: isize, look: &Look) -> Self { + fn new(table: Table, device: &dyn Device, min_width: Option, look: &Look) -> Self { use Axis2::*; use Extreme::*; @@ -288,7 +288,9 @@ impl RenderedTable { ); } } - if min_width > 0 { + if let Some(min_width) = min_width + && min_width > 0 + { for ext in [Min, Max] { distribute_spanned_width( min_width, @@ -506,7 +508,7 @@ impl Page { /// The new [Page] will be suitable for rendering on a device whose page /// size is `params.size`, but the caller is responsible for actually /// breaking it up to fit on such a device, using the [Break] abstraction. - pub fn new(table: Table, device: &dyn Device, min_width: isize, look: &Look) -> Self { + pub fn new(table: Table, device: &dyn Device, min_width: Option, look: &Look) -> Self { let table = Arc::new(RenderedTable::new(table, device, min_width, look)); let ranges = EnumMap::from_fn(|axis| { table.cp[axis][1 + table.h()[axis] * 2]..table.cp[axis].last().copied().unwrap() @@ -1005,7 +1007,7 @@ impl Pager { // Figure out the width of the body of the table. Use this to determine // the base scale. - let body_page = Page::new(output.body, device, 0, &pivot_table.style.look); + let body_page = Page::new(output.body, device, None, &pivot_table.style.look); let body_width = body_page.width(Axis2::X).min(device.params().size.x()); let mut scale = if body_width > device.params().size[Axis2::X] && pivot_table.style.look.shrink_to_fit[Axis2::X] @@ -1017,17 +1019,20 @@ impl Pager { }; let mut pages = SmallVec::new(); - for table in [output.title, output.layers].into_iter().flatten() { + if let Some(title) = output.title { pages.push(Page::new( - table, + title, device, - body_width, + Some(body_width), &pivot_table.style.look, )); } + for layer in output.layers { + pages.push(Page::new(layer, device, None, &pivot_table.style.look)); + } pages.push(body_page); for table in [output.caption, output.footnotes].into_iter().flatten() { - pages.push(Page::new(table, device, 0, &pivot_table.style.look)); + pages.push(Page::new(table, device, None, &pivot_table.style.look)); } pages.reverse(); diff --git a/rust/pspp/src/spv/read.rs b/rust/pspp/src/spv/read.rs index c12a2ab777..3dfeb4d669 100644 --- a/rust/pspp/src/spv/read.rs +++ b/rust/pspp/src/spv/read.rs @@ -781,7 +781,6 @@ impl Table { Ok(result) => result, Err(error) => panic!("{error:?}"), }; - dbg!(&self.table_properties); let pivot_table = visualization.decode( data, self.table_properties diff --git a/rust/pspp/src/spv/read/legacy_xml.rs b/rust/pspp/src/spv/read/legacy_xml.rs index da6c2914bc..a6c7a1be85 100644 --- a/rust/pspp/src/spv/read/legacy_xml.rs +++ b/rust/pspp/src/spv/read/legacy_xml.rs @@ -324,7 +324,11 @@ impl Visualization { } } let title = LabelFrame::decode_label(&labels[Purpose::Title], &footnotes); - let caption = LabelFrame::decode_label(&labels[Purpose::SubTitle], &footnotes); + let mut caption_labels = &labels[Purpose::SubTitle]; + if caption_labels.is_empty() { + caption_labels = &labels[Purpose::Footnote]; + } + let caption = LabelFrame::decode_label(&caption_labels, &footnotes); if let Some(style) = &graph.interval.labeling.style && let Some(style) = styles.get(style.references.as_str()) { @@ -363,7 +367,6 @@ impl Visualization { for sv in take(&mut source_variables) { match sv.decode(&data, &series) { Ok(s) => { - dbg!(&sv.id); series.insert(&sv.id, s); } Err(()) => source_variables.push(sv), @@ -373,7 +376,6 @@ impl Visualization { for dv in take(&mut derived_variables) { match dv.decode(&series) { Ok(s) => { - eprintln!("{:?} {:?} {:?}", &dv.id, dv.depends_on, &dv.value); series.insert(&dv.id, s); } Err(()) => derived_variables.push(dv), @@ -403,7 +405,6 @@ impl Visualization { { let mut dimension_style = AreaStyle::default_for_area(Area::Labels(a)); let style = label.style.get(&styles); - dbg!(variables, label, style); Style::decode_area( style, label.text_frame_style.as_ref().and_then(|r| r.get(styles)), @@ -431,7 +432,6 @@ impl Visualization { && let Some(gridline) = &axis.major_ticks.gridline && let Some(style) = gridline.style.get(&styles) { - dbg!(axis, style); if let Some(border_style) = style.border(BoxBorder::Bottom) { // XXX probably not necessary, the Look is supplied at a higher level look.borders[Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X))] = @@ -745,7 +745,7 @@ impl Visualization { for part in s.split(',') { if let Ok(index) = part.parse::() && let Some(index) = index.checked_sub(1) - && let Some(footnote) = dbg!(footnotes.get(index)) + && let Some(footnote) = footnotes.get(index) { value.add_footnote(footnote); } @@ -1068,7 +1068,7 @@ impl Visualization { .collect::>(); let mut pivot_table = PivotTable::new(dimensions) .with_look(Arc::new(look)) - .with_footnotes(dbg!(footnotes)) + .with_footnotes(footnotes) .with_data(data) .with_layer(¤t_layer); let decimal = Decimal::for_lang(&self.lang); @@ -2780,12 +2780,14 @@ impl LabelFrame { for t in labels { if let LabelChild::Text(text) = &t.child { for t in text { - if let Some(defines_reference) = t.defines_reference - && let Some(footnote) = footnotes.get(defines_reference.get() - 1) - { - f.push(footnote); - } else { - s += &t.text; + if t.uses_reference.is_none() { + if let Some(defines_reference) = t.defines_reference + && let Some(footnote) = footnotes.get(defines_reference.get() - 1) + { + f.push(footnote); + } else { + s += &t.text; + } } } } diff --git a/rust/pspp/src/spv/read/tests.rs b/rust/pspp/src/spv/read/tests.rs index cf51c110d1..96508810c9 100644 --- a/rust/pspp/src/spv/read/tests.rs +++ b/rust/pspp/src/spv/read/tests.rs @@ -55,6 +55,13 @@ fn legacy8() { test_raw_spvfile("legacy8"); } +/// Checks for caption defined as a footnote label, and for footnotes in layer +/// values. +#[test] +fn legacy9() { + test_raw_spvfile("legacy9"); +} + fn test_raw_spvfile(name: &str) { let input_filename = Path::new("src/spv/testdata") .join(name) diff --git a/rust/pspp/src/spv/testdata/legacy5.expected b/rust/pspp/src/spv/testdata/legacy5.expected index e4ed57eeb1..8b701e2eca 100644 --- a/rust/pspp/src/spv/testdata/legacy5.expected +++ b/rust/pspp/src/spv/testdata/legacy5.expected @@ -1,5 +1,5 @@ Statistics -Variables: Finished +Variables: Finished ╭─────────┬───╮ │N Valid │159│ │ Missing│ 0│ diff --git a/rust/pspp/src/spv/testdata/legacy6.expected b/rust/pspp/src/spv/testdata/legacy6.expected index 6f24244118..9abd155a08 100644 --- a/rust/pspp/src/spv/testdata/legacy6.expected +++ b/rust/pspp/src/spv/testdata/legacy6.expected @@ -1,5 +1,5 @@ Notes -Contents: Weight +Contents: Weight ────── ────── diff --git a/rust/pspp/src/spv/testdata/legacy9.expected b/rust/pspp/src/spv/testdata/legacy9.expected new file mode 100644 index 0000000000..9e88b6901c --- /dev/null +++ b/rust/pspp/src/spv/testdata/legacy9.expected @@ -0,0 +1,18 @@ + Analysis +Test: Duncan[a][b] + │ Subset +SP36 N│ 1 +───────┼─────── +3 12│27.4817 +───────┼─────── +2 12│27.4908 +───────┼─────── +1 12│27.5442 +───────┼─────── +Sig. │ .587 +───────┴─────── +Means for groups in homogeneous subsets are displayed. + Based on observed means. + The error term is Mean Square(Error) = ,069. +a. Uses Harmonic Mean Sample Size = 12,000. +b. Alpha = 0,05. diff --git a/rust/pspp/src/spv/testdata/legacy9.spv b/rust/pspp/src/spv/testdata/legacy9.spv new file mode 100644 index 0000000000000000000000000000000000000000..11724ae6c9105d16aab1b7d7d687b260f565f509 GIT binary patch literal 10371 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