/* PSPPIRE - a graphical user interface for PSPP. Copyright (C) 2015, 2016, 2017, 2018, 2020 Free Software Foundation This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "psppire-import-textfile.h" #include #include "libpspp/i18n.h" #include "libpspp/line-reader.h" #include "libpspp/message.h" #include "libpspp/hmap.h" #include "libpspp/hash-functions.h" #include "libpspp/str.h" #include "libpspp/misc.h" #include "data/casereader.h" #include "data/casereader-provider.h" #include "data/data-in.h" #include "data/format-guesser.h" #include "data/value-labels.h" #include "builder-wrapper.h" #include "psppire-data-store.h" #include "psppire-scanf.h" #include "ui/syntax-gen.h" #include #define _(msgid) gettext (msgid) #define N_(msgid) msgid /* Chooses a name for each column on the separators page */ static void choose_column_names (PsppireImportAssistant *ia); static gunichar get_quote_character (const PsppireImportAssistant *ia) { GtkToggleButton *quote_single = GTK_TOGGLE_BUTTON (ia->quote_single); GtkToggleButton *quote_double = GTK_TOGGLE_BUTTON (ia->quote_double); GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom); if (gtk_toggle_button_get_active (quote_custom)) { GtkEntry *quote_custom_entry = GTK_ENTRY (ia->quote_custom_entry); const gchar *text = gtk_entry_get_text (quote_custom_entry); return text && text[0] ? g_utf8_get_char (text) : 0; } else return (gtk_toggle_button_get_active (quote_single) ? '\'' : gtk_toggle_button_get_active (quote_double) ? '"' : 0); } /* Revises the contents of the fields tree view based on the currently chosen set of separators and quotes. */ static void revise_fields_preview (PsppireImportAssistant *ia) { GSList *delimiters = NULL; for (int i = 0; i < N_SEPARATORS; i++) { const struct separator *s = &separators[i]; GtkWidget *button = get_widget_assert (ia->text_builder, s->name); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c)); } } g_object_set (ia->delimiters_model, "delimiters", delimiters, "quote", get_quote_character (ia), NULL); choose_column_names (ia); } static void repopulate_delimiter_columns (PsppireImportAssistant *ia) { /* Remove all the columns */ while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view))> 0) { GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0); gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc); } gint n_fields = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)); /* ... and put them back again. */ gint f; for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)); f < n_fields; f++) { GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); const gchar *title = NULL; if (f == 0) title = _("line"); else { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb))) { title = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1); } if (title == NULL) title = _("var"); } GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (title, renderer, "text", f, NULL); g_object_set (column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column); } } static void reset_tree_view_model (PsppireImportAssistant *ia) { GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view)); g_object_ref (tm); gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL); repopulate_delimiter_columns (ia); gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm); // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view)); g_object_unref (tm); } /* Resets IA's intro page to its initial state. */ static void reset_intro_page (PsppireImportAssistant *ia) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->n_cases_button), TRUE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->percent_button), TRUE); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button), TRUE); gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->n_cases_spin), 1); gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->percent_spin), 0); } /* Called when one of the radio buttons is clicked. */ static void on_intro_amount_changed (PsppireImportAssistant *ia) { gtk_widget_set_sensitive (ia->n_cases_spin, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button))); gtk_widget_set_sensitive (ia->percent_spin, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button))); } static void on_treeview_selection_change (PsppireImportAssistant *ia) { GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view)); GtkTreeModel *model = NULL; GtkTreeIter iter; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gint max_lines; int n; GtkTreePath *path = gtk_tree_model_get_path (model, &iter); gint *index = gtk_tree_path_get_indices (path); n = *index; gtk_tree_path_free (path); g_object_get (model, "maximum-lines", &max_lines, NULL); gtk_widget_set_sensitive (ia->variable_names_cb, (n> 0 && n < max_lines)); ia->delimiters_model = psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file)); g_object_set (ia->delimiters_model, "first-line", n, NULL); } } static void render_text_preview_line (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { /* Set the text to a "insensitive" state if the row is greater than what the user declared to be the maximum. */ GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter); gint *ii = gtk_tree_path_get_indices (path); gint max_lines; g_object_get (tree_model, "maximum-lines", &max_lines, NULL); g_object_set (cell, "sensitive", (*ii < max_lines), NULL); gtk_tree_path_free (path); } /* Resets IA's "first line" page to its initial state. */ static void reset_first_line_page (PsppireImportAssistant *ia) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb), TRUE); GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view)); gtk_tree_selection_unselect_all (selection); } /* Initializes IA's first_line substructure. */ void first_line_page_create (PsppireImportAssistant *ia) { GtkWidget *w = get_widget_assert (ia->text_builder, "FirstLine"); g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change); g_object_set_data (G_OBJECT (w), "on-reset", reset_first_line_page); add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line")); GtkWidget *scrolled_window = get_widget_assert (ia->text_builder, "first-line-scroller"); if (ia->first_line_tree_view == NULL) { ia->first_line_tree_view = gtk_tree_view_new (); g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer, "text", 0, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0); gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0); gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column); g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed", G_CALLBACK (on_treeview_selection_change), ia); gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view); } gtk_widget_show_all (scrolled_window); ia->variable_names_cb = get_widget_assert (ia->text_builder, "variable-names"); reset_first_line_page (ia); } static void intro_on_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir) { if (dir != IMPORT_ASSISTANT_FORWARDS) return; gint lc = 0; g_object_get (ia->text_file, "line-count", &lc, NULL); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button))) { gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)); g_object_set (ia->text_file, "maximum-lines", max_lines, NULL); } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button))) { gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)); g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL); } else { g_object_set (ia->text_file, "maximum-lines", lc, NULL); } } static void intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir) { GtkBuilder *builder = ia->text_builder; GtkWidget *table = get_widget_assert (builder, "button-table"); struct string s; ds_init_empty (&s); ds_put_cstr (&s, _("This assistant will guide you through the process of " "importing data into PSPP from a text file with one line " "per case, in which fields are separated by tabs, " "commas, or other delimiters.\n\n")); if (ia->text_file) { if (ia->text_file->total_is_exact) { ds_put_format ( &s, ngettext ("The selected file contains %'lu line of text. ", "The selected file contains %'lu lines of text. ", ia->text_file->total_lines), ia->text_file->total_lines); } else if (ia->text_file->total_lines> 0) { ds_put_format ( &s, ngettext ( "The selected file contains approximately %'lu line of text. ", "The selected file contains approximately %'lu lines of text. ", ia->text_file->total_lines), ia->text_file->total_lines); ds_put_format ( &s, ngettext ( "Only the first %zu line of the file will be shown for " "preview purposes in the following screens. ", "Only the first %zu lines of the file will be shown for " "preview purposes in the following screens. ", ia->text_file->n_lines), ia->text_file->n_lines); } } ds_put_cstr (&s, _("You may choose below how much of the file should " "actually be imported.")); gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")), ds_cstr (&s)); ds_destroy (&s); if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL) { GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin); gtk_grid_attach (GTK_GRID (table), hbox_n_cases, 1, 1, 1, 1); } GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin)); gtk_adjustment_set_lower (adj, 1.0); if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL) { GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &ia->percent_spin); gtk_grid_attach (GTK_GRID (table), hbox_percent, 1, 2, 1, 1); } gtk_widget_show_all (table); if (dir != IMPORT_ASSISTANT_FORWARDS) return; reset_intro_page (ia); on_intro_amount_changed (ia); } /* Initializes IA's intro substructure. */ void intro_page_create (PsppireImportAssistant *ia) { GtkBuilder *builder = ia->text_builder; GtkWidget *w = get_widget_assert (builder, "Intro"); ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 1); add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import")); ia->all_cases_button = get_widget_assert (builder, "import-all-cases"); ia->n_cases_button = get_widget_assert (builder, "import-n-cases"); ia->percent_button = get_widget_assert (builder, "import-percent"); g_signal_connect_swapped (ia->all_cases_button, "toggled", G_CALLBACK (on_intro_amount_changed), ia); g_signal_connect_swapped (ia->n_cases_button, "toggled", G_CALLBACK (on_intro_amount_changed), ia); g_signal_connect_swapped (ia->percent_button, "toggled", G_CALLBACK (on_intro_amount_changed), ia); g_object_set_data (G_OBJECT (w), "on-leaving", intro_on_leave); g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter); g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page); } /* Chooses a name for each column on the separators page */ static void choose_column_names (PsppireImportAssistant *ia) { int i; char *encoding = NULL; g_object_get (ia->text_file, "encoding", &encoding, NULL); if (ia->dict) dict_unref (ia->dict); ia->dict = dict_create (encoding ? encoding : UTF8); g_free (encoding); for (i = 0; i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1; ++i) { const gchar *candidate_name = NULL; if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb))) { candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i); } dict_create_var_with_unique_name (ia->dict, candidate_name, 0); } } /* Called when the user toggles one of the separators checkboxes. */ static void on_separator_toggle (GtkToggleButton *toggle UNUSED, PsppireImportAssistant *ia) { revise_fields_preview (ia); } /* Called when the user changes the entry field for custom separators. */ static void on_separators_custom_entry_notify (GObject *gobject UNUSED, GParamSpec *arg1 UNUSED, PsppireImportAssistant *ia) { revise_fields_preview (ia); } /* Called when the user toggles the checkbox that enables custom separators. */ static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, PsppireImportAssistant *ia) { bool is_active = gtk_toggle_button_get_active (custom_cb); gtk_widget_set_sensitive (ia->custom_entry, is_active); revise_fields_preview (ia); } /* Called when the user changes any of the quote settings. */ static void on_quote_change (GtkWidget *widget UNUSED, PsppireImportAssistant *ia) { GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom); bool is_custom = gtk_toggle_button_get_active (quote_custom); gtk_widget_set_sensitive (ia->quote_custom_entry, is_custom); revise_fields_preview (ia); } static void on_quote_custom_entry_change (GObject *gobject UNUSED, GParamSpec *arg1 UNUSED, PsppireImportAssistant *ia) { revise_fields_preview (ia); } /* Called when the Reset button is clicked. */ static void reset_separators_page (PsppireImportAssistant *ia) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE); gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), ""); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_double), TRUE); gtk_entry_set_text (GTK_ENTRY (ia->quote_custom_entry), ""); for (gint i = 0; i < N_SEPARATORS; i++) { const struct separator *s = &separators[i]; GtkWidget *button = get_widget_assert (ia->text_builder, s->name); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), s->c == ','); } if (ia->delimiters_model) { repopulate_delimiter_columns (ia); revise_fields_preview (ia); } } /* Called just before the separators page becomes visible in the assistant. */ static void prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *new_page, enum IMPORT_ASSISTANT_DIRECTION dir) { if (dir != IMPORT_ASSISTANT_FORWARDS) return; gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), GTK_TREE_MODEL (ia->delimiters_model)); g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters", G_CALLBACK (reset_tree_view_model), ia); reset_separators_page (ia); } /* Initializes IA's separators substructure. */ void separators_page_create (PsppireImportAssistant *ia) { GtkBuilder *builder = ia->text_builder; size_t i; GtkWidget *w = get_widget_assert (builder, "Separators"); g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page); g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page); add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators")); ia->custom_cb = get_widget_assert (builder, "custom-cb"); ia->custom_entry = get_widget_assert (builder, "custom-entry"); ia->quote_none = get_widget_assert (builder, "quote-none"); ia->quote_single = get_widget_assert (builder, "quote-single"); ia->quote_double = get_widget_assert (builder, "quote-double"); ia->quote_custom = get_widget_assert (builder, "quote-custom"); ia->quote_custom_entry = get_widget_assert (builder, "quote-custom-entry"); gtk_widget_set_sensitive (ia->custom_entry, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb))); gtk_entry_set_max_length (GTK_ENTRY (ia->quote_custom_entry), 1); if (ia->fields_tree_view == NULL) { GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller"); ia->fields_tree_view = gtk_tree_view_new (); g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL); gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view)); gtk_widget_show_all (scroller); } g_signal_connect (ia->quote_none, "toggled", G_CALLBACK (on_quote_change), ia); g_signal_connect (ia->quote_single, "toggled", G_CALLBACK (on_quote_change), ia); g_signal_connect (ia->quote_double, "toggled", G_CALLBACK (on_quote_change), ia); g_signal_connect (ia->quote_custom, "toggled", G_CALLBACK (on_quote_change), ia); g_signal_connect (ia->quote_custom_entry, "notify::text", G_CALLBACK (on_quote_custom_entry_change), ia); g_signal_connect (ia->custom_entry, "notify::text", G_CALLBACK (on_separators_custom_entry_notify), ia); g_signal_connect (ia->custom_cb, "toggled", G_CALLBACK (on_separators_custom_cb_toggle), ia); for (i = 0; i < N_SEPARATORS; i++) g_signal_connect (get_widget_assert (builder, separators[i].name), "toggled", G_CALLBACK (on_separator_toggle), ia); reset_separators_page (ia); } static struct casereader_random_class my_casereader_class; static struct ccase * my_read (struct casereader *reader, void *aux, casenumber idx) { PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux); GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model); GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1); const struct caseproto *proto = casereader_get_proto (reader); GtkTreeIter iter; struct ccase *c = NULL; if (gtk_tree_model_get_iter (tm, &iter, tp)) { c = case_create (proto); int i; for (i = 0 ; i < caseproto_get_n_widths (proto); ++i) { GValue value = {0}; gtk_tree_model_get_value (tm, &iter, i + 1, &value); const struct variable *var = dict_get_var (ia->casereader_dict, i); const gchar *ss = g_value_get_string (&value); if (ss) { union value *v = case_data_rw (c, var); /* In this reader we derive the union value from the string in the tree_model. We retrieve the width and format from a dictionary which is stored directly after the reader creation. Changes in ia->dict in the variable window are not reflected here and therefore this is always compatible with the width in the caseproto. See bug #58298 */ char *xx = data_in (ss_cstr (ss), "UTF-8", var_get_write_format (var).type, settings_get_fmt_settings (), v, var_get_width (var), "UTF-8"); free (xx); } g_value_unset (&value); } } gtk_tree_path_free (tp); return c; } static void my_destroy (struct casereader *reader, void *aux) { g_print ("%s:%d %p\n", __FILE__, __LINE__, reader); } static void my_advance (struct casereader *reader, void *aux, casenumber n) { g_print ("%s:%d\n", __FILE__, __LINE__); } static struct casereader * textfile_create_reader (PsppireImportAssistant *ia) { int n_vars = dict_get_n_vars (ia->dict); int i; struct fmt_guesser **fg = XCALLOC (n_vars, struct fmt_guesser *); for (i = 0 ; i < n_vars; ++i) { fg[i] = fmt_guesser_create (); } gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL); GtkTreeIter iter; gboolean ok; for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter); ok; ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter)) { for (i = 0 ; i < n_vars; ++i) { gchar *s = NULL; gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1); if (s) fmt_guesser_add (fg[i], ss_cstr (s)); free (s); } } struct caseproto *proto = caseproto_create (); for (i = 0 ; i < n_vars; ++i) { struct fmt_spec fs = fmt_guesser_guess (fg[i]); fmt_fix (&fs, FMT_FOR_OUTPUT); struct variable *var = dict_get_var (ia->dict, i); int width = fmt_var_width (fs); var_set_width_and_formats (var, width, &fs, &fs); proto = caseproto_add_width (proto, width); fmt_guesser_destroy (fg[i]); } free (fg); struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia); /* Store the dictionary at this point when the casereader is created. my_read depends on the dictionary to interpret the strings in the treeview. This guarantees that the union value is produced according to the caseproto in the reader. */ ia->casereader_dict = dict_clone (ia->dict); caseproto_unref (proto); return cr; } /* When during import the variable type is changed, the reader is reinitialized based on the new dictionary with a fresh caseprototype. The default behaviour when a variable type is changed and the column is resized is that the union value is interpreted with new variable type and an overlay for that column is generated. Here we reinit to the original reader based on strings. As a result you can switch from string to numeric to string without loosing the string information. */ static void ia_variable_changed_cb (GObject *obj, gint var_num, guint what, const struct variable *oldvar, gpointer data) { PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (data); struct caseproto *proto = caseproto_create(); for (int i = 0; i < dict_get_n_vars (ia->dict); i++) { const struct variable *var = dict_get_var (ia->dict, i); int width = var_get_width (var); proto = caseproto_add_width (proto, width); } gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL); PsppireDataStore *store = NULL; g_object_get (ia->data_sheet, "data-model", &store, NULL); struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class, ia); psppire_data_store_set_reader (store, cr); dict_unref (ia->casereader_dict); ia->casereader_dict = dict_clone (ia->dict); } /* Set the data model for both the data sheet and the variable sheet. */ void textfile_set_data_models (PsppireImportAssistant *ia) { my_casereader_class.read = my_read; my_casereader_class.destroy = my_destroy; my_casereader_class.advance = my_advance; struct casereader *reader = textfile_create_reader (ia); PsppireDict *dict = psppire_dict_new_from_dict (ia->dict); PsppireDataStore *store = psppire_data_store_new (dict); psppire_data_store_set_reader (store, reader); g_signal_connect (dict, "variable-changed", G_CALLBACK (ia_variable_changed_cb), ia); g_object_set (ia->data_sheet, "data-model", store, NULL); g_object_set (ia->var_sheet, "data-model", dict, NULL); } static void first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s) { gint first_case = 0; g_object_get (ia->delimiters_model, "first-line", &first_case, NULL); if (first_case> 0) ds_put_format (s, " /FIRSTCASE=%d\n", first_case + 1); } /* Emits PSPP syntax to S that applies the dictionary attributes (such as missing values and value labels) of the variables in DICT. */ static void apply_dict (const struct dictionary *dict, struct string *s) { size_t n_vars = dict_get_n_vars (dict); for (size_t i = 0; i < n_vars; i++) { struct variable *var = dict_get_var (dict, i); const char *name = var_get_name (var); enum val_type type = var_get_type (var); int width = var_get_width (var); enum measure measure = var_get_measure (var); enum var_role role = var_get_role (var); enum alignment alignment = var_get_alignment (var); struct fmt_spec format = var_get_print_format (var); if (var_has_missing_values (var)) { const struct missing_values *mv = var_get_missing_values (var); size_t j; syntax_gen_pspp (s, "MISSING VALUES %ss (", name); for (j = 0; j < mv_n_values (mv); j++) { if (j) ds_put_cstr (s, ", "); syntax_gen_value (s, mv_get_value (mv, j), width, &format); } if (mv_has_range (mv)) { double low, high; if (mv_has_value (mv)) ds_put_cstr (s, ", "); mv_get_range (mv, &low, &high); syntax_gen_num_range (s, low, high, &format); } ds_put_cstr (s, ").\n"); } if (var_has_value_labels (var)) { const struct val_labs *vls = var_get_value_labels (var); const struct val_lab **labels = val_labs_sorted (vls); size_t n_labels = val_labs_count (vls); syntax_gen_pspp (s, "VALUE LABELS %ss", name); for (size_t j = 0; j < n_labels; j++) { const struct val_lab *vl = labels[j]; ds_put_cstr (s, "\n "); syntax_gen_value (s, &vl->value, width, &format); ds_put_byte (s, ' '); syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl))); } free (labels); ds_put_cstr (s, ".\n"); } if (var_has_label (var)) syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n", name, var_get_label (var)); if (measure != var_default_measure_for_type (type) && measure != MEASURE_UNKNOWN) syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n", name, measure_to_syntax (measure)); if (role != ROLE_INPUT) syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n", var_role_to_syntax (role), name); if (alignment != var_default_alignment (type)) syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n", name, alignment_to_syntax (alignment)); if (var_get_display_width (var) != var_default_display_width (width)) syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n", name, var_get_display_width (var)); } } static void intro_append_syntax (const PsppireImportAssistant *ia, struct string *s) { gint first_line = 0; g_object_get (ia->delimiters_model, "first-line", &first_line, NULL); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button))) ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n", gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line); else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button))) ds_put_format (s, "SAMPLE %.4g.\n", gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0); } static void formats_append_syntax (const PsppireImportAssistant *ia, struct string *s) { g_return_if_fail (ia->dict); ds_put_cstr (s, " /VARIABLES=\n"); int n_vars = dict_get_n_vars (ia->dict); for (int i = 0; i < n_vars; i++) { struct variable *var = dict_get_var (ia->dict, i); char format_string[FMT_STRING_LEN_MAX + 1]; fmt_to_string (var_get_print_format (var), format_string); ds_put_format (s, " %s %s%s\n", var_get_name (var), format_string, i == n_vars - 1 ? "." : ""); } } static void separators_append_syntax (const PsppireImportAssistant *ia, struct string *s) { int i; ds_put_cstr (s, " /DELIMITERS=\""); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab")))) ds_put_cstr (s, "\\t"); for (i = 0; i < N_SEPARATORS; i++) { const struct separator *seps = &separators[i]; GtkWidget *button = get_widget_assert (ia->text_builder, seps->name); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { if (seps->c == '\t') continue; ds_put_byte (s, seps->c); } } ds_put_cstr (s, "\"\n"); gunichar quote = get_quote_character (ia); if (quote) { GString *quote_s = g_string_new (NULL); g_string_append_unichar (quote_s, quote); syntax_gen_pspp (s, " /QUALIFIER=%sq\n", quote_s->str); g_string_free (quote_s, TRUE); } } void text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s) { gchar *file_name = NULL; gchar *encoding = NULL; g_object_get (ia->text_file, "file-name", &file_name, "encoding", &encoding, NULL); if (file_name == NULL) return; syntax_gen_pspp (s, "GET DATA" "\n /TYPE=TXT" "\n /FILE=%sq\n", file_name); if (encoding && strcmp (encoding, "Auto")) syntax_gen_pspp (s, " /ENCODING=%sq\n", encoding); ds_put_cstr (s, " /ARRANGEMENT=DELIMITED\n" " /DELCASE=LINE\n"); first_line_append_syntax (ia, s); separators_append_syntax (ia, s); formats_append_syntax (ia, s); apply_dict (ia->dict, s); intro_append_syntax (ia, s); } AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル
AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル