significant progess on SPOs
[pspp] / dump-spo.c
1 #include <assert.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <float.h>
5 #include <stdbool.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/stat.h>
11 #include <time.h>
12 #include <unistd.h>
13 #include "u8-mbtouc.h"
14
15 static const char *filename;
16 static uint8_t *data;
17 static size_t n;
18
19 int version;
20
21 unsigned int pos;
22
23 #define XSTR(x) #x
24 #define STR(x) XSTR(x)
25 #define WHERE __FILE__":" STR(__LINE__)
26
27 static uint8_t
28 get_byte(void)
29 {
30   return data[pos++];
31 }
32
33 static unsigned int
34 get_u32(void)
35 {
36   uint32_t x;
37   memcpy(&x, &data[pos], 4);
38   pos += 4;
39   return x;
40 }
41
42 static unsigned long long int
43 get_u64(void)
44 {
45   uint64_t x;
46   memcpy(&x, &data[pos], 8);
47   pos += 8;
48   return x;
49 }
50
51 static unsigned int
52 get_be32(void)
53 {
54   uint32_t x;
55   x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
56   pos += 4;
57   return x;
58 }
59
60 static unsigned int
61 get_u16(void)
62 {
63   uint16_t x;
64   memcpy(&x, &data[pos], 2);
65   pos += 2;
66   return x;
67 }
68
69 static double
70 get_double(void)
71 {
72   double x;
73   memcpy(&x, &data[pos], 8);
74   pos += 8;
75   return x;
76 }
77
78 static double __attribute__((unused))
79 get_float(void)
80 {
81   float x;
82   memcpy(&x, &data[pos], 4);
83   pos += 4;
84   return x;
85 }
86
87 static bool
88 match_u32(uint32_t x)
89 {
90   if (get_u32() == x)
91     return true;
92   pos -= 4;
93   return false;
94 }
95
96 static void
97 match_u32_assert(uint32_t x, const char *where)
98 {
99   unsigned int y = get_u32();
100   if (x != y)
101     {
102       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
103       exit(1);
104     }
105 }
106 #define match_u32_assert(x) match_u32_assert(x, WHERE)
107
108 static bool __attribute__((unused))
109 match_u64(uint64_t x)
110 {
111   if (get_u64() == x)
112     return true;
113   pos -= 8;
114   return false;
115 }
116
117 static void __attribute__((unused))
118 match_u64_assert(uint64_t x, const char *where)
119 {
120   unsigned long long int y = get_u64();
121   if (x != y)
122     {
123       fprintf(stderr, "%s: 0x%x: expected u64:%lu, got u64:%llu\n", where, pos - 8, x, y);
124       exit(1);
125     }
126 }
127 #define match_u64_assert(x) match_u64_assert(x, WHERE)
128
129 static bool __attribute__((unused))
130 match_be32(uint32_t x)
131 {
132   if (get_be32() == x)
133     return true;
134   pos -= 4;
135   return false;
136 }
137
138 static void
139 match_be32_assert(uint32_t x, const char *where)
140 {
141   unsigned int y = get_be32();
142   if (x != y)
143     {
144       fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y);
145       exit(1);
146     }
147 }
148 #define match_be32_assert(x) match_be32_assert(x, WHERE)
149
150 static bool
151 match_byte(uint8_t b)
152 {
153   if (pos < n && data[pos] == b)
154     {
155       pos++;
156       return true;
157     }
158   else
159     return false;
160 }
161
162 static void
163 match_byte_assert(uint8_t b, const char *where)
164 {
165   if (!match_byte(b))
166     {
167       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
168       exit(1);
169     }
170 }
171 #define match_byte_assert(b) match_byte_assert(b, WHERE)
172
173 static bool
174 match_bytes(int start, const int *bytes, size_t n_bytes)
175 {
176   for (size_t i = 0; i < n_bytes; i++)
177     if (bytes[i] >= 0 && data[start + i] != bytes[i])
178       return false;
179   return true;
180 }
181
182 static char *
183 xmemdup0(const void *p, size_t n)
184 {
185   char *s = malloc(n + 1);
186   memcpy(s, p, n);
187   s[n] = 0;
188   return s;
189 }
190
191 static bool
192 get_bool(void)
193 {
194   if (match_byte(0))
195     return false;
196   match_byte_assert(1);
197   return true;
198 }
199
200 static bool __attribute__((unused))
201 is_ascii(uint8_t p)
202 {
203   return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t';
204 }
205
206 static bool __attribute__((unused))
207 all_utf8(const char *p_, size_t len)
208 {
209   const uint8_t *p = (const uint8_t *) p_;
210   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
211     {
212       ucs4_t uc;
213
214       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
215       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
216         return false;
217     }
218   return true;
219 }
220
221 static char *
222 get_string(const char *where)
223 {
224   if (1
225       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
226       /*&& all_ascii(&data[pos + 4], data[pos])*/)
227     {
228       int len = data[pos] + data[pos + 1] * 256;
229       char *s = malloc(len + 1);
230
231       memcpy(s, &data[pos + 4], len);
232       s[len] = 0;
233       pos += 4 + len;
234       return s;
235     }
236   else
237     {
238       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
239       exit(1);
240     }
241 }
242 #define get_string() get_string(WHERE)
243
244 static char *
245 get_string_be(const char *where)
246 {
247   if (1
248       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
249       /*&& all_ascii(&data[pos + 4], data[pos])*/)
250     {
251       int len = data[pos + 2] * 256 + data[pos + 3];
252       char *s = malloc(len + 1);
253
254       memcpy(s, &data[pos + 4], len);
255       s[len] = 0;
256       pos += 4 + len;
257       return s;
258     }
259   else
260     {
261       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
262       exit(1);
263     }
264 }
265 #define get_string_be() get_string_be(WHERE)
266
267 static int
268 get_end(void)
269 {
270   int len = get_u32();
271   return pos + len;
272 }
273
274 static void __attribute__((unused))
275 hex_dump(FILE *stream, int ofs, int n)
276 {
277   int n_ascii = 0;
278   for (int i = 0; i < n; i++)
279     {
280       int c = data[ofs + i];
281       n_ascii += is_ascii(c);
282       fprintf(stream, " %02x", c);
283     }
284   if (n_ascii >= 3)
285     {
286       putc(' ', stream);
287       for (int i = 0; i < n; i++)
288         {
289           int c = data[ofs + i];
290           putc(c >= 32 && c < 127 ? c : '.', stream);
291         }
292     }
293   putc('\n', stream);
294 }
295
296 static void __attribute__((unused))
297 char_dump(FILE *stream, int ofs, int n)
298 {
299   for (int i = 0; i < n; i++)
300     {
301       int c = data[ofs + i];
302       putc(c >= 32 && c < 127 ? c : '.', stream);
303     }
304   putc('\n', stream);
305 }
306
307 static char *
308 dump_counted_string(void)
309 {
310   int inner_end = get_end();
311   if (pos == inner_end)
312     return NULL;
313
314   if (match_u32(5))
315     {
316       match_u32_assert(0);
317       match_byte_assert(0x58);
318     }
319   else
320     match_u32_assert(0);
321
322   char *s = NULL;
323   if (match_byte(0x31))
324     s = get_string();
325   else
326     match_byte_assert(0x58);
327   if (pos != inner_end)
328     {
329       fprintf(stderr, "inner end discrepancy\n");
330       exit(1);
331     }
332   return s;
333 }
334
335 static void
336 dump_style(FILE *stream)
337 {
338   if (match_byte(0x58))
339     return;
340
341   match_byte_assert(0x31);
342   if (get_bool())
343     printf (" bold=\"yes\"");
344   if (get_bool())
345     printf (" italic=\"yes\"");
346   if (get_bool())
347     printf (" underline=\"yes\"");
348   if (!get_bool())
349     printf (" show=\"no\"");
350   char *fg = get_string();     /* foreground */
351   char *bg = get_string();     /* background */
352   char *font = get_string();     /* font */
353   int size = get_byte() * (72. / 96.);
354   fprintf(stream, " fgcolor=\"%s\" bgcolor=\"%s\" font=\"%s\" size=\"%dpt\"",
355           fg, bg, font, size);
356 }
357
358 static void
359 dump_style2(FILE *stream)
360 {
361   if (match_byte(0x58))
362     return;
363
364   match_byte_assert(0x31);
365   uint32_t halign = get_u32();
366   printf (" halign=\"%s\"",
367           halign == 0 ? "center"
368           : halign == 2 ? "left"
369           : halign == 4 ? "right"
370           : halign == 6 ? "decimal"
371           : halign == 0xffffffad ? "mixed"
372           : "<error>");
373   int valign = get_u32();
374   printf (" valign=\"%s\"",
375           valign == 0 ? "center"
376           : valign == 1 ? "top"
377           : valign == 3 ? "bottom"
378           : "<error>");
379   printf (" offset=\"%gpt\"", get_double());
380   int l = get_u16();
381   int r = get_u16();
382   int t = get_u16();
383   int b = get_u16();
384   printf (" margins=\"%d %d %d %d\"", l, r, t, b);
385 }
386
387 static char *
388 dump_nested_string(FILE *stream)
389 {
390   char *s = NULL;
391
392   match_byte_assert (0);
393   match_byte_assert (0);
394   int outer_end = get_end();
395   s = dump_counted_string();
396   if (s)
397     fprintf(stream, " \"%s\"", s);
398   dump_style(stream);
399   match_byte_assert(0x58);
400   if (pos != outer_end)
401     {
402       fprintf(stderr, "outer end discrepancy\n");
403       exit(1);
404     }
405
406   return s;
407 }
408
409 static void
410 dump_value_modifier(FILE *stream)
411 {
412   if (match_byte (0x31))
413     {
414       if (match_u32 (0))
415         {
416           fprintf(stream, "<special0");
417           if (match_u32 (1))
418             {
419               /* Corpus frequencies:
420                  124 "a"
421                  12 "b"
422                  8 "a, b"
423
424                  The given text is appended to the cell in a subscript font.
425               */
426               fprintf(stream, " subscript=\"%s\"", get_string());
427             }
428           else
429             match_u32_assert (0);
430
431           if (version == 1)
432             {
433               /* We only have one SPV file for this version (with many
434                  tables). */
435               match_byte(0);
436               if (!match_u32(1))
437                 match_u32_assert(2);
438               match_byte(0);
439               match_byte(0);
440               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))
441                 match_u32_assert(10);
442               match_byte(0);
443               match_byte(0);
444               fprintf(stream, "/>\n");
445               return;
446             }
447
448           int outer_end = get_end();
449           
450           /* This counted-string appears to be a template string,
451              e.g. "Design\: [:^1:]1 Within Subjects Design\: [:^1:]2". */
452           char *template = dump_counted_string();
453           if (template)
454             fprintf(stream, " template=\"%s\"", template);
455
456           dump_style(stream);
457           dump_style2(stream);
458           if (pos != outer_end)
459             {
460               fprintf(stderr, "outer end discrepancy\n");
461               exit(1);
462             }
463           fprintf(stream, "/>\n");
464         }
465       else
466         {
467           int count = get_u32();
468           fprintf(stream, "<footnote-ref indexes=\"");
469           for (int i = 0; i < count; i++)
470             {
471               if (i)
472                 putc(' ', stream);
473               fprintf(stream, "%d", get_u16());
474             }
475           putc('"', stream);
476           match_byte_assert(0);
477           match_byte_assert(0);
478           dump_nested_string(stream);
479           fprintf(stream, "/>\n");
480         }
481     }
482   else
483     match_byte_assert (0x58);
484 }
485
486 static const char *
487 format_to_string (int type)
488 {
489   static char tmp[16];
490   switch (type)
491     {
492     case 1: return "A";
493     case 2: return "AHEX";
494     case 3: return "COMMA";
495     case 4: return "DOLLAR";
496     case 5: case 40: return "F";
497     case 6: return "IB";
498     case 7: return "PIBHEX";
499     case 8: return "P";
500     case 9: return "PIB";
501     case 10: return "PK";
502     case 11: return "RB";
503     case 12: return "RBHEX";
504     case 15: return "Z";
505     case 16: return "N";
506     case 17: return "E";
507     case 20: return "DATE";
508     case 21: return "TIME";
509     case 22: return "DATETIME";
510     case 23: return "ADATE";
511     case 24: return "JDATE";
512     case 25: return "DTIME";
513     case 26: return "WKDAY";
514     case 27: return "MONTH";
515     case 28: return "MOYR";
516     case 29: return "QYR";
517     case 30: return "WKYR";
518     case 31: return "PCT";
519     case 32: return "DOT";
520     case 33: return "CCA";
521     case 34: return "CCB";
522     case 35: return "CCC";
523     case 36: return "CCD";
524     case 37: return "CCE";
525     case 38: return "EDATE";
526     case 39: return "SDATE";
527     default:
528       assert(false);
529       sprintf(tmp, "<%d>", type);
530       return tmp;
531     }
532 }
533
534 static void
535 dump_value(FILE *stream, int level)
536 {
537   match_byte(0);
538   match_byte(0);
539   match_byte(0);
540   match_byte(0);
541
542   for (int i = 0; i <= level; i++)
543     fprintf (stream, "    ");
544
545   printf ("%02x: value (%d)\n", pos, data[pos]);
546   if (match_byte (1))
547     {
548       unsigned int format;
549       double value;
550
551       dump_value_modifier(stream);
552       format = get_u32 ();
553       value = get_double ();
554       fprintf (stream, "<number value=\"%.*g\" format=\"%s%d.%d\"/>\n",
555                DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
556     }
557   else if (match_byte (2))
558     {
559       unsigned int format;
560       char *var, *vallab;
561       double value;
562
563       dump_value_modifier (stream);
564       format = get_u32 ();
565       value = get_double ();
566       var = get_string ();
567       vallab = get_string ();
568       fprintf (stream, "<numeric-datum value=\"%.*g\" format=\"%s%d.%d\"",
569               DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
570       if (var[0])
571         fprintf (stream, " variable=\"%s\"", var);
572       if (vallab[0])
573         fprintf (stream, " label=\"%s\"", vallab);
574       fprintf (stream, "/>\n");
575       if (!match_byte (1) && !match_byte(2))
576         match_byte_assert (3);
577     }
578   else if (match_byte (3))
579     {
580       char *text =  get_string();
581       dump_value_modifier(stream);
582       char *identifier = get_string();
583       char *text_eng = get_string();
584       fprintf (stream, "<string c=\"%s\"", text_eng);
585       if (identifier[0])
586         fprintf (stream, " identifier=\"%s\"", identifier);
587       if (strcmp(text_eng, text))
588         fprintf (stream, " local=\"%s\"", text);
589       fprintf (stream, "/>\n");
590       if (!match_byte (0))
591         match_byte_assert(1);
592     }
593   else if (match_byte (4))
594     {
595       unsigned int format;
596       char *var, *vallab, *value;
597
598       dump_value_modifier(stream);
599       format = get_u32 ();
600       vallab = get_string ();
601       var = get_string ();
602       if (!match_byte(1) && !match_byte(2))
603         match_byte_assert (3);
604       value = get_string ();
605       fprintf (stream, "<string-datum value=\"%s\" format=\"%s%d.%d\"",
606               value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
607       if (var[0])
608         fprintf (stream, " variable=\"%s\"", var);
609       if (vallab[0])
610         fprintf (stream, " label=\"%s\"/>\n", vallab);
611       fprintf (stream, "/>\n");
612     }
613   else if (match_byte (5))
614     {
615       dump_value_modifier(stream);
616       char *name = get_string ();
617       char *label = get_string ();
618       fprintf (stream, "<variable name=\"%s\"", name);
619       if (label[0])
620         fprintf (stream, " label=\"%s\"", label);
621       fprintf (stream, "/>\n");
622       if (!match_byte(1) && !match_byte(2))
623         match_byte_assert(3);
624     }
625   else
626     {
627       printf ("else %#x\n", pos);
628       dump_value_modifier(stream);
629
630       char *base = get_string();
631       int x = get_u32();
632       fprintf (stream, "<template format=\"%s\">\n", base);
633       for (int i = 0; i < x; i++)
634         {
635           int y = get_u32();
636           if (!y)
637             y = 1;
638           else
639             match_u32_assert(0);
640           for (int j = 0; j <= level + 1; j++)
641             fprintf (stream, "    ");
642           fprintf (stream, "<substitution index=\"%d\">\n", i + 1);
643           for (int j = 0; j < y; j++)
644             dump_value (stream, level + 2);
645           for (int j = 0; j <= level + 1; j++)
646             fprintf (stream, "    ");
647           fprintf (stream, "</substitution>\n");
648         }
649       for (int j = 0; j <= level; j++)
650         fprintf (stream, "    ");
651       fprintf (stream, "</template>\n");
652     }
653 }
654
655 static int
656 compare_int(const void *a_, const void *b_)
657 {
658   const int *a = a_;
659   const int *b = b_;
660   return *a < *b ? -1 : *a > *b;
661 }
662
663 static void
664 check_permutation(int *a, int n, const char *name)
665 {
666   int b[n];
667   memcpy(b, a, n * sizeof *a);
668   qsort(b, n, sizeof *b, compare_int);
669   for (int i = 0; i < n; i++)
670     if (b[i] != i)
671       {
672         fprintf(stderr, "bad %s permutation:", name);
673         for (int i = 0; i < n; i++)
674           fprintf(stderr, " %d", a[i]);
675         putc('\n', stderr);
676         exit(1);
677       }
678 }
679
680 static void
681 dump_category(FILE *stream, int level, int **indexes, int *allocated_indexes,
682               int *n_indexes)
683 {
684   for (int i = 0; i <= level; i++)
685     fprintf (stream, "    ");
686   printf ("<category>\n");
687   dump_value (stream, level + 1);
688
689   bool merge = get_bool();
690   match_byte_assert (0);
691   int unindexed = get_bool();
692
693   int x = get_u32 ();
694   pos -= 4;
695   if (!match_u32 (0))
696     match_u32_assert (2);
697
698   int indx = get_u32();
699   int n_categories = get_u32();
700   if (indx == -1)
701     {
702       if (merge)
703         {
704           for (int i = 0; i <= level + 1; i++)
705             fprintf (stream, "    ");
706           fprintf (stream, "<merge/>\n");
707         }
708       assert (unindexed);
709     }
710   else
711     {
712       assert (!merge);
713       assert (!unindexed);
714       assert (x == 2);
715       assert (n_categories == 0);
716       if (*n_indexes >= *allocated_indexes)
717         {
718           *allocated_indexes = *allocated_indexes ? 2 * *allocated_indexes : 16;
719           *indexes = realloc(*indexes, *allocated_indexes * sizeof **indexes);
720         }
721       (*indexes)[(*n_indexes)++] = indx;
722     }
723
724   if (n_categories == 0)
725     {
726       for (int i = 0; i <= level + 1; i++)
727         fprintf (stream, "    ");
728       fprintf (stream, "<category-index>%d</category-index>\n", indx);
729     }
730   for (int i = 0; i < n_categories; i++)
731     dump_category (stream, level + 1, indexes, allocated_indexes, n_indexes);
732   for (int i = 0; i <= level; i++)
733     fprintf (stream, "    ");
734   printf ("</category>\n");
735 }
736
737 static int
738 dump_dim(int indx)
739 {
740   int n_categories;
741
742   printf ("<dimension index=\"%d\">\n", indx);
743   dump_value (stdout, 0);
744
745   /* This byte is usually 0 but many other values have been spotted.
746      No visible effect. */
747   pos++;
748
749   /* This byte can cause data to be oddly replicated. */
750   if (!match_byte(0) && !match_byte(1))
751     match_byte_assert(2);
752
753   if (!match_u32(0))
754     match_u32_assert(2);
755
756   bool show_dim_label = get_bool();
757   if (show_dim_label)
758     printf("  <show-dim-label/>\n");
759
760   bool hide_all_labels = get_bool();
761   if (hide_all_labels)
762     printf("  <hide-all-labels/>\n");
763
764   match_byte_assert(1);
765   if (!match_u32(UINT32_MAX))
766     match_u32_assert(indx);
767
768   n_categories = get_u32();
769
770   int *indexes = NULL;
771   int n_indexes = 0;
772   int allocated_indexes = 0;
773   for (int i = 0; i < n_categories; i++)
774     dump_category (stdout, 0, &indexes, &allocated_indexes, &n_indexes);
775   check_permutation(indexes, n_indexes, "categories");
776
777   fprintf (stdout, "</dimension>\n");
778   return n_indexes;
779 }
780
781 int n_dims;
782 static int dim_n_cats[64];
783 #define MAX_DIMS (sizeof dim_n_cats / sizeof *dim_n_cats)
784
785 static void
786 dump_dims(void)
787 {
788   n_dims = get_u32();
789   assert(n_dims < MAX_DIMS);
790   for (int i = 0; i < n_dims; i++)
791     dim_n_cats[i] = dump_dim (i);
792 }
793
794 static void
795 dump_data(void)
796 {
797   /* The first three numbers add to the number of dimensions. */
798   int l = get_u32();
799   int r = get_u32();
800   int c = n_dims - l - r;
801   match_u32_assert(c);
802
803   /* The next n_dims numbers are a permutation of the dimension numbers. */
804   int a[n_dims];
805   for (int i = 0; i < n_dims; i++)
806     {
807       int dim = get_u32();
808       a[i] = dim;
809
810       const char *name = i < l ? "layer" : i < l + r ? "row" : "column";
811       printf ("<%s dimension=\"%d\"/>\n", name, dim);
812     }
813   check_permutation(a, n_dims, "dimensions");
814
815   int x = get_u32();
816   printf ("<data>\n");
817   for (int i = 0; i < x; i++)
818     {
819       unsigned int indx = get_u32();
820       printf ("    <datum index=\"%d\" coords=", indx);
821
822       int coords[MAX_DIMS];
823       for (int i = n_dims; i-- > 0; )
824         {
825           coords[i] = indx % dim_n_cats[i];
826           indx /= dim_n_cats[i];
827         }
828       for (int i = 0; i < n_dims; i++)
829         printf("%c%d", i ? ',' : '"', coords[i]);
830
831       printf ("\">\n");
832       match_u32_assert(0);
833       if (version == 1)
834         match_byte(0);
835       dump_value(stdout, 1);
836       fprintf (stdout, "    </datum>\n");
837     }
838   printf ("</data>\n");
839 }
840
841 static void
842 dump_title(void)
843 {
844   printf ("<title-local>\n");
845   dump_value(stdout, 0);
846   match_byte(1);
847   printf ("</title-local>\n");
848
849   printf ("<subtype>\n");
850   dump_value(stdout, 0);
851   match_byte(1);
852   printf ("</subtype>\n");
853
854   match_byte_assert(0x31);
855
856   printf ("<title-c>\n");
857   dump_value(stdout, 0);
858   match_byte(1);
859   printf ("</title-c>\n");
860
861   if (match_byte(0x31))
862     {
863       printf ("<user-caption>\n");
864       dump_value(stdout, 0);
865       printf ("</user-caption>\n");
866     }
867   else
868     match_byte_assert(0x58);
869   if (match_byte(0x31))
870     {
871       printf ("<caption>\n");
872       dump_value(stdout, 0);
873       printf ("</caption>\n");
874     }
875   else
876     match_byte_assert(0x58);
877
878   int n_footnotes = get_u32();
879   for (int i = 0; i < n_footnotes; i++)
880     {
881       printf ("<footnote index=\"%d\">\n", i);
882       dump_value(stdout, 0);
883       /* Custom footnote marker string. */
884       if (match_byte (0x31))
885         dump_value(stdout, 0);
886       else
887         match_byte_assert (0x58);
888       int n = get_u32();
889       if (n >= 0)
890         {
891           /* Appears to be the number of references to a footnote. */
892           printf ("  <references n=\"%d\"/>\n", n);
893         }
894       else if (n == -2)
895         {
896           /* The user deleted the footnote references. */
897           printf ("  <deleted/>\n");
898         }
899       else
900         assert(0);
901       printf ("</footnote>\n");
902     }
903 }
904
905 static void
906 dump_fonts(void)
907 {
908   match_byte(0);
909   for (int i = 1; i <= 8; i++)
910     {
911       printf ("<style index=\"%d\"", i);
912       match_byte_assert(i);
913       match_byte_assert(0x31);
914       printf(" font=\"%s\"", get_string());
915
916       printf(" size=\"%gpt\"", get_float());
917
918       int style = get_u32();
919       if (style & 1)
920         printf(" bold=\"true\"");
921       if (style & 2)
922         printf(" italic=\"true\"");
923
924       bool underline = data[pos++];
925       if (underline)
926         printf(" underline=\"true\"");
927
928       int halign = get_u32();
929       printf(" halign=%d", halign);
930
931       int valign = get_u32();
932       printf(" valign=%d", valign);
933
934       printf (" fgcolor=\"%s\"", get_string());
935       printf (" bgcolor=\"%s\"", get_string());
936
937       if (!match_byte(0))
938         match_byte_assert(1);
939
940       char *alt_fgcolor = get_string();
941       if (alt_fgcolor[0])
942         printf (" altfg=\"%s\"", alt_fgcolor);
943       char *alt_bgcolor = get_string();
944       if (alt_bgcolor[0])
945         printf (" altbg=\"%s\"", alt_bgcolor);
946
947       if (version > 1)
948         {
949           printf(" margins=\"");
950           for (int i = 0; i < 4; i++)
951             {
952               if (i)
953                 putchar(' ');
954               printf("%d", get_u32());
955             }
956           putchar('"');
957         }
958
959       printf ("/>\n");
960     }
961
962   int x1 = get_u32();
963   int x1_end = pos + x1;
964   printf("<borders>\n");
965   match_be32_assert(1);
966   int n_borders = get_be32();
967   for (int i = 0; i < n_borders; i++)
968     {
969       int type = get_be32();
970       int stroke = get_be32();
971       int color = get_be32();
972       printf("  <border type=\"%d\" stroke=\"%s\" color=\"#%06x\"/>\n",
973              type,
974              (stroke == 0 ? "none"
975               : stroke == 1 ? "solid"
976               : stroke == 2 ? "dashed"
977               : stroke == 3 ? "thick"
978               : stroke == 4 ? "thin"
979               : stroke == 5 ? "double"
980               : "<error>"),
981              color);
982     }
983   bool grid = get_byte();
984   pos += 3;
985   printf("  <grid show=\"%s\"/>\n", grid ? "yes" : "no");
986   printf("</borders>\n");
987   assert(pos == x1_end);
988
989   int skip = get_u32();
990   assert(skip == 18 || skip == 25);
991   pos += skip;
992
993   int x3 = get_u32();
994   int x3_end = pos + x3;
995   if (version == 3)
996     {
997       match_be32_assert(1);
998       get_be32();
999       printf("<settings layer=\"%d\"", get_be32());
1000       if (!get_bool())
1001         printf(" skipempty=\"false\"");
1002       if (!get_bool())
1003         printf(" showdimensionincorner=\"false\"");
1004       if (!get_bool())
1005         printf(" markers=\"numeric\"");
1006       if (!get_bool())
1007         printf(" footnoteposition=\"subscript\"");
1008       get_byte();
1009       int nbytes = get_be32();
1010       int end = pos + nbytes;
1011       printf("\n");
1012       while (pos + 4 <= end)
1013         printf(" %d", get_be32());
1014       pos = end;
1015       printf("\n");
1016       pos += nbytes;
1017       char *notes = get_string_be();
1018       if (notes[0])
1019         printf(" notes=\"%s\"", notes);
1020       char *look = get_string_be();
1021       if (look[0])
1022         printf(" look=\"%s\"", look);
1023       printf(">\n");
1024     }
1025   pos = x3_end;
1026
1027   /* Manual column widths, if present. */
1028   int count = get_u32();
1029   if (count > 0)
1030     {
1031       printf("<columnwidths>");
1032       for (int i = 0; i < count; i++)
1033         {
1034           if (i)
1035             putchar(' ');
1036           printf("%d", get_u32());
1037         }
1038       printf("</columnwidths>\n");
1039     }
1040
1041   const char *locale = get_string();
1042   printf ("<locale>%s</locale>\n", locale);
1043
1044   printf ("<layer>%d</layer>\n", get_u32());
1045   if (!match_byte(0))
1046     match_byte_assert(1);
1047   if (!match_byte(0))
1048     match_byte_assert(1);
1049   if (!match_byte(0))
1050     match_byte_assert(1);
1051   printf("<epoch>%d</epoch>\n", get_u32());
1052
1053   int decimal = data[pos];
1054   int grouping = data[pos + 1];
1055   if (match_byte('.'))
1056     {
1057       if (!match_byte(',') && !match_byte('\''))
1058         match_byte_assert(' ');
1059     }
1060   else
1061     {
1062       match_byte_assert(',');
1063       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1064         match_byte_assert(0);
1065     }
1066   printf("<format decimal=\"%c\"", decimal);
1067   if (grouping)
1068     printf(" grouping=\"%c\"", grouping);
1069   printf("\"/>\n");
1070   if (match_u32(5))
1071     {
1072       for (int i = 0; i < 5; i++)
1073         printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1074     }
1075   else
1076     match_u32_assert(0);
1077
1078   /* The last chunk is an outer envelope that contains two inner envelopes.
1079      The second inner envelope has some interesting data like the encoding and
1080      the locale. */
1081   int outer_end = get_end();
1082   if (version == 3)
1083     {
1084       /* First inner envelope: byte*33 int[n] int*[n]. */
1085       int inner_len = get_u32();
1086       int inner_end = pos + inner_len;
1087       int array_start = pos + 33;
1088       match_byte_assert(0);
1089       pos++;                    /* 0, 1, 10 seen. */
1090       get_bool();
1091
1092       /* 0=en 1=de 2=es 3=it 5=ko 6=pl 8=zh-tw 10=pt_BR 11=fr */
1093       printf("lang=%d ", get_byte());
1094
1095       printf ("variable_mode=%d\n", get_byte());
1096       printf ("value_mode=%d\n", get_byte());
1097       if (!match_u64(0))
1098         match_u64_assert(UINT64_MAX);
1099       match_u32_assert(0);
1100       match_u32_assert(0);
1101       match_u32_assert(0);
1102       match_u32_assert(0);
1103       match_byte_assert(0);
1104       get_bool();
1105       match_byte_assert(1);
1106       pos = array_start;
1107
1108       assert(get_end() == inner_end);
1109       printf("<heights>");
1110       int n_heights = get_u32();
1111       for (int i = 0; i < n_heights; i++)
1112         {
1113           if (i)
1114             putchar(' ');
1115           printf("%d", get_u32());
1116         }
1117       printf("</heights>\n");
1118
1119       int n_style_map = get_u32();
1120       for (int i = 0; i < n_style_map; i++)
1121         {
1122           uint64_t cell = get_u64();
1123           int style = get_u16();
1124           printf("<style-map cell=\"%lu\" style=\"%d\"/>\n", cell, style);
1125         }
1126
1127       int n_styles = get_u32();
1128       for (int i = 0; i < n_styles; i++)
1129         {
1130           printf("<cell-style index=\"%d\"", i);
1131           dump_style(stdout);
1132           dump_style2(stdout);
1133           printf("/>\n");
1134         }
1135
1136       pos = get_end();
1137       assert(pos == inner_end);
1138
1139       /* Second inner envelope. */
1140       assert(get_end() == outer_end);
1141
1142       match_byte_assert(1);
1143       match_byte_assert(0);
1144       if (!match_byte(3) && !match_byte(4))
1145         match_byte_assert(5);
1146       match_byte_assert(0);
1147       match_byte_assert(0);
1148       match_byte_assert(0);
1149
1150       printf("<command>%s</command>\n", get_string());
1151       printf("<command-local>%s</command-local>\n", get_string());
1152       printf("<language>%s</language>\n", get_string());
1153       printf("<charset>%s</charset>\n", get_string());
1154       printf("<locale>%s</locale>\n", get_string());
1155
1156       get_bool();
1157       get_bool();
1158       get_bool();
1159       get_bool();
1160
1161       printf("<epoch2>%d</epoch2>\n", get_u32());
1162
1163       if (match_byte('.'))
1164         {
1165           if (!match_byte(',') && !match_byte('\''))
1166             match_byte_assert(' ');
1167         }
1168       else
1169         {
1170           match_byte_assert(',');
1171           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1172             match_byte_assert(0);
1173         }
1174
1175       printf ("small: %g\n", get_double());
1176
1177       match_byte_assert(1);
1178       if (outer_end - pos > 6)
1179         {
1180           /* There might be a pair of strings representing a dataset and
1181              datafile name, or there might be a set of custom currency strings.
1182              The custom currency strings start with a pair of integers, so we
1183              can distinguish these from a string by checking for a null byte; a
1184              small 32-bit integer will always contain a null and a text string
1185              never will. */
1186           int save_pos = pos;
1187           int len = get_u32();
1188           bool has_dataset = !memchr(&data[pos], '\0', len);
1189           pos = save_pos;
1190
1191           if (has_dataset)
1192             {
1193               printf("<dataset>%s</dataset>\n", get_string());
1194               printf("<datafile>%s</datafile>\n", get_string());
1195
1196               match_u32_assert(0);
1197
1198               time_t date = get_u32();
1199               struct tm tm = *localtime(&date);
1200               char s[128];
1201               strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm);
1202               printf("<date>%s</date>\n", s);
1203
1204               match_u32_assert(0);
1205             }
1206         }
1207
1208       if (match_u32(5))
1209         {
1210           for (int i = 0; i < 5; i++)
1211             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1212         }
1213       else
1214         match_u32_assert(0);
1215
1216       match_byte_assert('.');
1217       get_bool();
1218
1219       if (pos < outer_end)
1220         {
1221           get_u32();
1222           match_u32_assert(0);
1223         }
1224       assert(pos == outer_end);
1225
1226       pos = outer_end;
1227     }
1228   else if (outer_end != pos)
1229     {
1230       pos += 14;
1231       printf("<command>%s</command>\n", get_string());
1232       printf("<command-local>%s</command-local>\n", get_string());
1233       printf("<language>%s</command>\n", get_string());
1234       printf("<charset>%s</charset>\n", get_string());
1235       printf("<locale>%s</locale>\n", get_string());
1236       get_bool();
1237       match_byte_assert(0);
1238       get_bool();
1239       get_bool();
1240
1241       printf("<epoch2>%d</epoch2>\n", get_u32());
1242       int decimal = data[pos];
1243       int grouping = data[pos + 1];
1244       if (match_byte('.'))
1245         {
1246           if (!match_byte(',') && !match_byte('\''))
1247             match_byte_assert(' ');
1248         }
1249       else
1250         {
1251           match_byte_assert(',');
1252           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1253             match_byte_assert(0);
1254         }
1255       printf("<format decimal=\"%c\"", decimal);
1256       if (grouping)
1257         printf(" grouping=\"%c\"", grouping);
1258       printf("\"/>\n");
1259       if (match_u32(5))
1260         {
1261           for (int i = 0; i < 5; i++)
1262             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1263         }
1264       else
1265         match_u32_assert(0);
1266
1267       match_byte_assert('.');
1268       get_bool();
1269
1270       assert(pos == outer_end);
1271       pos = outer_end;
1272     }
1273 }
1274
1275 static const char *
1276 format_name (int format, char *buf)
1277 {
1278   switch (format)
1279     {
1280     case 1: return "A";
1281     case 2: return "AHEX";
1282     case 3: return "COMMA";
1283     case 4: return "DOLLAR";
1284     case 5: return "F";
1285     case 6: return "IB";
1286     case 7: return "PIBHEX";
1287     case 8: return "P";
1288     case 9: return "PIB";
1289     case 10: return "PK";
1290     case 11: return "RB";
1291     case 12: return "RBHEX";
1292     case 15: return "Z";
1293     case 16: return "N";
1294     case 17: return "E";
1295     case 20: return "DATE";
1296     case 21: return "TIME";
1297     case 22: return "DATETIME";
1298     case 23: return "ADATE";
1299     case 24: return "JDATE";
1300     case 25: return "DTIME";
1301     case 26: return "WKDAY";
1302     case 27: return "MONTH";
1303     case 28: return "MOYR";
1304     case 29: return "QYR";
1305     case 30: return "WKYR";
1306     case 31: return "PCT";
1307     case 32: return "DOT";
1308     case 33: return "CCA";
1309     case 34: return "CCB";
1310     case 35: return "CCC";
1311     case 36: return "CCD";
1312     case 37: return "CCE";
1313     case 38: return "EDATE";
1314     case 39: return "SDATE";
1315     case 40: return "MTIME";
1316     case 41: return "YMDHMS";
1317     default: sprintf(buf, "(%d)", format); return buf;
1318     }
1319 }
1320
1321 int
1322 main(int argc, char *argv[])
1323 {
1324   bool print_offsets = false;
1325   for (;;)
1326     {
1327       int c = getopt (argc, argv, "o");
1328       if (c == -1)
1329         break;
1330
1331       switch (c)
1332         {
1333         case 'o':
1334           print_offsets = true;
1335           break;
1336
1337         case '?':
1338           exit (-1);
1339         }
1340     }
1341   if (argc - optind != 1)
1342     {
1343       fprintf (stderr, "usage: %s FILE.bin", argv[0]);
1344       exit (1);
1345     }
1346
1347   const char *filename = argv[optind];
1348   int fd = open(filename, O_RDONLY);
1349   if (fd < 0)
1350     {
1351       fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
1352       exit (1);
1353     }
1354
1355   struct stat s;
1356   if (fstat(fd, &s))
1357     {
1358       perror("fstat");
1359       exit(1);
1360     }
1361   n = s.st_size;
1362   data = malloc(n);
1363   if (!data)
1364     {
1365       perror("malloc");
1366       exit(1);
1367     }
1368   if (read(fd, data, n) != n)
1369     {
1370       perror("read");
1371       exit(1);
1372     }
1373   close(fd);
1374
1375   setvbuf (stdout, NULL, _IOLBF, 0);
1376
1377   int sum = 0;
1378   
1379 #if 0
1380   unsigned int prev_end = 0;
1381   for (pos = 0; pos + 50 < n; pos++)
1382     {
1383       if (data[pos + 0] == 0xff &&
1384           data[pos + 1] == 0xff &&
1385           data[pos + 2] == 0 &&
1386           data[pos + 3] == 0)
1387         {
1388           int len = data[pos + 4] + (data[pos + 5] << 8);
1389           if (len < 3 || pos + len + 6 >= n || !all_utf8 ((char *) &data[pos + 6], len))
1390             continue;
1391
1392           printf ("+%04x %04x...%04x: %-25.*s\n",
1393                   pos - prev_end, pos, pos + 6 + len,
1394                   len < 50 ? (int) len : 50, &data[pos + 6]);
1395           prev_end = pos + 6 + len;
1396         }
1397     }
1398 #endif
1399 #if 0
1400   for (pos = 0; pos + 50 < n; pos++)
1401     {
1402       if (data[pos + 0] == 'L' &&
1403           data[pos + 1] == 'o' &&
1404           data[pos + 2] == 'g' &&
1405           !all_utf8((char *) &data[pos + 3], 1) &&
1406           data[pos - 1] != 'v')
1407         {
1408           if (print_offsets)
1409             printf ("%04x: ", pos);
1410           unsigned int p = pos;
1411           while (all_utf8 ((char *) &data[p], 1))
1412             p--;
1413           hex_dump (stdout, p - 28, 38);
1414         }
1415     }
1416 #endif
1417   unsigned int prev_end = 0;
1418   char *title = "";
1419   for (pos = 2; pos + 50 < n; pos++)
1420     {
1421       static const int cell_prefix[] = {
1422         0x00, 0x03,
1423         0x80, 0x00, 0x00, 0x00, 0x00, 0x00, -1 /* 00 or 10 */, 0x00, 0x00, 0x00, 0x00, -1,
1424
1425         /*14    15  16  17  18  19 */
1426         0x80, 0x01, -1, -1, -1, -1,
1427       };
1428       size_t cell_prefix_len = sizeof cell_prefix / sizeof *cell_prefix;
1429       if (match_bytes(pos, cell_prefix, cell_prefix_len))
1430         {
1431           if (prev_end != pos)
1432             {
1433               if (print_offsets)
1434                 printf ("%04x ", prev_end);
1435               hex_dump (stdout, prev_end, pos - prev_end);
1436
1437               if (!strcmp (title, "DspNumber")
1438                   && pos - prev_end == 2
1439                   && data[prev_end + 1] == 0x80)
1440                 {
1441                   static int already = false;
1442                   if (!already)
1443                     fprintf (stderr, " sum=%d %02x\n", sum, data[prev_end]);
1444                   already = true;
1445                 }
1446             }
1447
1448           char buf[64];
1449           printf ("cell %s%d.%d ",
1450                   format_name (data[pos + 18], buf),
1451                   data[pos + 17],
1452                   data[pos + 16]);
1453
1454           int len = cell_prefix_len;
1455           if (data[pos + 19] == 0)
1456             {
1457               assert (data[pos + 13] == 5);
1458               if (data[pos + 20] == 0)
1459                 {
1460                   int count = (data[pos + 22]);
1461                   printf ("%d %d \"%.*s\"\n",
1462                           data[pos + 21], data[pos + 22],
1463                           count, &data[pos + 23]);
1464                   len = 23 + count;
1465                 }
1466               else if (data[pos + 20] == 1
1467                        && data[pos + 21] == 0xff
1468                        && data[pos + 22] == 0xff)
1469                 {
1470                   int count = 255;
1471                   printf ("%d \"%.*s\"\n", count, data[pos + 23],
1472                           &data[pos + 24]);
1473                   len = 23 + count;
1474                 }
1475               else if (data[pos + 20] == 1)
1476                 {
1477                   int count = (data[pos + 21]);
1478                   printf ("\"%.*s\"\n",
1479                           count, &data[pos + 22]);
1480                   len = 22 + count;
1481                 }
1482               else
1483                 assert (false);
1484             }
1485           else if (data[pos + 19] == 128 && data[pos + 20] == 2)
1486             {
1487               /* pos + 13 is usually 22...53, and it's 3 more than the
1488                  " xx 80" separator between cells  */
1489               printf ("xxx%x ", data[pos + 13]);
1490               double d = *(double *) &data[pos + 21];
1491               len = 29;
1492               const union
1493                 {
1494                   uint8_t b[8];
1495                   double d;
1496                 }
1497               sysmis = {.b = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}};
1498               if (d == sysmis.d)
1499                 printf ("sysmis");
1500               else
1501                 printf ("%f", d);
1502
1503               if (data[pos + 29] < 0xff
1504                   && all_utf8((char *) &data[pos + 30], data[pos + 29]))
1505                 {
1506                   printf (" \"%.*s\"", (int) data[pos + 29],
1507                           &data[pos + 30]);
1508                   len += data[pos + 29] + 1;
1509                 }
1510               else
1511                 assert (false);
1512
1513               putchar ('\n');
1514             }
1515           else if (data[pos + 19] == 128 && data[pos + 20] == 1 &&
1516                    data[pos + 21] == 0)
1517             {
1518               if (data[pos + 23] < 0xff
1519                   && all_utf8((char *) &data[pos + 24], data[pos + 23]))
1520                 {
1521                   printf (" \"%.*s\"\n", (int) data[pos + 23],
1522                           &data[pos + 24]);
1523                   len = 24 + data[pos + 23];
1524                 }
1525               else
1526                 assert (false);
1527             }
1528           else
1529             {
1530               printf ("xxx%d %d %d %d\n",
1531                       data[pos + 19], data[pos + 20],
1532                       data[pos + 21], data[pos + 22]);
1533               assert(false);
1534             }
1535           pos += len - 1;
1536           prev_end = pos + 1;
1537           continue;
1538         }
1539
1540 #if 0
1541       static const int col_prefix[] = {
1542         0x11, 0x80, 0x00, -1, 0x00, 0x00, 0x00, 0x01, 0x00
1543       };
1544       size_t col_prefix_len = sizeof col_prefix / sizeof *col_prefix;
1545       if (match_bytes(pos, col_prefix, col_prefix_len))
1546         {
1547           if (prev_end != pos)
1548             {
1549               if (print_offsets)
1550                 printf ("%04x ", prev_end);
1551               hex_dump (stdout, prev_end, pos - prev_end);
1552             }
1553
1554           printf ("col %d\n", data[pos + 3]);
1555           pos += col_prefix_len - 1;
1556           prev_end = pos + 1;
1557           continue;
1558         }
1559 #endif
1560       
1561       static const int record_prefix[] = {
1562         0xff, 0xff, 0x00, 0x00,
1563       };
1564       size_t record_prefix_len = sizeof record_prefix / sizeof *record_prefix;
1565       if (match_bytes(pos, record_prefix, record_prefix_len))
1566         {
1567           int len = record_prefix_len;
1568           int slen = data[pos + 4] + (data[pos + 5] << 8);
1569           if (slen >= 2 && slen < 256 && all_utf8((char *) &data[pos + 6], slen))
1570             {
1571               if (prev_end != pos)
1572                 {
1573                   if (print_offsets)
1574                     printf ("%04x ", prev_end);
1575                   hex_dump (stdout, prev_end, pos - prev_end);
1576                 }
1577
1578               putchar ('\n');
1579
1580               printf ("rec:%-20.*s ", slen, &data[pos + 6]);
1581               len = slen + 6;
1582               title = xmemdup0(&data[pos + 6], slen);
1583               fprintf (stderr, "%s%d ", title, data[pos + len]);
1584               sum += data[pos+len];
1585
1586               pos += len - 1;
1587               prev_end = pos + 1;
1588               continue;
1589             }
1590         }
1591
1592       static const int number_prefix[] = {
1593         0x80, 0x02
1594       };
1595       size_t number_prefix_len = sizeof number_prefix / sizeof *number_prefix;
1596       if (match_bytes(pos, number_prefix, number_prefix_len))
1597         {
1598           if (prev_end != pos)
1599             {
1600               if (print_offsets)
1601                 printf ("%04x ", prev_end);
1602               hex_dump (stdout, prev_end, pos - prev_end);
1603             }
1604           prev_end = pos;
1605
1606           double d = *(double *) &data[pos + number_prefix_len];
1607           printf ("float %f\n", d);
1608
1609           pos += 10 - 1;
1610           prev_end = pos + 1;
1611           continue;
1612         }
1613
1614       if (!memcmp (&data[pos + 4], "{\\rtf", 5))
1615         {
1616           int len = data[pos] + (data[pos + 1] << 8) + (data[pos + 2] << 16)
1617             + (data[pos + 3] << 24);
1618           if (len < n - pos - 4)
1619             {
1620               if (prev_end != pos)
1621                 {
1622                   if (print_offsets)
1623                     printf ("%04x ", prev_end);
1624                   hex_dump (stdout, prev_end, pos - prev_end);
1625                 }
1626               prev_end = pos;
1627
1628               printf ("rtf\n");
1629               pos += 4 + len - 1;
1630               prev_end = pos + 1;
1631               continue;
1632             }
1633         }
1634
1635       static const int string_prefix[] = {
1636         0x80, 0x01, 0x02, 0x28, 0x05, 0x00, 0x01
1637       };
1638       size_t string_prefix_len = sizeof string_prefix / sizeof *string_prefix;
1639       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] != 255)
1640         {
1641           if (prev_end != pos)
1642             {
1643               if (print_offsets)
1644                 printf ("%04x ", prev_end);
1645               hex_dump (stdout, prev_end, pos - prev_end);
1646             }
1647           prev_end = pos;
1648
1649           int len = data[pos + 7];
1650           printf ("string %.*s\n", len, &data[pos + 8]);
1651           pos += 8 + len - 1;
1652           prev_end = pos + 1;
1653           continue;
1654         }
1655       if (match_bytes(pos, string_prefix, string_prefix_len) && data[pos + string_prefix_len] == 255)
1656         {
1657           if (prev_end != pos)
1658             {
1659               if (print_offsets)
1660                 printf ("%04x ", prev_end);
1661               hex_dump (stdout, prev_end, pos - prev_end);
1662             }
1663           prev_end = pos;
1664
1665           int len = data[pos + 8] + (data[pos + 9] << 8);
1666           printf ("\nlongstring %.*s\n", len, &data[pos + 10]);
1667           pos += 10 + len - 1;
1668           prev_end = pos + 1;
1669           continue;
1670         }
1671
1672       
1673
1674 #if 0
1675       static const int heading_prefix[] = {
1676         -1, 0x00, 0x00, 0x00, 0x50, 0x80, 0x00, 0x52, 0x80, 0x00, -1, 0x00,
1677         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
1678         0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
1679         0x00, 0x05, 0x80, 0x01, 0x02, 0x28, 0x05, 0x00, 0x01
1680       };
1681       size_t heading_prefix_len = sizeof heading_prefix / sizeof *heading_prefix;
1682       if (match_bytes(pos, heading_prefix, heading_prefix_len))
1683         {
1684           if (prev_end != pos)
1685             {
1686               if (print_offsets)
1687                 printf ("%04x ", prev_end);
1688               hex_dump (stdout, prev_end, pos - prev_end);
1689             }
1690
1691           printf ("heading %d %d\n", data[pos],data[pos + 10]);
1692           pos += heading_prefix_len - 1;
1693           prev_end = pos + 1;
1694           continue;
1695         }
1696
1697 #if 0
1698       static const int font_prefix[] = {
1699         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -1, 0x80, 0x00, 0x01, 0x00,
1700         0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, -1,
1701         0x80, 0x00, -1, 0x00, -1, 0x00, 0xc8, 0x00, -1, -1, -1, -1, -1,
1702         0x00, -1, 0x00, 0x00, 0x00, 0x01, 0x00, -1,
1703         0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1704         0x00, 0x00, 0x00, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1705         0x00, 0x00, -1 /* 12 or 22 */,
1706       };
1707       size_t font_prefix_len = sizeof font_prefix / sizeof *font_prefix;
1708       if (match_bytes(pos, font_prefix, font_prefix_len))
1709         {
1710           if (prev_end != pos)
1711             {
1712               if (print_offsets)
1713             printf ("%04x", prev_end);
1714               hex_dump (stdout, prev_end, pos - prev_end);
1715             }
1716
1717           printf ("font %d %d %d %d %d %d %d %d %d %d\n",
1718                   data[pos + 24], data[pos + 26],
1719                   data[pos + 30], data[pos + 31], data[pos + 32],
1720                   data[pos + 33], data[pos + 34], data[pos + 36],
1721                   data[pos + 58], data[pos + 59]);
1722           pos += font_prefix_len - 1;
1723           prev_end = pos + 1;
1724           continue;
1725         }
1726 #endif
1727       
1728       static const int table_prefix[] = {
1729         -1 /* ed or e9 */, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
1730         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x02, 0x00, 0x00,
1731         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x41, 0x72, 0x69,
1732         0x61, 0x6c, 0x00, -1, 0x00, -1, 0x00, 0x00, 0x00, 0x00, 0x00,
1733         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1734         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1735         0x00, 0x00, 0x00, 0x00, -1, 0x00, 0x00, 0x00, -1,
1736       };
1737       size_t table_prefix_len = sizeof table_prefix / sizeof *table_prefix;
1738       if (match_bytes(pos, table_prefix, table_prefix_len))
1739         {
1740           if (prev_end != pos)
1741             {
1742               if (print_offsets)
1743                 printf ("%04x", prev_end);
1744               hex_dump (stdout, prev_end, pos - prev_end);
1745             }
1746
1747           printf ("table %d\n", data[pos + 72]);
1748           pos += table_prefix_len - 1;
1749           prev_end = pos + 1;
1750           continue;
1751         }
1752
1753       static const int dim_prefix[] = {
1754         0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, -1,
1755         0x00, 0x00, 0x00, 0x00, 0x05, 0x80, 0x01, 0x02, 0x28,
1756         0x05, 0x00,
1757       };
1758       size_t dim_prefix_len = sizeof dim_prefix / sizeof *dim_prefix;
1759       if (match_bytes(pos, dim_prefix, dim_prefix_len))
1760         {
1761           if (prev_end != pos)
1762             {
1763               if (print_offsets)
1764                 printf ("%04x", prev_end);
1765               hex_dump (stdout, prev_end, pos - prev_end);
1766             }
1767
1768           printf ("dim %d\n", data[pos + 8]);
1769           pos += dim_prefix_len - 1;
1770           prev_end = pos + 1;
1771           continue;
1772         }
1773 #endif
1774
1775       if (!is_ascii(data[pos]))
1776         continue;
1777
1778       unsigned int start = pos;
1779       unsigned int end = pos + 1;
1780       while (is_ascii(data[end]))
1781         end++;
1782
1783       unsigned int len = end - start;
1784       if (len < 3)
1785         continue;
1786
1787       unsigned int len2 = data[start - 2] + (data[start - 1] << 8);
1788       unsigned int len3 = data[start - 1];
1789       int length_bytes;
1790       if (len2 && len2 <= len)
1791         {
1792           length_bytes = 2;
1793           len = len2;
1794         }
1795       else if (len3 && len3 <= len)
1796         {
1797           length_bytes = 1;
1798           len = len3;
1799         }
1800       else
1801         continue;
1802       if (len < 3)
1803         continue;
1804       end = start + len;
1805
1806       unsigned real_start = start - length_bytes;
1807       if (prev_end != real_start)
1808         {
1809           if (print_offsets)
1810             printf ("%04x ", prev_end);
1811           hex_dump (stdout, prev_end, real_start - prev_end);
1812         }
1813       if (print_offsets)
1814         printf ("%04x ", real_start);
1815       printf ("\"%.*s\"\n", 
1816               (int) end - start, (char *) &data[start]);
1817       prev_end = end;
1818       pos = end - 1;
1819     }
1820
1821   exit(0);
1822
1823   return 0;
1824 }