Merge commit 'origin/stable'
[pspp-builds.git] / src / data / por-file-reader.c
index c4fec4b0ed72d30a2608c9b6af49340d35d40384..a463124274a20c6cd62e1071f15c51cd95edcb3a 100644 (file)
@@ -1,53 +1,54 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
-   Code for parsing floating-point numbers adapted from GNU C
-   library.
-
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License as
-   published by the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 #include "por-file-reader.h"
-#include <libpspp/message.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
+
 #include <ctype.h>
 #include <errno.h>
 #include <math.h>
 #include <setjmp.h>
-#include <libpspp/alloc.h>
+#include <stdarg.h>
 #include <stdbool.h>
-#include "case.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <data/casereader-provider.h>
+#include <data/casereader.h>
+#include <data/dictionary.h>
+#include <data/file-handle-def.h>
+#include <data/file-name.h>
+#include <data/format.h>
+#include <data/missing-values.h>
+#include <data/short-names.h>
+#include <data/value-labels.h>
+#include <data/variable.h>
 #include <libpspp/compiler.h>
-#include "dictionary.h"
-#include "file-handle-def.h"
-#include "format.h"
 #include <libpspp/hash.h>
-#include <libpspp/magic.h>
+#include <libpspp/message.h>
 #include <libpspp/misc.h>
 #include <libpspp/pool.h>
 #include <libpspp/str.h>
-#include "value-labels.h"
-#include "variable.h"
+
+#include "minmax.h"
+#include "xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
-
-#include <libpspp/debug-print.h>
+#define N_(msgid) (msgid)
 
 /* portable_to_local[PORTABLE] translates the given portable
    character into the local character set. */
@@ -67,16 +68,19 @@ struct pfm_reader
     jmp_buf bail_out;           /* longjmp() target for error handling. */
 
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Read lock for file. */
     FILE *file;                        /* File stream. */
+    int line_length;            /* Number of characters so far on this line. */
     char cc;                   /* Current character. */
     char *trans;                /* 256-byte character set translation table. */
     int var_cnt;                /* Number of variables. */
     int weight_index;          /* 0-based index of weight variable, or -1. */
-    int *widths;                /* Variable widths, 0 for numeric. */
-    int value_cnt;             /* Number of `value's per case. */
+    struct caseproto *proto;    /* Format of output cases. */
     bool ok;                    /* Set false on I/O error. */
   };
 
+static const struct casereader_class por_file_casereader_class;
+
 static void
 error (struct pfm_reader *r, const char *msg,...)
      PRINTF_FORMAT (2, 3)
@@ -87,34 +91,91 @@ error (struct pfm_reader *r, const char *msg,...)
 static void
 error (struct pfm_reader *r, const char *msg, ...)
 {
-  struct error e;
-  const char *filename;
-  char *title;
+  struct msg m;
+  struct string text;
   va_list args;
 
-  e.class = ME;
-  e.where.filename = NULL;
-  e.where.line_number = 0;
-  filename = fh_get_filename (r->fh);
-  e.title = title = pool_alloc (r->pool, strlen (filename) + 80);
-  sprintf (title, _("portable file %s corrupt at offset %ld: "),
-           filename, ftell (r->file));
-
+  ds_init_empty (&text);
+  ds_put_format (&text, _("portable file %s corrupt at offset 0x%lx: "),
+                 fh_get_file_name (r->fh), ftell (r->file));
   va_start (args, msg);
-  err_vmsg (&e, msg, args);
+  ds_put_vformat (&text, msg, args);
   va_end (args);
 
+  m.category = MSG_GENERAL;
+  m.severity = MSG_ERROR;
+  m.where.file_name = NULL;
+  m.where.line_number = 0;
+  m.text = ds_cstr (&text);
+
+  msg_emit (&m);
+
   r->ok = false;
 
   longjmp (r->bail_out, 1);
 }
 
