SPSS Viewer File Format
***********************

SPSS Viewer or .spv files, here called SPV files, are written by SPSS 16 and later to represent the contents of its output editor. This chapter documents the format, based on examination of a corpus of about 8,000 files from a variety of sources.  This description is detailed enough to both read and write SPV files.

SPSS 15 and earlier versions instead use .spo files, which have a completely different output format based on the Microsoft Compound Document Format.  This format is not documented here.

An SPV file is a Zip archive that can be read with zipinfo and unzip and similar programs.  The final member in the Zip archive is the manifest, a file named META-INF/MANIFEST.MF.  This structure makes SPV files resemble Java ``JAR'' files (and ODF files), but whereas a JAR manifest contains a sequence of colon-delimited key/value pairs, an SPV manifest contains the string allowPivoting=true, without a new-line.  PSPP uses this string to identify an SPV file; it is invariant across the corpus. (SPV files always begin with the 7-byte sequence 50 4b 03 04 14 00 08, but this is not a useful magic number because most Zip archives start the same way.)

The rest of the members in an SPV file's Zip archive fall into two categories: structure and detail members.  Structure member names begin with outputViewerNNNNNNNNNN, where each N is a decimal digit, and end with .xml, and often include the string _heading in between.  Each of these members represents some kind of output item (a table, a heading, a block of text, etc.) or a group of them.  The member whose output goes at the beginning of the document is numbered 0, the next member in the output is numbered 1, and so on.

Structure members contain XML.  This XML is sometimes self-contained, but it often references detail members in the Zip archive, which are named as follows:

PREFIX_table.xml and PREFIX_tableData.bin
PREFIX_lightTableData.bin
     The structure of a table plus its data.  Older SPV files pair a PREFIX_table.xml file that describes the table's structure with a binary PREFIX_tableData.bin file that gives its data.  Newer SPV files (the majority of those in the corpus) instead include a single PREFIX_lightTableData.bin file that incorporates both into a single binary format.

PREFIX_warning.xml and PREFIX_warningData.bin
PREFIX_lightWarningData.bin
     Same format used for tables, with a different name.

PREFIX_notes.xml and PREFIX_notesData.bin
PREFIX_lightNotesData.bin
     Same format used for tables, with a different name.

PREFIX_chartData.bin and PREFIX_chart.xml
     The structure of a chart plus its data.  Charts do not have a ``light'' format.

PREFIX_pmml.scf
PREFIX_stats.scf

PREFIX_model.xml
     Not yet investigated.  The corpus contains few examples.

The PREFIX in the names of the detail members is typically an 11-digit decimal number that increases for each item, tending to skip values.  Older SPV files use different naming conventions.  Structure member refer to detail members by name, and so their exact names do not matter to readers as long as they are unique.

SPSS tolerates corrupted Zip archives that Zip reader libraries tend to reject.  These can be fixed up with zip -FF.

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.  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 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 example, the viewer-tree schema is associated with namespace http://xml.spss.com/spss/viewer-tree and in others with http://xml.spss.com/spss/viewer/viewer-tree (note the additional viewer/).  Under either name, the schema URIs are 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.  A structure member's root is heading element, which contains heading or container elements (or a mix), forming a tree.  In turn, container holds a label and one more child, usually text or table.

The following sections document the elements found in structure members in a context-free grammar-like fashion.  Consider the following example, which specifies the attributes and content for the container element:

     container
        :visibility=(visible | hidden)
        :page-break-before=(always)?
        :text-align=(left | center)?
        :width=dimension
     => label (table | container_text | graph | model | object | image | tree)

Each attribute specification begins with : followed by the attribute's name.  If the attribute's value has an easily specified form, then = and its description follows the name.  Finally, if the attribute is optional, the specification ends with ?.  The following value specifications are defined:

(A | B | ...)
     One of the listed literal strings.  If only one string is listed, it is the only acceptable value.  If OTHER is listed, then any string not explicitly listed is also accepted.

bool
     Either true or false.

dimension
     A floating-point number followed by a unit, e.g. 10pt.  Units in the corpus include in (inch), pt (points, 72/inch), px (``device-independent pixels'', 96/inch), and cm.  If the unit is omitted then points should be assumed.  The number and unit may be separated by white space.

     The corpus also includes localized names for units.  A reader must understand these to properly interpret the dimension:

     inch
          인치, pol., cala, cali

     point
          пт

     centimeter
          см

real
     A floating-point number.

int
     An integer.

color
     A color in one of the forms #RRGGBB or RRGGBB, or the string transparent, or one of the standard Web color names.

ref

ref ELEMENT
ref(ELEM1 | ELEM2 | ...)
     The name from the id attribute in some element.  If one or more elements are named, the name must refer to one of those elements, otherwise any element is acceptable.

All elements have an optional id attribute.  If present, its value must be unique.  In practice many elements are assigned id attributes that are never referenced.

The content specification for an element supports the following syntax:

ELEMENT
     An element.

A B
     A followed by B.

A | B | C
     One of A or B or C.

A?
     Zero or one instances of A.

A*
     Zero or more instances of A.

B+
     One or more instances of A.

(SUBEXPRESSION)
     Grouping for a subexpression.

EMPTY
     No content.

TEXT
     Text and CDATA.

Element and attribute names are sometimes suffixed by another name in square brackets to distinguish different uses of the same name.  For example, structure XML has two text elements, one inside container, the other inside pageParagraph.  The former is defined as text[container_text] and referenced as container_text, the latter defined as text[pageParagraph_text] and referenced as pageParagraph_text.

This language is used in the PSPP source code for parsing structure and detail XML members.  Refer to src/output/spv/structure-xml.grammar and src/output/spv/detail-xml.grammar for the full grammars.

The following example shows the contents of a typical structure member for a 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:

     <?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>

The heading Element
-------------------

     heading[root_heading]
        :creator-version?
        :creator?
        :creation-date-time?
        :lockReader=bool?
        :schemaLocation?
     => label pageSetup? (container | heading)*

     heading
        :creator-version?
        :commandName?
        :visibility[heading_visibility]=(collapsed)?
        :locale?
        :olang?
     => label (container | heading)*

The root of a structure member is a heading, which represents a section of output beginning with a title (the label) and ordinarily followed by content containers or further nested (sub)-sections of output.  Unlike heading elements in HTML and other common document formats, which precede the content that they head, heading contains the elements that appear below the heading.

The document root heading, only, may contain a pageSetup element.

The following attributes have been observed on both document root and nested heading elements.

 -- Attribute: creator-version
     The version of the software that created this SPV file.  A string of the form xxyyzzww represents software version xx.yy.zz.ww, e.g. 21000001 is version 21.0.0.1.  Trailing pairs of zeros are sometimes omitted, so that 21, 210000, and 21000000 are all version 21.0.0.0 (and the corpus contains all three of those forms).

The following attributes have been observed on document root heading elements only:

 -- Attribute: creator
     The directory in the file system of the software that created this SPV file.

 -- Attribute: creation-date-time
     The date and time at which the SPV file was written, in a locale-specific format, e.g. Friday, May 16, 2014 6:47:37 PM PDT or lunedì 17 marzo 2014 3.15.48 CET or even Friday, December 5, 2014 5:00:19 o'clock PM EST.

 -- Attribute: lockReader
     Whether a reader should be allowed to edit the output.  The possible values are true and false.  The value false is by far the most common.

 -- Attribute: schemaLocation
     This is actually an XML Namespace attribute.  A reader may ignore it.

The following attributes have been observed only on nested heading elements:

 -- Attribute: commandName
     A locale-invariant identifier for the command that produced the output, e.g. Frequencies, T-Test, Non Par Corr.

 -- Attribute: visibility
     To what degree the output represented by the element is visible.

 -- Attribute: locale
     The locale used for output, in Windows format, which is similar to the format used in Unix with the underscore replaced by a hyphen, e.g. en-US, en-GB, el-GR, sr-Cryl-RS.

 -- Attribute: olang
     The output language, e.g. en, it, es, de, pt-BR.

The label Element
-----------------

     label => TEXT

Every heading and container holds a label as its first child.  The root heading in a structure member always contains the string ``Output'' (localized).  Otherwise, the text in label describes what it labels, often by naming the statistical procedure that was executed, e.g. ``Frequencies'' or ``T-Test''. Labels are often very generic, especially within a container, e.g. ``Title'' or ``Warnings'' or ``Notes''.  Label text is localized according to the output language, e.g. in Italian a frequency table procedure is labeled ``Frequenze''.

The corpus contains a few examples of empty labels, ones that contain no text.

The container Element
---------------------

     container
        :visibility=(visible | hidden)
        :page-break-before=(always)?
        :text-align=(left | center)?
        :width=dimension
     => label (table | container_text | graph | model | object | image | tree)

A container serves to contain and label a table, text, or other kind of item.

This element has the following attributes.

 -- Attribute: visibility
     Whether the container's content is displayed.  ``Notes'' tables are often hidden; other data is usually

 -- Attribute: text-align
     Alignment of text within the container.  Observed with nested table and text elements.

 -- Attribute: width
     The width of the container, e.g. 1097px.

The text Element (Inside container)
-----------------------------------

     text[container_text]
       :type[text_type]=(title | log | text | page-title)
       :commandName?
       :creator-version?
     => html

This text element is nested inside a container.  There is a different text element that is nested inside a pageParagraph.

This element has the following attributes.

 -- Attribute: type
     The semantics of the text.

 -- Attribute: commandName
     As on the heading element.  For output not specific to a command, this is simply log.  The corpus contains one example of where commandName is present but set to the empty string.

 -- Attribute: creator-version
     As on the heading element.

The html Element
----------------

     html :lang=(en) => TEXT

The element contains an HTML document as text (or, in practice, as CDATA).  In some cases, the document starts with <html> and ends with </html>; in others the html element is implied.  Generally the HTML includes a head element with a CSS stylesheet.  The HTML body often begins with <BR>.

The HTML document uses only the following elements:

html
     Sometimes, the document is enclosed with <html>...</html>.

br
     The HTML body often begins with <BR> and may contain it as well.

b
i
u
     Styling.

font
     The attributes face, color, and size are observed.  The value of color takes one of the forms #RRGGBB or rgb (R, G, B).  The value of size is a number between 1 and 7, inclusive.

The CSS in the corpus is simple.  To understand it, a parser only needs to be able to skip white space, <!--, and -->, and parse style only for p elements.  Only font-weight, font-style, font-decoration, font-family, and font-size matter.

This element has the following attributes.

 -- Attribute: lang
     This always contains en in the corpus.

The table Element
-----------------

     table
        :VDPId?
        :ViZmlSource?
        :activePageId=int?
        :commandName
        :creator-version?
        :displayFiltering=bool?
        :maxNumCells=int?
        :orphanTolerance=int?
        :rowBreakNumber=int?
        :subType
        :tableId
        :tableLookId?
        :type[table_type]=(table | note | warning)
     => tableProperties? tableStructure

     tableStructure => path? dataPath csvPath?

