- if (!lex_match (lexer, '/'))
- break;
- }
- if (lex_end_of_command (lexer) != CMD_SUCCESS)
- goto error;
-
- if (handle == NULL)
- {
- lex_sbc_missing (lexer, "OUTFILE");
- goto error;
- }
-
- dict_compact_values (dict);
-
- if (fh_get_referent (handle) == FH_REF_FILE)
- {
- switch (writer_type)
- {
- case SYSFILE_WRITER:
- writer = sfm_open_writer (handle, dict, sysfile_opts);
- break;
- case PORFILE_WRITER:
- writer = pfm_open_writer (handle, dict, porfile_opts);
- break;
- }
- }
- else
- writer = any_writer_open (handle, dict);
- if (writer == NULL)
- goto error;
-
- map = finish_case_map (dict);
- if (map != NULL)
- writer = casewriter_create_translator (writer,
- get_translate_case,
- get_destroy_case_map,
- map);
- dict_destroy (dict);
-
- return writer;
-
- error:
- casewriter_destroy (writer);
- dict_destroy (dict);
- destroy_case_map (map);
- return NULL;
-}
-\f
-/* SAVE and EXPORT. */
-
-/* Parses and performs the SAVE or EXPORT procedure. */
-static int
-parse_output_proc (struct lexer *lexer, struct dataset *ds, enum writer_type writer_type)
-{
- bool retain_unselected;
- struct variable *saved_filter_variable;
- struct casewriter *output;
- bool ok;
-
- output = parse_write_command (lexer, ds, writer_type, PROC_CMD,
- &retain_unselected);
- if (output == NULL)
- return CMD_CASCADING_FAILURE;
-
- saved_filter_variable = dict_get_filter (dataset_dict (ds));
- if (retain_unselected)
- dict_set_filter (dataset_dict (ds), NULL);
-
- casereader_transfer (proc_open (ds), output);
- ok = casewriter_destroy (output);
- ok = proc_commit (ds) && ok;
-
- dict_set_filter (dataset_dict (ds), saved_filter_variable);
-
- return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
-}
-
-int
-cmd_save (struct lexer *lexer, struct dataset *ds)
-{
- return parse_output_proc (lexer, ds, SYSFILE_WRITER);
-}
-
-int
-cmd_export (struct lexer *lexer, struct dataset *ds)
-{
- return parse_output_proc (lexer, ds, PORFILE_WRITER);
-}
-\f
-/* XSAVE and XEXPORT. */
-
-/* Transformation. */
-struct output_trns
- {
- struct casewriter *writer; /* Writer. */
- };
-
-static trns_proc_func output_trns_proc;
-static trns_free_func output_trns_free;
-
-/* Parses the XSAVE or XEXPORT transformation command. */
-static int
-parse_output_trns (struct lexer *lexer, struct dataset *ds, enum writer_type writer_type)
-{
- struct output_trns *t = xmalloc (sizeof *t);
- t->writer = parse_write_command (lexer, ds, writer_type, XFORM_CMD, NULL);
- if (t->writer == NULL)
- {
- free (t);
- return CMD_CASCADING_FAILURE;
- }
-
- add_transformation (ds, output_trns_proc, output_trns_free, t);
- return CMD_SUCCESS;
-}
-
-/* Writes case C to the system file specified on XSAVE or XEXPORT. */
-static int
-output_trns_proc (void *trns_, struct ccase *c, casenumber case_num UNUSED)
-{
- struct output_trns *t = trns_;
- struct ccase tmp;
- case_clone (&tmp, c);
- casewriter_write (t->writer, &tmp);
- return TRNS_CONTINUE;
-}
-
-/* Frees an XSAVE or XEXPORT transformation.
- Returns true if successful, false if an I/O error occurred. */
-static bool
-output_trns_free (void *trns_)
-{
- struct output_trns *t = trns_;
- bool ok = casewriter_destroy (t->writer);
- free (t);
- return ok;
-}
-
-/* XSAVE command. */
-int
-cmd_xsave (struct lexer *lexer, struct dataset *ds)
-{
- return parse_output_trns (lexer, ds, SYSFILE_WRITER);
-}
-
-/* XEXPORT command. */
-int
-cmd_xexport (struct lexer *lexer, struct dataset *ds)
-{
- return parse_output_trns (lexer, ds, PORFILE_WRITER);
-}
-\f
-static bool rename_variables (struct lexer *lexer, struct dictionary *dict);
-static bool drop_variables (struct lexer *, struct dictionary *dict);
-static bool keep_variables (struct lexer *, 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. Returns true on success, false on failure. */
-static bool
-parse_dict_trim (struct lexer *lexer, struct dictionary *dict)
-{
- if (lex_match_id (lexer, "MAP"))
- {
- /* FIXME. */
- return true;
- }
- else if (lex_match_id (lexer, "DROP"))
- return drop_variables (lexer, dict);
- else if (lex_match_id (lexer, "KEEP"))
- return keep_variables (lexer, dict);
- else if (lex_match_id (lexer, "RENAME"))
- return rename_variables (lexer, dict);
- else
- {
- lex_error (lexer, _("expecting a valid subcommand"));
- return false;
- }
-}
-
-/* Parses and performs the RENAME subcommand of GET and SAVE. */
-static bool
-rename_variables (struct lexer *lexer, struct dictionary *dict)
-{
- size_t i;
-
- int success = 0;
-
- struct variable **v;
- char **new_names;
- size_t nv, nn;
- char *err_name;
-
- int group;
-
- lex_match (lexer, '=');
- if (lex_token (lexer) != '(')
- {
- struct variable *v;
-
- v = parse_variable (lexer, dict);
- if (v == NULL)
- return 0;
- if (!lex_force_match (lexer, '=')
- || !lex_force_id (lexer))
- return 0;
- if (dict_lookup_var (dict, lex_tokid (lexer)) != NULL)
- {
- msg (SE, _("Cannot rename %s as %s because there already exists "
- "a variable named %s. To rename variables with "
- "overlapping names, use a single RENAME subcommand "
- "such as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, "
- "\"/RENAME (A B C=B C A)\"."),
- var_get_name (v), lex_tokid (lexer), lex_tokid (lexer));
- return 0;
- }
-
- dict_rename_var (dict, v, lex_tokid (lexer));
- lex_get (lexer);
- return 1;
- }
-
- nv = nn = 0;
- v = NULL;
- new_names = 0;
- group = 1;
- while (lex_match (lexer, '('))
- {
- size_t old_nv = nv;
-
- if (!parse_variables (lexer, dict, &v, &nv, PV_NO_DUPLICATE | PV_APPEND))
- goto done;
- if (!lex_match (lexer, '='))
- {
- msg (SE, _("`=' expected after variable list."));
- goto done;
- }
- if (!parse_DATA_LIST_vars (lexer, &new_names, &nn, PV_APPEND | PV_NO_SCRATCH))
- goto done;
- 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."),
- (unsigned) (nv - old_nv), (unsigned) (nn - old_nv), group);
- goto done;
- }
- if (!lex_force_match (lexer, ')'))
- goto done;
- group++;
- }
-
- if (!dict_rename_vars (dict, v, new_names, nv, &err_name))
- {
- msg (SE, _("Requested renaming duplicates variable name %s."), err_name);
- goto done;
- }
- success = 1;
-
- done:
- for (i = 0; i < nn; i++)
- free (new_names[i]);
- free (new_names);
- free (v);
-
- return success;
-}
-
-/* Parses and performs the DROP subcommand of GET and SAVE.
- Returns true if successful, false on failure.*/
-static bool
-drop_variables (struct lexer *lexer, struct dictionary *dict)
-{
- struct variable **v;
- size_t nv;
-
- lex_match (lexer, '=');
- if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
- return false;
- dict_delete_vars (dict, v, nv);
- free (v);
-
- if (dict_get_var_cnt (dict) == 0)
- {
- msg (SE, _("Cannot DROP all variables from dictionary."));
- return false;
- }
- return true;
-}
-
-/* Parses and performs the KEEP subcommand of GET and SAVE.
- Returns true if successful, false on failure.*/
-static bool
-keep_variables (struct lexer *lexer, struct dictionary *dict)
-{
- struct variable **v;
- size_t nv;
- size_t i;
-
- lex_match (lexer, '=');
- if (!parse_variables (lexer, dict, &v, &nv, PV_NONE))
- return false;
-
- /* Move the specified variables to the beginning. */
- dict_reorder_vars (dict, v, nv);
-
- /* Delete the remaining variables. */
- v = xnrealloc (v, dict_get_var_cnt (dict) - nv, sizeof *v);
- for (i = nv; i < dict_get_var_cnt (dict); i++)
- v[i - nv] = dict_get_var (dict, i);
- dict_delete_vars (dict, v, dict_get_var_cnt (dict) - nv);
- free (v);
-
- return true;
-}
-\f
-/* MATCH FILES. */
-
-/* File types. */
-enum mtf_type
- {
- MTF_FILE, /* Specified on FILE= subcommand. */
- MTF_TABLE /* Specified on TABLE= subcommand. */
- };
-
-/* One of the FILEs or TABLEs on MATCH FILES. */
-struct mtf_file
- {
- struct ll ll; /* In list of all files and tables. */
-
- enum mtf_type type;
- int sequence;
-
- const struct variable **by; /* List of BY variables for this file. */
- struct mtf_variable *vars; /* Variables to copy to output. */
- size_t var_cnt; /* Number of other variables. */
-
- struct file_handle *handle; /* Input file handle. */
- struct dictionary *dict; /* Input file dictionary. */
- struct casereader *reader; /* Input reader. */
- struct ccase input; /* Input record (null at end of file). */
-
- /* IN subcommand. */
- char *in_name; /* Variable name. */
- struct variable *in_var; /* Variable (in master dictionary). */
- };
-
-struct mtf_variable
- {
- struct variable *in_var;
- struct variable *out_var;
- };
-
-/* MATCH FILES procedure. */
-struct mtf_proc
- {
- struct ll_list files; /* List of "struct mtf_file"s. */
- int nonempty_files; /* FILEs that are not at end-of-file. */
-
- bool ok; /* False if I/O error occurs. */
-
- struct dictionary *dict; /* Dictionary of output file. */
- struct casewriter *output; /* MATCH FILES output. */
-
- size_t by_cnt; /* Number of variables on BY subcommand. */
-
- /* FIRST, LAST.
- Only if "first" or "last" is nonnull are the remaining
- members used. */
- struct variable *first; /* Variable specified on FIRST (if any). */
- struct variable *last; /* Variable specified on LAST (if any). */
- struct ccase buffered_case; /* Case ready for output except that we don't
- know the value for the LAST variable yet. */
- struct ccase prev_BY_case; /* Case with values of last set of BY vars. */
- const struct variable **prev_BY; /* Last set of BY variables. */
- };
-
-static void mtf_free (struct mtf_proc *);
-
-static bool mtf_close_all_files (struct mtf_proc *);
-static bool mtf_merge_dictionary (struct dictionary *const, struct mtf_file *);
-static bool mtf_read_record (struct mtf_proc *mtf, struct mtf_file *);
-
-static void mtf_process_case (struct mtf_proc *);
-
-static bool create_flag_var (const char *subcommand_name, const char *var_name,
- struct dictionary *, struct variable **);
-static char *var_type_description (struct variable *);
-
-/* Parse and execute the MATCH FILES command. */
-int
-cmd_match_files (struct lexer *lexer, struct dataset *ds)
-{
- struct mtf_proc mtf;
- struct ll *first_table;
- struct mtf_file *file, *next;
-
- bool saw_in = false;
- struct casereader *active_file = NULL;
-
- char first_name[LONG_NAME_LEN + 1] = "";
- char last_name[LONG_NAME_LEN + 1] = "";
-
- struct taint *taint = NULL;
-
- size_t i;
-
- ll_init (&mtf.files);
- mtf.nonempty_files = 0;
- first_table = ll_null (&mtf.files);
- mtf.dict = dict_create ();
- mtf.output = NULL;
- mtf.by_cnt = 0;
- mtf.first = mtf.last = NULL;
- case_nullify (&mtf.buffered_case);
- case_nullify (&mtf.prev_BY_case);
- mtf.prev_BY = NULL;
-
- dict_set_case_limit (mtf.dict, dict_get_case_limit (dataset_dict (ds)));
-
- lex_match (lexer, '/');
- while (lex_token (lexer) == T_ID
- && (lex_id_match (ss_cstr ("FILE"), ss_cstr (lex_tokid (lexer)))
- || lex_id_match (ss_cstr ("TABLE"), ss_cstr (lex_tokid (lexer)))))
- {
- struct mtf_file *file = xmalloc (sizeof *file);
- file->by = NULL;
- file->handle = NULL;
- file->reader = NULL;
- file->dict = NULL;
- file->in_name = NULL;
- file->in_var = NULL;
- file->var_cnt = 0;
- file->vars = NULL;
- case_nullify (&file->input);
-
- if (lex_match_id (lexer, "FILE"))
- {
- file->type = MTF_FILE;
- ll_insert (first_table, &file->ll);
- mtf.nonempty_files++;
- }
- else if (lex_match_id (lexer, "TABLE"))
- {
- file->type = MTF_TABLE;
- ll_push_tail (&mtf.files, &file->ll);
- if (first_table == ll_null (&mtf.files))
- first_table = &file->ll;
- }
- else
- NOT_REACHED ();
- lex_match (lexer, '=');
-
- if (lex_match (lexer, '*'))
- {
- if (!proc_has_active_file (ds))
- {
- msg (SE, _("Cannot specify the active file since no active "
- "file has been defined."));
- goto error;
- }
-
- if (proc_make_temporary_transformations_permanent (ds))
- msg (SE,
- _("MATCH FILES may not be used after TEMPORARY when "
- "the active file is an input source. "
- "Temporary transformations will be made permanent."));
-
- file->dict = dict_clone (dataset_dict (ds));
- }
- else
- {
- file->handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH);
- if (file->handle == NULL)