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)
704 struct fmt_spec fs = fmt_guesser_guess (fg[i]);
706 fmt_fix (&fs, FMT_FOR_INPUT);
708 struct variable *var = dict_get_var (ia->dict, i);
710 int width = fmt_var_width (fs);
712 var_set_width_and_formats (var, width,
715 proto = caseproto_add_width (proto, width);
716 fmt_guesser_destroy (fg[i]);
721 struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
722 /* Store the dictionary at this point when the casereader is created.
723 my_read depends on the dictionary to interpret the strings in the treeview.
724 This guarantees that the union value is produced according to the
725 caseproto in the reader. */
726 ia->casereader_dict = dict_clone (ia->dict);
727 caseproto_unref (proto);
732 /* When during import the variable type is changed, the reader is reinitialized
733 based on the new dictionary with a fresh caseprototype. The default behaviour
734 when a variable type is changed and the column is resized is that the union
735 value is interpreted with new variable type and an overlay for that column
736 is generated. Here we reinit to the original reader based on strings.
737 As a result you can switch from string to numeric to string without loosing
738 the string information. */
740 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
741 const struct variable *oldvar, gpointer data)
743 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
745 struct caseproto *proto = caseproto_create();
746 for (int i = 0; i < dict_get_n_vars (ia->dict); i++)
748 const struct variable *var = dict_get_var (ia->dict, i);
749 int width = var_get_width (var);
750 proto = caseproto_add_width (proto, width);
753 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
755 PsppireDataStore *store = NULL;
756 g_object_get (ia->data_sheet, "data-model", &store, NULL);
758 struct casereader *cr = casereader_create_random (proto, n_rows,
759 &my_casereader_class, ia);
760 psppire_data_store_set_reader (store, cr);
761 dict_unref (ia->casereader_dict);
762 ia->casereader_dict = dict_clone (ia->dict);
766 /* Set the data model for both the data sheet and the variable sheet. */
768 textfile_set_data_models (PsppireImportAssistant *ia)
770 my_casereader_class.read = my_read;
771 my_casereader_class.destroy = my_destroy;
772 my_casereader_class.advance = my_advance;
774 struct casereader *reader = textfile_create_reader (ia);
776 PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
777 PsppireDataStore *store = psppire_data_store_new (dict);
778 psppire_data_store_set_reader (store, reader);
779 g_signal_connect (dict, "variable-changed",
780 G_CALLBACK (ia_variable_changed_cb),
783 g_object_set (ia->data_sheet, "data-model", store, NULL);
784 g_object_set (ia->var_sheet, "data-model", dict, NULL);
788 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
791 g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
794 ds_put_format (s, " /FIRSTCASE=%d\n", first_case + 1);
797 /* Emits PSPP syntax to S that applies the dictionary attributes
798 (such as missing values and value labels) of the variables in
801 apply_dict (const struct dictionary *dict, struct string *s)
803 size_t n_vars = dict_get_n_vars (dict);
805 for (size_t i = 0; i < n_vars; i++)
807 struct variable *var = dict_get_var (dict, i);
808 const char *name = var_get_name (var);
809 enum val_type type = var_get_type (var);
810 int width = var_get_width (var);
811 enum measure measure = var_get_measure (var);
812 enum var_role role = var_get_role (var);
813 enum alignment alignment = var_get_alignment (var);
814 struct fmt_spec format = var_get_print_format (var);
816 if (var_has_missing_values (var))
818 const struct missing_values *mv = var_get_missing_values (var);
821 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
822 for (j = 0; j < mv_n_values (mv); j++)
825 ds_put_cstr (s, ", ");
826 syntax_gen_value (s, mv_get_value (mv, j), width, &format);
829 if (mv_has_range (mv))
832 if (mv_has_value (mv))
833 ds_put_cstr (s, ", ");
834 mv_get_range (mv, &low, &high);
835 syntax_gen_num_range (s, low, high, &format);
837 ds_put_cstr (s, ").\n");
839 if (var_has_value_labels (var))
841 const struct val_labs *vls = var_get_value_labels (var);
842 const struct val_lab **labels = val_labs_sorted (vls);
843 size_t n_labels = val_labs_count (vls);
845 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
846 for (size_t j = 0; j < n_labels; j++)
848 const struct val_lab *vl = labels[j];
849 ds_put_cstr (s, "\n ");
850 syntax_gen_value (s, &vl->value, width, &format);
851 ds_put_byte (s, ' ');
852 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
855 ds_put_cstr (s, ".\n");
857 if (var_has_label (var))
858 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
859 name, var_get_label (var));
860 if (measure != var_default_measure_for_type (type)
861 && measure != MEASURE_UNKNOWN)
862 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
863 name, measure_to_syntax (measure));
864 if (role != ROLE_INPUT)
865 syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
866 var_role_to_syntax (role), name);
867 if (alignment != var_default_alignment (type))
868 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
869 name, alignment_to_syntax (alignment));
870 if (var_get_display_width (var) != var_default_display_width (width))
871 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
872 name, var_get_display_width (var));
878 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
881 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
883 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
884 ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
885 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
886 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
887 ds_put_format (s, "SAMPLE %.4g.\n",
888 gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
893 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
895 g_return_if_fail (ia->dict);
897 ds_put_cstr (s, " /VARIABLES=\n");
899 int n_vars = dict_get_n_vars (ia->dict);
900 for (int i = 0; i < n_vars; i++)
902 struct variable *var = dict_get_var (ia->dict, i);
903 char format_string[FMT_STRING_LEN_MAX + 1];
904 fmt_to_string (var_get_print_format (var), format_string);
905 ds_put_format (s, " %s %s%s\n",
906 var_get_name (var), format_string,
907 i == n_vars - 1 ? "." : "");
912 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
916 ds_put_cstr (s, " /DELIMITERS=\"");
918 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
919 ds_put_cstr (s, "\\t");
920 for (i = 0; i < N_SEPARATORS; i++)
922 const struct separator *seps = &separators[i];
923 GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
924 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
929 ds_put_byte (s, seps->c);
932 ds_put_cstr (s, "\"\n");
934 gunichar quote = get_quote_character (ia);
937 GString *quote_s = g_string_new (NULL);
938 g_string_append_unichar (quote_s, quote);
939 syntax_gen_pspp (s, " /QUALIFIER=%sq\n", quote_s->str);
940 g_string_free (quote_s, TRUE);
946 text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
948 gchar *file_name = NULL;
949 gchar *encoding = NULL;
950 g_object_get (ia->text_file,
951 "file-name", &file_name,
952 "encoding", &encoding,
955 if (file_name == NULL)
963 if (encoding && strcmp (encoding, "Auto"))
964 syntax_gen_pspp (s, " /ENCODING=%sq\n", encoding);
967 " /ARRANGEMENT=DELIMITED\n"
970 first_line_append_syntax (ia, s);
971 separators_append_syntax (ia, s);
973 formats_append_syntax (ia, s);
974 apply_dict (ia->dict, s);
975 intro_append_syntax (ia, s);