Update structure member descriptions.
[pspp] / spv-file-format.texi
index 192c1c3ea1384534518ece495cbbb3536463cf56..38059ba889c5878c7a39af61f9e935ce87c0cb0e 100644 (file)
@@ -80,13 +80,18 @@ their exact names do not matter to readers as long as they are unique.
 @node SPV Structure Member Format
 @section Structure Member Format
 
+A structure member lays out the high-level structure for a group of
+output items such as heading, tables, and charts.  Structure members
+do not include the details of tables and charts but instead refer to
+them by their member names.
+
 Structure members' XML files claim conformance with a collection of
 XML Schemas.  These schemas are distributed, under a nonfree license,
 with SPSS binaries.  Fortunately, the schemas are not necessary to
-understand the structure members.  To a degree, the schemas can even
+understand the structure members.  The schemas can even
 be deceptive because they document elements and attributes that are
 not in the corpus and do not document elements and attributes that are
-commonly found there.
+commonly found in the corpus.
 
 Structure members use a different XML namespace for each schema, but
 these namespaces are not entirely consistent.  In some SPV files, for
@@ -98,7 +103,26 @@ not resolvable to obtain the schemas themselves.
 
 One may ignore all of the above in interpreting a structure member.
 The actual XML has a simple and straightforward form that does not
-require a reader to take schemas or namespaces into account.
+require a reader to take schemas or namespaces into account.  A
+structure member's root is @code{heading} element, which contains
+@code{heading} or @code{container} elements (or a mix), forming a
+tree.  In turn, @code{container} holds a single @code{text} or
+@code{table} element.
+
+@ifnottex
+For a diagram illustrating the hierarchy of elements within an SPV
+structure member, please refer to a PDF version of the manual.
+@end ifnottex
+
+@iftex
+The following diagram shows the hierarchy within an SPV structure
+member more precisely.  Oval nodes are elements and <text> and <cdata>
+are plain text and CDATA within elements.  Edges point from parent to
+child.  Unlabeled edges indicate that the child appears exactly once;
+edges labeled with *, zero or more times; edges labeled with ?, zero
+or one times.
+@center @image{dev/spv-structure, 5in}
+@end iftex
 
 The elements found in structure members are documented below.  For
 each element, we note the possible parent elements and the element's
@@ -128,19 +152,45 @@ A choice between @var{a} and @var{b}.
 Zero or more @var{x}.
 @end table
 
-@ifnottex
-For a diagram illustrating the hierarchy of elements within an SPV
-structure member, please refer to a PDF version of the manual.
-@end ifnottex
+The following example shows the contents of a typical structure member
+for a @cmd{DESCRIPTIVES} procedure.  A real structure member is not
+indented.  This example also omits most attributes, all XML namespace
+information, and the CSS from the embedded HTML:
 
-@iftex
-The following diagram shows the hierarchy of elements within an SPV
-structure member.  Edges point from parent to child elements.
-Unlabeled edges indicate that the child appears exactly once; edges
-labeled with *, zero or more times; edges labeled with ?, zero or one
-times.
-@center @image{dev/spv-structure, 5in}
-@end iftex
+@example
+<?xml version="1.0" encoding="utf-8"?>
+<heading>
+  <label>Output</label>
+  <heading commandName="Descriptives">
+    <label>Descriptives</label>
+    <container>
+      <label>Title</label>
+      <text commandName="Descriptives" type="title">
+        <html lang="en">
+<![CDATA[<head><style type="text/css">...</style></head><BR>Descriptives]]>
+        </html>
+      </text>
+    </container>
+    <container visibility="hidden">
+      <label>Notes</label>
+      <table commandName="Descriptives" subType="Notes" type="note">
+        <tableStructure>
+          <dataPath>00000000001_lightNotesData.bin</dataPath>
+        </tableStructure>
+      </table>
+    </container>
+    <container>
+      <label>Descriptive Statistics</label>
+      <table commandName="Descriptives" subType="Descriptive Statistics"
+             type="table">
+        <tableStructure>
+          <dataPath>00000000002_lightTableData.bin</dataPath>
+        </tableStructure>
+      </table>
+    </container>
+  </heading>
+</heading>
+@end example
 
 @menu
 * SPV Structure heading Element::
