GET: Add an ENCODING subcommand. 20120208030501/pspp
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 8 Feb 2012 06:58:09 +0000 (22:58 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 8 Feb 2012 06:59:16 +0000 (22:59 -0800)
For example, this allows a Swedish EBCDIC file that doesn't contain
any indication of its codepage to be read with "ENCODING='IBM278'".

14 files changed:
NEWS
doc/files.texi
perl-module/PSPP.xs
src/data/any-reader.c
src/data/any-reader.h
src/data/sys-file-reader.c
src/data/sys-file-reader.h
src/data/sys-file-writer.c
src/language/data-io/combine-files.c
src/language/data-io/get.c
src/language/dictionary/apply-dictionary.c
src/language/dictionary/sys-file-info.c
src/libpspp/i18n.c
src/libpspp/i18n.h

diff --git a/NEWS b/NEWS
index 916c7d484890a3e10f009b0ce790bebab5d51f77..aa8c0df021fba1c811e5a9ba7bd84d2ea4fe7670 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -40,7 +40,7 @@ Changes from 0.6.2 to 0.7.9:
 
    - HOST has been updated to use more modern syntax.
 
 
    - HOST has been updated to use more modern syntax.
 
-   - INCLUDE and INSERT have a new ENCODING subcommand.
+   - GET, INCLUDE, and INSERT have a new ENCODING subcommand.
 
    - MISSING VALUES can now assign missing values to long string
      variables.
 
    - MISSING VALUES can now assign missing values to long string
      variables.
index cdce0a3c4689875560ef57dad998253c5f51b5e2..86aecab52c97b31025458e7af0a4d47e20ec9c07 100644 (file)
@@ -139,6 +139,7 @@ GET
         /DROP=var_list
         /KEEP=var_list
         /RENAME=(src_names=target_names)@dots{}
         /DROP=var_list
         /KEEP=var_list
         /RENAME=(src_names=target_names)@dots{}
+        /ENCODING='encoding'
 @end display
 
 @cmd{GET} clears the current dictionary and active dataset and
 @end display
 
 @cmd{GET} clears the current dictionary and active dataset and
@@ -171,6 +172,13 @@ Each may be present any number of times.  @cmd{GET} never modifies a
 file on disk.  Only the active dataset read from the file
 is affected by these subcommands.
 
 file on disk.  Only the active dataset read from the file
 is affected by these subcommands.
 
+PSPP tries to automatically detect the encoding of string data in the
+file.  Sometimes, however, this does not work well encoding,
+especially for files written by old versions of SPSS or PSPP.  Specify
+the ENCODING subcommand with an IANA character set name as its string
+argument to override the default.  The ENCODING subcommand is a PSPP
+extension.
+
 @cmd{GET} does not cause the data to be read, only the dictionary.  The data
 is read later, when a procedure is executed.
 
 @cmd{GET} does not cause the data to be read, only the dictionary.  The data
 is read later, when a procedure is executed.
 
index f6afa29b10098ee7614e8fa880cccaf1269b5097..834ec401f483900d5cd1cb5bfb5d3e08045e7e4a 100644 (file)
@@ -709,7 +709,7 @@ CODE:
         fh_create_file (NULL, name, fh_default_properties () );
 
  sri = xmalloc (sizeof (*sri));
         fh_create_file (NULL, name, fh_default_properties () );
 
  sri = xmalloc (sizeof (*sri));
- sri->reader = sfm_open_reader (fh, &sri->dict, &sri->opts);
+ sri->reader = sfm_open_reader (fh, NULL, &sri->dict, &sri->opts);
 
  if ( NULL == sri->reader)
  {
 
  if ( NULL == sri->reader)
  {
index 50feb6892e936f4fbf9ea54d14f90707c11acf67..1b488f208a84c78f27936f4c1b513d46864c4c0d 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2006, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2006, 2010, 2011, 2012 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
 
    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
@@ -80,9 +80,15 @@ any_reader_may_open (const char *file)
 
 /* Returns a casereader for HANDLE.  On success, returns the new
    casereader and stores the file's dictionary into *DICT.  On
 
 /* Returns a casereader for HANDLE.  On success, returns the new
    casereader and stores the file's dictionary into *DICT.  On
-   failure, returns a null pointer. */
+   failure, returns a null pointer.
+
+   Ordinarily the reader attempts to automatically detect the character
+   encoding based on the file's contents.  This isn't always possible,
+   especially for files written by old versions of SPSS or PSPP, so specifying
+   a nonnull ENCODING overrides the choice of character encoding.  */
 struct casereader *
 struct casereader *
-any_reader_open (struct file_handle *handle, struct dictionary **dict)
+any_reader_open (struct file_handle *handle, const char *encoding,
+                 struct dictionary **dict)
 {
   switch (fh_get_referent (handle))
     {
 {
   switch (fh_get_referent (handle))
     {
@@ -94,7 +100,7 @@ any_reader_open (struct file_handle *handle, struct dictionary **dict)
         if (result == IO_ERROR)
           return NULL;
         else if (result == YES)
         if (result == IO_ERROR)
           return NULL;
         else if (result == YES)
-          return sfm_open_reader (handle, dict, NULL);
+          return sfm_open_reader (handle, encoding, dict, NULL);
 
         result = try_detect (fh_get_file_name (handle), pfm_detect);
         if (result == IO_ERROR)
 
         result = try_detect (fh_get_file_name (handle), pfm_detect);
         if (result == IO_ERROR)
index e999aa33f5be28543d18c1f0b120d47ded4264d0..fb36e99ccec87af844dd94249296901d69b0d130 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2006, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2006, 2010, 2012 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
 
    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
@@ -22,7 +22,7 @@
 struct file_handle;
 struct dictionary;
 bool any_reader_may_open (const char *file_name);
 struct file_handle;
 struct dictionary;
 bool any_reader_may_open (const char *file_name);
-struct casereader *any_reader_open (struct file_handle *,
+struct casereader *any_reader_open (struct file_handle *, const char *encoding,
                                     struct dictionary **);
 
 #endif /* any-reader.h */
                                     struct dictionary **);
 
 #endif /* any-reader.h */
index 024b4ae1827994d1d6d60a7820e4535295190f5d..7e8bcf0de38d316ea6d770976caff1f86ab76018 100644 (file)
@@ -312,12 +312,17 @@ sfm_read_info_destroy (struct sfm_read_info *info)
 /* Opens the system file designated by file handle FH for reading.  Reads the
    system file's dictionary into *DICT.
 
 /* Opens the system file designated by file handle FH for reading.  Reads the
    system file's dictionary into *DICT.
 
+   Ordinarily the reader attempts to automatically detect the character
+   encoding based on the file's contents.  This isn't always possible,
+   especially for files written by old versions of SPSS or PSPP, so specifying
+   a nonnull ENCODING overrides the choice of character encoding.
+
    If INFO is non-null, then it receives additional info about the system file,
    which the caller must eventually free with sfm_read_info_destroy() when it
    is no longer needed. */
 struct casereader *
    If INFO is non-null, then it receives additional info about the system file,
    which the caller must eventually free with sfm_read_info_destroy() when it
    is no longer needed. */
 struct casereader *
-sfm_open_reader (struct file_handle *fh, struct dictionary **dictp,
-                 struct sfm_read_info *infop)
+sfm_open_reader (struct file_handle *fh, const char *volatile encoding,
+                 struct dictionary **dictp, struct sfm_read_info *infop)
 {
   struct sfm_reader *volatile r = NULL;
   struct sfm_read_info info;
 {
   struct sfm_reader *volatile r = NULL;
   struct sfm_read_info info;
@@ -454,8 +459,10 @@ sfm_open_reader (struct file_handle *fh, struct dictionary **dictp,
 
      First, figure out the correct character encoding, because this determines
      how the rest of the header data is to be interpreted. */
 
      First, figure out the correct character encoding, because this determines
      how the rest of the header data is to be interpreted. */
-  dict = dict_create (choose_encoding (r, &header, extensions[EXT_INTEGER],
-                                       extensions[EXT_ENCODING]));
+  dict = dict_create (encoding
+                      ? encoding
+                      : choose_encoding (r, &header, extensions[EXT_INTEGER],
+                                         extensions[EXT_ENCODING]));
   r->encoding = dict_get_encoding (dict);
 
   /* These records don't use variables at all. */
   r->encoding = dict_get_encoding (dict);
 
   /* These records don't use variables at all. */
index be01277235717ef14456a7333d69ef4aab29ce95..a8f16e10db5e452299bb4ffbdb7b24f8e91dd98f 100644 (file)
@@ -52,7 +52,7 @@ void sfm_read_info_destroy (struct sfm_read_info *);
 
 struct dictionary;
 struct file_handle;
 
 struct dictionary;
 struct file_handle;
-struct casereader *sfm_open_reader (struct file_handle *,
+struct casereader *sfm_open_reader (struct file_handle *, const char *encoding,
                                     struct dictionary **,
                                     struct sfm_read_info *);
 bool sfm_detect (FILE *);
                                     struct dictionary **,
                                     struct sfm_read_info *);
 bool sfm_detect (FILE *);
index cf7bc70b2229127193d77028ad9e8d0341a9a39c..5003ca2b89bf1d1b4a4fbf3b98efdec02040f8b1 100644 (file)
@@ -74,6 +74,7 @@ struct sfm_writer
 
     bool compress;             /* 1=compressed, 0=not compressed. */
     casenumber case_cnt;       /* Number of cases written so far. */
 
     bool compress;             /* 1=compressed, 0=not compressed. */
     casenumber case_cnt;       /* Number of cases written so far. */
+    uint8_t space;              /* ' ' in the file's character encoding. */
 
     /* Compression buffering.
 
 
     /* Compression buffering.
 
@@ -176,6 +177,7 @@ struct casewriter *
 sfm_open_writer (struct file_handle *fh, struct dictionary *d,
                  struct sfm_write_options opts)
 {
 sfm_open_writer (struct file_handle *fh, struct dictionary *d,
                  struct sfm_write_options opts)
 {
+  struct encoding_info encoding_info;
   struct sfm_writer *w;
   mode_t mode;
   int i;
   struct sfm_writer *w;
   mode_t mode;
   int i;
@@ -227,6 +229,9 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
       goto error;
     }
 
       goto error;
     }
 
+  get_encoding_info (&encoding_info, dict_get_encoding (d));
+  w->space = encoding_info.space[0];
+
   /* Write the file header. */
   write_header (w, d);
 
   /* Write the file header. */
   write_header (w, d);
 
@@ -712,6 +717,12 @@ write_mrsets (struct sfm_writer *w, const struct dictionary *dict,
   size_t n_mrsets;
   size_t i;
 
   size_t n_mrsets;
   size_t i;
 
+  if (is_encoding_ebcdic_compatible (encoding))
+    {
+      /* FIXME. */
+      return;
+    }
+
   n_mrsets = dict_get_n_mrsets (dict);
   if (n_mrsets == 0)
     return;
   n_mrsets = dict_get_n_mrsets (dict);
   if (n_mrsets == 0)
     return;
@@ -1251,7 +1262,7 @@ put_cmp_string (struct sfm_writer *w, const void *data, size_t size)
   assert (w->data_cnt < 8);
   assert (size <= 8);
 
   assert (w->data_cnt < 8);
   assert (size <= 8);
 
-  memset (w->data[w->data_cnt], ' ', 8);
+  memset (w->data[w->data_cnt], w->space, 8);
   memcpy (w->data[w->data_cnt], data, size);
   w->data_cnt++;
 }
   memcpy (w->data[w->data_cnt], data, size);
   w->data_cnt++;
 }
@@ -1313,7 +1324,7 @@ write_string (struct sfm_writer *w, const char *string, size_t width)
   size_t pad_bytes = width - data_bytes;
   write_bytes (w, string, data_bytes);
   while (pad_bytes-- > 0)
   size_t pad_bytes = width - data_bytes;
   write_bytes (w, string, data_bytes);
   while (pad_bytes-- > 0)
-    putc (' ', w->file);
+    putc (w->space, w->file);
 }
 
 /* Recodes null-terminated UTF-8 encoded STRING into ENCODING, and writes the
 }
 
 /* Recodes null-terminated UTF-8 encoded STRING into ENCODING, and writes the
@@ -1374,5 +1385,5 @@ static void
 write_spaces (struct sfm_writer *w, size_t n)
 {
   while (n-- > 0)
 write_spaces (struct sfm_writer *w, size_t n)
 {
   while (n-- > 0)
-    putc (' ', w->file);
+    putc (w->space, w->file);
 }
 }
index f306cad6f645c1b10ce408233e78da63fe2d4fe6..21736da8c645239dec3dadcde4cae59765d0f712 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
 
    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
@@ -227,7 +227,7 @@ combine_files (enum comb_command_type command,
           if (file->handle == NULL)
             goto error;
 
           if (file->handle == NULL)
             goto error;
 
-          file->reader = any_reader_open (file->handle, &file->dict);
+          file->reader = any_reader_open (file->handle, NULL, &file->dict);
           if (file->reader == NULL)
             goto error;
         }
           if (file->reader == NULL)
             goto error;
         }
index d32f25567a25dcfd28cf7b3dad7189cc2bee29f6..35b894a75037868ba3119b437e7a01e0bfd7b4c5 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2007, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2007, 2010, 2011, 2012 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
 
    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
@@ -73,6 +73,7 @@ parse_read_command (struct lexer *lexer, struct dataset *ds,
   struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
   struct case_map *map = NULL;
   struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
   struct case_map *map = NULL;
+  char *encoding = NULL;
 
   for (;;)
     {
 
   for (;;)
     {
@@ -87,6 +88,18 @@ parse_read_command (struct lexer *lexer, struct dataset *ds,
          if (fh == NULL)
             goto error;
        }
          if (fh == NULL)
             goto error;
        }
+      else if (command == GET_CMD && lex_match_id (lexer, "ENCODING"))
+        {
+         lex_match (lexer, T_EQUALS);
+
+          if (!lex_force_string (lexer))
+            goto error;
+
+          free (encoding);
+          encoding = ss_xstrdup (lex_tokss (lexer));
+
+          lex_get (lexer);
+        }
       else if (command == IMPORT_CMD && lex_match_id (lexer, "TYPE"))
        {
          lex_match (lexer, T_EQUALS);
       else if (command == IMPORT_CMD && lex_match_id (lexer, "TYPE"))
        {
          lex_match (lexer, T_EQUALS);
@@ -108,7 +121,7 @@ parse_read_command (struct lexer *lexer, struct dataset *ds,
       goto error;
     }
 
       goto error;
     }
 
-  reader = any_reader_open (fh, &dict);
+  reader = any_reader_open (fh, encoding, &dict);
   if (reader == NULL)
     goto error;
 
   if (reader == NULL)
     goto error;
 
@@ -130,6 +143,7 @@ parse_read_command (struct lexer *lexer, struct dataset *ds,
   dataset_set_source (ds, reader);
 
   fh_unref (fh);
   dataset_set_source (ds, reader);
 
   fh_unref (fh);
+  free (encoding);
   return CMD_SUCCESS;
 
  error:
   return CMD_SUCCESS;
 
  error:
@@ -137,5 +151,6 @@ parse_read_command (struct lexer *lexer, struct dataset *ds,
   casereader_destroy (reader);
   if (dict != NULL)
     dict_destroy (dict);
   casereader_destroy (reader);
   if (dict != NULL)
     dict_destroy (dict);
+  free (encoding);
   return CMD_CASCADING_FAILURE;
 }
   return CMD_CASCADING_FAILURE;
 }
index c2de9318ae81b13462c940aae839a580f886c2d4..8531ba56d2d257d30e3cc085ab42fafa91bca19c 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012 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
 
    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
@@ -53,7 +53,7 @@ cmd_apply_dictionary (struct lexer *lexer, struct dataset *ds)
   handle = fh_parse (lexer, FH_REF_FILE, dataset_session (ds));
   if (!handle)
     return CMD_FAILURE;
   handle = fh_parse (lexer, FH_REF_FILE, dataset_session (ds));
   if (!handle)
     return CMD_FAILURE;
-  reader = any_reader_open (handle, &dict);
+  reader = any_reader_open (handle, NULL, &dict);
   fh_unref (handle);
   if (dict == NULL)
     return CMD_FAILURE;
   fh_unref (handle);
   if (dict == NULL)
     return CMD_FAILURE;
index bb990c79d9e093aa28d292e2368faa34eaccdc13..31a685aa824276afd1d38326140bf9cac530504d 100644 (file)
@@ -81,7 +81,7 @@ cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED)
   if (!h)
     return CMD_FAILURE;
 
   if (!h)
     return CMD_FAILURE;
 
-  reader = sfm_open_reader (h, &d, &info);
+  reader = sfm_open_reader (h, NULL, &d, &info);
   if (!reader)
     {
       fh_unref (h);
   if (!reader)
     {
       fh_unref (h);
index 149ad6fb2fa4a396a60fcb4d1d11963123c180ef..9658866056f01f7911c905e1d2d4896fb0288cbe 100644 (file)
@@ -694,29 +694,37 @@ get_encoding_info (struct encoding_info *e, const char *name)
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
     "abcdefghijklmnopqrstuvwxyz{|}~");
 
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
     "abcdefghijklmnopqrstuvwxyz{|}~");
 
-  struct substring out, cr, lf;
+  struct substring out, cr, lf, space;
   bool ok;
 
   memset (e, 0, sizeof *e);
 
   cr = recode_substring_pool (name, "UTF-8", ss_cstr ("\r"), NULL);
   lf = recode_substring_pool (name, "UTF-8", ss_cstr ("\n"), NULL);
   bool ok;
 
   memset (e, 0, sizeof *e);
 
   cr = recode_substring_pool (name, "UTF-8", ss_cstr ("\r"), NULL);
   lf = recode_substring_pool (name, "UTF-8", ss_cstr ("\n"), NULL);
-  ok = cr.length >= 1 && cr.length <= MAX_UNIT && cr.length == lf.length;
+  space = recode_substring_pool (name, "UTF-8", ss_cstr (" "), NULL);
+  ok = (cr.length >= 1
+        && cr.length <= MAX_UNIT
+        && cr.length == lf.length
+        && cr.length == space.length);
   if (!ok)
     {
       fprintf (stderr, "warning: encoding `%s' is not supported.\n", name);
       ss_dealloc (&cr);
       ss_dealloc (&lf);
   if (!ok)
     {
       fprintf (stderr, "warning: encoding `%s' is not supported.\n", name);
       ss_dealloc (&cr);
       ss_dealloc (&lf);
+      ss_dealloc (&space);
       ss_alloc_substring (&cr, ss_cstr ("\r"));
       ss_alloc_substring (&lf, ss_cstr ("\n"));
       ss_alloc_substring (&cr, ss_cstr ("\r"));
       ss_alloc_substring (&lf, ss_cstr ("\n"));
+      ss_alloc_substring (&space, ss_cstr (" "));
     }
 
   e->unit = cr.length;
   memcpy (e->cr, cr.string, e->unit);
   memcpy (e->lf, lf.string, e->unit);
     }
 
   e->unit = cr.length;
   memcpy (e->cr, cr.string, e->unit);
   memcpy (e->lf, lf.string, e->unit);
+  memcpy (e->space, space.string, e->unit);
 
   ss_dealloc (&cr);
   ss_dealloc (&lf);
 
   ss_dealloc (&cr);
   ss_dealloc (&lf);
+  ss_dealloc (&space);
 
   out = recode_substring_pool ("UTF-8", name, in, NULL);
   e->is_ascii_compatible = ss_equals (in, out);
 
   out = recode_substring_pool ("UTF-8", name, in, NULL);
   e->is_ascii_compatible = ss_equals (in, out);
index 27ccce361e5ccfa8a6bf03c85967e96eaf4174ae..383ff12da53ab6437ede88f815292f4ad2aa86ab 100644 (file)
@@ -134,6 +134,7 @@ struct encoding_info
     int unit;                   /* Unit width, in bytes. */
     char cr[MAX_UNIT];          /* \r in encoding, 'unit' bytes long. */
     char lf[MAX_UNIT];          /* \n in encoding, 'unit' bytes long. */
     int unit;                   /* Unit width, in bytes. */
     char cr[MAX_UNIT];          /* \r in encoding, 'unit' bytes long. */
     char lf[MAX_UNIT];          /* \n in encoding, 'unit' bytes long. */
+    char space[MAX_UNIT];       /* ' ' in encoding, 'unit' bytes long. */
   };
 
 bool get_encoding_info (struct encoding_info *, const char *name);
   };
 
 bool get_encoding_info (struct encoding_info *, const char *name);