This element has the following attributes.

 -- Attribute: commandName
     As on the heading element.

 -- Attribute: type
     One of table, note, or warning.

 -- Attribute: subType
     The locale-invariant command ID for the particular kind of output that this table represents in the procedure.  This can be the same as commandName e.g. Frequencies, or different, e.g. Case Processing Summary.  Generic subtypes Notes and Warnings are often used.

 -- Attribute: tableId
     A number that uniquely identifies the table within the SPV file, typically a large negative number such as -4147135649387905023.

 -- Attribute: creator-version
     As on the heading element.  In the corpus, this is only present for version 21 and up and always includes all 8 digits.

See SPV Detail Legacy Properties, for details on the tableProperties element.

The graph Element
-----------------

     graph
        :VDPId?
        :ViZmlSource?
        :commandName?
        :creator-version?
        :dataMapId?
        :dataMapURI?
        :editor?
        :refMapId?
        :refMapURI?
        :csvFileIds?
        :csvFileNames?
     => dataPath? path csvPath?

This element represents a graph.  The dataPath and path elements name the Zip members that give the details of the graph. Normally, both elements are present; there is only one counterexample in the corpus.

csvPath only appears in one SPV file in the corpus, for two graphs.  In these two cases, dataPath, path, and csvPath all appear.  These csvPath name Zip members with names of the form NUMBER_csv.bin, where NUMBER is a many-digit number and the same as the csvFileIds.  The named Zip members are CSV text files (despite the .bin extension). The CSV files are encoded in UTF-8 and begin with a U+FEFF byte-order marker.

The model Element
-----------------

     model
        :PMMLContainerId?
        :PMMLId
        :StatXMLContainerId
        :VDPId
        :auxiliaryViewName
        :commandName
        :creator-version
        :mainViewName
     => ViZml? dataPath? path | pmmlContainerPath statsContainerPath

     pmmlContainerPath => TEXT

     statsContainerPath => TEXT

     ViZml :viewName? => TEXT

This element represents a model.  The dataPath and path elements name the Zip members that give the details of the model. Normally, both elements are present; there is only one counterexample in the corpus.

The details are unexplored.  The ViZml element contains base-64 encoded text, that decodes to a binary format with some embedded text strings, and path names an Zip member that contains XML. Alternatively, pmmlContainerPath and statsContainerPath name Zip members with .scf extension.

The tree Element
----------------

     tree
        :commandName
        :creator-version
        :name
        :type
     => dataPath path

This element represents a tree.  The dataPath and path elements name the Zip members that give the details of the tree. The details are unexplored.

Path Elements
-------------

     dataPath => TEXT

     path => TEXT

     csvPath => TEXT

These element contain the name of the Zip members that hold details for a container.  For tables:

  * When a ``light'' format is used, only dataPath is present, and it names a .bin member of the Zip file that has light in its name, e.g. 0000000001437_lightTableData.bin (see SPV Light Detail Member Format).

  * When the legacy format is used, both are present.  In this case, dataPath names a Zip member with a legacy binary format that contains relevant data (see SPV Legacy Detail Member Binary Format), and path names a Zip member that uses an XML format (see SPV Legacy Detail Member XML Format).

Graphs normally follow the legacy approach described above.  The corpus contains one example of a graph with path but not dataPath.  The reason is unexplored.

Models use path but not dataPath.  See SPV Structure graph Element, for more information.

These elements have no attributes.

The pageSetup Element
---------------------

     pageSetup
        :initial-page-number=int?
        :chart-size=(as-is | full-height | half-height | quarter-height | OTHER)?
        :margin-left=dimension?
        :margin-right=dimension?
        :margin-top=dimension?
        :margin-bottom=dimension?
        :paper-height=dimension?
        :paper-width=dimension?
        :reference-orientation?
        :space-after=dimension?
     => pageHeader pageFooter

     pageHeader => pageParagraph?

     pageFooter => pageParagraph?

     pageParagraph => pageParagraph_text

The pageSetup element has the following attributes.

 -- Attribute: initial-page-number
     The page number to put on the first page of printed output.  Usually 1.

 -- Attribute: chart-size
     One of the listed, self-explanatory chart sizes, quarter-height, or a localization (!) of one of these (e.g. dimensione attuale, Wie vorgegeben).

 -- Attribute: margin-left
 -- Attribute: margin-right
 -- Attribute: margin-top
 -- Attribute: margin-bottom
     Margin sizes, e.g. 0.25in.

 -- Attribute: paper-height
 -- Attribute: paper-width
     Paper sizes.

 -- Attribute: reference-orientation
     Indicates the orientation of the output page.  Either 0deg (portrait) or 90deg (landscape),

 -- Attribute: space-after
     The amount of space between printed objects, typically 12pt.

The text Element (Inside pageParagraph)
---------------------------------------

     text[pageParagraph_text] :type=(title | text) => TEXT

This text element is nested inside a pageParagraph.  There is a different text element that is nested inside a container.

The element is either empty, or contains CDATA that holds almost-XHTML text: in the corpus, either an html or p element.  It is almost-XHTML because the html element designates the default namespace as http://xml.spss.com/spss/viewer/viewer-tree instead of an XHTML namespace, and because the CDATA can contain substitution variables.  The following variables are supported:

&[Date]
&[Time]
     The current date or time in the preferred format for the locale.

&[Head1]
&[Head2]
&[Head3]
&[Head4]
     First-, second-, third-, or fourth-level heading.

&[PageTitle]
     The page title.

&[Filename]
     Name of the output file.

&[Page]
     The page number.

&[Page] for the page number and &[PageTitle] for the page title.

Typical contents (indented for clarity):

     <html xmlns="http://xml.spss.com/spss/viewer/viewer-tree">
         <head></head>
         <body>
             <p style="text-align:right; margin-top: 0">Page &[Page]</p>
         </body>
     </html>

This element has the following attributes.

 -- Attribute: type
     Always text.

Light Detail Member Format
==========================

This section describes the format of ``light'' detail .bin members.  These members have a binary format which we describe here in terms of a context-free grammar using the following conventions:

NonTerminal => ...
     Nonterminals have CamelCaps names, and => indicates a production.  The right-hand side of a production is often broken across multiple lines.  Break points are chosen for aesthetics only and have no semantic significance.

00, 01, ..., ff.
     A bytes with a fixed value, written as a pair of hexadecimal digits.

i0, i1, ..., i9, i10, i11, ...
ib0, ib1, ..., ib9, ib10, ib11, ...
     A 32-bit integer in little-endian or big-endian byte order, respectively, with a fixed value, written in decimal.  Prefixed by i for little-endian or ib for big-endian.

byte
     A byte.

bool
     A byte with value 0 or 1.

int16
be16
     A 16-bit unsigned integer in little-endian or big-endian byte order, respectively.

int32
be32
     A 32-bit unsigned integer in little-endian or big-endian byte order, respectively.

int64
be64
     A 64-bit unsigned integer in little-endian or big-endian byte order, respectively.

double
     A 64-bit IEEE floating-point number.

float
     A 32-bit IEEE floating-point number.

string
bestring
     A 32-bit unsigned 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.)

X?
     X is optional, e.g. 00? is an optional zero byte.

X*N
     X is repeated N times, e.g. byte*10 for ten arbitrary bytes.

X[NAME]
     Gives X the specified NAME.  Names are used in textual explanations.  They are also used, also bracketed, to indicate counts, e.g. int32[n] byte*[n] for a 32-bit integer followed by the specified number of arbitrary bytes.

A | B
     Either A or B.

(X)
     Parentheses are used for grouping to make precedence clear, especially in the presence of |, e.g. in 00 (01 | 02 | 03) 00.

count(X)
becount(X)
     A 32-bit unsigned integer, in little-endian or big-endian byte order, respectively, that indicates the number of bytes in X, followed by X itself.

v1(X)
     In a version 1 .bin member, X; in version 3, nothing. (The .bin header indicates the version.)

v3(X)
     In a version 3 .bin member, X; in version 1, nothing.

PSPP uses this grammar to parse light detail members.  See src/output/spv/light-binary.grammar in the PSPP source tree for the full grammar.

Little-endian byte order is far more common in this format, but a few pieces of the format use big-endian byte order.

Light detail members express linear units in two ways: points (pt), at 72/inch, and ``device-independent pixels'' (px), at 96/inch.  To convert from pt to px, multiply by 1.33 and round up.  To convert from px to pt, divide by 1.33 and round down.

A ``light'' detail member .bin consists of a number of sections concatenated together, terminated by an optional byte 01:

     LightMember =>
         Header Titles Footnotes
         Areas Borders PrintSettings TableSettings Formats
         Dimensions Axes Cells
         01?

The following sections go into more detail.

Header
------

An SPV light member begins with a 39-byte header:

     Header =>
         01 00
         (i1 | i3)[version]
         bool[x0]
         bool[x1]
         bool[rotate-inner-column-labels]
         bool[rotate-outer-row-labels]
         bool[x2]
         int32[x3]
         int32[min-col-width] int32[max-col-width]
         int32[min-row-width] int32[max-row-width]
         int64[table-id]

version is a version number that affects the interpretation of some of the other data in the member.  We will refer to ``version 1'' and ``version 3'' later on and use v1(...) and v3(...) for version-specific formatting (as described previously).

If 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 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.

table-id is a binary version of the tableId attribute in the structure member that refers to the detail member.  For example, if tableId is -4122591256483201023, then table-id would be 0xc6c99d183b300001.

min-col-width is the minimum width that a column will be assigned automatically.  max-col-width is the maximum width that a column will be assigned to accommodate a long column label. min-row-width and max-row-width are a similar range for the width of row labels.  All of these measurements are in 1/96 inch units (called a ``device independent pixel'' unit in Windows).

The meaning of the other variable parts of the header is not known.  A writer may safely use version 3, true for x0, false for x1, true for x2, and 0x15 for x3.

Titles
------

     Titles =>
         Value[title] 01?
         Value[subtype] 01? 31
         Value[user-title] 01?
         (31 Value[corner-text] | 58)
         (31 Value[caption] | 58)

The Titles follow the Header and specify the table's title, caption, and corner text.

The user-title is shown above the title and reflects any user editing of the title text or style.  The title is the title originally generated by the procedure.  Both of these are appropriate for presentation and localized to the user's language.  For example, for a frequency table, title and user-title normally name the variable and c is simply ``Frequencies''.

subtype is the same as the subType attribute in the table structure XML element that referred to this member. See SPV Structure table Element, for details.

The corner-text, if present, is shown in the upper-left corner of the table, above the row headings and to the left of the column headings.  It is usually absent.  Corner text prevents row dimension labels from being displayed above the dimension's group and category labels (see show-row-labels-in-corner).

