};
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() {
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"))?;
)?;
}
- if let Some(layers) = output.layers {
+ if !output.layers.is_empty() {
writeln!(&mut self.writer, "<thead>")?;
- for cell in layers.cells() {
+ for layer in &output.layers {
writeln!(&mut self.writer, "<tr>")?;
- 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, "</tr>")?;
}
writeln!(&mut self.writer, "</thead>")?;
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) {
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.
-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;
}
}
- fn create_aux_table3<I>(&self, area: Area, rows: I) -> Table
+ fn create_aux_table<I>(&self, area: Area, axis: Axis2, cells: I) -> Table
where
I: Iterator<Item = Box<Value>> + 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),
);
}
I: Iterator<Item = Box<Value>> + ExactSizeIterator,
{
if rows.len() > 0 {
- Some(self.create_aux_table3(area, rows))
+ Some(self.create_aux_table(area, Axis2::Y, rows))
} else {
None
}
/// Constructs a [Table] for this `PivotTable`'s title. Returns `None` if
/// the table doesn't have a title.
pub fn output_title(&self) -> Option<Table> {
- 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<Table> {
- 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<Table> {
+ 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<Table> {
- Some(self.create_aux_table3(
+ Some(self.create_aux_table(
Area::Caption,
+ Axis2::Y,
[self.metadata.caption.as_ref()?.clone()].into_iter(),
))
}
.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,
pub struct OutputTables {
/// Title table, if any.
pub title: Option<Table>,
- /// Layers table, if any.
- pub layers: Option<Table>,
+ /// Layers tables, if any.
+ pub layers: Vec<Table>,
/// Table body.
pub body: Table,
/// Table caption, if any.
Column (All Layers)
-b: b1
+b: b1
╭──┬──┬──╮
│a1│a2│a3│
├──┼──┼──┤
╰──┴──┴──╯
Column (All Layers)
-b: b2
+b: b2
╭──┬──┬──╮
│a1│a2│a3│
├──┼──┼──┤
╰──┴──┴──╯
Column (All Layers)
-b: b3
+b: b3
╭──┬──┬──╮
│a1│a2│a3│
├──┼──┼──┤
Column x b1
-b: b1
+b: b1
╭──┬──┬──╮
│a1│a2│a3│
├──┼──┼──┤
Column x b2
-b: b2
+b: b2
╭──┬──┬──╮
│a1│a2│a3│
├──┼──┼──┤
Row (All Layers)
-b: b1
+b: b1
╭──┬─╮
│a1│0│
│a2│1│
╰──┴─╯
Row (All Layers)
-b: b2
+b: b2
╭──┬─╮
│a1│3│
│a2│4│
╰──┴─╯
Row (All Layers)
-b: b3
+b: b3
╭──┬─╮
│a1│6│
│a2│7│
Row x b1
-b: b1
+b: b1
╭──┬─╮
│a1│0│
│a2│1│
Row x b2
-b: b2
+b: b2
╭──┬─╮
│a1│3│
│a2│4│
Column x b1 x a1
-b: b1
-a: a1
+b: b1
+a: a1
╭──┬──┬──┬──┬──╮
│c1│c2│c3│c4│c5│
├──┼──┼──┼──┼──┤
Column x b2 x a1
-b: b2
-a: a1
+b: b2
+a: a1
╭──┬──┬──┬──┬──╮
│c1│c2│c3│c4│c5│
├──┼──┼──┼──┼──┤
Column x b3 x a2
-b: b3
-a: a2
+b: b3
+a: a2
╭──┬──┬──┬──┬──╮
│c1│c2│c3│c4│c5│
├──┼──┼──┼──┼──┤
/// Constructs a new text `Value` from `s`, which should have been provided
/// by the user.
pub fn new_user_text(s: impl Into<String>) -> 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`.
self
}
+ pub fn with_footnotes<'a>(
+ mut self,
+ footnotes: impl IntoIterator<Item = &'a Arc<Footnote>>,
+ ) -> Self {
+ for footnote in footnotes {
+ self.add_footnote(footnote);
+ }
+ self
+ }
+
/// Adds `footnote` to this `Value`.
pub fn add_footnote(&mut self, footnote: &Arc<Footnote>) {
let footnotes = &mut self.styling_mut().footnotes;
footnotes.sort_by_key(|f| f.index);
}
+ pub fn with_subscripts<'a>(
+ mut self,
+ subscripts: impl IntoIterator<Item = impl Into<String>>,
+ ) -> Self {
+ for subscript in subscripts {
+ self.add_subscript(subscript);
+ }
+ self
+ }
+
+ pub fn with_subscript(mut self, subscript: impl Into<String>) -> Self {
+ self.add_subscript(subscript);
+ self
+ }
+
+ pub fn add_subscript(&mut self, subscript: impl Into<String>) {
+ 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<Show>) -> Self {
/// 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())
}
show_label,
}
}
+
+ pub fn new_user_text(s: impl Into<String>) -> 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].
/// 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<isize>, look: &Look) -> Self {
use Axis2::*;
use Extreme::*;
);
}
}
- if min_width > 0 {
+ if let Some(min_width) = min_width
+ && min_width > 0
+ {
for ext in [Min, Max] {
distribute_spanned_width(
min_width,
/// 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<isize>, 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()
// 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]
};
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();
Ok(result) => result,
Err(error) => panic!("{error:?}"),
};
- dbg!(&self.table_properties);
let pivot_table = visualization.decode(
data,
self.table_properties
}
}
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())
{
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),
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),
{
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)),
&& 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))] =
for part in s.split(',') {
if let Ok(index) = part.parse::<usize>()
&& let Some(index) = index.checked_sub(1)
- && let Some(footnote) = dbg!(footnotes.get(index))
+ && let Some(footnote) = footnotes.get(index)
{
value.add_footnote(footnote);
}
.collect::<Vec<_>>();
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);
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;
+ }
}
}
}
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)
Statistics
-Variables: Finished
+Variables: Finished
╭─────────┬───╮
│N Valid │159│
│ Missing│ 0│
Notes
-Contents: Weight
+Contents: Weight
──────
<none>
──────
--- /dev/null
+ 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.