some success with dissecting data and dimensions
[pspp] / dump-spo.c
index 2a75bef6a928fd81d55b6fc2f948eb45b404e1ec..1ca9f0a03f3fda520956e510c9db883a84767d9c 100644 (file)
@@ -93,6 +93,15 @@ match_u32(uint32_t x)
   return false;
 }
 
+bool
+match_u16(uint16_t x)
+{
+  if (get_u16() == x)
+    return true;
+  pos -= 2;
+  return false;
+}
+
 static void
 match_u32_assert(uint32_t x, const char *where)
 {
@@ -105,6 +114,18 @@ match_u32_assert(uint32_t x, const char *where)
 }
 #define match_u32_assert(x) match_u32_assert(x, WHERE)
 
+static void
+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);
+      exit(1);
+    }
+}
+#define match_u16_assert(x) match_u16_assert(x, WHERE)
+
 static bool __attribute__((unused))
 match_u64(uint64_t x)
 {
@@ -203,6 +224,15 @@ is_ascii(uint8_t p)
   return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t';
 }
 
+static int
+count_zeros(const uint8_t *p)
+{
+  size_t n = 0;
+  while (p[n] == 0)
+    n++;
+  return n;
+}
+
 static bool __attribute__((unused))
 all_utf8(const char *p_, size_t len)
 {
@@ -218,6 +248,24 @@ all_utf8(const char *p_, size_t len)
   return true;
 }
 
+static char *
+get_string1(void)
+{
+  int len = data[pos++];
+  char *s = xmemdup0(&data[pos], len);
+  pos += len;
+  return s;
+}
+
+static char *
+get_string2(void)
+{
+  int len = data[pos] + data[pos + 1] * 256;
+  char *s = xmemdup0(&data[pos + 2], len);
+  pos += 2 + len;
+  return s;
+}
+
 static char *
 get_string(const char *where)
 {
@@ -304,190 +352,20 @@ char_dump(FILE *stream, int ofs, int n)
   putc('\n', stream);
 }
 
-static char *
-dump_counted_string(void)
-{
-  int inner_end = get_end();
-  if (pos == inner_end)
-    return NULL;
-
-  if (match_u32(5))
-    {
-      match_u32_assert(0);
-      match_byte_assert(0x58);
-    }
-  else
-    match_u32_assert(0);
-
-  char *s = NULL;
-  if (match_byte(0x31))
-    s = get_string();
-  else
-    match_byte_assert(0x58);
-  if (pos != inner_end)
-    {
-      fprintf(stderr, "inner end discrepancy\n");
-      exit(1);
-    }
-  return s;
-}
-
-static void
-dump_style(FILE *stream)
-{
-  if (match_byte(0x58))
-    return;
-
-  match_byte_assert(0x31);
-  if (get_bool())
-    printf (" bold=\"yes\"");
-  if (get_bool())
-    printf (" italic=\"yes\"");
-  if (get_bool())
-    printf (" underline=\"yes\"");
-  if (!get_bool())
-    printf (" show=\"no\"");
-  char *fg = get_string();     /* foreground */
-  char *bg = get_string();     /* background */
-  char *font = get_string();     /* font */
-  int size = get_byte() * (72. / 96.);
-  fprintf(stream, " fgcolor=\"%s\" bgcolor=\"%s\" font=\"%s\" size=\"%dpt\"",
-          fg, bg, font, size);
-}
-
-static void
-dump_style2(FILE *stream)
-{
-  if (match_byte(0x58))
-    return;
 
-  match_byte_assert(0x31);
-  uint32_t halign = get_u32();
-  printf (" halign=\"%s\"",
-          halign == 0 ? "center"
-          : halign == 2 ? "left"
-          : halign == 4 ? "right"
-          : halign == 6 ? "decimal"
-          : halign == 0xffffffad ? "mixed"
-          : "<error>");
-  int valign = get_u32();
-  printf (" valign=\"%s\"",
-          valign == 0 ? "center"
-          : valign == 1 ? "top"
-          : valign == 3 ? "bottom"
-          : "<error>");
-  printf (" offset=\"%gpt\"", get_double());
-  int l = get_u16();
-  int r = get_u16();
-  int t = get_u16();
-  int b = get_u16();
-  printf (" margins=\"%d %d %d %d\"", l, r, t, b);
-}
-
-static char *
-dump_nested_string(FILE *stream)
+static int
+compare_int(const void *a_, const void *b_)
 {
-  char *s = NULL;
-
-  match_byte_assert (0);
-  match_byte_assert (0);
-  int outer_end = get_end();
-  s = dump_counted_string();
-  if (s)
-    fprintf(stream, " \"%s\"", s);
-  dump_style(stream);
-  match_byte_assert(0x58);
-  if (pos != outer_end)
-    {
-      fprintf(stderr, "outer end discrepancy\n");
-      exit(1);
-    }
-
-  return s;
+  const int *a = a_;
+  const int *b = b_;
+  return *a < *b ? -1 : *a > *b;
 }
 
