Revamp SAVE, XSAVE, EXPORT. Add (or at least parse) all the
authorBen Pfaff <blp@gnu.org>
Sun, 21 Aug 2005 07:21:06 +0000 (07:21 +0000)
committerBen Pfaff <blp@gnu.org>
Sun, 21 Aug 2005 07:21:06 +0000 (07:21 +0000)
subcommands that we didn't support.  Fix bug 13911.  Fix bug reported
by Adam Pierson (COMPRESSED and other subcommands didn't work on
SAVE).  Refactor all related code.

21 files changed:
doc/files.texi
src/ChangeLog
src/aggregate.c
src/case.h
src/command.def
src/command.h
src/dictionary.c
src/dictionary.h
src/expressions/helpers.c
src/expressions/helpers.h
src/get.c
src/lexer.c
src/lexer.h
src/pfm-read.h
src/pfm-write.c
src/pfm-write.h
src/sfm-write.c
src/sfm-write.h
tests/ChangeLog
tests/command/import-export.sh
tests/command/sysfiles-old.sh

index 6f723213ef52447f957f414a37981dd3aaac0fd2..ea1ff885f471af869ba2aea5aea63601a26cd7da 100644 (file)
@@ -12,10 +12,11 @@ portable files.
 * MATCH FILES::                 Merge system files.
 * SAVE::                        Write to a system file.
 * SYSFILE INFO::                Display system file dictionary.
 * MATCH FILES::                 Merge system files.
 * SAVE::                        Write to a system file.
 * SYSFILE INFO::                Display system file dictionary.
-* XSAVE::                       Write to a system file, as a transform.
+* XEXPORT::                     Write to a portable file, as a transformation.
+* XSAVE::                       Write to a system file, as a transformation.
 @end menu
 
 @end menu
 
-@node APPLY DICTIONARY, EXPORT, System and Portable Files, System and Portable Files
+@node APPLY DICTIONARY
 @section APPLY DICTIONARY
 @vindex APPLY DICTIONARY
 
 @section APPLY DICTIONARY
 @vindex APPLY DICTIONARY
 
@@ -61,21 +62,38 @@ the active file weighting variable.
 active
 file.  The system file is not modified.
 
 active
 file.  The system file is not modified.
 
-@node EXPORT, GET, APPLY DICTIONARY, System and Portable Files
+@node EXPORT
 @section EXPORT
 @vindex EXPORT
 
 @display
 EXPORT
         /OUTFILE='filename'
 @section EXPORT
 @vindex EXPORT
 
 @display
 EXPORT
         /OUTFILE='filename'
+        /UNSELECTED=@{RETAIN,DELETE@}
+        /DIGITS=n
         /DROP=var_list
         /KEEP=var_list
         /RENAME=(src_names=target_names)@dots{}
         /DROP=var_list
         /KEEP=var_list
         /RENAME=(src_names=target_names)@dots{}
+        /TYPE=@{COMM,TAPE@}
+        /MAP
 @end display
 
 The @cmd{EXPORT} procedure writes the active file dictionary and data to a
 specified portable file.
 
 @end display
 
 The @cmd{EXPORT} procedure writes the active file dictionary and data to a
 specified portable file.
 
+By default, cases excluded with FILTER are written to the portable
+file.  These can be excluded by specifying DELETE on the UNSELECTED
+subcommand.  Specifying RETAIN makes the default explicit.
+
+Portable files express real numbers in base 30.  Integers are always
+expressed to the maximum precision needed to make them exact.
+Non-integers are, by default, expressed to the machine's maximum
+natural precision (approximately 15 decimal digits on many machines).
+If many numbers require this many digits, the portable file may
+significantly increase in size.  As an alternative, the DIGITS
+subcommand may be used to specify the number of decimal digits of
+precision to write.  DIGITS applies only to non-integers.
+
 The OUTFILE subcommand, which is the only required subcommand, specifies
 the portable file to be written as a file name string or a file handle
 (@pxref{FILE HANDLE}).
 The OUTFILE subcommand, which is the only required subcommand, specifies
 the portable file to be written as a file name string or a file handle
 (@pxref{FILE HANDLE}).
@@ -83,9 +101,14 @@ the portable file to be written as a file name string or a file handle
 DROP, KEEP, and RENAME follow the same format as the SAVE procedure
 (@pxref{SAVE}).
 
 DROP, KEEP, and RENAME follow the same format as the SAVE procedure
 (@pxref{SAVE}).
 
+The TYPE subcommand specifies the character set for use in the
+portable file.  Its value is currently not used.
+
+The MAP subcommand is currently ignored.
+
 @cmd{EXPORT} is a procedure.  It causes the active file to be read.
 
 @cmd{EXPORT} is a procedure.  It causes the active file to be read.
 
-@node GET, IMPORT, EXPORT, System and Portable Files
+@node GET
 @section GET
 @vindex GET
 
 @section GET
 @vindex GET
 
@@ -130,7 +153,7 @@ is affected by these subcommands.
 @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.
 
-@node IMPORT, MATCH FILES, GET, System and Portable Files
+@node IMPORT
 @section IMPORT
 @vindex IMPORT
 
 @section IMPORT
 @vindex IMPORT
 
@@ -158,7 +181,7 @@ DROP, KEEP, and RENAME follow the syntax used by @cmd{GET} (@pxref{GET}).
 @cmd{IMPORT} does not cause the data to be read, only the dictionary.  The
 data is read later, when a procedure is executed.
 
 @cmd{IMPORT} does not cause the data to be read, only the dictionary.  The
 data is read later, when a procedure is executed.
 
-@node MATCH FILES, SAVE, IMPORT, System and Portable Files
+@node MATCH FILES
 @section MATCH FILES
 @vindex MATCH FILES
 
 @section MATCH FILES
 @vindex MATCH FILES
 
@@ -224,18 +247,22 @@ FIRST, LAST, and MAP are currently ignored.
 @cmd{MATCH FILES} may not be specified following @cmd{TEMPORARY}
 (@pxref{TEMPORARY}) if the active file is used as an input source.
 
 @cmd{MATCH FILES} may not be specified following @cmd{TEMPORARY}
 (@pxref{TEMPORARY}) if the active file is used as an input source.
 
-@node SAVE, SYSFILE INFO, MATCH FILES, System and Portable Files
+@node SAVE
 @section SAVE
 @vindex SAVE
 
 @display
 SAVE
         /OUTFILE='filename'
 @section SAVE
 @vindex SAVE
 
 @display
 SAVE
         /OUTFILE='filename'
+        /UNSELECTED=@{RETAIN,DELETE@}
         /@{COMPRESSED,UNCOMPRESSED@}
         /@{COMPRESSED,UNCOMPRESSED@}
+        /PERMISSIONS=@{WRITEABLE,READONLY@}
         /DROP=var_list
         /KEEP=var_list
         /VERSION=version
         /RENAME=(src_names=target_names)@dots{}
         /DROP=var_list
         /KEEP=var_list
         /VERSION=version
         /RENAME=(src_names=target_names)@dots{}
+        /NAMES
+        /MAP
 @end display
 
 The @cmd{SAVE} procedure causes the dictionary and data in the active
 @end display
 
 The @cmd{SAVE} procedure causes the dictionary and data in the active
@@ -246,10 +273,18 @@ OUTFILE is the only required subcommand.  Specify the system
 file to be written as a string file name or a file handle (@pxref{FILE
 HANDLE}).
 
 file to be written as a string file name or a file handle (@pxref{FILE
 HANDLE}).
 
+By default, cases excluded with FILTER are written to the system file.
+These can be excluded by specifying DELETE on the UNSELECTED
+subcommand.  Specifying RETAIN makes the default explicit.
+
 The COMPRESS and UNCOMPRESS subcommand determine whether the saved
 system file is compressed.  By default, system files are compressed.
 This default can be changed with the SET command (@pxref{SET}).
 
 The COMPRESS and UNCOMPRESS subcommand determine whether the saved
 system file is compressed.  By default, system files are compressed.
 This default can be changed with the SET command (@pxref{SET}).
 
+The PERMISSIONS subcommand specifies permissions for the new system
+file.  WRITEABLE, the default, creates the file with read and write
+permission.  READONLY creates the file for read-only access.
+
 By default, all the variables in the active file dictionary are written
 to the system file.  The DROP subcommand can be used to specify a list
 of variables not to be written.  In contrast, KEEP specifies variables
 By default, all the variables in the active file dictionary are written
 to the system file.  The DROP subcommand can be used to specify a list
 of variables not to be written.  In contrast, KEEP specifies variables
@@ -274,14 +309,15 @@ the active file.  DROP, KEEP, and RENAME only affect the system file
 written to disk.
 
 The VERSION subcommand specifies the version of the file format. Valid
 written to disk.
 
 The VERSION subcommand specifies the version of the file format. Valid