+/* Displays MSG as an warning for the current position in
+   portable file reader R. */
+static void
+warning (struct pfm_reader *r, const char *msg, ...)
+{
+  struct msg m;
+  struct string text;
+  va_list args;
+
+  ds_init_empty (&text);
+  ds_put_format (&text, _("reading portable file %s at offset 0x%lx: "),
+                 fh_get_file_name (r->fh), ftell (r->file));
+  va_start (args, msg);
+  ds_put_vformat (&text, msg, args);
+  va_end (args);
+
+  m.category = MSG_GENERAL;
+  m.severity = MSG_WARNING;
+  m.where.file_name = NULL;
+  m.where.line_number = 0;
+  m.text = ds_cstr (&text);
+
+  msg_emit (&m);
+}
+
+/* Close and destroy R.
+   Returns false if an error was detected on R, true otherwise. */
+static bool
+close_reader (struct pfm_reader *r)
+{
+  bool ok;
+  if (r == NULL)
+    return true;
+
+  if (r->file)
+    {
+      if (fn_close (fh_get_file_name (r->fh), r->file) == EOF)
+        {
+          msg (ME, _("Error closing portable file \"%s\": %s."),
+               fh_get_file_name (r->fh), strerror (errno));
+          r->ok = false;
+        }
+      r->file = NULL;
+    }
+
+  fh_unlock (r->lock);
+  fh_unref (r->fh);
+
+  ok = r->ok;
+  pool_destroy (r->pool);
+
+  return ok;
+}
+
 /* Closes portable file reader R, after we're done with it. */
-void
-pfm_close_reader (struct pfm_reader *r)
+static void
+por_file_casereader_destroy (struct casereader *reader, void *r_)
 {
-  if (r != NULL)
-    pool_destroy (r->pool);
+  struct pfm_reader *r = r_;
+  if (!close_reader (r))
+    casereader_force_error (reader);
 }
 
 /* Read a single character into cur_char.  */
@@ -123,14 +184,33 @@ advance (struct pfm_reader *r)
 {
   int c;
 
-  while ((c = getc (r->file)) == '\r' || c == '\n')
-    continue;
+  /* Read the next character from the file.
+     Ignore carriage returns entirely.
+     Mostly ignore new-lines, but if a new-line occurs before the
+     line has reached 80 bytes in length, then treat the
+     "missing" bytes as spaces. */
+  for (;;)
+    {
+      while ((c = getc (r->file)) == '\r')
+        continue;
+      if (c != '\n')
+        break;
+
+      if (r->line_length < 80)
+        {
+          c = ' ';
+          ungetc ('\n', r->file);
+          break;
+        }
+      r->line_length = 0;
+    }
   if (c == EOF)
-    error (r, _("unexpected end of file")); 
+    error (r, _("unexpected end of file"));
 
   if (r->trans != NULL)
-    c = r->trans[c]; 
+    c = r->trans[c];
   r->cc = c;
+  r->line_length++;
 }
 
 /* Skip a single character if present, and return whether it was
@@ -151,12 +231,12 @@ static void read_header (struct pfm_reader *);
 static void read_version_data (struct pfm_reader *, struct pfm_read_info *);
 static void read_variables (struct pfm_reader *, struct dictionary *);
 static void read_value_label (struct pfm_reader *, struct dictionary *);
-void dump_dictionary (struct dictionary *);
+static void read_documents (struct pfm_reader *, struct dictionary *);
 
 /* Reads the dictionary from file with handle H, and returns it in a
    dictionary structure.  This dictionary may be modified in order to
    rename, reorder, and delete variables, etc. */
