Figure some details of categories and dimensions.
[pspp] / spv-file-format.texi
index 422eeb5a82b8a6ec0222d551bb200b2f89fcfa4c..0fe2865a60af3bbba5244c47a690d553d4d469bf 100644 (file)
@@ -488,34 +488,46 @@ across multiple lines.  Break points are chosen for aesthetics only
 and have no semantic significance.
 
 @item 00, 01, @dots{}, ff.
-Bytes with fixed values are written in hexadecimal:
+A bytes with a fixed value, written as a pair of hexadecimal digits.
 
 @item i0, i1, @dots{}, i9, i10, i11, @dots{}
-32-bit integers with fixed values are written in decimal, prefixed by
+@itemx b0, b1, @dots{}, b9, b10, b11, @dots{}
+A 32-bit integer in little-endian or big-endian byte order,
+respectively, with a fixed value, written in decimal, prefixed by
 @samp{i}.
 
 @item byte
-An arbitrary byte.
+A byte.
 
 @item bool
 A byte with value 0 or 1.
 
 @item int16
-An arbitrary 16-bit integer.
+@itemx be16
+A 16-bit integer in little-endian or big-endian byte order,
+respectively.
 
 @item int
-An arbitrary 32-bit integer.
+@itemx be32
+A 32-bit integer in little-endian or big-endian byte order,
+respectively.
+
+@item int64
+@itemx be64
+A 64-bit integer in little-endian or big-endian byte order,
+respectively.
 
 @item double
-An arbitrary 64-bit IEEE floating-point number.
+A 64-bit IEEE floating-point number.
 
 @item float
-An arbitrary 32-bit IEEE floating-point number.
+A 32-bit IEEE floating-point number.
 
 @item string
-A 32-bit integer followed by the specified number of bytes of
-character data.  (The encoding is indicated by the Formats
-nonterminal.)
+@itemx bestring
+A 32-bit integer, in little-endian or big-endian byte order,
+respectively, followed by the specified number of bytes of character
+data.  (The encoding is indicated by the Formats nonterminal.)
 
 @item @var{x}?
 @var{x} is optional, e.g.@: 00? is an optional zero byte.
@@ -549,15 +561,20 @@ In a version 1 @file{.bin} member, @var{x}; in version 3, nothing.
 In a version 3 @file{.bin} member, @var{x}; in version 1, nothing.
 @end table
 
-All integer and floating-point values in this format use little-endian
-byte order.
+Little-endian byte order is far more common in this format, but a few
+pieces of the format use big-endian byte order.
 
 A ``light'' detail member @file{.bin} consists of a number of sections
 concatenated together, terminated by a byte 01:
 
 @cartouche
 @format
-LightMember @result{} Header Title Caption Footnotes Fonts Formats Dimensions Data 01
+LightMember @result{}
+    Header Title
+    Caption Footnotes
+    Fonts Borders PrintSettings TableSettings Formats
+    Dimensions Data
+    01
 @end format
 @end cartouche
 
@@ -566,9 +583,12 @@ The following sections go into more detail.
 @menu
 * SPV Light Member Header::
 * SPV Light Member Title::
-* PSV Light Member Caption::
+* SPV Light Member Caption::
 * SPV Light Member Footnotes::
 * SPV Light Member Fonts::
+* SPV Light Member Borders::
+* SPV Light Member Print Settings::
+* SPV Light Member Table Settings::
 * SPV Light Member Formats::
 * SPV Light Member Dimensions::
 * SPV Light Member Categories::
@@ -580,15 +600,22 @@ The following sections go into more detail.
 @node SPV Light Member Header
 @subsection Header
 
-An SPV file begins with an 39-byte header:
+An SPV light member begins with a 39-byte header:
 
 @cartouche
 @format
 Header @result{}
     01 00
     (i1 @math{|} i3)[@t{version}]
