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.
-* 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
 
-@node APPLY DICTIONARY, EXPORT, System and Portable Files, System and Portable Files
+@node 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.
 
-@node EXPORT, GET, APPLY DICTIONARY, System and Portable Files
+@node EXPORT
 @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{}
+        /TYPE=@{COMM,TAPE@}
+        /MAP
 @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}).
@@ -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}).
 
+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.
 
-@node GET, IMPORT, EXPORT, System and Portable Files
+@node 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.
 
-@node IMPORT, MATCH FILES, GET, System and Portable Files
+@node 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.
 
-@node MATCH FILES, SAVE, IMPORT, System and Portable Files
+@node 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.
 
-@node SAVE, SYSFILE INFO, MATCH FILES, System and Portable Files
+@node SAVE
 @section SAVE
 @vindex SAVE
 
 @display
 SAVE
         /OUTFILE='filename'
+        /UNSELECTED=@{RETAIN,DELETE@}
         /@{COMPRESSED,UNCOMPRESSED@}
+        /PERMISSIONS=@{WRITEABLE,READONLY@}
         /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
@@ -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}).
 
+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 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
@@ -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
-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.
 
-@node SYSFILE INFO, XSAVE, SAVE, System and Portable Files
+@node 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.
 
-@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
 
@@ -305,17 +374,27 @@ a system file and displays information on its dictionary.
 XSAVE
         /OUTFILE='filename'
         /@{COMPRESSED,UNCOMPRESSED@}
+        /PERMISSIONS=@{WRITEABLE,READONLY@}
         /DROP=var_list
         /KEEP=var_list
+        /VERSION=version
         /RENAME=(src_names=target_names)@dots{}
+        /NAMES
+        /MAP
 @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
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.
index bc554b97bf588845ca0a2fa4e1e794cef200dcbc..5979204d08c0c407a078f2ca1ad367665ff0ed67 100644 (file)
@@ -278,7 +278,8 @@ cmd_aggregate (void)
     }
   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;
       
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 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);
 
@@ -170,7 +170,7 @@ case_num (const struct ccase *c, size_t idx)
   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;
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 ("XEXPORT",                ERRO, INPU, TRAN, TRAN, cmd_xexport)
 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;
 
-
-char *  pspp_completion_function (const char *text,   int state);
-
+char *pspp_completion_function (const char *text,   int state);
 
 int cmd_parse (void);
 
index 3f1177d35f4f3f54ca827c47a68d659b4c012fe3..c63b2e1bcf43a0ab48866da22c4ecb9e513b2224 100644 (file)
@@ -491,6 +491,23 @@ dict_delete_vars (struct dictionary *d,
     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. */
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_scratch_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
-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);
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 *,
-                                 const char *, size_t length);
+                                 const unsigned char *, size_t length);
 
 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. */
   };
 
-static bool trim_dictionary (struct dictionary *,
-                             enum operation, int *compress);
+static bool parse_dict_trim (struct dictionary *);
 \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);
-  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);
@@ -114,7 +119,7 @@ cmd_get (void)
 
  error:
   get_pgm_free (pgm);
-  if (dict != NULL)
+  if (dict != NULL) 
     dict_destroy (dict);
   return CMD_FAILURE;
 }
@@ -175,186 +180,352 @@ const struct case_source_class get_source_class =
     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 (;;)
     {
-      if (lex_match_id ("VERSION"))
+      if (lex_match_id ("OUTFILE"))
        {
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
          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 ('=');
-      
-         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;
-
     }
+  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;
     }
 
-  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:
-  assert (t != NULL);
+  any_writer_destroy (aw);
   dict_destroy (dict);
-  save_trns_free (&t->h);
   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;
+
+  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
-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
-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
-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;
 }
 
-/* Frees a SAVE transformation. */
+/* Frees an XSAVE or XEXPORT transformation. */
 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);
@@ -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
-   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
-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. */
@@ -484,8 +616,8 @@ rename_variables (struct dictionary *dict)
       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;
        }
@@ -501,7 +633,7 @@ rename_variables (struct dictionary *dict)
     }
   success = 1;
 
-done:
+ done:
   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
-/* 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"
@@ -657,8 +704,7 @@ enum
 /* 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_*. */
@@ -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. */
     
-    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];
@@ -1054,7 +1100,7 @@ cmd_match_files (void)
   mtf_free (&mtf);
   return CMD_SUCCESS;
   
-error:
+ error:
   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;
-  int type;
-
-  pgm = xmalloc (sizeof *pgm);
-  pgm->reader = NULL;
-  pgm->map = NULL;
-  case_nullify (&pgm->bounce);
+  enum pfm_type type;
 