-struct pfm_reader *
+struct casereader *
 pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
                  struct pfm_read_info *info)
 {
@@ -164,33 +244,40 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
   struct pfm_reader *volatile r = NULL;
 
   *dict = dict_create ();
-  if (!fh_open (fh, FH_REF_FILE, "portable file", "rs"))
-    goto error;
 
   /* Create and initialize reader. */
   pool = pool_create ();
   r = pool_alloc (pool, sizeof *r);
   r->pool = pool;
-  if (setjmp (r->bail_out))
-    goto error;
-  r->fh = fh;
-  r->file = pool_fopen (r->pool, fh_get_filename (r->fh), "rb");
+  r->fh = fh_ref (fh);
+  r->lock = NULL;
+  r->file = NULL;
+  r->line_length = 0;
   r->weight_index = -1;
   r->trans = NULL;
   r->var_cnt = 0;
-  r->widths = NULL;
-  r->value_cnt = 0;
+  r->proto = NULL;
   r->ok = true;
+  if (setjmp (r->bail_out))
+    goto error;
+
+  /* Lock file. */
+  /* TRANSLATORS: this fragment will be interpolated into
+     messages in fh_lock() that identify types of files. */
+  r->lock = fh_lock (fh, FH_REF_FILE, N_("portable file"), FH_ACC_READ, false);
+  if (r->lock == NULL)
+    goto error;
 
-  /* Check that file open succeeded, prime reading. */
+  /* Open file. */
+  r->file = fn_open (fh_get_file_name (r->fh), "rb");
   if (r->file == NULL)
     {
       msg (ME, _("An error occurred while opening \"%s\" for reading "
                  "as a portable file: %s."),
-           fh_get_filename (r->fh), strerror (errno));
+           fh_get_file_name (r->fh), strerror (errno));
       goto error;
     }
-  
+
   /* Read header, version, date info, product id, variables. */
   read_header (r);
   read_version_data (r, info);
@@ -200,14 +287,20 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
   while (match (r, 'D'))
     read_value_label (r, *dict);
 
+  /* Read documents. */
+  if (match (r, 'E'))
+    read_documents (r, *dict);
+
   /* Check that we've made it to the data. */
   if (!match (r, 'F'))
     error (r, _("Data record expected."));
 
-  return r;
+  r->proto = caseproto_ref_pool (dict_get_proto (*dict), r->pool);
+  return casereader_create_sequential (NULL, r->proto, CASENUMBER_MAX,
+                                       &por_file_casereader_class, r);
 
  error:
-  pfm_close_reader (r);
+  close_reader (r);
   dict_destroy (*dict);
   *dict = NULL;
   return NULL;
@@ -216,7 +309,7 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
 /* Returns the value of base-30 digit C,
    or -1 if C is not a base-30 digit. */
 static int
-base_30_value (unsigned char c) 
+base_30_value (unsigned char c)
 {
   static const char base_30_digits[] = "0123456789ABCDEFGHIJKLMNOPQRST";
   const char *p = strchr (base_30_digits, c);
@@ -282,7 +375,7 @@ read_float (struct pfm_reader *r)
 
   /* Check that we had some digits. */
   if (!got_digit)
-    error (r, "Number expected.");
+    error (r, _("Number expected."));
 
   /* Get exponent if any. */
   if (r->cc == '+' || r->cc == '-')
@@ -326,7 +419,7 @@ read_float (struct pfm_reader *r)
 
   return negative ? -num : num;
 }
-  
+
 /* Read an integer and return its value. */
 static int
 read_int (struct pfm_reader *r)
@@ -345,7 +438,7 @@ read_string (struct pfm_reader *r, char *buf)
   int n = read_int (r);
   if (n < 0 || n > 255)
     error (r, _("Bad string length %d."), n);
-  
+
   while (n-- > 0)
     {
       *buf++ = r->cc;
@@ -354,10 +447,32 @@ read_string (struct pfm_reader *r, char *buf)
   *buf = '\0';
 }
 
+
+/* Reads a string into BUF, which must have room for 256
+   characters.
+   Returns the number of bytes read.
+*/
+static size_t
+read_bytes (struct pfm_reader *r, uint8_t *buf)
+{
+  int n = read_int (r);
+  if (n < 0 || n > 255)
+    error (r, _("Bad string length %d."), n);
+
+  while (n-- > 0)
+    {
+      *buf++ = r->cc;
+      advance (r);
+    }
+  return n;
+}
+
+
+
 /* Reads a string and returns a copy of it allocated from R's
    pool. */
 static char *