@@ -166,7 +216,9 @@ Contents: @code{pageSetup}? @code{label} (@code{container} @math{|} @code{headin
 The root of a structure member is a @code{heading}, which represents a
 section of output beginning with a title (the @code{label}) and
 ordinarily followed by content containers or further nested
-(sub)-sections of output.
+(sub)-sections of output.  Unlike heading elements in HTML and other
+common document formats, which precede the content that they head,
+@code{heading} contains the elements that appear below the heading.
 
 The document root heading, only, may also contain a @code{pageSetup}
 element.
@@ -250,7 +302,7 @@ often very generic, especially within a @code{container}, e.g.@:
 according to the output language, e.g.@: in Italian a frequency table
 procedure is labeled ``Frequenze''.
 
-The corpus contains one example of an empty label, one that contains
+The corpus contains a few examples of empty labels, ones that contain
 no text.
 
 This element has no attributes.
@@ -315,7 +367,7 @@ Parent: @code{text} @*
 Contents: CDATA
 
 The CDATA contains an HTML document.  In some cases, the document
-starts with @code{<html>} and ends with @code{</html}; in others the
+starts with @code{<html>} and ends with @code{</html>}; in others the
 @code{html} element is implied.  Generally the HTML includes a
 @code{head} element with a CSS stylesheet.  The HTML body often begins
 with @code{<BR>}.  The actual content ranges from trivial to simple:
@@ -440,7 +492,7 @@ This element has no attributes.
 @node SPV Structure @code{text} Element (Inside @code{pageParagraph})
 @subsection The @code{text} Element (Inside @code{pageParagraph})
 
-Parent: @code{pageParagraph}
+Parent: @code{pageParagraph} @*
 Contents: CDATA?
 
 This @code{text} element is nested inside a @code{pageParagraph}.  There
@@ -451,7 +503,7 @@ The element is either empty, or contains CDATA that holds almost-XHTML
 text: in the corpus, either an @code{html} or @code{p} element.  It is
 @emph{almost}-XHTML because the @code{html} element designates the
 default namespace as
-@code{http://xml.spss.com/spss/viewer/viewer-tree} instead of an XHTML
+@indicateurl{http://xml.spss.com/spss/viewer/viewer-tree} instead of an XHTML
 namespace, and because the CDATA can contain substitution variables:
 @code{&[Page]} for the page number and @code{&[PageTitle]} for the
 page title.
@@ -572,7 +624,7 @@ concatenated together, terminated by a byte 01:
 LightMember @result{}
     Header Title
     Caption Footnotes
-    Fonts Formats Borders PrintSettings TableSettings
+    Fonts Borders PrintSettings TableSettings Formats
     Dimensions Data
     01
 @end format
@@ -583,7 +635,7 @@ 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::
@@ -607,9 +659,14 @@ An SPV light member begins with a 39-byte header:
 Header @result{}
     01 00
     (i1 @math{|} i3)[@t{version}]
-    01 bool*4 int
+    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-height}] int[@t{max-row-height}]
+    int[@t{min-row-width}] int[@t{max-row-width}]
     int64[@t{table-id}]
 @end format
 @end cartouche
@@ -619,11 +676,30 @@ 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{-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.
 
 @node SPV Light Member Title
@@ -648,7 +724,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
@@ -727,6 +803,9 @@ 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.
 
+@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 Borders
 @subsection Borders
 
@@ -753,8 +832,8 @@ endianness.
 @code{show-grid-lines} is 1 to draw grid lines, otherwise 0.
 
 Each Border describes one kind of border.  @code{n-borders} seems to
-always be 19.  Each @code{border-type} appears once in order, and they
-correspond to the following borders:
+always be 19.  Each @code{border-type} appears once (although in an
+unpredictable order) and correspond to the following borders:
 
 @table @asis
 @item 0
@@ -803,7 +882,7 @@ opaque color, therefore opaque black is 0xff000000.
 @format
 PrintSettings @result{}
     b1[@t{endian}]
-    bool[@t{layers}]
+    bool[@t{all-layers}]
     bool[@t{paginate-layers}]
     bool[@t{fit-width}]
     bool[@t{fit-length}]
@@ -817,11 +896,12 @@ PrintSettings @result{}
 The PrintSettings reflect settings for printing.  The fixed value of
 @code{endian} can be used to validate the endianness.
 
-@code{layers} is 1 to print all layers, 0 to print only the visible
-layers.
+@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.
+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.
@@ -849,11 +929,24 @@ TableSettings @result{}
     bool[@t{footnote-marker-position}]
     v3(
       byte
-      be32[@t{n}] byte*[@t{n}]
-      bestring
+      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
 
@@ -875,6 +968,24 @@ 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.
 
@@ -886,32 +997,61 @@ TableSettings ends with an arbitrary number of null bytes.
 @cartouche
 @format
 Formats @result{}
-    int[@t{n4}] int*[@t{n4}]
+    int[@t{n-widths}] int*[@t{n-widths}]
     string[@t{encoding}]
-    (i0 @math{|} i-1) (00 @math{|} 01) 00 (00 @math{|} 01)
-    int
+    int[@t{current-layer}]
+    bool[@t{digit-grouping}] bool[@t{leading-zero}] bool
+    int[@t{epoch}]
     byte[@t{decimal}] byte[@t{grouping}]
-    int[@t{n-ccs}] string*[@t{n-ccs}]
-    v1(i0)
-    v3(count(count(X5) count(X6)))
-
-X5 @result{} byte*33 int[@t{n}] int*[@t{n}]
-X6 @result{}
+    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{subcommand}]
+    string[@t{command}] string[@t{command-local}]
     string[@t{language}] string[@t{charset}] string[@t{locale}]
-    (00 @math{|} 01) 00 (00 @math{|} 01) (00 @math{|} 01)
+    bool 00 bool bool
     int[@t{epoch}]
     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)?
+    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
 
-Observed values of @code{n4} vary from 0 to 17.  Out of 7,060 examples
-in the corpus, it is nonzero only 36 times.
+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
@@ -921,7 +1061,7 @@ 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, @t{epoch}
+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
@@ -932,11 +1072,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
 
@@ -946,30 +1112,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
@@ -980,23 +1150,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
@@ -1022,23 +1195,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.
@@ -1058,6 +1231,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
@@ -1072,7 +1246,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)
@@ -1227,13 +1401,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
 
@@ -1253,7 +1440,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
@@ -1437,6 +1639,7 @@ elements are assigned @code{id} attributes that are never referenced.
 * SPV Detail coordinates Element::
 * SPV Detail faceting Element::
 * SPV Detail facetLayout Element::
+* SPV Detail style Element::
 @end menu
 
 @node SPV Detail visualization Element
@@ -1481,7 +1684,7 @@ The title of the pivot table, localized to the output language.
 
 @defvr {Required} style
 The @code{id} of a @code{style} element (@pxref{SPV Detail style
-element}).  This is the base style for the entire pivot table.  In
+Element}).  This is the base style for the entire pivot table.  In
 every example in the corpus, the value is @code{visualizationStyle}
 and the corresponding @code{style} element has no attributes other
 than @code{id}.