-    01 (00 @math{|} 01) byte*21 00 00
-    int[@t{table-id}] byte*4
+    bool
+    bool[@t{show-numeric-markers}]
+    bool[@t{rotate-inner-column-labels}]
+    bool[@t{rotate-outer-row-labels}]
+    bool
+    int
+    int[@t{min-column-width}] int[@t{max-column-width}]
+    int[@t{min-row-width}] int[@t{max-row-width}]
+    int64[@t{table-id}]
 @end format
 @end cartouche
 
@@ -597,10 +624,29 @@ some of the other data in the member.  We will refer to ``version 1''
 and ``version 3'' later on and use v1(@dots{}) and v3(@dots{}) for
 version-specific formatting (as described previously).
 
+If @code{show-numeric-markers} is 1, footnote markers are shown as
+numbers, starting from 1; otherwise, they are shown as letters,
+starting from @samp{a}.
+
+If @code{rotate-inner-column-labels} is 1, then column labels closest
+to the data are rotated to be vertical; otherwise, they are shown
+in the normal way.
+
+If @code{rotate-outer-row-labels} is 1, then row labels farthest from
+the data are rotated to be vertical; otherwise, they are shown in the
+normal way.
+
 @code{table-id} is a binary version of the @code{tableId} attribute in
 the structure member that refers to the detail member.  For example,
-if @code{tableId} is @code{-4154297861994971133}, then @code{table-id}
-would be 0xdca00003.
+if @code{tableId} is @code{-4122591256483201023}, then @code{table-id}
+would be 0xc6c99d183b300001.
+
+@code{min-column-width} is the minimum width that a column will be
+assigned automatically.  @code{max-column-width} is the maximum width
+that a column will be assigned to accommodate a long column label.
+@code{min-row-width} and @code{max-row-width} are a similar range for
+the width of row labels.  All of these measurements are in 1/96 inch
+units.
 
 The meaning of the other variable parts of the header is not known.
 
@@ -626,7 +672,7 @@ appropriate for presentation, and localized to the user's language,
 well formatted.  For example, for a frequency table, @code{title1} and
 @code{title2} name the variable and @code{c} is simply ``Frequencies''.
 
-@node PSV Light Member Caption
+@node SPV Light Member Caption
 @subsection Caption
 
 @cartouche
@@ -705,111 +751,37 @@ background color, respectively.  In the corpus, these are always
 should be the same color.  When @code{alternate} is 01, @code{altfg}
 and @code{altbg} specify the colors for the alternate rows.
 
-The meaning of the remaining data is unknown.  It seems likely to
-include font sizes, attributes such as bold or italic, and margins.
-
-The table below lists the values observed in the corpus.  When a cell
-contains a single value, then 99@math{+}% of the corpus contains that value.
-When a cell contains a pair of values, then the first value is seen in
-about two-thirds of the corpus and the second value in about the
-remaining one-third.  In fonts that include multiple pairs, values are
-correlated, that is, for font 3, f5 = 24, f6 = 24, f7 = 2 appears
-about two-thirds of the time, as does the combination of f4 = 0, f6 =
-10 for font 7.
-
-@multitable {font} {40} {f2} {64173} {0/1} {24/11} {10/11} {2/3} {f8}
-@headitem font @tab f1 @tab f2 @tab f3 @tab f4 @tab f5 @tab f6 @tab f7 @tab f8
-@item 1 @tab 40 @tab 1 @tab     0 @tab   0 @tab 8 @tab 10/11 @tab   1 @tab 8
-@item 2 @tab 40 @tab 0 @tab     2 @tab   1 @tab 8 @tab 10/11 @tab   1 @tab 1
-@item 3 @tab 40 @tab 0 @tab     2 @tab 1 @tab 24/11 @tab 24/ 8 @tab 2/3 @tab 4
-@item 4 @tab 40 @tab 0 @tab     2 @tab   3 @tab 8 @tab 10/11 @tab   1 @tab 1
-@item 5 @tab 40 @tab 0 @tab     0 @tab   1 @tab 8 @tab 10/11 @tab   1 @tab 4
-@item 6 @tab 40 @tab 0 @tab     2 @tab   1 @tab 8 @tab 10/11 @tab   1 @tab 4
-@item 7 @tab 40 @tab 0 @tab 64173 @tab 0/1 @tab 8 @tab 10/11 @tab   1 @tab 1
-@item 8 @tab 40 @tab 0 @tab     2 @tab   3 @tab 8 @tab 10/11 @tab   1  @tab 4
-@end multitable
+@code{left-margin}, @code{right-margin}, @code{top-margin}, and
+@code{bottom-margin} are measured in multiples of 1/96 inch.
 
