start moving beyond PMModelItemInfo
[pspp] / dump-spo2.c
index 33671632755321814dcae1ac86a1d78983fbf1cb..9bef78c5668a6674a5567ca4c8d2e4cdeef36188 100644 (file)
@@ -2,6 +2,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <float.h>
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -20,10 +21,15 @@ int version;
 
 unsigned int pos;
 
+static int n_dims;
+
 #define XSTR(x) #x
 #define STR(x) XSTR(x)
 #define WHERE __FILE__":" STR(__LINE__)
 
+static void __attribute__((unused))
+hex_dump(FILE *stream, int ofs, int n);
+
 static uint8_t
 get_byte(void)
 {
@@ -108,7 +114,8 @@ match_u32_assert(uint32_t x, const char *where)
   unsigned int y = get_u32();
   if (x != y)
     {
-      fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
+      fprintf(stderr, "%s: 0x%x: expected i%u, got i%u: ", where, pos - 4, x, y);
+      hex_dump(stderr, pos - 4, 64);
       exit(1);
     }
 }
@@ -120,7 +127,8 @@ match_u16_assert(uint16_t x, const char *where)
   unsigned int y = get_u16();
   if (x != y)
     {
-      fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u\n", where, pos - 2, x, y);
+      fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u: ", where, pos - 2, x, y);
+      hex_dump(stderr, pos - 2, 64);
       exit(1);
     }
 }
@@ -185,7 +193,8 @@ match_byte_assert(uint8_t b, const char *where)
 {
   if (!match_byte(b))
     {
-      fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
+      fprintf(stderr, "%s: 0x%x: expected %02x, got %02x: ", where, pos, b, data[pos]);
+      hex_dump(stderr, pos, 64);
       exit(1);
     }
 }
@@ -249,45 +258,81 @@ all_utf8(const char *p_, size_t len)
 }
 
 static char *
-get_string1(void)
+pull_string(int len, const char *where)
 {
-  int len = data[pos++];
+  assert (len >= 0);
+  for (int i = 0; i < len - 1; i++)
+    if (!data[pos + i])
+      {
+        fprintf(stderr, "%s: %d-byte string starting at 0x%x has null byte "
+                "at offset %d: ", where, len, pos, i);
+        hex_dump(stderr, pos, len + 64);
+        exit(1);
+      }
+
   char *s = xmemdup0(&data[pos], len);
   pos += len;
   return s;
 }
 
 static char *
-get_string2(void)
+get_string2(const char *where)
 {
-  int len = data[pos] + data[pos + 1] * 256;
-  char *s = xmemdup0(&data[pos + 2], len);
-  pos += 2 + len;
-  return s;
+  return pull_string(get_u16(), where);
 }
+#define get_string2() get_string2(WHERE)
 
 static char *
-get_string(const char *where)
+get_string1(const char *where)
 {
-  if (1
-      /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
-      /*&& all_ascii(&data[pos + 4], data[pos])*/)
-    {
-      int len = data[pos] + data[pos + 1] * 256;
-      char *s = malloc(len + 1);
+  int len = data[pos++];
+  return len == 0xff ? (get_string2)(where) : pull_string(len, where);
+}
+#define get_string1() get_string1(WHERE)
 
-      memcpy(s, &data[pos + 4], len);
-      s[len] = 0;
-      pos += 4 + len;
-      return s;
+static void
+match_string1_assert(const char *exp, const char *where)
+{
+  int start = pos;
+  char *act = (get_string1)(where);
+  if (strcmp(act, exp)) 
+    {
+      fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
+              where, start, exp, act);
+      exit(1);
     }
-  else
+}
+#define match_string1_assert(x) match_string1_assert(x, WHERE)
+
+static void
+match_string2_assert(const char *exp, const char *where)
+{
+  int start = pos;
+  char *act = (get_string2)(where);
+  if (strcmp(act, exp)) 
     {
-      fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
+      fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
+              where, start, exp, act);
       exit(1);
     }
 }
-#define get_string() get_string(WHERE)
+#define match_string2_assert(x) match_string2_assert(x, WHERE)
+
+static char *
+get_string4(const char *where)
+{
+  assert(data[pos + 3] == 0);
+  return pull_string(get_u32(), where);
+}
+#define get_string4() get_string4(WHERE)
+
+static char *
+get_padded_string(int len)
+{
+  char *s = xmemdup0(&data[pos], len);
+  pos += len;
+  return s;
+}
 
 static char *
 get_string_be(const char *where)
@@ -406,6 +451,989 @@ format_name (int format, char *buf)
     }
 }
 