-static void
-dump_value_modifier(FILE *stream)
-{
-  if (match_byte (0x31))
-    {
-      if (match_u32 (0))
-        {
-          fprintf(stream, "<special0");
-          if (match_u32 (1))
-            {
-              /* Corpus frequencies:
-                 124 "a"
-                 12 "b"
-                 8 "a, b"
-
-                 The given text is appended to the cell in a subscript font.
-              */
-              fprintf(stream, " subscript=\"%s\"", get_string());
-            }
-          else
-            match_u32_assert (0);
-
-          if (version == 1)
-            {
-              /* We only have one SPV file for this version (with many
-                 tables). */
-              match_byte(0);
-              if (!match_u32(1))
-                match_u32_assert(2);
-              match_byte(0);
-              match_byte(0);
-              if (!match_u32(0) && !match_u32(1) && !match_u32(2) && !match_u32(3) && !match_u32(4) && !match_u32(5) && !match_u32(6) && !match_u32(7) && !match_u32(8) && !match_u32(9))
-                match_u32_assert(10);
-              match_byte(0);
-              match_byte(0);
-              fprintf(stream, "/>\n");
-              return;
-            }
-
-          int outer_end = get_end();
-          
-          /* This counted-string appears to be a template string,
-             e.g. "Design\: [:^1:]1 Within Subjects Design\: [:^1:]2". */
-          char *template = dump_counted_string();
-          if (template)
-            fprintf(stream, " template=\"%s\"", template);
-
-          dump_style(stream);
-          dump_style2(stream);
-          if (pos != outer_end)
-            {
-              fprintf(stderr, "outer end discrepancy\n");
-              exit(1);
-            }
-          fprintf(stream, "/>\n");
-        }
-      else
-        {
-          int count = get_u32();
-          fprintf(stream, "<footnote-ref indexes=\"");
-          for (int i = 0; i < count; i++)
-            {
-              if (i)
-                putc(' ', stream);
-              fprintf(stream, "%d", get_u16());
-            }
-          putc('"', stream);
-          match_byte_assert(0);
-          match_byte_assert(0);
-          dump_nested_string(stream);
-          fprintf(stream, "/>\n");
-        }
-    }
-  else
-    match_byte_assert (0x58);
-}
 
 static const char *
-format_to_string (int type)
+format_name (int format, char *buf)
 {
-  static char tmp[16];
-  switch (type)
+  switch (format)
     {
     case 1: return "A";
     case 2: return "AHEX";
@@ -524,797 +402,265 @@ format_to_string (int type)
     case 37: return "CCE";
     case 38: return "EDATE";
     case 39: return "SDATE";
-    default:
-      assert(false);
-      sprintf(tmp, "<%d>", type);
-      return tmp;
+    default: sprintf(buf, "(%d)", format); return buf;
     }
 }
 
 static void