-versions are '3' and '3x'.  Version 3x system files are identical to
-version 3 files, except that variable names greater than 8 bytes will
-be truncated.  The default version is 3.  The VERSION subcommand is
-optional.  There is no need ever to use it.
+versions are 2 and 3.  The default version is 3.  In version 2 system
+files, variable names longer than 8 bytes will be truncated.  The two
+versions are otherwise identical.
+
+The NAMES and MAP subcommands are currently ignored.
 
 @cmd{SAVE} causes the data to be read.  It is a procedure.
 
 
 @cmd{SAVE} causes the data to be read.  It is a procedure.
 
-@node SYSFILE INFO, XSAVE, SAVE, System and Portable Files
+@node SYSFILE INFO
 @section SYSFILE INFO
 @vindex SYSFILE INFO
 
 @section SYSFILE INFO
 @vindex SYSFILE INFO
 
@@ -297,7 +333,40 @@ a system file and displays information on its dictionary.
 
 @cmd{SYSFILE INFO} does not affect the current active file.
 
 
 @cmd{SYSFILE INFO} does not affect the current active file.
 
-@node XSAVE,  , SYSFILE INFO, System and Portable Files
+@node XEXPORT
+@section XEXPORT
+@vindex XEXPORT
+
+@display
+EXPORT
+        /OUTFILE='filename'
+        /DIGITS=n
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+        /TYPE=@{COMM,TAPE@}
+        /MAP
+@end display
+
+The @cmd{EXPORT} transformation writes the active file dictionary and
+data to a specified portable file.
+
+This transformation is a PSPP extension.
+
+It is similar to the @cmd{EXPORT} procedure, with two differences:
+
+@itemize
+@item
+@cmd{XEXPORT} is a transformation, not a procedure.  It is executed when
+the data is read by a procedure or procedure-like command.
+
+@item
+@cmd{XEXPORT} does not support the UNSELECTED subcommand.
+@end itemize
+
+@xref{EXPORT}, for more information.
+
+@node XSAVE,  , XEXPORT, System and Portable Files
 @section XSAVE
 @vindex XSAVE
 
 @section XSAVE
 @vindex XSAVE
 
@@ -305,17 +374,27 @@ a system file and displays information on its dictionary.
 XSAVE
         /OUTFILE='filename'
         /@{COMPRESSED,UNCOMPRESSED@}
 XSAVE
         /OUTFILE='filename'
         /@{COMPRESSED,UNCOMPRESSED@}
+        /PERMISSIONS=@{WRITEABLE,READONLY@}
         /DROP=var_list
         /KEEP=var_list
         /DROP=var_list
         /KEEP=var_list
+        /VERSION=version
         /RENAME=(src_names=target_names)@dots{}
         /RENAME=(src_names=target_names)@dots{}
+        /NAMES
+        /MAP
 @end display
 
 The @cmd{XSAVE} transformation writes the active file dictionary and
 @end display
 
 The @cmd{XSAVE} transformation writes the active file dictionary and
-data to a
-system file stored on disk.
+data to a system file stored on disk.  It is similar to the @cmd{SAVE}
+procedure, with two differences:
+
+@itemize
+@item
+@cmd{XSAVE} is a transformation, not a procedure.  It is executed when
+the data is read by a procedure or procedure-like command.
+
+@item
+@cmd{XSAVE} does not support the UNSELECTED subcommand.
+@end itemize
 
 
-@cmd{XSAVE} is a transformation, not a procedure.  It is executed when the
-data is read by a procedure or procedure-like command.  In all other
-respects, @cmd{XSAVE} is identical to @cmd{SAVE}.  @xref{SAVE}, for
-more information on syntax and usage.
+@xref{SAVE}, for more information.
 @setfilename ignored
 @setfilename ignored
index fb536fd127ba24816c678c7268cec9ff9f3d4cd7..dc2c605edd292403bad594e0b93e56d7b374a82f 100644 (file)
@@ -1,3 +1,74 @@
+Sun Aug 21 00:12:24 2005  Ben Pfaff  <blp@gnu.org>
+
+       * lexer.c: (lex_sbc_only_once) New function.
+       (lex_sbc_missing) New function.
+
+Sun Aug 21 00:00:47 2005  Ben Pfaff  <blp@gnu.org>
+
+       * case.h: (case_str) Make it return `unsigned char'.
+
+Sat Aug 20 23:56:14 2005  Ben Pfaff  <blp@gnu.org>
+
+       Revamp SAVE, XSAVE, EXPORT.  Add (or at least parse) all the
+       subcommands that we didn't support.  Fix bug 13911.  Fix bug
+       reported by Adam Pierson (COMPRESSED and other subcommands didn't
+       work on SAVE).  Refactor all related code.
+       
+       * command.def: Add XEXPORT command.
+
+       * dictionary.c: (dict_delete_scratch_vars) New function.
+
+       * get.c: (cmd_get) Fix parsing.
+       (struct save_trns) Removed.
+       (cmd_save_internal) Removed.
+       (cmd_save) Removed.
+       (do_write_case) Removed.
+       (save_write_case_func) Removed.
+       (save_trns_proc) Removed.
+       (save_trns_free) Removed.
+       (trim_dictionary) Removed.
+       (struct export_proc) Removed.
+       (cmd_export) Rewrote.
+       (export_write_case_func) Removed.
+       (export_proc_free) Removed.
+       (enum writer_type) New enum.
+       (enum command_type) New enum.
+       (struct any_writer) New struct.
+       (any_writer_destroy) New function.
+       (parse_write_command) New function.
+       (any_writer_write_case) New function.
+       (parse_output_proc) New function.
+       (output_proc) New function.
+       (cmd_save) Rewrote.
+       (cmd_xsave) Rewrote.
+       (struct output_trns) New struct.
+       (parse_output_trns) New function.
+       (output_trns_proc) New function.
+       (output_trns_free) New function.
+       (cmd_xsave) Rewrote.
+       (cmd_xexport) New function.
+       (parse_dict_trim) New function.
+       (struct mtf_proc) Change `by_cnt' member type to `int'.
+       (cmd_import) Rewrote.
+
+       * pfm-write.c: (struct pfm_writer) Add `digits' member.
+       (pfm_writer_default_options) New function.
+       (pfm_open_writer) Add `opts' argument and handle options.
+       (write_float) Write only as many digits as `digits' member says.
+       (format_trig_double) Limit base-10 precision to LDBL_DIG.
+
+       * pfm-write.h: (enum pfm_type) Moved here from pfm-read.h.
+       (struct pfm_write_options) New struct.
+
+       * sfm-write.c: (sfm_writer_default_options) New function.
+       (sfm_open_writer) Remove `compress', `omit_long_names' args.  Add
+       `opts' argument.  Implement options.
+
+       * sfm-write.h: (struct sfm_write_options) New struct.
+
+       * expressions/helpers.c: (copy_string) Make `old' arg `unsigned
+       char *' instead of `char *'.
+       
 Sat Aug  6 21:29:15 2005  Ben Pfaff  <blp@gnu.org>
 
        * factor_stats.c: Needed <config.h> included earlier.
 Sat Aug  6 21:29:15 2005  Ben Pfaff  <blp@gnu.org>
 
        * factor_stats.c: Needed <config.h> included earlier.
index bc554b97bf588845ca0a2fa4e1e794cef200dcbc..5979204d08c0c407a078f2ca1ad367665ff0ed67 100644 (file)
@@ -278,7 +278,8 @@ cmd_aggregate (void)
     }
   else
     {
     }
   else
     {
-      agr.writer = sfm_open_writer (out_file, agr.dict, get_scompression (), 0);
+      agr.writer = sfm_open_writer (out_file, agr.dict,
+                                    sfm_writer_default_options ());
       if (agr.writer == NULL)
         goto error;
       
       if (agr.writer == NULL)
         goto error;
       
index cf99e0226e4bb1883050f7d3be2b7611763cd58b..541a3e2ebdc9c2c677a67fdfcc301e2e4f9f61ce 100644 (file)
@@ -73,7 +73,7 @@ CASE_INLINE void case_from_values (struct ccase *,
 
 CASE_INLINE const union value *case_data (const struct ccase *, size_t idx);
 CASE_INLINE double case_num (const struct ccase *, size_t idx);
 
 CASE_INLINE const union value *case_data (const struct ccase *, size_t idx);
 CASE_INLINE double case_num (const struct ccase *, size_t idx);
-CASE_INLINE const char *case_str (const struct ccase *, size_t idx);
+CASE_INLINE const unsigned char *case_str (const struct ccase *, size_t idx);
 
 CASE_INLINE union value *case_data_rw (struct ccase *, size_t idx);
 
 
 CASE_INLINE union value *case_data_rw (struct ccase *, size_t idx);
 
@@ -170,7 +170,7 @@ case_num (const struct ccase *c, size_t idx)
   return c->case_data->values[idx].f;
 }
 
   return c->case_data->values[idx].f;
 }
 