-@node SPV Light Member Formats
-@subsection Formats
+@node SPV Light Member Borders
+@subsection Borders
 
 @cartouche
 @format
-Formats @result{}
-    Borders 
-    PrintSettings
-    TableSettings
-    int[@t{n4}] int*[@t{n4}]
-    string[@t{encoding}]
-    (i0 @math{|} i-1) (00 @math{|} 01) 00 (00 @math{|} 01)
-    int
-    byte[@t{decimal}] byte[@t{grouping}]
-    int[@t{n-ccs}] string*[@t{n-ccs}]
-    v1(i0)
-    v3(count(count(X5) count(X6)))
-
 Borders @result{}
-    int[@t{endian}]
-    int[@t[n-borders}] Border*[@t{n-borders}]
+    b1[@t{endian}]
+    be32[@t{n-borders}] Border*[@t{n-borders}]
     bool[@t{show-grid-lines}]
     00 00 00
 
 Border @result{}
-    int[@t{border-type}]
-    int[@t{stroke-type}]
-    int[@t{color}]
-
-PrintSettings @result{}
-    int[@t{endian}]
-    bool[@t{all-layers}]
-    bool[@t{new-layers}]
-    bool[@t{fit-width}]
-    bool[@t{fit-length}]
-    bool[@t{top-continuation}]
-    bool[@t{bottom-continuation}]
-    int[@t{n-orphan-lines}]
-    string[@t{continuation-string}]
-
-TableSettings @result{}
-    int[@t{endian}]
-    int
-    int[@t{current-layer}]
-    bool[@t{skip-empty}]
-    bool[@t{show-dimension-in-corner}]
-    bool[@t{use-alphabetic-markers}]
-    bool[@t{footnote-marker-position}]
-    v3(
-      byte
-      int[@t{n}] byte*[@t{n}]
-      string
-      string[@t{table-look}]
-      00...
-    )
-
-X5 @result{} byte*33 int[@t{n}] int*[@t{n}]
-X6 @result{}
-    01 00 (03 @math{|} 04) 00 00 00
-    string[@t{command}] string[@t{subcommand}]
-    string[@t{language}] string[@t{charset}] string[@t{locale}]
-    (00 @math{|} 01) 00 (00 @math{|} 01) (00 @math{|} 01)
-    int
-    byte[@t{decimal}] byte[@t{grouping}]
-    byte*8 01
-    (string[@t{dataset}] string[@t{data file}] i0 int i0)?
-    int[@t{n-ccs}] string*[@t{n-ccs}]
-    2e (00 @math{|} 01) (i2000000 i0)?
+    be32[@t{border-type}]
+    be32[@t{stroke-type}]
+    be32[@t{color}]
 @end format
 @end cartouche
 
-The Borders reflect how borders between regions are drawn.  If
-@code{endian} is 1, then values inside Borders, including
-@code{endian} itself, are big-endian, otherwise they are
-little-endian.  In practice, they seem to always be big-endian, even
-though the rest of the file is little-endian.  @code{n-borders} seems
-to always be 19.  @code{show-grid-lines} is 1 to draw grid lines,
-otherwise 0.
+The Borders reflect how borders between regions are drawn.
+
+The fixed value of @code{endian} can be used to validate the
+endianness.
+
+@code{show-grid-lines} is 1 to draw grid lines, otherwise 0.
 
-Each Border describes one kind of border.  Each @code{border-type}
-appears once in order, and they correspond to the following borders:
+Each Border describes one kind of border.  @code{n-borders} seems to
+always be 19.  Each @code{border-type} appears once (although in an
+unpredictable order) and correspond to the following borders:
 
 @table @asis
 @item 0