-dump_value(FILE *stream, int level)
+dump_DspNumber(void)
 {
-  match_byte(0);
-  match_byte(0);
-  match_byte(0);
-  match_byte(0);
+  match_byte_assert(1);
+  int d = get_byte();
+  int w = get_byte();
+  int fmt = get_byte();
+  char buf[64];
+  printf ("%s%d.%d ", format_name (fmt, buf), w, d);
 
-  for (int i = 0; i <= level; i++)
-    fprintf (stream, "    ");
+  match_byte_assert(0x80);
+  match_byte_assert(2);
+  printf ("%f ", get_double ());
+  printf ("\"%s\"\n", get_string1 ());
 
-  printf ("%02x: value (%d)\n", pos, data[pos]);
-  if (match_byte (1))
-    {
-      unsigned int format;
-      double value;
-
-      dump_value_modifier(stream);
-      format = get_u32 ();
-      value = get_double ();
-      fprintf (stream, "<number value=\"%.*g\" format=\"%s%d.%d\"/>\n",
-               DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
-    }
-  else if (match_byte (2))
-    {
-      unsigned int format;
-      char *var, *vallab;
-      double value;
-
-      dump_value_modifier (stream);
-      format = get_u32 ();
-      value = get_double ();
-      var = get_string ();
-      vallab = get_string ();
-      fprintf (stream, "<numeric-datum value=\"%.*g\" format=\"%s%d.%d\"",
-              DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
-      if (var[0])
-        fprintf (stream, " variable=\"%s\"", var);
-      if (vallab[0])
-        fprintf (stream, " label=\"%s\"", vallab);
-      fprintf (stream, "/>\n");
-      if (!match_byte (1) && !match_byte(2))
-        match_byte_assert (3);
-    }
-  else if (match_byte (3))
-    {
-      char *text =  get_string();
-      dump_value_modifier(stream);
-      char *identifier = get_string();
-      char *text_eng = get_string();
-      fprintf (stream, "<string c=\"%s\"", text_eng);
-      if (identifier[0])
-        fprintf (stream, " identifier=\"%s\"", identifier);
-      if (strcmp(text_eng, text))
-        fprintf (stream, " local=\"%s\"", text);
-      fprintf (stream, "/>\n");
-      if (!match_byte (0))
-        match_byte_assert(1);
-    }
-  else if (match_byte (4))
-    {
-      unsigned int format;
-      char *var, *vallab, *value;
-
-      dump_value_modifier(stream);
-      format = get_u32 ();
-      vallab = get_string ();
-      var = get_string ();
-      if (!match_byte(1) && !match_byte(2))
-        match_byte_assert (3);
-      value = get_string ();
-      fprintf (stream, "<string-datum value=\"%s\" format=\"%s%d.%d\"",
-              value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
-      if (var[0])
-        fprintf (stream, " variable=\"%s\"", var);
-      if (vallab[0])
-        fprintf (stream, " label=\"%s\"/>\n", vallab);
-      fprintf (stream, "/>\n");
-    }
-  else if (match_byte (5))
-    {
-      dump_value_modifier(stream);
-      char *name = get_string ();
-      char *label = get_string ();
-      fprintf (stream, "<variable name=\"%s\"", name);
-      if (label[0])
-        fprintf (stream, " label=\"%s\"", label);
-      fprintf (stream, "/>\n");
-      if (!match_byte(1) && !match_byte(2))
-        match_byte_assert(3);
-    }
-  else
+  for (;;)
     {
-      printf ("else %#x\n", pos);
-      dump_value_modifier(stream);
-
-      char *base = get_string();
-      int x = get_u32();
-      fprintf (stream, "<template format=\"%s\">\n", base);
-      for (int i = 0; i < x; i++)
+      if (data[pos] == 0xff)
         {
-          int y = get_u32();
-          if (!y)
-            y = 1;
-          else
-            match_u32_assert(0);
-          for (int j = 0; j <= level + 1; j++)
-            fprintf (stream, "    ");
-          fprintf (stream, "<substitution index=\"%d\">\n", i + 1);
-          for (int j = 0; j < y; j++)
-            dump_value (stream, level + 2);
-          for (int j = 0; j <= level + 1; j++)
-            fprintf (stream, "    ");
-          fprintf (stream, "</substitution>\n");
+          printf ("\nff exit");
+          break;
         }
-      for (int j = 0; j <= level; j++)
-        fprintf (stream, "    ");
-      fprintf (stream, "</template>\n");
-    }
-}
 
-static int
-compare_int(const void *a_, const void *b_)
-{
-  const int *a = a_;
-  const int *b = b_;
-  return *a < *b ? -1 : *a > *b;
-}
-
-static void
-check_permutation(int *a, int n, const char *name)
-{
-  int b[n];
-  memcpy(b, a, n * sizeof *a);
-  qsort(b, n, sizeof *b, compare_int);
-  for (int i = 0; i < n; i++)
-    if (b[i] != i)
-      {
-        fprintf(stderr, "bad %s permutation:", name);
-        for (int i = 0; i < n; i++)
-          fprintf(stderr, " %d", a[i]);
-        putc('\n', stderr);
-        exit(1);
-      }
-}
-
-static void
-dump_category(FILE *stream, int level, int **indexes, int *allocated_indexes,
-              int *n_indexes)
-{
-  for (int i = 0; i <= level; i++)
-    fprintf (stream, "    ");
-  printf ("<category>\n");
-  dump_value (stream, level + 1);
-
-  bool merge = get_bool();
-  match_byte_assert (0);
-  int unindexed = get_bool();
-
-  int x = get_u32 ();
-  pos -= 4;
-  if (!match_u32 (0))
-    match_u32_assert (2);
-
-  int indx = get_u32();
-  int n_categories = get_u32();
-  if (indx == -1)
-    {
-      if (merge)
+      if (data[pos] == 0x80 && data[pos + 1] == 1)
         {
-          for (int i = 0; i <= level + 1; i++)
-            fprintf (stream, "    ");
-          fprintf (stream, "<merge/>\n");
+          pos += 2;
+          int d = get_byte();
+          int w = get_byte();
+          int fmt = get_byte();
+          char buf[64];
+          printf ("\n%% %s%d.%d\n", format_name (fmt, buf), w, d);
         }
-      assert (unindexed);
-    }
-  else
-    {
-      assert (!merge);
-      assert (!unindexed);
-      assert (x == 2);
-      assert (n_categories == 0);
-      if (*n_indexes >= *allocated_indexes)
+      else if (data[pos] == 0x80 && data[pos + 1] == 2)
         {
-          *allocated_indexes = *allocated_indexes ? 2 * *allocated_indexes : 16;
-          *indexes = realloc(*indexes, *allocated_indexes * sizeof **indexes);
+          pos += 2;
+          printf ("\n%f ", get_double ());
+          printf ("'%s'\n", get_string1 ());
         }
-      (*indexes)[(*n_indexes)++] = indx;
-    }
-
-  if (n_categories == 0)
-    {
-      for (int i = 0; i <= level + 1; i++)
-        fprintf (stream, "    ");
-      fprintf (stream, "<category-index>%d</category-index>\n", indx);
-    }
-  for (int i = 0; i < n_categories; i++)
-    dump_category (stream, level + 1, indexes, allocated_indexes, n_indexes);
-  for (int i = 0; i <= level; i++)
-    fprintf (stream, "    ");
-  printf ("</category>\n");
-}
-
-static int
-dump_dim(int indx)
-{
-  int n_categories;
-
-  printf ("<dimension index=\"%d\">\n", indx);
-  dump_value (stdout, 0);
-
-  /* This byte is usually 0 but many other values have been spotted.
-     No visible effect. */
-  pos++;
-
-  /* This byte can cause data to be oddly replicated. */
-  if (!match_byte(0) && !match_byte(1))
-    match_byte_assert(2);
-
-  if (!match_u32(0))
-    match_u32_assert(2);
-
-  bool show_dim_label = get_bool();
-  if (show_dim_label)
-    printf("  <show-dim-label/>\n");
-
-  bool hide_all_labels = get_bool();
-  if (hide_all_labels)
-    printf("  <hide-all-labels/>\n");
-
-  match_byte_assert(1);
-  if (!match_u32(UINT32_MAX))
-    match_u32_assert(indx);
-
-  n_categories = get_u32();
-
-  int *indexes = NULL;
-  int n_indexes = 0;
-  int allocated_indexes = 0;
-  for (int i = 0; i < n_categories; i++)
-    dump_category (stdout, 0, &indexes, &allocated_indexes, &n_indexes);
-  check_permutation(indexes, n_indexes, "categories");
-
-  fprintf (stdout, "</dimension>\n");
-  return n_indexes;
-}
-
-int n_dims;
-static int dim_n_cats[64];
-#define MAX_DIMS (sizeof dim_n_cats / sizeof *dim_n_cats)
-
-static void
-dump_dims(void)
-{
-  n_dims = get_u32();
-  assert(n_dims < MAX_DIMS);
-  for (int i = 0; i < n_dims; i++)
-    dim_n_cats[i] = dump_dim (i);
-}
-
-static void
-dump_data(void)
-{
-  /* The first three numbers add to the number of dimensions. */
-  int l = get_u32();
-  int r = get_u32();
-  int c = n_dims - l - r;
-  match_u32_assert(c);
-
-  /* The next n_dims numbers are a permutation of the dimension numbers. */
-  int a[n_dims];
-  for (int i = 0; i < n_dims; i++)
-    {
-      int dim = get_u32();
-      a[i] = dim;
-
-      const char *name = i < l ? "layer" : i < l + r ? "row" : "column";
-      printf ("<%s dimension=\"%d\"/>\n", name, dim);
-    }
-  check_permutation(a, n_dims, "dimensions");
-
-  int x = get_u32();
-  printf ("<data>\n");
-  for (int i = 0; i < x; i++)
-    {
-      unsigned int indx = get_u32();
-      printf ("    <datum index=\"%d\" coords=", indx);
-
-      int coords[MAX_DIMS];
-      for (int i = n_dims; i-- > 0; )
+      else if (data[pos] == 0x80 && data[pos + 1] == 0 && data[pos + 2] == 3)
+        pos += 3;
+      else if (data[pos] == 0x80 && count_zeros(&data[pos + 1]) == 10)
+        pos += 11;
+      else if (data[pos] == 0x1 && data[pos + 1] == 0xff)
         {
-          coords[i] = indx % dim_n_cats[i];
-          indx /= dim_n_cats[i];
+          pos += 2;
+          printf ("\n\"%s\"\n", get_string2 ());
         }
-      for (int i = 0; i < n_dims; i++)
-        printf("%c%d", i ? ',' : '"', coords[i]);
-
-      printf ("\">\n");
-      match_u32_assert(0);
-      if (version == 1)
-        match_byte(0);
-      dump_value(stdout, 1);
-      fprintf (stdout, "    </datum>\n");
+      else if (data[pos] == 0x1 && data[pos + 1])
+        {
+          pos += 1;
+          printf ("\n\"%s\"\n", get_string1 ());
+        }
+      else
+        printf ("%02x ", get_byte());
     }
-  printf ("</data>\n");
 }
 
 static void
-dump_title(void)
+dump_cell(void)
 {
-  printf ("<title-local>\n");
-  dump_value(stdout, 0);
-  match_byte(1);
-  printf ("</title-local>\n");
-
-  printf ("<subtype>\n");
-  dump_value(stdout, 0);
-  match_byte(1);
-  printf ("</subtype>\n");
-
-  match_byte_assert(0x31);
-
-  printf ("<title-c>\n");
-  dump_value(stdout, 0);
-  match_byte(1);
-  printf ("</title-c>\n");
-
-  if (match_byte(0x31))
-    {
-      printf ("<user-caption>\n");
-      dump_value(stdout, 0);
-      printf ("</user-caption>\n");
-    }
-  else
-    match_byte_assert(0x58);
-  if (match_byte(0x31))
+  static const int cell_prefix[] = {
+    0x00, 0x03, 0x80,
+    0x00, 0x00, 0x00, 0x00, 0x00, -1 /* 00 or 10 */, 0x00, 0x00, 0x00, 0x00,
+
+    /*13  14    15  16  17  18  19 */
+    -1, 0x80, 0x01, -1, -1, -1, -1,
+  };
+  size_t cell_prefix_len = sizeof cell_prefix / sizeof *cell_prefix;
+  if (!match_bytes(pos, cell_prefix, cell_prefix_len))
     {
-      printf ("<caption>\n");
-      dump_value(stdout, 0);
-      printf ("</caption>\n");
+      printf ("match failed at %x\n", pos);
+      return;
     }
-  else
-    match_byte_assert(0x58);
 
-  int n_footnotes = get_u32();
-  for (int i = 0; i < n_footnotes; i++)
+  char buf[64];
+  printf ("cell %s%d.%d ",
+          format_name (data[pos + 18], buf),
+          data[pos + 17],
+          data[pos + 16]);
+
+  int len = cell_prefix_len;
+  if (data[pos + 19] == 0)
     {
-      printf ("<footnote index=\"%d\">\n", i);
-      dump_value(stdout, 0);
-      /* Custom footnote marker string. */
-      if (match_byte (0x31))
-        dump_value(stdout, 0);
-      else
-        match_byte_assert (0x58);
-      int n = get_u32();
-      if (n >= 0)
+      assert (data[pos + 13] == 5);
+      if (data[pos + 20] == 0)
+        {
+          int count = (data[pos + 22]);
+          printf ("%d %d \"%.*s\"",
+                  data[pos + 21], data[pos + 22],
+                  count, &data[pos + 23]);
+          len = 23 + count;
+        }
+      else if (data[pos + 20] == 1
+               && data[pos + 21] == 0xff
+               && data[pos + 22] == 0xff)
+        {
+          int count = 255;
+          printf ("%d \"%.*s\"", count, data[pos + 23],
+                  &data[pos + 24]);
+          len = 23 + count;
+        }
+      else if (data[pos + 20] == 1 && data[pos + 21] == 255)
         {
-          /* Appears to be the number of references to a footnote. */
-          printf ("  <references n=\"%d\"/>\n", n);
+          int count = data[pos + 22] + (data[pos + 23] << 8);
+          printf ("\"%.*s\"",
+                  count, &data[pos + 24]);
+          len = 24 + count;
         }
-      else if (n == -2)
+      else if (data[pos + 20] == 1)
         {
-          /* The user deleted the footnote references. */
-          printf ("  <deleted/>\n");
+          int count = (data[pos + 21]);
+          printf ("\"%.*s\"",
+                  count, &data[pos + 22]);
+          len = 22 + count;
         }
       else
-        assert(0);
-      printf ("</footnote>\n");
+        assert (false);
     }
-}
-
-static void
-dump_fonts(void)
-{
-  match_byte(0);
-  for (int i = 1; i <= 8; i++)
+  else if (data[pos + 19] == 128 && data[pos + 20] == 2)
     {
-      printf ("<style index=\"%d\"", i);
-      match_byte_assert(i);
-      match_byte_assert(0x31);
-      printf(" font=\"%s\"", get_string());
-
-      printf(" size=\"%gpt\"", get_float());
-
-      int style = get_u32();
-      if (style & 1)
-        printf(" bold=\"true\"");
-      if (style & 2)
-        printf(" italic=\"true\"");
-
-      bool underline = data[pos++];
-      if (underline)
-        printf(" underline=\"true\"");
-
-      int halign = get_u32();
-      printf(" halign=%d", halign);
-
-      int valign = get_u32();
-      printf(" valign=%d", valign);
-
-      printf (" fgcolor=\"%s\"", get_string());
-      printf (" bgcolor=\"%s\"", get_string());
-
-      if (!match_byte(0))
-        match_byte_assert(1);
-
-      char *alt_fgcolor = get_string();
-      if (alt_fgcolor[0])
-        printf (" altfg=\"%s\"", alt_fgcolor);
-      char *alt_bgcolor = get_string();
-      if (alt_bgcolor[0])
-        printf (" altbg=\"%s\"", alt_bgcolor);
-
-      if (version > 1)
+      /* pos + 13 is usually 22...53, and it's 3 more than the
+         " xx 80" separator between cells  */
+      printf ("xxx%x ", data[pos + 13]);
+      double d = *(double *) &data[pos + 21];
+      len = 29;
+      const union
         {
-          printf(" margins=\"");
-          for (int i = 0; i < 4; i++)
-            {
-              if (i)
-                putchar(' ');
-              printf("%d", get_u32());
-            }
-          putchar('"');
+          uint8_t b[8];
+          double d;
         }
+      sysmis = {.b = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}};
+      if (d == sysmis.d)
+        printf ("sysmis");
+      else
+        printf ("%f", d);
 
-      printf ("/>\n");
-    }
-
-  int x1 = get_u32();
-  int x1_end = pos + x1;
-  printf("<borders>\n");
-  match_be32_assert(1);
-  int n_borders = get_be32();
-  for (int i = 0; i < n_borders; i++)
-    {
-      int type = get_be32();
-      int stroke = get_be32();
-      int color = get_be32();
-      printf("  <border type=\"%d\" stroke=\"%s\" color=\"#%06x\"/>\n",
-             type,
-             (stroke == 0 ? "none"
-              : stroke == 1 ? "solid"
-              : stroke == 2 ? "dashed"
-              : stroke == 3 ? "thick"
-              : stroke == 4 ? "thin"
-              : stroke == 5 ? "double"
-              : "<error>"),
-             color);
-    }
-  bool grid = get_byte();
-  pos += 3;
-  printf("  <grid show=\"%s\"/>\n", grid ? "yes" : "no");
-  printf("</borders>\n");
-  assert(pos == x1_end);
-
-  int skip = get_u32();
-  assert(skip == 18 || skip == 25);
-  pos += skip;
-
-  int x3 = get_u32();
-  int x3_end = pos + x3;
-  if (version == 3)
-    {
-      match_be32_assert(1);
-      get_be32();
-      printf("<settings layer=\"%d\"", get_be32());
-      if (!get_bool())
-        printf(" skipempty=\"false\"");
-      if (!get_bool())
-        printf(" showdimensionincorner=\"false\"");
-      if (!get_bool())
-        printf(" markers=\"numeric\"");
-      if (!get_bool())
-        printf(" footnoteposition=\"subscript\"");
-      get_byte();
-      int nbytes = get_be32();
-      int end = pos + nbytes;
-      printf("\n");
-      while (pos + 4 <= end)
-        printf(" %d", get_be32());
-      pos = end;
-      printf("\n");
-      pos += nbytes;
-      char *notes = get_string_be();
-      if (notes[0])
-        printf(" notes=\"%s\"", notes);
-      char *look = get_string_be();
-      if (look[0])
-        printf(" look=\"%s\"", look);
-      printf(">\n");
-    }
-  pos = x3_end;
-
-  /* Manual column widths, if present. */
-  int count = get_u32();
-  if (count > 0)
-    {
-      printf("<columnwidths>");
-      for (int i = 0; i < count; i++)
+      if (data[pos + 29] < 0xff
+          && all_utf8((char *) &data[pos + 30], data[pos + 29]))
         {
-          if (i)
-            putchar(' ');
-          printf("%d", get_u32());
+          printf (" \"%.*s\"", (int) data[pos + 29],
+                  &data[pos + 30]);
+          len += data[pos + 29] + 1;
         }
-      printf("</columnwidths>\n");
+      else
+        assert (false);
     }
-
-  const char *locale = get_string();
-  printf ("<locale>%s</locale>\n", locale);
-
-  printf ("<layer>%d</layer>\n", get_u32());
-  if (!match_byte(0))
-    match_byte_assert(1);
-  if (!match_byte(0))
-    match_byte_assert(1);
-  if (!match_byte(0))
-    match_byte_assert(1);
-  printf("<epoch>%d</epoch>\n", get_u32());
-
-  int decimal = data[pos];
-  int grouping = data[pos + 1];
-  if (match_byte('.'))
+  else if (data[pos + 19] == 128 && data[pos + 20] == 1 &&
+           data[pos + 21] == 0)
     {
-      if (!match_byte(',') && !match_byte('\''))
-        match_byte_assert(' ');
+      if (data[pos + 23] < 0xff
+          && all_utf8((char *) &data[pos + 24], data[pos + 23]))
+        {
+          printf (" \"%.*s\"", (int) data[pos + 23],
+                  &data[pos + 24]);
+          len = 24 + data[pos + 23];
+        }
+      else
+        assert (false);
     }
   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);
+      printf ("xxx%d %d %d %d",
+              data[pos + 19], data[pos + 20],
+              data[pos + 21], data[pos + 22]);
+      assert(false);
     }
-  else
-    match_u32_assert(0);
-
-  /* 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)
-    {
-      /* 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. */
-      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);
-      match_u32_assert(0);
-      match_u32_assert(0);
-      match_u32_assert(0);
-      match_byte_assert(0);
-      get_bool();
-      match_byte_assert(1);
-      pos = array_start;
-
-      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=\"%lu\" 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);
-
-      match_byte_assert(1);
-      match_byte_assert(0);
-      if (!match_byte(3) && !match_byte(4))
-        match_byte_assert(5);
-      match_byte_assert(0);
-      match_byte_assert(0);
-      match_byte_assert(0);
+  pos += len;
+}
 
