From: Ben Pfaff Date: Thu, 25 Dec 2025 21:18:42 +0000 (-0800) Subject: more legacy X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1109965e74e9f2695ae1a09ea24f138b34c2a40a;p=pspp more legacy --- 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 0000000000..11724ae6c9 Binary files /dev/null and b/rust/pspp/src/spv/testdata/legacy9.spv differ