The caption, if present, is shown below the table. caption reflects user editing of the caption.

Footnotes
---------

     Footnotes => int32[n-footnotes] Footnote*[n-footnotes]
     Footnote => Value[text] (58 | 31 Value[marker]) int32[show]

Each footnote has text and an optional custom marker (such as *).

show is a 32-bit signed integer.  It is positive to show the footnote or negative to hide it.  Its magnitude is often 1, and in other cases tends to be the number of references to the footnote.

Areas
-----

     Areas => 00? Area*8
     Area =>
         byte[index] 31
         string[typeface] float[size] int32[style] bool[underline]
         int32[halign] int32[valign]
         string[fg-color] string[bg-color]
         bool[alternate] string[alt-fg-color] string[alt-bg-color]
         v3(int32[left-margin] int32[right-margin] int32[top-margin] int32[bottom-margin])

Each Area represents the style for a different area of the table, in the following order: title, caption, footer, corner, column labels, row labels, data, and layers.

index is the 1-based index of the Area, i.e. 1 for the first Area, through 8 for the final Area.

typeface is the string name of the font used in the area.  In the corpus, this is SansSerif in over 99% of instances and Times New Roman in the rest.

size is the size of the font, in px (see SPV Light Detail Member Format) The most common size in the corpus is 12 px.  Even though size has a floating-point type, in the corpus its values are always integers.

style is a bit mask.  Bit 0 (with value 1) is set for bold, bit 1 (with value 2) is set for italic.

underline is 1 if the font is underlined, 0 otherwise.

halign specifies horizontal alignment: 0 for center, 2 for left, 4 for right, 61453 for decimal, 64173 for mixed.  Mixed alignment varies according to type: string data is left-justified, numbers and most other formats are right-justified.

valign specifies vertical alignment: 0 for center, 1 for top, 3 for bottom.

fg-color and bg-color are the foreground color and background color, respectively.  In the corpus, these are always #000000 and #ffffff, respectively.

alternate is 1 if rows should alternate colors, 0 if all rows should be the same color.  When alternate is 1, alt-fg-color and alt-bg-color specify the colors for the alternate rows; otherwise they are empty strings.

left-margin, right-margin, top-margin, and bottom-margin are measured in px.

Borders
-------

     Borders =>
         count(
             ib1[endian]
             be32[n-borders] Border*[n-borders]
             bool[show-grid-lines]
             00 00 00)

     Border =>
         be32[border-type]
         be32[stroke-type]
         be32[color]

The Borders reflect how borders between regions are drawn.

The fixed value of endian can be used to validate the endianness.

show-grid-lines is 1 to draw grid lines, otherwise 0.

Each Border describes one kind of border.  n-borders seems to always be 19.  Each border-type appears once (although in an unpredictable order) and correspond to the following borders:

0
     Title.

1...4
     Left, top, right, and bottom outer frame.

5...8
     Left, top, right, and bottom inner frame.

9, 10
     Left and top of data area.

11, 12
     Horizontal and vertical dimension rows.

13, 14
     Horizontal and vertical dimension columns.

15, 16
     Horizontal and vertical category rows.

17, 18
     Horizontal and vertical category columns.

stroke-type describes how a border is drawn, as one of:

0
     No line.

1
     Solid line.

2
     Dashed line.

3
     Thick line.

4
     Thin line.

5
     Double line.

color is an RGB color.  Bits 24--31 are alpha, bits 16--23 are red, 8--15 are green, 0--7 are blue.  An alpha of 255 indicates an opaque color, therefore opaque black is 0xff000000.

Print Settings
--------------

     PrintSettings =>
         count(
             ib1[endian]
             bool[all-layers]
             bool[paginate-layers]
             bool[fit-width]
             bool[fit-length]
             bool[top-continuation]
             bool[bottom-continuation]
             be32[n-orphan-lines]
             bestring[continuation-string])

The PrintSettings reflect settings for printing.  The fixed value of endian can be used to validate the endianness.

all-layers is 1 to print all layers, 0 to print only the visible layers.

paginate-layers is 1 to print each layer at the start of a new page, 0 otherwise.  (This setting is honored only all-layers is 1, since otherwise only one layer is printed.)

fit-width and fit-length control whether the table is shrunk to fit within a page's width or length, respectively.

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 top-continuation is 1, then continuation-string is printed at the top of a page when a table is broken across pages for printing; similarly for bottom-continuation and the bottom of a page.  Usually, continuation-string is empty.

Table Settings
--------------

     TableSettings =>
         count(
           v3(
             ib1[endian]
             be32[x5]
             be32[current-layer]
             bool[omit-empty]
             bool[show-row-labels-in-corner]
             bool[show-alphabetic-markers]
             bool[footnote-marker-superscripts]
             byte[x6]
             becount(
               Breakpoints[row-breaks] Breakpoints[column-breaks]
               Keeps[row-keeps] Keeps[column-keeps]
               PointKeeps[row-point-keeps] PointKeeps[column-point-keeps]
             )
             bestring[notes]
             bestring[table-look]
             00...))

     Breakpoints => be32[n-breaks] be32*[n-breaks]

     Keeps => be32[n-keeps] Keep*[n-keeps]
     Keep => be32[offset] be32[n]

     PointKeeps => be32[n-point-keeps] PointKeep*[n-point-keeps]
     PointKeep => be32[offset] be32 be32

The TableSettings reflect display settings.  The fixed value of endian can be used to validate the endianness.

current-layer is the displayed layer.  The interpretation when there is more than one layer dimension is not yet known.

If omit-empty is 1, empty rows or columns (ones with nothing in any cell) are hidden; otherwise, they are shown.

If show-row-labels-in-corner is 1, then row labels are shown in the upper left corner; otherwise, they are shown nested.

If show-alphabetic-markers is 1, markers are shown as letters (e.g. a, b, c, ...); otherwise, they are shown as numbers starting from 1.

When footnote-marker-superscripts 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 offset 1 and 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.

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.

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.  A writer may safely write 82 null bytes.

A writer may safely use 4 for x5 and 0 for x6.

Formats
-------

     Formats =>
         int32[n-widths] int32*[n-widths]
         string[locale]
         int32[current-layer]
         bool bool bool
         Y0
         CustomCurrency
         count(
           v1(X0?)
           v3(count(X1 count(X2)) count(X3)))
     Y0 => int32[epoch] byte[decimal] byte[grouping]
     CustomCurrency => int32[n-ccs] string*[n-ccs]

If n-widths is nonzero, then the accompanying integers are column widths as manually adjusted by the user.

locale is a locale including an encoding, such as en_US.windows-1252 or 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.

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, epoch ranges from 1943 to 1948, plus some contain -1.

decimal is the decimal point character.  The observed values are . and ,.

grouping is the grouping character.  Usually, it is , if decimal is ., and vice versa.  Other observed values are ' (apostrophe),   (space), and zero (presumably indicating that digits should not be grouped).

n-ccs is observed as either 0 or 5.  When it is 5, the following strings are CCA through CCE format strings.  See Custom Currency Formats (PSPP).  Most commonly these are all -,,, but other strings occur.

X0
..

X0 only appears, optionally, in version 1 members.

     X0 => byte*14 Y1 Y2
     Y1 =>
         string[command] string[command-local]
         string[language] string[charset] string[locale]
         bool bool bool bool
         Y0
     Y2 => CustomCurrency byte[missing] bool[x17]

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.''  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 command.

dataset is the name of the dataset analyzed to produce the output, 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.

missing is the character used to indicate that a cell contains a missing value.  It is always observed as ..

X0 repeats decimal, grouping, CustomCurrency, and missing already included in Formats.

A writer may safely use false for x17.

X1
..

X1 only appears in version 3 members.

     X1 =>
         bool byte[x15] bool[x16]
         byte[lang]
         byte[show-variables]
         byte[show-values]
         int32[x18] int32[x19]
         00*17
         bool[x20]
         bool[show-caption]

lang may indicate the language in use.  Some values seem to be 0: en, 1: de, 2: es, 3: it, 5: ko, 6: pl, 8: zh-tw, 10: pt_BR, 11: fr.  The locale in Formats and the language, charset, and locale in X0 are more likely to be useful in practice.

show-variables determines how variables are displayed by default.  A value of 1 means to display variable names, 2 to display variable labels when available, 3 to display both (name followed by label, separated by a space).  The most common value is 0, which probably means to use a global default.

show-values is a similar setting for values.  A value of 1 means to display the value, 2 to display the value label when available, 3 to display both.  Again, the most common value is 0, which probably means to use a global default.

show-caption is true to show the caption, false to hide it.

A writer may safely use false for x14, 1 for x15, false for x16, -1 for x18 and x19, and false for x20.

X2
..

X2 only appears in version 3 members.

     X2 =>
         int32[n-row-heights] int32*[n-row-heights]
         int32[n-style-map] StyleMap*[n-style-map]
         int32[n-styles] StylePair*[n-styles]
         count((i0 i0)?)
     StyleMap => int64[cell-index] int16[style-index]

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 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 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 (see SPV Light Member Cells), along with a 0-based index into the accompanying StylePair array.

A writer may safely omit the optional i0 i0 inside the count(...).

X3
..

X3 only appears in version 3 members.

     X3 =>
         01 00 byte[x21] 00 00 00
         Y1
         double[small] 01
         (string[dataset] string[datafile] i0 int32[date] i0)?
         Y2
         (int32[x22] i0)?

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.

X3 repeats decimal, grouping, CustomCurrency, and missing already included in Formats.  command, command-local, language, charset, and locale have the same meaning as in X0.

small is a small real number, e.g. .001.  Numbers smaller than this in absolute value are displayed in scientific notation.

Sometimes dataset, datafile, and 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 dataset contains a null byte (a valid string never will).

x22 is usually 0 or 2000000.

A writer may safely use 4 for x21 and omit x22 and the other optional bytes at the end.

Dimensions
----------

A pivot table presents multidimensional data.  A Dimension identifies the categories associated with each dimension.

     Dimensions => int32[n-dims] Dimension*[n-dims]
     Dimension =>
         Value[name] DimProperties
         int32[n-categories] Category*[n-categories]
     DimProperties =>
         byte[x1]
         byte[x2]
         int32[x3]
         bool[hide-dim-label]
         bool[hide-all-labels]
         01 int32[dim-index]

name is the name of the dimension, e.g. Variables, Statistics, or a variable name.

The meanings of x1 and x3 are unknown.  x1 is usually 0 but many other values have been observed.  A writer may safely use 0 for x1 and 2 for x3.

x2 is 0, 1, or 2.  For a pivot table with L layer dimensions, R row dimensions, and C column dimensions, x2 is 2 for the first L dimensions, 0 for the next R dimensions, and 1 for the remaining C dimensions.  This does not mean that the layer dimensions must be presented first, followed by the row dimensions, followed by the column dimensions---on the contrary, they are frequently in a different order---but x2 must follow this pattern to prevent the pivot table from being misinterpreted.