+static void
+parse_format(void)
+{
+  int d = data[pos++];
+  int w = data[pos++];
+  int fmt = data[pos++];
+  char buf[32];
+  printf ("%s%d.%d", format_name(fmt, buf), w, d);
+}
+
+static void
+parse_heading(const char *name)
+{
+  match_u16_assert(0xffff);
+  match_u16_assert(0);
+  match_string2_assert(name);
+  printf("%#x: %s\n", pos, name);
+}
+
+static void
+match_zeros_assert(int count, const char *where)
+{
+  for (int i = 0; i < count; i++)
+    if (data[pos + i])
+      {
+        fprintf (stderr,
+                 "%s: %#x: expected %d zeros here but offset %d is %#"PRIx8": ",
+                 where, pos, count, i, data[pos + i]);
+        hex_dump (stderr, pos, 64);
+        exit (1);
+      }
+  pos += count;
+}
+#define match_zeros_assert(count) match_zeros_assert(count, WHERE)
+
+static void
+put_safe(const char *s)
+{
+  while (*s)
+    {
+      if (*s == '\n')
+        printf ("\\n");
+      else if (*s == '\r')
+        printf ("\\r");
+      else if (*s < 0x20 || *s > 0x7e)
+        printf ("\\x%02"PRIx8, (uint8_t) *s);
+      else
+        putchar (*s);
+      s++;
+    }
+}
+
+static void parse_flexible(void);
+
+static int count;
+static void
+parse_DspString(void)
+{
+  printf("%#x: DspString#%d(", pos, count++);
+  if (match_byte(2))
+    {
+      printf("%f, \"", get_double());
+      printf("%s\")\n", get_string1());
+    }
+  else
+    {
+      match_byte_assert(1);
+      parse_format();
+      printf(" \"");
+      match_byte_assert(0);
+      match_byte_assert(1);
+      put_safe(get_string1());
+      printf("\")\n");
+    }
+  
+}
+
+static void
+match_DspString(void)
+{                               /* 05 80 */
+  match_byte_assert(5);
+  match_byte_assert(0x80);
+  parse_DspString();
+}
+
+static void
+parse_DspSimpleText(void)
+{
+  match_byte_assert(0);
+  if (match_byte(0))
+    {
+      match_zeros_assert(3);
+      if (!match_byte(0x10))
+        match_byte_assert(0);
+      match_zeros_assert(4);
+    }
+  /* Followed by DspString or DspNumber. */
+}
+
+static void
+match_DspSimpleText(void)
+{                               /* 03 80 */
+  match_byte_assert(3);
+  match_byte_assert(0x80);
+  parse_DspSimpleText();
+}
+
+static void
+parse_weirdness(void)
+{
+  match_byte_assert(1);
+  get_u32();
+  match_zeros_assert(12);
+  pos++;                        /* 90 or BC */
+  if (!match_byte(2))
+    match_byte_assert(1);
+  match_zeros_assert(5);
+  pos++;
+  match_zeros_assert(3);
+  puts(get_padded_string(32));
+}
+
+static void
+parse_NavTreeViewItem(void)
+{                               /* 07 80 */
+  int start_pos = pos;
+  match_zeros_assert(1);
+  if (!match_byte(0) && !match_byte(7) && !match_byte(2) && !match_byte(0xc))
+    match_byte_assert(8);
+  match_zeros_assert(3);
+  pos++;
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_byte_assert(0);
+  if (match_byte(0))
+    {
+      match_byte_assert(0);
+      if (!match_byte(1))
+        match_byte_assert(0);
+      match_zeros_assert(5);
+      if (!match_byte(0))
+        match_byte_assert(1);
+      match_zeros_assert(5);
+      get_string1();
+      if (match_byte(1))
+        {
+          if (data[pos] == 1)
+            {
+              parse_weirdness();
+              match_byte_assert(0);
+              pos++;
+              match_zeros_assert(11);
+              match_byte_assert(1);
+              match_zeros_assert(3);
+              get_string4();
+              match_byte_assert(0);
+              if (match_byte(0))
+                {
+                  match_zeros_assert(2);
+                  if (match_u32(8500))
+                    match_u32_assert(11000);
+                  else
+                    {
+                      match_u32_assert(11000);
+                      match_u32_assert(8500);
+                    }
+                  pos += 32;
+                  get_string1();
+                  if (!match_byte(0))
+                    match_byte_assert(1);
+                  pos++;
+                  pos++;
+                  pos++;
+                  pos++;
+                  get_string4();                /* page title */
+                  match_byte_assert(1);
+                  match_byte_assert(1);
+                  match_zeros_assert(3);
+                  get_string4();                /* page number */
+                  match_byte_assert(0);
+                  pos += 2;
+                  match_u16_assert(2);
+                }
+              parse_flexible();
+            }
+        }
+      else
+        match_zeros_assert(3);
+    }
+  //fprintf(stderr, "%#x ", pos - 16);
+  hex_dump(stdout, start_pos, pos - start_pos);
+}
+
+static void
+match_NavTreeViewItem(void)
+{
+  match_byte_assert(7);
+  match_byte_assert(0x80);
+  parse_NavTreeViewItem();
+}
+
+static void
+parse_DspNumber(void)
+{
+  printf("%#x: DspNumber#%d(", pos, count++);
+  match_byte_assert(1);
+  parse_format();
+  match_byte_assert(0x80);
+  match_byte(2);
+  printf (" %f", get_double());
+  printf (" \"%s\")\n", get_string1());
+}
+
+static void
+match_DspNumber(void)
+{
+  if (!match_byte(0x18) && !match_byte(0x19))
+    match_byte_assert(0x2a);
+  match_byte_assert(0x80);
+  parse_DspNumber();
+}
+
+static void
+parse_DspCell(void)
+{
+  match_byte_assert(0);
+}
+
+static void
+match_DspCell(void)
+{                               /* 27 80 */
+  match_byte_assert(0x27);
+  match_byte_assert(0x80);
+  parse_DspCell();
+}
+
+static void
+parse_NavLog(void)
+{
+  match_byte_assert(2);
+  pos += 32;
+}
+
+static void
+match_NavLog(void)
+{                               /* 09 80 */
+  match_byte_assert(9);
+  match_byte_assert(0x80);
+  parse_NavLog();
+}
+
+static void
+parse_category(int level, int j, int *n_leaves)
+{
+  for (size_t k = 0; k < level; k++)
+    putchar('\t');
+  get_u16(); match_byte_assert(0);
+  get_u16(); match_byte_assert(0);
+  int leaf_idx = get_u32();
+  printf("%d ", leaf_idx);
+  match_u32_assert(0);
+  if (get_u16() == 0xffff)
+    match_u16_assert(0xffff);
+  else if (!match_u16(0))
+    match_u16_assert(0x0e74);
+  match_byte_assert(0);
+  match_DspSimpleText();
+  if (match_u16(0x8018))
+    {
+      printf("18 80(%02x %02x %02x) ",
+             data[pos], data[pos + 1], data[pos + 2]);
+      pos += 3;
+    }
+  if (match_u16(0x8011))
+    {
+      printf("11 80(");
+      pos += 2;
+      for (size_t i = 0; i < 16; i++)
+        printf("%s%02x", i > 0 ? " " : "", data[pos++]);
+      printf(") ");
+    }
+  match_DspString();
+
+  int n_subcategories = get_u32();
+  if (n_subcategories)
+    assert (leaf_idx == 0);
+  else
+    {
+      assert (leaf_idx == *n_leaves);
+      ++*n_leaves;
+    }
+  for (int k = 0; k < n_subcategories; k++)
+    parse_category(level + 1, k, n_leaves);
+}
+
+static void
+parse_dimension(int i)
+{
+  printf ("%#x: dimension %d\n", pos, i);
+  if (i == 0)
+    {
+      match_zeros_assert(5);
+    }
+  else
+    {
+      match_zeros_assert(6);
+
+      int n_units16 = get_u32();
+      match_u16_assert(1);
+      for (int j = 0; j < n_units16; j++)
+        get_u16();
+
+      match_byte_assert(0);
+
+      int n_units32 = get_u32();
+      match_u16_assert(0);
+      for (int j = 0; j < n_units32; j++)
+        get_u32();
+
+      get_u16(); match_byte_assert(0);
+
+      get_u16(); match_byte_assert(0);
+      get_u16(); match_byte_assert(0);
+      match_u32_assert(0);
+    }
+
+  if (!match_u32(0))
+    match_u32_assert(1);
+
+  get_u16();
+  if (!match_u16(0xffff))
+    match_u16_assert(0x0e74);
+  match_byte_assert(0);
+  match_DspSimpleText();
+  match_DspString();
+
+  int n_leaves = 0;
+  int n_categories = get_u32();
+  for (int j = 0; j < n_categories; j++)
+    parse_category(1, j, &n_leaves);
+}
+
+static void skip_item(const char *name);
+
+static void
+parse_PMModelItemInfo(void)
+{
+  for (int i = 0; i < n_dims; i++)
+    parse_dimension(i);
+  printf("%#x: end of model\n", pos);
+
+  int n_units16 = get_u32();
+  match_u16_assert(0);
+  for (int j = 0; j < n_units16; j++)
+    get_u16();
+
+  n_units16 = get_u32();
+  match_u16_assert(1);
+  for (int j = 0; j < n_units16; j++)
+    get_u16();
+
+  match_byte_assert(0);
+  int n_units32 = get_u32();
+  match_u16_assert(0);
+  for (int j = 0; j < n_units32; j++)
+    match_u32_assert(j);
+
+  skip_item("end of PMModelItemInfo");
+}
+
+static void
+match_PMModelItemInfo(void)
+{                               /* 54 80 */
+  match_byte_assert(0x54);
+  match_byte_assert(0x80);
+  parse_PMModelItemInfo();
+  /* DspSimpleText */
+  /* DspString */
+}
+
+static void
+match_PMPivotItemTree(void)
+{                               /* 52 80 */
+  match_byte_assert(0x52);
+  match_byte_assert(0x80);
+  match_byte_assert(0);
+  match_PMModelItemInfo();
+}
+
+static void
+parse_NavHead(void)
+{
+  match_byte_assert(2);
+  match_zeros_assert(24);
+  match_byte_assert(1);
+  match_zeros_assert(3);
+  if (!match_byte(1))
+    match_byte_assert(0);
+  match_zeros_assert(3);
+  /* DspSimpleText */
+}
+
+static void
+parse_NavOleItem(void)
+{
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_zeros_assert(2);
+  pos++;
+  match_zeros_assert(9);
+  match_byte_assert(1);
+  match_zeros_assert(10);
+  match_byte_assert(1);
+  match_zeros_assert(5);
+  get_string1();
+  match_byte_assert(1);
+  parse_weirdness();
+  match_byte_assert(0);
+  pos++;
+  match_zeros_assert(11);
+  match_byte_assert(1);
+  match_zeros_assert(3);
+  get_string4();
+  match_byte_assert(0);
+}
+
+static void
+match_NavOleItem(void)
+{                               /* 0e 80 or 12 80*/
+  if (!match_byte(0x12))
+    match_byte_assert(0x0e);
+  match_byte_assert(0x80);
+  parse_NavOleItem();
+}
+
+static void
+parse_NavTitle(void)
+{
+  match_byte_assert(2);
+  match_zeros_assert(8);
+  match_u32_assert(24);
+  get_u32();
+  pos++;
+  if (!match_byte(3))
+    match_byte_assert(4);
+  match_zeros_assert(2);
+  get_u32();
+  match_u32_assert(2);
+  if (!match_u32(2))
+    match_u32_assert(1);
+}
+
+static void
+parse_NavNote(void)
+{
+  match_byte_assert(2);
+  match_zeros_assert(8);
+  match_u32_assert(24);
+  if (!match_u32(0) && !match_u32(0xffffff4b))
+    match_u32_assert(-40);
+  pos += 8;
+  if (!match_u32(1))
+    match_u32_assert(2);
+  if (!match_u32(2))
+    match_u32_assert(1);
+}
+
+static void
+parse_PTPivotController(void)
+{
+  match_byte_assert(2);
+  pos += 8;
+  match_u32_assert(100);
+  match_u32_assert(100);
+  match_u32_assert(100);
+  match_u32_assert(100);
+}
+
+static void
+parse_PVPivotView(void)
+{
+  match_byte_assert(5);
+  printf ("PVPivotView(%d)\n", get_u32());
+}
+
+static void
+parse_NDimensional__DspCell(void)
+{
+  match_byte_assert(0);
+  n_dims = get_u32();
+  printf ("NDimensional__DspCell(n_dims=%d)\n", n_dims);
+}
+
+static void
+parse_IndexedCollection(void)
+{
+  printf("IndexedCollection");
+  for (size_t i = 0; ; i++)
+    {
+      match_byte_assert(0);
+      printf("%c%d", i ? 'x' : '(', get_u32());
+      match_u16_assert(1);
+      if (!match_u16(0x8011))
+        break;
+    }
+  printf(")\n");
+}
+
+static void
+parse_PTTableLook(void)
+{
+  match_byte_assert(2);
+  match_byte_assert(2);
+  match_zeros_assert(7);
+  match_u32_assert(0x36);
+  match_u32_assert(0x12);
+}
+
+static void
+parse_PVViewDimension(void)
+{
+  while (data[pos + 1] != 0x80
+         && (data[pos] != 0xff || data[pos + 1] != 0xff))
+    {
+      assert(pos < n);
+      pos++;
+    }
+}
+
+static void
+parse_PVSeparatorStyle(void)
+{
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_zeros_assert(15);
+  pos++;
+  match_byte_assert(0x80);
+  match_byte_assert(0);
+
+  match_byte_assert(1);
+  match_zeros_assert(9);
+  while (data[pos + 1] != 0x80
+         && (data[pos] != 0xff || data[pos + 1] != 0xff))
+    {
+      assert(pos < n);
+      pos++;
+    }
+}
+
+static void
+parse_PVCellStyle(void)
+{
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_zeros_assert(5);
+  match_u32_assert(0xffffff);
+  match_zeros_assert(2);
+}
+
+static void
+skip_item(const char *name)
+{
+  int start_pos = pos;
+  printf("%#x: skipping %s bytes...", pos, name);
+  while (pos < n
+         && data[pos + 1] != 0x80
+         && !(data[pos] == 0xff && data[pos + 1] == 0xff
+              && data[pos + 2] == 0 && data[pos + 3] == 0))
+    {
+      pos++;
+    }
+  printf("until %#x:", pos);
+  hex_dump(stdout, start_pos, pos - start_pos);
+}
+
+static void
+parse_DspAnnotation(void)
+{
+  match_zeros_assert(10);
+  match_u32_assert(1);
+}
+
+static void
+parse_DspTextComponentHandle(void)
+{
+  match_byte_assert(0);
+  match_DspString();
+  printf("after DspTextComponentHandle: ");
+  for (int i = 0; ; i++)
+    if (!memcmp(&data[pos + 6 + i], "PVView", 6))
+      {
+        hex_dump(stdout, pos, i);
+        exit(0);
+      }
+}
+
+static void
+parse_flexible(void)
+{
+  int start = pos;
+  if (match_u16(0xffff))
+    {
+      match_u16_assert(0);
+      char *heading = get_string2();
+      printf("%#x: %s\n", pos, heading);
+      if (!strcmp(heading, "NavRoot"))
+        {
+          match_byte_assert(2);
+          match_zeros_assert(32);
+        }
+      else if (!strcmp(heading, "NavPivot"))
+        {
+          hex_dump(stdout, pos, 021);
+          pos += 0x21;
+        }
+      else if (!strcmp(heading, "DspCell"))
+        parse_DspCell();
+      else if (!strcmp(heading, "DspSimpleText"))
+        parse_DspSimpleText();
+      else if (!strcmp(heading, "DspNumber"))
+        parse_DspNumber();
+      else if (!strcmp(heading, "DspString"))
+        parse_DspString();
+      else if (!strcmp(heading, "NavHead"))
+        parse_NavHead();
+      else if (!strcmp(heading, "NavTreeViewItem"))
+        {
+          if (0)
+            parse_NavTreeViewItem();
+          else
+            skip_item(heading);
+        }
+      else if (!strcmp(heading, "IndexedCollection"))
+        parse_IndexedCollection();
+      else if (!strcmp(heading, "NavOleItem"))
+        parse_NavOleItem();
+      else if (!strcmp(heading, "NavTitle"))
+        parse_NavTitle();
+      else if (!strcmp(heading, "NavNote"))
+        parse_NavNote();
+      else if (!strcmp(heading, "PTPivotController"))
+        parse_PTPivotController();
+      else if (!strcmp(heading, "PVPivotView"))
+        parse_PVPivotView();
+      else if (!strcmp(heading, "PMPivotModel"))
+        match_byte_assert(3);
+      else if (!strcmp(heading, "NDimensional__DspCell"))
+        parse_NDimensional__DspCell();
+      else if (!strcmp(heading, "PMPivotItemTree"))
+        match_byte_assert(0);
+      else if (!strcmp(heading, "PMModelItemInfo"))
+        parse_PMModelItemInfo();
+      else if (!strcmp(heading, "AbstractTreeBranch"))
+        match_byte_assert(0);
+      else if (!strcmp(heading, "PTTableLook"))
+        parse_PTTableLook();
+      else if (!strcmp(heading, "PVViewDimension"))
+        parse_PVViewDimension();
+      else if (!strcmp(heading, "PVSeparatorStyle"))
+        parse_PVSeparatorStyle();
+      else if (!strcmp(heading, "PVCellStyle"))
+        parse_PVCellStyle();
+      else if (!strcmp(heading, "PVTextStyle"))
+        exit(0);
+      else if (!strcmp(heading, "DspAnnotation"))
+        parse_DspAnnotation();
+      else if (!strcmp(heading, "DspTextComponentHandle"))
+        parse_DspTextComponentHandle();
+      else
+        {
+          fprintf(stderr, "don't know %s at offset 0x%x: ", heading, start);
+          hex_dump(stderr, pos, 128);
+          assert(0);
+        }
+    }
+  else if (data[pos + 1] == 0x80)
+    {
+      if ((data[pos] == 0x2a || data[pos] == 0x18 || data[pos] == 0x19) && data[pos + 1] == 0x80)
+        match_DspNumber();
+      else if (data[pos] == 0x27 && data[pos + 1] == 0x80)
+        match_DspCell();
+      else if (data[pos] == 0x5 && data[pos + 1] == 0x80)
+        match_DspString();
+      else if (data[pos] == 0x7 && data[pos + 1] == 0x80)
+        {
+          if (0)
+            match_NavTreeViewItem();
+          else
+            {
+              pos += 2;
+              skip_item("NavTreeViewItem");
+            }
+        }
+      else if (data[pos] == 0x3 && data[pos + 1] == 0x80)
+        match_DspSimpleText();
+      else if ((data[pos] == 0x3c || data[pos] == 0x39)
+               && data[pos + 1] == 0x80)
+        {
+          /* 3c 80 */
+          /* 39 80 */
+          printf("%#x: %02x %02x ", pos, data[pos], data[pos + 1]);
+          pos += 2;
+          parse_format();
+          printf ("\n");
+/*      match_byte_assert(0x01);
+        match_byte_assert(0x02);
+        match_byte_assert(0x0d); */
+        }
+      else if ((data[pos] == 0x15 || data[pos] == 0x14)
+               && data[pos + 1] == 0x80)
+        {
+          /* 14 80 */
+          /* 15 80 */
+          pos += 2;
+          if (match_byte(2))
+            {
+              printf ("%02x 80(%f", data[pos - 2], get_double());
+              printf (" \"%s\")\n", get_string1());
+              if (match_byte(1))
+                {
+                  match_byte_assert(0);
+                  get_string1();
+                  if (!match_byte(2) && !match_byte(3))
+                    match_byte_assert(0);
+                  match_zeros_assert(3);
+                  get_string1();
+                  match_byte_assert(0);
+                  match_byte_assert(1);
+                  match_zeros_assert(3);
+                  match_byte_assert(1);
+                  match_byte_assert(0);
+                }
+            }
+          else
+            {
+              match_byte_assert(0);
+            }
+        }
+      else if (data[pos] == 0x17 || data[pos] == 0x25)
+        {
+          printf("%02x %02x(%02x %02x %02x)\n",
+                 data[pos], data[pos + 1],
+                 data[pos + 2], data[pos + 3], data[pos + 4]);
+          pos += 5;
+        }
+      else if (data[pos] == 0x9 && data[pos + 1] == 0x80)
+        {
+          match_NavLog();
+        }
+      else if (data[pos] == 0xe || data[pos] == 0x12)
+        match_NavOleItem();
+      else if (data[pos] == 0x11 || data[pos] == 0x13)
+        {
+          int type = data[pos];
+          pos += 2;
+          match_byte_assert(0);
+          if (data[pos] != 0)
+            {
+              int x = get_u32();
+              int y = get_u16();
+              if (y == 0)
+                {
+                  int index = get_u32();
+                  printf("%02x 80(footnote %d)\n", type, index);
+                }
+              else
+                printf("%02x 80(%d %d)\n", type, x, y);
+            }
+          else
+            match_zeros_assert(13);
+        }
+      else if (data[pos] == 0x29 ||
+               data[pos] == 0x2b ||
+               data[pos] == 0x2d ||
+               data[pos] == 0x31 ||
+               data[pos] == 0x32 ||
+               data[pos] == 0x4a ||
+               data[pos] == 0x4c ||
+               data[pos] == 0x4f ||
+               data[pos] == 0x4d ||
+               data[pos] == 0x50 ||
+               data[pos] == 0x36 ||
+               data[pos] == 0x52 ||
+               data[pos] == 0x53 ||
+               data[pos] == 0x54 ||
+               data[pos] == 0x55 ||
+               data[pos] == 0x57 ||
+               data[pos] == 0x56 ||
+               data[pos] == 0x58 ||
+               data[pos] == 0x5c ||
+               data[pos] == 0x5b ||
+               data[pos] == 0x5e ||
+               data[pos] == 0x62 ||
+               data[pos] == 0x64 ||
+               data[pos] == 0x4e ||
+               data[pos] == 0x51 ||
+               data[pos] == 0x59 ||
+               data[pos] == 0x5a ||
+               data[pos] == 0x5d ||
+               data[pos] == 0x66 ||
+               data[pos] == 0x60 ||
+               data[pos] == 0x68 ||
+               data[pos] == 0x48 ||
+               data[pos] == 0x6a ||
+               data[pos] == 0x37)
+        {
+          pos += 2;
+          match_byte_assert(0);
+        }
+      else if (data[pos] == 0x2c ||
+               data[pos] == 0x2e ||
+               data[pos] == 0x30 ||
+               data[pos] == 0x34 ||
+               data[pos] == 0x3d ||
+               data[pos] == 0x40 ||
+               data[pos] == 0x3f ||
+               data[pos] == 0x42 ||
+               data[pos] == 0x43 ||
+               data[pos] == 0x44 ||
+               data[pos] == 0x49 ||
+               data[pos] == 0x3e ||
+               data[pos] == 0x46)
+        {
+          printf ("%#x: %02x %02x(%02x %02x %02x)\n",
+                  pos, data[pos], data[pos + 1],
+                  data[pos + 2], data[pos + 3], data[pos + 4]);
+          pos += 2;
+          pos += 3;
+        }
+      else
+        {
+          fprintf (stderr, "%#x: unknown record", pos);
+          hex_dump (stderr, pos, 64);
+          exit(1);
+        }
+    }
+  else if (match_byte(0xa)) 
+    {
+      if (!match_byte(7))
+        match_byte_assert(0);
+      if (match_u16(0x0e74))
+        match_byte_assert(0);
+      else
+        {
+          match_zeros_assert(4);
+          if (pos == n)
+            exit (0);
+          match_zeros_assert (2);
+        }
+    }
+#if 0
+  else if (match_byte(1))
+    {
+      match_byte_assert(0);
+      get_string1();
+      if (!match_byte(2))
+        match_byte_assert(0);
+      if (match_byte(0))
+        {
+          match_zeros_assert(2);
+          get_string1();
+          if (match_byte(0x08))
+            {
+              match_byte_assert(0);
+              match_u16_assert(0x0e74);
+              match_byte_assert(0);
+            }
+          else if (match_byte(3))
+            {
+              match_byte_assert(0);
+              if (match_u16(0x0e74))
+                match_byte_assert(0);
+              else
+                {
+                  match_zeros_assert(6);
+                  if (!match_byte(0xe) && !match_byte(0x11))
+                    match_byte_assert(0);
+                  match_byte_assert(0);
+                  if (!match_u16(0x0e74))
+                    match_u16_assert(0);
+                  match_byte_assert(0);
+                }
+            }
+          else
+            {
+              match_byte_assert(0);
+              match_byte_assert(1);
+              match_zeros_assert(3);
+              match_byte_assert(1);
+              match_byte_assert(0);
+            }
+        }
+    }
+#endif
+  else if (match_u16(1))
+    {
+      int start_pos = pos;
+      char *title = get_string1();
+      printf("%#x: title(\"%s\", ", start_pos, title);
+      if (!match_u32(2))
+        match_u32_assert(0);
+      char *id = get_string1();
+      printf("\"%s\")\n", id);
+      match_byte_assert(0);
+      if (!match_u32(2))
+        match_u32_assert(3);
+      match_u16_assert(1);
+    }
+  else //if (match_u16(2) || match_u16(3) || match_u16(4) || match_u16(5) || match_u16(6) || match_u16(7) || match_u16(8) || match_u16(9))
+    skip_item("unknown");
+#if 0
+  else if (match_byte(7) || match_byte(4) || match_byte(5) || match_byte(6) || match_byte(8) || match_byte(9) || match_byte(0xb) || match_byte(0xc) || match_byte(0x15) || match_byte(0x16) || match_byte(0x17) || match_byte(0x18) || match_byte(0x1e)  || match_byte(0x1a))
+    {
+      if (!match_byte(7))
+        match_byte_assert(0);
+      if (!match_u16(0x0e74))
+        match_byte_assert(0);
+      match_byte_assert(0);
+    }
+  else if (match_byte(2) || match_byte(3))
+    {
+      match_byte_assert(0);
+      if (!match_u16(0x0e74))
+        {
+          match_zeros_assert(2);
+          if (match_byte(0))
+            {
+              match_zeros_assert(3);
+              if (match_byte(0))
+                match_zeros_assert(4);
+              else
+                {
+                  pos++;
+                  match_byte(0);
+                  match_u16_assert(0x0e74);
+                }
+            }
+        }
+      //match_byte_assert(0);
+    }
+  else if (match_byte(0xd) || match_byte(0xe) || match_byte(0xf)
+           || match_byte(0x11) || match_byte(0x12) || match_byte(0x13)
+           || match_byte(0x14) || match_byte(0x1b))
+    {
+      if (!match_byte(0x07))
+        match_byte_assert(0);
+      if (!match_u16(0x0e74))
+        match_zeros_assert(11);
+      else
+        match_byte_assert(0);
+    }
+  else if (match_byte(0xe3) || match_byte(0xdb) || match_byte(0xd8) || match_byte(0xe9) || match_byte(0xf3))
+    {
+      match_byte_assert(0x0e);
+      match_byte_assert(0x74);
+      match_byte_assert(0x0e);
+      match_byte_assert(0);
+    }
+  else if (match_byte(0x9d) || match_byte(0x9e) || match_byte(0x9c))
+    match_u32_assert(0x000e741a);
+  else if (match_byte(0x10))
+    {
+      match_byte_assert(0);
+      if (match_byte(0))
+        match_zeros_assert(10);
+      else
+        {
+          match_u16_assert(0x0e74);
+          match_byte_assert(0);
+        }
+    }
+  else if (match_byte(0x39) || match_byte(0x3a) || match_byte(0x3b))
+    match_u32_assert(0x000e7409);
+  else
+    {
+      //fprintf (stderr, "bad record start at offset %x: ", pos);
+      hex_dump (stderr, pos, 64);
+      assert(0);
+    }
+#endif
+}
+
+
 
 int
 main(int argc, char *argv[])