-read_pool_string (struct pfm_reader *r) 
+read_pool_string (struct pfm_reader *r)
 {
   char string[256];
   read_string (r, string);
@@ -374,7 +489,7 @@ read_header (struct pfm_reader *r)
   /* Read and ignore vanity splash strings. */
   for (i = 0; i < 200; i++)
     advance (r);
-  
+
   /* Skip the first 64 characters of the translation table.
      We don't care about these.  They are probably all set to
      '0', marking them as untranslatable, and that would screw
@@ -385,7 +500,7 @@ read_header (struct pfm_reader *r)
   /* Read the rest of the translation table. */
   trans = pool_malloc (r->pool, 256);
   memset (trans, 0, 256);
-  for (; i < 256; i++) 
+  for (; i < 256; i++)
     {
       unsigned char c;
 
@@ -399,13 +514,13 @@ read_header (struct pfm_reader *r)
   /* Set up the translation table, then read the first
      translated character. */
   r->trans = trans;
-  advance (r); 
+  advance (r);
 
   /* Skip and verify signature. */
-  for (i = 0; i < 8; i++) 
-    if (!match (r, "SPSSPORT"[i])) 
+  for (i = 0; i < 8; i++)
+    if (!match (r, "SPSSPORT"[i]))
       {
-        msg (SE, _("%s: Not a portable file."), fh_get_filename (r->fh));
+        msg (SE, _("%s: Not a portable file."), fh_get_file_name (r->fh));
         longjmp (r->bail_out, 1);
       }
 }
@@ -415,13 +530,14 @@ read_header (struct pfm_reader *r)
 static void
 read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
 {
-  static char empty_string[] = "";
-  char *date, *time, *product, *author, *subproduct;
+  static const char empty_string[] = "";
+  char *date, *time;
+  const char *product, *author, *subproduct;
   int i;
 
   /* Read file. */
   if (!match (r, 'A'))
-    error (r, "Unrecognized version code `%c'.", r->cc);
+    error (r, _("Unrecognized version code `%c'."), r->cc);
   date = read_pool_string (r);
   time = read_pool_string (r);
   product = match (r, '1') ? read_pool_string (r) : empty_string;
@@ -430,18 +546,18 @@ read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
 
   /* Validate file. */
   if (strlen (date) != 8)
-    error (r, _("Bad date string length %d."), strlen (date));
+    error (r, _("Bad date string length %zu."), strlen (date));
   if (strlen (time) != 6)
-    error (r, _("Bad time string length %d."), strlen (time));
+    error (r, _("Bad time string length %zu."), strlen (time));
 
   /* Save file info. */
-  if (info != NULL) 
+  if (info != NULL)
     {
       /* Date. */
-      for (i = 0; i < 8; i++) 
+      for (i = 0; i < 8; i++)
         {
           static const int map[] = {6, 7, 8, 9, 3, 4, 0, 1};
-          info->creation_date[map[i]] = date[i]; 
+          info->creation_date[map[i]] = date[i];
         }
       info->creation_date[2] = info->creation_date[5] = ' ';
       info->creation_date[10] = 0;
@@ -464,25 +580,56 @@ read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
 /* Translates a format specification read from portable file R as
    the three integers INTS into a normal format specifier FORMAT,
    checking that the format is appropriate for variable V. */
-static void
+static struct fmt_spec
 convert_format (struct pfm_reader *r, const int portable_format[3],
-                struct fmt_spec *format, struct variable *v)
+                struct variable *v, bool *report_error)
 {
-  format->type = translate_fmt (portable_format[0]);
-  if (format->type == -1)
-    error (r, _("%s: Bad format specifier byte (%d)."),
-           v->name, portable_format[0]);
-  format->w = portable_format[1];
-  format->d = portable_format[2];
-
-  if (!check_output_specifier (format, false)
-      || !check_specifier_width (format, v->width, false))
-    error (r, _("%s variable %s has invalid format specifier %s."),
-           v->type == NUMERIC ? _("Numeric") : _("String"),
-           v->name, fmt_to_string (format));
+  struct fmt_spec format;
+  bool ok;
+
+  if (!fmt_from_io (portable_format[0], &format.type))
+    {
+      if (*report_error)
+        warning (r, _("%s: Bad format specifier byte (%d).  Variable "
+                      "will be assigned a default format."),
+                 var_get_name (v), portable_format[0]);
+      goto assign_default;
+    }
+
+  format.w = portable_format[1];
+  format.d = portable_format[2];
+
+  msg_disable ();
+  ok = (fmt_check_output (&format)
+        && fmt_check_width_compat (&format, var_get_width (v)));
+  msg_enable ();
+
+  if (!ok)
+    {
+      if (*report_error)
+        {
+          char fmt_string[FMT_STRING_LEN_MAX + 1];
+          fmt_to_string (&format, fmt_string);
+          if (var_is_numeric (v))
+            warning (r, _("Numeric variable %s has invalid format "
+                          "specifier %s."),
+                     var_get_name (v), fmt_string);
+          else
+            warning (r, _("String variable %s with width %d has "
+                          "invalid format specifier %s."),
+                     var_get_name (v), var_get_width (v), fmt_string);
+        }
+      goto assign_default;
+    }
+
+  return format;
+
+assign_default:
+  *report_error = false;
+  return fmt_default_for_width (var_get_width (v));
 }
 
-static union value parse_value (struct pfm_reader *, struct variable *);
+static void parse_value (struct pfm_reader *, int width, union value *);
 
 /* Read information on all the variables.  */
 static void
@@ -490,14 +637,13 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
 {
   char *weight_name = NULL;
   int i;
-  
+
   if (!match (r, '4'))
     error (r, _("Expected variable count record."));
-  
+
   r->var_cnt = read_int (r);
-  if (r->var_cnt <= 0 || r->var_cnt == NOT_INT)
+  if (r->var_cnt <= 0)
     error (r, _("Invalid number of variables %d."), r->var_cnt);
-  r->widths = pool_nalloc (r->pool, r->var_cnt, sizeof *r->widths);
 
   /* Purpose of this value is unknown.  It is typically 161. */
   read_int (r);
@@ -505,16 +651,19 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
   if (match (r, '6'))
     {
       weight_name = read_pool_string (r);
-      if (strlen (weight_name) > SHORT_NAME_LEN) 
+      if (strlen (weight_name) > SHORT_NAME_LEN)
         error (r, _("Weight variable name (%s) truncated."), weight_name);
     }
-  
+
   for (i = 0; i < r->var_cnt; i++)
     {
       int width;
       char name[256];
       int fmt[6];
       struct variable *v;
+      struct missing_values miss;
+      struct fmt_spec print, write;
+      bool report_error = true;
       int j;
 
       if (!match (r, '7'))
@@ -523,54 +672,78 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
       width = read_int (r);
       if (width < 0)
        error (r, _("Invalid variable width %d."), width);
-      r->widths[i] = width;
 
       read_string (r, name);
       for (j = 0; j < 6; j++)
         fmt[j] = read_int (r);
 
       if (!var_is_valid_name (name, false) || *name == '#' || *name == '$')
-        error (r, _("position %d: Invalid variable name `%s'."), i, name);
+        error (r, _("Invalid variable name `%s' in position %d."), name, i);
       str_uppercase (name);
 
       if (width < 0 || width > 255)
-       error (r, "Bad width %d for variable %s.", width, name);
+       error (r, _("Bad width %d for variable %s."), width, name);
 
       v = dict_create_var (dict, name, width);
       if (v == NULL)
-       error (r, _("Duplicate variable name %s."), name);
+        {
+          int i;
+          for (i = 1; i < 100000; i++)
+            {
+              char try_name[VAR_NAME_LEN + 1];
+              sprintf (try_name, "%.*s_%d", VAR_NAME_LEN - 6, name, i);
+              v = dict_create_var (dict, try_name, width);
+              if (v != NULL)
+                break;
+            }
+          if (v == NULL)
+            error (r, _("Duplicate variable name %s in position %d."), name, i);
+          warning (r, _("Duplicate variable name %s in position %d renamed "
+                        "to %s."), name, i, var_get_name (v));
+        }
 
-      convert_format (r, &fmt[0], &v->print, v);
-      convert_format (r, &fmt[3], &v->write, v);
+      print = convert_format (r, &fmt[0], v, &report_error);
+      write = convert_format (r, &fmt[3], v, &report_error);
+      var_set_print_format (v, &print);
+      var_set_write_format (v, &write);
 
       /* Range missing values. */
-      if (match (r, 'B')) 
+      mv_init (&miss, width);
+      if (match (r, 'B'))
         {
           double x = read_float (r);
           double y = read_float (r);
-          mv_add_num_range (&v->miss, x, y);
+          mv_add_range (&miss, x, y);
         }
       else if (match (r, 'A'))
-        mv_add_num_range (&v->miss, read_float (r), HIGHEST);
+        mv_add_range (&miss, read_float (r), HIGHEST);
       else if (match (r, '9'))
-        mv_add_num_range (&v->miss, LOWEST, read_float (r));
+        mv_add_range (&miss, LOWEST, read_float (r));
 
       /* Single missing values. */
-      while (match (r, '8')) 
+      while (match (r, '8'))
         {
-          union value value = parse_value (r, v);
-          mv_add_value (&v->miss, &value); 
+          int mv_width = MIN (width, 8);
+          union value value;
+
+          parse_value (r, mv_width, &value);
+          value_resize (&value, mv_width, width);
+          mv_add_value (&miss, &value);
+          value_destroy (&value, width);
         }
 
-      if (match (r, 'C')) 
+      var_set_missing_values (v, &miss);
+      mv_destroy (&miss);
+
+      if (match (r, 'C'))
         {
           char label[256];
           read_string (r, label);
-          v->label = xstrdup (label); 
+          var_set_label (v, label);
         }
     }
 
-  if (weight_name != NULL) 
+  if (weight_name != NULL)
     {
       struct variable *weight_var = dict_lookup_var (dict, weight_name);
       if (weight_var == NULL)
@@ -581,22 +754,19 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
     }
 }
 
-/* Parse a value for variable VV into value V. */
-static union value
-parse_value (struct pfm_reader *r, struct variable *vv)
+/* Parse a value of with WIDTH into value V. */
+static void
+parse_value (struct pfm_reader *r, int width, union value *v)
 {
-  union value v;
-  
-  if (vv->type == ALPHA) 
+  value_init (v, width);
+  if (width > 0)
     {
-      char string[256];
-      read_string (r, string);
-      buf_copy_str_rpad (v.s, 8, string); 
+      uint8_t buf[256];
+      size_t n_bytes = read_bytes (r, buf);
+      value_copy_buf_rpad (v, width, buf, n_bytes, ' ');
     }
   else
-    v.f = read_float (r);
-
-  return v;
+    v->f = read_float (r);
 }
 
 /* Parse a value label record and return success. */
@@ -623,10 +793,10 @@ read_value_label (struct pfm_reader *r, struct dictionary *dict)
       if (v[i] == NULL)
        error (r, _("Unknown variable %s while parsing value labels."), name);
 
-      if (v[0]->width != v[i]->width)
+      if (var_get_type (v[0]) != var_get_type (v[i]))
        error (r, _("Cannot assign value labels to %s and %s, which "
-                   "have different variable types or widths."),
-              v[0]->name, v[i]->name);
+                   "have different variable types."),
+              var_get_name (v[0]), var_get_name (v[i]));
     }
 
   n_labels = read_int (r);