If hide-dim-label is 00, the pivot table displays a label for the dimension itself.  Because usually the group and category labels are enough explanation, it is usually 01.

If hide-all-labels is 01, the pivot table omits all labels for the dimension, including group and category labels.  It is usually 00. When hide-all-labels is 01, show-dim-label is ignored.

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.

Categories
----------

Categories are arranged in a tree.  Only the leaf nodes in the tree are really categories; the others just serve as grouping constructs.

     Category => Value[name] (Leaf | Group)
     Leaf => 00 00 00 i2 int32[leaf-index] i0
     Group =>
         bool[merge] 00 01 int32[x23]
         i-1 int32[n-subcategories] Category*[n-subcategories]

name is the name of the category (or group).

A Leaf represents a leaf category.  The Leaf's leaf-index is a nonnegative integer unique within the Dimension and less than n-categories in the Dimension.  If the user does not sort or rearrange the categories, then leaf-index starts at 0 for the first Leaf in the dimension and increments by 1 with each successive Leaf.  If the user does sorts or rearrange the categories, then the order of categories in the file reflects that change and leaf-index reflects the original order.

Occasionally a dimension has no leaf categories at all.  A table that contains such a dimension necessarily has no data at all.

A Group is a group of nested categories.  Usually a Group contains at least one Category, so that n-subcategories is positive, but a few Groups with n-subcategories 0 has been observed.

If a Group's merge is 00, the most common value, then the group is really a distinct group that should be represented as such in the visual representation and user interface.  If merge is 01, the categories in this group should be shown and treated as if they were direct children of the group's containing group (or if it has no parent group, then direct children of the dimension), and this group's name is irrelevant and should not be displayed.  (Merged groups can be nested!)

(For writing an SPV file, there is no need to use the merge feature unless it is convenient.)

A Group's x23 appears to be i2 when all of the categories within a group are leaf categories that directly represent data values for a variable (e.g. in a frequency table or crosstabulation, a group of values in a variable being tabulated) and i0 otherwise.  A writer may safely write a constant 0 in this field.

Axes
----

After the dimensions come assignment of each dimension to one of the axes: layers, rows, and columns.

     Axes =>
         int32[n-layers] int32[n-rows] int32[n-columns]
         int32*[n-layers] int32*[n-rows] int32*[n-columns]

The values of n-layers, n-rows, and 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 n-dimensions from Dimensions (see SPV Light Member Dimensions).

The following n-dimensions integers, in three groups, are a permutation of the 0-based dimension numbers.  The first n-layers integers specify each of the dimensions represented by layers, the next n-rows integers specify the dimensions represented by rows, and the final 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.

Cells
-----

The final part of an SPV light member contains the actual data.

     Cells => int32[n-cells] Cell*[n-cells]
     Cell => int64[index] v1(00?) Value

A Cell consists of an index and a Value.  Suppose there are d dimensions, numbered 1 through d in the order given in the Dimensions previously, and that dimension i, has n_i categories.  Consider the cell at coordinates x_i, 1 \le i \le d, and note that 0 \le x_i < n_i.  Then the index is calculated by the following algorithm:

     let index = 0
     for each i from 1 to d:
         index = (n_i \times index) + x_i

For example, suppose there are 3 dimensions with 3, 4, and 5 categories, respectively.  The cell at coordinates (1, 2, 3) has index 5 \times (4 \times (3 \times 0 + 1) + 2) + 3 = 33. Within a given dimension, the index is the leaf-index in a Leaf.

Value
-----

Value is used throughout the SPV light member format.  It boils down to a number or a string.

     Value => 00? 00? 00? 00? RawValue
     RawValue =>
         01 ValueMod int32[format] double[x]
       | 02 ValueMod int32[format] double[x]
         string[var-name] string[value-label] byte[show]
       | 03 string[local] ValueMod string[id] string[c] bool[fixed]
       | 04 ValueMod int32[format] string[value-label] string[var-name]
         byte[show] string[s]
       | 05 ValueMod string[var-name] string[var-label] byte[show]
       | ValueMod string[template] int32[n-args] Argument*[n-args]
     Argument =>
         i0 Value
       | int32[x] i0 Value*[x]      /* x > 0 */

There are several possible encodings, which one can distinguish by the first nonzero byte in the encoding.

01
     The numeric value x, intended to be presented to the user formatted according to format, which is in the format described for system files, except that format 40 is a synonym for F format instead of MTIME.  See System File Output Formats, for details. Most commonly, format has width 40 (the maximum).

     An x with the maximum negative double value -DBL_MAX represents the system-missing value SYSMIS.  (HIGHEST and LOWEST have not been observed.)  See System File Format, for more about these special values.

02
     Similar to 01, with the additional information that x is a value of variable var-name and has value label value-label.  Both var-name and value-label can be the empty string, the latter very commonly.

     show determines whether to show the numeric value or the value label.  A value of 1 means to show the value, 2 to show the label, 3 to show both, and 0 means to use the default specified in show-values (see SPV Light Member Formats).

03
     A text string, in two forms: c is in English, and sometimes abbreviated or obscure, and local is localized to the user's locale.  In an English-language locale, the two strings are often the same, and in the cases where they differ, local is more appropriate for a user interface, e.g. c of ``Not a PxP table for MCN...'' versus local of ``Computed only for a PxP table, where P must be greater than 1.''

     c and local are always either both empty or both nonempty.

     id is a brief identifying string whose form seems to resemble a programming language identifier, e.g. cumulative_percent or factor_14.  It is not unique.

     fixed is 00 for text taken from user input, such as syntax fragment, expressions, file names, data set names, and 01 for fixed text strings such as names of procedures or statistics.  In the former case, id is always the empty string; in the latter case, id is still sometimes empty.

04
     The string value s, intended to be presented to the user formatted according to format.  The format for a string is not too interesting, and the corpus contains many clearly invalid formats like A16.39 or A255.127 or A134.1, so readers should probably ignore the format entirely.

     s is a value of variable var-name and has value label value-label.  var-name is never empty but value-label is commonly empty.

     show has the same meaning as in the encoding for 02.

05
     Variable var-name, which is rarely observed as empty in the corpus, with variable label var-label, which is often empty.

     show determines whether to show the variable name or the variable label.  A value of 1 means to show the name, 2 to show the label, 3 to show both, and 0 means to use the default specified in show-variables (see SPV Light Member Formats).

otherwise
     When the first byte of a RawValue is not one of the above, the RawValue starts with a ValueMod, whose syntax is described in the next section.  (A ValueMod always begins with byte 31 or 58.)

     This case is a template string, analogous to printf, followed by one or more Arguments, each of which has one or more values.  The template string is copied directly into the output except for the following special syntax,

     \%
     \:
     \[
     \]
          Each of these expands to the character following \\, to escape characters that have special meaning in template strings.  These are effective inside and outside the [...]  syntax forms described below.

     \n
          Expands to a new-line, inside or outside the [...] forms described below.

     ^I
          Expands to a formatted version of argument I, which must have only a single value.  For example, ^1 expands to the first argument's value.

     [:A:]I
          Expands A for each of the values in I.  A should contain one or more ^J conversions, which are drawn from the values for argument I in order.  Some examples from the corpus:

          [:^1:]1
               All of the values for the first argument, concatenated.

          [:^1\n:]1
               Expands to the values for the first argument, each followed by a new-line.

          [:^1 = ^2:]2
               Expands to X = Y where X is the second argument's first value and Y is its second value.  (This would be used only if the argument has two values.  If there were more values, the second and third values would be directly concatenated, which would look funny.)

     [A:B:]I
          This extends the previous form so that the first values are expanded using A and later values are expanded using B.  For an unknown reason, within A the ^J conversions are instead written as %J.  Some examples from the corpus:

          [%1:*^1:]1
               Expands to all of the values for the first argument, separated by *.

          [%1 = %2:, ^1 = ^2:]1
               Given appropriate values for the first argument, expands to X = 1, Y = 2, Z = 3.

          [%1:, ^1:]1
               Given appropriate values, expands to 1, 2, 3.

     The template string is localized to the user's locale.

A writer may safely omit all of the optional 00 bytes at the beginning of a Value, except that it should write a single 00 byte before a templated Value.

ValueMod
--------

A ValueMod can specify special modifications to a Value.

     ValueMod =>
         58
       | 31
         int32[n-refs] int16*[n-refs]
         int32[n-subscripts] string*[n-subscripts]
         v1(00 (i1 | i2) 00? 00? int32 00? 00?)
         v3(count(TemplateString StylePair))

     TemplateString => count((count((i0 (58 | 31 55))?) (58 | 31 string[id]))?)

     StylePair =>
         (31 FontStyle | 58)
         (31 CellStyle | 58)

     FontStyle =>
         bool[bold] bool[italic] bool[underline] bool[show]
         string[fg-color] string[bg-color]
         string[typeface] byte[size]

     CellStyle =>
         int32[halign] int32[valign] double[decimal-offset]
         int16[left-margin] int16[right-margin]
         int16[top-margin] int16[bottom-margin]

A ValueMod that begins with ``31'' specifies special modifications to a Value.

Each of the n-refs integers is a reference to a Footnote (see SPV Light Member Footnotes) by 0-based index.  Footnote markers are shown appended to the main text of the Value, as superscripts.

The subscripts, if present, are strings to append to the main text of the Value, as subscripts.  Each subscript text is a brief indicator, e.g. a or b, with its meaning indicated by the table caption.  When multiple subscripts are present, they are displayed separated by commas.

The id inside the TemplateString, if present, is a template string for substitutions using the syntax explained previously.  It appears to be an English-language version of the localized template string in the Value in which the Template is nested.  A writer may safely omit the optional fixed data in TemplateString.

FontStyle and CellStyle, if present, change the style for this individual Value.  In FontStyle, bold, italic, and underline control the particular style.  show is ordinarily 1; if it is 0, then the cell data is not shown. fg-color and bg-color are strings in the format #rrggbb, e.g. #ff0000 for red or #ffffff for white.  The empty string is occasionally observed also.  The size is a font size in units of 1/128 inch.

In CellStyle, halign is 0 for center, 2 for left, 4 for right, 6 for decimal, 0xffffffad for mixed.  For decimal alignment, decimal-offset is the decimal point's offset from the right side of the cell, in pt (see SPV Light Detail Member Format). valign specifies vertical alignment: 0 for center, 1 for top, 3 for bottom.  left-margin, right-margin, top-margin, and bottom-margin are in pt.

Legacy Detail Member Binary Format
==================================

Whereas the light binary format represents everything about a given pivot table, the legacy binary format conceptually consists of a number of named sources, each of which consists of a number of named variables, each of which is a 1-dimensional array of numbers or strings or a mix.  Thus, the legacy binary member format is quite simple.

