Lots more details.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 5 Jun 2017 05:55:36 +0000 (22:55 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 5 Jun 2017 05:55:36 +0000 (22:55 -0700)
dump.c
spv-file-format.texi

diff --git a/dump.c b/dump.c
index f6a8b49f4857ba9903dd4a835109a1637a1fff73..8a820d7b86c12ad2ca78b1be232b9993a643cc0a 100644 (file)
--- a/dump.c
+++ b/dump.c
@@ -12,6 +12,7 @@
 #include <unistd.h>
 #include "u8-mbtouc.h"
 
 #include <unistd.h>
 #include "u8-mbtouc.h"
 
+static const char *filename;
 static uint8_t *data;
 static size_t n;
 
 static uint8_t *data;
 static size_t n;
 
@@ -248,24 +249,25 @@ get_end(void)
 }
 
 static void __attribute__((unused))
 }
 
 static void __attribute__((unused))
-hex_dump(int ofs, int n)
+hex_dump(FILE *stream, int ofs, int n)
 {
   for (int i = 0; i < n; i++)
     {
       int c = data[ofs + i];
 #if 1
       if (i && !(i % 16))
 {
   for (int i = 0; i < n; i++)
     {
       int c = data[ofs + i];
 #if 1
       if (i && !(i % 16))
-        printf("-");
+        putc('-', stream);
       else
       else
-        printf(" ");
+        putc(' ', stream);
 #endif
 #endif
-      printf("%02x", c);
+      fprintf(stream, "%02x", c);
     }
   for (int i = 0; i < n; i++)
     {
       int c = data[ofs + i];
     }
   for (int i = 0; i < n; i++)
     {
       int c = data[ofs + i];
-      printf("%c", c >= 32 && c < 127 ? c : '.');
+      putc(c >= 32 && c < 127 ? c : '.', stream);
     }
     }
+  putc('\n', stream);
 }
 
 static char *
 }
 
 static char *
@@ -862,7 +864,19 @@ dump_title(void)
         dump_value(stdout, 0);
       else
         match_byte_assert (0x58);
         dump_value(stdout, 0);
       else
         match_byte_assert (0x58);
-      get_u32 ();
+      int n = get_u32();
+      if (n >= 0)
+        {
+          /* Appears to be the number of references to a footnote. */
+          printf ("  <references n=\"%d\"/>\n", n);
+        }
+      else if (n == -2)
+        {
+          /* The user deleted the footnote references. */
+          printf ("  <deleted/>\n");
+        }
+      else
+        assert(0);
       printf ("</footnote>\n");
     }
 }
       printf ("</footnote>\n");
     }
 }
@@ -962,18 +976,21 @@ dump_fonts(void)
       match_be32_assert(1);
       get_be32();
       printf("<settings layer=\"%d\"", get_be32());
       match_be32_assert(1);
       get_be32();
       printf("<settings layer=\"%d\"", get_be32());
-      if (!get_byte())
+      if (!get_bool())
         printf(" skipempty=\"false\"");
         printf(" skipempty=\"false\"");
-      if (!get_byte())
+      if (!get_bool())
         printf(" showdimensionincorner=\"false\"");
         printf(" showdimensionincorner=\"false\"");
-      if (!get_byte())
+      if (!get_bool())
         printf(" markers=\"numeric\"");
         printf(" markers=\"numeric\"");
-      if (!get_byte())
+      if (!get_bool())
         printf(" footnoteposition=\"subscript\"");
       get_byte();
       int nbytes = get_be32();
         printf(" footnoteposition=\"subscript\"");
       get_byte();
       int nbytes = get_be32();
+      int end = pos + nbytes;
       printf("\n");
       printf("\n");
-      hex_dump(pos, nbytes);
+      while (pos + 4 <= end)
+        printf(" %d", get_be32());
+      pos = end;
       printf("\n");
       pos += nbytes;
       char *notes = get_string_be();
       printf("\n");
       pos += nbytes;
       char *notes = get_string_be();
