Document creation date.
[pspp] / dump.c
1 #include <assert.h>
2 #include <float.h>
3 #include <stdbool.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include "u8-mbtouc.h"
12
13 static uint8_t *data;
14 static size_t n;
15
16 int version;
17
18 static bool
19 all_ascii(const uint8_t *p, size_t n)
20 {
21   for (size_t i = 0; i < n; i++)
22     if (p[i] < 32 || p[i] > 126)
23       return false;
24   return true;
25 }
26
27 static size_t
28 try_find(const char *target, size_t target_len)
29 {
30   const uint8_t *pos = (const uint8_t *) memmem (data, n, target, target_len);
31   return pos ? pos - data : 0;
32 }
33
34 static size_t
35 find(const char *target, size_t target_len)
36 {
37   size_t pos = try_find(target, target_len);
38   if (!pos)
39     {
40       fprintf (stderr, "not found\n");
41       exit(1);
42     }
43   return pos;
44 }
45
46 size_t pos;
47
48 #define XSTR(x) #x
49 #define STR(x) XSTR(x)
50 #define WHERE __FILE__":" STR(__LINE__)
51
52 static uint8_t
53 get_byte(void)
54 {
55   return data[pos++];
56 }
57
58 static unsigned int
59 get_u32(void)
60 {
61   uint32_t x;
62   memcpy(&x, &data[pos], 4);
63   pos += 4;
64   return x;
65 }
66
67 static unsigned long long int
68 get_u64(void)
69 {
70   uint64_t x;
71   memcpy(&x, &data[pos], 8);
72   pos += 8;
73   return x;
74 }
75
76 static unsigned int
77 get_be32(void)
78 {
79   uint32_t x;
80   x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
81   pos += 4;
82   return x;
83 }
84
85 static unsigned int
86 get_u16(void)
87 {
88   uint16_t x;
89   memcpy(&x, &data[pos], 2);
90   pos += 2;
91   return x;
92 }
93
94 static double
95 get_double(void)
96 {
97   double x;
98   memcpy(&x, &data[pos], 8);
99   pos += 8;
100   return x;
101 }
102
103 static double __attribute__((unused))
104 get_float(void)
105 {
106   float x;
107   memcpy(&x, &data[pos], 4);
108   pos += 4;
109   return x;
110 }
111
112 static bool
113 match_u32(uint32_t x)
114 {
115   if (get_u32() == x)
116     return true;
117   pos -= 4;
118   return false;
119 }
120
121 static void
122 match_u32_assert(uint32_t x, const char *where)
123 {
124   unsigned int y = get_u32();
125   if (x != y)
126     {
127       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u\n", where, pos - 4, x, y);
128       exit(1);
129     }
130 }
131 #define match_u32_assert(x) match_u32_assert(x, WHERE)
132
133 static bool __attribute__((unused))
134 match_be32(uint32_t x)
135 {
136   if (get_be32() == x)
137     return true;
138   pos -= 4;
139   return false;
140 }
141
142 static void
143 match_be32_assert(uint32_t x, const char *where)
144 {
145   unsigned int y = get_be32();
146   if (x != y)
147     {
148       fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y);
149       exit(1);
150     }
151 }
152 #define match_be32_assert(x) match_be32_assert(x, WHERE)
153
154 static bool
155 match_byte(uint8_t b)
156 {
157   if (pos < n && data[pos] == b)
158     {
159       pos++;
160       return true;
161     }
162   else
163     return false;
164 }
165
166 static void
167 match_byte_assert(uint8_t b, const char *where)
168 {
169   if (!match_byte(b))
170     {
171       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x\n", where, pos, b, data[pos]);
172       exit(1);
173     }
174 }
175 #define match_byte_assert(b) match_byte_assert(b, WHERE)
176
177 static void
178 newline(FILE *stream, int pos)
179 {
180   fprintf(stream, "\n%08x: ", pos);
181 }
182
183 static void
184 dump_raw(FILE *stream, int start, int end)
185 {
186   for (size_t i = start; i < end; )
187     {
188       if (i + 5 <= n
189           && data[i]
190           //&& !data[i + 1]
191           && !data[i + 2]
192           && !data[i + 3]
193           && i + 4 + data[i] + data[i + 1] * 256 <= end
194           && all_ascii(&data[i + 4], data[i] + data[i + 1] * 256))
195         {
196           newline(stream, i);
197           fprintf(stream, "\"");
198           fwrite(&data[i + 4], 1, data[i] + data[i + 1] * 256, stream);
199           fputs("\" ", stream);
200
201           i += 4 + data[i] + data[i + 1] * 256;
202         }
203       else if (i + 12 <= end
204                && data[i + 1] == 40
205                && data[i + 2] == 5
206                && data[i + 3] == 0)
207         {
208           double d;
209
210           memcpy (&d, &data[i + 4], 8);
211           fprintf (stream, "F40.%d(%.*f)", data[i], data[i], d);
212           i += 12;
213           newline (stream, i);
214         }
215       else if (i + 12 <= end
216                && data[i + 1] == 40
217                && data[i + 2] == 31
218                && data[i + 3] == 0)
219         {
220           double d;
221
222           memcpy (&d, &data[i + 4], 8);
223           fprintf (stream, "PCT40.%d(%.*f)", data[i], data[i], d);
224           i += 12;
225           newline(stream, i);
226         }
227       else if (i + 4 <= end
228                && (data[i] && data[i] != 88 && data[i] != 0x41)
229                && !data[i + 1]
230                && !data[i + 2]
231                && !data[i + 3])
232         {
233           fprintf (stream, "i%d ", data[i]);
234           i += 4;
235         }
236       else
237         {
238           fprintf(stream, "%02x ", data[i]);
239           i++;
240         }
241     }
242
243
244 }
245
246 static bool __attribute__((unused))
247 all_utf8(const char *p_)
248 {
249   const uint8_t *p = (const uint8_t *) p_;
250   size_t len = strlen ((char *) p);
251   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
252     {
253       ucs4_t uc;
254
255       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
256       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
257         return false;
258     }
259   return true;
260 }
261
262 static char *
263 get_string(const char *where)
264 {
265   if (1
266       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
267       /*&& all_ascii(&data[pos + 4], data[pos])*/)
268     {
269       int len = data[pos] + data[pos + 1] * 256;
270       char *s = malloc(len + 1);
271
272       memcpy(s, &data[pos + 4], len);
273       s[len] = 0;
274       pos += 4 + len;
275       return s;
276     }
277   else
278     {
279       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
280       exit(1);
281     }
282 }
283 #define get_string() get_string(WHERE)
284
285 static char *
286 get_string_be(const char *where)
287 {
288   if (1
289       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
290       /*&& all_ascii(&data[pos + 4], data[pos])*/)
291     {
292       int len = data[pos + 2] * 256 + data[pos + 3];
293       char *s = malloc(len + 1);
294
295       memcpy(s, &data[pos + 4], len);
296       s[len] = 0;
297       pos += 4 + len;
298       return s;
299     }
300   else
301     {
302       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
303       exit(1);
304     }
305 }
306 #define get_string_be() get_string_be(WHERE)
307
308 static int
309 get_end(void)
310 {
311   int len = get_u32();
312   return pos + len;
313 }
314
315 static void __attribute__((unused))
316 hex_dump(int ofs, int n)
317 {
318   for (int i = 0; i < n; i++)
319     {
320       int c = data[ofs + i];
321 #if 1
322       if (i && !(i % 16))
323         fprintf(stderr, "-");
324       else
325         fprintf(stderr, " ");
326 #endif
327       fprintf(stderr, "%02x", c);
328     }
329   for (int i = 0; i < n; i++)
330     {
331       int c = data[ofs + i];
332       fprintf(stderr, "%c", c >= 32 && c < 127 ? c : '.');
333     }
334   fprintf(stderr, "\n");
335 }
336
337 static char *
338 dump_counted_string(void)
339 {
340   char *s = NULL;
341   int inner_end = get_end();
342   if (pos != inner_end)
343     {
344       if (match_u32(5))
345         {
346           match_u32_assert(0);
347           match_byte_assert(0x58);
348         }
349       else
350         match_u32_assert(0);
351       if (match_byte(0x31))
352         s = get_string();
353       else
354         match_byte_assert(0x58);
355       if (pos != inner_end)
356         {
357           fprintf(stderr, "inner end discrepancy\n");
358           exit(1);
359         }
360     }
361   return s;
362 }
363
364 static void
365 dump_style(FILE *stream)
366 {
367   match_byte(1);
368   match_byte(0);
369   match_byte(0);
370   match_byte(0);
371   match_byte_assert(1);
372   char *fg = get_string();     /* foreground */
373   char *bg = get_string();     /* background */
374   char *font = get_string();     /* font */
375   int size = data[pos];
376   if (!match_byte(14))
377     match_byte_assert(12); /* size? */
378   fprintf(stream, " fgcolor=\"%s\" bgcolor=\"%s\" font=\"%s\" size=\"%d\"",
379           fg, bg, font, size);
380 }
381
382 static char *
383 dump_nested_string(FILE *stream)
384 {
385   char *s = NULL;
386
387   match_byte_assert (0);
388   match_byte_assert (0);
389   int outer_end = get_end();
390   s = dump_counted_string();
391   if (s)
392     fprintf(stream, " \"%s\"", s);
393   if (match_byte(0x31))
394     dump_style(stream);
395   else
396     match_byte_assert(0x58);
397   match_byte_assert(0x58);
398   if (pos != outer_end)
399     {
400       fprintf(stderr, "outer end discrepancy\n");
401       exit(1);
402     }
403
404   return s;
405 }
406
407 static void
408 dump_value_modifier(FILE *stream)
409 {
410   if (match_byte (0x31))
411     {
412       if (match_u32 (0))
413         {
414           fprintf(stream, "<special0");
415           if (match_u32 (1))
416             {
417               /* Corpus frequencies:
418                  124 "a"
419                  12 "b"
420                  8 "a, b"
421
422                  The given text is appended to the cell in a subscript font.
423               */
424               fprintf(stream, " subscript=\"%s\"", get_string());
425             }
426           else
427             match_u32_assert (0);
428
429           if (version == 1)
430             {
431               /* We only have one SPV file for this version (with many
432                  tables). */
433               match_byte(0);
434               if (!match_u32(1))
435                 match_u32_assert(2);
436               match_byte(0);
437               match_byte(0);
438               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))
439                 match_u32_assert(10);
440               match_byte(0);
441               match_byte(0);
442               fprintf(stream, "/>\n");
443               return;
444             }
445
446           int outer_end = get_end();
447           
448           /* This counted-string appears to be a template string,
449              e.g. "Design\: [:^1:]1 Within Subjects Design\: [:^1:]2". */
450           char *template = dump_counted_string();
451           if (template)
452             fprintf(stream, " template=\"%s\"", template);
453
454           if (match_byte(0x31))
455             dump_style(stream);
456           else
457             match_byte_assert(0x58);
458           if (match_byte(0x31))
459             {
460               /* Only two SPV files have anything like this, so it's hard to
461                  generalize. */
462               match_u32_assert(0);
463               match_u32_assert(0);
464               match_u32_assert(0);
465               match_u32_assert(0);
466               match_byte_assert(1);
467               match_byte_assert(0);
468               if (!match_byte(8) && !match_byte(1))
469                 match_byte_assert(2);
470               match_byte_assert(0);
471               match_byte_assert(8);
472               match_byte_assert(0);
473               match_byte_assert(10);
474               match_byte_assert(0);
475             }
476           else
477             match_byte_assert(0x58);
478           if (pos != outer_end)
479             {
480               fprintf(stderr, "outer end discrepancy\n");
481               exit(1);
482             }
483           fprintf(stream, "/>\n");
484         }
485       else
486         {
487           int count = get_u32();
488           fprintf(stream, "<footnote-ref indexes=\"");
489           for (int i = 0; i < count; i++)
490             {
491               if (i)
492                 putc(' ', stream);
493               fprintf(stream, "%d", get_u16());
494             }
495           putc('"', stream);
496           match_byte_assert(0);
497           match_byte_assert(0);
498           dump_nested_string(stream);
499           fprintf(stream, "/>\n");
500         }
501     }
502   else
503     match_byte_assert (0x58);
504 }
505
506 static const char *
507 format_to_string (int type)
508 {
509   static char tmp[16];
510   switch (type)
511     {
512     case 1: return "A";
513     case 2: return "AHEX";
514     case 3: return "COMMA";
515     case 4: return "DOLLAR";
516     case 5: case 40: return "F";
517     case 6: return "IB";
518     case 7: return "PIBHEX";
519     case 8: return "P";
520     case 9: return "PIB";
521     case 10: return "PK";
522     case 11: return "RB";
523     case 12: return "RBHEX";
524     case 15: return "Z";
525     case 16: return "N";
526     case 17: return "E";
527     case 20: return "DATE";
528     case 21: return "TIME";
529     case 22: return "DATETIME";
530     case 23: return "ADATE";
531     case 24: return "JDATE";
532     case 25: return "DTIME";
533     case 26: return "WKDAY";
534     case 27: return "MONTH";
535     case 28: return "MOYR";
536     case 29: return "QYR";
537     case 30: return "WKYR";
538     case 31: return "PCT";
539     case 32: return "DOT";
540     case 33: return "CCA";
541     case 34: return "CCB";
542     case 35: return "CCC";
543     case 36: return "CCD";
544     case 37: return "CCE";
545     case 38: return "EDATE";
546     case 39: return "SDATE";
547     default:
548       abort();
549       sprintf(tmp, "<%d>", type);
550       return tmp;
551     }
552 }
553
554 static void
555 dump_value(FILE *stream, int level)
556 {
557   match_byte(0);
558   match_byte(0);
559   match_byte(0);
560   match_byte(0);
561
562   for (int i = 0; i <= level; i++)
563     fprintf (stream, "    ");
564
565   if (match_byte (1))
566     {
567       unsigned int format;
568       double value;
569
570       dump_value_modifier(stream);
571       format = get_u32 ();
572       value = get_double ();
573       fprintf (stream, "<number value=\"%.*g\" format=\"%s%d.%d\"/>\n",
574                DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
575     }
576   else if (match_byte (2))
577     {
578       unsigned int format;
579       char *var, *vallab;
580       double value;
581
582       dump_value_modifier (stream);
583       format = get_u32 ();
584       value = get_double ();
585       var = get_string ();
586       vallab = get_string ();
587       fprintf (stream, "<numeric-datum value=\"%.*g\" format=\"%s%d.%d\"",
588               DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
589       if (var[0])
590         fprintf (stream, " variable=\"%s\"", var);
591       if (vallab[0])
592         fprintf (stream, " label=\"%s\"", vallab);
593       fprintf (stream, "/>\n");
594       if (!match_byte (1) && !match_byte(2))
595         match_byte_assert (3);
596     }
597   else if (match_byte (3))
598     {
599       char *text =  get_string();
600       dump_value_modifier(stream);
601       char *identifier = get_string();
602       char *text_eng = get_string();
603       fprintf (stream, "<string c=\"%s\"", text_eng);
604       if (identifier[0])
605         fprintf (stream, " identifier=\"%s\"", identifier);
606       if (strcmp(text_eng, text))
607         fprintf (stream, " local=\"%s\"", text);
608       fprintf (stream, "/>\n");
609       if (!match_byte (0))
610         match_byte_assert(1);
611     }
612   else if (match_byte (4))
613     {
614       unsigned int format;
615       char *var, *vallab, *value;
616
617       dump_value_modifier(stream);
618       format = get_u32 ();
619       vallab = get_string ();
620       var = get_string ();
621       if (!match_byte(1) && !match_byte(2))
622         match_byte_assert (3);
623       value = get_string ();
624       fprintf (stream, "<string-datum value=\"%s\" format=\"%s%d.%d\"",
625               value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
626       if (var[0])
627         fprintf (stream, " variable=\"%s\"", var);
628       if (vallab[0])
629         fprintf (stream, " label=\"%s\"/>\n", vallab);
630       fprintf (stream, "/>\n");
631     }
632   else if (match_byte (5))
633     {
634       dump_value_modifier(stream);
635       char *name = get_string ();
636       char *label = get_string ();
637       fprintf (stream, "<variable name=\"%s\"", name);
638       if (label[0])
639         fprintf (stream, " label=\"%s\"", label);
640       fprintf (stream, "/>\n");
641       if (!match_byte(1) && !match_byte(2))
642         match_byte_assert(3);
643     }
644   else
645     {
646       dump_value_modifier(stream);
647
648       char *base = get_string();
649       int x = get_u32();
650       fprintf (stream, "<template format=\"%s\">\n", base);
651       for (int i = 0; i < x; i++)
652         {
653           int y = get_u32();
654           if (!y)
655             y = 1;
656           else
657             match_u32_assert(0);
658           for (int j = 0; j <= level + 1; j++)
659             fprintf (stream, "    ");
660           fprintf (stream, "<substitution index=\"%d\">\n", i + 1);
661           for (int j = 0; j < y; j++)
662             dump_value (stream, level + 2);
663           for (int j = 0; j <= level + 1; j++)
664             fprintf (stream, "    ");
665           fprintf (stream, "</substitution>\n");
666         }
667       for (int j = 0; j <= level; j++)
668         fprintf (stream, "    ");
669       fprintf (stream, "</template>\n");
670     }
671 }
672
673 static int
674 compare_int(const void *a_, const void *b_)
675 {
676   const int *a = a_;
677   const int *b = b_;
678   return *a < *b ? -1 : *a > *b;
679 }
680
681 static void
682 check_permutation(int *a, int n, const char *name)
683 {
684   int b[n];
685   memcpy(b, a, n * sizeof *a);
686   qsort(b, n, sizeof *b, compare_int);
687   for (int i = 0; i < n; i++)
688     if (b[i] != i)
689       {
690         fprintf(stderr, "bad %s permutation:", name);
691         for (int i = 0; i < n; i++)
692           fprintf(stderr, " %d", a[i]);
693         putc('\n', stderr);
694         exit(1);
695       }
696 }
697
698 static void
699 dump_category(FILE *stream, int level, int **indexes, int *allocated_indexes,
700               int *n_indexes)
701 {
702   for (int i = 0; i <= level; i++)
703     fprintf (stream, "    ");
704   printf ("<category>\n");
705   dump_value (stream, level + 1);
706
707   int merge = data[pos];
708   if (!match_byte(0))
709     match_byte_assert (1);
710
711   match_byte_assert (0);
712
713   int unindexed = data[pos];
714   if (!match_byte(0))
715     match_byte_assert (1);
716
717   int x = get_u32 ();
718   pos -= 4;
719   if (!match_u32 (0))
720     match_u32_assert (2);
721
722   int indx = get_u32();
723   int n_categories = get_u32();
724   if (indx == -1)
725     {
726       if (merge)
727         {
728           for (int i = 0; i <= level + 1; i++)
729             fprintf (stream, "    ");
730           fprintf (stream, "<merge/>\n");
731         }
732     }
733   else
734     {
735       if (merge)
736         {
737           fprintf(stderr, "index not -1 but merged\n");
738           exit(1);
739         }
740       if (x != 2)
741         {
742           fprintf(stderr, "index not -1 but x != 2\n");
743           exit(1);
744         }
745       if (n_categories != 0)
746         {
747           fprintf(stderr, "index not -1 but subcategories\n");
748           exit(1);
749         }
750       if (*n_indexes >= *allocated_indexes)
751         {
752           *allocated_indexes = *allocated_indexes ? 2 * *allocated_indexes : 16;
753           *indexes = realloc(*indexes, *allocated_indexes * sizeof **indexes);
754         }
755       (*indexes)[(*n_indexes)++] = indx;
756     }
757
758   int expected_unindexed = indx == -1;
759   if (unindexed != expected_unindexed)
760     {
761       fprintf(stderr, "unindexed (%d) mismatch with indx (%d)\n",
762               unindexed, indx);
763       exit(1);
764     }
765
766   if (n_categories == 0)
767     {
768       for (int i = 0; i <= level + 1; i++)
769         fprintf (stream, "    ");
770       fprintf (stream, "<category-index>%d</category-index>\n", indx);
771     }
772   for (int i = 0; i < n_categories; i++)
773     dump_category (stream, level + 1, indexes, allocated_indexes, n_indexes);
774   for (int i = 0; i <= level; i++)
775     fprintf (stream, "    ");
776   printf ("</category>\n");
777 }
778
779 static int
780 dump_dim(int indx)
781 {
782   int n_categories;
783
784   printf ("<dimension index=\"%d\">\n", indx);
785   dump_value (stdout, 0);
786
787   /* This byte is usually 0 but many other values have been spotted. */
788   pos++;
789
790   if (!match_byte(0) && !match_byte(1))
791     match_byte_assert(2);
792   if (!match_u32(0))
793     match_u32_assert(2);
794   if (!match_byte(0))
795     match_byte_assert(1);
796   if (!match_byte(0))
797     match_byte_assert(1);
798   match_byte_assert(1);
799   if (!match_u32(UINT32_MAX))
800     match_u32_assert(indx);
801   n_categories = get_u32();
802
803   int *indexes = NULL;
804   int n_indexes = 0;
805   int allocated_indexes = 0;
806   for (int i = 0; i < n_categories; i++)
807     dump_category (stdout, 0, &indexes, &allocated_indexes, &n_indexes);
808   check_permutation(indexes, n_indexes, "categories");
809
810   fprintf (stdout, "</dimension>\n");
811   return n_indexes;
812 }
813
814 int n_dims;
815 static int dim_n_cats[64];
816 #define MAX_DIMS (sizeof dim_n_cats / sizeof *dim_n_cats)
817
818 static void
819 dump_dims(void)
820 {
821   n_dims = get_u32();
822   assert(n_dims < MAX_DIMS);
823   for (int i = 0; i < n_dims; i++)
824     dim_n_cats[i] = dump_dim (i);
825 }
826
827 static void
828 dump_data(void)
829 {
830   /* The first three numbers add to the number of dimensions. */
831   int l = get_u32();
832   int r = get_u32();
833   int c = n_dims - l - r;
834   match_u32_assert(c);
835
836   /* The next n_dims numbers are a permutation of the dimension numbers. */
837   int a[n_dims];
838   for (int i = 0; i < n_dims; i++)
839     {
840       int dim = get_u32();
841       a[i] = dim;
842
843       const char *name = i < l ? "layer" : i < l + r ? "row" : "column";
844       printf ("<%s dimension=\"%d\"/>\n", name, dim);
845     }
846   check_permutation(a, n_dims, "dimensions");
847
848   int x = get_u32();
849   printf ("<data>\n");
850   for (int i = 0; i < x; i++)
851     {
852       unsigned int indx = get_u32();
853       printf ("    <datum index=\"%d\" coords=", indx);
854
855       int coords[MAX_DIMS];
856       for (int i = n_dims; i-- > 0; )
857         {
858           coords[i] = indx % dim_n_cats[i];
859           indx /= dim_n_cats[i];
860         }
861       for (int i = 0; i < n_dims; i++)
862         printf("%c%d", i ? ',' : '"', coords[i]);
863
864       printf ("\">\n");
865       match_u32_assert(0);
866       if (version == 1)
867         match_byte(0);
868       dump_value(stdout, 1);
869       fprintf (stdout, "    </datum>\n");
870     }
871   printf ("</data>\n");
872 }
873
874 static void
875 dump_title(void)
876 {
877   printf ("<title-local>\n");
878   dump_value(stdout, 0);
879   match_byte(1);
880   printf ("</title-local>\n");
881
882   printf ("<subtype>\n");
883   dump_value(stdout, 0);
884   match_byte(1);
885   printf ("</subtype>\n");
886
887   match_byte_assert(0x31);
888
889   printf ("<title-c>\n");
890   dump_value(stdout, 0);
891   match_byte(1);
892   printf ("</title-c>\n");
893
894   if (match_byte(0x31))
895     {
896       printf ("<user-caption>\n");
897       dump_value(stdout, 0);
898       printf ("</user-caption>\n");
899     }
900   else
901     match_byte_assert(0x58);
902   if (match_byte(0x31))
903     {
904       printf ("<caption>\n");
905       dump_value(stdout, 0);
906       printf ("</caption>\n");
907     }
908   else
909     match_byte_assert(0x58);
910
911   int n_footnotes = get_u32();
912   for (int i = 0; i < n_footnotes; i++)
913     {
914       printf ("<footnote index=\"%d\">\n", i);
915       dump_value(stdout, 0);
916       /* Custom footnote marker string. */
917       if (match_byte (0x31))
918         dump_value(stdout, 0);
919       else
920         match_byte_assert (0x58);
921       get_u32 ();
922       printf ("</footnote>\n");
923     }
924 }
925
926 static void
927 dump_fonts(void)
928 {
929   match_byte(0);
930   for (int i = 1; i <= 8; i++)
931     {
932       printf ("<style index=\"%d\"", i);
933       match_byte_assert(i);
934       match_byte_assert(0x31);
935       printf(" font=\"%s\"", get_string());
936
937       printf(" size=\"%gpt\"", get_float());
938
939       int style = get_u32();
940       if (style & 1)
941         printf(" bold=\"true\"");
942       if (style & 2)
943         printf(" italic=\"true\"");
944
945       bool underline = data[pos++];
946       if (underline)
947         printf(" underline=\"true\"");
948
949       int halign = get_u32();
950       printf("\nhalign=%d\n", halign);
951
952       int valign = get_u32();
953       printf("\nvalign=%d\n", valign);
954
955       printf (" fgcolor=\"%s\"", get_string());
956       printf (" bgcolor=\"%s\"", get_string());
957
958       if (!match_byte(0))
959         match_byte_assert(1);
960       match_u32_assert(0);
961       char *othercolor = get_string();
962       if (othercolor[0])
963         printf(" othercolor=\"%s\"", othercolor);
964
965       if (version > 1)
966         {
967           printf("\nfonts:");
968           for (int i = 0; i < 4; i++)
969             printf(" %2d", get_u32());
970           printf("\n");
971         }
972
973       printf ("/>\n");
974     }
975
976   int x1 = get_u32();
977   int x1_end = pos + x1;
978   printf("<borders>\n");
979   match_be32_assert(1);
980   int n_borders = get_be32();
981   for (int i = 0; i < n_borders; i++)
982     {
983       int type = get_be32();
984       int stroke = get_be32();
985       int color = get_be32();
986       printf("  <border type=\"%d\" stroke=\"%s\" color=\"#%06x\"/>\n",
987              type,
988              (stroke == 0 ? "none"
989               : stroke == 1 ? "solid"
990               : stroke == 2 ? "dashed"
991               : stroke == 3 ? "thick"
992               : stroke == 4 ? "thin"
993               : stroke == 5 ? "double"
994               : "<error>"),
995              color);
996     }
997   bool grid = get_byte();
998   pos += 3;
999   printf("  <grid show=\"%s\">\n", grid ? "yes" : "no");
1000   printf("</borders>\n");
1001   assert(pos == x1_end);
1002
1003   int skip = get_u32();
1004   assert(skip == 18 || skip == 25);
1005   pos += skip;
1006
1007   int x3 = get_u32();
1008   int x3_end = pos + x3;
1009   if (version == 3)
1010     {
1011       match_be32_assert(1);
1012       get_be32();
1013       printf("<settings layer=\"%d\"", get_be32());
1014       if (!get_byte())
1015         printf(" skipempty=\"false\"");
1016       if (!get_byte())
1017         printf(" showdimensionincorner=\"false\"");
1018       if (!get_byte())
1019         printf(" markers=\"numeric\"");
1020       if (!get_byte())
1021         printf(" footnoteposition=\"subscript\"");
1022       get_byte();
1023       pos += get_be32();
1024       get_string_be();
1025       char *look = get_string_be();
1026       if (look[0])
1027         printf(" look=\"%s\"", look);
1028       printf(">\n");
1029     }
1030   pos = x3_end;
1031
1032   int count = get_u32();
1033   pos += 4 * count;
1034
1035   const char *locale = get_string();
1036   printf ("<locale>%s</locale>\n", locale);
1037
1038   get_u32();            /* Seen: 0, UINT32_MAX, 2, 3, 4, 5, 6, 8, 9, 21, 24. */
1039   if (!match_byte(0))
1040     match_byte_assert(1);
1041   match_byte_assert(0);
1042   if (!match_byte(0))
1043     match_byte_assert(1);
1044   printf("<epoch>%d</epoch>\n", get_u32());
1045
1046   int decimal = data[pos];
1047   int grouping = data[pos + 1];
1048   if (match_byte('.'))
1049     {
1050       if (!match_byte(',') && !match_byte('\''))
1051         match_byte_assert(' ');
1052     }
1053   else
1054     {
1055       match_byte_assert(',');
1056       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1057         match_byte_assert(0);
1058     }
1059   printf("<format decimal=\"%c\" grouping=\"", decimal);
1060   if (grouping)
1061     putchar(grouping);
1062   printf("\"/>\n");
1063   if (match_u32(5))
1064     {
1065       for (int i = 0; i < 5; i++)
1066         printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1067     }
1068   else
1069     match_u32_assert(0);
1070
1071   /* The last chunk is an outer envelope that contains two inner envelopes.
1072      The second inner envelope has some interesting data like the encoding and
1073      the locale. */
1074   if (version == 3)
1075     {
1076       int outer_end = get_end();
1077
1078       /* First inner envelope: byte*33 int[n] int*[n]. */
1079       pos = get_end();
1080
1081       /* Second inner envelope. */
1082       assert(get_end() == outer_end);
1083
1084       match_byte_assert(1);
1085       match_byte_assert(0);
1086       if (!match_byte(3) && !match_byte(4))
1087         match_byte_assert(5);
1088       match_byte_assert(0);
1089       match_byte_assert(0);
1090       match_byte_assert(0);
1091
1092       printf("<command>%s</command>\n", get_string());
1093       printf("<subcommand>%s</subcommand>\n", get_string());
1094       printf("<language>%s</language>\n", get_string());
1095       printf("<charset>%s</charset>\n", get_string());
1096       printf("<locale>%s</locale>\n", get_string());
1097
1098       if (!match_byte(0))
1099         match_byte_assert(1);
1100       match_byte_assert(0);
1101       if (!match_byte(0))
1102         match_byte_assert(1);
1103       if (!match_byte(0))
1104         match_byte_assert(1);
1105
1106       printf("<epoch2>%d</epoch2>\n", get_u32());
1107
1108       if (match_byte('.'))
1109         {
1110           if (!match_byte(',') && !match_byte('\''))
1111             match_byte_assert(' ');
1112         }
1113       else
1114         {
1115           match_byte_assert(',');
1116           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1117             match_byte_assert(0);
1118         }
1119
1120       pos += 8;
1121       match_byte_assert(1);
1122
1123       if (outer_end - pos > 6)
1124         {
1125           /* There might be a pair of strings representing a dataset and
1126              datafile name, or there might be a set of custom currency strings.
1127              The custom currency strings start with a pair of integers, so we
1128              can distinguish these from a string by checking for a null byte; a
1129              small 32-bit integer will always contain a null and a text string
1130              never will. */
1131           int save_pos = pos;
1132           int len = get_u32();
1133           bool has_dataset = !memchr(&data[pos], '\0', len);
1134           pos = save_pos;
1135
1136           if (has_dataset)
1137             {
1138               printf("<dataset>%s</dataset>\n", get_string());
1139               printf("<datafile>%s</datafile>\n", get_string());
1140
1141               match_u32_assert(0);
1142
1143               time_t date = get_u32();
1144               struct tm tm = *localtime(&date);
1145               char s[128];
1146               strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm);
1147               printf("<date>%s</date>\n", s);
1148
1149               match_u32_assert(0);
1150             }
1151         }
1152
1153       if (match_u32(5))
1154         {
1155           for (int i = 0; i < 5; i++)
1156             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1157         }
1158       else
1159         match_u32_assert(0);
1160
1161       match_byte_assert(0x2e);
1162       if (!match_byte(0))
1163         match_byte_assert(1);
1164
1165       if (pos < outer_end)
1166         {
1167           printf("<seed>%d</seed>\n", get_u32());
1168           match_u32_assert(0);
1169         }
1170       assert(pos == outer_end);
1171
1172       pos = outer_end;
1173     }
1174   else
1175     {
1176       pos = get_end();
1177     }
1178 }
1179
1180 int
1181 main(int argc, char *argv[])
1182 {
1183   size_t start;
1184   struct stat s;
1185
1186   if (isatty(STDIN_FILENO))
1187     {
1188       fprintf(stderr, "redirect stdin from a .bin file\n");
1189       exit(1);
1190     }
1191   if (fstat(STDIN_FILENO, &s))
1192     {
1193       perror("fstat");
1194       exit(1);
1195     }
1196   n = s.st_size;
1197   data = malloc(n);
1198   if (!data)
1199     {
1200       perror("malloc");
1201       exit(1);
1202     }
1203   if (read(STDIN_FILENO, data, n) != n)
1204     {
1205       perror("read");
1206       exit(1);
1207     }
1208
1209   if (argc != 2)
1210     {
1211       fprintf (stderr, "usage: %s TYPE < .bin", argv[0]);
1212       exit (1);
1213     }
1214
1215   if (!strcmp(argv[1], "title0"))
1216     {
1217       pos = 0x27;
1218       if (match_byte (0x03)
1219           || (match_byte (0x05) && match_byte (0x58)))
1220         printf ("%s\n", get_string());
1221       else
1222         printf ("<unknown>\n");
1223       return 0;
1224     }
1225   else if (!strcmp(argv[1], "title"))
1226     {
1227       pos = 0x27;
1228       dump_title();
1229       exit(0);
1230     }
1231   else if (!strcmp(argv[1], "titleraw"))
1232     {
1233       const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
1234       start = 0x27;
1235       n = find(fonts, sizeof fonts - 1);
1236     }
1237   else if (!strcmp(argv[1], "fonts"))
1238     {
1239       const char fonts[] = "\x01\x31\x09\0\0\0SansSerif";
1240       const char styles[] = "\xf0\0\0\0";
1241       start = find(fonts, sizeof fonts - 1);
1242       n = find(styles, sizeof styles - 1);
1243     }
1244   else if (!strcmp(argv[1], "styles"))
1245     {
1246       const char styles[] = "\xf0\0\0\0";
1247       const char dimensions[] = "-,,,.\0";
1248       start = find(styles, sizeof styles - 1);
1249       n = find(dimensions, sizeof dimensions - 1) + sizeof dimensions - 1;
1250     }
1251   else if (!strcmp(argv[1], "dimensions") || !strcmp(argv[1], "all"))
1252     {
1253       pos = 0;
1254       match_byte_assert(1);
1255       match_byte_assert(0);
1256
1257       /* This might be a version number of some kind, because value 1 seems
1258          to only appear in an SPV file that also required its own weird
1259          special cases in dump_value_modifier(). */
1260       version = get_u32();
1261       pos -= 4;
1262       if (!match_u32(1))
1263         match_u32_assert(3);
1264
1265       match_byte_assert(1);
1266       if (!match_byte(0))
1267         match_byte_assert(1);
1268
1269       /* Offset 8. */
1270       match_byte_assert(0);
1271       if (!match_byte(0))
1272         match_byte_assert(1);
1273       if (!match_byte(0))
1274         match_byte_assert(1);
1275
1276       /* Offset 11. */
1277       pos++;
1278       match_byte_assert(0);
1279       match_byte_assert(0);
1280       match_byte_assert(0);
1281
1282       /* Offset 15. */
1283       pos++;
1284       if (!match_byte(0))
1285         match_byte_assert(1);
1286       match_byte_assert(0);
1287       match_byte_assert(0);
1288
1289       /* Offset 19. */
1290       pos++;
1291       if (!match_byte(0))
1292         match_byte_assert(1);
1293       match_byte_assert(0);
1294       match_byte_assert(0);
1295
1296       /* Offset 23. */
1297       pos++;
1298       if (!match_byte(0))
1299         match_byte_assert(1);
1300       match_byte_assert(0);
1301       match_byte_assert(0);
1302
1303       /* Offset 27. */
1304       pos++;
1305       pos++;
1306       pos++;
1307       pos++;
1308
1309       /* Offset 31. */
1310       printf("<tableid>%lld</tableid>", get_u64());
1311
1312       dump_title ();
1313       dump_fonts();
1314       dump_dims ();
1315       dump_data ();
1316       match_byte (1);
1317       if (pos != n)
1318         {
1319           fprintf (stderr, "%x / %x\n", pos, n);
1320           exit(1);
1321         }
1322       exit(0);
1323     }
1324   else if (!strcmp(argv[1], "raw"))
1325     {
1326       start = 0x27;
1327
1328       dump_raw(stdout, start, n);
1329     }
1330   else
1331     {
1332       fprintf (stderr, "unknown section %s\n", argv[1]);
1333       exit(1);
1334     }
1335
1336   return 0;
1337 }