@@ -1691,7 +1894,7 @@ Contents: @code{location}@math{+} @code{coordinates} @code{faceting} @code{facet
 @defvr {Required} cellStyle
 @defvrx {Required} style
 Each of these is the @code{id} of a @code{style} element (@pxref{SPV
-Detail style element}).  The former is the default style for
+Detail style Element}).  The former is the default style for
 individual cells, the latter for the entire table.
 @end defvr
 
@@ -2469,7 +2672,7 @@ This element has the following attributes.
 @defvrx {Optional} useGroupging
 The syntax and meaning of these attributes is the same as on the
 @code{format} element for a numeric format.  @pxref{SPV Detail format
-element}.
+Element}.
 @end defvr
 
 @node SPV Detail stringFormat Element
@@ -2643,7 +2846,7 @@ A value, or multiple values separated by semicolons,
 e.g.@: @code{0} or @code{13;14;15;16}.
 @end defvr
 
-@subsubheading The @code{intersectWhere}
+@subsubheading The @code{intersectWhere} Element
 
 Parent: @code{intersect} @*
 Contents: empty
@@ -2656,3 +2859,8 @@ The meaning of these attributes is unknown.  In the four examples in
 the corpus they always take the values @code{dimension2categories} and
 @code{dimension0categories}, respectively.
 @end defvr
+
+@node SPV Detail style Element
+@subsection The @code{style} Element
+
+TBD.