-static inline const char *
+static inline const unsigned char *
 case_str (const struct ccase *c, size_t idx)
 {
   return c->case_data->values[idx].s;
 case_str (const struct ccase *c, size_t idx)
 {
   return c->case_data->values[idx].s;
index fea29e934adbb4b6bc57d03778fc25098f3be24f..fa821b413844d3712e6866398b84875a6b93ab3a 100644 (file)
@@ -188,4 +188,5 @@ UNIMPL ("VERIFY",                 ERRO, ERRO, ERRO, ERRO, "Report time series")
 DEFCMD ("WEIGHT",                 ERRO, INPU, TRAN, TRAN, cmd_weight)
 DEFCMD ("WRITE",                  ERRO, INPU, TRAN, TRAN, cmd_write)
 DEFCMD ("WRITE FORMATS",          ERRO, INPU, TRAN, TRAN, cmd_write_formats)
 DEFCMD ("WEIGHT",                 ERRO, INPU, TRAN, TRAN, cmd_weight)
 DEFCMD ("WRITE",                  ERRO, INPU, TRAN, TRAN, cmd_write)
 DEFCMD ("WRITE FORMATS",          ERRO, INPU, TRAN, TRAN, cmd_write_formats)
+DEFCMD ("XEXPORT",                ERRO, INPU, TRAN, TRAN, cmd_xexport)
 DEFCMD ("XSAVE",                  ERRO, INPU, TRAN, TRAN, cmd_xsave)
 DEFCMD ("XSAVE",                  ERRO, INPU, TRAN, TRAN, cmd_xsave)
index 43478ef4cacf2d1af06fbe3fc36052ef2ae5e046..a62f8d60b78917a9b2c1b70943b4a5c978deab15 100644 (file)
@@ -43,9 +43,7 @@ enum
 extern int pgm_state;
 extern const char *cur_proc;
 
 extern int pgm_state;
 extern const char *cur_proc;
 
-
-char *  pspp_completion_function (const char *text,   int state);
-
+char *pspp_completion_function (const char *text,   int state);
 
 int cmd_parse (void);
 
 
 int cmd_parse (void);
 
index 3f1177d35f4f3f54ca827c47a68d659b4c012fe3..c63b2e1bcf43a0ab48866da22c4ecb9e513b2224 100644 (file)
@@ -491,6 +491,23 @@ dict_delete_vars (struct dictionary *d,
     dict_delete_var (d, *vars++);
 }
 
     dict_delete_var (d, *vars++);
 }
 
+/* Deletes scratch variables from dictionary D. */
+void
+dict_delete_scratch_vars (struct dictionary *d)
+{
+  int i;
+
+  /* FIXME: this can be done in O(count) time, but this algorithm
+     is O(count**2). */
+  assert (d != NULL);
+
+  for (i = 0; i < d->var_cnt; )
+    if (dict_class_from_id (d->var[i]->name) == DC_SCRATCH)
+      dict_delete_var (d, d->var[i]);
+    else
+      i++;
+}
+
 /* Moves V to 0-based position IDX in D.  Other variables in D,
    if any, retain their relative positions.  Runs in time linear
    in the distance moved. */
 /* Moves V to 0-based position IDX in D.  Other variables in D,
    if any, retain their relative positions.  Runs in time linear
    in the distance moved. */
index 06e190a3db1ab7d3f88074bd75132ba887820f3f..e923d52dc252d3a25debe74007e083a463c36f75 100644 (file)
@@ -54,6 +54,7 @@ int dict_contains_var (const struct dictionary *, const struct variable *);
 void dict_delete_var (struct dictionary *, struct variable *);
 void dict_delete_vars (struct dictionary *,
                        struct variable *const *, size_t count);
 void dict_delete_var (struct dictionary *, struct variable *);
 void dict_delete_vars (struct dictionary *,
                        struct variable *const *, size_t count);
+void dict_delete_scratch_vars (struct dictionary *);
 void dict_reorder_var (struct dictionary *d, struct variable *v,
                        size_t new_index);
 void dict_reorder_vars (struct dictionary *,
 void dict_reorder_var (struct dictionary *d, struct variable *v,
                        size_t new_index);
 void dict_reorder_vars (struct dictionary *,
index b4534c8111719e8e1f9de3ac73d3b9a3cf66dffd..25fe143728ca2a48444452de3c0a54ef216fbe09 100644 (file)
@@ -158,7 +158,7 @@ alloc_string (struct expression *e, size_t length)
 }
 
 struct fixed_string
 }
 
 struct fixed_string
-copy_string (struct expression *e, const char *old, size_t length) 
+copy_string (struct expression *e, const unsigned char *old, size_t length) 
 {
   struct fixed_string s = alloc_string (e, length);
   memcpy (s.string, old, length);
 {
   struct fixed_string s = alloc_string (e, length);
   memcpy (s.string, old, length);
index 4011d843def35d74877cabcaa0db53596af10720..0d6082a51a5c47897db50d7a752efd087a5aa3d5 100644 (file)
@@ -52,7 +52,7 @@ double expr_yrmoda (double year, double month, double day);
 
 struct fixed_string alloc_string (struct expression *, size_t length);
 struct fixed_string copy_string (struct expression *,
 
 struct fixed_string alloc_string (struct expression *, size_t length);
 struct fixed_string copy_string (struct expression *,
-                                 const char *, size_t length);
+                                 const unsigned char *, size_t length);
 
 static inline bool
 is_valid (double d) 
 
 static inline bool
 is_valid (double d) 
index f484d120fc756dda1d700f4aa583c5cc470c7a4e..debc85b71c61cf10c0819a521e9a48177c11319b 100644 (file)
--- a/src/get.c
+++ b/src/get.c
@@ -60,8 +60,7 @@ enum operation
     OP_EXPORT   /* EXPORT. */
   };
 
     OP_EXPORT   /* EXPORT. */
   };
 
-static bool trim_dictionary (struct dictionary *,
-                             enum operation, int *compress);
+static bool parse_dict_trim (struct dictionary *);
 \f
 /* GET input program. */
 struct get_pgm 
 \f
 /* GET input program. */
 struct get_pgm 
@@ -101,8 +100,14 @@ cmd_get (void)
   case_create (&pgm->bounce, dict_get_next_value_idx (dict));
 
   start_case_map (dict);
   case_create (&pgm->bounce, dict_get_next_value_idx (dict));
 
   start_case_map (dict);
-  if (!trim_dictionary (dict, OP_READ, NULL))
-    goto error;
+  while (lex_match ('/'))
+    if (!parse_dict_trim (dict))
+      goto error;
+
+  if (!lex_end_of_command ())
+    return false;
+
+  dict_compact_values (dict);
   pgm->map = finish_case_map (dict);
 
   dict_destroy (default_dict);
   pgm->map = finish_case_map (dict);
 
   dict_destroy (default_dict);
@@ -114,7 +119,7 @@ cmd_get (void)
 
  error:
   get_pgm_free (pgm);
 
  error:
   get_pgm_free (pgm);
-  if (dict != NULL)
+  if (dict != NULL) 
     dict_destroy (dict);
   return CMD_FAILURE;
 }
     dict_destroy (dict);
   return CMD_FAILURE;
 }
@@ -175,186 +180,352 @@ const struct case_source_class get_source_class =
     get_source_destroy,
   };
 \f
     get_source_destroy,
   };
 \f
-/* XSAVE transformation and SAVE procedure. */
-struct save_trns
+/* Type of output file. */
+enum writer_type
   {
   {
-    struct trns_header h;
-    struct sfm_writer *writer;  /* System file writer. */
-    struct case_map *map;       /* Map from active file to system file dict. */
-    struct ccase bounce;        /* Bounce buffer. */
+    SYSFILE_WRITER,     /* System file. */
+    PORFILE_WRITER      /* Portable file. */
+  };
+
+/* Type of a command. */
+enum command_type 
+  {
+    XFORM_CMD,          /* Transformation. */
+    PROC_CMD            /* Procedure. */
   };
 
   };
 