-      printf("<command>%s</command>\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());
+static void
+dump_category (int level, uint8_t *catstart, bool *have_catstart)
+{
+  int cat_index = get_u32();
+  assert (cat_index < 256);
 
-      get_bool();
-      get_bool();
-      get_bool();
-      get_bool();
+  if (!match_u32 (0))
+    match_u32_assert (1);
 
-      printf("<epoch2>%d</epoch2>\n", get_u32());
+  get_u16();
 
-      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);
-        }
+  if (!match_u16(0xe74) && !match_u16(0xffff))
+    match_u16_assert(0);
 
-      printf ("small: %g\n", get_double());
+  for (int i = 0; i < level; i++)
+    printf ("  ");
 
-      match_byte_assert(1);
-      if (outer_end - pos > 6)
-        {
-          /* There might be a pair of strings representing a dataset and
-             datafile name, or there might be a set of custom currency strings.
-             The custom currency strings start with a pair of integers, so we
-             can distinguish these from a string by checking for a null byte; a
-             small 32-bit integer will always contain a null and a text string
-             never will. */
-          int save_pos = pos;
-          int len = get_u32();
-          bool has_dataset = !memchr(&data[pos], '\0', len);
-          pos = save_pos;
-
-          if (has_dataset)
-            {
-              printf("<dataset>%s</dataset>\n", get_string());
-              printf("<datafile>%s</datafile>\n", get_string());
+  for (int i = 0; ; i++, pos++)
+    if (data[pos] == 5 && data[pos + 1] == 0x80)
+      break;
+    else if (i >= 100)
+      assert(false);
 
-              match_u32_assert(0);
+  match_byte_assert (5);
+  match_byte_assert (0x80);
+  if (match_byte(2))
+    get_double ();
+  else
+    {
+      match_byte_assert (1);
+      match_byte_assert (2);
+      match_byte_assert (0x28);
+      match_byte_assert (5);
+      match_byte_assert (0);
+      match_byte_assert (1);
+    }
+  printf (" \"%s\"", get_string1());
 
-              time_t date = get_u32();
-              struct tm tm = *localtime(&date);
-              char s[128];
-              strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm);
-              printf("<date>%s</date>\n", s);
+  int n_children = get_u32();
+  assert (n_children < 256);
+  if (n_children)
+    printf (" (group with %d children)", n_children);
+  else
+    printf (" (category #%d)", cat_index);
 
-              match_u32_assert(0);
-            }
-        }
+  printf (" %02x %02x %02x %02x %02x %02x\n",
+          data[pos], data[pos + 1], data[pos + 2],
+          data[pos + 3], data[pos + 4], data[pos + 5]);
+  if (!*have_catstart)
+    {
+      *have_catstart = true;
+      memcpy(catstart, &data[pos], 6);
+    }
+  pos += 6;
 