@@ -1003,7 +1020,7 @@ dump_fonts(void)
   const char *locale = get_string();
   printf ("<locale>%s</locale>\n", locale);
 
   const char *locale = get_string();
   printf ("<locale>%s</locale>\n", locale);
 
-  get_u32();            /* Seen: 0, UINT32_MAX, 2, 3, 4, 5, 6, 8, 9, 21, 24. */
+  printf ("<layer>%d</layer>\n", get_u32());
   if (!match_byte(0))
     match_byte_assert(1);
   match_byte_assert(0);
   if (!match_byte(0))
     match_byte_assert(1);
   match_byte_assert(0);
@@ -1024,9 +1041,9 @@ dump_fonts(void)
       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
         match_byte_assert(0);
     }
       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
         match_byte_assert(0);
     }
-  printf("<format decimal=\"%c\" grouping=\"", decimal);
+  printf("<format decimal=\"%c\"", decimal);
   if (grouping)
   if (grouping)
-    putchar(grouping);
+    printf(" grouping=\"%c\"", grouping);
   printf("\"/>\n");
   if (match_u32(5))
     {
   printf("\"/>\n");
   if (match_u32(5))
     {
@@ -1039,22 +1056,22 @@ dump_fonts(void)
   /* The last chunk is an outer envelope that contains two inner envelopes.
      The second inner envelope has some interesting data like the encoding and
      the locale. */
   /* The last chunk is an outer envelope that contains two inner envelopes.
      The second inner envelope has some interesting data like the encoding and
      the locale. */
+  int outer_end = get_end();
   if (version == 3)
     {
   if (version == 3)
     {
-      int outer_end = get_end();
-
       /* First inner envelope: byte*33 int[n] int*[n]. */
       int inner_len = get_u32();
       int inner_end = pos + inner_len;
       int array_start = pos + 33;
       match_byte_assert(0);
       pos++;                    /* 0, 1, 10 seen. */
       /* First inner envelope: byte*33 int[n] int*[n]. */
       int inner_len = get_u32();
       int inner_end = pos + inner_len;
       int array_start = pos + 33;
       match_byte_assert(0);
       pos++;                    /* 0, 1, 10 seen. */
-      match_byte_assert(0);
-      pos++;                    /* 0...11 seen. */
-      if (!match_byte(0) && !match_byte(1) && !match_byte(2))
-        match_byte_assert(3);
-      if (!match_byte(0) && !match_byte(2))
-        match_byte_assert(3);
+      get_bool();
+
+      /* 0=en 1=de 2=es 3=it 5=ko 6=pl 8=zh-tw 10=pt_BR 11=fr */
+      printf("lang=%d ", get_byte());
+
+      printf ("variable_mode=%d\n", get_byte());
+      printf ("value_mode=%d\n", get_byte());
       if (!match_u64(0))
         match_u64_assert(UINT64_MAX);
       match_u32_assert(0);
       if (!match_u64(0))
         match_u64_assert(UINT64_MAX);
       match_u32_assert(0);
@@ -1062,17 +1079,40 @@ dump_fonts(void)
       match_u32_assert(0);
       match_u32_assert(0);
       match_byte_assert(0);
       match_u32_assert(0);
       match_u32_assert(0);
       match_byte_assert(0);
-      if (!match_byte(0))
-        match_byte_assert(1);
+      get_bool();
       match_byte_assert(1);
       pos = array_start;
       match_byte_assert(1);
       pos = array_start;
-#if 1
-      printf("widths:");
-      while (pos < inner_end)
-        printf(" %d", get_u32());
-      printf("\n");
-#endif
-      pos = inner_end;;
+
+      assert(get_end() == inner_end);
+      printf("<heights>");
+      int n_heights = get_u32();
+      for (int i = 0; i < n_heights; i++)
+        {
+          if (i)
+            putchar(' ');
+          printf("%d", get_u32());
+        }
+      printf("</heights>\n");
+
+      int n_style_map = get_u32();
+      for (int i = 0; i < n_style_map; i++)
+        {
+          uint64_t cell = get_u64();
+          int style = get_u16();
+          printf("<style-map cell=\"%llu\" style=\"%d\"/>\n", cell, style);
+        }
+
+      int n_styles = get_u32();
+      for (int i = 0; i < n_styles; i++)
+        {
+          printf("<cell-style index=\"%d\"", i);
+          dump_style(stdout);
+          dump_style2(stdout);
+          printf("/>\n");
+        }
+
+      pos = get_end();
+      assert(pos == inner_end);
 
       /* Second inner envelope. */
       assert(get_end() == outer_end);
 
       /* Second inner envelope. */
       assert(get_end() == outer_end);
@@ -1086,18 +1126,15 @@ dump_fonts(void)
       match_byte_assert(0);
 
       printf("<command>%s</command>\n", get_string());
       match_byte_assert(0);
 
       printf("<command>%s</command>\n", get_string());
-      printf("<subcommand>%s</subcommand>\n", get_string());
+      printf("<command-local>%s</command-local>\n", get_string());
       printf("<language>%s</language>\n", get_string());
       printf("<charset>%s</charset>\n", get_string());
       printf("<locale>%s</locale>\n", get_string());
 
       printf("<language>%s</language>\n", get_string());
       printf("<charset>%s</charset>\n", get_string());
       printf("<locale>%s</locale>\n", get_string());
 
-      if (!match_byte(0))
-        match_byte_assert(1);
+      get_bool();
       match_byte_assert(0);
       match_byte_assert(0);
-      if (!match_byte(0))
-        match_byte_assert(1);
-      if (!match_byte(0))
-        match_byte_assert(1);
+      get_bool();
+      get_bool();
 
       printf("<epoch2>%d</epoch2>\n", get_u32());
 
 
       printf("<epoch2>%d</epoch2>\n", get_u32());
 
@@ -1155,21 +1192,61 @@ dump_fonts(void)
         match_u32_assert(0);
 
       match_byte_assert('.');
         match_u32_assert(0);
 
       match_byte_assert('.');
-      if (!match_byte(0))
-        match_byte_assert(1);
+      get_bool();
 
       if (pos < outer_end)
         {
 
       if (pos < outer_end)
         {
-          printf("<seed>%d</seed>\n", get_u32());
+          get_u32();
           match_u32_assert(0);
         }
       assert(pos == outer_end);
 
       pos = outer_end;
     }
           match_u32_assert(0);
         }
       assert(pos == outer_end);
 
       pos = outer_end;
     }
-  else
+  else if (outer_end != pos)
     {
     {
-      pos = get_end();
+      pos += 14;
+      printf("<command>%s</command>\n", get_string());
+      printf("<command-local>%s</command-local>\n", get_string());
+      printf("<language>%s</command>\n", get_string());
+      printf("<charset>%s</charset>\n", get_string());
+      printf("<locale>%s</locale>\n", get_string());
+      get_bool();
+      match_byte_assert(0);
+      get_bool();
+      get_bool();
+
+      printf("<epoch2>%d</epoch2>\n", get_u32());
+      int decimal = data[pos];
+      int grouping = data[pos + 1];
+      if (match_byte('.'))
+        {
+          if (!match_byte(',') && !match_byte('\''))
+            match_byte_assert(' ');
+        }
+      else
+        {
+          match_byte_assert(',');
+          if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
+            match_byte_assert(0);
+        }
+      printf("<format decimal=\"%c\"", decimal);
+      if (grouping)
+        printf(" grouping=\"%c\"", grouping);
+      printf("\"/>\n");
+      if (match_u32(5))
+        {
+          for (int i = 0; i < 5; i++)
+            printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
+        }
+      else
+        match_u32_assert(0);
+
+      match_byte_assert('.');
+      get_bool();
+
+      assert(pos == outer_end);
+      pos = outer_end;
     }
 }
 
     }
 }
 