-static int save_write_case_func (struct ccase *, void *);
-static trns_proc_func save_trns_proc;
-static trns_free_func save_trns_free;
+/* Portable or system file writer plus a case map. */
+struct any_writer
+  {
+    enum writer_type writer_type;
+    void *writer;
+    struct case_map *map;       /* Map to output file dictionary
+                                   (null pointer for identity mapping). */
+    struct ccase bounce;        /* Bounce buffer for mapping (if needed). */
+  };
 
 
-/* Parses the SAVE or XSAVE command
-   and returns the parsed transformation. */
-static struct save_trns *
-cmd_save_internal (void)
+/* Destroys AW. */
+static void
+any_writer_destroy (struct any_writer *aw)
 {
 {
-  struct file_handle *fh = NULL;
-  struct dictionary *dict = NULL;
-  struct save_trns *t = NULL;
-  int compress = get_scompression ();
-  const int default_version = 3;
-  int version = default_version;
-  short no_name_table = 0;
-
-  t = xmalloc (sizeof *t);
-  t->h.proc = save_trns_proc;
-  t->h.free = save_trns_free;
-  t->writer = NULL;
-  t->map = NULL;
-  case_nullify (&t->bounce);
-  
+  if (aw != NULL) 
+    {
+      switch (aw->writer_type) 
+        {
+        case PORFILE_WRITER:
+          pfm_close_writer (aw->writer);
+          break;
+        case SYSFILE_WRITER:
+          sfm_close_writer (aw->writer);
+          break;
+        }
+      destroy_case_map (aw->map);
+      case_destroy (&aw->bounce);
+      free (aw);
+    }
+}
+
+/* Parses SAVE or XSAVE or EXPORT or XEXPORT command.
+   WRITER_TYPE identifies the type of file to write,
+   and COMMAND_TYPE identifies the type of command.
+
+   On success, returns a writer.
+   For procedures only, sets *RETAIN_UNSELECTED to true if cases
+   that would otherwise be excluded by FILTER or USE should be
+   included.
+
+   On failure, returns a null pointer. */
+static struct any_writer *
+parse_write_command (enum writer_type writer_type,
+                     enum command_type command_type,
+                     bool *retain_unselected)
+{
+  /* Common data. */
+  struct file_handle *handle; /* Output file. */
+  struct dictionary *dict;    /* Dictionary for output file. */
+  struct any_writer *aw;      /* Writer. */  
+
+  /* Common options. */
+  bool print_map;             /* Print map?  TODO. */
+  bool print_short_names;     /* Print long-to-short name map.  TODO. */
+  struct sfm_write_options sysfile_opts;
+  struct pfm_write_options porfile_opts;
+
+  assert (writer_type == SYSFILE_WRITER || writer_type == PORFILE_WRITER);
+  assert (command_type == XFORM_CMD || command_type == PROC_CMD);
+  assert ((retain_unselected != NULL) == (command_type == PROC_CMD));
+
+  if (command_type == PROC_CMD)
+    *retain_unselected = true;
 
 
-  /* Read most of the subcommands. */
+  handle = NULL;
+  dict = dict_clone (default_dict);
+  aw = xmalloc (sizeof *aw);
+  aw->writer_type = writer_type;
+  aw->writer = NULL;
+  aw->map = NULL;
+  case_nullify (&aw->bounce);
+  print_map = false;
+  print_short_names = false;
+  sysfile_opts = sfm_writer_default_options ();
+  porfile_opts = pfm_writer_default_options ();
+
+  start_case_map (dict);
+  dict_delete_scratch_vars (dict);
+
+  lex_match ('/');
   for (;;)
     {
   for (;;)
     {
-      if (lex_match_id ("VERSION"))
+      if (lex_match_id ("OUTFILE"))
        {
        {
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
          lex_match ('=');
          lex_match ('=');
-         if (lex_force_int ()) 
-           {
-             version = lex_integer ();
-              lex_get ();
-             
-             if (lex_match_id ("X")) 
-                no_name_table = 1;
-           }
+      
+         handle = fh_parse ();
+         if (handle == NULL)
+           goto error;
        }
        }
-      else if (lex_match_id ("OUTFILE"))
+      else if (lex_match_id ("NAMES"))
+        print_short_names = true;
+      else if (lex_match_id ("PERMISSIONS")) 
+        {
+          bool cw;
+          
+          lex_match ('=');
+          if (lex_match_id ("READONLY"))
+            cw = false;
+          else if (lex_match_id ("WRITEABLE"))
+            cw = true;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "READONLY", "WRITEABLE");
+              goto error;
+            }
+          sysfile_opts.create_writeable = porfile_opts.create_writeable = cw;
+        }
+      else if (command_type == PROC_CMD && lex_match_id ("UNSELECTED")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("RETAIN"))
+            *retain_unselected = true;
+          else if (lex_match_id ("DELETE"))
+            *retain_unselected = false;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "RETAIN", "DELETE");
+              goto error;
+            }
+        }
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("COMPRESSED"))
+       sysfile_opts.compress = true;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("UNCOMPRESSED"))
+       sysfile_opts.compress = false;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("VERSION"))
        {
          lex_match ('=');
        {
          lex_match ('=');
-      
-         fh = fh_parse ();
-         if (fh == NULL)
-           goto error;
-
+         if (!lex_force_int ())
+            goto error;
+          sysfile_opts.version = lex_integer ();
+          lex_get ();
        }
        }
-      if ( ! lex_match('/')  ) 
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("TYPE")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("COMMUNICATIONS"))
+            porfile_opts.type = PFM_COMM;
+          else if (lex_match_id ("TAPE"))
+            porfile_opts.type = PFM_TAPE;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "COMM", "TAPE");
+              goto error;
+            }
+        }
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("DIGITS")) 
+        {
+          lex_match ('=');
+          if (!lex_force_int ())
+            goto error;
+          porfile_opts.digits = lex_integer ();
+          lex_get ();
+        }
+      else if (!parse_dict_trim (dict))
+        goto error;
+      
+      if (!lex_match ('/'))
        break;
        break;
-
     }
     }
+  if (lex_end_of_command () != CMD_SUCCESS)
+    goto error;
 
 
-  if (token != '.')
+  if (handle == NULL) 
     {
     {
-      lex_error (_("expecting end of command"));
+      lex_sbc_missing ("OUTFILE");
       goto error;
     }
 
       goto error;
     }
 
-  if ( fh == NULL ) 
-    {
-      msg ( ME, _("The required %s subcommand was not present"), "OUTFILE");
-      goto error;
-    }
+  dict_compact_values (dict);
+  aw->map = finish_case_map (dict);
+  if (aw->map != NULL)
+    case_create (&aw->bounce, dict_get_next_value_idx (dict));
 
 
-  if ( version != default_version )
+  switch (writer_type) 
     {
     {
-      msg (MW, _("Unsupported sysfile version: %d. Using version %d instead."),
-          version, default_version);
-
-      version = default_version;
+    case SYSFILE_WRITER:
+      aw->writer = sfm_open_writer (handle, dict, sysfile_opts);
+      break;
+    case PORFILE_WRITER:
+      aw->writer = pfm_open_writer (handle, dict, porfile_opts);
+      break;
     }
     }
-
-  dict = dict_clone (default_dict);
-  start_case_map (dict);
-  if (!trim_dictionary (dict, OP_SAVE, &compress))
-    goto error;
-  t->map = finish_case_map (dict);
-  if (t->map != NULL)
-    case_create (&t->bounce, dict_get_next_value_idx (dict));
-
-  t->writer = sfm_open_writer (fh, dict, compress, no_name_table);
-  if (t->writer == NULL)
-    goto error;
-
-  dict_destroy (dict);
-
-  return t;
+  
+  return aw;
 
  error:
 
  error:
-  assert (t != NULL);
+  any_writer_destroy (aw);
   dict_destroy (dict);
   dict_destroy (dict);
-  save_trns_free (&t->h);
   return NULL;
 }
 
   return NULL;
 }
 
-/* Parses and performs the SAVE procedure. */
-int
-cmd_save (void)
+/* Writes case C to writer AW. */
+static void
+any_writer_write_case (struct any_writer *aw, struct ccase *c) 
 {
 {
-  struct save_trns *t = cmd_save_internal ();
-  if (t != NULL) 
+  if (aw->map != NULL) 
     {
     {
-      procedure (save_write_case_func, t);
-      save_trns_free (&t->h);
-      free(t);
-      return CMD_SUCCESS;
+      map_case (aw->map, c, &aw->bounce);
+      c = &aw->bounce; 
     }
     }
-  else
+  
+  switch (aw->writer_type) 
+    {
+    case SYSFILE_WRITER:
+      sfm_write_case (aw->writer, c);
+      break;
+    case PORFILE_WRITER:
+      pfm_write_case (aw->writer, c);
+      break;
+    }
+}
+\f
+/* SAVE and EXPORT. */
+
+static int output_proc (struct ccase *, void *);
+
+/* Parses and performs the SAVE or EXPORT procedure. */
+static int
+parse_output_proc (enum writer_type writer_type)
+{
+  bool retain_unselected;
+  struct variable *saved_filter_variable;
+  struct any_writer *aw;
+
+  aw = parse_write_command (writer_type, PROC_CMD, &retain_unselected);
+  if (aw == NULL) 
     return CMD_FAILURE;
     return CMD_FAILURE;
+
+  saved_filter_variable = dict_get_filter (default_dict);
+  if (retain_unselected) 
+    dict_set_filter (default_dict, NULL);
+  procedure (output_proc, aw);
+  dict_set_filter (default_dict, saved_filter_variable);
+
+  any_writer_destroy (aw);
+  return CMD_SUCCESS;
+}
+
+/* Writes case C to file. */
+static int
+output_proc (struct ccase *c, void *aw_) 
+{
+  struct any_writer *aw = aw_;
+  any_writer_write_case (aw, c);
+  return 0;
 }
 
 }
 