-      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);
+  for (int i = 0; i < n_children; i++)
+    dump_category (level + 1, catstart, have_catstart);
+}
 
-      match_byte_assert('.');
-      get_bool();
+static void
+dump_PMModelItemInfo(int ndims)
+{
+  return;
+#if 0
+  if (data[pos + 9] && data[pos + 9] != 0xff)//count_zeros (&data[pos + 9]) < 4)
+    return;
+#endif
+  
+  match_byte_assert (0);
 
-      if (pos < outer_end)
-        {
-          get_u32();
-          match_u32_assert(0);
-        }
-      assert(pos == outer_end);
+  uint8_t catstart[6];
+  bool have_catstart = false;
+  dump_category (0, catstart, &have_catstart);
+  assert(have_catstart);
 
-      pos = outer_end;
-    }
-  else if (outer_end != pos)
+  for (int i = 1; i < ndims; i++)
     {
-      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('.'))
+      for (int j = 0; ; j++, pos++)
         {
-          if (!match_byte(',') && !match_byte('\''))
-            match_byte_assert(' ');
+          assert (j <= 1000);
+          if (!memcmp(&data[pos], catstart, 6))
+            break;
         }
-      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);
+      pos += 6;
 
-      match_byte_assert('.');
-      get_bool();
-
-      assert(pos == outer_end);
-      pos = outer_end;
-    }
-}
-
-static const char *
-format_name (int format, char *buf)
-{
-  switch (format)
-    {
-    case 1: return "A";
-    case 2: return "AHEX";
-    case 3: return "COMMA";
-    case 4: return "DOLLAR";
-    case 5: return "F";
-    case 6: return "IB";
-    case 7: return "PIBHEX";
-    case 8: return "P";
-    case 9: return "PIB";
-    case 10: return "PK";
-    case 11: return "RB";
-    case 12: return "RBHEX";
-    case 15: return "Z";
-    case 16: return "N";
-    case 17: return "E";
-    case 20: return "DATE";
-    case 21: return "TIME";
-    case 22: return "DATETIME";
-    case 23: return "ADATE";
-    case 24: return "JDATE";
-    case 25: return "DTIME";
-    case 26: return "WKDAY";
-    case 27: return "MONTH";
-    case 28: return "MOYR";
-    case 29: return "QYR";
-    case 30: return "WKYR";
-    case 31: return "PCT";
-    case 32: return "DOT";
-    case 33: return "CCA";
-    case 34: return "CCB";
-    case 35: return "CCC";
-    case 36: return "CCD";
-    case 37: return "CCE";
-    case 38: return "EDATE";
-    case 39: return "SDATE";
-    case 40: return "MTIME";
-    case 41: return "YMDHMS";
-    default: sprintf(buf, "(%d)", format); return buf;
+      dump_category (0, catstart, &have_catstart);
     }
 }
 