@@ -851,28 +823,195 @@ Double line.
 red, 8--15 are green, 0--7 are blue.  An alpha of 255 indicates an
 opaque color, therefore opaque black is 0xff000000.
 
-The PrintSettings reflect settings for printing.  Like Borders, they
-have independent endianness.  The @code{continuation-string} is
-usually empty but it may contain a text string such as ``(cont.)''.
-
-The TableSettings reflect display settings.  Like Borders, they
-have independent endianness.  @code{current-layer} is the displayed
-layer.  @code{use-alphabetic-markers} is 1 to show markers as letters
-(e.g. @samp{a}, @samp{b}, @samp{c}, @dots{}), otherwise they are shown
-as numbers starting from 1.  When @code{footnote-marker-position} is
-1, footnote markers are shown as superscripts, otherwise as
-subscripts.  @code{table-look} is the name of a SPSS ``TableLook''
-table style, such as ``Default'' or ``Academic''; it is often empty.
+@node SPV Light Member Print Settings
+@subsection Print Settings
+
+@cartouche
+@format
+PrintSettings @result{}
+    b1[@t{endian}]
+    bool[@t{all-layers}]
+    bool[@t{paginate-layers}]
+    bool[@t{fit-width}]
+    bool[@t{fit-length}]
+    bool[@t{top-continuation}]
+    bool[@t{bottom-continuation}]
+    be32[@t{n-orphan-lines}]
+    bestring[@t{continuation-string}]
+@end format
+@end cartouche
+
+The PrintSettings reflect settings for printing.  The fixed value of
+@code{endian} can be used to validate the endianness.
+
+@code{all-layers} is 1 to print all layers, 0 to print only the
+visible layers.
+
+@code{paginate-layers} is 1 to print each layer at the start of a new
+page, 0 otherwise.  (This setting is honored only @code{all-layers} is
+1, since otherwise only one layer is printed.)
+
+@code{fit-width} and @code{fit-length} control whether the table is
+shrunk to fit within a page's width or length, respectively.
+
+@code{n-orphan-lines} is the minimum number of rows or columns to put
+in one part of a table that is broken across pages.
+
+If @code{top-continuation} is 1, then @code{continuation-string} is
+printed at the top of a page when a table is broken across pages for
+printing; similarly for @code{bottom-continuation} and the bottom of a
+page.  Usually, @code{continuation-string} is empty.
+
+@node SPV Light Member Table Settings
+@subsection Table Settings
+
+@cartouche
+@format
+TableSettings @result{}
+    be32[@t{endian}]
+    be32
+    be32[@t{current-layer}]
+    bool[@t{omit-empty}]
+    bool[@t{show-row-labels-in-corner}]
+    bool[@t{show-alphabetic-markers}]
+    bool[@t{footnote-marker-position}]
+    v3(
+      byte
+      count(
+        Breakpoints[@t{row-breaks}] Breakpoints[@t{column-breaks}]
+        Keeps[@t{row-keeps}] Keeps[@t{column-keeps}]
+        PointKeeps[@t{row-keeps}] PointKeeps[@t{column-keeps}]
+      )
+      bestring[@t{notes}]
+      bestring[@t{table-look}]
+      00...
+    )
+
+Breakpoints @result{} be32[@t{n-breaks}] be32*[@t{n-breaks}]
+
+Keeps @result{} be32[@t{n-keeps}] Keep*@t{n-keeps}
+Keep @result{} be32[@t{offset}] be[@t{n}]
+
+PointKeeps @result{} be32[@t{n-point-keeps}] PointKeep*@t{n-point-keeps}
+PointKeep @result{} be32[@t{offset}] be32 be32
+
+@end format
+@end cartouche
+
+The TableSettings reflect display settings.  The fixed value of
+@code{endian} can be used to validate the endianness.
+
+@code{current-layer} is the displayed layer.
+
+If @code{omit-empty} is 1, empty rows or columns (ones with nothing in
+any cell) are hidden; otherwise, they are shown.
+
+If @code{show-row-labels-in-corner} is 1, then row labels are shown in
+the upper left corner; otherwise, they are shown nested.
+
+If @code{show-alphabetic-markers} is 1, markers are shown as letters
+(e.g. @samp{a}, @samp{b}, @samp{c}, @dots{}); otherwise, they are
+shown as numbers starting from 1.
+
+When @code{footnote-marker-position} is 1, footnote markers are shown
+as superscripts, otherwise as subscripts.
+
+The Breakpoints are rows or columns after which there is a page break;
+for example, a row break of 1 requests a page break after the second
+row.  Usually no breakpoints are specified, indicating that page
+breaks should be selected automatically.
+
+The Keeps are ranges of rows or columns to be kept together without a
+page break; for example, a row Keep with @code{offset} 1 and @code{n}
+10 requests that the 10 rows starting with the second row be kept
+together.  Usually no Keeps are specified.
+
+The PointKeeps seem to be generated automatically based on
+user-specified Keeps.  They seems to indicate a conversion from rows
+or columns to pixel or point offsets.
+
+@code{notes} is a text string that contains user-specified notes.  It
+is displayed when the user hovers the cursor over the table, like
+``alt text'' on a webpage.  It is not printed.  It is usually empty.
+
+@code{table-look} is the name of a SPSS ``TableLook'' table style,
+such as ``Default'' or ``Academic''; it is often empty.
+
 TableSettings ends with an arbitrary number of null bytes.
 