-/* Parses the XSAVE transformation command. */
 int
 int
-cmd_xsave (void)
+cmd_save (void) 
 {
 {
-  struct save_trns *t = cmd_save_internal ();
-  if (t != NULL) 
-    {
-      add_transformation (&t->h);
-      return CMD_SUCCESS; 
-    }
-  else
-    return CMD_FAILURE;
+  return parse_output_proc (SYSFILE_WRITER);
 }
 
 }
 
-/* Writes the given C to the file specified by T. */
-static void
-do_write_case (struct save_trns *t, struct ccase *c) 
+int
+cmd_export (void) 
 {
 {
-  if (t->map == NULL)
-    sfm_write_case (t->writer, c);
-  else 
-    {
-      map_case (t->map, c, &t->bounce);
-      sfm_write_case (t->writer, &t->bounce);
-    }
+  return parse_output_proc (PORFILE_WRITER);
 }
 }
+\f
+/* XSAVE and XEXPORT. */
+
+/* Transformation. */
+struct output_trns 
+  {
+    struct trns_header h;       /* Header. */
+    struct any_writer *aw;      /* Writer. */
+  };
+
+static trns_proc_func output_trns_proc;
+static trns_free_func output_trns_free;
 
 
-/* Writes case C to the system file specified on SAVE. */
+/* Parses the XSAVE or XEXPORT transformation command. */
 static int
 static int
-save_write_case_func (struct ccase *c, void *aux UNUSED)
+parse_output_trns (enum writer_type writer_type) 
 {
 {
-  do_write_case (aux, c);
-  return 1;
+  struct output_trns *t = xmalloc (sizeof *t);
+  t->h.proc = output_trns_proc;
+  t->h.free = output_trns_free;
+  t->aw = parse_write_command (writer_type, XFORM_CMD, NULL);
+  if (t->aw == NULL) 
+    {
+      free (t);
+      return CMD_FAILURE;
+    }
+
+  add_transformation (&t->h);
+  return CMD_SUCCESS;
 }
 
 }
 
-/* Writes case C to the system file specified on XSAVE. */
+/* Writes case C to the system file specified on XSAVE or XEXPORT. */
 static int
 static int
-save_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED)
+output_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED)
 {
 {
-  struct save_trns *t = (struct save_trns *) h;
-  do_write_case (t, c);
+  struct output_trns *t = (struct output_trns *) h;
+  any_writer_write_case (t->aw, c);
   return -1;
 }
 
   return -1;
 }
 
-/* Frees a SAVE transformation. */
+/* Frees an XSAVE or XEXPORT transformation. */
 static void
 static void
-save_trns_free (struct trns_header *t_)
+output_trns_free (struct trns_header *h)
 {
 {
-  struct save_trns *t = (struct save_trns *) t_;
+  struct output_trns *t = (struct output_trns *) h;
 
 
-  if (t != NULL) 
+  if (t != NULL)
     {
     {
-      sfm_close_writer (t->writer);
-      destroy_case_map (t->map);
-      case_destroy (&t->bounce);
+      any_writer_destroy (t->aw);
+      free (t);
     }
 }
 
     }
 }
 
+/* XSAVE command. */
+int
+cmd_xsave (void) 
+{
+  return parse_output_trns (SYSFILE_WRITER);
+}
+
+/* XEXPORT command. */
+int
+cmd_xexport (void) 
+{
+  return parse_output_trns (PORFILE_WRITER);
+}
+\f
 static bool rename_variables (struct dictionary *dict);
 static bool drop_variables (struct dictionary *dict);
 static bool keep_variables (struct dictionary *dict);
 static bool rename_variables (struct dictionary *dict);
 static bool drop_variables (struct dictionary *dict);
 static bool keep_variables (struct dictionary *dict);
@@ -362,65 +533,26 @@ static bool keep_variables (struct dictionary *dict);
 /* Commands that read and write system files share a great deal
    of common syntactic structure for rearranging and dropping
    variables.  This function parses this syntax and modifies DICT
 /* Commands that read and write system files share a great deal
    of common syntactic structure for rearranging and dropping
    variables.  This function parses this syntax and modifies DICT
-   appropriately.
-
-   OP is the operation being performed.  For operations that
-   write a system file, *COMPRESS is set to 1 if the system file
-   should be compressed, 0 otherwise.
-   
-   Returns true on success, false on failure. */
+   appropriately.  Returns true on success, false on failure. */
 static bool
 static bool
-trim_dictionary (struct dictionary *dict, enum operation op, int *compress)
+parse_dict_trim (struct dictionary *dict)
 {
 {
-  assert ((compress != NULL) == (op == OP_SAVE));
-  if (get_scompression())
-    *compress = 1;
-
-  if (op == OP_SAVE || op == OP_EXPORT)
+  if (lex_match_id ("MAP")) 
     {
     {
-      /* Delete all the scratch variables. */
-      struct variable **v;
-      size_t nv;
-      size_t i;
-
-      v = xmalloc (sizeof *v * dict_get_var_cnt (dict));
-      nv = 0;
-      for (i = 0; i < dict_get_var_cnt (dict); i++) 
-        if (dict_class_from_id (dict_get_var (dict, i)->name) == DC_SCRATCH)
-          v[nv++] = dict_get_var (dict, i);
-      dict_delete_vars (dict, v, nv);
-      free (v);
+      /* FIXME. */
+      return true;
     }
     }
-  
-  while (lex_match ('/'))
+  else if (lex_match_id ("DROP"))
+    return drop_variables (dict);
+  else if (lex_match_id ("KEEP"))
+    return keep_variables (dict);
+  else if (lex_match_id ("RENAME"))
+    return rename_variables (dict);
+  else
     {
     {
-      bool ok = true;
-      
-      if (op == OP_SAVE && lex_match_id ("COMPRESSED"))
-       *compress = 1;
-      else if (op == OP_SAVE && lex_match_id ("UNCOMPRESSED"))
-       *compress = 0;
-      else if (lex_match_id ("DROP"))
-        ok = drop_variables (dict);
-      else if (lex_match_id ("KEEP"))
-       ok = keep_variables (dict);
-      else if (lex_match_id ("RENAME"))
-        ok = rename_variables (dict);
-      else
-       {
-         lex_error (_("expecting a valid subcommand"));
-         ok = false;
-       }
-
-      if (!ok)
-        return false;
+      lex_error (_("expecting a valid subcommand"));
+      return false;
     }
     }
-
-  if (!lex_end_of_command ())
-    return false;
-
-  dict_compact_values (dict);
-  return true;
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
@@ -484,8 +616,8 @@ rename_variables (struct dictionary *dict)
       if (nn != nv)
        {
          msg (SE, _("Number of variables on left side of `=' (%d) does not "
       if (nn != nv)
        {
          msg (SE, _("Number of variables on left side of `=' (%d) does not "
-              "match number of variables on right side (%d), in "
-              "parenthesized group %d of RENAME subcommand."),
+                     "match number of variables on right side (%d), in "
+                     "parenthesized group %d of RENAME subcommand."),
               nv - old_nv, nn - old_nv, group);
          goto done;
        }
               nv - old_nv, nn - old_nv, group);
          goto done;
        }
@@ -501,7 +633,7 @@ rename_variables (struct dictionary *dict)
     }
   success = 1;
 
     }
   success = 1;
 
-done:
+ done:
   for (i = 0; i < nn; i++)
     free (new_names[i]);
   free (new_names);
   for (i = 0; i < nn; i++)
     free (new_names[i]);
   free (new_names);
@@ -558,91 +690,6 @@ keep_variables (struct dictionary *dict)
   return true;
 }
 \f
   return true;
 }
 \f