@@ -1416,7 +762,8 @@ main(int argc, char *argv[])
 #endif
   unsigned int prev_end = 0;
   char *title = "";
-  for (pos = 2; pos + 50 < n; pos++)
+  int ndims = 0;
+  for (pos = 2; pos + 50 < n; )
     {
       static const int cell_prefix[] = {
         0x00, 0x03,
@@ -1433,16 +780,6 @@ main(int argc, char *argv[])
               if (print_offsets)
                 printf ("%04x ", prev_end);
               hex_dump (stdout, prev_end, pos - prev_end);
-
-              if (!strcmp (title, "DspNumber")
-                  && pos - prev_end == 2
-                  && data[prev_end + 1] == 0x80)
-                {
-                  static int already = false;
-                  if (!already)
-                    fprintf (stderr, " sum=%d %02x\n", sum, data[prev_end]);
-                  already = true;
-                }
             }
 
           char buf[64];
@@ -1472,6 +809,13 @@ main(int argc, char *argv[])
                           &data[pos + 24]);
                   len = 23 + count;
                 }
+              else if (data[pos + 20] == 1 && data[pos + 21] == 255)
+                {
+                  int count = data[pos + 22] + (data[pos + 23] << 8);
+                  printf ("\"%.*s\"\n",
+                          count, &data[pos + 24]);
+                  len = 24 + count;
+                }
               else if (data[pos + 20] == 1)
                 {
                   int count = (data[pos + 21]);
@@ -1532,8 +876,8 @@ main(int argc, char *argv[])
                       data[pos + 21], data[pos + 22]);
               assert(false);
             }
