Make PSPP able to read all the portable files I could find on the
[pspp-builds.git] / src / data / por-file-reader.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include "por-file-reader.h"
19
20 #include <ctype.h>
21 #include <errno.h>
22 #include <math.h>
23 #include <setjmp.h>
24 #include <stdarg.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include <data/casereader-provider.h>
30 #include <data/casereader.h>
31 #include <data/dictionary.h>
32 #include <data/file-handle-def.h>
33 #include <data/format.h>
34 #include <data/missing-values.h>
35 #include <data/value-labels.h>
36 #include <data/variable.h>
37 #include <libpspp/alloc.h>
38 #include <libpspp/compiler.h>
39 #include <libpspp/hash.h>
40 #include <libpspp/magic.h>
41 #include <libpspp/message.h>
42 #include <libpspp/misc.h>
43 #include <libpspp/pool.h>
44 #include <libpspp/str.h>
45
46 #include "gettext.h"
47 #define _(msgid) gettext (msgid)
48
49 /* portable_to_local[PORTABLE] translates the given portable
50    character into the local character set. */
51 static const char portable_to_local[256] =
52   {
53     "                                                                "
54     "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
55     "<(+|&[]!$*);^-/|,%_>?`:$@'=\"      ~-   0123456789   -() {}\\     "
56     "                                                                "
57   };
58
59 /* Portable file reader. */
60 struct pfm_reader
61   {
62     struct pool *pool;          /* All the portable file state. */
63
64     jmp_buf bail_out;           /* longjmp() target for error handling. */
65
66     struct file_handle *fh;     /* File handle. */
67     FILE *file;                 /* File stream. */
68     int line_length;            /* Number of characters so far on this line. */
69     char cc;                    /* Current character. */
70     char *trans;                /* 256-byte character set translation table. */
71     int var_cnt;                /* Number of variables. */
72     int weight_index;           /* 0-based index of weight variable, or -1. */
73     int *widths;                /* Variable widths, 0 for numeric. */
74     size_t value_cnt;           /* Number of `value's per case. */
75     bool ok;                    /* Set false on I/O error. */
76   };
77
78 static struct casereader_class por_file_casereader_class;
79
80 static void
81 error (struct pfm_reader *r, const char *msg,...)
82      PRINTF_FORMAT (2, 3)
83      NO_RETURN;
84
85 /* Displays MSG as an error message and aborts reading the
86    portable file via longjmp(). */
87 static void
88 error (struct pfm_reader *r, const char *msg, ...)
89 {
90   struct msg m;
91   struct string text;
92   va_list args;
93
94   ds_init_empty (&text);
95   ds_put_format (&text, _("portable file %s corrupt at offset 0x%lx: "),
96                  fh_get_file_name (r->fh), ftell (r->file));
97   va_start (args, msg);
98   ds_put_vformat (&text, msg, args);
99   va_end (args);
100
101   m.category = MSG_GENERAL;
102   m.severity = MSG_ERROR;
103   m.where.file_name = NULL;
104   m.where.line_number = 0;
105   m.text = ds_cstr (&text);
106
107   msg_emit (&m);
108
109   r->ok = false;
110
111   longjmp (r->bail_out, 1);
112 }
113
114 /* Displays MSG as an warning for the current position in
115    portable file reader R. */
116 static void
117 warning (struct pfm_reader *r, const char *msg, ...)
118 {
119   struct msg m;
120   struct string text;
121   va_list args;
122
123   ds_init_empty (&text);
124   ds_put_format (&text, _("reading portable file %s at offset 0x%lx: "),
125                  fh_get_file_name (r->fh), ftell (r->file));
126   va_start (args, msg);
127   ds_put_vformat (&text, msg, args);
128   va_end (args);
129
130   m.category = MSG_GENERAL;
131   m.severity = MSG_WARNING;
132   m.where.file_name = NULL;
133   m.where.line_number = 0;
134   m.text = ds_cstr (&text);
135
136   msg_emit (&m);
137 }
138
139 /* Closes portable file reader R, after we're done with it. */
140 static void
141 por_file_casereader_destroy (struct casereader *reader UNUSED, void *r_)
142 {
143   struct pfm_reader *r = r_;
144   pool_destroy (r->pool);
145 }
146
147 /* Read a single character into cur_char.  */
148 static void
149 advance (struct pfm_reader *r)
150 {
151   int c;
152
153   /* Read the next character from the file.
154      Ignore carriage returns entirely.
155      Mostly ignore new-lines, but if a new-line occurs before the
156      line has reached 80 bytes in length, then treat the
157      "missing" bytes as spaces. */
158   for (;;)
159     {
160       while ((c = getc (r->file)) == '\r')
161         continue;
162       if (c != '\n')
163         break;
164
165       if (r->line_length < 80)
166         {
167           c = ' ';
168           ungetc ('\n', r->file);
169           break;
170         }
171       r->line_length = 0;
172     }
173   if (c == EOF)
174     error (r, _("unexpected end of file"));
175
176   if (r->trans != NULL)
177     c = r->trans[c];
178   r->cc = c;
179   r->line_length++;
180 }
181
182 /* Skip a single character if present, and return whether it was
183    skipped. */
184 static inline bool
185 match (struct pfm_reader *r, int c)
186 {
187   if (r->cc == c)
188     {
189       advance (r);
190       return true;
191     }
192   else
193     return false;
194 }
195
196 static void read_header (struct pfm_reader *);
197 static void read_version_data (struct pfm_reader *, struct pfm_read_info *);
198 static void read_variables (struct pfm_reader *, struct dictionary *);
199 static void read_value_label (struct pfm_reader *, struct dictionary *);
200 static void read_documents (struct pfm_reader *, struct dictionary *);
201
202 /* Reads the dictionary from file with handle H, and returns it in a
203    dictionary structure.  This dictionary may be modified in order to
204    rename, reorder, and delete variables, etc. */
205 struct casereader *
206 pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
207                  struct pfm_read_info *info)
208 {
209   struct pool *volatile pool = NULL;
210   struct pfm_reader *volatile r = NULL;
211
212   *dict = dict_create ();
213   if (!fh_open (fh, FH_REF_FILE, "portable file", "rs"))
214     goto error;
215
216   /* Create and initialize reader. */
217   pool = pool_create ();
218   r = pool_alloc (pool, sizeof *r);
219   r->pool = pool;
220   if (setjmp (r->bail_out))
221     goto error;
222   r->fh = fh;
223   r->file = pool_fopen (r->pool, fh_get_file_name (r->fh), "rb");
224   r->line_length = 0;
225   r->weight_index = -1;
226   r->trans = NULL;
227   r->var_cnt = 0;
228   r->widths = NULL;
229   r->value_cnt = 0;
230   r->ok = true;
231
232   /* Check that file open succeeded, prime reading. */
233   if (r->file == NULL)
234     {
235       msg (ME, _("An error occurred while opening \"%s\" for reading "
236                  "as a portable file: %s."),
237            fh_get_file_name (r->fh), strerror (errno));
238       goto error;
239     }
240
241   /* Read header, version, date info, product id, variables. */
242   read_header (r);
243   read_version_data (r, info);
244   read_variables (r, *dict);
245
246   /* Read value labels. */
247   while (match (r, 'D'))
248     read_value_label (r, *dict);
249
250   /* Read documents. */
251   if (match (r, 'E'))
252     read_documents (r, *dict);
253
254   /* Check that we've made it to the data. */
255   if (!match (r, 'F'))
256     error (r, _("Data record expected."));
257
258   r->value_cnt = dict_get_next_value_idx (*dict);
259   return casereader_create_sequential (NULL, r->value_cnt, CASENUMBER_MAX,
260                                        &por_file_casereader_class, r);
261
262  error:
263   pool_destroy (r->pool);
264   dict_destroy (*dict);
265   *dict = NULL;
266   return NULL;
267 }
268 \f
269 /* Returns the value of base-30 digit C,
270    or -1 if C is not a base-30 digit. */
271 static int
272 base_30_value (unsigned char c)
273 {
274   static const char base_30_digits[] = "0123456789ABCDEFGHIJKLMNOPQRST";
275   const char *p = strchr (base_30_digits, c);
276   return p != NULL ? p - base_30_digits : -1;
277 }
278
279 /* Read a floating point value and return its value. */
280 static double
281 read_float (struct pfm_reader *r)
282 {
283   double num = 0.;
284   int exponent = 0;
285   bool got_dot = false;         /* Seen a decimal point? */
286   bool got_digit = false;       /* Seen any digits? */
287   bool negative = false;        /* Number is negative? */
288
289   /* Skip leading spaces. */
290   while (match (r, ' '))
291     continue;
292
293   /* `*' indicates system-missing. */
294   if (match (r, '*'))
295     {
296       advance (r);      /* Probably a dot (.) but doesn't appear to matter. */
297       return SYSMIS;
298     }
299
300   negative = match (r, '-');
301   for (;;)
302     {
303       int digit = base_30_value (r->cc);
304       if (digit != -1)
305         {
306           got_digit = true;
307
308           /* Make sure that multiplication by 30 will not overflow.  */
309           if (num > DBL_MAX * (1. / 30.))
310             /* The value of the digit doesn't matter, since we have already
311                gotten as many digits as can be represented in a `double'.
312                This doesn't necessarily mean the result will overflow.
313                The exponent may reduce it to within range.
314
315                We just need to record that there was another
316                digit so that we can multiply by 10 later.  */
317             ++exponent;
318           else
319             num = (num * 30.0) + digit;
320
321           /* Keep track of the number of digits after the decimal point.
322              If we just divided by 30 here, we would lose precision.  */
323           if (got_dot)
324             --exponent;
325         }
326       else if (!got_dot && r->cc == '.')
327         /* Record that we have found the decimal point.  */
328         got_dot = 1;
329       else
330         /* Any other character terminates the number.  */
331         break;
332
333       advance (r);
334     }
335
336   /* Check that we had some digits. */
337   if (!got_digit)
338     error (r, _("Number expected."));
339
340   /* Get exponent if any. */
341   if (r->cc == '+' || r->cc == '-')
342     {
343       long int exp = 0;
344       bool negative_exponent = r->cc == '-';
345       int digit;
346
347       for (advance (r); (digit = base_30_value (r->cc)) != -1; advance (r))
348         {
349           if (exp > LONG_MAX / 30)
350             {
351               exp = LONG_MAX;
352               break;
353             }
354           exp = exp * 30 + digit;
355         }
356
357       /* We don't check whether there were actually any digits, but we
358          probably should. */
359       if (negative_exponent)
360         exp = -exp;
361       exponent += exp;
362     }
363
364   /* Numbers must end with `/'. */
365   if (!match (r, '/'))
366     error (r, _("Missing numeric terminator."));
367
368   /* Multiply `num' by 30 to the `exponent' power, checking for
369      overflow.  */
370   if (exponent < 0)
371     num *= pow (30.0, (double) exponent);
372   else if (exponent > 0)
373     {
374       if (num > DBL_MAX * pow (30.0, (double) -exponent))
375         num = DBL_MAX;
376       else
377         num *= pow (30.0, (double) exponent);
378     }
379
380   return negative ? -num : num;
381 }
382
383 /* Read an integer and return its value. */
384 static int
385 read_int (struct pfm_reader *r)
386 {
387   double f = read_float (r);
388   if (floor (f) != f || f >= INT_MAX || f <= INT_MIN)
389     error (r, _("Invalid integer."));
390   return f;
391 }
392
393 /* Reads a string into BUF, which must have room for 256
394    characters. */
395 static void
396 read_string (struct pfm_reader *r, char *buf)
397 {
398   int n = read_int (r);
399   if (n < 0 || n > 255)
400     error (r, _("Bad string length %d."), n);
401
402   while (n-- > 0)
403     {
404       *buf++ = r->cc;
405       advance (r);
406     }
407   *buf = '\0';
408 }
409
410 /* Reads a string and returns a copy of it allocated from R's
411    pool. */
412 static char *
413 read_pool_string (struct pfm_reader *r)
414 {
415   char string[256];
416   read_string (r, string);
417   return pool_strdup (r->pool, string);
418 }
419 \f
420 /* Reads the 464-byte file header. */
421 static void
422 read_header (struct pfm_reader *r)
423 {
424   char *trans;
425   int i;
426
427   /* Read and ignore vanity splash strings. */
428   for (i = 0; i < 200; i++)
429     advance (r);
430
431   /* Skip the first 64 characters of the translation table.
432      We don't care about these.  They are probably all set to
433      '0', marking them as untranslatable, and that would screw
434      up our actual translation of the real '0'. */
435   for (i = 0; i < 64; i++)
436     advance (r);
437
438   /* Read the rest of the translation table. */
439   trans = pool_malloc (r->pool, 256);
440   memset (trans, 0, 256);
441   for (; i < 256; i++)
442     {
443       unsigned char c;
444
445       advance (r);
446
447       c = r->cc;
448       if (trans[c] == 0)
449         trans[c] = portable_to_local[i];
450     }
451
452   /* Set up the translation table, then read the first
453      translated character. */
454   r->trans = trans;
455   advance (r);
456
457   /* Skip and verify signature. */
458   for (i = 0; i < 8; i++)
459     if (!match (r, "SPSSPORT"[i]))
460       {
461         msg (SE, _("%s: Not a portable file."), fh_get_file_name (r->fh));
462         longjmp (r->bail_out, 1);
463       }
464 }
465
466 /* Reads the version and date info record, as well as product and
467    subproduct identification records if present. */
468 static void
469 read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
470 {
471   static char empty_string[] = "";
472   char *date, *time, *product, *author, *subproduct;
473   int i;
474
475   /* Read file. */
476   if (!match (r, 'A'))
477     error (r, _("Unrecognized version code `%c'."), r->cc);
478   date = read_pool_string (r);
479   time = read_pool_string (r);
480   product = match (r, '1') ? read_pool_string (r) : empty_string;
481   author = match (r, '2') ? read_pool_string (r) : empty_string;
482   subproduct = match (r, '3') ? read_pool_string (r) : empty_string;
483
484   /* Validate file. */
485   if (strlen (date) != 8)
486     error (r, _("Bad date string length %d."), (int) strlen (date));
487   if (strlen (time) != 6)
488     error (r, _("Bad time string length %d."), (int) strlen (time));
489
490   /* Save file info. */
491   if (info != NULL)
492     {
493       /* Date. */
494       for (i = 0; i < 8; i++)
495         {
496           static const int map[] = {6, 7, 8, 9, 3, 4, 0, 1};
497           info->creation_date[map[i]] = date[i];
498         }
499       info->creation_date[2] = info->creation_date[5] = ' ';
500       info->creation_date[10] = 0;
501
502       /* Time. */
503       for (i = 0; i < 6; i++)
504         {
505           static const int map[] = {0, 1, 3, 4, 6, 7};
506           info->creation_time[map[i]] = time[i];
507         }
508       info->creation_time[2] = info->creation_time[5] = ' ';
509       info->creation_time[8] = 0;
510
511       /* Product. */
512       str_copy_trunc (info->product, sizeof info->product, product);
513       str_copy_trunc (info->subproduct, sizeof info->subproduct, subproduct);
514     }
515 }
516
517 /* Translates a format specification read from portable file R as
518    the three integers INTS into a normal format specifier FORMAT,
519    checking that the format is appropriate for variable V. */
520 static struct fmt_spec
521 convert_format (struct pfm_reader *r, const int portable_format[3],
522                 struct variable *v, bool *report_error)
523 {
524   struct fmt_spec format;
525   bool ok;
526
527   if (!fmt_from_io (portable_format[0], &format.type))
528     {
529       if (*report_error)
530         warning (r, _("%s: Bad format specifier byte (%d).  Variable "
531                       "will be assigned a default format."),
532                  var_get_name (v), portable_format[0]);
533       goto assign_default;
534     }
535
536   format.w = portable_format[1];
537   format.d = portable_format[2];
538
539   msg_disable ();
540   ok = (fmt_check_output (&format)
541         && fmt_check_width_compat (&format, var_get_width (v)));
542   msg_enable ();
543
544   if (!ok)
545     {
546       if (*report_error)
547         {
548           char fmt_string[FMT_STRING_LEN_MAX + 1];
549           fmt_to_string (&format, fmt_string);
550           if (var_is_numeric (v))
551             warning (r, _("Numeric variable %s has invalid format "
552                           "specifier %s."),
553                      var_get_name (v), fmt_string);
554           else
555             warning (r, _("String variable %s with width %d has "
556                           "invalid format specifier %s."),
557                      var_get_name (v), var_get_width (v), fmt_string);
558         }
559       goto assign_default;
560     }
561
562   return format;
563
564 assign_default:
565   *report_error = false;
566   return fmt_default_for_width (var_get_width (v));
567 }
568
569 static union value parse_value (struct pfm_reader *, struct variable *);
570
571 /* Read information on all the variables.  */
572 static void
573 read_variables (struct pfm_reader *r, struct dictionary *dict)
574 {
575   char *weight_name = NULL;
576   int i;
577
578   if (!match (r, '4'))
579     error (r, _("Expected variable count record."));
580
581   r->var_cnt = read_int (r);
582   if (r->var_cnt <= 0 || r->var_cnt == NOT_INT)
583     error (r, _("Invalid number of variables %d."), r->var_cnt);
584   r->widths = pool_nalloc (r->pool, r->var_cnt, sizeof *r->widths);
585
586   /* Purpose of this value is unknown.  It is typically 161. */
587   read_int (r);
588
589   if (match (r, '6'))
590     {
591       weight_name = read_pool_string (r);
592       if (strlen (weight_name) > SHORT_NAME_LEN)
593         error (r, _("Weight variable name (%s) truncated."), weight_name);
594     }
595
596   for (i = 0; i < r->var_cnt; i++)
597     {
598       int width;
599       char name[256];
600       int fmt[6];
601       struct variable *v;
602       struct missing_values miss;
603       struct fmt_spec print, write;
604       bool report_error = true;
605       int j;
606
607       if (!match (r, '7'))
608         error (r, _("Expected variable record."));
609
610       width = read_int (r);
611       if (width < 0)
612         error (r, _("Invalid variable width %d."), width);
613       r->widths[i] = width;
614
615       read_string (r, name);
616       for (j = 0; j < 6; j++)
617         fmt[j] = read_int (r);
618
619       if (!var_is_valid_name (name, false) || *name == '#' || *name == '$')
620         error (r, _("Invalid variable name `%s' in position %d."), name, i);
621       str_uppercase (name);
622
623       if (width < 0 || width > 255)
624         error (r, _("Bad width %d for variable %s."), width, name);
625
626       v = dict_create_var (dict, name, width);
627       if (v == NULL)
628         {
629           int i;
630           for (i = 1; i < 100000; i++)
631             {
632               char try_name[LONG_NAME_LEN + 1];
633               sprintf (try_name, "%.*s_%d", LONG_NAME_LEN - 6, name, i);
634               v = dict_create_var (dict, try_name, width);
635               if (v != NULL)
636                 break;
637             }
638           if (v == NULL)
639             error (r, _("Duplicate variable name %s in position %d."), name, i);
640           warning (r, _("Duplicate variable name %s in position %d renamed "
641                         "to %s."), name, i, var_get_name (v));
642         }
643
644       print = convert_format (r, &fmt[0], v, &report_error);
645       write = convert_format (r, &fmt[3], v, &report_error);
646       var_set_print_format (v, &print);
647       var_set_write_format (v, &write);
648
649       /* Range missing values. */
650       mv_init (&miss, var_get_width (v));
651       if (match (r, 'B'))
652         {
653           double x = read_float (r);
654           double y = read_float (r);
655           mv_add_num_range (&miss, x, y);
656         }
657       else if (match (r, 'A'))
658         mv_add_num_range (&miss, read_float (r), HIGHEST);
659       else if (match (r, '9'))
660         mv_add_num_range (&miss, LOWEST, read_float (r));
661
662       /* Single missing values. */
663       while (match (r, '8'))
664         {
665           union value value = parse_value (r, v);
666           mv_add_value (&miss, &value);
667         }
668
669       var_set_missing_values (v, &miss);
670
671       if (match (r, 'C'))
672         {
673           char label[256];
674           read_string (r, label);
675           var_set_label (v, label);
676         }
677     }
678
679   if (weight_name != NULL)
680     {
681       struct variable *weight_var = dict_lookup_var (dict, weight_name);
682       if (weight_var == NULL)
683         error (r, _("Weighting variable %s not present in dictionary."),
684                weight_name);
685
686       dict_set_weight (dict, weight_var);
687     }
688 }
689
690 /* Parse a value for variable VV into value V. */
691 static union value
692 parse_value (struct pfm_reader *r, struct variable *vv)
693 {
694   union value v;
695
696   if (var_is_alpha (vv))
697     {
698       char string[256];
699       read_string (r, string);
700       buf_copy_str_rpad (v.s, 8, string);
701     }
702   else
703     v.f = read_float (r);
704
705   return v;
706 }
707
708 /* Parse a value label record and return success. */
709 static void
710 read_value_label (struct pfm_reader *r, struct dictionary *dict)
711 {
712   /* Variables. */
713   int nv;
714   struct variable **v;
715
716   /* Labels. */
717   int n_labels;
718
719   int i;
720
721   nv = read_int (r);
722   v = pool_nalloc (r->pool, nv, sizeof *v);
723   for (i = 0; i < nv; i++)
724     {
725       char name[256];
726       read_string (r, name);
727
728       v[i] = dict_lookup_var (dict, name);
729       if (v[i] == NULL)
730         error (r, _("Unknown variable %s while parsing value labels."), name);
731
732       if (var_get_type (v[0]) != var_get_type (v[i]))
733         error (r, _("Cannot assign value labels to %s and %s, which "
734                     "have different variable types."),
735                var_get_name (v[0]), var_get_name (v[i]));
736     }
737
738   n_labels = read_int (r);
739   for (i = 0; i < n_labels; i++)
740     {
741       union value val;
742       char label[256];
743       int j;
744
745       val = parse_value (r, v[0]);
746       read_string (r, label);
747
748       /* Assign the value label to each variable. */
749       for (j = 0; j < nv; j++)
750         {
751           struct variable *var = v[j];
752
753           if (!var_is_long_string (var))
754             var_replace_value_label (var, &val, label);
755         }
756     }
757 }
758
759 /* Reads a set of documents from portable file R into DICT. */
760 static void
761 read_documents (struct pfm_reader *r, struct dictionary *dict)
762 {
763   int line_cnt;
764   int i;
765
766   line_cnt = read_int (r);
767   for (i = 0; i < line_cnt; i++)
768     {
769       char line[256];
770       read_string (r, line);
771       dict_add_document_line (dict, line);
772     }
773 }
774
775 /* Reads one case from portable file R into C. */
776 static bool
777 por_file_casereader_read (struct casereader *reader, void *r_, struct ccase *c)
778 {
779   struct pfm_reader *r = r_;
780   size_t i;
781   size_t idx;
782
783   case_create (c, casereader_get_value_cnt (reader));
784   setjmp (r->bail_out);
785   if (!r->ok)
786     {
787       casereader_force_error (reader);
788       case_destroy (c);
789       return false;
790     }
791
792   /* Check for end of file. */
793   if (r->cc == 'Z')
794     {
795       case_destroy (c);
796       return false;
797     }
798
799   idx = 0;
800   for (i = 0; i < r->var_cnt; i++)
801     {
802       int width = r->widths[i];
803
804       if (width == 0)
805         {
806           case_data_rw_idx (c, idx)->f = read_float (r);
807           idx++;
808         }
809       else
810         {
811           char string[256];
812           read_string (r, string);
813           buf_copy_str_rpad (case_data_rw_idx (c, idx)->s, width, string);
814           idx += DIV_RND_UP (width, MAX_SHORT_STRING);
815         }
816     }
817
818   return true;
819 }
820
821 /* Returns true if FILE is an SPSS portable file,
822    false otherwise. */
823 bool
824 pfm_detect (FILE *file)
825 {
826   unsigned char header[464];
827   char trans[256];
828   int cooked_cnt, raw_cnt;
829   int i;
830
831   cooked_cnt = raw_cnt = 0;
832   while (cooked_cnt < sizeof header)
833     {
834       int c = getc (file);
835       if (c == EOF || raw_cnt++ > 512)
836         return false;
837       else if (c != '\n' && c != '\r')
838         header[cooked_cnt++] = c;
839     }
840
841   memset (trans, 0, 256);
842   for (i = 64; i < 256; i++)
843     {
844       unsigned char c = header[i + 200];
845       if (trans[c] == 0)
846         trans[c] = portable_to_local[i];
847     }
848
849   for (i = 0; i < 8; i++)
850     if (trans[header[i + 456]] != "SPSSPORT"[i])
851       return false;
852
853   return true;
854 }
855
856 static struct casereader_class por_file_casereader_class =
857   {
858     por_file_casereader_read,
859     por_file_casereader_destroy,
860     NULL,
861     NULL,
862   };