This section uses the same context-free grammar notation as in the previous section, with the following additions:

vAF(X)
     In a version 0xaf legacy member, X; in other versions, nothing. (The legacy member header indicates the version; see below.)

vB0(X)
     In a version 0xb0 legacy member, X; in other versions, nothing.

A legacy detail member .bin has the following overall format:

     LegacyBinary =>
         00 byte[version] int16[n-sources] int32[member-size]
         Metadata*[n-sources]
         #Data*[n-sources]
         #Strings?

version is a version number that affects the interpretation of some of the other data in the member.  Versions 0xaf and 0xb0 are known.  We will refer to ``version 0xaf'' and ``version 0xb0'' members later on.

A legacy member consists of n-sources data sources, each of which has Metadata and Data.

member-size is the size of the legacy binary member, in bytes.

The Data and Strings above are commented out because the Metadata has some oddities that mean that the Data sometimes seems to start at an unexpected place.  The following section goes into detail.

Metadata
--------

     Metadata =>
         int32[n-values] int32[n-variables] int32[data-offset]
         vAF(byte*28[source-name])
         vB0(byte*64[source-name] int32[x])

A data source has n-variables variables, each with n-values data values.

source-name is a 28- or 64-byte string padded on the right with 0-bytes.  The names that appear in the corpus are very generic: usually tableData for pivot table data or source0 for chart data.

A given Metadata's data-offset is the offset, in bytes, from the beginning of the member to the start of the corresponding Data. This allows programs to skip to the beginning of the data for a particular source.  In every case in the corpus, the Data follow the Metadata in the same order, but it is important to use data-offset instead of reading sequentially through the file because of the exception described below.

One SPV file in the corpus has legacy binary members with version 0xb0 but a 28-byte source-name field (and only a single source).  In practice, this means that the 64-byte source-name used in version 0xb0 has a lot of 0-bytes in the middle followed by the variable-name of the following Data.  As long as a reader treats the first 0-byte in the source-name as terminating the string, it can properly interpret these members.

The meaning of x in version 0xb0 is unknown.

Numeric Data
------------

     Data => Variable*[n-variables]
     Variable => byte*288[variable-name] double*[n-values]

Data follow the Metadata in the legacy binary format, with sources in the same order (but readers should use the data-offset in Metadata records, rather than reading sequentially).  Each Variable begins with a variable-name that generally indicates its role in the pivot table, e.g. ``cell'', ``cellFormat'', ``dimension0categories'', ``dimension0group0'', followed by the numeric data, one double per datum.  A double with the maximum negative double -DBL_MAX represents the system-missing value SYSMIS.

String Data
-----------

     Strings => SourceMaps[maps] Labels

     SourceMaps => int32[n-maps] SourceMap*[n-maps]

     SourceMap => string[source-name] int32[n-variables] VariableMap*[n-variables]
     VariableMap => string[variable-name] int32[n-data] DatumMap*[n-data]
     DatumMap => int32[value-idx] int32[label-idx]

     Labels => int32[n-labels] Label*[n-labels]
     Label => int32[frequency] string[label]

Each variable may include a mix of numeric and string data values.  If a legacy binary member contains any string data, Strings is present; otherwise, it ends just after the last Data element.

The string data overlays the numeric data.  When a variable includes any string data, its Variable represents the string values with a SYSMIS or NaN placeholder.  (Not all such values need be placeholders.)

Each SourceMap provides a mapping between SYSMIS or NaN values in source source-name and the string data that they represent. n-variables is the number of variables in the source that include string data.  More precisely, it is the 1-based index of the last variable in the source that includes any string data; thus, it would be 4 if there are 5 variables and only the fourth one includes string data.

A VariableMap repeats its variable's name, but variables are always present in the same order as the source, starting from the first variable, without skipping any even if they have no string values. Each VariableMap contains DatumMap nonterminals, each of which maps from a 0-based index within its variable's data to a 0-based label index, e.g. pair value-idx = 2, label-idx = 3, means that the third data value (which must be SYSMIS or NaN) is to be replaced by the string of the fourth Label.

The labels themselves follow the pairs.  The valuable part of each label is the string label.  Each label also includes a frequency that reports the number of DatumMaps that reference it (although this is not useful).

Legacy Detail Member XML Format
===============================

The design of the detail XML format is not what one would end up with for describing pivot tables.  This is because it is a special case of a much more general format (``visualization XML'' or ``VizML'') that can describe a wide range of visualizations.  Most of this generality is overkill for tables, and so we end up with a funny subset of a general-purpose format.

An XML Schema for VizML is available, distributed with SPSS binaries, under a nonfree license.  It contains documentation that is occasionally helpful.

This section describes the detail XML format using the same notation already used for the structure XML format (see SPV Structure Member Format).  See src/output/spv/detail-xml.grammar in the PSPP source tree for the full grammar that it uses for parsing.

The important elements of the detail XML format are:

  * Variables.  See SPV Detail Variable Elements.

  * Assignment of variables to axes.  A variable can appear as columns, or rows, or layers.  The faceting element and its sub-elements describe this assignment.

  * Styles and other annotations.

This description is not detailed enough to write legacy tables. Instead, write tables in the light binary format.

The visualization Element
-------------------------

     visualization
        :creator
        :date
        :lang
        :name
        :style[style_ref]=ref style
        :type
        :version
        :schemaLocation?
     => visualization_extension?
        userSource
        (sourceVariable | derivedVariable)+
        categoricalDomain?
        graph
        labelFrame[lf1]*
        container?
        labelFrame[lf2]*
        style+
        layerController?

     extension[visualization_extension]
        :numRows=int?
        :showGridline=bool?
        :minWidthSet=(true)?
        :maxWidthSet=(true)?
     => EMPTY

     userSource :missing=(listwise | pairwise)? => EMPTY

     categoricalDomain => variableReference simpleSort

     simpleSort :method[sort_method]=(custom) => categoryOrder

     container :style=ref style => container_extension? location+ labelFrame*

     extension[container_extension] :combinedFootnotes=(true) => EMPTY

     layerController
        :source=(tableData)
        :target=ref label?
     => EMPTY

The visualization element is the root of detail XML member.  It has the following attributes:

 -- Attribute: creator
     The version of the software that created this SPV file, as a string of the form xxyyzz, which represents software version xx.yy.zz, e.g. 160001 is version 16.0.1.  The corpus includes major versions 16 through 19.

 -- Attribute: date
     The date on the which the file was created, as a string of the form YYYY-MM-DD.

 -- Attribute: lang
     The locale used for output, in Windows format, which is similar to the format used in Unix with the underscore replaced by a hyphen, e.g. en-US, en-GB, el-GR, sr-Cryl-RS.

 -- Attribute: name
     The title of the pivot table, localized to the output language.

 -- Attribute: style
     The base style for the pivot table.  In every example in the corpus, the style element has no attributes other than id.

 -- Attribute: type
     A floating-point number.  The meaning is unknown.

 -- Attribute: version
     The visualization schema version number.  In the corpus, the value is one of 2.4, 2.5, 2.7, and 2.8.

The userSource element has no visible effect.

The extension element as a child of visualization has the following attributes.

 -- Attribute: numRows
     An integer that presumably defines the number of rows in the displayed pivot table.

 -- Attribute: showGridline
     Always set to false in the corpus.

 -- Attribute: minWidthSet
 -- Attribute: maxWidthSet
     Always set to true in the corpus.

The extension element as a child of container has the following attribute

 -- Attribute: combinedFootnotes
     Meaning unknown.

The categoricalDomain and simpleSort elements have no visible effect.

The layerController element has no visible effect.

Variable Elements
-----------------

A ``variable'' in detail XML is a 1-dimensional array of data.  Each element of the array may, independently, have string or numeric content.  All of the variables in a given detail XML member either have the same number of elements or have zero elements.

Two different elements define variables and their content:

sourceVariable
     These variables' data comes from the associated tableData.bin member.

derivedVariable
     These variables are defined in terms of a mapping function from a source variable, or they are empty.

A variable named cell always exists.  This variable holds the data displayed in the table.

Variables in detail XML roughly correspond to the dimensions in a light detail member.  Each dimension has the following variables with stylized names, where N is a number for the dimension starting from 0:

dimensionNcategories
     The dimension's leaf categories (see SPV Light Member Categories).

dimensionNgroup0
     Present only if the dimension's categories are grouped, this variable holds the group labels for the categories.  Grouping is inferred through adjacent identical labels.  Categories that are not part of a group have empty-string data in this variable.

dimensionNgroup1
     Present only if the first-level groups are further grouped, this variable holds the labels for the second-level groups.  There can be additional variables with further levels of grouping.

dimensionN
     An empty variable.

Determining the data for a (non-empty) variable is a multi-step process:

 1. Draw initial data from its source, for a sourceVariable, or from another named variable, for a derivedVariable.

 2. Apply mappings from valueMapEntry elements within the derivedVariable element, if any.

 3. Apply mappings from relabel elements within a format or stringFormat element in the sourceVariable or derivedVariable element, if any.

 4. If the variable is a sourceVariable with a labelVariable attribute, and there were no mappings to apply in previous steps, then replace each element of the variable by the corresponding value in the label variable.

A single variable's data can be modified in two of the steps, if both valueMapEntry and relabel are used.  The following example from the corpus maps several integers to 2, then maps 2 in turn to the string ``Input'':

     <derivedVariable categorical="true" dependsOn="dimension0categories"
                      id="dimension0group0map" value="map(dimension0group0)">
       <stringFormat>
         <relabel from="2" to="Input"/>
         <relabel from="10" to="Missing Value Handling"/>
         <relabel from="14" to="Resources"/>
         <relabel from="0" to=""/>
         <relabel from="1" to=""/>
         <relabel from="13" to=""/>
       </stringFormat>
       <valueMapEntry from="2;3;5;6;7;8;9" to="2"/>
       <valueMapEntry from="10;11" to="10"/>
       <valueMapEntry from="14;15" to="14"/>
       <valueMapEntry from="0" to="0"/>
       <valueMapEntry from="1" to="1"/>
       <valueMapEntry from="13" to="13"/>
     </derivedVariable>

The sourceVariable Element
..........................

     sourceVariable
        :id
        :categorical=(true)
        :source
        :domain=ref categoricalDomain?
        :sourceName
        :dependsOn=ref sourceVariable?
        :label?
        :labelVariable=ref sourceVariable?
     => variable_extension* (format | stringFormat)?

This element defines a variable whose data comes from the tableData.bin member that corresponds to this .xml.