@@ -636,103 +806,127 @@ read_value_label (struct pfm_reader *r, struct dictionary *dict)
       char label[256];
       int j;
 
-      val = parse_value (r, v[0]);
+      parse_value (r, var_get_width (v[0]), &val);
       read_string (r, label);
 
-      /* Assign the value_label's to each variable. */
+      /* Assign the value label to each variable. */
       for (j = 0; j < nv; j++)
-       {
-         struct variable *var = v[j];
+        var_replace_value_label (v[j], &val, label);
 
-         if (!val_labs_replace (var->val_labs, val, label))
-           continue;
+      value_destroy (&val, var_get_width (v[0]));
+    }
+}
 
-         if (var->type == NUMERIC)
-           error (r, _("Duplicate label for value %g for variable %s."),
-                  val.f, var->name);
-         else
-           error (r, _("Duplicate label for value `%.*s' for variable %s."),
-                  var->width, val.s, var->name);
-       }
+/* Reads a set of documents from portable file R into DICT. */
+static void
+read_documents (struct pfm_reader *r, struct dictionary *dict)
+{
+  int line_cnt;
+  int i;
+
+  line_cnt = read_int (r);
+  for (i = 0; i < line_cnt; i++)
+    {
+      char line[256];
+      read_string (r, line);
+      dict_add_document_line (dict, line);
     }
 }
 
