1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2015, 2016, 2017, 2018, 2020 Free Software Foundation
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "psppire-import-textfile.h"
22 #include "libpspp/i18n.h"
23 #include "libpspp/line-reader.h"
24 #include "libpspp/message.h"
25 #include "libpspp/hmap.h"
26 #include "libpspp/hash-functions.h"
27 #include "libpspp/str.h"
28 #include "libpspp/misc.h"
30 #include "data/casereader.h"
31 #include "data/casereader-provider.h"
32 #include "data/data-in.h"
33 #include "data/format-guesser.h"
34 #include "data/value-labels.h"
36 #include "builder-wrapper.h"
38 #include "psppire-data-store.h"
39 #include "psppire-scanf.h"
41 #include "ui/syntax-gen.h"
44 #define _(msgid) gettext (msgid)
45 #define N_(msgid) msgid
47 /* Chooses a name for each column on the separators page */
48 static void choose_column_names (PsppireImportAssistant *ia);
51 get_quote_character (const PsppireImportAssistant *ia)
53 GtkToggleButton *quote_single = GTK_TOGGLE_BUTTON (ia->quote_single);
54 GtkToggleButton *quote_double = GTK_TOGGLE_BUTTON (ia->quote_double);
55 GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom);
57 if (gtk_toggle_button_get_active (quote_custom))
59 GtkEntry *quote_custom_entry = GTK_ENTRY (ia->quote_custom_entry);
60 const gchar *text = gtk_entry_get_text (quote_custom_entry);
61 return text && text[0] ? g_utf8_get_char (text) : 0;
64 return (gtk_toggle_button_get_active (quote_single) ? '\''
65 : gtk_toggle_button_get_active (quote_double) ? '"'
69 /* Revises the contents of the fields tree view based on the
70 currently chosen set of separators and quotes. */
72 revise_fields_preview (PsppireImportAssistant *ia)
74 GSList *delimiters = NULL;
75 for (int i = 0; i < N_SEPARATORS; i++)
77 const struct separator *s = &separators[i];
78 GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
79 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
81 delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c));
85 g_object_set (ia->delimiters_model,
86 "delimiters", delimiters,
87 "quote", get_quote_character (ia),
90 choose_column_names (ia);
95 repopulate_delimiter_columns (PsppireImportAssistant *ia)
97 /* Remove all the columns */
98 while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
100 GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
101 gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
105 gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
107 /* ... and put them back again. */
109 for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
112 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
114 const gchar *title = NULL;
120 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
123 psppire_delimited_text_get_header_title
124 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
130 GtkTreeViewColumn *column =
131 gtk_tree_view_column_new_with_attributes (title,
135 g_object_set (column,
137 "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
140 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
145 reset_tree_view_model (PsppireImportAssistant *ia)
147 GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
149 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
152 repopulate_delimiter_columns (ia);
154 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
155 // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
160 /* Resets IA's intro page to its initial state. */
162 reset_intro_page (PsppireImportAssistant *ia)
164 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->n_cases_button),
166 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->percent_button),
168 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
171 gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->n_cases_spin), 1);
172 gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->percent_spin), 0);
175 /* Called when one of the radio buttons is clicked. */
177 on_intro_amount_changed (PsppireImportAssistant *ia)
179 gtk_widget_set_sensitive (ia->n_cases_spin,
180 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)));
182 gtk_widget_set_sensitive (ia->percent_spin,
183 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)));
187 on_treeview_selection_change (PsppireImportAssistant *ia)
189 GtkTreeSelection *selection =
190 gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
191 GtkTreeModel *model = NULL;
193 if (gtk_tree_selection_get_selected (selection, &model, &iter))
197 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
198 gint *index = gtk_tree_path_get_indices (path);
200 gtk_tree_path_free (path);
201 g_object_get (model, "maximum-lines", &max_lines, NULL);
202 gtk_widget_set_sensitive (ia->variable_names_cb,
203 (n > 0 && n < max_lines));
204 ia->delimiters_model =
205 psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
206 g_object_set (ia->delimiters_model, "first-line", n, NULL);
211 render_text_preview_line (GtkTreeViewColumn *tree_column,
212 GtkCellRenderer *cell,
213 GtkTreeModel *tree_model,
218 Set the text to a "insensitive" state if the row
219 is greater than what the user declared to be the maximum.
221 GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
222 gint *ii = gtk_tree_path_get_indices (path);
224 g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
225 g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
226 gtk_tree_path_free (path);
229 /* Resets IA's "first line" page to its initial state. */
231 reset_first_line_page (PsppireImportAssistant *ia)
233 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb), FALSE);
235 GtkTreeSelection *selection =
236 gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
238 gtk_tree_selection_unselect_all (selection);
241 /* Initializes IA's first_line substructure. */
243 first_line_page_create (PsppireImportAssistant *ia)
245 GtkWidget *w = get_widget_assert (ia->text_builder, "FirstLine");
247 g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
248 g_object_set_data (G_OBJECT (w), "on-reset", reset_first_line_page);
250 add_page_to_assistant (ia, w,
251 GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
253 GtkWidget *scrolled_window = get_widget_assert (ia->text_builder, "first-line-scroller");
255 if (ia->first_line_tree_view == NULL)
257 ia->first_line_tree_view = gtk_tree_view_new ();
258 g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
260 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
262 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
263 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
267 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
268 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
270 renderer = gtk_cell_renderer_text_new ();
271 column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
272 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
274 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
276 g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
277 G_CALLBACK (on_treeview_selection_change), ia);
278 gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
281 gtk_widget_show_all (scrolled_window);
283 ia->variable_names_cb = get_widget_assert (ia->text_builder, "variable-names");
285 reset_first_line_page (ia);
289 intro_on_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
291 if (dir != IMPORT_ASSISTANT_FORWARDS)
295 g_object_get (ia->text_file, "line-count", &lc, NULL);
296 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
298 gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
299 g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
301 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
303 gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
304 g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
308 g_object_set (ia->text_file, "maximum-lines", lc, NULL);
314 intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
316 GtkBuilder *builder = ia->text_builder;
317 GtkWidget *table = get_widget_assert (builder, "button-table");
322 ds_put_cstr (&s, _("This assistant will guide you through the process of "
323 "importing data into PSPP from a text file with one line "
324 "per case, in which fields are separated by tabs, "
325 "commas, or other delimiters.\n\n"));
329 if (ia->text_file->total_is_exact)
332 &s, ngettext ("The selected file contains %'lu line of text. ",
333 "The selected file contains %'lu lines of text. ",
334 ia->text_file->total_lines),
335 ia->text_file->total_lines);
337 else if (ia->text_file->total_lines > 0)
341 "The selected file contains approximately %'lu line of text. ",
342 "The selected file contains approximately %'lu lines of text. ",
343 ia->text_file->total_lines),
344 ia->text_file->total_lines);
347 "Only the first %zu line of the file will be shown for "
348 "preview purposes in the following screens. ",
349 "Only the first %zu lines of the file will be shown for "
350 "preview purposes in the following screens. ",
351 ia->text_file->n_lines),
352 ia->text_file->n_lines);
356 ds_put_cstr (&s, _("You may choose below how much of the file should "
357 "actually be imported."));
359 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
363 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
365 GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
366 gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
371 GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
372 gtk_adjustment_set_lower (adj, 1.0);
374 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
376 GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
379 gtk_grid_attach (GTK_GRID (table), hbox_percent,
384 gtk_widget_show_all (table);
387 if (dir != IMPORT_ASSISTANT_FORWARDS)
390 reset_intro_page (ia);
391 on_intro_amount_changed (ia);
394 /* Initializes IA's intro substructure. */
396 intro_page_create (PsppireImportAssistant *ia)
398 GtkBuilder *builder = ia->text_builder;
400 GtkWidget *w = get_widget_assert (builder, "Intro");
402 ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 1);
404 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
406 ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
407 ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
408 ia->percent_button = get_widget_assert (builder, "import-percent");
410 g_signal_connect_swapped (ia->all_cases_button, "toggled",
411 G_CALLBACK (on_intro_amount_changed), ia);
412 g_signal_connect_swapped (ia->n_cases_button, "toggled",
413 G_CALLBACK (on_intro_amount_changed), ia);
414 g_signal_connect_swapped (ia->percent_button, "toggled",
415 G_CALLBACK (on_intro_amount_changed), ia);
417 g_object_set_data (G_OBJECT (w), "on-leaving", intro_on_leave);
418 g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
419 g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
425 /* Chooses a name for each column on the separators page */
427 choose_column_names (PsppireImportAssistant *ia)
430 unsigned long int generated_name_count = 0;
431 char *encoding = NULL;
432 g_object_get (ia->text_file, "encoding", &encoding, NULL);
434 dict_unref (ia->dict);
435 ia->dict = dict_create (encoding ? encoding : UTF8);
439 i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
442 const gchar *candidate_name = NULL;
444 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
446 candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
449 char *name = dict_make_unique_var_name (ia->dict,
451 &generated_name_count);
453 dict_create_var_assert (ia->dict, name, 0);
458 /* Called when the user toggles one of the separators
461 on_separator_toggle (GtkToggleButton *toggle UNUSED,
462 PsppireImportAssistant *ia)
464 revise_fields_preview (ia);
468 /* Called when the user changes the entry field for custom
471 on_separators_custom_entry_notify (GObject *gobject UNUSED,
472 GParamSpec *arg1 UNUSED,
473 PsppireImportAssistant *ia)
475 revise_fields_preview (ia);
478 /* Called when the user toggles the checkbox that enables custom
481 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
482 PsppireImportAssistant *ia)
484 bool is_active = gtk_toggle_button_get_active (custom_cb);
485 gtk_widget_set_sensitive (ia->custom_entry, is_active);
486 revise_fields_preview (ia);
489 /* Called when the user changes any of the quote settings. */
491 on_quote_change (GtkWidget *widget UNUSED, PsppireImportAssistant *ia)
493 GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom);
494 bool is_custom = gtk_toggle_button_get_active (quote_custom);
495 gtk_widget_set_sensitive (ia->quote_custom_entry, is_custom);
496 revise_fields_preview (ia);
500 on_quote_custom_entry_change (GObject *gobject UNUSED,
501 GParamSpec *arg1 UNUSED,
502 PsppireImportAssistant *ia)
504 revise_fields_preview (ia);
507 /* Called when the Reset button is clicked. */
509 reset_separators_page (PsppireImportAssistant *ia)
511 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE);
512 gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
513 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_double), TRUE);
514 gtk_entry_set_text (GTK_ENTRY (ia->quote_custom_entry), "");
516 for (gint i = 0; i < N_SEPARATORS; i++)
518 const struct separator *s = &separators[i];
519 GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
520 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), s->c == ',');
523 if (ia->delimiters_model)
525 repopulate_delimiter_columns (ia);
527 revise_fields_preview (ia);
531 /* Called just before the separators page becomes visible in the
534 prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *new_page, enum IMPORT_ASSISTANT_DIRECTION dir)
536 if (dir != IMPORT_ASSISTANT_FORWARDS)
539 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
540 GTK_TREE_MODEL (ia->delimiters_model));
542 g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
543 G_CALLBACK (reset_tree_view_model), ia);
546 reset_separators_page (ia);
550 /* Initializes IA's separators substructure. */
552 separators_page_create (PsppireImportAssistant *ia)
554 GtkBuilder *builder = ia->text_builder;
558 GtkWidget *w = get_widget_assert (builder, "Separators");
560 g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
561 g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
563 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
565 ia->custom_cb = get_widget_assert (builder, "custom-cb");
566 ia->custom_entry = get_widget_assert (builder, "custom-entry");
567 ia->quote_none = get_widget_assert (builder, "quote-none");
568 ia->quote_single = get_widget_assert (builder, "quote-single");
569 ia->quote_double = get_widget_assert (builder, "quote-double");
570 ia->quote_custom = get_widget_assert (builder, "quote-custom");
571 ia->quote_custom_entry = get_widget_assert (builder, "quote-custom-entry");
573 gtk_widget_set_sensitive (ia->custom_entry,
574 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
576 gtk_entry_set_max_length (GTK_ENTRY (ia->quote_custom_entry), 1);
578 if (ia->fields_tree_view == NULL)
580 GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
581 ia->fields_tree_view = gtk_tree_view_new ();
582 g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
583 gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
584 gtk_widget_show_all (scroller);
587 g_signal_connect (ia->quote_none, "toggled", G_CALLBACK (on_quote_change), ia);
588 g_signal_connect (ia->quote_single, "toggled", G_CALLBACK (on_quote_change), ia);
589 g_signal_connect (ia->quote_double, "toggled", G_CALLBACK (on_quote_change), ia);
590 g_signal_connect (ia->quote_custom, "toggled", G_CALLBACK (on_quote_change), ia);
591 g_signal_connect (ia->quote_custom_entry, "notify::text", G_CALLBACK (on_quote_custom_entry_change), ia);
592 g_signal_connect (ia->custom_entry, "notify::text",
593 G_CALLBACK (on_separators_custom_entry_notify), ia);
594 g_signal_connect (ia->custom_cb, "toggled",
595 G_CALLBACK (on_separators_custom_cb_toggle), ia);
596 for (i = 0; i < N_SEPARATORS; i++)
597 g_signal_connect (get_widget_assert (builder, separators[i].name),
598 "toggled", G_CALLBACK (on_separator_toggle), ia);
600 reset_separators_page (ia);
605 static struct casereader_random_class my_casereader_class;
607 static struct ccase *
608 my_read (struct casereader *reader, void *aux, casenumber idx)
610 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
611 GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
613 GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
615 const struct caseproto *proto = casereader_get_proto (reader);
618 struct ccase *c = NULL;
619 if (gtk_tree_model_get_iter (tm, &iter, tp))
621 c = case_create (proto);
623 for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
626 gtk_tree_model_get_value (tm, &iter, i + 1, &value);
628 const struct variable *var = dict_get_var (ia->casereader_dict, i);
630 const gchar *ss = g_value_get_string (&value);
633 union value *v = case_data_rw (c, var);
634 /* In this reader we derive the union value from the
635 string in the tree_model. We retrieve the width and format
636 from a dictionary which is stored directly after
637 the reader creation. Changes in ia->dict in the
638 variable window are not reflected here and therefore
639 this is always compatible with the width in the
640 caseproto. See bug #58298 */
641 char *xx = data_in (ss_cstr (ss),
643 var_get_write_format (var)->type,
644 settings_get_fmt_settings (),
645 v, var_get_width (var), "UTF-8");
649 g_value_unset (&value);
653 gtk_tree_path_free (tp);
659 my_destroy (struct casereader *reader, void *aux)
661 g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
665 my_advance (struct casereader *reader, void *aux, casenumber n)
667 g_print ("%s:%d\n", __FILE__, __LINE__);
670 static struct casereader *
671 textfile_create_reader (PsppireImportAssistant *ia)
673 int n_vars = dict_get_n_vars (ia->dict);
677 struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *);
678 for (i = 0 ; i < n_vars; ++i)
680 fg[i] = fmt_guesser_create ();
683 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
687 for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
689 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
691 for (i = 0 ; i < n_vars; ++i)
694 gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
696 fmt_guesser_add (fg[i], ss_cstr (s));
701 struct caseproto *proto = caseproto_create ();
702 for (i = 0 ; i < n_vars; ++i)
705 fmt_guesser_guess (fg[i], &fs);
707 fmt_fix (&fs, FMT_FOR_INPUT);
709 struct variable *var = dict_get_var (ia->dict, i);
711 int width = fmt_var_width (&fs);
713 var_set_width_and_formats (var, width,
716 proto = caseproto_add_width (proto, width);
717 fmt_guesser_destroy (fg[i]);
722 struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
723 /* Store the dictionary at this point when the casereader is created.
724 my_read depends on the dictionary to interpret the strings in the treeview.
725 This guarantees that the union value is produced according to the
726 caseproto in the reader. */
727 ia->casereader_dict = dict_clone (ia->dict);
728 caseproto_unref (proto);
733 /* When during import the variable type is changed, the reader is reinitialized
734 based on the new dictionary with a fresh caseprototype. The default behaviour
735 when a variable type is changed and the column is resized is that the union
736 value is interpreted with new variable type and an overlay for that column
737 is generated. Here we reinit to the original reader based on strings.
738 As a result you can switch from string to numeric to string without loosing
739 the string information. */
741 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
742 const struct variable *oldvar, gpointer data)
744 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
746 struct caseproto *proto = caseproto_create();
747 for (int i = 0; i < dict_get_n_vars (ia->dict); i++)
749 const struct variable *var = dict_get_var (ia->dict, i);
750 int width = var_get_width (var);
751 proto = caseproto_add_width (proto, width);
754 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
756 PsppireDataStore *store = NULL;
757 g_object_get (ia->data_sheet, "data-model", &store, NULL);
759 struct casereader *cr = casereader_create_random (proto, n_rows,
760 &my_casereader_class, ia);
761 psppire_data_store_set_reader (store, cr);
762 dict_unref (ia->casereader_dict);
763 ia->casereader_dict = dict_clone (ia->dict);
767 /* Set the data model for both the data sheet and the variable sheet. */
769 textfile_set_data_models (PsppireImportAssistant *ia)
771 my_casereader_class.read = my_read;
772 my_casereader_class.destroy = my_destroy;
773 my_casereader_class.advance = my_advance;
775 struct casereader *reader = textfile_create_reader (ia);
777 PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
778 PsppireDataStore *store = psppire_data_store_new (dict);
779 psppire_data_store_set_reader (store, reader);
780 g_signal_connect (dict, "variable-changed",
781 G_CALLBACK (ia_variable_changed_cb),
784 g_object_set (ia->data_sheet, "data-model", store, NULL);
785 g_object_set (ia->var_sheet, "data-model", dict, NULL);
789 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
792 g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
795 ds_put_format (s, " /FIRSTCASE=%d\n", first_case + 1);
798 /* Emits PSPP syntax to S that applies the dictionary attributes
799 (such as missing values and value labels) of the variables in
802 apply_dict (const struct dictionary *dict, struct string *s)
804 size_t n_vars = dict_get_n_vars (dict);
806 for (size_t i = 0; i < n_vars; i++)
808 struct variable *var = dict_get_var (dict, i);
809 const char *name = var_get_name (var);
810 enum val_type type = var_get_type (var);
811 int width = var_get_width (var);
812 enum measure measure = var_get_measure (var);
813 enum var_role role = var_get_role (var);
814 enum alignment alignment = var_get_alignment (var);
815 const struct fmt_spec *format = var_get_print_format (var);
817 if (var_has_missing_values (var))
819 const struct missing_values *mv = var_get_missing_values (var);
822 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
823 for (j = 0; j < mv_n_values (mv); j++)
826 ds_put_cstr (s, ", ");
827 syntax_gen_value (s, mv_get_value (mv, j), width, format);
830 if (mv_has_range (mv))
833 if (mv_has_value (mv))
834 ds_put_cstr (s, ", ");
835 mv_get_range (mv, &low, &high);
836 syntax_gen_num_range (s, low, high, format);
838 ds_put_cstr (s, ").\n");
840 if (var_has_value_labels (var))
842 const struct val_labs *vls = var_get_value_labels (var);
843 const struct val_lab **labels = val_labs_sorted (vls);
844 size_t n_labels = val_labs_count (vls);
846 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
847 for (size_t j = 0; j < n_labels; j++)
849 const struct val_lab *vl = labels[j];
850 ds_put_cstr (s, "\n ");
851 syntax_gen_value (s, &vl->value, width, format);
852 ds_put_byte (s, ' ');
853 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
856 ds_put_cstr (s, ".\n");
858 if (var_has_label (var))
859 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
860 name, var_get_label (var));
861 if (measure != var_default_measure_for_type (type)
862 && measure != MEASURE_UNKNOWN)
863 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
864 name, measure_to_syntax (measure));
865 if (role != ROLE_INPUT)
866 syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
867 var_role_to_syntax (role), name);
868 if (alignment != var_default_alignment (type))
869 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
870 name, alignment_to_syntax (alignment));
871 if (var_get_display_width (var) != var_default_display_width (width))
872 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
873 name, var_get_display_width (var));
879 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
882 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
884 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
885 ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
886 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
887 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
888 ds_put_format (s, "SAMPLE %.4g.\n",
889 gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
894 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
896 g_return_if_fail (ia->dict);
898 ds_put_cstr (s, " /VARIABLES=\n");
900 int n_vars = dict_get_n_vars (ia->dict);
901 for (int i = 0; i < n_vars; i++)
903 struct variable *var = dict_get_var (ia->dict, i);
904 char format_string[FMT_STRING_LEN_MAX + 1];
905 fmt_to_string (var_get_print_format (var), format_string);
906 ds_put_format (s, " %s %s%s\n",
907 var_get_name (var), format_string,
908 i == n_vars - 1 ? "." : "");
913 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
917 ds_put_cstr (s, " /DELIMITERS=\"");
919 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
920 ds_put_cstr (s, "\\t");
921 for (i = 0; i < N_SEPARATORS; i++)
923 const struct separator *seps = &separators[i];
924 GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
925 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
930 ds_put_byte (s, seps->c);
933 ds_put_cstr (s, "\"\n");
935 gunichar quote = get_quote_character (ia);
938 GString *quote_s = g_string_new (NULL);
939 g_string_append_unichar (quote_s, quote);
940 syntax_gen_pspp (s, " /QUALIFIER=%sq\n", quote_s->str);
941 g_string_free (quote_s, TRUE);
947 text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
949 gchar *file_name = NULL;
950 gchar *encoding = NULL;
951 g_object_get (ia->text_file,
952 "file-name", &file_name,
953 "encoding", &encoding,
956 if (file_name == NULL)
964 if (encoding && strcmp (encoding, "Auto"))
965 syntax_gen_pspp (s, " /ENCODING=%sq\n", encoding);
968 " /ARRANGEMENT=DELIMITED\n"
971 first_line_append_syntax (ia, s);
972 separators_append_syntax (ia, s);
974 formats_append_syntax (ia, s);
975 apply_dict (ia->dict, s);
976 intro_append_syntax (ia, s);