Figure some details of categories and dimensions.
[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   if (match_byte (1))
512     {
513       unsigned int format;
514       double value;
515
516       dump_value_modifier(stream);
517       format = get_u32 ();
518       value = get_double ();
519       fprintf (stream, "<number value=\"%.*g\" format=\"%s%d.%d\"/>\n",
520                DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
521     }
522   else if (match_byte (2))
523     {
524       unsigned int format;
525       char *var, *vallab;
526       double value;
527
528       dump_value_modifier (stream);
529       format = get_u32 ();
530       value = get_double ();
531       var = get_string ();
532       vallab = get_string ();
533       fprintf (stream, "<numeric-datum value=\"%.*g\" format=\"%s%d.%d\"",
534               DBL_DIG, value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
535       if (var[0])
536         fprintf (stream, " variable=\"%s\"", var);
537       if (vallab[0])
538         fprintf (stream, " label=\"%s\"", vallab);
539       fprintf (stream, "/>\n");
540       if (!match_byte (1) && !match_byte(2))
541         match_byte_assert (3);
542     }
543   else if (match_byte (3))
544     {
545       char *text =  get_string();
546       dump_value_modifier(stream);
547       char *identifier = get_string();
548       char *text_eng = get_string();
549       fprintf (stream, "<string c=\"%s\"", text_eng);
550       if (identifier[0])
551         fprintf (stream, " identifier=\"%s\"", identifier);
552       if (strcmp(text_eng, text))
553         fprintf (stream, " local=\"%s\"", text);
554       fprintf (stream, "/>\n");
555       if (!match_byte (0))
556         match_byte_assert(1);
557     }
558   else if (match_byte (4))
559     {
560       unsigned int format;
561       char *var, *vallab, *value;
562
563       dump_value_modifier(stream);
564       format = get_u32 ();
565       vallab = get_string ();
566       var = get_string ();
567       if (!match_byte(1) && !match_byte(2))
568         match_byte_assert (3);
569       value = get_string ();
570       fprintf (stream, "<string-datum value=\"%s\" format=\"%s%d.%d\"",
571               value, format_to_string(format >> 16), (format >> 8) & 0xff, format & 0xff);
572       if (var[0])
573         fprintf (stream, " variable=\"%s\"", var);
574       if (vallab[0])
575         fprintf (stream, " label=\"%s\"/>\n", vallab);
576       fprintf (stream, "/>\n");
577     }
578   else if (match_byte (5))
579     {
580       dump_value_modifier(stream);
581       char *name = get_string ();
582       char *label = get_string ();
583       fprintf (stream, "<variable name=\"%s\"", name);
584       if (label[0])
585         fprintf (stream, " label=\"%s\"", label);
586       fprintf (stream, "/>\n");
587       if (!match_byte(1) && !match_byte(2))
588         match_byte_assert(3);
589     }
590   else
591     {
592       dump_value_modifier(stream);
593
594       char *base = get_string();
595       int x = get_u32();
596       fprintf (stream, "<template format=\"%s\">\n", base);
597       for (int i = 0; i < x; i++)
598         {
599           int y = get_u32();
600           if (!y)
601             y = 1;
602           else
603             match_u32_assert(0);
604           for (int j = 0; j <= level + 1; j++)
605             fprintf (stream, "    ");
606           fprintf (stream, "<substitution index=\"%d\">\n", i + 1);
607           for (int j = 0; j < y; j++)
608             dump_value (stream, level + 2);
609           for (int j = 0; j <= level + 1; j++)
610             fprintf (stream, "    ");
611           fprintf (stream, "</substitution>\n");
612         }
613       for (int j = 0; j <= level; j++)
614         fprintf (stream, "    ");
615       fprintf (stream, "</template>\n");
616     }
617 }
618
619 static int
620 compare_int(const void *a_, const void *b_)
621 {
622   const int *a = a_;
623   const int *b = b_;
624   return *a < *b ? -1 : *a > *b;
625 }
626
627 static void
628 check_permutation(int *a, int n, const char *name)
629 {
630   int b[n];
631   memcpy(b, a, n * sizeof *a);
632   qsort(b, n, sizeof *b, compare_int);
633   for (int i = 0; i < n; i++)
634     if (b[i] != i)
635       {
636         fprintf(stderr, "bad %s permutation:", name);
637         for (int i = 0; i < n; i++)
638           fprintf(stderr, " %d", a[i]);
639         putc('\n', stderr);
640         exit(1);
641       }
642 }
643
644 static void
645 dump_category(FILE *stream, int level, int **indexes, int *allocated_indexes,
646               int *n_indexes)
647 {
648   for (int i = 0; i <= level; i++)
649     fprintf (stream, "    ");
650   printf ("<category>\n");
651   dump_value (stream, level + 1);
652
653   bool merge = get_bool();
654   match_byte_assert (0);
655   int unindexed = get_bool();
656
657   int x = get_u32 ();
658   pos -= 4;
659   if (!match_u32 (0))
660     match_u32_assert (2);
661
662   int indx = get_u32();
663   int n_categories = get_u32();
664   if (indx == -1)
665     {
666       if (merge)
667         {
668           for (int i = 0; i <= level + 1; i++)
669             fprintf (stream, "    ");
670           fprintf (stream, "<merge/>\n");
671         }
672       assert (unindexed);
673     }
674   else
675     {
676       assert (!merge);
677       assert (!unindexed);
678       assert (x == 2);
679       assert (n_categories == 0);
680       if (*n_indexes >= *allocated_indexes)
681         {
682           *allocated_indexes = *allocated_indexes ? 2 * *allocated_indexes : 16;
683           *indexes = realloc(*indexes, *allocated_indexes * sizeof **indexes);
684         }
685       (*indexes)[(*n_indexes)++] = indx;
686     }
687
688   if (n_categories == 0)
689     {
690       for (int i = 0; i <= level + 1; i++)
691         fprintf (stream, "    ");
692       fprintf (stream, "<category-index>%d</category-index>\n", indx);
693     }
694   for (int i = 0; i < n_categories; i++)
695     dump_category (stream, level + 1, indexes, allocated_indexes, n_indexes);
696   for (int i = 0; i <= level; i++)
697     fprintf (stream, "    ");
698   printf ("</category>\n");
699 }
700
701 static int
702 dump_dim(int indx)
703 {
704   int n_categories;
705
706   printf ("<dimension index=\"%d\">\n", indx);
707   dump_value (stdout, 0);
708
709   /* This byte is usually 0 but many other values have been spotted.
710      No visible effect. */
711   pos++;
712
713   /* This byte can cause data to be oddly replicated. */
714   if (!match_byte(0) && !match_byte(1))
715     match_byte_assert(2);
716
717   if (!match_u32(0))
718     match_u32_assert(2);
719
720   bool show_dim_label = get_bool();
721   if (show_dim_label)
722     printf("  <show-dim-label/>\n");
723
724   bool hide_all_labels = get_bool();
725   if (hide_all_labels)
726     printf("  <hide-all-labels/>\n");
727
728   match_byte_assert(1);
729   if (!match_u32(UINT32_MAX))
730     match_u32_assert(indx);
731
732   n_categories = get_u32();
733
734   int *indexes = NULL;
735   int n_indexes = 0;
736   int allocated_indexes = 0;
737   for (int i = 0; i < n_categories; i++)
738     dump_category (stdout, 0, &indexes, &allocated_indexes, &n_indexes);
739   check_permutation(indexes, n_indexes, "categories");
740
741   fprintf (stdout, "</dimension>\n");
742   return n_indexes;
743 }
744
745 int n_dims;
746 static int dim_n_cats[64];
747 #define MAX_DIMS (sizeof dim_n_cats / sizeof *dim_n_cats)
748
749 static void
750 dump_dims(void)
751 {
752   n_dims = get_u32();
753   assert(n_dims < MAX_DIMS);
754   for (int i = 0; i < n_dims; i++)
755     dim_n_cats[i] = dump_dim (i);
756 }
757
758 static void
759 dump_data(void)
760 {
761   /* The first three numbers add to the number of dimensions. */
762   int l = get_u32();
763   int r = get_u32();
764   int c = n_dims - l - r;
765   match_u32_assert(c);
766
767   /* The next n_dims numbers are a permutation of the dimension numbers. */
768   int a[n_dims];
769   for (int i = 0; i < n_dims; i++)
770     {
771       int dim = get_u32();
772       a[i] = dim;
773
774       const char *name = i < l ? "layer" : i < l + r ? "row" : "column";
775       printf ("<%s dimension=\"%d\"/>\n", name, dim);
776     }
777   check_permutation(a, n_dims, "dimensions");
778
779   int x = get_u32();
780   printf ("<data>\n");
781   for (int i = 0; i < x; i++)
782     {
783       unsigned int indx = get_u32();
784       printf ("    <datum index=\"%d\" coords=", indx);
785
786       int coords[MAX_DIMS];
787       for (int i = n_dims; i-- > 0; )
788         {
789           coords[i] = indx % dim_n_cats[i];
790           indx /= dim_n_cats[i];
791         }
792       for (int i = 0; i < n_dims; i++)
793         printf("%c%d", i ? ',' : '"', coords[i]);
794
795       printf ("\">\n");
796       match_u32_assert(0);
797       if (version == 1)
798         match_byte(0);
799       dump_value(stdout, 1);
800       fprintf (stdout, "    </datum>\n");
801     }
802   printf ("</data>\n");
803 }
804
805 static void
806 dump_title(void)
807 {
808   printf ("<title-local>\n");
809   dump_value(stdout, 0);
810   match_byte(1);
811   printf ("</title-local>\n");
812
813   printf ("<subtype>\n");
814   dump_value(stdout, 0);
815   match_byte(1);
816   printf ("</subtype>\n");
817
818   match_byte_assert(0x31);
819
820   printf ("<title-c>\n");
821   dump_value(stdout, 0);
822   match_byte(1);
823   printf ("</title-c>\n");
824
825   if (match_byte(0x31))
826     {
827       printf ("<user-caption>\n");
828       dump_value(stdout, 0);
829       printf ("</user-caption>\n");
830     }
831   else
832     match_byte_assert(0x58);
833   if (match_byte(0x31))
834     {
835       printf ("<caption>\n");
836       dump_value(stdout, 0);
837       printf ("</caption>\n");
838     }
839   else
840     match_byte_assert(0x58);
841
842   int n_footnotes = get_u32();
843   for (int i = 0; i < n_footnotes; i++)
844     {
845       printf ("<footnote index=\"%d\">\n", i);
846       dump_value(stdout, 0);
847       /* Custom footnote marker string. */
848       if (match_byte (0x31))
849         dump_value(stdout, 0);
850       else
851         match_byte_assert (0x58);
852       int n = get_u32();
853       if (n >= 0)
854         {
855           /* Appears to be the number of references to a footnote. */
856           printf ("  <references n=\"%d\"/>\n", n);
857         }
858       else if (n == -2)
859         {
860           /* The user deleted the footnote references. */
861           printf ("  <deleted/>\n");
862         }
863       else
864         assert(0);
865       printf ("</footnote>\n");
866     }
867 }
868
869 static void
870 dump_fonts(void)
871 {
872   match_byte(0);
873   for (int i = 1; i <= 8; i++)
874     {
875       printf ("<style index=\"%d\"", i);
876       match_byte_assert(i);
877       match_byte_assert(0x31);
878       printf(" font=\"%s\"", get_string());
879
880       printf(" size=\"%gpt\"", get_float());
881
882       int style = get_u32();
883       if (style & 1)
884         printf(" bold=\"true\"");
885       if (style & 2)
886         printf(" italic=\"true\"");
887
888       bool underline = data[pos++];
889       if (underline)
890         printf(" underline=\"true\"");
891
892       int halign = get_u32();
893       printf(" halign=%d", halign);
894
895       int valign = get_u32();
896       printf(" valign=%d", valign);
897
898       printf (" fgcolor=\"%s\"", get_string());
899       printf (" bgcolor=\"%s\"", get_string());
900
901       if (!match_byte(0))
902         match_byte_assert(1);
903
904       char *alt_fgcolor = get_string();
905       if (alt_fgcolor[0])
906         printf (" altfg=\"%s\"", alt_fgcolor);
907       char *alt_bgcolor = get_string();
908       if (alt_bgcolor[0])
909         printf (" altbg=\"%s\"", alt_bgcolor);
910
911       if (version > 1)
912         {
913           printf(" margins=\"");
914           for (int i = 0; i < 4; i++)
915             {
916               if (i)
917                 putchar(' ');
918               printf("%d", get_u32());
919             }
920           putchar('"');
921         }
922
923       printf ("/>\n");
924     }
925
926   int x1 = get_u32();
927   int x1_end = pos + x1;
928   printf("<borders>\n");
929   match_be32_assert(1);
930   int n_borders = get_be32();
931   for (int i = 0; i < n_borders; i++)
932     {
933       int type = get_be32();
934       int stroke = get_be32();
935       int color = get_be32();
936       printf("  <border type=\"%d\" stroke=\"%s\" color=\"#%06x\"/>\n",
937              type,
938              (stroke == 0 ? "none"
939               : stroke == 1 ? "solid"
940               : stroke == 2 ? "dashed"
941               : stroke == 3 ? "thick"
942               : stroke == 4 ? "thin"
943               : stroke == 5 ? "double"
944               : "<error>"),
945              color);
946     }
947   bool grid = get_byte();
948   pos += 3;
949   printf("  <grid show=\"%s\"/>\n", grid ? "yes" : "no");
950   printf("</borders>\n");
951   assert(pos == x1_end);
952
953   int skip = get_u32();
954   assert(skip == 18 || skip == 25);
955   pos += skip;
956
957   int x3 = get_u32();
958   int x3_end = pos + x3;
959   if (version == 3)
960     {
961       match_be32_assert(1);
962       get_be32();
963       printf("<settings layer=\"%d\"", get_be32());
964       if (!get_bool())
965         printf(" skipempty=\"false\"");
966       if (!get_bool())
967         printf(" showdimensionincorner=\"false\"");
968       if (!get_bool())
969         printf(" markers=\"numeric\"");
970       if (!get_bool())
971         printf(" footnoteposition=\"subscript\"");
972       get_byte();
973       int nbytes = get_be32();
974       int end = pos + nbytes;
975       printf("\n");
976       while (pos + 4 <= end)
977         printf(" %d", get_be32());
978       pos = end;
979       printf("\n");
980       pos += nbytes;
981       char *notes = get_string_be();
982       if (notes[0])
983         printf(" notes=\"%s\"", notes);
984       char *look = get_string_be();
985       if (look[0])
986         printf(" look=\"%s\"", look);
987       printf(">\n");
988     }
989   pos = x3_end;
990
991   /* Manual column widths, if present. */
992   int count = get_u32();
993   if (count > 0)
994     {
995       printf("<columnwidths>");
996       for (int i = 0; i < count; i++)
997         {
998           if (i)
999             putchar(' ');
1000           printf("%d", get_u32());
1001         }
1002       printf("</columnwidths>\n");
1003     }
1004
1005   const char *locale = get_string();
1006   printf ("<locale>%s</locale>\n", locale);
1007
1008   printf ("<layer>%d</layer>\n", get_u32());
1009   if (!match_byte(0))
1010     match_byte_assert(1);
1011   match_byte_assert(0);
1012   if (!match_byte(0))
1013     match_byte_assert(1);
1014   printf("<epoch>%d</epoch>\n", get_u32());
1015
1016   int decimal = data[pos];
1017   int grouping = data[pos + 1];
1018   if (match_byte('.'))
1019     {
1020       if (!match_byte(',') && !match_byte('\''))
1021         match_byte_assert(' ');
1022     }
1023   else
1024     {
1025       match_byte_assert(',');
1026       if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1027         match_byte_assert(0);
1028     }
1029   printf("<format decimal=\"%c\"", decimal);
1030   if (grouping)
1031     printf(" grouping=\"%c\"", grouping);
1032   printf("\"/>\n");
1033   if (match_u32(5))
1034     {
1035       for (int i = 0; i < 5; i++)
1036         printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1037     }
1038   else
1039     match_u32_assert(0);
1040
1041   /* The last chunk is an outer envelope that contains two inner envelopes.
1042      The second inner envelope has some interesting data like the encoding and
1043      the locale. */
1044   int outer_end = get_end();
1045   if (version == 3)
1046     {
1047       /* First inner envelope: byte*33 int[n] int*[n]. */
1048       int inner_len = get_u32();
1049       int inner_end = pos + inner_len;
1050       int array_start = pos + 33;
1051       match_byte_assert(0);
1052       pos++;                    /* 0, 1, 10 seen. */
1053       get_bool();
1054
1055       /* 0=en 1=de 2=es 3=it 5=ko 6=pl 8=zh-tw 10=pt_BR 11=fr */
1056       printf("lang=%d ", get_byte());
1057
1058       printf ("variable_mode=%d\n", get_byte());
1059       printf ("value_mode=%d\n", get_byte());
1060       if (!match_u64(0))
1061         match_u64_assert(UINT64_MAX);
1062       match_u32_assert(0);
1063       match_u32_assert(0);
1064       match_u32_assert(0);
1065       match_u32_assert(0);
1066       match_byte_assert(0);
1067       get_bool();
1068       match_byte_assert(1);
1069       pos = array_start;
1070
1071       assert(get_end() == inner_end);
1072       printf("<heights>");
1073       int n_heights = get_u32();
1074       for (int i = 0; i < n_heights; i++)
1075         {
1076           if (i)
1077             putchar(' ');
1078           printf("%d", get_u32());
1079         }
1080       printf("</heights>\n");
1081
1082       int n_style_map = get_u32();
1083       for (int i = 0; i < n_style_map; i++)
1084         {
1085           uint64_t cell = get_u64();
1086           int style = get_u16();
1087           printf("<style-map cell=\"%llu\" style=\"%d\"/>\n", cell, style);
1088         }
1089
1090       int n_styles = get_u32();
1091       for (int i = 0; i < n_styles; i++)
1092         {
1093           printf("<cell-style index=\"%d\"", i);
1094           dump_style(stdout);
1095           dump_style2(stdout);
1096           printf("/>\n");
1097         }
1098
1099       pos = get_end();
1100       assert(pos == inner_end);
1101
1102       /* Second inner envelope. */
1103       assert(get_end() == outer_end);
1104
1105       match_byte_assert(1);
1106       match_byte_assert(0);
1107       if (!match_byte(3) && !match_byte(4))
1108         match_byte_assert(5);
1109       match_byte_assert(0);
1110       match_byte_assert(0);
1111       match_byte_assert(0);
1112
1113       printf("<command>%s</command>\n", get_string());
1114       printf("<command-local>%s</command-local>\n", get_string());
1115       printf("<language>%s</language>\n", get_string());
1116       printf("<charset>%s</charset>\n", get_string());
1117       printf("<locale>%s</locale>\n", get_string());
1118
1119       get_bool();
1120       match_byte_assert(0);
1121       get_bool();
1122       get_bool();
1123
1124       printf("<epoch2>%d</epoch2>\n", get_u32());
1125
1126       if (match_byte('.'))
1127         {
1128           if (!match_byte(',') && !match_byte('\''))
1129             match_byte_assert(' ');
1130         }
1131       else
1132         {
1133           match_byte_assert(',');
1134           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1135             match_byte_assert(0);
1136         }
1137
1138       printf ("small: %g\n", get_double());
1139
1140       match_byte_assert(1);
1141       if (outer_end - pos > 6)
1142         {
1143           /* There might be a pair of strings representing a dataset and
1144              datafile name, or there might be a set of custom currency strings.
1145              The custom currency strings start with a pair of integers, so we
1146              can distinguish these from a string by checking for a null byte; a
1147              small 32-bit integer will always contain a null and a text string
1148              never will. */
1149           int save_pos = pos;
1150           int len = get_u32();
1151           bool has_dataset = !memchr(&data[pos], '\0', len);
1152           pos = save_pos;
1153
1154           if (has_dataset)
1155             {
1156               printf("<dataset>%s</dataset>\n", get_string());
1157               printf("<datafile>%s</datafile>\n", get_string());
1158
1159               match_u32_assert(0);
1160
1161               time_t date = get_u32();
1162               struct tm tm = *localtime(&date);
1163               char s[128];
1164               strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm);
1165               printf("<date>%s</date>\n", s);
1166
1167               match_u32_assert(0);
1168             }
1169         }
1170
1171       if (match_u32(5))
1172         {
1173           for (int i = 0; i < 5; i++)
1174             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1175         }
1176       else
1177         match_u32_assert(0);
1178
1179       match_byte_assert('.');
1180       get_bool();
1181
1182       if (pos < outer_end)
1183         {
1184           get_u32();
1185           match_u32_assert(0);
1186         }
1187       assert(pos == outer_end);
1188
1189       pos = outer_end;
1190     }
1191   else if (outer_end != pos)
1192     {
1193       pos += 14;
1194       printf("<command>%s</command>\n", get_string());
1195       printf("<command-local>%s</command-local>\n", get_string());
1196       printf("<language>%s</command>\n", get_string());
1197       printf("<charset>%s</charset>\n", get_string());
1198       printf("<locale>%s</locale>\n", get_string());
1199       get_bool();
1200       match_byte_assert(0);
1201       get_bool();
1202       get_bool();
1203
1204       printf("<epoch2>%d</epoch2>\n", get_u32());
1205       int decimal = data[pos];
1206       int grouping = data[pos + 1];
1207       if (match_byte('.'))
1208         {
1209           if (!match_byte(',') && !match_byte('\''))
1210             match_byte_assert(' ');
1211         }
1212       else
1213         {
1214           match_byte_assert(',');
1215           if (!match_byte('.') && !match_byte(' ') && !match_byte(','))
1216             match_byte_assert(0);
1217         }
1218       printf("<format decimal=\"%c\"", decimal);
1219       if (grouping)
1220         printf(" grouping=\"%c\"", grouping);
1221       printf("\"/>\n");
1222       if (match_u32(5))
1223         {
1224           for (int i = 0; i < 5; i++)
1225             printf("<CC%c>%s</CC%c>\n", 'A' + i, get_string(), 'A' + i);
1226         }
1227       else
1228         match_u32_assert(0);
1229
1230       match_byte_assert('.');
1231       get_bool();
1232
1233       assert(pos == outer_end);
1234       pos = outer_end;
1235     }
1236 }
1237
1238 int
1239 main(int argc, char *argv[])
1240 {
1241   if (argc != 2)
1242     {
1243       fprintf (stderr, "usage: %s FILE.bin", argv[0]);
1244       exit (1);
1245     }
1246
1247   filename = argv[1];
1248   int fd = open(filename, O_RDONLY);
1249   if (fd < 0)
1250     {
1251       fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
1252       exit (1);
1253     }
1254
1255   struct stat s;
1256   if (fstat(fd, &s))
1257     {
1258       perror("fstat");
1259       exit(1);
1260     }
1261   n = s.st_size;
1262   data = malloc(n);
1263   if (!data)
1264     {
1265       perror("malloc");
1266       exit(1);
1267     }
1268   if (read(fd, data, n) != n)
1269     {
1270       perror("read");
1271       exit(1);
1272     }
1273   close(fd);
1274
1275   pos = 0;
1276   match_byte_assert(1);
1277   match_byte_assert(0);
1278
1279   version = get_u32();
1280   assert(version == 1 || version == 3);
1281
1282   match_byte_assert(1);
1283   bool number_footnotes = get_bool();
1284   printf("<footnote markers=\"%s\"/>\n",
1285          number_footnotes ? "number" : "letter");
1286   bool rotate_inner_column_labels = get_bool();
1287   bool rotate_outer_row_labels = get_bool();
1288   printf("x=%d\n", get_bool());
1289   printf("<rotate-labels inner-column=\"%s\" outer-row=\"%s\"/>",
1290          rotate_inner_column_labels ? "yes" : "no",
1291          rotate_outer_row_labels ? "yes" : "no");
1292   //fprintf(stderr, "option-number=%d\n", get_u32());
1293   get_u32();
1294
1295   int min_col_width = get_u32();
1296   int max_col_width = get_u32();
1297   int min_row_width = get_u32();
1298   int max_row_width = get_u32();
1299   printf("<label-width min-col=\"%d\" max-col=\"%d\" min-row=\"%d\" "
1300          "max-row=\"%d\"/>\n",
1301          min_col_width, max_col_width,
1302          min_row_width, max_row_width);
1303
1304   /* Offset 31. */
1305   printf("<tableid>%lld</tableid>", get_u64());
1306
1307   dump_title ();
1308   dump_fonts();
1309   dump_dims ();
1310   dump_data ();
1311   match_byte (1);
1312   if (pos != n)
1313     {
1314       fprintf (stderr, "%x / %x\n", pos, n);
1315       exit(1);
1316     }
1317   exit(0);
1318
1319   return 0;
1320 }