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