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