@@ -1182,10 +1259,11 @@ main(int argc, char *argv[])
       exit (1);
     }
 
       exit (1);
     }
 
-  int fd = open(argv[1], O_RDONLY);
+  filename = argv[1];
+  int fd = open(filename, O_RDONLY);
   if (fd < 0)
     {
   if (fd < 0)
     {
-      fprintf (stderr, "%s: open failed (%s)", argv[1], strerror (errno));
+      fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
       exit (1);
     }
 
       exit (1);
     }
 
@@ -1217,8 +1295,16 @@ main(int argc, char *argv[])
   assert(version == 1 || version == 3);
 
   match_byte_assert(1);
   assert(version == 1 || version == 3);
 
   match_byte_assert(1);
-  for (int i = 0; i < 4; i++)
-    get_bool();
+  bool number_footnotes = get_bool();
+  printf("<footnote markers=\"%s\"/>\n",
+         number_footnotes ? "number" : "letter");
+  bool rotate_inner_column_labels = get_bool();
+  bool rotate_outer_row_labels = get_bool();
+  printf("x=%d\n", get_bool());
+  printf("<rotate-labels inner-column=\"%s\" outer-row=\"%s\"/>",
+         rotate_inner_column_labels ? "yes" : "no",
+         rotate_outer_row_labels ? "yes" : "no");
+  //fprintf(stderr, "option-number=%d\n", get_u32());
   get_u32();
 
   int min_col_width = get_u32();
   get_u32();
 
   int min_col_width = get_u32();