-Observed values of @code{n4} vary from 0 to 17.  Out of 7,060 examples
-in the corpus, it is nonzero only 36 times.
+@node SPV Light Member Formats
+@subsection Formats
+
+@cartouche
+@format
+Formats @result{}
+    int[@t{n-widths}] int*[@t{n-widths}]
+    string[@t{encoding}]
+    int[@t{current-layer}]
+    bool[@t{digit-grouping}] bool[@t{leading-zero}] bool
+    int[@t{epoch}]
+    byte[@t{decimal}] byte[@t{grouping}]
+    CustomCurrency
+    count(
+      v1(X0?)
+      v3(count(X1 count(X2)) count(X3))
+
+X0 @result{}
+    byte*14
+    string[@t{command}] string[@t{command-local}]
+    string[@t{language}] string[@t{charset}] string[@t{locale}]
+    bool 00 bool bool
+    int[@t{epoch}]
+    byte[@t{decimal}] byte[@t{grouping}]
+    CustomCurrency
+    byte[@t{missing}] bool
+
+X1 @result{}
+    byte*2
+    byte[@t{lang}]
+    byte[@t{variable-mode}]
+    byte[@t{value-mode}]
+    int*2
+    00*17
+    bool
+    01
+X2 @result{}
+    int[@t{n-heights}] int*[@t{n-heights}]
+    int[@t{n-style-map}] BlankMap*[@t{n-style-map}]
+    int[@t{n-styles}] StylePair*[@t{n-styles}]
+    count((i0 i0)?)
+StyleMap @result{} int64[@t{cell-index}] int16[@t{style-index}]
+X3 @result{}
+    01 00 (03 @math{|} 04) 00 00 00
+    string[@t{command}] string[@t{command-local}]
+    string[@t{language}] string[@t{charset}] string[@t{locale}]
+    bool 00 bool bool
+    int[@t{epoch}]
+    byte[@t{decimal}] byte[@t{grouping}]
+    double[@t{small}] 01
+    (string[@t{dataset}] string[@t{datafile}] i0 int[@t{date}] i0)?
+    CustomCurrency
+    byte[@t{missing}] bool (i2000000 i0)?
+
+CustomCurrency @result{} int[@t{n-ccs}] string*[@t{n-ccs}]
+@end format
+@end cartouche
+
+If @code{n-widths} is nonzero, then the accompanying integers are
+column widths as manually adjusted by the user.  (Row heights are
+computed automatically based on the widths.)
 
 @code{encoding} is a character encoding, usually a Windows code page
 such as @code{en_US.windows-1252} or @code{it_IT.windows-1252}.  The
 rest of the character strings in the member use this encoding.  The
 encoding string is itself encoded in US-ASCII.
 
+@code{epoch} is the year that starts the epoch.  A 2-digit year is
+interpreted as belonging to the 100 years beginning at the epoch.  The
+default epoch year is 69 years prior to the current year; thus, in
+2017 this field by default contains 1948.  In the corpus, @code{epoch}
+ranges from 1943 to 1948, plus some contain -1.
+
 @code{decimal} is the decimal point character.  The observed values
 are @samp{.} and @samp{,}.
 
@@ -881,11 +1020,37 @@ are @samp{.} and @samp{,}.
 @samp{'} (apostrophe), @samp{ } (space), and zero (presumably
 indicating that digits should not be grouped).
 
+@code{command} describes the statistical procedure that generated the
+output, in English.  It is not necessarily the literal syntax name of
+the procedure: for example, NPAR TESTS becomes ``Nonparametric
+Tests.''  @code{command-local} is the procedure's name, translated
+into the output language; it is often empty and, when it is not,
+sometimes the same as @code{command}.
+
+@code{dataset} is the name of the dataset analyzed to produce the
+output, e.g.@: @code{DataSet1}, and @code{datafile} the name of the
+file it was read from, e.g.@: @file{C:\Users\foo\bar.sav}.  The latter
+is sometimes the empty string.
+
+@code{date} is a date, as seconds since the epoch, i.e.@: since
+January 1, 1970.  Pivot tables within an SPV files often have dates a
+few minutes apart, so this is probably a creation date for the tables
+rather than for the file.
+
+Sometimes @code{dataset}, @code{datafile}, and @code{date} are present
+and other times they are absent.  The reader can distinguish by
+assuming that they are present and then checking whether the
+presumptive @code{dataset} contains a null byte (a valid string never
+will).
+
 @code{n-ccs} is observed as either 0 or 5.  When it is 5, the
 following strings are CCA through CCE format strings.  @xref{Custom
 Currency Formats,,, pspp, PSPP}.  Most commonly these are all
 @code{-,,,} but other strings occur.
 
+@code{missing} is the character used to indicate that a cell contains
+a missing value.  It is always observed as @samp{.}.
+
 @node SPV Light Member Dimensions
 @subsection Dimensions
 
@@ -895,30 +1060,34 @@ the categories associated with each dimension.
 @cartouche
 @format
 Dimensions @result{} int[@t{n-dims}] Dimension*[@t{n-dims}]
-Dimension @result{} Value[@t{name}] DimUnknown int[@t{n-categories}] Category*[@t{n-categories}]
-DimUnknown @result{}
+Dimension @result{} Value[@t{name}] DimProperties int[@t{n-categories}] Category*[@t{n-categories}]
+DimProperties @result{}
     byte[@t{d1}]
     (00 @math{|} 01 @math{|} 02)[@t{d2}]
     (i0 @math{|} i2)[@t{d3}]
-    (00 @math{|} 01)[@t{d4}]
-    (00 @math{|} 01)[@t{d5}]
-    01
-    int[@t{d6}]
+    bool[@t{show-dim-label}]
+    bool[@t{hide-all-labels}]
+    01 int[@t{dim-index}]
 @end format
 @end cartouche
 
 @code{name} is the name of the dimension, e.g. @code{Variables},
 @code{Statistics}, or a variable name.
 
+The meanings of @code{d1}, @code{d2}, and @code{d3} are unknown.
 @code{d1} is usually 0 but many other values have been observed.
 
-@code{d3} is 2 over 99% of the time.
+If @code{show-dim-label} is 01, the pivot table displays a label for
+the dimension itself.  Because usually the group and category labels
+are enough explanation, it is usually 00.
 
-@code{d5} is 0 over 99% of the time.
+If @code{hide-all-labels} is 01, the pivot table omits all labels for
+the dimension, including group and category labels.  It is usually 00.
+When @code{hide-all-labels} is 01, @code{show-dim-label} is ignored.
 
-@code{d6} is either -1 or the 0-based index of the dimension, e.g.@: 0
-for the first dimension, 1 for the second, and so on.  The latter is
-the case 98% of the time in the corpus.
+@code{dim-index} is usually the 0-based index of the dimension, e.g.@:
+0 for the first dimension, 1 for the second, and so on.  Sometimes it
+is -1.  There is no visible difference.
 
 @node SPV Light Member Categories
 @subsection Categories
@@ -929,23 +1098,26 @@ are really categories; the others just serve as grouping constructs.
 @cartouche
 @format
 Category @result{} Value[@t{name}] (Leaf @math{|} Group)
-Leaf @result{} 00 00 00 i2 int[@t{index}] i0
+Leaf @result{} 00 00 00 i2 int[@t{cat-index}] i0
 Group @result{}
-    (00 @math{|} 01)[@t{merge}] 00 01 (i0 @math{|} i2)[@t{data}]
+    bool[@t{merge}] 00 01 (i0 @math{|} i2)[@t{data}]
     i-1 int[@t{n-subcategories}] Category*[@t{n-subcategories}]
 @end format
 @end cartouche
 
 @code{name} is the name of the category (or group).
 
-A Leaf represents a leaf category.  The Leaf's @code{index} is a
+A Leaf represents a leaf category.  The Leaf's @code{cat-index} is a
 nonnegative integer less than @code{n-categories} in the Dimension in
-which the Category is nested (directly or indirectly).
+which the Category is nested (directly or indirectly).  These
+categories represent the original order in which the categories were
+sorted; if the user sorted or rearranged the categories, then the
+order of categories in the file reflects that without changing the
+@code{cat-index} values.
 
-A Group represents a Group of nested categories.  Usually a Group
-contains at least one Category, so that @code{n-subcategories} is
-positive, but a few Groups with @code{n-subcategories} 0 has been
-observed.
+A Group is a group of nested categories.  Usually a Group contains at
+least one Category, so that @code{n-subcategories} is positive, but a
+few Groups with @code{n-subcategories} 0 has been observed.
 
 If a Group's @code{merge} is 00, the most common value, then the group
 is really a distinct group that should be represented as such in the
@@ -971,23 +1143,23 @@ The final part of an SPV light member contains the actual data.
 Data @result{}
     int[@t{layers}] int[@t{rows}] int[@t{columns}] int*[@t{n-dimensions}]
     int[@t{n-data}] Datum*[@t{n-data}]
-Datum @result{} int64[@t{index}] v3(00?) Value
+Datum @result{} int64[@t{index}] v1(00?) Value
 @end format
 @end cartouche
 
-The values of @code{layers}, @code{rows}, and @code{columns} each
-specifies the number of dimensions displayed in layers, rows, and
+The values of @code{n-layers}, @code{n-rows}, and @code{n-columns}
+each specifies the number of dimensions displayed in layers, rows, and
 columns, respectively.  Any of them may be zero.  Their values sum to
 @code{n-dimensions} from Dimensions (@pxref{SPV Light Member
 Dimensions}).
 
 The @code{n-dimensions} integers are a permutation of the 0-based
-dimension numbers.  The first @code{layers} integers specify each of
-the dimensions represented by layers, the next @code{rows} integers
+dimension numbers.  The first @code{n-layers} integers specify each of
+the dimensions represented by layers, the next @code{n-rows} integers
 specify the dimensions represented by rows, and the final
-@code{columns} integers specify the dimensions represented by columns.
-When there is more than one dimension of a given kind, the inner
-dimensions are given first.
+@code{n-columns} integers specify the dimensions represented by
+columns.  When there is more than one dimension of a given kind, the
+inner dimensions are given first.
 
 The format of a Datum varies slightly from version 1 to version 3: in
 version 1 it allows for an extra optional 00 byte.
@@ -1007,6 +1179,7 @@ for each @math{i} from 0 to @math{d - 1}:
 For example, suppose there are 3 dimensions with 3, 4, and 5
 categories, respectively.  The datum at coordinates (1, 2, 3) has
 index @math{5 \times (4 \times (3 \times 0 + 1) + 2) + 3 = 33}.
+Within a given dimension, the index is the @code{cat-index} in a Leaf.
 
 @node SPV Light Member Value
 @subsection Value
@@ -1021,7 +1194,7 @@ RawValue @result{}
     01 ValueMod int[@t{format}] double[@t{x}]
   @math{|} 02 ValueMod int[@t{format}] double[@t{x}]
     string[@t{varname}] string[@t{vallab}] (01 @math{|} 02 @math{|} 03)
-  @math{|} 03 string[@t{local}] ValueMod string[@t{id}] string[@t{c}] (00 @math{|} 01)[@t{type}]
+  @math{|} 03 string[@t{local}] ValueMod string[@t{id}] string[@t{c}] bool[@t{type}]
   @math{|} 04 ValueMod int[@t{format}] string[@t{vallab}] string[@t{varname}]
     (01 @math{|} 02 @math{|} 03) string[@t{s}]
   @math{|} 05 ValueMod string[@t{varname}] string[@t{varlabel}] (01 @math{|} 02 @math{|} 03)
@@ -1176,13 +1349,26 @@ A ValueMod can specify special modifications to a Value.
 ValueMod @result{}
     31 i0 (i0 @math{|} i1 string[@t{subscript}])
     v1(00 (i1 @math{|} i2) 00 00 int 00 00)
-    v3(count(FormatString Style ValueModUnknown))
+    v3(count(FormatString StylePair))
   @math{|} 31 int[@t{n-refs}] int16*[@t{n-refs}] Format
   @math{|} 58
-Style @result{} 58 @math{|} 31 01? 00? 00? 00? 01 string[@t{fgcolor}] string[@t{bgcolor}] string[@t{typeface}] byte
+
 Format @result{} 00 00 count(FormatString Style 58)
-FormatString @result{} count((i0 (58 @math{|} 31 string))?)
-ValueModUnknown @result{} 58 @math{|} 31 i0 i0 i0 i0 01 00 (01 @math{|} 02 @math{|} 08) 00 08 00 0a 00)
+FormatString @result{} count((count((i0 58)?) (58 @math{|} 31 string))?)
+
+StylePair @result{}
+    (31 Style | 58)
+    (31 Style2 | 58)
+
+Style @result{}
+    bool[@t{bold}] bool[@t{italic}] bool[@t{underline}] bool[@t{show}]
+    string[@t{fgcolor}] string[@t{bgcolor}]
+    string[@t{typeface}] byte[@t{size}]
+
+Style2 @result{}
+    int[@t{halign}] int[@t{valign}] double[@t{offset}]
+    int16[@t{left-margin}] int16[@t{right-margin}]
+    int16[@t{top-margin}] int16[@t{bottom-margin}]
 @end format
 @end cartouche
 
@@ -1202,7 +1388,22 @@ syntax explained previously.  It appears to be an English-language
 version of the localized format string in the Value in which the
 Format is nested.
 
-The Style, if present, changes the style for this individual Value.
+Style and Style2, if present, change the style for this individual
+Value.  @code{bold}, @code{italic}, and @code{underline} control the
+particular style.  @code{fgcolor} and @code{bgcolor} are strings, such
+as @code{#ffffff}.  The @code{size} is a font size in units of 1/96
+inch.
+
+@code{halign} is 0 for center, 2 for left, 4 for right, 6 for decimal,
+0xffffffad for mixed.  For decimal alignment, @code{offset} is the
+decimal point's offset from the right side of the cell, in units of
+1/72 inch.
+
+@code{valign} specifies vertical alignment: 0 for center, 1 for top, 3
+for bottom.
+
+@code{left-margin}, @code{right-margin}, @code{top-margin}, and
+@code{bottom-margin} are in units of 1/72 inch.
 
 @node SPV Legacy Detail Member Binary Format
 @section Legacy Detail Member Binary Format