This element has the following attributes.

 -- Attribute: id
     An id is always present because this element exists to be referenced from other elements.

 -- Attribute: categorical
     Always set to true.

 -- Attribute: source
     Always set to tableData, the source-name in the corresponding tableData.bin member (see SPV Legacy Member Metadata).

 -- Attribute: sourceName
     The name of a variable within the source, corresponding to the variable-name in the tableData.bin member (see SPV Legacy Member Numeric Data).

 -- Attribute: label
     The variable label, if any.

 -- Attribute: labelVariable
     The variable-name of a variable whose string values correspond one-to-one with the values of this variable and are suitable for use as value labels.

 -- Attribute: dependsOn
     This attribute doesn't affect the display of a table.

The derivedVariable Element
...........................

     derivedVariable
        :id
        :categorical=(true)
        :value
        :dependsOn=ref sourceVariable?
     => variable_extension* (format | stringFormat)? valueMapEntry*

Like sourceVariable, this element defines a variable whose values can be used elsewhere in the visualization.  Instead of being read from a data source, the variable's data are defined by a mathematical expression.

This element has the following attributes.

 -- Attribute: id
     An id is always present because this element exists to be referenced from other elements.

 -- Attribute: categorical
     Always set to true.

 -- Attribute: value
     An expression that defines the variable's value.  In theory this could be an arbitrary expression in terms of constants, functions, and other variables, e.g. (VAR1 + VAR2) / 2.  In practice, the corpus contains only the following forms of expressions:

     constant(0)
     constant(VARIABLE)
          All zeros.  The reason why a variable is sometimes named is unknown. Sometimes the ``variable name'' has spaces in it.

     map(VARIABLE)
          Transforms the values in the named VARIABLE using the valueMapEntrys contained within the element.

 -- Attribute: dependsOn
     This attribute doesn't affect the display of a table.

The valueMapEntry Element
.........................

     valueMapEntry :from :to => EMPTY

A valueMapEntry element defines a mapping from one or more values of a source expression to a target value.  (In the corpus, the source expression is always just the name of a variable.)  Each target value requires a separate valueMapEntry.  If multiple source values map to the same target value, they can be combined or separate.

In the corpus, all of the source and target values are integers.

valueMapEntry has the following attributes.

 -- Attribute: from
     A source value, or multiple source values separated by semicolons, e.g. 0 or 13;14;15;16.

 -- Attribute: to
     The target value, e.g. 0.

The extension Element
---------------------

This is a general-purpose ``extension'' element.  Readers that don't understand a given extension should be able to safely ignore it.  The attributes on this element, and their meanings, vary based on the context.  Each known usage is described separately below.  The current extensions use attributes exclusively, without any nested elements.

container Parent Element
........................

     extension[container_extension] :combinedFootnotes=(true) => EMPTY

With container as its parent element, extension has the following attributes.

 -- Attribute: combinedFootnotes
     Always set to true in the corpus.

sourceVariable and derivedVariable Parent Element
.................................................

     extension[variable_extension] :from :helpId => EMPTY

With sourceVariable or derivedVariable as its parent element, extension has the following attributes.  A given parent element often contains several extension elements that specify the meaning of the source data's variables or sources, e.g.

     <extension from="0" helpId="corrected_model"/>
     <extension from="3" helpId="error"/>
     <extension from="4" helpId="total_9"/>
     <extension from="5" helpId="corrected_total"/>

More commonly they are less helpful, e.g.

     <extension from="0" helpId="notes"/>
     <extension from="1" helpId="notes"/>
     <extension from="2" helpId="notes"/>
     <extension from="5" helpId="notes"/>
     <extension from="6" helpId="notes"/>
     <extension from="7" helpId="notes"/>
     <extension from="8" helpId="notes"/>
     <extension from="12" helpId="notes"/>
     <extension from="13" helpId="no_help"/>
     <extension from="14" helpId="notes"/>

 -- Attribute: from
     An integer or a name like ``dimension0''.

 -- Attribute: helpId
     An identifier.

The graph Element
-----------------

     graph
        :cellStyle=ref style
        :style=ref style
     => location+ coordinates faceting facetLayout interval

     coordinates => EMPTY

graph has the following attributes.

 -- Attribute: cellStyle
 -- Attribute: style
     Each of these is the id of a style element (see SPV Detail style Element).  The former is the default style for individual cells, the latter for the entire table.

The location Element
--------------------

     location
        :part=(height | width | top | bottom | left | right)
        :method=(sizeToContent | attach | fixed | same)
        :min=dimension?
        :max=dimension?
        :target=ref (labelFrame | graph | container)?
        :value?
     => EMPTY

Each instance of this element specifies where some part of the table frame is located.  All the examples in the corpus have four instances of this element, one for each of the parts height, width, left, and top.  Some examples in the corpus add a fifth for part bottom, even though it is not clear how all of top, bottom, and height can be honored at the same time.  In any case, location seems to have little importance in representing tables; a reader can safely ignore it.

 -- Attribute: part
     The part of the table being located.

 -- Attribute: method
     How the location is determined:

     sizeToContent
          Based on the natural size of the table.  Observed only for parts height and width.

     attach
          Based on the location specified in target.  Observed only for parts top and bottom.

     fixed
          Using the value in value.  Observed only for parts top, bottom, and left.

     same
          Same as the specified target.  Observed only for part left.

 -- Attribute: min
     Minimum size.  Only observed with value 100pt.  Only observed for part width.

 -- Dependent: target
     Required when method is attach or same, not observed otherwise.  This identifies an element to attach to. Observed with the ID of title, footnote, graph, and other elements.

 -- Dependent: value
     Required when method is fixed, not observed otherwise. Observed values are 0%, 0px, 1px, and 3px on parts top and left, and 100% on part bottom.

The faceting Element
--------------------

     faceting => layer[layers1]* cross layer[layers2]*

     cross => (unity | nest) (unity | nest)

     unity => EMPTY

     nest => variableReference[vars]+

     variableReference :ref=ref (sourceVariable | derivedVariable) => EMPTY

     layer
        :variable=ref (sourceVariable | derivedVariable)
        :value
        :visible=bool?
        :method[layer_method]=(nest)?
        :titleVisible=bool?
     => EMPTY

The faceting element describes the row, column, and layer structure of the table.  Its cross child determines the row and column structure, and each layer child (if any) represents a layer.  Layers may appear before or after cross.

The cross element describes the row and column structure of the table.  It has exactly two children, the first of which describes the table's columns and the second the table's rows.  Each child is a nest element if the table has any dimensions along the axis in question, otherwise a unity element.

A nest element contains of one or more dimensions listed from innermost to outermost, each represented by variableReference child elements.  Each variable in a dimension is listed in order. See SPV Detail Variable Elements, for information on the variables that comprise a dimension.

A nest can contain a single dimension, e.g.:

     <nest>
       <variableReference ref="dimension0categories"/>
       <variableReference ref="dimension0group0"/>
       <variableReference ref="dimension0"/>
     </nest>

A nest can contain multiple dimensions, e.g.:

     <nest>
       <variableReference ref="dimension1categories"/>
       <variableReference ref="dimension1group0"/>
       <variableReference ref="dimension1"/>
       <variableReference ref="dimension0categories"/>
       <variableReference ref="dimension0"/>
     </nest>

A nest may have no dimensions, in which case it still has one variableReference child, which references a derivedVariable whose value attribute is constant(0).  In the corpus, such a derivedVariable has row or column, respectively, as its id.  This is equivalent to using a unity element in place of nest.

A variableReference element refers to a variable through its ref attribute.

Each layer element represents a dimension, e.g.:

     <layer value="0" variable="dimension0categories" visible="true"/>
     <layer value="dimension0" variable="dimension0" visible="false"/>

layer has the following attributes.

 -- Attribute: variable
     Refers to a sourceVariable or derivedVariable element.

 -- Attribute: value
     The value to select.  For a category variable, this is always 0; for a data variable, it is the same as the variable attribute.

 -- Attribute: visible
     Whether the layer is visible.  Generally, category layers are visible and data layers are not, but sometimes this attribute is omitted.

 -- Attribute: method
     When present, this is always nest.

The facetLayout Element
-----------------------

     facetLayout => tableLayout setCellProperties[scp1]*
                    facetLevel+ setCellProperties[scp2]*

     tableLayout
        :verticalTitlesInCorner=bool
        :style=ref style?
        :fitCells=(ticks both)?
     => EMPTY

The facetLayout element and its descendants control styling for the table.

Its tableLayout child has the following attributes

 -- Attribute: verticalTitlesInCorner
     If true, in the absence of corner text, row headings will be displayed in the corner.

 -- Attribute: style
     Refers to a style element.

 -- Attribute: fitCells
     Meaning unknown.

The facetLevel Element
......................

     facetLevel :level=int :gap=dimension? => axis

     axis :style=ref style => label? majorTicks

     majorTicks
        :labelAngle=int
        :length=dimension
        :style=ref style
        :tickFrameStyle=ref style
        :labelFrequency=int?
        :stagger=bool?
     => gridline?

     gridline
        :style=ref style
        :zOrder=int
     => EMPTY

Each facetLevel describes a variableReference or layer, and a table has one facetLevel element for each such element.  For example, an SPV detail member that contains four variableReference elements and two layer elements will contain six facetLevel elements.

In the corpus, facetLevel elements and the elements that they describe are always in the same order.  The correspondence may also be observed in two other ways.  First, one may use the level attribute, described below.  Second, in the corpus, a facetLevel always has an id that is the same as the id of the element it describes with _facetLevel appended.  One should not formally rely on this, of course, but it is usefully indicative.

 -- Attribute: level
     A 1-based index into the variableReference and layer elements, e.g. a facetLayout with a level of 1 describes the first variableReference in the SPV detail member, and in a member with four variableReference elements, a facetLayout with a level of 5 describes the first layer in the member.

 -- Attribute: gap
     Always observed as 0pt.

Each facetLevel contains an axis, which in turn may contain a label for the facetLevel (see SPV Detail label Element) and does contain a majorTicks element.

 -- Attribute: labelAngle
     Normally 0.  The value -90 causes inner column or outer row labels to be rotated vertically.

 -- Attribute: style
 -- Attribute: tickFrameStyle
     Each refers to a style element.  style is the style of the tick labels, tickFrameStyle the style for the frames around the labels.

The label Element
-----------------

     label
        :style=ref style
        :textFrameStyle=ref style?
        :purpose=(title | subTitle | subSubTitle | layer | footnote)?
     => text+ | descriptionGroup

     descriptionGroup
        :target=ref faceting
        :separator?
     => (description | text)+

     description :name=(variable | value) => EMPTY

     text
        :usesReference=int?
        :definesReference=int?
        :position=(subscript | superscript)?
        :style=ref style
     => TEXT

This element represents a label on some aspect of the table.

 -- Attribute: style
 -- Attribute: textFrameStyle
     Each of these refers to a style element.  style is the style of the label text, textFrameStyle the style for the frame around the label.

 -- Attribute: purpose
     The kind of entity being labeled.

