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