-/* EXPORT procedure. */
-struct export_proc 
-  {
-    struct pfm_writer *writer;  /* System file writer. */
-    struct case_map *map;       /* Map from active file to system file dict. */
-    struct ccase bounce;        /* Bounce buffer. */
-  };
-
-static int export_write_case_func (struct ccase *, void *);
-static void export_proc_free (struct export_proc *);
-     
-/* Parses the EXPORT command.  */
-/* FIXME: same as cmd_save_internal(). */
-int
-cmd_export (void)
-{
-  struct file_handle *fh;
-  struct dictionary *dict;
-  struct export_proc *proc;
-
-  proc = xmalloc (sizeof *proc);
-  proc->writer = NULL;
-  proc->map = NULL;
-  case_nullify (&proc->bounce);
-
-  lex_match ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
-  fh = fh_parse ();
-  if (fh == NULL)
-    return CMD_FAILURE;
-
-  dict = dict_clone (default_dict);
-  start_case_map (dict);
-  if (!trim_dictionary (dict, OP_EXPORT, NULL))
-    goto error;
-  proc->map = finish_case_map (dict);
-  if (proc->map != NULL)
-    case_create (&proc->bounce, dict_get_next_value_idx (dict));
-
-  proc->writer = pfm_open_writer (fh, dict);
-  if (proc->writer == NULL)
-    goto error;
-  
-  dict_destroy (dict);
-
-  procedure (export_write_case_func, proc);
-  export_proc_free (proc);
-  free (proc);
-
-  return CMD_SUCCESS;
-
- error:
-  dict_destroy (dict);
-  export_proc_free (proc);
-  free (proc);
-  return CMD_FAILURE;
-}
-
-/* Writes case C to the EXPORT file. */
-static int
-export_write_case_func (struct ccase *c, void *aux) 
-{
-  struct export_proc *proc = aux;
-  if (proc->map == NULL)
-    pfm_write_case (proc->writer, c);
-  else 
-    {
-      map_case (proc->map, c, &proc->bounce);
-      pfm_write_case (proc->writer, &proc->bounce);
-    }
-  return 1;
-}
-
-static void
-export_proc_free (struct export_proc *proc) 
-{
-  if (proc != NULL) 
-    {
-      pfm_close_writer (proc->writer);
-      destroy_case_map (proc->map);
-      case_destroy (&proc->bounce);
-    }
-}
-\f
 /* MATCH FILES. */
 
 #include "debug-print.h"
 /* MATCH FILES. */
 
 #include "debug-print.h"
@@ -657,8 +704,7 @@ enum
 /* One of the files on MATCH FILES. */
 struct mtf_file
   {
 /* One of the files on MATCH FILES. */
 struct mtf_file
   {
-    struct mtf_file *next, *prev;
-                               /* Next, previous in the list of files. */
+    struct mtf_file *next, *prev; /* Next, previous in the list of files. */
     struct mtf_file *next_min; /* Next in the chain of minimums. */
     
     int type;                  /* One of MTF_*. */
     struct mtf_file *next_min; /* Next in the chain of minimums. */
     
     int type;                  /* One of MTF_*. */
@@ -680,7 +726,7 @@ struct mtf_proc
     struct mtf_file *head;      /* First file mentioned on FILE or TABLE. */
     struct mtf_file *tail;      /* Last file mentioned on FILE or TABLE. */
     
     struct mtf_file *head;      /* First file mentioned on FILE or TABLE. */
     struct mtf_file *tail;      /* Last file mentioned on FILE or TABLE. */
     
-    size_t by_cnt;              /* Number of variables on BY subcommand. */
+    int by_cnt;                 /* Number of variables on BY subcommand. */
 
     /* Names of FIRST, LAST variables. */
     char first[LONG_NAME_LEN + 1], last[LONG_NAME_LEN + 1];
 
     /* Names of FIRST, LAST variables. */
     char first[LONG_NAME_LEN + 1], last[LONG_NAME_LEN + 1];
@@ -1054,7 +1100,7 @@ cmd_match_files (void)
   mtf_free (&mtf);
   return CMD_SUCCESS;
   
   mtf_free (&mtf);
   return CMD_SUCCESS;
   
-error:
+ error:
   mtf_free (&mtf);
   return CMD_FAILURE;
 }
   mtf_free (&mtf);
   return CMD_FAILURE;
 }
@@ -1474,26 +1520,20 @@ cmd_import (void)
   struct import_pgm *pgm = NULL;
   struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
   struct import_pgm *pgm = NULL;
   struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
-  int type;
-
-  pgm = xmalloc (sizeof *pgm);
-  pgm->reader = NULL;
-  pgm->map = NULL;
-  case_nullify (&pgm->bounce);
+  enum pfm_type type;
 
 
+  lex_match ('/');
   for (;;)
     {
   for (;;)
     {
-      lex_match ('/');
-      
-      if (lex_match_id ("FILE") || token == T_STRING)
+      if (pgm == NULL && (lex_match_id ("FILE") || token == T_STRING))
        {
          lex_match ('=');
 
          fh = fh_parse ();
          if (fh == NULL)
        {
          lex_match ('=');
 
          fh = fh_parse ();
          if (fh == NULL)
-           return CMD_FAILURE;
+            goto error;
        }
        }
-      else if (lex_match_id ("TYPE"))
+      else if (pgm == NULL && lex_match_id ("TYPE"))
        {
          lex_match ('=');
 
        {
          lex_match ('=');
 
@@ -1504,27 +1544,48 @@ cmd_import (void)
          else
            {
              lex_error (_("expecting COMM or TAPE"));
          else
            {
              lex_error (_("expecting COMM or TAPE"));
-             return CMD_FAILURE;
+              goto error;
            }
        }
            }
        }
-      else break;
+      else 
+        {
+          if (pgm == NULL) 
+            {
+              if (fh == NULL) 
+                {
+                  lex_sbc_missing ("FILE");
+                  goto error;
+                }
+              
+              discard_variables ();
+
+              pgm = xmalloc (sizeof *pgm);
+              pgm->reader = pfm_open_reader (fh, &dict, NULL);
+              pgm->map = NULL;
+              case_nullify (&pgm->bounce);
+              if (pgm->reader == NULL)
+                goto error;
+
+              case_create (&pgm->bounce, dict_get_next_value_idx (dict));
+  
+              start_case_map (dict);
+            }
+
+          if (token == '.')
+            break;
+          
+          if (!parse_dict_trim (dict))
+            goto error;
+        }
+
+      lex_match ('/');
     }
     }
-  if (!lex_match ('/') && token != '.')
+  if (pgm == NULL) 
     {
       lex_error (NULL);
     {
       lex_error (NULL);
-      return CMD_FAILURE;
+      goto error;
     }
 
     }
 
-  discard_variables ();
-
-  pgm->reader = pfm_open_reader (fh, &dict, NULL);
-  if (pgm->reader == NULL)
-    return CMD_FAILURE;
-  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
-  
-  start_case_map (dict);
-  if (!trim_dictionary (dict, OP_READ, NULL))
-    goto error;
   pgm->map = finish_case_map (dict);
   
   dict_destroy (default_dict);
   pgm->map = finish_case_map (dict);
   
   dict_destroy (default_dict);
@@ -1619,7 +1680,7 @@ struct case_map
    at will before using finish_case_map() to produce the case
    map.
 
    at will before using finish_case_map() to produce the case
    map.
 
-   Uses D's aux members, which may not otherwise be in use. */
+   Uses D's aux members, which must otherwise not be in use. */
 static void
 start_case_map (struct dictionary *d) 
 {
 static void
 start_case_map (struct dictionary *d) 
 {
index fe999ff1aff50c24c783bab6a96dfa12dcd3d9a6..8a3ccc21881c1cea1d2e9510fe88b9e1741c650e 100644 (file)
@@ -384,6 +384,22 @@ lex_get (void)
 #endif
 }
 
 #endif
 }
 
+/* Reports an error to the effect that subcommand SBC may only be
+   specified once. */
+void
+lex_sbc_only_once (const char *sbc) 
+{
+  msg (SE, _("Subcommand %s may only be specified once."), sbc);
+}
+
+/* Reports an error to the effect that subcommand SBC is
+   missing. */
+void
+lex_sbc_missing (const char *sbc) 
+{
+  lex_error (_("missing required subcommand %s"), sbc);
+}
+
 /* Prints a syntax error message containing the current token and
    given message MESSAGE (if non-null). */
 void
 /* Prints a syntax error message containing the current token and
    given message MESSAGE (if non-null). */
 void
index af3aeb0ae0f7bf54b5c5b226dd37ad3775fbbe17..816b4317f9bb8318d4d1a4a7232c6a73231d01b0 100644 (file)
@@ -87,6 +87,8 @@ void lex_done (void);
 /* Common functions. */
 void lex_get (void);
 void lex_error (const char *, ...);
 /* Common functions. */
 void lex_get (void);
 void lex_error (const char *, ...);
+void lex_sbc_only_once (const char *);
+void lex_sbc_missing (const char *);
 int lex_end_of_command (void);
 
 /* Token testing functions. */
 int lex_end_of_command (void);
 
 /* Token testing functions. */
index 5346f1539149db09a32b2aec385c34aab9d1a4d9..3f84e16023fd4f36ef6fd13667fec2a4eca7d01a 100644 (file)
 
 #include <stdbool.h>
 
 
 #include <stdbool.h>
 