+  lex_match ('/');
   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)
-           return CMD_FAILURE;
+            goto error;
        }
-      else if (lex_match_id ("TYPE"))
+      else if (pgm == NULL && lex_match_id ("TYPE"))
        {
          lex_match ('=');
 
@@ -1504,27 +1544,48 @@ cmd_import (void)
          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);
-      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);
@@ -1619,7 +1680,7 @@ struct 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) 
 {
index fe999ff1aff50c24c783bab6a96dfa12dcd3d9a6..8a3ccc21881c1cea1d2e9510fe88b9e1741c650e 100644 (file)
@@ -384,6 +384,22 @@ lex_get (void)
 #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
index af3aeb0ae0f7bf54b5c5b226dd37ad3775fbbe17..816b4317f9bb8318d4d1a4a7232c6a73231d01b0 100644 (file)
@@ -87,6 +87,8 @@ void lex_done (void);
 /* 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. */
index 5346f1539149db09a32b2aec385c34aab9d1a4d9..3f84e16023fd4f36ef6fd13667fec2a4eca7d01a 100644 (file)
 
 #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
index fbe56f6a2eb95ab196371bc3af373b88189c3062..cd088b7b85048be501fbd2ccbec3c45b316ddcee 100644 (file)
 #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 <sys/stat.h>
 #include <time.h>
+#include <unistd.h>
 #include "alloc.h"
 #include "case.h"
 #include "dictionary.h"
@@ -35,6 +38,7 @@
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
+#include "stat-macros.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. */
+
+    int digits;                 /* Digits of precision. */
   };
 
 /* 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[]);
 
-/* 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 *
-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;
+  mode_t mode;
+  int fd;
   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;
-  
-  /* Open the physical disk file. */
+
+  /* Initialize data structures. */
   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;
   
-  /* 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++) 
@@ -113,6 +138,14 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
       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)
@@ -123,9 +156,16 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
 
   return w;
 
-error:
+ error:
   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
@@ -169,7 +209,7 @@ static int
 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);
 }
 
@@ -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);
+  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)
     {
index 3f82038d9a9dc6638f6170e611bbcac2fdefb951..e335098a846bc87e4f823d2a9b6e6e88822092b3 100644 (file)
 #ifndef PFM_WRITE_H
 #define PFM_WRITE_H
 
+#include <stdbool.h>
+
 /* 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 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 *);
 
index 80b7840820761310cac33d384bb5ad8d56834255..4f0f9ec18a763abcfb1ca4b7cf8c548c637028b2 100644 (file)
@@ -24,6 +24,8 @@
 #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. */
@@ -37,6 +39,8 @@
 #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"
@@ -107,33 +111,61 @@ var_flt64_cnt (const struct variable *v)
   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
-   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 *
-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;
+  mode_t mode;
+  int fd;
   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;
-  w->file = fopen (handle_get_filename (fh), "wb");
+  w->file = fdopen (fd, "w");
 
   w->needs_translation = does_dict_need_translation (d);
-  w->compress = compress;
+  w->compress = opts.compress;
   w->case_cnt = 0;
   w->flt64_cnt = 0;
 
@@ -152,13 +184,10 @@ sfm_open_writer (struct file_handle *fh,
     }
 
   /* 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. */
@@ -189,7 +218,7 @@ sfm_open_writer (struct file_handle *fh,
   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;
@@ -225,6 +254,12 @@ sfm_open_writer (struct file_handle *fh,
  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
index dfddd4f614f16d07f5342e09286e5f09355aed29..896353dbef30a77d72e4cac1d2614d385fb1e85c 100644 (file)
 #ifndef SFM_WRITE_H
 #define SFM_WRITE_H 1
 
+#include <stdbool.h>
+
 /* 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 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 *);
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.
index 4476fa7df4cee45b675509311a4e5302761792aa..74a27543401bf6e61c24865ef530aedf6f512006 100755 (executable)
@@ -50,7 +50,7 @@ cd $TEMPDIR
 
 activity="create program"
 cat > $TESTFILE <<EOF
-DATA LIST LIST /X * Y *.
+DATA LIST LIST NOTABLE /X Y.
 BEGIN DATA.
 1 2
 3 4
@@ -58,9 +58,6 @@ BEGIN DATA.
 END DATA.
 
 EXPORT /OUTFILE='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
-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 
-
 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'
-     /VERSION=3x
+     /VERSION=2
      .
 
 GET /FILE='$TEMPDIR/foo.sav'.