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