user-specified Keeps. They seems to indicate a conversion from rows or
columns to pixel or point offsets.
-`notes` is a text string that contains user-specified notes. It is
-displayed when the user hovers the cursor over the table, like text in
-the `title` attribute in HTML. It is not printed. It is usually empty.
+<a name="notes">`notes`</a> is a text string that contains
+user-specified notes. It is displayed when the user hovers the cursor
+over the table, like text in the `title` attribute in HTML. It is not
+printed. It is usually empty. See also
+[`notes-unexpanded`](#notes-unexpanded).
`table-look` is the name of a SPSS "TableLook" table style, such as
"Default" or "Academic"; it is often empty.
Y0
CustomCurrency
count(
- v1(X0?)
- v3(count(X1 count(X2)) count(X3)))
+ v1(N0?)
+ v3(count(N1 count(N2)) count(N3)))
Y0 => int32[epoch] byte[decimal] byte[grouping]
CustomCurrency => int32[n-ccs] string*[n-ccs]
```
A writer may safely use false for `x7`, `x8`, and `x9`.
-### X0
+### N0
-`X0` only appears, optionally, in version 1 members.
+`N0` only appears, optionally, in version 1 members.
```
-X0 => byte*14 Y1 Y2
+N0 => byte*14 Y1 Y2
Y1 =>
string[command] string[command-local]
string[language] string[charset] string[locale]
A writer may safely use false for `x10` and `x17` and true for `x12`
and `x13`.
-### X1
+### N1
-`X1` only appears in version 3 members.
+`N1` only appears in version 3 members.
```
-X1 =>
+N1 =>
bool[x14]
byte[show-title]
bool[x16]
A writer may safely use false for `x14`, false for `x16`, 0 for
`lang`, -1 for `x18` and `x19`, and false for `x20`.
-### X2
+### N2
-`X2` only appears in version 3 members.
+`N2` only appears in version 3 members.
```
-X2 =>
+N2 =>
int32[n-row-heights] int32*[n-row-heights]
int32[n-style-map] StyleMap*[n-style-map]
int32[n-styles] StylePair*[n-styles]
If present, `n-row-heights` and the accompanying integers are row
heights as manually adjusted by the user.
-The rest of `X2` specifies styles for data cells. At first glance
+The rest of `N2` specifies styles for data cells. At first glance
this is odd, because each data cell can have its own style embedded as
-part of the data, but in practice `X2` specifies a style for a cell
+part of the data, but in practice `N2` specifies a style for a cell
only if that cell is empty (and thus does not appear in the data at
all). Each `StyleMap` specifies the index of a blank cell, calculated
the same was as in the [Cells](#cells), along with a 0-based index
A writer may safely omit the optional `i0 i0` inside the
`count(...)`.
-### X3
+### N3
-`X3` only appears in version 3 members.
+`N3` only appears in version 3 members.
```
-X3 =>
+N3 =>
01 00 byte[x21] 00 00 00
Y1
double[small] 01
- (string[dataset] string[datafile] i0 int32[date] i0)?
+ (string[dataset] string[datafile] string[notes-unexpanded] int32[date] i0)?
Y2
(int32[x22] i0 bool[x25]?)?
```
e.g. `DataSet1`, and `datafile` the name of the file it was read from,
e.g. `C:\Users\foo\bar.sav`. The latter is sometimes the empty string.
-`date` is a date, as seconds since the epoch, i.e. since January 1,
-1970. Pivot tables within an SPV file often have dates a few minutes
-apart, so this is probably a creation date for the table rather than for
-the file.
+<a name="notes-unexpanded">`notes-unexpanded`</a> is a text string
+that contains user-specified notes. It may contain special variables
+such as `)TITLE` that are expanded before the notes are displayed.
+See [`notes`](#notes) for the expanded version. This text string is
+often empty even if [`notes`](#notes) is nonempty; `notes-unexpanded`
+has only been observed to be nonempty when it contains variables.
+
+> `notes-unexpanded` could be used to allow the user to edit the notes
+in their original form, but to otherwise display them as expanded at
+the time the file was written. Note that the expansion might change
+if redone since variables can include the date and time, although the
+pivot table also includes [`date`](#date) for anchoring the date.
+
+<a name="date">`date`</a> is a date, as seconds since the epoch,
+i.e. since January 1, 1970. Pivot tables within an SPV file often
+have dates a few minutes apart, so this is probably a creation date
+for the table rather than for the file.
Sometimes `dataset`, `datafile`, and `date` are present and other
times they are absent. The reader can distinguish by assuming that they
- `locale` in `Formats` itself.
- `locale` in `Y1` (in version 1, `Y1` is optionally nested inside
-`X0`; in version 3, `Y1` is nested inside `X3`).
+`N0`; in version 3, `Y1` is nested inside `N3`).
- `charset` in version 3, in `Y1`.
-- `lang` in X1, in version 3.
+- `lang` in `N1`, in version 3.
`charset`, if present, is a good indication of character encoding, and
in its absence the encoding suffix on `locale` in `Formats` will work.
pub fn decode(&self) -> Result<PivotTable, LightError> {
let encoding = self.formats.encoding();
- let x1 = self.formats.x1();
- let x2 = self.formats.x2();
- let x3 = self.formats.x3();
- let x3_inner = x3.and_then(|x3| x3.inner.as_ref());
+ let n1 = self.formats.n1();
+ let n2 = self.formats.n2();
+ let n3 = self.formats.n3();
+ let n3_inner = n3.and_then(|n3| n3.inner.as_ref());
let y1 = self.formats.y1();
let footnotes = self
.footnotes
rotate_inner_column_labels: self.header.rotate_inner_column_labels,
rotate_outer_row_labels: self.header.rotate_outer_row_labels,
show_grid_lines: self.borders.show_grid_lines,
- show_title: x1.map_or(true, |x1| x1.show_title != 10),
- show_caption: x1.map_or(true, |x1| x1.show_caption),
- show_values: x1.map_or(None, |x1| x1.show_values),
- show_variables: x1.map_or(None, |x1| x1.show_variables),
+ show_title: n1.map_or(true, |x1| x1.show_title != 10),
+ show_caption: n1.map_or(true, |x1| x1.show_caption),
+ show_values: n1.map_or(None, |x1| x1.show_values),
+ show_variables: n1.map_or(None, |x1| x1.show_variables),
sizing: self.table_settings.sizing.decode(
&self.formats.column_widths,
- x2.map_or(&[], |x2| &x2.row_heights),
+ n2.map_or(&[], |x2| &x2.row_heights),
),
settings: Settings {
epoch: self.formats.y0.epoch(),
let grouping = self.formats.y0.grouping;
b",.' ".contains(&grouping).then_some(grouping as char)
},
- small: x3.map_or(0.0, |x3| x3.small),
+ small: n3.map_or(0.0, |n3| n3.small),
weight_format: Format::F40,
})
.with_metadata(PivotTableMetadata {
command_c: y1.map(|y1| y1.command.decode(encoding)),
language: y1.map(|y1| y1.language.decode(encoding)),
locale: y1.map(|y1| y1.locale.decode(encoding)),
- dataset: x3_inner.and_then(|strings| strings.dataset.decode_optional(encoding)),
- datafile: x3_inner.and_then(|strings| strings.datafile.decode_optional(encoding)),
- date: x3_inner.and_then(|inner| {
+ dataset: n3_inner.and_then(|strings| strings.dataset.decode_optional(encoding)),
+ datafile: n3_inner.and_then(|strings| strings.datafile.decode_optional(encoding)),
+ date: n3_inner.and_then(|inner| {
if inner.date != 0 {
DateTime::from_timestamp(inner.date as i64, 0).map(|dt| dt.naive_utc())
} else {
y0: Y0,
custom_currency: CustomCurrency,
#[br(if(version == Version::V1))]
- v1: Optional<Counted<X0>>,
+ v1: Optional<Counted<N0>>,
#[br(if(version == Version::V3))]
v3: Option<Counted<FormatsV3>>,
}
fn y1(&self) -> Option<&Y1> {
self.v1
.as_ref()
- .map(|x0| &x0.y1)
- .or_else(|| self.v3.as_ref().map(|v3| &v3.x3.y1))
+ .map(|n0| &n0.y1)
+ .or_else(|| self.v3.as_ref().map(|v3| &v3.n3.y1))
}
- fn x1(&self) -> Option<&X1> {
- self.v3.as_ref().map(|v3| &v3.x1_x2.x1)
+ fn n1(&self) -> Option<&N1> {
+ self.v3.as_ref().map(|v3| &v3.n1_n2.x1)
}
- fn x2(&self) -> Option<&X2> {
- self.v3.as_ref().map(|v3| &v3.x1_x2.x2)
+ fn n2(&self) -> Option<&N2> {
+ self.v3.as_ref().map(|v3| &v3.n1_n2.x2)
}
- fn x3(&self) -> Option<&X3> {
- self.v3.as_ref().map(|v3| &v3.x3)
+ fn n3(&self) -> Option<&N3> {
+ self.v3.as_ref().map(|v3| &v3.n3)
}
fn charset(&self) -> Option<&U32String> {
#[derive(Debug)]
struct FormatsV3 {
#[br(parse_with(parse_counted))]
- x1_x2: X1X2,
+ n1_n2: N1N2,
#[br(parse_with(parse_counted))]
- x3: X3,
+ n3: N3,
}
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X1X2 {
- x1: X1,
+struct N1N2 {
+ x1: N1,
#[br(parse_with(parse_counted))]
- x2: X2,
+ x2: N2,
}
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X0 {
+struct N0 {
#[br(temp)]
_bytes: [u8; 14],
y1: Y1,
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X1 {
+struct N1 {
#[br(temp, parse_with(parse_bool))]
_x14: bool,
show_title: u8,
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X2 {
+struct N2 {
#[br(parse_with(parse_vec))]
row_heights: Vec<i32>,
#[br(parse_with(parse_vec))]
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X3 {
+struct N3 {
#[br(temp, magic = b"\x01\0")]
_x21: u8,
#[br(magic = b"\0\0\0")]
small: f64,
#[br(magic = 1u8, temp)]
_one: (),
- inner: Optional<X3Inner>,
+ inner: Optional<N3Inner>,
y2: Y2,
#[br(temp)]
- _tail: Optional<X3Tail>,
+ _tail: Optional<N3Tail>,
}
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X3Inner {
+struct N3Inner {
dataset: U32String,
datafile: U32String,
- #[br(magic = 0u32)]
+ notes_unexpanded: U32String,
date: i32,
#[br(magic = 0u32, temp)]
_tail: (),
#[binread]
#[br(little)]
#[derive(Debug)]
-struct X3Tail {
+struct N3Tail {
#[br(temp)]
_x22: i32,
#[br(temp, assert(_zero == 0))]
) -> Result<Vec<(Axis3, pivot::Dimension)>, LightError> {
let n = self.layers.len() + self.rows.len() + self.columns.len();
if n != dimensions.len() {
- return Err(LightError::WrongAxisCount {
- expected: dimensions.len(),
- actual: n,
- n_layers: self.layers.len(),
- n_rows: self.rows.len(),
- n_columns: self.columns.len(),
- });
+ /* XXX warn
+ return Err(LightError::WrongAxisCount {
+ expected: dimensions.len(),
+ actual: n,
+ n_layers: self.layers.len(),
+ n_rows: self.rows.len(),
+ n_columns: self.columns.len(),
+ });*/
+ return Ok(dimensions
+ .into_iter()
+ .map(|dimension| (Axis3::Y, dimension))
+ .collect());
}
fn axis_dims(axis: Axis3, dimensions: &[u32]) -> impl Iterator<Item = (Axis3, usize)> {