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