index 6e7480ba12aca0f32cadcd6a016f3ea35ce35061..ed2e96092d75f27a2f5611a1a50cd0c6ca493bc1 100644 (file)
@@ -607,7 +607,12 @@ An SPV light member begins with a 39-byte header:
 Header @result{}
     01 00
     (i1 @math{|} i3)[@t{version}]
 Header @result{}
     01 00
     (i1 @math{|} i3)[@t{version}]
-    01 bool*4 int
+    bool
+    bool[@t{show-numeric-markers}]
+    bool[@t{rotate-inner-column-labels}]
+    bool[@t{rotate-outer-row-labels}]
+    bool
+    int
     int[@t{min-column-width}] int[@t{max-column-width}]
     int[@t{min-row-width}] int[@t{max-row-width}]
     int64[@t{table-id}]
     int[@t{min-column-width}] int[@t{max-column-width}]
     int[@t{min-row-width}] int[@t{max-row-width}]
     int64[@t{table-id}]
@@ -619,6 +624,18 @@ some of the other data in the member.  We will refer to ``version 1''
 and ``version 3'' later on and use v1(@dots{}) and v3(@dots{}) for
 version-specific formatting (as described previously).
 
 and ``version 3'' later on and use v1(@dots{}) and v3(@dots{}) for
 version-specific formatting (as described previously).
 
+If @code{show-numeric-markers} is 1, footnote markers are shown as
+numbers, starting from 1; otherwise, they are shown as letters,
+starting from @samp{a}.
+
+If @code{rotate-inner-column-labels} is 1, then column labels closest
+to the data are rotated to be vertical; otherwise, they are shown
+in the normal way.
+
+If @code{rotate-outer-row-labels} is 1, then row labels farthest from
+the data are rotated to be vertical; otherwise, they are shown in the
+normal way.
+
 @code{table-id} is a binary version of the @code{tableId} attribute in
 the structure member that refers to the detail member.  For example,
 if @code{tableId} is @code{-4122591256483201023}, then @code{table-id}
 @code{table-id} is a binary version of the @code{tableId} attribute in
 the structure member that refers to the detail member.  For example,
 if @code{tableId} is @code{-4122591256483201023}, then @code{table-id}
@@ -860,11 +877,24 @@ TableSettings @result{}
     bool[@t{footnote-marker-position}]
     v3(
       byte
     bool[@t{footnote-marker-position}]
     v3(
       byte
-      be32[@t{n}] byte*[@t{n}]
+      count(
+        Breakpoints[@t{row-breaks}] Breakpoints[@t{column-breaks}]
+        Keeps[@t{row-keeps}] Keeps[@t{column-keeps}]
+        PointKeeps[@t{row-keeps}] PointKeeps[@t{column-keeps}]
+      )
       bestring[@t{notes}]
       bestring[@t{table-look}]
       00...
     )
       bestring[@t{notes}]
       bestring[@t{table-look}]
       00...
     )