-/* Reads one case from portable file R into C. */
-bool
-pfm_read_case (struct pfm_reader *r, struct ccase *c)
+/* Reads and returns one case from portable file R.  Returns a
+   null pointer on failure. */
+static struct ccase *
+por_file_casereader_read (struct casereader *reader, void *r_)
 {
+  struct pfm_reader *r = r_;
+  struct ccase *volatile c;
   size_t i;
-  size_t idx;
 
+  c = case_create (r->proto);
   setjmp (r->bail_out);
   if (!r->ok)
-    return false;
-  
+    {
+      casereader_force_error (reader);
+      case_unref (c);
+      return NULL;
+    }
+
   /* Check for end of file. */
   if (r->cc == 'Z')
-    return false;
+    {
+      case_unref (c);
+      return NULL;
+    }
 
-  idx = 0;
-  for (i = 0; i < r->var_cnt; i++) 
+  for (i = 0; i < r->var_cnt; i++)
     {
-      int width = r->widths[i];
-      
+      int width = caseproto_get_width (r->proto, i);
+
       if (width == 0)
-        {
-          case_data_rw (c, idx)->f = read_float (r);
-          idx++;
-        }
+        case_data_rw_idx (c, i)->f = read_float (r);
       else
         {
-          char string[256];
-          read_string (r, string);
-          buf_copy_str_rpad (case_data_rw (c, idx)->s, width, string);
-          idx += DIV_RND_UP (width, MAX_SHORT_STRING);
+          uint8_t buf[256];
+          size_t n_bytes = read_bytes (r, buf);
+          u8_buf_copy_rpad (case_str_rw_idx (c, i), width, buf, n_bytes, ' ');
         }
     }
-  
-  return true;
-}
 