A descriptionGroup concatenates one or more elements to form a label.  Each element can be a text element, which contains literal text, or a description element that substitutes a value or a variable name.

 -- Attribute: target
     The id of an element being described.  In the corpus, this is always faceting.

 -- Attribute: separator
     A string to separate the description of multiple groups, if the target has more than one.  In the corpus, this is always a new-line.

Typical contents for a descriptionGroup are a value by itself:

     <description name="value"/>

or a variable and its value, separated by a colon:

     <description name="variable"/><text>:</text><description name="value"/>

A description is like a macro that expands to some property of the target of its parent descriptionGroup.  The name attribute specifies the property.

The setCellProperties Element
-----------------------------

     setCellProperties
        :applyToConverse=bool?
     => (setStyle | setFrameStyle | setFormat | setMetaData)* union[union_]?

The setCellProperties element sets style properties of cells or row or column labels.

Interpreting setCellProperties requires answering two questions: which cells or labels to style, and what styles to use.

Which Cells?
............

     union => intersect+

     intersect => where+ | intersectWhere | alternating | EMPTY

     where
        :variable=ref (sourceVariable | derivedVariable)
        :include
     => EMPTY

     intersectWhere
        :variable=ref (sourceVariable | derivedVariable)
        :variable2=ref (sourceVariable | derivedVariable)
     => EMPTY

     alternating => EMPTY

When union is present with intersect children, each of those children specifies a group of cells that should be styled, and the total group is all those cells taken together.  When union is absent, every cell is styled.  One attribute on setCellProperties affects the choice of cells:

 -- Attribute: applyToConverse
     If true, this inverts the meaning of the cell selection: the selected cells are the ones not designated.  This is confusing, given the additional restrictions of union, but in the corpus applyToConverse is never present along with union.

An intersect specifies restrictions on the cells to be matched. Each where child specifies which values of a given variable to include.  The attributes of intersect are:

 -- Attribute: variable
     Refers to a variable, e.g. dimension0categories.  Only ``categories'' variables make sense here, but other variables, e.g. dimension0group0map, are sometimes seen.  The reader may ignore these.

 -- Attribute: include
     A value, or multiple values separated by semicolons, e.g. 0 or 13;14;15;16.

PSPP ignores setCellProperties when intersectWhere is present.

What Styles?
............

     setStyle
        :target=ref (labeling | graph | interval | majorTicks)
        :style=ref style
     => EMPTY

     setMetaData :target=ref graph :key :value => EMPTY

     setFormat
        :target=ref (majorTicks | labeling)
        :reset=bool?
     => format | numberFormat | stringFormat+ | dateTimeFormat | elapsedTimeFormat

     setFrameStyle
        :style=ref style
        :target=ref majorTicks
     => EMPTY

The set* children of setCellProperties determine the styles to set.

When setCellProperties contains a setFormat whose target references a labeling element, or if it contains a setStyle that references a labeling or interval element, the setCellProperties sets the style for table cells. The format from the setFormat, if present, replaces the cells' format.  The style from the setStyle that references labeling, if present, replaces the label's font and cell styles, except that the background color is taken instead from the interval's style, if present.

When setCellProperties contains a setFormat whose target references a majorTicks element, or if it contains a setStyle whose target references a majorTicks, or if it contains a setFrameStyle element, the setCellProperties sets the style for row or column labels. In this case, the setCellProperties always contains a single where element whose variable designates the variable whose labels are to be styled.  The format from the setFormat, if present, replaces the labels' format.  The style from the setStyle that references majorTicks, if present, replaces the labels' font and cell styles, except that the background color is taken instead from the setFrameStyle's style, if present.

When setCellProperties contains a setStyle whose target references a graph element, and one that references a labeling element, and the union element contains alternating, the setCellProperties sets the alternate foreground and background colors for the data area.  The foreground color is taken from the style referenced by the setStyle that targets the graph, the background color from the setStyle for labeling.

A reader may ignore a setCellProperties that only contains setMetaData, as well as setMetaData within other setCellProperties.

A reader may ignore a setCellProperties whose only set* child is a setStyle that targets the graph element.

The setStyle Element
....................

     setStyle
        :target=ref (labeling | graph | interval | majorTicks)
        :style=ref style
     => EMPTY

This element associates a style with the target.

 -- Attribute: target
     The id of an element whose style is to be set.

 -- Attribute: style
     The id of a style element that identifies the style to set on the target.

The setFormat Element
---------------------

     setFormat
        :target=ref (majorTicks | labeling)
        :reset=bool?
     => format | numberFormat | stringFormat+ | dateTimeFormat | elapsedTimeFormat

This element sets the format of the target, ``format'' in this case meaning the SPSS print format for a variable.

The details of this element vary depending on the schema version, as declared in the root visualization element's version attribute (see SPV Detail visualization Element).  A reader can interpret the content without knowing the schema version.

The setFormat element itself has the following attributes.

 -- Attribute: target
     Refers to an element whose style is to be set.

 -- Attribute: reset
     If this is true, this format replaces the target's previous format.  If it is false, the modifies the previous format.

The numberFormat Element
........................

     numberFormat
        :minimumIntegerDigits=int?
        :maximumFractionDigits=int?
        :minimumFractionDigits=int?
        :useGrouping=bool?
        :scientific=(onlyForSmall | whenNeeded | true | false)?
        :small=real?
        :prefix?
        :suffix?
     => affix*

Specifies a format for displaying a number.  The available options are a superset of those available from PSPP print formats.  PSPP chooses a print format type for a numberFormat as follows:

 1. If scientific is true, uses E format.

 2. If prefix is $, uses DOLLAR format.

 3. If suffix is %, uses PCT format.

 4. If useGrouping is true, uses COMMA format.

 5. Otherwise, uses F format.

For translating to a print format, PSPP uses maximumFractionDigits as the number of decimals, unless that attribute is missing or out of the range [0,15], in which case it uses 2 decimals.

 -- Attribute: minimumIntegerDigits
     Minimum number of digits to display before the decimal point.  Always observed as 0.

 -- Attribute: maximumFractionDigits
 -- Attribute: minimumFractionDigits
     Maximum or minimum, respectively, number of digits to display after the decimal point.  The observed values of each attribute range from 0 to 9.

 -- Attribute: useGrouping
     Whether to use the grouping character to group digits in large numbers.

 -- Attribute: scientific
     This attribute controls when and whether the number is formatted in scientific notation.  It takes the following values:

     onlyForSmall
          Use scientific notation only when the number's magnitude is smaller than the value of the small attribute.

     whenNeeded
          Use scientific notation when the number will not otherwise fit in the available space.

     true
          Always use scientific notation.  Not observed in the corpus.

     false
          Never use scientific notation.  A number that won't otherwise fit will be replaced by an error indication (see the errorCharacter attribute).  Not observed in the corpus.

 -- Attribute: small
     Only present when the scientific attribute is onlyForSmall, this is a numeric magnitude below which the number will be formatted in scientific notation.  The values 0 and 0.0001 have been observed.  The value 0 seems like a pathological choice, since no real number has a magnitude less than 0; perhaps in practice such a choice is equivalent to setting scientific to false.

 -- Attribute: prefix
 -- Attribute: suffix
     Specifies a prefix or a suffix to apply to the formatted number.  Only suffix has been observed, with value %.

The stringFormat Element
........................

     stringFormat => relabel* affix*

     relabel :from=real :to => EMPTY

The stringFormat element specifies how to display a string.  By default, a string is displayed verbatim, but relabel can change it.

The relabel element appears as a child of stringFormat (and of format, when it is used to format strings).  It specifies how to display a given value.  It is used to implement value labels and to display the system-missing value in a human-readable way.  It has the following attributes:

 -- Attribute: from
     The value to map.  In the corpus this is an integer or the system-missing value -1.797693134862316E300.

 -- Attribute: to
     The string to display in place of the value of from.  In the corpus this is a wide variety of value labels; the system-missing value is mapped to ..

The dateTimeFormat Element
..........................

     dateTimeFormat
        :baseFormat[dt_base_format]=(date | time | dateTime)
        :separatorChars?
        :mdyOrder=(dayMonthYear | monthDayYear | yearMonthDay)?
        :showYear=bool?
        :yearAbbreviation=bool?
        :showQuarter=bool?
        :quarterPrefix?
        :quarterSuffix?
        :showMonth=bool?
        :monthFormat=(long | short | number | paddedNumber)?
        :showWeek=bool?
        :weekPadding=bool?
        :weekSuffix?
        :showDayOfWeek=bool?
        :dayOfWeekAbbreviation=bool?
        :dayPadding=bool?
        :dayOfMonthPadding=bool?
        :hourPadding=bool?
        :minutePadding=bool?
        :secondPadding=bool?
        :showDay=bool?
        :showHour=bool?
        :showMinute=bool?
        :showSecond=bool?
        :showMillis=bool?
        :dayType=(month | year)?
        :hourFormat=(AMPM | AS_24 | AS_12)?
     => affix*

This element appears only in schema version 2.5 and earlier (see SPV Detail visualization Element).

Data to be formatted in date formats is stored as strings in legacy data, in the format yyyy-mm-ddTHH:MM:SS.SSS and must be parsed and reformatted by the reader.

The following attribute is required.

 -- Attribute: baseFormat
     Specifies whether a date and time are both to be displayed, or just one of them.

Many of the attributes' meanings are obvious.  The following seem to be worth documenting.

 -- Attribute: separatorChars
     Exactly four characters.  In order, these are used for: decimal point, grouping, date separator, time separator.  Always .,-:.

 -- Attribute: mdyOrder
     Within a date, the order of the days, months, and years. dayMonthYear is the only observed value, but one would expect that monthDayYear and yearMonthDay to be reasonable as well.

 -- Attribute: showYear
 -- Attribute: yearAbbreviation
     Whether to include the year and, if so, whether the year should be shown abbreviated, that is, with only 2 digits.  Each is true or false; only values of true and false, respectively, have been observed.

 -- Attribute: showMonth
 -- Attribute: monthFormat
     Whether to include the month (true or false) and, if so, how to format it.  monthFormat is one of the following:

     long
          The full name of the month, e.g. in an English locale, September.

     short
          The abbreviated name of the month, e.g. in an English locale, Sep.

     number
          The number representing the month, e.g. 9 for September.

     paddedNumber
          A two-digit number representing the month, e.g. 09 for September.

     Only values of true and short, respectively, have been observed.

 -- Attribute: dayType
     This attribute is always month in the corpus, specifying that the day of the month is to be displayed; a value of year is supposed to indicate that the day of the year, where 1 is January 1, is to be displayed instead.

 -- Attribute: hourFormat
     hourFormat, if present, is one of:

     AMPM
          The time is displayed with an am or pm suffix, e.g. 10:15pm.

     AS_24
          The time is displayed in a 24-hour format, e.g. 22:15.

          This is the only value observed in the corpus.

     AS_12
          The time is displayed in a 12-hour format, without distinguishing morning or evening, e.g. 10;15.

     hourFormat is sometimes present for elapsedTime formats, which is confusing since a time duration does not have a concept of AM or PM.  This might indicate a bug in the code that generated the XML in the corpus, or it might indicate that elapsedTime is sometimes used to format a time of day.