+
+Breakpoints @result{} be32[@t{n-breaks}] be32*[@t{n-breaks}]
+
+Keeps @result{} be32[@t{n-keeps}] Keep*@t{n-keeps}
+Keep @result{} be32[@t{offset}] be[@t{n}]
+
+PointKeeps @result{} be32[@t{n-point-keeps}] PointKeep*@t{n-point-keeps}
+PointKeep @result{} be32[@t{offset}] be32 be32
+
 @end format
 @end cartouche
 
 @end format
 @end cartouche
 
@@ -886,6 +916,20 @@ shown as numbers starting from 1.
 When @code{footnote-marker-position} is 1, footnote markers are shown
 as superscripts, otherwise as subscripts.
 
 When @code{footnote-marker-position} is 1, footnote markers are shown
 as superscripts, otherwise as subscripts.
 
+The Breakpoints are rows or columns after which there is a page break;
+for example, a row break of 1 requests a page break after the second
+row.  Usually no breakpoints are specified, indicating that page
+breaks should be selected automatically.
+
+The Keeps are ranges of rows or columns to be kept together without a
+page break; for example, a row Keep with @code{offset} 1 and @code{n}
+10 requests that the 10 rows starting with the second row be kept
+together.  Usually no Keeps are specified.
+
+The PointKeeps seem to be generated automatically based on
+user-specified Keeps.  They seems to indicate a conversion from rows
+or columns to pixel or point offsets.
+
 @code{notes} is a text string that contains user-specified notes.  It
 is displayed when the user hovers the cursor over the table, like
 ``alt text'' on a webpage.  It is not printed.  It is usually empty.
 @code{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.
@@ -901,33 +945,59 @@ TableSettings ends with an arbitrary number of null bytes.
 @cartouche
 @format
 Formats @result{}
 @cartouche
 @format
 Formats @result{}
-    int[@t{nwidths}] int*[@t{nwidths}]
+    int[@t{n-widths}] int*[@t{n-widths}]
     string[@t{encoding}]
     string[@t{encoding}]
-    int (00 @math{|} 01) 00 (00 @math{|} 01)
+    int[@t{current-layer}]
+    bool[@t{digit-grouping}] bool[@t{leading-zero}] bool
     int[@t{epoch}]
     byte[@t{decimal}] byte[@t{grouping}]
     CustomCurrency
     int[@t{epoch}]
     byte[@t{decimal}] byte[@t{grouping}]
     CustomCurrency
-    v1(i0)
-    v3(count(count(X5) count(X6)))
-
-CustomCurrency @result{} int[@t{n-ccs}] string*[@t{n-ccs}]
+    count(
+      v1(X0?)
+      v3(count(X1 count(X2)) count(X3))
 
 
-X5 @result{} byte*33 int[@t{n}] int*[@t{n}]
-X6 @result{}
+X0 @result{}
+    byte*14
+    string[@t{command}] string[@t{command-local}]
+    string[@t{language}] string[@t{charset}] string[@t{locale}]
+    bool 00 bool bool
+    int[@t{epoch}]
+    byte[@t{decimal}] byte[@t{grouping}]
+    CustomCurrency
+    byte[@t{missing}] bool
+
+X1 @result{}
+    byte*2
+    byte[@t{lang}]
+    byte[@t{variable-mode}]
+    byte[@t{value-mode}]
+    int*2
+    00*17
+    bool
+    01
+X2 @result{}
+    int[@t{n-heights}] int*[@t{n-heights}]
+    int[@t{n-style-map}] BlankMap*[@t{n-style-map}]
+    int[@t{n-styles}] StylePair*[@t{n-styles}]
+    count((i0 i0)?)
+StyleMap @result{} int64[@t{cell-index}] int16[@t{style-index}]
+X3 @result{}
     01 00 (03 @math{|} 04) 00 00 00
     01 00 (03 @math{|} 04) 00 00 00
-    string[@t{command}] string[@t{subcommand}]
+    string[@t{command}] string[@t{command-local}]
     string[@t{language}] string[@t{charset}] string[@t{locale}]
     string[@t{language}] string[@t{charset}] string[@t{locale}]
-    (00 @math{|} 01) 00 bool bool
+    bool 00 bool bool
     int[@t{epoch}]
     byte[@t{decimal}] byte[@t{grouping}]
     double[@t{small}] 01
     (string[@t{dataset}] string[@t{datafile}] i0 int[@t{date}] i0)?
     CustomCurrency
     byte[@t{missing}] bool (i2000000 i0)?
     int[@t{epoch}]
     byte[@t{decimal}] byte[@t{grouping}]
     double[@t{small}] 01
     (string[@t{dataset}] string[@t{datafile}] i0 int[@t{date}] i0)?
     CustomCurrency
     byte[@t{missing}] bool (i2000000 i0)?
+
+CustomCurrency @result{} int[@t{n-ccs}] string*[@t{n-ccs}]
 @end format
 @end cartouche
 
 @end format
 @end cartouche
 
-If @code{nwidths} is nonzero, then the accompanying integers are
+If @code{n-widths} is nonzero, then the accompanying integers are
 column widths as manually adjusted by the user.  (Row heights are
 computed automatically based on the widths.)
 
 column widths as manually adjusted by the user.  (Row heights are
 computed automatically based on the widths.)
 
@@ -950,6 +1020,13 @@ are @samp{.} and @samp{,}.
 @samp{'} (apostrophe), @samp{ } (space), and zero (presumably
 indicating that digits should not be grouped).
 
 @samp{'} (apostrophe), @samp{ } (space), and zero (presumably
 indicating that digits should not be grouped).
 
+@code{command} describes the statistical procedure that generated the
+output, in English.  It is not necessarily the literal syntax name of
+the procedure: for example, NPAR TESTS becomes ``Nonparametric
+Tests.''  @code{command-local} is the procedure's name, translated
+into the output language; it is often empty and, when it is not,
+sometimes the same as @code{command}.
+
 @code{dataset} is the name of the dataset analyzed to produce the
 output, e.g.@: @code{DataSet1}, and @code{datafile} the name of the
 file it was read from, e.g.@: @file{C:\Users\foo\bar.sav}.  The latter
 @code{dataset} is the name of the dataset analyzed to produce the
 output, e.g.@: @code{DataSet1}, and @code{datafile} the name of the
 file it was read from, e.g.@: @file{C:\Users\foo\bar.sav}.  The latter
@@ -971,6 +1048,9 @@ following strings are CCA through CCE format strings.  @xref{Custom
 Currency Formats,,, pspp, PSPP}.  Most commonly these are all
 @code{-,,,} but other strings occur.
 
 Currency Formats,,, pspp, PSPP}.  Most commonly these are all
 @code{-,,,} but other strings occur.
 
+@code{missing} is the character used to indicate that a cell contains
+a missing value.  It is always observed as @samp{.}.
+
 @node SPV Light Member Dimensions
 @subsection Dimensions
 
 @node SPV Light Member Dimensions
 @subsection Dimensions
 
@@ -985,8 +1065,8 @@ DimUnknown @result{}
     byte[@t{d1}]
     (00 @math{|} 01 @math{|} 02)[@t{d2}]
     (i0 @math{|} i2)[@t{d3}]
     byte[@t{d1}]
     (00 @math{|} 01 @math{|} 02)[@t{d2}]
     (i0 @math{|} i2)[@t{d3}]
-    (00 @math{|} 01)[@t{d4}]
-    (00 @math{|} 01)[@t{d5}]
+    bool[@t{d4}]
+    bool[@t{d5}]
     01
     int[@t{d6}]
 @end format
     01
     int[@t{d6}]
 @end format
@@ -1016,7 +1096,7 @@ are really categories; the others just serve as grouping constructs.
 Category @result{} Value[@t{name}] (Leaf @math{|} Group)
 Leaf @result{} 00 00 00 i2 int[@t{index}] i0
 Group @result{}
 Category @result{} Value[@t{name}] (Leaf @math{|} Group)
 Leaf @result{} 00 00 00 i2 int[@t{index}] i0
 Group @result{}
-    (00 @math{|} 01)[@t{merge}] 00 01 (i0 @math{|} i2)[@t{data}]
+    bool[@t{merge}] 00 01 (i0 @math{|} i2)[@t{data}]
     i-1 int[@t{n-subcategories}] Category*[@t{n-subcategories}]
 @end format
 @end cartouche
     i-1 int[@t{n-subcategories}] Category*[@t{n-subcategories}]
 @end format
 @end cartouche
@@ -1106,7 +1186,7 @@ RawValue @result{}
     01 ValueMod int[@t{format}] double[@t{x}]
   @math{|} 02 ValueMod int[@t{format}] double[@t{x}]
     string[@t{varname}] string[@t{vallab}] (01 @math{|} 02 @math{|} 03)
     01 ValueMod int[@t{format}] double[@t{x}]
   @math{|} 02 ValueMod int[@t{format}] double[@t{x}]
     string[@t{varname}] string[@t{vallab}] (01 @math{|} 02 @math{|} 03)
-  @math{|} 03 string[@t{local}] ValueMod string[@t{id}] string[@t{c}] (00 @math{|} 01)[@t{type}]
+  @math{|} 03 string[@t{local}] ValueMod string[@t{id}] string[@t{c}] bool[@t{type}]
   @math{|} 04 ValueMod int[@t{format}] string[@t{vallab}] string[@t{varname}]
     (01 @math{|} 02 @math{|} 03) string[@t{s}]
   @math{|} 05 ValueMod string[@t{varname}] string[@t{varlabel}] (01 @math{|} 02 @math{|} 03)
   @math{|} 04 ValueMod int[@t{format}] string[@t{vallab}] string[@t{varname}]
     (01 @math{|} 02 @math{|} 03) string[@t{s}]
   @math{|} 05 ValueMod string[@t{varname}] string[@t{varlabel}] (01 @math{|} 02 @math{|} 03)
@@ -1261,15 +1341,17 @@ A ValueMod can specify special modifications to a Value.
 ValueMod @result{}
     31 i0 (i0 @math{|} i1 string[@t{subscript}])
     v1(00 (i1 @math{|} i2) 00 00 int 00 00)
 ValueMod @result{}
     31 i0 (i0 @math{|} i1 string[@t{subscript}])
     v1(00 (i1 @math{|} i2) 00 00 int 00 00)
-    v3(count(FormatString
-             (31 Style | 58)
-             (31 Style2 | 58)))
+    v3(count(FormatString StylePair))
   @math{|} 31 int[@t{n-refs}] int16*[@t{n-refs}] Format
   @math{|} 58
 
 Format @result{} 00 00 count(FormatString Style 58)
 FormatString @result{} count((count((i0 58)?) (58 @math{|} 31 string))?)
 
   @math{|} 31 int[@t{n-refs}] int16*[@t{n-refs}] Format
   @math{|} 58
 
 Format @result{} 00 00 count(FormatString Style 58)
 FormatString @result{} count((count((i0 58)?) (58 @math{|} 31 string))?)
 
+StylePair @result{}
+    (31 Style | 58)
+    (31 Style2 | 58)
+
 Style @result{}
     bool[@t{bold}] bool[@t{italic}] bool[@t{underline}] bool[@t{show}]
     string[@t{fgcolor}] string[@t{bgcolor}]
 Style @result{}
     bool[@t{bold}] bool[@t{italic}] bool[@t{underline}] bool[@t{show}]
     string[@t{fgcolor}] string[@t{bgcolor}]