-/* Portable file types. */
-enum pfm_type
-  {
-    PFM_COMM,   /* Formatted for communication. */
-    PFM_TAPE    /* Formatted for tape. */
-  };
-
 /* Information produced by pfm_read_dictionary() that doesn't fit into
    a dictionary struct. */
 struct pfm_read_info
 /* Information produced by pfm_read_dictionary() that doesn't fit into
    a dictionary struct. */
 struct pfm_read_info
index fbe56f6a2eb95ab196371bc3af373b88189c3062..cd088b7b85048be501fbd2ccbec3c45b316ddcee 100644 (file)
 #include "error.h"
 #include <ctype.h>
 #include <errno.h>
 #include "error.h"
 #include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <float.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <float.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/stat.h>
 #include <time.h>
 #include <time.h>
+#include <unistd.h>
 #include "alloc.h"
 #include "case.h"
 #include "dictionary.h"
 #include "alloc.h"
 #include "case.h"
 #include "dictionary.h"
@@ -35,6 +38,7 @@
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
+#include "stat-macros.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
@@ -55,6 +59,8 @@ struct pfm_writer
 
     size_t var_cnt;             /* Number of variables. */
     struct pfm_var *vars;       /* Variables. */
 
     size_t var_cnt;             /* Number of variables. */
     struct pfm_var *vars;       /* Variables. */
+
+    int digits;                 /* Digits of precision. */
   };
 
 /* A variable to write to the portable file. */
   };
 
 /* A variable to write to the portable file. */
@@ -73,36 +79,55 @@ static int write_value_labels (struct pfm_writer *, const struct dictionary *);
 static void format_trig_double (long double, int base_10_precision, char[]);
 static char *format_trig_int (int, bool force_sign, char[]);
 
 static void format_trig_double (long double, int base_10_precision, char[]);
 static char *format_trig_int (int, bool force_sign, char[]);
 
-/* Writes the dictionary DICT to portable file HANDLE.  Returns
-   nonzero only if successful.  DICT will not be modified, except
-   to assign short names. */
+/* Returns default options for writing a portable file. */
+struct pfm_write_options
+pfm_writer_default_options (void) 
+{
+  struct pfm_write_options opts;
+  opts.create_writeable = true;
+  opts.type = PFM_COMM;
+  opts.digits = DBL_DIG;
+  return opts;
+}
+
+/* Writes the dictionary DICT to portable file HANDLE according
+   to the given OPTS.  Returns nonzero only if successful.  DICT
+   will not be modified, except to assign short names. */
 struct pfm_writer *
 struct pfm_writer *
-pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
+pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
+                 struct pfm_write_options opts)
 {
   struct pfm_writer *w = NULL;
 {
   struct pfm_writer *w = NULL;
+  mode_t mode;
+  int fd;
   size_t i;
 
   size_t i;
 
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  fd = open (handle_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd < 0) 
+    goto open_error;
+
+  /* Open file handle. */
   if (!fh_open (fh, "portable file", "we"))
     goto error;
   if (!fh_open (fh, "portable file", "we"))
     goto error;
-  
-  /* Open the physical disk file. */
+
+  /* Initialize data structures. */
   w = xmalloc (sizeof *w);
   w->fh = fh;
   w = xmalloc (sizeof *w);
   w->fh = fh;
-  w->file = fopen (handle_get_filename (fh), "wb");
+  w->file = fdopen (fd, "w");
+  if (w->file == NULL) 
+    {
+      close (fd);
+      goto open_error;
+    }
+  
   w->lc = 0;
   w->var_cnt = 0;
   w->vars = NULL;
   
   w->lc = 0;
   w->var_cnt = 0;
   w->vars = NULL;
   
-  /* Check that file create succeeded. */
-  if (w->file == NULL)
-    {
-      msg (ME, _("An error occurred while opening \"%s\" for writing "
-          "as a portable file: %s."),
-           handle_get_filename (fh), strerror (errno));
-      err_cond_fail ();
-      goto error;
-    }
-  
   w->var_cnt = dict_get_var_cnt (dict);
   w->vars = xmalloc (sizeof *w->vars * w->var_cnt);
   for (i = 0; i < w->var_cnt; i++) 
   w->var_cnt = dict_get_var_cnt (dict);
   w->vars = xmalloc (sizeof *w->vars * w->var_cnt);
   for (i = 0; i < w->var_cnt; i++) 
@@ -113,6 +138,14 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
       pv->fv = dv->fv;
     }
 
       pv->fv = dv->fv;
     }
 
