* 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
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}).
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
@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
@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
@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
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
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
@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
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
+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.
}
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;
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);
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;
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)
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);
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. */
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 *,
}
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 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)
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
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);
error:
get_pgm_free (pgm);
- if (dict != NULL)
+ if (dict != NULL)
dict_destroy (dict);
return CMD_FAILURE;
}
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);
/* 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. */
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;
}
}
success = 1;
-done:
+ done:
for (i = 0; i < nn; i++)
free (new_names[i]);
free (new_names);
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"
/* 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 *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];
mtf_free (&mtf);
return CMD_SUCCESS;
-error:
+ error:
mtf_free (&mtf);
return CMD_FAILURE;
}
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 ('=');
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);
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)
{
#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
/* 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. */
#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
#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"
#include "hash.h"
#include "magic.h"
#include "misc.h"
+#include "stat-macros.h"
#include "str.h"
#include "value-labels.h"
#include "var.h"
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. */
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++)
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)
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
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);
}
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)
{
#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 *);
#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 "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"
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;
}
/* 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. */
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;
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
#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 *);
+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.
activity="create program"
cat > $TESTFILE <<EOF
-DATA LIST LIST /X * Y *.
+DATA LIST LIST NOTABLE /X Y.
BEGIN DATA.
1 2
3 4
END DATA.
EXPORT /OUTFILE='wiz.por'.
-
-LIST.
-
IMPORT /FILE='wiz.por'.
LIST.
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
END DATA.
SAVE /OUTFILE='$TEMPDIR/foo.sav'
- /VERSION=3x
+ /VERSION=2
.
GET /FILE='$TEMPDIR/foo.sav'.