-          pos += len - 1;
-          prev_end = pos + 1;
+          pos += len;
+          prev_end = pos;
           continue;
         }
 
@@ -1559,11 +903,24 @@ main(int argc, char *argv[])
               printf ("rec:%-20.*s ", slen, &data[pos + 6]);
               len = slen + 6;
               title = xmemdup0(&data[pos + 6], slen);
-              fprintf (stderr, "%s%d ", title, data[pos + len]);
               sum += data[pos+len];
 
-              pos += len - 1;
-              prev_end = pos + 1;
+              pos += len;
+
+              if (!strcmp(title, "DspNumber"))
+                dump_DspNumber();
+              else if (!strcmp(title, "PMModelItemInfo"))
+                {
+                  assert(ndims);
+                  dump_PMModelItemInfo(ndims);
+                }
+              else if (!strcmp(title, "NDimensional__DspCell"))
+                {
+                  match_byte_assert(0);
+                  ndims = get_u32();
+                  assert(ndims < 10);
+                }
+              prev_end = pos;
               continue;
             }
         }
@@ -1585,8 +942,8 @@ main(int argc, char *argv[])
           double d = *(double *) &data[pos + number_prefix_len];
           printf ("float %f\n", d);
 
-          pos += 10 - 1;
-          prev_end = pos + 1;
+          pos += 10;
+          prev_end = pos;
           continue;
         }
 