@@ -448,7 +1476,7 @@ main(int argc, char *argv[])
       exit(1);
     }
   n = s.st_size;
-  data = malloc(n);
+  data = malloc(n + 256);
   if (!data)
     {
       perror("malloc");
@@ -459,9 +1487,285 @@ main(int argc, char *argv[])
       perror("read");
       exit(1);
     }
+  for (int i = 0; i < 256; i++)
+    data[n + i] = i % 2 ? 0xaa : 0x55;
   close(fd);
 
-  setvbuf (stdout, NULL, _IOLBF, 0);
+  setvbuf (stdout, NULL, _IONBF, 0);
+
+  match_byte_assert(4);
+  match_u32_assert(0);
+  match_string1_assert("SPSS Output Document");
+  match_u32_assert(1);
+  match_byte_assert(0x63);
+
+  while (pos < n)
+    {
+      if (data[pos] == 0)
+        {
+          //printf("zero\n");
+          pos++;
+        }
+      else
+        parse_flexible();
+    }
+  exit(0);
+
+  parse_heading("NavRoot");
+  match_byte_assert(2);
+  match_zeros_assert(32);
+
+  parse_heading("DspSimpleText");
+  match_zeros_assert(10);
+
+  parse_heading("DspString");
+  parse_DspString();
+
+  parse_heading("NavTreeViewItem");
+  match_byte_assert(0);
+  if (!match_u32(1))
+    match_u32_assert(0);
+  match_byte_assert(2);
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_zeros_assert(9);
+  match_u32_assert(1);
+
+  match_u32_assert(0);
+  match_u32_assert(0x18);
+  if (!match_u32(0))
+    match_u32_assert(0xffffffd8);
+  match_u32_assert(0xffffffde);
+  match_u32_assert(0x18);
+  if (!match_u32(0))
+    match_u32_assert(0xffffffd8);
+  match_u32_assert(0x28);
+  match_u32_assert(0x28);
+  pos += 8;
+  if (data[pos] == 0)
+    {
+      match_zeros_assert(5);
+
+      if (match_u32(8500))
+        match_u32_assert(11000);
+      else
+        {
+          match_u32_assert(11000);
+          match_u32_assert(8500);
+        }
+      pos += 32;
+      get_string1();
+      if (!match_byte(0))
+        match_byte_assert(1);
+      pos++;
+      pos++;
+      pos++;
+      pos++;
+      get_string4();                /* page title */
+      match_byte_assert(1);
+      match_byte_assert(1);
+      match_zeros_assert(3);
+      get_string4();                /* page number */
+      match_byte_assert(0);
+      pos += 2;
+      match_u16_assert(2);
+    }
+
+  if (data[pos + 9] != 'L')
+    exit(0);
+  parse_heading("NavLog");
+  parse_NavLog();
+  for (;;)
+    {
+      if (data[pos] == 0)
+        {
+          //printf("zero\n");
+          pos++;
+        }
+      else
+        parse_flexible();
+    }
+  exit(0);
+  puts(get_padded_string(32));
+  if (!match_u32(80))
+    match_u32_assert(132);
+  match_zeros_assert(8);
+  match_u32_assert(1);
+  printf ("0x%x\n", pos);
+  get_string4();
+  match_byte_assert(0);
+
+  parse_heading("NavHead");
+  parse_NavHead();
+  match_NavTreeViewItem();
+  match_zeros_assert(3);
+
+  parse_heading("NavTitle");
+  pos += 33;
+  match_DspSimpleText();
+  match_DspString();
+  match_NavTreeViewItem();
+
+  match_byte_assert(1);
+  match_byte_assert(1);
+  match_u32_assert(-19);
+  match_zeros_assert(12);
+  match_byte_assert(0xbc);
+  match_byte_assert(2);
+  match_zeros_assert(9);
+  match_byte_assert(0x22);
+  puts(get_padded_string(32));
+  match_u32_assert(80);
+  match_zeros_assert(8);
+  match_u32_assert(1);
+  get_string4();
+  match_byte_assert(0);
+
+  parse_heading("NavNote");
+  match_byte_assert(2);
+  match_zeros_assert(8);
+  match_u32_assert(24);
+  if (!match_u32(0))
+    match_u32_assert(-40);
+  pos += 8;
+  match_u32_assert(2);
+  match_u32_assert(1);
+  match_DspSimpleText();
+  match_DspString();
+  match_NavTreeViewItem();
+  match_byte_assert(1);
+
+  parse_heading("PTPivotController");
+  match_byte_assert(2);
+  pos += 8;
+  match_u32_assert(100);
+  match_u32_assert(100);
+  match_u32_assert(100);
+  match_u32_assert(100);
+
+  parse_heading("PVPivotView");
+  match_u32_assert(5);
+  match_byte_assert(0);
+
+  parse_heading("PMPivotModel");
+  match_byte_assert(3);
+
+  parse_heading("NDimensional__DspCell");
+  match_byte_assert(0);
+  match_u32_assert(1);
+
+  parse_heading("IndexedCollection");
+  match_byte_assert(0);
+  pos++;
+  match_zeros_assert(3);
+  match_byte_assert(1);
+  match_byte_assert(0);
+  match_zeros_assert(7);
+
+  while (pos < n)
+    {
+      if (data[pos] == 0)
+        {
+          printf("zero\n");
+          pos++;
+        }
+      else
+        parse_flexible();
+    }
+
+  match_byte_assert(1);
+  match_byte_assert(0);
+  puts(get_string1());
+  if (!match_u32(0))
+    match_u32_assert(2);
+  puts(get_string1());
+
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_byte_assert(0);
+  match_byte_assert(0);
+  match_byte_assert(0);
+  match_byte_assert(1);
+  match_byte_assert(0);
+
+  exit (0);
+
+  parse_heading("PMPivotItemTree");
+  match_byte_assert(0);
+
+  parse_heading("AbstractTreeBranch");
+  match_byte_assert(0);
+
+  parse_heading("PMModelItemInfo");
+  parse_PMModelItemInfo();
+  match_DspSimpleText();
+  match_DspString();
+
+  match_u32_assert(7);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(6);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(2);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(2);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+  match_PMPivotItemTree();
+
+  match_u32_assert(0);
+
+  /* ...unknown... */
+
+  while (data[pos] != 0xff || data[pos + 1] != 0xff)
+    pos++;
+  parse_heading("PVViewDimension");
+
+  int i;
+  for (i = 0; data[pos + i] != 0xff || data[pos + i + 1] != 0xff; i++)
+    ;
+  hex_dump(stdout, pos, i);
+
+  printf ("%#x: end of successful parse\n", pos);
 
   return 0;
 }