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);
50 /* Revises the contents of the fields tree view based on the
51 currently chosen set of separators. */
53 revise_fields_preview (PsppireImportAssistant *ia)
55 choose_column_names (ia);
59 struct separator_count_node
61 struct hmap_node node;
62 int occurance; /* The number of times the separator occurs in a line */
63 int quantity; /* The number of lines with this occurance */
67 /* Picks the most likely separator and quote characters based on
70 choose_likely_separators (PsppireImportAssistant *ia)
73 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
79 struct hmap count_map[SEPARATOR_CNT];
80 for (j = 0; j < SEPARATOR_CNT; ++j)
81 hmap_init (count_map + j);
83 GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
85 for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
87 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
89 gchar *line_text = NULL;
90 gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
92 gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
94 struct substring cs = ss_cstr (line_text);
96 UINT32_MAX != ss_first_mb (cs);
99 ucs4_t character = ss_first_mb (cs);
102 for (s = 0; s < SEPARATOR_CNT; ++s)
104 if (character == separators[s].c)
110 for (j = 0; j < SEPARATOR_CNT; ++j)
114 struct separator_count_node *cn = NULL;
115 unsigned int hash = hash_int (counts[j], 0);
116 HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
118 if (cn->occurance == counts[j])
124 struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
125 new_cn->occurance = counts[j];
126 new_cn->quantity = 1;
127 hmap_insert (&count_map[j], &new_cn->node, hash);
137 gtk_tree_path_free (p);
139 if (hmap_count (count_map) > 0)
141 int most_frequent = -1;
143 for (j = 0; j < SEPARATOR_CNT; ++j)
145 struct separator_count_node *cn;
146 struct separator_count_node *next;
147 HMAP_FOR_EACH_SAFE (cn, next, struct separator_count_node, node, &count_map[j])
149 if (largest < cn->quantity)
151 largest = cn->quantity;
156 hmap_destroy (&count_map[j]);
159 g_return_if_fail (most_frequent >= 0);
162 get_widget_assert (ia->text_builder, separators[most_frequent].name);
163 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
168 repopulate_delimiter_columns (PsppireImportAssistant *ia)
170 /* Remove all the columns */
171 while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
173 GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
174 gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
178 gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
180 /* ... and put them back again. */
182 for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
185 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
187 const gchar *title = NULL;
193 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
196 psppire_delimited_text_get_header_title
197 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
203 GtkTreeViewColumn *column =
204 gtk_tree_view_column_new_with_attributes (title,
208 g_object_set (column,
210 "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
213 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
218 reset_tree_view_model (PsppireImportAssistant *ia)
220 GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
222 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
225 repopulate_delimiter_columns (ia);
227 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
228 // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
233 /* Resets IA's intro page to its initial state. */
235 reset_intro_page (PsppireImportAssistant *ia)
237 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->n_cases_button),
239 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->percent_button),
241 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
244 gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->n_cases_spin), 1);
245 gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->percent_spin), 0);
248 /* Called when one of the radio buttons is clicked. */
250 on_intro_amount_changed (PsppireImportAssistant *ia)
252 gtk_widget_set_sensitive (ia->n_cases_spin,
253 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)));
255 gtk_widget_set_sensitive (ia->percent_spin,
256 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)));
260 on_treeview_selection_change (PsppireImportAssistant *ia)
262 GtkTreeSelection *selection =
263 gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
264 GtkTreeModel *model = NULL;
266 if (gtk_tree_selection_get_selected (selection, &model, &iter))
270 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
271 gint *index = gtk_tree_path_get_indices (path);
273 gtk_tree_path_free (path);
274 g_object_get (model, "maximum-lines", &max_lines, NULL);
275 gtk_widget_set_sensitive (ia->variable_names_cb,
276 (n > 0 && n < max_lines));
277 ia->delimiters_model =
278 psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
279 g_object_set (ia->delimiters_model, "first-line", n, NULL);
284 render_text_preview_line (GtkTreeViewColumn *tree_column,
285 GtkCellRenderer *cell,
286 GtkTreeModel *tree_model,
291 Set the text to a "insensitive" state if the row
292 is greater than what the user declared to be the maximum.
294 GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
295 gint *ii = gtk_tree_path_get_indices (path);
297 g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
298 g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
299 gtk_tree_path_free (path);
302 /* Resets IA's "first line" page to its initial state. */
304 reset_first_line_page (PsppireImportAssistant *ia)
306 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb), FALSE);
308 GtkTreeSelection *selection =
309 gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
311 gtk_tree_selection_unselect_all (selection);
314 /* Initializes IA's first_line substructure. */
316 first_line_page_create (PsppireImportAssistant *ia)
318 GtkWidget *w = get_widget_assert (ia->text_builder, "FirstLine");
320 g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
321 g_object_set_data (G_OBJECT (w), "on-reset", reset_first_line_page);
323 add_page_to_assistant (ia, w,
324 GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
326 GtkWidget *scrolled_window = get_widget_assert (ia->text_builder, "first-line-scroller");
328 if (ia->first_line_tree_view == NULL)
330 ia->first_line_tree_view = gtk_tree_view_new ();
331 g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
333 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
335 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
336 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
340 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
341 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
343 renderer = gtk_cell_renderer_text_new ();
344 column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
345 gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
347 gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
349 g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
350 G_CALLBACK (on_treeview_selection_change), ia);
351 gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
354 gtk_widget_show_all (scrolled_window);
356 ia->variable_names_cb = get_widget_assert (ia->text_builder, "variable-names");
358 reset_first_line_page (ia);
362 intro_on_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
364 if (dir != IMPORT_ASSISTANT_FORWARDS)
368 g_object_get (ia->text_file, "line-count", &lc, NULL);
369 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
371 gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
372 g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
374 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
376 gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
377 g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
381 g_object_set (ia->text_file, "maximum-lines", lc, NULL);
387 intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
389 GtkBuilder *builder = ia->text_builder;
390 GtkWidget *table = get_widget_assert (builder, "button-table");
395 ds_put_cstr (&s, _("This assistant will guide you through the process of "
396 "importing data into PSPP from a text file with one line "
397 "per case, in which fields are separated by tabs, "
398 "commas, or other delimiters.\n\n"));
402 if (ia->text_file->total_is_exact)
405 &s, ngettext ("The selected file contains %'lu line of text. ",
406 "The selected file contains %'lu lines of text. ",
407 ia->text_file->total_lines),
408 ia->text_file->total_lines);
410 else if (ia->text_file->total_lines > 0)
414 "The selected file contains approximately %'lu line of text. ",
415 "The selected file contains approximately %'lu lines of text. ",
416 ia->text_file->total_lines),
417 ia->text_file->total_lines);
420 "Only the first %zu line of the file will be shown for "
421 "preview purposes in the following screens. ",
422 "Only the first %zu lines of the file will be shown for "
423 "preview purposes in the following screens. ",
424 ia->text_file->line_cnt),
425 ia->text_file->line_cnt);
429 ds_put_cstr (&s, _("You may choose below how much of the file should "
430 "actually be imported."));
432 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
436 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
438 GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
439 gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
444 GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
445 gtk_adjustment_set_lower (adj, 1.0);
447 if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
449 GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
452 gtk_grid_attach (GTK_GRID (table), hbox_percent,
457 gtk_widget_show_all (table);
460 if (dir != IMPORT_ASSISTANT_FORWARDS)
463 reset_intro_page (ia);
464 on_intro_amount_changed (ia);
467 /* Initializes IA's intro substructure. */
469 intro_page_create (PsppireImportAssistant *ia)
471 GtkBuilder *builder = ia->text_builder;
473 GtkWidget *w = get_widget_assert (builder, "Intro");
475 ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 1);
477 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
479 ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
480 ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
481 ia->percent_button = get_widget_assert (builder, "import-percent");
483 g_signal_connect_swapped (ia->all_cases_button, "toggled",
484 G_CALLBACK (on_intro_amount_changed), ia);
485 g_signal_connect_swapped (ia->n_cases_button, "toggled",
486 G_CALLBACK (on_intro_amount_changed), ia);
487 g_signal_connect_swapped (ia->percent_button, "toggled",
488 G_CALLBACK (on_intro_amount_changed), ia);
490 g_object_set_data (G_OBJECT (w), "on-leaving", intro_on_leave);
491 g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
492 g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
498 /* Chooses a name for each column on the separators page */
500 choose_column_names (PsppireImportAssistant *ia)
503 unsigned long int generated_name_count = 0;
504 char *encoding = NULL;
505 g_object_get (ia->text_file, "encoding", &encoding, NULL);
507 dict_unref (ia->dict);
508 ia->dict = dict_create (encoding ? encoding : UTF8);
512 i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
515 const gchar *candidate_name = NULL;
517 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
519 candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
522 char *name = dict_make_unique_var_name (ia->dict,
524 &generated_name_count);
526 dict_create_var_assert (ia->dict, name, 0);
531 /* Called when the user toggles one of the separators
534 on_separator_toggle (GtkToggleButton *toggle UNUSED,
535 PsppireImportAssistant *ia)
538 GSList *delimiters = NULL;
539 for (i = 0; i < SEPARATOR_CNT; i++)
541 const struct separator *s = &separators[i];
542 GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
543 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
545 delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c));
549 g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
551 revise_fields_preview (ia);
555 /* Called when the user changes the entry field for custom
558 on_separators_custom_entry_notify (GObject *gobject UNUSED,
559 GParamSpec *arg1 UNUSED,
560 PsppireImportAssistant *ia)
562 revise_fields_preview (ia);
565 /* Called when the user toggles the checkbox that enables custom
568 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
569 PsppireImportAssistant *ia)
571 bool is_active = gtk_toggle_button_get_active (custom_cb);
572 gtk_widget_set_sensitive (ia->custom_entry, is_active);
573 revise_fields_preview (ia);
576 /* Called when the user changes the selection in the combo box
577 that selects a quote character. */
579 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
581 revise_fields_preview (ia);
584 /* Called when the user toggles the checkbox that enables
587 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
589 bool is_active = gtk_toggle_button_get_active (quote_cb);
590 gtk_widget_set_sensitive (ia->quote_combo, is_active);
591 revise_fields_preview (ia);
595 /* Called when the Reset button is clicked. */
597 reset_separators_page (PsppireImportAssistant *ia)
599 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE);
600 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_cb), FALSE);
601 gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
603 for (gint i = 0; i < SEPARATOR_CNT; i++)
605 const struct separator *s = &separators[i];
606 GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
607 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
610 repopulate_delimiter_columns (ia);
612 revise_fields_preview (ia);
613 choose_likely_separators (ia);
616 /* Called just before the separators page becomes visible in the
619 prepare_separators_page (PsppireImportAssistant *ia)
621 gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
622 GTK_TREE_MODEL (ia->delimiters_model));
624 g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
625 G_CALLBACK (reset_tree_view_model), ia);
628 reset_separators_page (ia);
632 /* Initializes IA's separators substructure. */
634 separators_page_create (PsppireImportAssistant *ia)
636 GtkBuilder *builder = ia->text_builder;
640 GtkWidget *w = get_widget_assert (builder, "Separators");
642 g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
643 g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
645 add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
647 ia->custom_cb = get_widget_assert (builder, "custom-cb");
648 ia->custom_entry = get_widget_assert (builder, "custom-entry");
649 ia->quote_combo = get_widget_assert (builder, "quote-combo");
650 ia->quote_cb = get_widget_assert (builder, "quote-cb");
652 gtk_widget_set_sensitive (ia->custom_entry,
653 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
655 gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
657 if (ia->fields_tree_view == NULL)
659 GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
660 ia->fields_tree_view = gtk_tree_view_new ();
661 g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
662 gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
663 gtk_widget_show_all (scroller);
666 g_signal_connect (ia->quote_combo, "changed",
667 G_CALLBACK (on_quote_combo_change), ia);
668 g_signal_connect (ia->quote_cb, "toggled",
669 G_CALLBACK (on_quote_cb_toggle), ia);
670 g_signal_connect (ia->custom_entry, "notify::text",
671 G_CALLBACK (on_separators_custom_entry_notify), ia);
672 g_signal_connect (ia->custom_cb, "toggled",
673 G_CALLBACK (on_separators_custom_cb_toggle), ia);
674 for (i = 0; i < SEPARATOR_CNT; i++)
675 g_signal_connect (get_widget_assert (builder, separators[i].name),
676 "toggled", G_CALLBACK (on_separator_toggle), ia);
678 reset_separators_page (ia);
683 static struct casereader_random_class my_casereader_class;
685 static struct ccase *
686 my_read (struct casereader *reader, void *aux, casenumber idx)
688 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
689 GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
691 GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
693 const struct caseproto *proto = casereader_get_proto (reader);
696 struct ccase *c = NULL;
697 if (gtk_tree_model_get_iter (tm, &iter, tp))
699 c = case_create (proto);
701 for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
704 gtk_tree_model_get_value (tm, &iter, i + 1, &value);
706 const struct variable *var = dict_get_var (ia->casereader_dict, i);
708 const gchar *ss = g_value_get_string (&value);
711 union value *v = case_data_rw (c, var);
712 /* In this reader we derive the union value from the
713 string in the tree_model. We retrieve the width and format
714 from a dictionary which is stored directly after
715 the reader creation. Changes in ia->dict in the
716 variable window are not reflected here and therefore
717 this is always compatible with the width in the
718 caseproto. See bug #58298 */
719 char *xx = data_in (ss_cstr (ss),
721 var_get_write_format (var)->type,
722 v, var_get_width (var), "UTF-8");
726 g_value_unset (&value);
730 gtk_tree_path_free (tp);
736 my_destroy (struct casereader *reader, void *aux)
738 g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
742 my_advance (struct casereader *reader, void *aux, casenumber cnt)
744 g_print ("%s:%d\n", __FILE__, __LINE__);
747 static struct casereader *
748 textfile_create_reader (PsppireImportAssistant *ia)
750 int n_vars = dict_get_var_cnt (ia->dict);
754 struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *);
755 for (i = 0 ; i < n_vars; ++i)
757 fg[i] = fmt_guesser_create ();
760 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
764 for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
766 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
768 for (i = 0 ; i < n_vars; ++i)
771 gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
773 fmt_guesser_add (fg[i], ss_cstr (s));
778 struct caseproto *proto = caseproto_create ();
779 for (i = 0 ; i < n_vars; ++i)
782 fmt_guesser_guess (fg[i], &fs);
784 fmt_fix (&fs, FMT_FOR_INPUT);
786 struct variable *var = dict_get_var (ia->dict, i);
788 int width = fmt_var_width (&fs);
790 var_set_width_and_formats (var, width,
793 proto = caseproto_add_width (proto, width);
794 fmt_guesser_destroy (fg[i]);
799 struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia);
800 /* Store the dictionary at this point when the casereader is created.
801 my_read depends on the dictionary to interpret the strings in the treeview.
802 This guarantees that the union value is produced according to the
803 caseproto in the reader. */
804 ia->casereader_dict = dict_clone (ia->dict);
805 caseproto_unref (proto);
810 /* When during import the variable type is changed, the reader is reinitialized
811 based on the new dictionary with a fresh caseprototype. The default behaviour
812 when a variable type is changed and the column is resized is that the union
813 value is interpreted with new variable type and an overlay for that column
814 is generated. Here we reinit to the original reader based on strings.
815 As a result you can switch from string to numeric to string without loosing
816 the string information. */
818 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
819 const struct variable *oldvar, gpointer data)
821 PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data);
823 struct caseproto *proto = caseproto_create();
824 for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
826 const struct variable *var = dict_get_var (ia->dict, i);
827 int width = var_get_width (var);
828 proto = caseproto_add_width (proto, width);
831 gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
833 PsppireDataStore *store = NULL;
834 g_object_get (ia->data_sheet, "data-model", &store, NULL);
836 struct casereader *cr = casereader_create_random (proto, n_rows,
837 &my_casereader_class, ia);
838 psppire_data_store_set_reader (store, cr);
839 dict_unref (ia->casereader_dict);
840 ia->casereader_dict = dict_clone (ia->dict);
844 /* Set the data model for both the data sheet and the variable sheet. */
846 textfile_set_data_models (PsppireImportAssistant *ia)
848 my_casereader_class.read = my_read;
849 my_casereader_class.destroy = my_destroy;
850 my_casereader_class.advance = my_advance;
852 struct casereader *reader = textfile_create_reader (ia);
854 PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
855 PsppireDataStore *store = psppire_data_store_new (dict);
856 psppire_data_store_set_reader (store, reader);
857 g_signal_connect (dict, "variable-changed",
858 G_CALLBACK (ia_variable_changed_cb),
861 g_object_set (ia->data_sheet, "data-model", store, NULL);
862 g_object_set (ia->var_sheet, "data-model", dict, NULL);
866 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
869 g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
872 ds_put_format (s, " /FIRSTCASE=%d\n", first_case + 1);
875 /* Emits PSPP syntax to S that applies the dictionary attributes
876 (such as missing values and value labels) of the variables in
879 apply_dict (const struct dictionary *dict, struct string *s)
881 size_t var_cnt = dict_get_var_cnt (dict);
884 for (i = 0; i < var_cnt; i++)
886 struct variable *var = dict_get_var (dict, i);
887 const char *name = var_get_name (var);
888 enum val_type type = var_get_type (var);
889 int width = var_get_width (var);
890 enum measure measure = var_get_measure (var);
891 enum var_role role = var_get_role (var);
892 enum alignment alignment = var_get_alignment (var);
893 const struct fmt_spec *format = var_get_print_format (var);
895 if (var_has_missing_values (var))
897 const struct missing_values *mv = var_get_missing_values (var);
900 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
901 for (j = 0; j < mv_n_values (mv); j++)
904 ds_put_cstr (s, ", ");
905 syntax_gen_value (s, mv_get_value (mv, j), width, format);
908 if (mv_has_range (mv))
911 if (mv_has_value (mv))
912 ds_put_cstr (s, ", ");
913 mv_get_range (mv, &low, &high);
914 syntax_gen_num_range (s, low, high, format);
916 ds_put_cstr (s, ").\n");
918 if (var_has_value_labels (var))
920 const struct val_labs *vls = var_get_value_labels (var);
921 const struct val_lab **labels = val_labs_sorted (vls);
922 size_t n_labels = val_labs_count (vls);
925 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
926 for (i = 0; i < n_labels; i++)
928 const struct val_lab *vl = labels[i];
929 ds_put_cstr (s, "\n ");
930 syntax_gen_value (s, &vl->value, width, format);
931 ds_put_byte (s, ' ');
932 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
935 ds_put_cstr (s, ".\n");
937 if (var_has_label (var))
938 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
939 name, var_get_label (var));
940 if (measure != var_default_measure (type))
941 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
942 name, measure_to_syntax (measure));
943 if (role != ROLE_INPUT)
944 syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
945 var_role_to_syntax (role), name);
946 if (alignment != var_default_alignment (type))
947 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
948 name, alignment_to_syntax (alignment));
949 if (var_get_display_width (var) != var_default_display_width (width))
950 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
951 name, var_get_display_width (var));
957 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
960 g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
962 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
963 ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
964 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
965 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
966 ds_put_format (s, "SAMPLE %.4g.\n",
967 gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
972 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
977 g_return_if_fail (ia->dict);
979 ds_put_cstr (s, " /VARIABLES=\n");
981 var_cnt = dict_get_var_cnt (ia->dict);
982 for (i = 0; i < var_cnt; i++)
984 struct variable *var = dict_get_var (ia->dict, i);
985 char format_string[FMT_STRING_LEN_MAX + 1];
986 fmt_to_string (var_get_print_format (var), format_string);
987 ds_put_format (s, " %s %s%s\n",
988 var_get_name (var), format_string,
989 i == var_cnt - 1 ? "." : "");
994 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
998 ds_put_cstr (s, " /DELIMITERS=\"");
1000 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
1001 ds_put_cstr (s, "\\t");
1002 for (i = 0; i < SEPARATOR_CNT; i++)
1004 const struct separator *seps = &separators[i];
1005 GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
1006 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1008 if (seps->c == '\t')
1011 ds_put_byte (s, seps->c);
1014 ds_put_cstr (s, "\"\n");
1016 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1018 GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1019 gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1020 if (quotes && *quotes)
1021 syntax_gen_pspp (s, " /QUALIFIER=%sq\n", quotes);
1028 text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1030 gchar *file_name = NULL;
1031 gchar *encoding = NULL;
1032 g_object_get (ia->text_file,
1033 "file-name", &file_name,
1034 "encoding", &encoding,
1037 if (file_name == NULL)
1045 if (encoding && strcmp (encoding, "Auto"))
1046 syntax_gen_pspp (s, " /ENCODING=%sq\n", encoding);
1049 " /ARRANGEMENT=DELIMITED\n"
1050 " /DELCASE=LINE\n");
1052 first_line_append_syntax (ia, s);
1053 separators_append_syntax (ia, s);
1055 formats_append_syntax (ia, s);
1056 apply_dict (ia->dict, s);
1057 intro_append_syntax (ia, s);