frequencies-only also works
[pspp] / dump-spo2.c
1 #include <assert.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <float.h>
5 #include <inttypes.h>
6 #include <stdbool.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include "u8-mbtouc.h"
15
16 static const char *filename;
17 static uint8_t *data;
18 static size_t n;
19
20 int version;
21
22 unsigned int pos;
23
24 static int n_dims;
25
26 #define XSTR(x) #x
27 #define STR(x) XSTR(x)
28 #define WHERE __FILE__":" STR(__LINE__)
29
30 static void __attribute__((unused))
31 hex_dump(FILE *stream, int ofs, int n);
32
33 static uint8_t
34 get_byte(void)
35 {
36   return data[pos++];
37 }
38
39 static unsigned int
40 get_u32(void)
41 {
42   uint32_t x;
43   memcpy(&x, &data[pos], 4);
44   pos += 4;
45   return x;
46 }
47
48 static unsigned long long int
49 get_u64(void)
50 {
51   uint64_t x;
52   memcpy(&x, &data[pos], 8);
53   pos += 8;
54   return x;
55 }
56
57 static unsigned int
58 get_be32(void)
59 {
60   uint32_t x;
61   x = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
62   pos += 4;
63   return x;
64 }
65
66 static unsigned int
67 get_u16(void)
68 {
69   uint16_t x;
70   memcpy(&x, &data[pos], 2);
71   pos += 2;
72   return x;
73 }
74
75 static double
76 get_double(void)
77 {
78   double x;
79   memcpy(&x, &data[pos], 8);
80   pos += 8;
81   return x;
82 }
83
84 static double __attribute__((unused))
85 get_float(void)
86 {
87   float x;
88   memcpy(&x, &data[pos], 4);
89   pos += 4;
90   return x;
91 }
92
93 static bool
94 match_u32(uint32_t x)
95 {
96   if (get_u32() == x)
97     return true;
98   pos -= 4;
99   return false;
100 }
101
102 bool
103 match_u16(uint16_t x)
104 {
105   if (get_u16() == x)
106     return true;
107   pos -= 2;
108   return false;
109 }
110
111 static void
112 match_u32_assert(uint32_t x, const char *where)
113 {
114   unsigned int y = get_u32();
115   if (x != y)
116     {
117       fprintf(stderr, "%s: 0x%x: expected i%u, got i%u: ", where, pos - 4, x, y);
118       hex_dump(stderr, pos - 4, 64);
119       exit(1);
120     }
121 }
122 #define match_u32_assert(x) match_u32_assert(x, WHERE)
123
124 static void
125 match_u16_assert(uint16_t x, const char *where)
126 {
127   unsigned int y = get_u16();
128   if (x != y)
129     {
130       fprintf(stderr, "%s: 0x%x: expected u16:%u, got u16:%u: ", where, pos - 2, x, y);
131       hex_dump(stderr, pos - 2, 64);
132       exit(1);
133     }
134 }
135 #define match_u16_assert(x) match_u16_assert(x, WHERE)
136
137 static bool __attribute__((unused))
138 match_u64(uint64_t x)
139 {
140   if (get_u64() == x)
141     return true;
142   pos -= 8;
143   return false;
144 }
145
146 static void __attribute__((unused))
147 match_u64_assert(uint64_t x, const char *where)
148 {
149   unsigned long long int y = get_u64();
150   if (x != y)
151     {
152       fprintf(stderr, "%s: 0x%x: expected u64:%lu, got u64:%llu\n", where, pos - 8, x, y);
153       exit(1);
154     }
155 }
156 #define match_u64_assert(x) match_u64_assert(x, WHERE)
157
158 static bool __attribute__((unused))
159 match_be32(uint32_t x)
160 {
161   if (get_be32() == x)
162     return true;
163   pos -= 4;
164   return false;
165 }
166
167 static void
168 match_be32_assert(uint32_t x, const char *where)
169 {
170   unsigned int y = get_be32();
171   if (x != y)
172     {
173       fprintf(stderr, "%s: 0x%x: expected be%u, got be%u\n", where, pos - 4, x, y);
174       exit(1);
175     }
176 }
177 #define match_be32_assert(x) match_be32_assert(x, WHERE)
178
179 static bool
180 match_byte(uint8_t b)
181 {
182   if (pos < n && data[pos] == b)
183     {
184       pos++;
185       return true;
186     }
187   else
188     return false;
189 }
190
191 static void
192 match_byte_assert(uint8_t b, const char *where)
193 {
194   if (!match_byte(b))
195     {
196       fprintf(stderr, "%s: 0x%x: expected %02x, got %02x: ", where, pos, b, data[pos]);
197       hex_dump(stderr, pos, 64);
198       exit(1);
199     }
200 }
201 #define match_byte_assert(b) match_byte_assert(b, WHERE)
202
203 static bool
204 match_bytes(int start, const int *bytes, size_t n_bytes)
205 {
206   for (size_t i = 0; i < n_bytes; i++)
207     if (bytes[i] >= 0 && data[start + i] != bytes[i])
208       return false;
209   return true;
210 }
211
212 static char *
213 xmemdup0(const void *p, size_t n)
214 {
215   char *s = malloc(n + 1);
216   memcpy(s, p, n);
217   s[n] = 0;
218   return s;
219 }
220
221 static bool
222 get_bool(void)
223 {
224   if (match_byte(0))
225     return false;
226   match_byte_assert(1);
227   return true;
228 }
229
230 static bool __attribute__((unused))
231 is_ascii(uint8_t p)
232 {
233   return (p >= ' ' && p < 127) || p == '\r' || p == '\n' || p == '\t';
234 }
235
236 static int
237 count_zeros(const uint8_t *p)
238 {
239   size_t n = 0;
240   while (p[n] == 0)
241     n++;
242   return n;
243 }
244
245 static bool __attribute__((unused))
246 all_utf8(const char *p_, size_t len)
247 {
248   const uint8_t *p = (const uint8_t *) p_;
249   for (size_t ofs = 0, mblen; ofs < len; ofs += mblen)
250     {
251       ucs4_t uc;
252
253       mblen = u8_mbtouc (&uc, p + ofs, len - ofs);
254       if ((uc < 32 && uc != '\n') || uc == 127 || uc == 0xfffd)
255         return false;
256     }
257   return true;
258 }
259
260 static char *
261 pull_string(int len, const char *where)
262 {
263   assert (len >= 0);
264   for (int i = 0; i < len - 1; i++)
265     if (!data[pos + i])
266       {
267         fprintf(stderr, "%s: %d-byte string starting at 0x%x has null byte "
268                 "at offset %d: ", where, len, pos, i);
269         hex_dump(stderr, pos, len + 64);
270         exit(1);
271       }
272
273   char *s = xmemdup0(&data[pos], len);
274   pos += len;
275   return s;
276 }
277
278 static char *
279 get_string2(const char *where)
280 {
281   return pull_string(get_u16(), where);
282 }
283 #define get_string2() get_string2(WHERE)
284
285 static char *
286 get_string1(const char *where)
287 {
288   int len = data[pos++];
289   return len == 0xff ? (get_string2)(where) : pull_string(len, where);
290 }
291 #define get_string1() get_string1(WHERE)
292
293 static void
294 match_string1_assert(const char *exp, const char *where)
295 {
296   int start = pos;
297   char *act = (get_string1)(where);
298   if (strcmp(act, exp)) 
299     {
300       fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
301               where, start, exp, act);
302       exit(1);
303     }
304 }
305 #define match_string1_assert(x) match_string1_assert(x, WHERE)
306
307 static void
308 match_string2_assert(const char *exp, const char *where)
309 {
310   int start = pos;
311   char *act = (get_string2)(where);
312   if (strcmp(act, exp)) 
313     {
314       fprintf(stderr, "%s: 0x%x: expected \"%s\", got \"%s\"\n",
315               where, start, exp, act);
316       exit(1);
317     }
318 }
319 #define match_string2_assert(x) match_string2_assert(x, WHERE)
320
321 static char *
322 get_string4(const char *where)
323 {
324   assert(data[pos + 3] == 0);
325   return pull_string(get_u32(), where);
326 }
327 #define get_string4() get_string4(WHERE)
328
329 static char *
330 get_padded_string(int len)
331 {
332   char *s = xmemdup0(&data[pos], len);
333   pos += len;
334   return s;
335 }
336
337 static char *
338 get_string_be(const char *where)
339 {
340   if (1
341       /*data[pos + 1] == 0 && data[pos + 2] == 0 && data[pos + 3] == 0*/
342       /*&& all_ascii(&data[pos + 4], data[pos])*/)
343     {
344       int len = data[pos + 2] * 256 + data[pos + 3];
345       char *s = malloc(len + 1);
346
347       memcpy(s, &data[pos + 4], len);
348       s[len] = 0;
349       pos += 4 + len;
350       return s;
351     }
352   else
353     {
354       fprintf(stderr, "%s: 0x%x: expected string\n", where, pos);
355       exit(1);
356     }
357 }
358 #define get_string_be() get_string_be(WHERE)
359
360 static int
361 get_end(void)
362 {
363   int len = get_u32();
364   return pos + len;
365 }
366
367 static void __attribute__((unused))
368 hex_dump(FILE *stream, int ofs, int n)
369 {
370   int n_ascii = 0;
371   for (int i = 0; i < n; i++)
372     {
373       int c = data[ofs + i];
374       n_ascii += is_ascii(c);
375       fprintf(stream, " %02x", c);
376     }
377   if (n_ascii >= 3)
378     {
379       putc(' ', stream);
380       for (int i = 0; i < n; i++)
381         {
382           int c = data[ofs + i];
383           putc(c >= 32 && c < 127 ? c : '.', stream);
384         }
385     }
386   putc('\n', stream);
387 }
388
389 static void __attribute__((unused))
390 char_dump(FILE *stream, int ofs, int n)
391 {
392   for (int i = 0; i < n; i++)
393     {
394       int c = data[ofs + i];
395       putc(c >= 32 && c < 127 ? c : '.', stream);
396     }
397   putc('\n', stream);
398 }
399
400
401 static int
402 compare_int(const void *a_, const void *b_)
403 {
404   const int *a = a_;
405   const int *b = b_;
406   return *a < *b ? -1 : *a > *b;
407 }
408
409
410 static const char *
411 format_name (int format, char *buf)
412 {
413   switch (format)
414     {
415     case 1: return "A";
416     case 2: return "AHEX";
417     case 3: return "COMMA";
418     case 4: return "DOLLAR";
419     case 5: case 40: return "F";
420     case 6: return "IB";
421     case 7: return "PIBHEX";
422     case 8: return "P";
423     case 9: return "PIB";
424     case 10: return "PK";
425     case 11: return "RB";
426     case 12: return "RBHEX";
427     case 15: return "Z";
428     case 16: return "N";
429     case 17: return "E";
430     case 20: return "DATE";
431     case 21: return "TIME";
432     case 22: return "DATETIME";
433     case 23: return "ADATE";
434     case 24: return "JDATE";
435     case 25: return "DTIME";
436     case 26: return "WKDAY";
437     case 27: return "MONTH";
438     case 28: return "MOYR";
439     case 29: return "QYR";
440     case 30: return "WKYR";
441     case 31: return "PCT";
442     case 32: return "DOT";
443     case 33: return "CCA";
444     case 34: return "CCB";
445     case 35: return "CCC";
446     case 36: return "CCD";
447     case 37: return "CCE";
448     case 38: return "EDATE";
449     case 39: return "SDATE";
450     default: sprintf(buf, "(%d)", format); return buf;
451     }
452 }
453
454 static void
455 parse_format(void)
456 {
457   int d = data[pos++];
458   int w = data[pos++];
459   int fmt = data[pos++];
460   char buf[32];
461   printf ("%s%d.%d", format_name(fmt, buf), w, d);
462 }
463
464 static void
465 parse_heading(const char *name)
466 {
467   match_u16_assert(0xffff);
468   match_u16_assert(0);
469   match_string2_assert(name);
470   printf("%#x: %s\n", pos, name);
471 }
472
473 static void
474 match_zeros_assert(int count, const char *where)
475 {
476   for (int i = 0; i < count; i++)
477     if (data[pos + i])
478       {
479         fprintf (stderr,
480                  "%s: %#x: expected %d zeros here but offset %d is %#"PRIx8": ",
481                  where, pos, count, i, data[pos + i]);
482         hex_dump (stderr, pos, 64);
483         exit (1);
484       }
485   pos += count;
486 }
487 #define match_zeros_assert(count) match_zeros_assert(count, WHERE)
488
489 static void
490 put_safe(const char *s)
491 {
492   while (*s)
493     {
494       if (*s == '\n')
495         printf ("\\n");
496       else if (*s == '\r')
497         printf ("\\r");
498       else if (*s < 0x20 || *s > 0x7e)
499         printf ("\\x%02"PRIx8, (uint8_t) *s);
500       else
501         putchar (*s);
502       s++;
503     }
504 }
505
506 static void parse_flexible(void);
507
508 static int count;
509 static void
510 parse_DspString(void)
511 {
512   printf("%#x: DspString#%d(", pos, count++);
513   if (match_byte(2))
514     {
515       printf("%f, \"", get_double());
516       printf("%s\")\n", get_string1());
517     }
518   else
519     {
520       match_byte_assert(1);
521       parse_format();
522       printf(" \"");
523       match_byte_assert(0);
524       match_byte_assert(1);
525       put_safe(get_string1());
526       printf("\")\n");
527     }
528   
529 }
530
531 static void
532 match_DspString(void)
533 {                               /* 05 80 */
534   match_byte_assert(5);
535   match_byte_assert(0x80);
536   parse_DspString();
537 }
538
539 static void
540 parse_DspSimpleText(void)
541 {
542   match_byte_assert(0);
543   if (match_byte(0))
544     {
545       match_zeros_assert(3);
546       if (!match_byte(0x10))
547         match_byte_assert(0);
548       match_zeros_assert(4);
549     }
550   /* Followed by DspString or DspNumber. */
551 }
552
553 static void
554 match_DspSimpleText(void)
555 {                               /* 03 80 */
556   match_byte_assert(3);
557   match_byte_assert(0x80);
558   parse_DspSimpleText();
559 }
560
561 static void
562 parse_weirdness(void)
563 {
564   match_byte_assert(1);
565   get_u32();
566   match_zeros_assert(12);
567   pos++;                        /* 90 or BC */
568   if (!match_byte(2))
569     match_byte_assert(1);
570   match_zeros_assert(5);
571   pos++;
572   match_zeros_assert(3);
573   puts(get_padded_string(32));
574 }
575
576 static void
577 parse_NavTreeViewItem(void)
578 {                               /* 07 80 */
579   int start_pos = pos;
580   match_zeros_assert(1);
581   if (!match_byte(0) && !match_byte(7) && !match_byte(2) && !match_byte(0xc))
582     match_byte_assert(8);
583   match_zeros_assert(3);
584   pos++;
585   match_byte_assert(0);
586   match_byte_assert(1);
587   match_byte_assert(0);
588   if (match_byte(0))
589     {
590       match_byte_assert(0);
591       if (!match_byte(1))
592         match_byte_assert(0);
593       match_zeros_assert(5);
594       if (!match_byte(0))
595         match_byte_assert(1);
596       match_zeros_assert(5);
597       get_string1();
598       if (match_byte(1))
599         {
600           if (data[pos] == 1)
601             {
602               parse_weirdness();
603               match_byte_assert(0);
604               pos++;
605               match_zeros_assert(11);
606               match_byte_assert(1);
607               match_zeros_assert(3);
608               get_string4();
609               match_byte_assert(0);
610               if (match_byte(0))
611                 {
612                   match_zeros_assert(2);
613                   if (match_u32(8500))
614                     match_u32_assert(11000);
615                   else
616                     {
617                       match_u32_assert(11000);
618                       match_u32_assert(8500);
619                     }
620                   pos += 32;
621                   get_string1();
622                   if (!match_byte(0))
623                     match_byte_assert(1);
624                   pos++;
625                   pos++;
626                   pos++;
627                   pos++;
628                   get_string4();                /* page title */
629                   match_byte_assert(1);
630                   match_byte_assert(1);
631                   match_zeros_assert(3);
632                   get_string4();                /* page number */
633                   match_byte_assert(0);
634                   pos += 2;
635                   match_u16_assert(2);
636                 }
637               parse_flexible();
638             }
639         }
640       else
641         match_zeros_assert(3);
642     }
643   //fprintf(stderr, "%#x ", pos - 16);
644   hex_dump(stdout, start_pos, pos - start_pos);
645 }
646
647 static void
648 match_NavTreeViewItem(void)
649 {
650   match_byte_assert(7);
651   match_byte_assert(0x80);
652   parse_NavTreeViewItem();
653 }
654
655 static void
656 parse_DspNumber(void)
657 {
658   printf("%#x: DspNumber#%d(", pos, count++);
659   match_byte_assert(1);
660   parse_format();
661   match_byte_assert(0x80);
662   match_byte(2);
663   printf (" %f", get_double());
664   printf (" \"%s\")\n", get_string1());
665 }
666
667 static void
668 match_DspNumber(void)
669 {
670   if (!match_byte(0x18) && !match_byte(0x19))
671     match_byte_assert(0x2a);
672   match_byte_assert(0x80);
673   parse_DspNumber();
674 }
675
676 static void
677 parse_DspCell(void)
678 {
679   match_byte_assert(0);
680 }
681
682 static void
683 match_DspCell(void)
684 {                               /* 27 80 */
685   match_byte_assert(0x27);
686   match_byte_assert(0x80);
687   parse_DspCell();
688 }
689
690 static void
691 parse_NavLog(void)
692 {
693   match_byte_assert(2);
694   pos += 32;
695 }
696
697 static void
698 match_NavLog(void)
699 {                               /* 09 80 */
700   match_byte_assert(9);
701   match_byte_assert(0x80);
702   parse_NavLog();
703 }
704
705 static void
706 parse_category(int level, int j, int *n_leaves)
707 {
708   for (size_t k = 0; k < level; k++)
709     putchar('\t');
710   get_u16(); match_byte_assert(0);
711   get_u16(); match_byte_assert(0);
712   int leaf_idx = get_u32();
713   printf("%d ", leaf_idx);
714   match_u32_assert(0);
715   if (get_u16() == 0xffff)
716     match_u16_assert(0xffff);
717   else
718     match_u16_assert(0x0e74);
719   match_byte_assert(0);
720   match_DspSimpleText();
721   match_DspString();
722
723   int n_subcategories = get_u32();
724   if (n_subcategories)
725     assert (leaf_idx == 0);
726   else
727     {
728       assert (leaf_idx == *n_leaves);
729       ++*n_leaves;
730     }
731   for (int k = 0; k < n_subcategories; k++)
732     parse_category(level + 1, k, n_leaves);
733 }
734
735 static void
736 parse_dimension(int i)
737 {
738   printf ("%#x: dimension %d\n", pos, i);
739   if (i == 0)
740     {
741       match_zeros_assert(5);
742       match_u32_assert(1);
743     }
744   else
745     {
746       match_zeros_assert(6);
747
748       int n_units16 = get_u32();
749       match_u16_assert(1);
750       for (int j = 0; j < n_units16; j++)
751         get_u16();
752
753       match_byte_assert(0);
754
755       int n_units32 = get_u32();
756       match_u16_assert(0);
757       for (int j = 0; j < n_units32; j++)
758         get_u32();
759
760       get_u16(); match_byte_assert(0);
761
762       get_u16(); match_byte_assert(0);
763       get_u16(); match_byte_assert(0);
764       match_u32_assert(0);
765       match_u32_assert(1);
766     }
767
768   get_u16();
769   if (!match_u16(0xffff))
770     match_u16_assert(0x0e74);
771   match_byte_assert(0);
772   match_DspSimpleText();
773   match_DspString();
774
775   int n_leaves = 0;
776   int n_categories = get_u32();
777   for (int j = 0; j < n_categories; j++)
778     parse_category(1, j, &n_leaves);
779 }
780
781 static void
782 parse_PMModelItemInfo(void)
783 {
784   for (int i = 0; i < n_dims; i++)
785     parse_dimension(i);
786   printf("%#x: end of model\n", pos);
787   exit(0);
788 }
789
790 static void
791 match_PMModelItemInfo(void)
792 {                               /* 54 80 */
793   match_byte_assert(0x54);
794   match_byte_assert(0x80);
795   parse_PMModelItemInfo();
796   /* DspSimpleText */
797   /* DspString */
798 }
799
800 static void
801 match_PMPivotItemTree(void)
802 {                               /* 52 80 */
803   match_byte_assert(0x52);
804   match_byte_assert(0x80);
805   match_byte_assert(0);
806   match_PMModelItemInfo();
807 }
808
809 static void
810 parse_NavHead(void)
811 {
812   match_byte_assert(2);
813   match_zeros_assert(24);
814   match_byte_assert(1);
815   match_zeros_assert(3);
816   if (!match_byte(1))
817     match_byte_assert(0);
818   match_zeros_assert(3);
819   /* DspSimpleText */
820 }
821
822 static void
823 parse_NavOleItem(void)
824 {
825   match_byte_assert(0);
826   match_byte_assert(1);
827   match_zeros_assert(2);
828   pos++;
829   match_zeros_assert(9);
830   match_byte_assert(1);
831   match_zeros_assert(10);
832   match_byte_assert(1);
833   match_zeros_assert(5);
834   get_string1();
835   match_byte_assert(1);
836   parse_weirdness();
837   match_byte_assert(0);
838   pos++;
839   match_zeros_assert(11);
840   match_byte_assert(1);
841   match_zeros_assert(3);
842   get_string4();
843   match_byte_assert(0);
844 }
845
846 static void
847 match_NavOleItem(void)
848 {                               /* 0e 80 or 12 80*/
849   if (!match_byte(0x12))
850     match_byte_assert(0x0e);
851   match_byte_assert(0x80);
852   parse_NavOleItem();
853 }
854
855 static void
856 parse_NavTitle(void)
857 {
858   match_byte_assert(2);
859   match_zeros_assert(8);
860   match_u32_assert(24);
861   get_u32();
862   pos++;
863   if (!match_byte(3))
864     match_byte_assert(4);
865   match_zeros_assert(2);
866   get_u32();
867   match_u32_assert(2);
868   if (!match_u32(2))
869     match_u32_assert(1);
870 }
871
872 static void
873 parse_NavNote(void)
874 {
875   match_byte_assert(2);
876   match_zeros_assert(8);
877   match_u32_assert(24);
878   if (!match_u32(0) && !match_u32(0xffffff4b))
879     match_u32_assert(-40);
880   pos += 8;
881   match_u32_assert(2);
882   if (!match_u32(2))
883     match_u32_assert(1);
884 }
885
886 static void
887 parse_PTPivotController(void)
888 {
889   match_byte_assert(2);
890   pos += 8;
891   match_u32_assert(100);
892   match_u32_assert(100);
893   match_u32_assert(100);
894   match_u32_assert(100);
895 }
896
897 static void
898 parse_PVPivotView(void)
899 {
900   match_byte_assert(5);
901   printf ("PVPivotView(%d)\n", get_u32());
902 }
903
904 static void
905 parse_NDimensional__DspCell(void)
906 {
907   match_byte_assert(0);
908   n_dims = get_u32();
909   printf ("NDimensional__DspCell(n_dims=%d)\n", n_dims);
910 }
911
912 static void
913 parse_IndexedCollection(void)
914 {
915   printf("IndexedCollection");
916   for (size_t i = 0; ; i++)
917     {
918       match_byte_assert(0);
919       printf("%c%d", i ? 'x' : '(', get_u32());
920       match_u16_assert(1);
921       if (!match_u16(0x8011))
922         break;
923     }
924   printf(")\n");
925 }
926
927 static void
928 parse_PTTableLook(void)
929 {
930   match_byte_assert(2);
931   match_byte_assert(2);
932   match_zeros_assert(7);
933   match_u32_assert(0x36);
934   match_u32_assert(0x12);
935 }
936
937 static void
938 parse_PVViewDimension(void)
939 {
940   while (data[pos + 1] != 0x80
941          && (data[pos] != 0xff || data[pos + 1] != 0xff))
942     {
943       assert(pos < n);
944       pos++;
945     }
946 }
947
948 static void
949 parse_PVSeparatorStyle(void)
950 {
951   match_byte_assert(0);
952   match_byte_assert(1);
953   match_zeros_assert(15);
954   pos++;
955   match_byte_assert(0x80);
956   match_byte_assert(0);
957
958   match_byte_assert(1);
959   match_zeros_assert(9);
960   while (data[pos + 1] != 0x80
961          && (data[pos] != 0xff || data[pos + 1] != 0xff))
962     {
963       assert(pos < n);
964       pos++;
965     }
966 }
967
968 static void
969 parse_PVCellStyle(void)
970 {
971   match_byte_assert(0);
972   match_byte_assert(1);
973   match_zeros_assert(5);
974   match_u32_assert(0xffffff);
975   match_zeros_assert(2);
976 }
977
978 static void
979 skip_item(const char *name)
980 {
981   int start_pos = pos;
982   printf("%#x: skipping %s bytes...", pos, name);
983   while (data[pos + 1] != 0x80
984          && !(data[pos] == 0xff && data[pos + 1] == 0xff
985               && data[pos + 2] == 0 && data[pos + 3] == 0))
986     {
987       assert(pos < n);
988       pos++;
989     }
990   printf("until %#x:", pos);
991   hex_dump(stdout, start_pos, pos - start_pos);
992 }
993
994 static void
995 parse_flexible(void)
996 {
997   int start = pos;
998   if (match_u16(0xffff))
999     {
1000       match_u16_assert(0);
1001       char *heading = get_string2();
1002       printf("%#x: %s\n", pos, heading);
1003       if (!strcmp(heading, "NavRoot"))
1004         {
1005           match_byte_assert(2);
1006           match_zeros_assert(32);
1007         }
1008       else if (!strcmp(heading, "NavPivot"))
1009         {
1010           hex_dump(stdout, pos, 021);
1011           pos += 0x21;
1012         }
1013       else if (!strcmp(heading, "DspCell"))
1014         parse_DspCell();
1015       else if (!strcmp(heading, "DspSimpleText"))
1016         parse_DspSimpleText();
1017       else if (!strcmp(heading, "DspNumber"))
1018         parse_DspNumber();
1019       else if (!strcmp(heading, "DspString"))
1020         parse_DspString();
1021       else if (!strcmp(heading, "NavHead"))
1022         parse_NavHead();
1023       else if (!strcmp(heading, "NavTreeViewItem"))
1024         {
1025           if (0)
1026             parse_NavTreeViewItem();
1027           else
1028             skip_item(heading);
1029         }
1030       else if (!strcmp(heading, "IndexedCollection"))
1031         parse_IndexedCollection();
1032       else if (!strcmp(heading, "NavOleItem"))
1033         parse_NavOleItem();
1034       else if (!strcmp(heading, "NavTitle"))
1035         parse_NavTitle();
1036       else if (!strcmp(heading, "NavNote"))
1037         parse_NavNote();
1038       else if (!strcmp(heading, "PTPivotController"))
1039         parse_PTPivotController();
1040       else if (!strcmp(heading, "PVPivotView"))
1041         parse_PVPivotView();
1042       else if (!strcmp(heading, "PMPivotModel"))
1043         match_byte_assert(3);
1044       else if (!strcmp(heading, "NDimensional__DspCell"))
1045         parse_NDimensional__DspCell();
1046       else if (!strcmp(heading, "PMPivotItemTree"))
1047         match_byte_assert(0);
1048       else if (!strcmp(heading, "PMModelItemInfo"))
1049         parse_PMModelItemInfo();
1050       else if (!strcmp(heading, "AbstractTreeBranch"))
1051         match_byte_assert(0);
1052       else if (!strcmp(heading, "PTTableLook"))
1053         parse_PTTableLook();
1054       else if (!strcmp(heading, "PVViewDimension"))
1055         parse_PVViewDimension();
1056       else if (!strcmp(heading, "PVSeparatorStyle"))
1057         parse_PVSeparatorStyle();
1058       else if (!strcmp(heading, "PVCellStyle"))
1059         parse_PVCellStyle();
1060       else if (!strcmp(heading, "PVTextStyle"))
1061         exit(0);
1062       else
1063         {
1064           fprintf(stderr, "don't know %s at offset 0x%x: ", heading, start);
1065           hex_dump(stderr, pos, 128);
1066           assert(0);
1067         }
1068     }
1069   else if (data[pos + 1] == 0x80)
1070     {
1071       if ((data[pos] == 0x2a || data[pos] == 0x18 || data[pos] == 0x19) && data[pos + 1] == 0x80)
1072         match_DspNumber();
1073       else if (data[pos] == 0x27 && data[pos + 1] == 0x80)
1074         match_DspCell();
1075       else if (data[pos] == 0x5 && data[pos + 1] == 0x80)
1076         match_DspString();
1077       else if (data[pos] == 0x7 && data[pos + 1] == 0x80)
1078         match_NavTreeViewItem();
1079       else if (data[pos] == 0x3 && data[pos + 1] == 0x80)
1080         match_DspSimpleText();
1081       else if ((data[pos] == 0x3c || data[pos] == 0x39)
1082                && data[pos + 1] == 0x80)
1083         {
1084           /* 3c 80 */
1085           /* 39 80 */
1086           printf("%#x: %02x %02x ", pos, data[pos], data[pos + 1]);
1087           pos += 2;
1088           parse_format();
1089           printf ("\n");
1090 /*      match_byte_assert(0x01);
1091         match_byte_assert(0x02);
1092         match_byte_assert(0x0d); */
1093         }
1094       else if ((data[pos] == 0x15 || data[pos] == 0x14)
1095                && data[pos + 1] == 0x80)
1096         {
1097           /* 14 80 */
1098           /* 15 80 */
1099           pos += 2;
1100           if (match_byte(2))
1101             {
1102               printf ("%02x 80(%f", data[pos - 2], get_double());
1103               printf (" \"%s\")\n", get_string1());
1104               if (match_byte(1))
1105                 {
1106                   match_byte_assert(0);
1107                   get_string1();
1108                   if (!match_byte(2) && !match_byte(3))
1109                     match_byte_assert(0);
1110                   match_zeros_assert(3);
1111                   get_string1();
1112                   match_byte_assert(0);
1113                   match_byte_assert(1);
1114                   match_zeros_assert(3);
1115                   match_byte_assert(1);
1116                   match_byte_assert(0);
1117                 }
1118             }
1119           else
1120             {
1121               match_byte_assert(0);
1122             }
1123         }
1124       else if (data[pos] == 0x17)
1125         {
1126           printf("%02x %02x(%02x %02x %02x)\n",
1127                  data[pos], data[pos + 1],
1128                  data[pos + 2], data[pos + 3], data[pos + 4]);
1129           pos += 5;
1130         }
1131       else if (data[pos] == 0x9 && data[pos + 1] == 0x80)
1132         {
1133           match_NavLog();
1134         }
1135       else if (data[pos] == 0xe || data[pos] == 0x12)
1136         match_NavOleItem();
1137       else if (data[pos] == 0x11 || data[pos] == 0x13)
1138         {
1139           int type = data[pos];
1140           pos += 2;
1141           match_byte_assert(0);
1142           if (data[pos] != 0)
1143             {
1144               int x = get_u32();
1145               int y = get_u16();
1146               if (y == 0)
1147                 {
1148                   int index = get_u32();
1149                   printf("%02x 80(footnote %d)\n", type, index);
1150                 }
1151               else
1152                 printf("%02x 80(%d %d)\n", type, x, y);
1153             }
1154           else
1155             match_zeros_assert(13);
1156         }
1157       else if (data[pos] == 0x29 ||
1158                data[pos] == 0x2b ||
1159                data[pos] == 0x2d ||
1160                data[pos] == 0x31 ||
1161                data[pos] == 0x32 ||
1162                data[pos] == 0x4a ||
1163                data[pos] == 0x4c ||
1164                data[pos] == 0x4f ||
1165                data[pos] == 0x4d ||
1166                data[pos] == 0x50 ||
1167                data[pos] == 0x36 ||
1168                data[pos] == 0x52 ||
1169                data[pos] == 0x53 ||
1170                data[pos] == 0x54 ||
1171                data[pos] == 0x55 ||
1172                data[pos] == 0x57 ||
1173                data[pos] == 0x56 ||
1174                data[pos] == 0x58 ||
1175                data[pos] == 0x5c ||
1176                data[pos] == 0x5b ||
1177                data[pos] == 0x5e ||
1178                data[pos] == 0x62 ||
1179                data[pos] == 0x64 ||
1180                data[pos] == 0x4e ||
1181                data[pos] == 0x51 ||
1182                data[pos] == 0x59 ||
1183                data[pos] == 0x5a ||
1184                data[pos] == 0x5d ||
1185                data[pos] == 0x66 ||
1186                data[pos] == 0x60 ||
1187                data[pos] == 0x68 ||
1188                data[pos] == 0x48 ||
1189                data[pos] == 0x6a ||
1190                data[pos] == 0x37)
1191         {
1192           pos += 2;
1193           match_byte_assert(0);
1194         }
1195       else if (data[pos] == 0x2c ||
1196                data[pos] == 0x2e ||
1197                data[pos] == 0x30 ||
1198                data[pos] == 0x34 ||
1199                data[pos] == 0x3d ||
1200                data[pos] == 0x40 ||
1201                data[pos] == 0x3f ||
1202                data[pos] == 0x42 ||
1203                data[pos] == 0x43 ||
1204                data[pos] == 0x44 ||
1205                data[pos] == 0x49 ||
1206                data[pos] == 0x3e ||
1207                data[pos] == 0x46)
1208         {
1209           printf ("%#x: %02x %02x(%02x %02x %02x)\n",
1210                   pos, data[pos], data[pos + 1],
1211                   data[pos + 2], data[pos + 3], data[pos + 4]);
1212           pos += 2;
1213           pos += 3;
1214         }
1215       else
1216         {
1217           fprintf (stderr, "%#x: unknown record", pos);
1218           hex_dump (stderr, pos, 64);
1219           exit(1);
1220         }
1221     }
1222   else if (match_byte(0xa)) 
1223     {
1224       if (!match_byte(7))
1225         match_byte_assert(0);
1226       if (match_u16(0x0e74))
1227         match_byte_assert(0);
1228       else
1229         {
1230           match_zeros_assert(4);
1231           if (pos == n)
1232             exit (0);
1233           match_zeros_assert (2);
1234         }
1235     }
1236 #if 0
1237   else if (match_byte(1))
1238     {
1239       match_byte_assert(0);
1240       get_string1();
1241       if (!match_byte(2))
1242         match_byte_assert(0);
1243       if (match_byte(0))
1244         {
1245           match_zeros_assert(2);
1246           get_string1();
1247           if (match_byte(0x08))
1248             {
1249               match_byte_assert(0);
1250               match_u16_assert(0x0e74);
1251               match_byte_assert(0);
1252             }
1253           else if (match_byte(3))
1254             {
1255               match_byte_assert(0);
1256               if (match_u16(0x0e74))
1257                 match_byte_assert(0);
1258               else
1259                 {
1260                   match_zeros_assert(6);
1261                   if (!match_byte(0xe) && !match_byte(0x11))
1262                     match_byte_assert(0);
1263                   match_byte_assert(0);
1264                   if (!match_u16(0x0e74))
1265                     match_u16_assert(0);
1266                   match_byte_assert(0);
1267                 }
1268             }
1269           else
1270             {
1271               match_byte_assert(0);
1272               match_byte_assert(1);
1273               match_zeros_assert(3);
1274               match_byte_assert(1);
1275               match_byte_assert(0);
1276             }
1277         }
1278     }
1279 #endif
1280   else if (match_u16(1))
1281     {
1282       int start_pos = pos;
1283       char *title = get_string1();
1284       printf("%#x: title(\"%s\", ", start_pos, title);
1285       if (!match_u32(2))
1286         match_u32_assert(0);
1287       char *id = get_string1();
1288       printf("\"%s\")\n", id);
1289       match_byte_assert(0);
1290       if (!match_u32(2))
1291         match_u32_assert(3);
1292       match_u16_assert(1);
1293     }
1294   else //if (match_u16(2) || match_u16(3) || match_u16(4) || match_u16(5) || match_u16(6) || match_u16(7) || match_u16(8) || match_u16(9))
1295     skip_item("unknown");
1296 #if 0
1297   else if (match_byte(7) || match_byte(4) || match_byte(5) || match_byte(6) || match_byte(8) || match_byte(9) || match_byte(0xb) || match_byte(0xc) || match_byte(0x15) || match_byte(0x16) || match_byte(0x17) || match_byte(0x18) || match_byte(0x1e)  || match_byte(0x1a))
1298     {
1299       if (!match_byte(7))
1300         match_byte_assert(0);
1301       if (!match_u16(0x0e74))
1302         match_byte_assert(0);
1303       match_byte_assert(0);
1304     }
1305   else if (match_byte(2) || match_byte(3))
1306     {
1307       match_byte_assert(0);
1308       if (!match_u16(0x0e74))
1309         {
1310           match_zeros_assert(2);
1311           if (match_byte(0))
1312             {
1313               match_zeros_assert(3);
1314               if (match_byte(0))
1315                 match_zeros_assert(4);
1316               else
1317                 {
1318                   pos++;
1319                   match_byte(0);
1320                   match_u16_assert(0x0e74);
1321                 }
1322             }
1323         }
1324       //match_byte_assert(0);
1325     }
1326   else if (match_byte(0xd) || match_byte(0xe) || match_byte(0xf)
1327            || match_byte(0x11) || match_byte(0x12) || match_byte(0x13)
1328            || match_byte(0x14) || match_byte(0x1b))
1329     {
1330       if (!match_byte(0x07))
1331         match_byte_assert(0);
1332       if (!match_u16(0x0e74))
1333         match_zeros_assert(11);
1334       else
1335         match_byte_assert(0);
1336     }
1337   else if (match_byte(0xe3) || match_byte(0xdb) || match_byte(0xd8) || match_byte(0xe9) || match_byte(0xf3))
1338     {
1339       match_byte_assert(0x0e);
1340       match_byte_assert(0x74);
1341       match_byte_assert(0x0e);
1342       match_byte_assert(0);
1343     }
1344   else if (match_byte(0x9d) || match_byte(0x9e) || match_byte(0x9c))
1345     match_u32_assert(0x000e741a);
1346   else if (match_byte(0x10))
1347     {
1348       match_byte_assert(0);
1349       if (match_byte(0))
1350         match_zeros_assert(10);
1351       else
1352         {
1353           match_u16_assert(0x0e74);
1354           match_byte_assert(0);
1355         }
1356     }
1357   else if (match_byte(0x39) || match_byte(0x3a) || match_byte(0x3b))
1358     match_u32_assert(0x000e7409);
1359   else
1360     {
1361       //fprintf (stderr, "bad record start at offset %x: ", pos);
1362       hex_dump (stderr, pos, 64);
1363       assert(0);
1364     }
1365 #endif
1366 }
1367
1368
1369
1370 int
1371 main(int argc, char *argv[])
1372 {
1373   bool print_offsets = false;
1374   for (;;)
1375     {
1376       int c = getopt (argc, argv, "o");
1377       if (c == -1)
1378         break;
1379
1380       switch (c)
1381         {
1382         case 'o':
1383           print_offsets = true;
1384           break;
1385
1386         case '?':
1387           exit (-1);
1388         }
1389     }
1390   if (argc - optind != 1)
1391     {
1392       fprintf (stderr, "usage: %s FILE.bin", argv[0]);
1393       exit (1);
1394     }
1395
1396   const char *filename = argv[optind];
1397   int fd = open(filename, O_RDONLY);
1398   if (fd < 0)
1399     {
1400       fprintf (stderr, "%s: open failed (%s)", filename, strerror (errno));
1401       exit (1);
1402     }
1403
1404   struct stat s;
1405   if (fstat(fd, &s))
1406     {
1407       perror("fstat");
1408       exit(1);
1409     }
1410   n = s.st_size;
1411   data = malloc(n + 256);
1412   if (!data)
1413     {
1414       perror("malloc");
1415       exit(1);
1416     }
1417   if (read(fd, data, n) != n)
1418     {
1419       perror("read");
1420       exit(1);
1421     }
1422   for (int i = 0; i < 256; i++)
1423     data[n + i] = i % 2 ? 0xaa : 0x55;
1424   close(fd);
1425
1426   setvbuf (stdout, NULL, _IONBF, 0);
1427
1428   match_byte_assert(4);
1429   match_u32_assert(0);
1430   match_string1_assert("SPSS Output Document");
1431   match_u32_assert(1);
1432   match_byte_assert(0x63);
1433
1434   for (;;)
1435     {
1436       if (data[pos] == 0)
1437         {
1438           //printf("zero\n");
1439           pos++;
1440         }
1441       else
1442         parse_flexible();
1443     }
1444   exit(0);
1445
1446   parse_heading("NavRoot");
1447   match_byte_assert(2);
1448   match_zeros_assert(32);
1449
1450   parse_heading("DspSimpleText");
1451   match_zeros_assert(10);
1452
1453   parse_heading("DspString");
1454   parse_DspString();
1455
1456   parse_heading("NavTreeViewItem");
1457   match_byte_assert(0);
1458   if (!match_u32(1))
1459     match_u32_assert(0);
1460   match_byte_assert(2);
1461   match_byte_assert(0);
1462   match_byte_assert(1);
1463   match_zeros_assert(9);
1464   match_u32_assert(1);
1465
1466   match_u32_assert(0);
1467   match_u32_assert(0x18);
1468   if (!match_u32(0))
1469     match_u32_assert(0xffffffd8);
1470   match_u32_assert(0xffffffde);
1471   match_u32_assert(0x18);
1472   if (!match_u32(0))
1473     match_u32_assert(0xffffffd8);
1474   match_u32_assert(0x28);
1475   match_u32_assert(0x28);
1476   pos += 8;
1477   if (data[pos] == 0)
1478     {
1479       match_zeros_assert(5);
1480
1481       if (match_u32(8500))
1482         match_u32_assert(11000);
1483       else
1484         {
1485           match_u32_assert(11000);
1486           match_u32_assert(8500);
1487         }
1488       pos += 32;
1489       get_string1();
1490       if (!match_byte(0))
1491         match_byte_assert(1);
1492       pos++;
1493       pos++;
1494       pos++;
1495       pos++;
1496       get_string4();                /* page title */
1497       match_byte_assert(1);
1498       match_byte_assert(1);
1499       match_zeros_assert(3);
1500       get_string4();                /* page number */
1501       match_byte_assert(0);
1502       pos += 2;
1503       match_u16_assert(2);
1504     }
1505
1506   if (data[pos + 9] != 'L')
1507     exit(0);
1508   parse_heading("NavLog");
1509   parse_NavLog();
1510   for (;;)
1511     {
1512       if (data[pos] == 0)
1513         {
1514           //printf("zero\n");
1515           pos++;
1516         }
1517       else
1518         parse_flexible();
1519     }
1520   exit(0);
1521   puts(get_padded_string(32));
1522   if (!match_u32(80))
1523     match_u32_assert(132);
1524   match_zeros_assert(8);
1525   match_u32_assert(1);
1526   printf ("0x%x\n", pos);
1527   get_string4();
1528   match_byte_assert(0);
1529
1530   parse_heading("NavHead");
1531   parse_NavHead();
1532   match_NavTreeViewItem();
1533   match_zeros_assert(3);
1534
1535   parse_heading("NavTitle");
1536   pos += 33;
1537   match_DspSimpleText();
1538   match_DspString();
1539   match_NavTreeViewItem();
1540
1541   match_byte_assert(1);
1542   match_byte_assert(1);
1543   match_u32_assert(-19);
1544   match_zeros_assert(12);
1545   match_byte_assert(0xbc);
1546   match_byte_assert(2);
1547   match_zeros_assert(9);
1548   match_byte_assert(0x22);
1549   puts(get_padded_string(32));
1550   match_u32_assert(80);
1551   match_zeros_assert(8);
1552   match_u32_assert(1);
1553   get_string4();
1554   match_byte_assert(0);
1555
1556   parse_heading("NavNote");
1557   match_byte_assert(2);
1558   match_zeros_assert(8);
1559   match_u32_assert(24);
1560   if (!match_u32(0))
1561     match_u32_assert(-40);
1562   pos += 8;
1563   match_u32_assert(2);
1564   match_u32_assert(1);
1565   match_DspSimpleText();
1566   match_DspString();
1567   match_NavTreeViewItem();
1568   match_byte_assert(1);
1569
1570   parse_heading("PTPivotController");
1571   match_byte_assert(2);
1572   pos += 8;
1573   match_u32_assert(100);
1574   match_u32_assert(100);
1575   match_u32_assert(100);
1576   match_u32_assert(100);
1577
1578   parse_heading("PVPivotView");
1579   match_u32_assert(5);
1580   match_byte_assert(0);
1581
1582   parse_heading("PMPivotModel");
1583   match_byte_assert(3);
1584
1585   parse_heading("NDimensional__DspCell");
1586   match_byte_assert(0);
1587   match_u32_assert(1);
1588
1589   parse_heading("IndexedCollection");
1590   match_byte_assert(0);
1591   pos++;
1592   match_zeros_assert(3);
1593   match_byte_assert(1);
1594   match_byte_assert(0);
1595   match_zeros_assert(7);
1596
1597   while (data[pos] != 1)
1598     {
1599       if (data[pos] == 0)
1600         {
1601           printf("zero\n");
1602           pos++;
1603         }
1604       else
1605         parse_flexible();
1606     }
1607
1608   match_byte_assert(1);
1609   match_byte_assert(0);
1610   puts(get_string1());
1611   if (!match_u32(0))
1612     match_u32_assert(2);
1613   puts(get_string1());
1614
1615   match_byte_assert(0);
1616   match_byte_assert(1);
1617   match_byte_assert(0);
1618   match_byte_assert(0);
1619   match_byte_assert(0);
1620   match_byte_assert(1);
1621   match_byte_assert(0);
1622
1623   exit (0);
1624
1625   parse_heading("PMPivotItemTree");
1626   match_byte_assert(0);
1627
1628   parse_heading("AbstractTreeBranch");
1629   match_byte_assert(0);
1630
1631   parse_heading("PMModelItemInfo");
1632   parse_PMModelItemInfo();
1633   match_DspSimpleText();
1634   match_DspString();
1635
1636   match_u32_assert(7);
1637   match_PMPivotItemTree();
1638
1639   match_u32_assert(0);
1640   match_PMPivotItemTree();
1641
1642   match_u32_assert(0);
1643   match_PMPivotItemTree();
1644
1645   match_u32_assert(6);
1646   match_PMPivotItemTree();
1647
1648   match_u32_assert(0);
1649   match_PMPivotItemTree();
1650
1651   match_u32_assert(0);
1652   match_PMPivotItemTree();
1653
1654   match_u32_assert(0);
1655   match_PMPivotItemTree();
1656
1657   match_u32_assert(0);
1658   match_PMPivotItemTree();
1659
1660   match_u32_assert(0);
1661   match_PMPivotItemTree();
1662
1663   match_u32_assert(0);
1664   match_PMPivotItemTree();
1665
1666   match_u32_assert(2);
1667   match_PMPivotItemTree();
1668
1669   match_u32_assert(0);
1670   match_PMPivotItemTree();
1671
1672   match_u32_assert(0);
1673   match_PMPivotItemTree();
1674
1675   match_u32_assert(0);
1676   match_PMPivotItemTree();
1677
1678   match_u32_assert(0);
1679   match_PMPivotItemTree();
1680
1681   match_u32_assert(2);
1682   match_PMPivotItemTree();
1683
1684   match_u32_assert(0);
1685   match_PMPivotItemTree();
1686
1687   match_u32_assert(0);
1688
1689   /* ...unknown... */
1690
1691   while (data[pos] != 0xff || data[pos + 1] != 0xff)
1692     pos++;
1693   parse_heading("PVViewDimension");
1694
1695   int i;
1696   for (i = 0; data[pos + i] != 0xff || data[pos + i + 1] != 0xff; i++)
1697     ;
1698   hex_dump(stdout, pos, i);
1699
1700   printf ("%#x: end of successful parse\n", pos);
1701
1702   return 0;
1703 }