+  w->digits = opts.digits;
+  if (w->digits < 1) 
+    {
+      msg (ME, _("Invalid decimal digits count %d.  Treating as %d."),
+           w->digits, DBL_DIG);
+      w->digits = DBL_DIG;
+    }
+
   /* Write file header. */
   if (!write_header (w)
       || !write_version_data (w)
   /* Write file header. */
   if (!write_header (w)
       || !write_version_data (w)
@@ -123,9 +156,16 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
 
   return w;
 
 
   return w;
 
-error:
+ error:
   pfm_close_writer (w);
   return NULL;
   pfm_close_writer (w);
   return NULL;
+
+ open_error:
+  msg (ME, _("An error occurred while opening \"%s\" for writing "
+             "as a portable file: %s."),
+       handle_get_filename (fh), strerror (errno));
+  err_cond_fail ();
+  goto error;
 }
 \f  
 /* Write NBYTES starting at BUF to the portable file represented by
 }
 \f  
 /* Write NBYTES starting at BUF to the portable file represented by
@@ -169,7 +209,7 @@ static int
 write_float (struct pfm_writer *w, double d)
 {
   char buffer[64];
 write_float (struct pfm_writer *w, double d)
 {
   char buffer[64];
-  format_trig_double (d, DBL_DIG, buffer);
+  format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer);
   return buf_write (w, buffer, strlen (buffer)) && buf_write (w, "/", 1);
 }
 
   return buf_write (w, buffer, strlen (buffer)) && buf_write (w, "/", 1);
 }
 
@@ -754,6 +794,8 @@ format_trig_double (long double value, int base_10_precision, char output[])
      required base-30 precision as 2/3 of the base-10 precision
      (log30(10) = .68). */
   assert (base_10_precision > 0);
      required base-30 precision as 2/3 of the base-10 precision
      (log30(10) = .68). */
   assert (base_10_precision > 0);
+  if (base_10_precision > LDBL_DIG)
+    base_10_precision = LDBL_DIG;
   base_30_precision = DIV_RND_UP (base_10_precision * 2, 3);
   if (trig_cnt > base_30_precision)
     {
   base_30_precision = DIV_RND_UP (base_10_precision * 2, 3);
   if (trig_cnt > base_30_precision)
     {
index 3f82038d9a9dc6638f6170e611bbcac2fdefb951..e335098a846bc87e4f823d2a9b6e6e88822092b3 100644 (file)
 #ifndef PFM_WRITE_H
 #define PFM_WRITE_H
 
 #ifndef PFM_WRITE_H
 #define PFM_WRITE_H
 
+#include <stdbool.h>
+
 /* Portable file writing. */
 
 /* Portable file writing. */
 
+/* Portable file types. */
+enum pfm_type
+  {
+    PFM_COMM,   /* Formatted for communication. */
+    PFM_TAPE    /* Formatted for tape. */
+  };
+
+/* Portable file writing options. */
+struct pfm_write_options 
+  {
+    bool create_writeable;      /* File perms: writeable or read/only? */
+    enum pfm_type type;         /* Type of portable file (TODO). */
+    int digits;                 /* Digits of precision. */
+  };
+
 struct file_handle;
 struct dictionary;
 struct ccase;
 struct file_handle;
 struct dictionary;
 struct ccase;
-struct pfm_writer *pfm_open_writer (struct file_handle *, struct dictionary *);
+struct pfm_writer *pfm_open_writer (struct file_handle *, struct dictionary *,
+                                    struct pfm_write_options);
+struct pfm_write_options pfm_writer_default_options (void);
+
 int pfm_write_case (struct pfm_writer *, struct ccase *);
 void pfm_close_writer (struct pfm_writer *);
 
 int pfm_write_case (struct pfm_writer *, struct ccase *);
 void pfm_close_writer (struct pfm_writer *);
 
index 80b7840820761310cac33d384bb5ad8d56834255..4f0f9ec18a763abcfb1ca4b7cf8c548c637028b2 100644 (file)
@@ -24,6 +24,8 @@
 #include <stdlib.h>
 #include <ctype.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 #include <time.h>
 #if HAVE_UNISTD_H
 #include <unistd.h>    /* Required by SunOS4. */
 #include <time.h>
 #if HAVE_UNISTD_H
 #include <unistd.h>    /* Required by SunOS4. */
@@ -37,6 +39,8 @@
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
+#include "settings.h"
+#include "stat-macros.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
@@ -107,33 +111,61 @@ var_flt64_cnt (const struct variable *v)
   return v->type == NUMERIC ? 1 : DIV_RND_UP (v->width, sizeof (flt64));
 }
 
   return v->type == NUMERIC ? 1 : DIV_RND_UP (v->width, sizeof (flt64));
 }
 
+/* Returns default options for writing a system file. */
+struct sfm_write_options
+sfm_writer_default_options (void) 
+{
+  struct sfm_write_options opts;
+  opts.create_writeable = true;
+  opts.compress = get_scompression ();
+  opts.version = 3;
+  return opts;
+}
+
 /* Opens the system file designated by file handle FH for writing
 /* Opens the system file designated by file handle FH for writing
-   cases from dictionary D.  If COMPRESS is nonzero, the
-   system file will be compressed.  If OMIT_LONG_NAMES is nonzero, the
-   long name table will be omitted.
+   cases from dictionary D according to the given OPTS.  If
+   COMPRESS is nonzero, the system file will be compressed.
 
    No reference to D is retained, so it may be modified or
    destroyed at will after this function returns.  D is not
    modified by this function, except to assign short names. */
 struct sfm_writer *
 
    No reference to D is retained, so it may be modified or
    destroyed at will after this function returns.  D is not
    modified by this function, except to assign short names. */
 struct sfm_writer *
-sfm_open_writer (struct file_handle *fh,
-                 struct dictionary *d, int compress, 
-                short omit_long_names)
+sfm_open_writer (struct file_handle *fh, struct dictionary *d,
+                 struct sfm_write_options opts)
 {
   struct sfm_writer *w = NULL;
 {
   struct sfm_writer *w = NULL;
+  mode_t mode;
+  int fd;
   int idx;
   int i;
 
   int idx;
   int i;
 
+  /* Check version. */
+  if (opts.version != 2 && opts.version != 3) 
+    {
+      msg (ME, _("Unknown system file version %d. Treating as version %d."),
+           opts.version, 3);
+      opts.version = 3;
+    }
+
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  fd = open (handle_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd < 0) 
+    goto open_error;
+
+  /* Open file handle. */
   if (!fh_open (fh, "system file", "we"))
     goto error;
 
   /* Create and initialize writer. */
   w = xmalloc (sizeof *w);
   w->fh = fh;
   if (!fh_open (fh, "system file", "we"))
     goto error;
 
   /* Create and initialize writer. */
   w = xmalloc (sizeof *w);
   w->fh = fh;
-  w->file = fopen (handle_get_filename (fh), "wb");
+  w->file = fdopen (fd, "w");
 
   w->needs_translation = does_dict_need_translation (d);
 
   w->needs_translation = does_dict_need_translation (d);
-  w->compress = compress;
+  w->compress = opts.compress;
   w->case_cnt = 0;
   w->flt64_cnt = 0;
 
   w->case_cnt = 0;
   w->flt64_cnt = 0;
 
@@ -152,13 +184,10 @@ sfm_open_writer (struct file_handle *fh,
     }
 
   /* Check that file create succeeded. */
     }
 
   /* Check that file create succeeded. */
-  if (w->file == NULL)
+  if (w->file == NULL) 
     {
     {
-      msg (ME, _("Error opening \"%s\" for writing "
-                 "as a system file: %s."),
-           handle_get_filename (w->fh), strerror (errno));
-      err_cond_fail ();
-      goto error;
+      close (fd);
+      goto open_error;
     }
 
   /* Write the file header. */
     }
 
   /* Write the file header. */
@@ -189,7 +218,7 @@ sfm_open_writer (struct file_handle *fh,
   if (!write_variable_display_parameters (w, d))
     goto error;
 
   if (!write_variable_display_parameters (w, d))
     goto error;
 
-  if (!omit_long_names
+  if (opts.version >= 3
     {
       if (!write_longvar_table (w, d))
        goto error;
     {
       if (!write_longvar_table (w, d))
        goto error;
@@ -225,6 +254,12 @@ sfm_open_writer (struct file_handle *fh,
  error:
   sfm_close_writer (w);
   return NULL;
  error:
   sfm_close_writer (w);
   return NULL;
+
+ open_error:
+  msg (ME, _("Error opening \"%s\" for writing as a system file: %s."),
+       handle_get_filename (w->fh), strerror (errno));
+  err_cond_fail ();
+  goto error;
 }
 
 static int
 }
 
 static int
index dfddd4f614f16d07f5342e09286e5f09355aed29..896353dbef30a77d72e4cac1d2614d385fb1e85c 100644 (file)
 #ifndef SFM_WRITE_H
 #define SFM_WRITE_H 1
 
 #ifndef SFM_WRITE_H
 #define SFM_WRITE_H 1
 
+#include <stdbool.h>
+
 /* Writing system files. */
 
 /* Writing system files. */
 
+/* Options for creating a system file. */
+struct sfm_write_options 
+  {
+    bool create_writeable;      /* File perms: writeable or read/only? */
+    bool compress;              /* Compress file? */
+    int version;                /* System file version (currently 2 or 3). */
+  };
+
 struct file_handle;
 struct dictionary;
 struct ccase;
 struct file_handle;
 struct dictionary;
 struct ccase;
-struct sfm_writer *sfm_open_writer (struct file_handle *, struct dictionary *, 
-                                   int compress, short omit_longnames);
+struct sfm_writer *sfm_open_writer (struct file_handle *, struct dictionary *,
+                                    struct sfm_write_options);
+struct sfm_write_options sfm_writer_default_options (void);
 
 int sfm_write_case (struct sfm_writer *, struct ccase *);
 void sfm_close_writer (struct sfm_writer *);
 
 int sfm_write_case (struct sfm_writer *, struct ccase *);
 void sfm_close_writer (struct sfm_writer *);
index f51511930205a27164f3b42a4cd7bbf8b30342cc..0b5d0de2032914e19f72c86827fd93d0802e61dd 100644 (file)
@@ -1,3 +1,9 @@
+Sun Aug 21 00:20:02 2005  Ben Pfaff  <blp@gnu.org>
+
+       * command/import-export.sh: Simplify.
+
+       * command/sysfiles-old.sh: Use version 2, not 3x.
+
 Sat Aug  6 17:32:39 2005  Ben Pfaff  <blp@gnu.org>
 
        * command/missing-values.sh: New test.
 Sat Aug  6 17:32:39 2005  Ben Pfaff  <blp@gnu.org>
 
        * command/missing-values.sh: New test.
index 4476fa7df4cee45b675509311a4e5302761792aa..74a27543401bf6e61c24865ef530aedf6f512006 100755 (executable)
@@ -50,7 +50,7 @@ cd $TEMPDIR
 
 activity="create program"
 cat > $TESTFILE <<EOF
 
 activity="create program"
 cat > $TESTFILE <<EOF
-DATA LIST LIST /X * Y *.
+DATA LIST LIST NOTABLE /X Y.
 BEGIN DATA.
 1 2
 3 4
 BEGIN DATA.
 1 2
 3 4
@@ -58,9 +58,6 @@ BEGIN DATA.
 END DATA.
 
 EXPORT /OUTFILE='wiz.por'.
 END DATA.
 
 EXPORT /OUTFILE='wiz.por'.
-
-LIST.
-
 IMPORT /FILE='wiz.por'.
 
 LIST.
 IMPORT /FILE='wiz.por'.
 
 LIST.
@@ -74,26 +71,11 @@ if [ $? -ne 0 ] ; then no_result ; fi
 
 activity="compare output"
 diff -b -B $TEMPDIR/pspp.list - << EOF
 
 activity="compare output"
 diff -b -B $TEMPDIR/pspp.list - << EOF
-1.1 DATA LIST.  Reading free-form data from the command file.
-+--------+------+
-|Variable|Format|
-#========#======#
-|X       |F8.0  |
-|Y       |F8.0  |
-+--------+------+
-
        X        Y
 -------- --------
     1.00     2.00 
     3.00     4.00 
     5.00     6.00 
        X        Y
 -------- --------
     1.00     2.00 
     3.00     4.00 
     5.00     6.00 
-
-       X        Y
--------- --------
-    1.00     2.00 
-    3.00     4.00 
-    5.00     6.00 
-
 EOF
 if [ $? -ne 0 ] ; then fail ; fi
 
 EOF
 if [ $? -ne 0 ] ; then fail ; fi
 
index 85b915ad958af316c81b817a657e07db1cf1938b..6e23c97f8a930a0cb4380e9604d4496045d4e1da 100755 (executable)
@@ -65,7 +65,7 @@ BEGIN DATA.
 END DATA.
 
 SAVE /OUTFILE='$TEMPDIR/foo.sav'
 END DATA.
 
 SAVE /OUTFILE='$TEMPDIR/foo.sav'
-     /VERSION=3x
+     /VERSION=2
      .
 
 GET /FILE='$TEMPDIR/foo.sav'.
      .
 
 GET /FILE='$TEMPDIR/foo.sav'.