For a baseFormat of date, PSPP chooses a print format type based on the following rules:

 1. If showQuarter is true: QYR.

 2. Otherwise, if showWeek is true: WKYR.

 3. Otherwise, if mdyOrder is dayMonthYear:

      a. If monthFormat is number or paddedNumber: EDATE.

      b. Otherwise: DATE.

 4. Otherwise, if mdyOrder is yearMonthDay: SDATE.

 5. Otherwise, ADATE.

For a baseFormat of dateTime, PSPP uses YMDHMS if mdyOrder is yearMonthDay and DATETIME otherwise. For a baseFormat of time, PSPP uses DTIME if showDay is true, otherwise TIME if showHour is true, otherwise MTIME.

For a baseFormat of date, the chosen width is the minimum for the format type, adding 2 if yearAbbreviation is false or omitted.  For other base formats, the chosen width is the minimum for its type, plus 3 if showSecond is true, plus 4 more if showMillis is also true.  Decimals are 0 by default, or 3 if showMillis is true.

The elapsedTimeFormat Element
.............................

     elapsedTimeFormat
        :baseFormat[dt_base_format]=(date | time | dateTime)
        :dayPadding=bool?
        :hourPadding=bool?
        :minutePadding=bool?
        :secondPadding=bool?
        :showYear=bool?
        :showDay=bool?
        :showHour=bool?
        :showMinute=bool?
        :showSecond=bool?
        :showMillis=bool?
     => affix*

This element specifies the way to display a time duration.

Data to be formatted in elapsed time formats is stored as strings in legacy data, in the format H:MM:SS.SSS, with additional hour digits as needed for long durations, and must be parsed and reformatted by the reader.

The following attribute is required.

 -- Attribute: baseFormat
     Specifies whether a day and a time are both to be displayed, or just one of them.

The remaining attributes specify exactly how to display the elapsed time.

For baseFormat of time, PSPP converts this element to print format type DTIME; otherwise, if showHour is true, to TIME; otherwise, to MTIME.  The chosen width is the minimum for the chosen type, adding 3 if showSecond is true, adding 4 more if showMillis is also true.  Decimals are 0 by default, or 3 if showMillis is true.

The format Element
..................

     format
        :baseFormat[f_base_format]=(date | time | dateTime | elapsedTime)?
        :errorCharacter?
        :separatorChars?
        :mdyOrder=(dayMonthYear | monthDayYear | yearMonthDay)?
        :showYear=bool?
        :showQuarter=bool?
        :quarterPrefix?
        :quarterSuffix?
        :yearAbbreviation=bool?
        :showMonth=bool?
        :monthFormat=(long | short | number | paddedNumber)?
        :dayPadding=bool?
        :dayOfMonthPadding=bool?
        :showWeek=bool?
        :weekPadding=bool?
        :weekSuffix?
        :showDayOfWeek=bool?
        :dayOfWeekAbbreviation=bool?
        :hourPadding=bool?
        :minutePadding=bool?
        :secondPadding=bool?
        :showDay=bool?
        :showHour=bool?
        :showMinute=bool?
        :showSecond=bool?
        :showMillis=bool?
        :dayType=(month | year)?
        :hourFormat=(AMPM | AS_24 | AS_12)?
        :minimumIntegerDigits=int?
        :maximumFractionDigits=int?
        :minimumFractionDigits=int?
        :useGrouping=bool?
        :scientific=(onlyForSmall | whenNeeded | true | false)?
        :small=real?
        :prefix?
        :suffix?
        :tryStringsAsNumbers=bool?
        :negativesOutside=bool?
     => relabel* affix*

This element is the union of all of the more-specific format elements. It is interpreted in the same way as one of those format elements, using baseFormat to determine which kind of format to use.

There are a few attributes not present in the more specific formats:

 -- Attribute: tryStringsAsNumbers
     When this is true, it is supposed to indicate that string values should be parsed as numbers and then displayed according to numeric formatting rules.  However, in the corpus it is always false.

 -- Attribute: negativesOutside
     If true, the negative sign should be shown before the prefix; if false, it should be shown after.

The affix Element
.................

     affix
        :definesReference=int
        :position=(subscript | superscript)
        :suffix=bool
        :value
     => EMPTY

This defines a suffix (or, theoretically, a prefix) for a formatted value.  It is used to insert a reference to a footnote.  It has the following attributes:

 -- Attribute: definesReference
     This specifies the footnote number as a natural number: 1 for the first footnote, 2 for the second, and so on.

 -- Attribute: position
     Position for the footnote label.  Always superscript.

 -- Attribute: suffix
     Whether the affix is a suffix (true) or a prefix (false).  Always true.

 -- Attribute: value
     The text of the suffix or prefix.  Typically a letter, e.g. a for footnote 1, b for footnote 2, ...  The corpus contains other values: *, **, and a few that begin with at least one comma: ,b, ,c, ,,b, and ,,c.

The interval Element
--------------------

     interval :style=ref style => labeling footnotes?

     labeling
        :style=ref style?
        :variable=ref (sourceVariable | derivedVariable)
     => (formatting | format | footnotes)*

     formatting :variable=ref (sourceVariable | derivedVariable) => formatMapping*

     formatMapping :from=int => format?

     footnotes
        :superscript=bool?
        :variable=ref (sourceVariable | derivedVariable)
     => footnoteMapping*

     footnoteMapping :definesReference=int :from=int :to => EMPTY

The interval element and its descendants determine the basic formatting and labeling for the table's cells.  These basic styles are overridden by more specific styles set using setCellProperties (see SPV Detail setCellProperties Element).

The style attribute of interval itself may be ignored.

The labeling element may have a single formatting child. If present, its variable attribute refers to a variable whose values are format specifiers as numbers, e.g. value 0x050802 for F8.2. However, the numbers are not actually interpreted that way.  Instead, each number actually present in the variable's data is mapped by a formatMapping child of formatting to a format that specifies how to display it.

The labeling element may also have a footnotes child element.  The variable attribute of this element refers to a variable whose values are comma-delimited strings that list the 1-based indexes of footnote references.  (Cells without any footnote references are numeric 0 instead of strings.)

Each footnoteMapping child of the footnotes element defines the footnote marker to be its to attribute text for the footnote whose 1-based index is given in its definesReference attribute.

The style Element
-----------------

     style
        :color=color?
        :color2=color?
        :labelAngle=real?
        :border-bottom=(solid | thick | thin | double | none)?
        :border-top=(solid | thick | thin | double | none)?
        :border-left=(solid | thick | thin | double | none)?
        :border-right=(solid | thick | thin | double | none)?
        :border-bottom-color?
        :border-top-color?
        :border-left-color?
        :border-right-color?
        :font-family?
        :font-size?
        :font-weight=(regular | bold)?
        :font-style=(regular | italic)?
        :font-underline=(none | underline)?
        :margin-bottom=dimension?
        :margin-left=dimension?
        :margin-right=dimension?
        :margin-top=dimension?
        :textAlignment=(left | right | center | decimal | mixed)?
        :labelLocationHorizontal=(positive | negative | center)?
        :labelLocationVertical=(positive | negative | center)?
        :decimal-offset=dimension?
        :size?
        :width?
        :visible=bool?
     => EMPTY

A style element has an effect only when it is referenced by another element to set some aspect of the table's style.  Most of the attributes are self-explanatory.  The rest are described below.

 -- Attribute: color
     In some cases, the text color; in others, the background color.

 -- Attribute: color2
     Not used.

 -- Attribute: labelAngle
     Normally 0.  The value -90 causes inner column or outer row labels to be rotated vertically.

 -- Attribute: labelLocationHorizontal
     Not used.

 -- Attribute: labelLocationVertical
     The value positive corresponds to vertically aligning text to the top of a cell, negative to the bottom, center to the middle.

The labelFrame Element
----------------------

     labelFrame :style=ref style => location+ label? paragraph?

     paragraph :hangingIndent=dimension? => EMPTY

A labelFrame element specifies content and style for some aspect of a table.  Only labelFrame elements that have a label child are important.  The purpose attribute in the label determines what the labelFrame affects:

title
     The table's title and its style.

subTitle
     The table's caption and its style.

footnote
     The table's footnotes and the style for the footer area.

layer
     The style for the layer area.

subSubTitle
     Ignored.

The style attribute references the style to use for the area.

The label, if present, specifies the text to put into the title or caption or footnotes.  For footnotes, the label has two text children for every footnote, each of which has a usesReference attribute identifying the 1-based index of a footnote.  The first, third, fifth, ... text child specifies the content for a footnote; the second, fourth, sixth, ... child specifies the marker.  Content tends to end in a new-line, which the reader may wish to trim; similarly, markers tend to end in ..

The paragraph, if present, may be ignored, since it is always empty.

Legacy Properties
-----------------

The detail XML format has features for styling most of the aspects of a table.  It also inherits defaults for many aspects from structure XML, which has the following tableProperties element:

     tableProperties
     => generalProperties footnoteProperties cellFormatProperties borderProperties printingProperties

     generalProperties
        :hideEmptyRows=bool?
        :maximumColumnWidth=dimension?
        :maximumRowWidth=dimension?
        :minimumColumnWidth=dimension?
        :minimumRowWidth=dimension?
        :rowDimensionLabels=(inCorner | nested)?
     => EMPTY

     footnoteProperties
        :markerPosition=(superscript | subscript)?
        :numberFormat=(alphabetic | numeric)?
     => EMPTY

     cellFormatProperties => cell_style+

     any[cell_style]
        :alternatingColor=color?
        :alternatingTextColor=color?
     => style

     style
        :color=color?
        :color2=color?
        :font-family?
        :font-size?
        :font-style=(regular | italic)?
        :font-weight=(regular | bold)?
        :labelLocationVertical=(positive | negative | center)?
        :margin-bottom=dimension?
        :margin-left=dimension?
        :margin-right=dimension?
        :margin-top=dimension?
        :textAlignment=(left | right | center | decimal | mixed)?
        :decimal-offset=dimension?
     => EMPTY

     borderProperties => border_style+

     any[border_style]
        :borderStyleType=(none | solid | dashed | thick | thin | double)?
        :color=color?
     => EMPTY

     printingProperties
        :printAllLayers=bool?
        :rescaleLongTableToFitPage=bool?
        :rescaleWideTableToFitPage=bool?
        :windowOrphanLines=int?
        :continuationText?
        :continuationTextAtBottom=bool?
        :continuationTextAtTop=bool?
        :printEachLayerOnSeparatePage=bool?
     => EMPTY