1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017 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 #define _(msgid) gettext (msgid)
20 #define P_(msgid) msgid
22 #include "psppire-delimited-text.h"
23 #include "psppire-text-file.h"
24 #include "libpspp/str.h"
25 #include "libpspp/i18n.h"
40 count_delims (PsppireDelimitedText *tf)
42 if (tf->child == NULL)
45 tf->max_delimiters = 0;
48 for (valid = gtk_tree_model_get_iter_first (tf->child, &iter);
50 valid = gtk_tree_model_iter_next (tf->child, &iter))
53 // FIXME: Box these lines to avoid constant allocation/deallocation
55 gtk_tree_model_get (tf->child, &iter, 1, &line, -1);
59 for (p = line; ; p = g_utf8_find_next_char (p, NULL))
61 const gunichar c = g_utf8_get_char (p);
67 else if (c == tf->quotes[0] || c == tf->quotes[1])
73 for (del = tf->delimiters; del; del = g_slist_next (del))
75 if (c == GPOINTER_TO_INT (del->data))
80 tf->max_delimiters = MAX (tf->max_delimiters, count);
87 cache_invalidate (PsppireDelimitedText *tf)
89 memset (tf->cache_starts, 0, sizeof tf->cache_starts);
90 if (tf->const_cache.string)
92 ss_dealloc (&tf->const_cache);
93 tf->const_cache.string = NULL;
99 psppire_delimited_text_set_property (GObject *object,
104 PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
108 case PROP_FIRST_LINE:
109 tf->first_line = g_value_get_int (value);
112 tf->child = g_value_get_object (value);
113 g_return_if_fail (PSPPIRE_IS_TEXT_FILE (tf->child));
115 case PROP_DELIMITERS:
116 g_slist_free (tf->delimiters);
117 tf->delimiters = g_slist_copy (g_value_get_pointer (value));
121 tf->quotes[0] = tf->quotes[1] = -1;
123 const gchar *s = g_value_get_string (value);
124 for (size_t i = 0; i < 2 && s && s[0]; i++)
126 tf->quotes[i] = g_utf8_get_char (s);
127 s = g_utf8_find_next_char (s, NULL);
132 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
136 cache_invalidate (tf);
141 psppire_delimited_text_get_property (GObject *object,
146 PsppireDelimitedText *text_file = PSPPIRE_DELIMITED_TEXT (object);
150 case PROP_FIRST_LINE:
151 g_value_set_int (value, text_file->first_line);
153 case PROP_DELIMITERS:
154 g_value_set_pointer (value, text_file->delimiters);
158 GString *s = g_string_new (NULL);
159 for (size_t i = 0; i < 2; i++)
161 gunichar quote = text_file->quotes[i];
162 if (quote && quote != -1)
163 g_string_append_unichar (s, quote);
168 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
173 static void psppire_delimited_text_finalize (GObject *object);
174 static void psppire_delimited_text_dispose (GObject *object);
176 static GObjectClass *parent_class = NULL;
179 n_lines (PsppireDelimitedText *file)
181 PsppireTextFile *child = PSPPIRE_TEXT_FILE (file->child);
183 return child->maximum_lines;
187 __tree_get_iter (GtkTreeModel *tree_model,
191 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
196 gint *indices = gtk_tree_path_get_indices (path);
203 gint children = n_lines (file);
205 if (n >= children - file->first_line)
209 iter->user_data = GINT_TO_POINTER (n);
210 iter->stamp = file->stamp;
217 __tree_iter_next (GtkTreeModel *tree_model,
220 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
221 g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
223 gint n = GPOINTER_TO_INT (iter->user_data);
226 gint children = n_lines (file);
228 if (n + 1 >= children - file->first_line)
231 iter->user_data = GINT_TO_POINTER (n + 1);
238 __tree_get_column_type (GtkTreeModel *tree_model,
244 return G_TYPE_STRING;
248 __iter_has_child (GtkTreeModel *tree_model,
256 __iter_parent (GtkTreeModel *tree_model,
264 __tree_get_path (GtkTreeModel *tree_model,
267 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
268 g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
270 gint n = GPOINTER_TO_INT (iter->user_data);
272 gint children = n_lines (file);
274 if (n >= children - file->first_line)
277 return gtk_tree_path_new_from_indices (n, -1);
282 __iter_children (GtkTreeModel *tree_model,
291 __tree_model_iter_n_children (GtkTreeModel *tree_model,
294 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
295 g_assert (iter == NULL);
297 gint children = n_lines (file);
299 return children - file->first_line;
302 static GtkTreeModelFlags
303 __tree_model_get_flags (GtkTreeModel *model)
305 g_return_val_if_fail (PSPPIRE_IS_DELIMITED_TEXT (model), (GtkTreeModelFlags) 0);
307 return GTK_TREE_MODEL_LIST_ONLY;
311 __tree_model_get_n_columns (GtkTreeModel *tree_model)
313 PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (tree_model);
315 /* + 1 for the trailing field and +1 for the leading line number column */
316 return tf->max_delimiters + 1 + 1;
321 __iter_nth_child (GtkTreeModel *tree_model,
326 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
328 g_assert (parent == NULL);
330 g_return_val_if_fail (file, FALSE);
332 gint children = gtk_tree_model_iter_n_children (file->child, NULL);
334 if (n >= children - file->first_line)
337 iter->user_data = NULL;
341 iter->user_data = GINT_TO_POINTER (n);
342 iter->stamp = file->stamp;
349 nullify_char (struct substring cs)
351 int char_len = ss_first_mblen (cs);
354 cs.string[char_len - 1] = '\0';
360 /* Split row N into it's delimited fields (if it is not already cached)
361 and set this row as the current cache. */
363 split_row_into_fields (PsppireDelimitedText *file, gint n)
365 if (n == file->cache_row) /* Cache hit */
370 memset (file->cache_starts, 0, sizeof file->cache_starts);
372 if (file->const_cache.string)
374 ss_dealloc (&file->const_cache);
376 ss_alloc_substring_pool (&file->const_cache,
377 PSPPIRE_TEXT_FILE (file->child)->lines[n], NULL);
378 struct substring cs = file->const_cache;
380 file->cache_starts[0] = cs.string;
383 UINT32_MAX != ss_first_mb (cs);
386 ucs4_t character = ss_first_mb (cs);
387 gboolean char_is_quote = FALSE;
390 if (character == file->quotes[0] || character == file->quotes[1])
393 char_is_quote = TRUE;
394 file->cache_starts[field] += ss_first_mblen (cs);
397 else if (character == quote)
399 char_is_quote = TRUE;
404 if (quote == -1 && char_is_quote == FALSE)
407 for (del = file->delimiters; del; del = g_slist_next (del))
409 if (character == GPOINTER_TO_INT (del->data))
412 int char_len = ss_first_mblen (cs);
413 file->cache_starts[field] = cs.string + char_len;
425 psppire_delimited_text_get_header_title (PsppireDelimitedText *file, gint column)
427 if (file->first_line <= 0)
430 split_row_into_fields (file, file->first_line - 1);
432 return file->cache_starts [column];
436 __get_value (GtkTreeModel *tree_model,
441 PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
443 g_return_if_fail (iter->stamp == file->stamp);
445 gint n = GPOINTER_TO_INT (iter->user_data) + file->first_line;
450 g_value_init (value, G_TYPE_INT);
451 g_value_set_int (value, n + 1);
455 g_value_init (value, G_TYPE_STRING);
457 split_row_into_fields (file, n);
459 g_value_set_string (value, file->cache_starts [column - 1]);
464 __tree_model_init (GtkTreeModelIface *iface)
466 iface->get_flags = __tree_model_get_flags;
467 iface->get_n_columns = __tree_model_get_n_columns ;
468 iface->get_column_type = __tree_get_column_type;
469 iface->get_iter = __tree_get_iter;
470 iface->iter_next = __tree_iter_next;
471 iface->get_path = __tree_get_path;
472 iface->get_value = __get_value;
474 iface->iter_children = __iter_children;
475 iface->iter_has_child = __iter_has_child;
476 iface->iter_n_children = __tree_model_iter_n_children;
477 iface->iter_nth_child = __iter_nth_child;
478 iface->iter_parent = __iter_parent;
481 G_DEFINE_TYPE_WITH_CODE (PsppireDelimitedText, psppire_delimited_text, G_TYPE_OBJECT,
482 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
486 psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
488 GObjectClass *object_class;
490 parent_class = g_type_class_peek_parent (class);
491 object_class = G_OBJECT_CLASS (class);
493 GParamSpec *first_line_spec =
494 g_param_spec_int ("first-line",
496 P_("The first line to be considered."),
500 GParamSpec *delimiters_spec =
501 g_param_spec_pointer ("delimiters",
503 P_("A GSList of gunichars which delimit the fields."),
506 GParamSpec *quotes_spec =
507 g_param_spec_string ("quotes",
509 P_("A string of characters that quote the fields."),
513 GParamSpec *child_spec =
514 g_param_spec_object ("child",
516 P_("The GtkTextModel which this object wraps."),
518 G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
520 object_class->set_property = psppire_delimited_text_set_property;
521 object_class->get_property = psppire_delimited_text_get_property;
523 g_object_class_install_property (object_class,
527 g_object_class_install_property (object_class,
531 g_object_class_install_property (object_class,
535 g_object_class_install_property (object_class,
539 object_class->finalize = psppire_delimited_text_finalize;
540 object_class->dispose = psppire_delimited_text_dispose;
545 psppire_delimited_text_init (PsppireDelimitedText *text_file)
547 text_file->child = NULL;
548 text_file->first_line = 0;
549 text_file->delimiters = g_slist_prepend (NULL, GINT_TO_POINTER (':'));
551 text_file->const_cache.string = NULL;
552 text_file->const_cache.length = 0;
553 text_file->cache_row = -1;
554 memset (text_file->cache_starts, 0, sizeof text_file->cache_starts);
556 text_file->max_delimiters = 0;
558 text_file->quotes[0] = text_file->quotes[1] = -1;
560 text_file->dispose_has_run = FALSE;
561 text_file->stamp = g_random_int ();
565 PsppireDelimitedText *
566 psppire_delimited_text_new (GtkTreeModel *child)
569 g_object_new (PSPPIRE_TYPE_DELIMITED_TEXT,
575 psppire_delimited_text_finalize (GObject *object)
577 PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
579 g_slist_free (tf->delimiters);
581 ss_dealloc (&tf->const_cache);
584 (* parent_class->finalize) (object);
589 psppire_delimited_text_dispose (GObject *object)
591 PsppireDelimitedText *ds = PSPPIRE_DELIMITED_TEXT (object);
593 if (ds->dispose_has_run)
597 (* parent_class->dispose) (object);
599 ds->dispose_has_run = TRUE;