@@ -1605,8 +962,8 @@ main(int argc, char *argv[])
               prev_end = pos;
 
               printf ("rtf\n");
-              pos += 4 + len - 1;
-              prev_end = pos + 1;
+              pos += 4 + len;
+              prev_end = pos;
               continue;
             }
         }
@@ -1627,8 +984,8 @@ main(int argc, char *argv[])
             + (data[pos + 2] << 16) + (data[pos + 3] << 24);
           printf ("%d (%+d) ", num, num - prev_num);
           prev_num = num;
-          pos += 4 - 1;
-          prev_end = pos + 1;
+          pos += 4;
+          prev_end = pos;
           continue;
         }
 
@@ -1649,13 +1006,13 @@ main(int argc, char *argv[])
 
           printf ("font\n");
 
-          pos += font_prefix_len - 1;
-          prev_end = pos + 1;
+          pos += font_prefix_len;
+          prev_end = pos;
           continue;
         }
 
       static const int string_prefix[] = {
-        0x80, 0x01, 0x02, 0x28, 0x05, 0x00, 0x01
+        0x05, 0x80, 0x01, 0x02, 0x28, 0x05, 0x00, 0x01
       };
       size_t string_prefix_len = sizeof string_prefix / sizeof *string_prefix;
       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] != 255)
@@ -1668,10 +1025,10 @@ main(int argc, char *argv[])
             }
           prev_end = pos;
 
-          int len = data[pos + 7];
-          printf ("string %.*s\n", len, &data[pos + 8]);
-          pos += 8 + len - 1;
-          prev_end = pos + 1;
+          int len = data[pos + 8];
+          printf ("string %.*s\n", len, &data[pos + 9]);
+          pos += 8 + len;
+          prev_end = pos;
           continue;
         }
       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] == 255)
@@ -1684,10 +1041,10 @@ main(int argc, char *argv[])
             }
           prev_end = pos;
 
-          int len = data[pos + 8] + (data[pos + 9] << 8);
-          printf ("\nlongstring %.*s\n", len, &data[pos + 10]);
-          pos += 10 + len - 1;
-          prev_end = pos + 1;
+          int len = data[pos + 9] + (data[pos + 10] << 8);
+          printf ("\nlongstring %.*s\n", len, &data[pos + 11]);
+          pos += 11 + len;
+          prev_end = pos;
           continue;
         }
 
@@ -1695,7 +1052,10 @@ main(int argc, char *argv[])
 
 
       if (!is_ascii(data[pos]))
-        continue;
+        {
+          pos++;
+          continue;
+        }
 
       unsigned int start = pos;
       unsigned int end = pos + 1;
@@ -1704,7 +1064,10 @@ main(int argc, char *argv[])
 
       unsigned int len = end - start;
       if (len < 3)
-        continue;
+        {
+          pos++;
+          continue;
+        }
 
       unsigned int len2 = data[start - 2] + (data[start - 1] << 8);
       unsigned int len3 = data[start - 1];
@@ -1720,9 +1083,15 @@ main(int argc, char *argv[])
           len = len3;
         }
       else
-        continue;
+        {
+          pos++;
+          continue;
+        }
       if (len < 3)
-        continue;
+        {
+          pos++;
+          continue;
+        }
       end = start + len;
 
       unsigned real_start = start - length_bytes;
@@ -1737,7 +1106,7 @@ main(int argc, char *argv[])
       printf ("\"%.*s\"\n", 
               (int) end - start, (char *) &data[start]);
       prev_end = end;
-      pos = end - 1;
+      pos = end;
     }
 
   exit(0);