-/* Returns true if an I/O error has occurred on READER, false
-   otherwise. */
-bool
-pfm_read_error (const struct pfm_reader *reader) 
-{
-  return !reader->ok;
+  return c;
 }
 
 /* Returns true if FILE is an SPSS portable file,
    false otherwise. */
 bool
-pfm_detect (FILE *file) 
+pfm_detect (FILE *file)
 {
   unsigned char header[464];
   char trans[256];
-  int cooked_cnt, raw_cnt;
+  int cooked_cnt, raw_cnt, line_len;
   int i;
 
   cooked_cnt = raw_cnt = 0;
+  line_len = 0;
   while (cooked_cnt < sizeof header)
     {
       int c = getc (file);
       if (c == EOF || raw_cnt++ > 512)
         return false;
-      else if (c != '\n' && c != '\r') 
-        header[cooked_cnt++] = c;
+      else if (c == '\n')
+        {
+          while (line_len < 80 && cooked_cnt < sizeof header)
+            {
+              header[cooked_cnt++] = ' ';
+              line_len++;
+            }
+          line_len = 0;
+        }
+      else if (c != '\r')
+        {
+          header[cooked_cnt++] = c;
+          line_len++;
+        }
     }
 
   memset (trans, 0, 256);
-  for (i = 64; i < 256; i++) 
+  for (i = 64; i < 256; i++)
     {
       unsigned char c = header[i + 200];
       if (trans[c] == 0)
         trans[c] = portable_to_local[i];
     }
 
-  for (i = 0; i < 8; i++) 
-    if (trans[header[i + 456]] != "SPSSPORT"[i]) 
-      return false; 
+  for (i = 0; i < 8; i++)
+    if (trans[header[i + 456]] != "SPSSPORT"[i])
+      return false;
 
   return true;
 }
+
+static const struct casereader_class por_file_casereader_class =
+  {
+    por_file_casereader_read,
+    por_file_casereader_destroy,
+    NULL,
+    NULL,
+  };