r16561 - gnucash/trunk - Merge csv-import branch back into trunk.
Andreas Köhler
andi5 at cvs.gnucash.org
Fri Oct 12 18:51:36 EDT 2007
Author: andi5
Date: 2007-10-12 18:51:34 -0400 (Fri, 12 Oct 2007)
New Revision: 16561
Trac: http://svn.gnucash.org/trac/changeset/16561
Added:
gnucash/trunk/lib/stf/
gnucash/trunk/lib/stf/Makefile.am
gnucash/trunk/lib/stf/README
gnucash/trunk/lib/stf/stf-parse.c
gnucash/trunk/lib/stf/stf-parse.h
gnucash/trunk/src/import-export/csv/Makefile.am
gnucash/trunk/src/import-export/csv/example-file.csv
gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.c
gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.h
gnucash/trunk/src/import-export/csv/gnc-csv-import.c
gnucash/trunk/src/import-export/csv/gnc-csv-import.h
gnucash/trunk/src/import-export/csv/gnc-csv-model.c
gnucash/trunk/src/import-export/csv/gnc-csv-model.h
gnucash/trunk/src/import-export/csv/gnc-csv-preview-dialog.glade
gnucash/trunk/src/import-export/csv/gnc-plugin-csv-ui.xml
gnucash/trunk/src/import-export/csv/gnc-plugin-csv.c
gnucash/trunk/src/import-export/csv/gnc-plugin-csv.h
gnucash/trunk/src/import-export/csv/gncmod-csv-import.c
Removed:
gnucash/trunk/src/import-export/csv/gnc-csv2glist.c
gnucash/trunk/src/import-export/csv/gnc-csv2glist.h
Modified:
gnucash/trunk/configure.in
gnucash/trunk/lib/Makefile.am
gnucash/trunk/src/bin/gnucash-bin.c
gnucash/trunk/src/import-export/Makefile.am
Log:
Merge csv-import branch back into trunk.
configure.in | 2
lib/Makefile.am | 4
lib/stf/Makefile.am | 13
lib/stf/README | 2
lib/stf/stf-parse.c | 1414 +++++++++++++++++++++
lib/stf/stf-parse.h | 112 +
src/bin/gnucash-bin.c | 1
src/import-export/Makefile.am | 4
src/import-export/csv/Makefile.am | 59
src/import-export/csv/example-file.csv | 4
src/import-export/csv/gnc-csv-gnumeric-popup.c | 194 ++
src/import-export/csv/gnc-csv-gnumeric-popup.h | 78 +
src/import-export/csv/gnc-csv-import.c | 1173 +++++++++++++++++
src/import-export/csv/gnc-csv-import.h | 33
src/import-export/csv/gnc-csv-model.c | 1199 +++++++++++++++++
src/import-export/csv/gnc-csv-model.h | 122 +
src/import-export/csv/gnc-csv-preview-dialog.glade | 496 +++++++
src/import-export/csv/gnc-csv2glist.c | 187 --
src/import-export/csv/gnc-csv2glist.h | 39
src/import-export/csv/gnc-plugin-csv-ui.xml | 11
src/import-export/csv/gnc-plugin-csv.c | 160 ++
src/import-export/csv/gnc-plugin-csv.h | 60
src/import-export/csv/gncmod-csv-import.c | 91 +
23 files changed, 5228 insertions(+), 230 deletions(-)
Modified: gnucash/trunk/configure.in
===================================================================
--- gnucash/trunk/configure.in 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/configure.in 2007-10-12 22:51:34 UTC (rev 16561)
@@ -1436,6 +1436,7 @@
lib/libqof/qof/Makefile
lib/libqof/backend/Makefile
lib/libqof/backend/file/Makefile
+ lib/stf/Makefile
packaging/Makefile
packaging/win32/Makefile
packaging/win32/gnucash.iss
@@ -1489,6 +1490,7 @@
src/import-export/schemas/Makefile
src/import-export/ofx/Makefile
src/import-export/ofx/test/Makefile
+ src/import-export/csv/Makefile
src/import-export/log-replay/Makefile
src/import-export/hbci/Makefile
src/import-export/hbci/glade/Makefile
Modified: gnucash/trunk/lib/Makefile.am
===================================================================
--- gnucash/trunk/lib/Makefile.am 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/lib/Makefile.am 2007-10-12 22:51:34 UTC (rev 16561)
@@ -1,5 +1,5 @@
-SUBDIRS = libc glib28 guile-www srfi
-DIST_SUBDIRS = libc glib28 guile-www srfi libqof
+SUBDIRS = libc glib28 guile-www srfi stf
+DIST_SUBDIRS = libc glib28 guile-www srfi libqof stf
if USE_LIBQOF
SUBDIRS += libqof
Added: gnucash/trunk/lib/stf/Makefile.am
===================================================================
--- gnucash/trunk/lib/stf/Makefile.am 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/lib/stf/Makefile.am 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libgnc-stf.la
+
+REALSRCS = stf-parse.c
+REALHDRS = stf-parse.h
+
+libgnc_stf_la_SOURCES = ${REALSRCS}
+noinst_HEADERS = ${REALHDRS}
+
+libgnc_stf_la_LDFLAGS = $(pkg-config --libs libgoffice-0.3)
+
+AM_CFLAGS = $(GOFFICE_CFLAGS)
+
+EXTRA_DIST = $(REALSRCS) $(REALHDRS)
Added: gnucash/trunk/lib/stf/README
===================================================================
--- gnucash/trunk/lib/stf/README 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/lib/stf/README 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,2 @@
+This consists of code taken from Gnumeric's Structured Text Format
+parser. It is used for the CSV/Fixed-Width file importer.
Added: gnucash/trunk/lib/stf/stf-parse.c
===================================================================
--- gnucash/trunk/lib/stf/stf-parse.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/lib/stf/stf-parse.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,1414 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * stf-parse.c : Structured Text Format parser. (STF)
+ * A general purpose engine for parsing data
+ * in CSV and Fixed width format.
+ *
+ *
+ * Copyright (C) Almer. S. Tigelaar.
+ * EMail: almer1 at dds.nl or almer-t at bigfoot.com
+ *
+ * Copyright (C) 2003 Andreas J. Guelzow <aguelzow at taliesin.ca>
+ * Copyright (C) 2003 Morten Welinder <terra at gnome.org>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define GETTEXT_PACKAGE gnumeric
+
+#include <glib/gi18n-lib.h>
+/* #include "gnumeric.h" */
+#include "stf-parse.h"
+
+/* #include "workbook.h" */
+/* #include "cell.h" */
+/* #include "sheet.h" */
+/* #include "clipboard.h" */
+/* #include "sheet-style.h" */
+/* #include "value.h" */
+/* #include "mstyle.h" */
+/* #include "number-match.h" */
+/* #include "gutils.h" */
+/* #include "parse-util.h" */
+#include <goffice/utils/go-glib-extras.h>
+#include <goffice/utils/go-format.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <locale.h>
+#include <string.h>
+#include <math.h>
+
+#define SETUP_LOCALE_SWITCH char *oldlocale = NULL
+
+#define START_LOCALE_SWITCH if (parseoptions->locale) {\
+oldlocale = g_strdup(go_setlocale (LC_ALL, NULL)); \
+go_setlocale(LC_ALL, parseoptions->locale);}
+
+#define END_LOCALE_SWITCH if (oldlocale) {\
+go_setlocale(LC_ALL, oldlocale);\
+g_free (oldlocale);}
+
+/* Source_t struct, used for interchanging parsing information between the low level parse functions */
+typedef struct {
+ GStringChunk *chunk;
+ char const *position; /* Indicates the current position within data */
+
+ /* Used internally for fixed width parsing */
+ int splitpos; /* Indicates current position in splitpositions array */
+ int linepos; /* Position on the current line */
+} Source_t;
+
+/* Struct used for autodiscovery */
+typedef struct {
+ int start;
+ int stop;
+} AutoDiscovery_t;
+
+/*
+ * Some silly dude make the length field an unsigned int. C just does
+ * not deal very well with that.
+ */
+static inline int
+my_garray_len (GArray const *a)
+{
+ return (int)a->len;
+}
+
+static inline int
+my_gptrarray_len (GPtrArray const *a)
+{
+ return (int)a->len;
+}
+
+static int
+compare_terminator (char const *s, StfParseOptions_t *parseoptions)
+{
+ guchar const *us = (guchar const *)s;
+ GSList *l;
+
+ if (*us > parseoptions->compiled_terminator.max ||
+ *us < parseoptions->compiled_terminator.min)
+ return 0;
+
+ for (l = parseoptions->terminator; l; l = l->next) {
+ char const *term = l->data;
+ char const *d = s;
+
+ while (*term) {
+ if (*d != *term)
+ goto next;
+ term++;
+ d++;
+ }
+ return d - s;
+
+ next:
+ ;
+ }
+ return 0;
+}
+
+
+/*******************************************************************************************************
+ * STF PARSE OPTIONS : StfParseOptions related
+ *******************************************************************************************************/
+
+/**
+ * stf_parse_options_new:
+ *
+ * This will return a new StfParseOptions_t struct.
+ * The struct should, after being used, freed with stf_parse_options_free.
+ **/
+StfParseOptions_t *
+stf_parse_options_new (void)
+{
+ StfParseOptions_t* parseoptions = g_new0 (StfParseOptions_t, 1);
+
+ parseoptions->parsetype = PARSE_TYPE_NOTSET;
+
+ parseoptions->terminator = NULL;
+ stf_parse_options_add_line_terminator (parseoptions, "\r\n");
+ stf_parse_options_add_line_terminator (parseoptions, "\n");
+ stf_parse_options_add_line_terminator (parseoptions, "\r");
+
+ parseoptions->trim_spaces = (TRIM_TYPE_RIGHT | TRIM_TYPE_LEFT);
+ parseoptions->locale = NULL;
+
+ parseoptions->splitpositions = NULL;
+ stf_parse_options_fixed_splitpositions_clear (parseoptions);
+
+ parseoptions->stringindicator = '"';
+ parseoptions->indicator_2x_is_single = TRUE;
+ parseoptions->duplicates = FALSE;
+ parseoptions->trim_seps = FALSE;
+
+ parseoptions->sep.str = NULL;
+ parseoptions->sep.chr = NULL;
+
+ parseoptions->col_import_array = NULL;
+ parseoptions->col_import_array_len = 0;
+ parseoptions->formats = NULL;
+
+ parseoptions->cols_exceeded = FALSE;
+
+ return parseoptions;
+}
+
+/**
+ * stf_parse_options_free:
+ *
+ * will free @parseoptions, note that this will not free the splitpositions
+ * member (GArray) of the struct, the caller is responsible for that.
+ **/
+void
+stf_parse_options_free (StfParseOptions_t *parseoptions)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ g_free (parseoptions->col_import_array);
+ g_free (parseoptions->locale);
+ g_free (parseoptions->sep.chr);
+
+ if (parseoptions->sep.str) {
+ GSList *l;
+
+ for (l = parseoptions->sep.str; l != NULL; l = l->next)
+ g_free ((char *) l->data);
+ g_slist_free (parseoptions->sep.str);
+ }
+
+ g_array_free (parseoptions->splitpositions, TRUE);
+
+ stf_parse_options_clear_line_terminator (parseoptions);
+
+ if (parseoptions->formats) {
+ unsigned int ui;
+ GPtrArray *formats = parseoptions->formats;
+
+ for (ui = 0; ui < formats->len; ui++)
+ go_format_unref (g_ptr_array_index (formats, ui));
+ g_ptr_array_free (formats, TRUE);
+ parseoptions->formats = NULL;
+ }
+
+ g_free (parseoptions);
+}
+
+void
+stf_parse_options_set_type (StfParseOptions_t *parseoptions, StfParseType_t const parsetype)
+{
+ g_return_if_fail (parseoptions != NULL);
+ g_return_if_fail (parsetype == PARSE_TYPE_CSV || parsetype == PARSE_TYPE_FIXED);
+
+ parseoptions->parsetype = parsetype;
+}
+
+static gint
+long_string_first (gchar const *a, gchar const *b)
+{
+ /* This actually is UTF-8 safe. */
+ return strlen (b) - strlen (a);
+}
+
+static void
+compile_terminators (StfParseOptions_t *parseoptions)
+{
+ GSList *l;
+ GO_SLIST_SORT (parseoptions->terminator, (GCompareFunc)long_string_first);
+
+ parseoptions->compiled_terminator.min = 255;
+ parseoptions->compiled_terminator.max = 0;
+ for (l = parseoptions->terminator; l; l = l->next) {
+ const guchar *term = l->data;
+ parseoptions->compiled_terminator.min =
+ MIN (parseoptions->compiled_terminator.min, *term);
+ parseoptions->compiled_terminator.max =
+ MAX (parseoptions->compiled_terminator.max, *term);
+ }
+}
+
+/**
+ * stf_parse_options_add_line_terminator:
+ *
+ * This will add to the line terminators, in both the Fixed width and CSV delimited importers
+ * this indicates the end of a row.
+ *
+ **/
+void
+stf_parse_options_add_line_terminator (StfParseOptions_t *parseoptions, char const *terminator)
+{
+ g_return_if_fail (parseoptions != NULL);
+ g_return_if_fail (terminator != NULL && *terminator != 0);
+
+ GO_SLIST_PREPEND (parseoptions->terminator, g_strdup (terminator));
+ compile_terminators (parseoptions);
+}
+
+/**
+ * stf_parse_options_clear_line_terminator:
+ *
+ * This will clear the line terminator, in both the Fixed width and CSV delimited importers
+ * this indicates the end of a row.
+ *
+ **/
+void
+stf_parse_options_clear_line_terminator (StfParseOptions_t *parseoptions)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ go_slist_free_custom (parseoptions->terminator, g_free);
+ parseoptions->terminator = NULL;
+ compile_terminators (parseoptions);
+}
+
+/**
+ * stf_parse_options_set_trim_spaces:
+ *
+ * If enabled will trim spaces in every parsed field on left and/or right
+ * sides.
+ **/
+void
+stf_parse_options_set_trim_spaces (StfParseOptions_t *parseoptions, StfTrimType_t const trim_spaces)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ parseoptions->trim_spaces = trim_spaces;
+}
+
+/**
+ * stf_parse_options_csv_set_separators:
+ *
+ * A copy is made of the parameters.
+ **/
+void
+stf_parse_options_csv_set_separators (StfParseOptions_t *parseoptions, char const *character,
+ GSList const *string)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ g_free (parseoptions->sep.chr);
+ parseoptions->sep.chr = g_strdup (character);
+
+ go_slist_free_custom (parseoptions->sep.str, g_free);
+ parseoptions->sep.str = go_slist_map (string, (GOMapFunc)g_strdup);
+}
+
+void
+stf_parse_options_csv_set_stringindicator (StfParseOptions_t *parseoptions, gunichar const stringindicator)
+{
+ g_return_if_fail (parseoptions != NULL);
+ g_return_if_fail (stringindicator != '\0');
+
+ parseoptions->stringindicator = stringindicator;
+}
+
+/**
+ * stf_parse_options_csv_set_indicator_2x_is_single:
+ * @indic_2x : a boolean value indicating whether we want to see two
+ * adjacent string indicators as a single string indicator
+ * that is part of the cell, rather than a terminator.
+ **/
+void
+stf_parse_options_csv_set_indicator_2x_is_single (StfParseOptions_t *parseoptions,
+ gboolean const indic_2x)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ parseoptions->indicator_2x_is_single = indic_2x;
+}
+
+/**
+ * stf_parse_options_csv_set_duplicates:
+ * @duplicates : a boolean value indicating whether we want to see two
+ * separators right behind each other as one
+ **/
+void
+stf_parse_options_csv_set_duplicates (StfParseOptions_t *parseoptions, gboolean const duplicates)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ parseoptions->duplicates = duplicates;
+}
+
+/**
+ * stf_parse_options_csv_set_trim_seps:
+ * @trim_seps : a boolean value indicating whether we want to ignore
+ * separators at the beginning of lines
+ **/
+void
+stf_parse_options_csv_set_trim_seps (StfParseOptions_t *parseoptions, gboolean const trim_seps)
+{
+ g_return_if_fail (parseoptions != NULL);
+
+ parseoptions->trim_seps = trim_seps;
+}
+
+/**
+ * stf_parse_options_fixed_splitpositions_clear:
+ *
+ * This will clear the splitpositions (== points on which a line is split)
+ **/
+void
+stf_parse_options_fixed_splitpositions_clear (StfParseOptions_t *parseoptions)
+{
+ int minus_one = -1;
+ g_return_if_fail (parseoptions != NULL);
+
+ if (parseoptions->splitpositions)
+ g_array_free (parseoptions->splitpositions, TRUE);
+ parseoptions->splitpositions = g_array_new (FALSE, FALSE, sizeof (int));
+
+ g_array_append_val (parseoptions->splitpositions, minus_one);
+}
+
+/**
+ * stf_parse_options_fixed_splitpositions_add:
+ *
+ * @position will be added to the splitpositions.
+ **/
+void
+stf_parse_options_fixed_splitpositions_add (StfParseOptions_t *parseoptions, int position)
+{
+ unsigned int ui;
+
+ g_return_if_fail (parseoptions != NULL);
+ g_return_if_fail (position >= 0);
+
+ for (ui = 0; ui < parseoptions->splitpositions->len - 1; ui++) {
+ int here = g_array_index (parseoptions->splitpositions, int, ui);
+ if (position == here)
+ return;
+ if (position < here)
+ break;
+ }
+
+ g_array_insert_val (parseoptions->splitpositions, ui, position);
+}
+
+void
+stf_parse_options_fixed_splitpositions_remove (StfParseOptions_t *parseoptions, int position)
+{
+ unsigned int ui;
+
+ g_return_if_fail (parseoptions != NULL);
+ g_return_if_fail (position >= 0);
+
+ for (ui = 0; ui < parseoptions->splitpositions->len - 1; ui++) {
+ int here = g_array_index (parseoptions->splitpositions, int, ui);
+ if (position == here)
+ g_array_remove_index (parseoptions->splitpositions, ui);
+ if (position <= here)
+ return;
+ }
+}
+
+int
+stf_parse_options_fixed_splitpositions_count (StfParseOptions_t *parseoptions)
+{
+ return parseoptions->splitpositions->len;
+}
+
+int
+stf_parse_options_fixed_splitpositions_nth (StfParseOptions_t *parseoptions, int n)
+{
+ return g_array_index (parseoptions->splitpositions, int, n);
+}
+
+
+/**
+ * stf_parse_options_valid:
+ * @parseoptions : an import options struct
+ *
+ * Checks if @parseoptions is correctly filled
+ *
+ * returns : TRUE if it is correctly filled, FALSE otherwise.
+ **/
+static gboolean
+stf_parse_options_valid (StfParseOptions_t *parseoptions)
+{
+ g_return_val_if_fail (parseoptions != NULL, FALSE);
+
+ if (parseoptions->parsetype == PARSE_TYPE_CSV) {
+ if (parseoptions->stringindicator == '\0') {
+ g_warning ("STF: Cannot have \\0 as string indicator");
+ return FALSE;
+ }
+
+ } else if (parseoptions->parsetype == PARSE_TYPE_FIXED) {
+ if (!parseoptions->splitpositions) {
+ g_warning ("STF: No splitpositions in struct");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*******************************************************************************************************
+ * STF PARSE : The actual routines that do the 'trick'
+ *******************************************************************************************************/
+
+static void
+trim_spaces_inplace (char *field, StfParseOptions_t const *parseoptions)
+{
+ if (!field) return;
+
+ if (parseoptions->trim_spaces & TRIM_TYPE_LEFT) {
+ char *s = field;
+
+ while (g_unichar_isspace (g_utf8_get_char (s)))
+ s = g_utf8_next_char (s);
+
+ if (s != field)
+ strcpy (field, s);
+ }
+
+ if (parseoptions->trim_spaces & TRIM_TYPE_RIGHT) {
+ char *s = field + strlen (field);
+
+ while (field != s) {
+ s = g_utf8_prev_char (s);
+ if (!g_unichar_isspace (g_utf8_get_char (s)))
+ break;
+ *s = 0;
+ }
+ }
+}
+
+/**
+ * stf_parse_csv_is_separator:
+ *
+ * returns NULL if @character is not a separator, a pointer to the character
+ * after the separator otherwise.
+ **/
+static char const *
+stf_parse_csv_is_separator (char const *character, char const *chr, GSList const *str)
+{
+ g_return_val_if_fail (character != NULL, NULL);
+
+ if (*character == 0)
+ return NULL;
+
+ if (str) {
+ GSList const *l;
+
+ for (l = str; l != NULL; l = l->next) {
+ char const *s = l->data;
+ char const *r;
+ glong cnt;
+ glong const len = g_utf8_strlen (s, -1);
+
+ /* Don't compare past the end of the buffer! */
+ for (r = character, cnt = 0; cnt < len; cnt++, r = g_utf8_next_char (r))
+ if (*r == '\0')
+ break;
+
+ if ((cnt == len) && (memcmp (character, s, len) == 0))
+ return g_utf8_offset_to_pointer (character, len);
+ }
+ }
+
+ if (chr && g_utf8_strchr (chr, -1,
+ g_utf8_get_char (character)))
+ return g_utf8_next_char(character);
+
+ return NULL;
+}
+
+/*
+ * stf_parse_eat_separators:
+ *
+ * skip over leading separators
+ *
+ */
+
+static void
+stf_parse_eat_separators (Source_t *src, StfParseOptions_t *parseoptions)
+{
+ char const *cur, *next;
+
+ g_return_if_fail (src != NULL);
+ g_return_if_fail (parseoptions != NULL);
+
+ cur = src->position;
+
+ if (*cur == '\0' || compare_terminator (cur, parseoptions))
+ return;
+ while ((next = stf_parse_csv_is_separator (cur, parseoptions->sep.chr, parseoptions->sep.str)))
+ cur = next;
+ src->position = cur;
+ return;
+}
+
+
+typedef enum {
+ STF_CELL_ERROR,
+ STF_CELL_EOF,
+ STF_CELL_EOL,
+ STF_CELL_FIELD_NO_SEP,
+ STF_CELL_FIELD_SEP,
+} StfParseCellRes;
+
+static StfParseCellRes
+stf_parse_csv_cell (GString *text, Source_t *src, StfParseOptions_t *parseoptions)
+{
+ char const *cur;
+ gboolean saw_sep = FALSE;
+
+ g_return_val_if_fail (src != NULL, STF_CELL_ERROR);
+ g_return_val_if_fail (parseoptions != NULL, STF_CELL_ERROR);
+
+ cur = src->position;
+ g_return_val_if_fail (cur != NULL, STF_CELL_ERROR);
+
+ /* Skip whitespace, but stop at line terminators. */
+ while (1) {
+ int term_len;
+
+ if (*cur == 0) {
+ src->position = cur;
+ return STF_CELL_EOF;
+ }
+
+ term_len = compare_terminator (cur, parseoptions);
+ if (term_len) {
+ src->position = cur + term_len;
+ return STF_CELL_EOL;
+ }
+
+ if ((parseoptions->trim_spaces & TRIM_TYPE_LEFT) == 0)
+ break;
+
+ if (stf_parse_csv_is_separator (cur, parseoptions->sep.chr,
+ parseoptions->sep.str))
+ break;
+
+ if (!g_unichar_isspace (g_utf8_get_char (cur)))
+ break;
+ cur = g_utf8_next_char (cur);
+ }
+
+ if (g_utf8_get_char (cur) == parseoptions->stringindicator) {
+ cur = g_utf8_next_char (cur);
+ while (*cur) {
+ gunichar uc = g_utf8_get_char (cur);
+ cur = g_utf8_next_char (cur);
+
+ if (uc == parseoptions->stringindicator) {
+ if (parseoptions->indicator_2x_is_single &&
+ g_utf8_get_char (cur) == parseoptions->stringindicator)
+ cur = g_utf8_next_char (cur);
+ else {
+ /* "field content"dropped-garbage, */
+ while (*cur && !compare_terminator (cur, parseoptions)) {
+ char const *post = stf_parse_csv_is_separator
+ (cur, parseoptions->sep.chr, parseoptions->sep.str);
+ if (post) {
+ cur = post;
+ saw_sep = TRUE;
+ break;
+ }
+ cur = g_utf8_next_char (cur);
+ }
+ break;
+ }
+ }
+
+ g_string_append_unichar (text, uc);
+ }
+
+ /* We silently allow a missing terminating quote. */
+ } else {
+ /* Unquoted field. */
+
+ while (*cur && !compare_terminator (cur, parseoptions)) {
+
+ char const *post = stf_parse_csv_is_separator
+ (cur, parseoptions->sep.chr, parseoptions->sep.str);
+ if (post) {
+ cur = post;
+ saw_sep = TRUE;
+ break;
+ }
+
+ g_string_append_unichar (text, g_utf8_get_char (cur));
+ cur = g_utf8_next_char (cur);
+ }
+
+ if (parseoptions->trim_spaces & TRIM_TYPE_RIGHT) {
+ while (text->len) {
+ const char *last = g_utf8_prev_char (text->str + text->len);
+ if (!g_unichar_isspace (g_utf8_get_char (last)))
+ break;
+ g_string_truncate (text, last - text->str);
+ }
+ }
+ }
+
+ src->position = cur;
+
+ if (saw_sep && parseoptions->duplicates)
+ stf_parse_eat_separators (src, parseoptions);
+
+ return saw_sep ? STF_CELL_FIELD_SEP : STF_CELL_FIELD_NO_SEP;
+}
+
+/**
+ * stf_parse_csv_line:
+ *
+ * This will parse one line from the current @src->position.
+ * NOTE: The calling routine is responsible for freeing the result.
+ *
+ * returns : a GPtrArray of char*'s
+ **/
+static GPtrArray *
+stf_parse_csv_line (Source_t *src, StfParseOptions_t *parseoptions)
+{
+ GPtrArray *line;
+ gboolean cont = FALSE;
+
+ g_return_val_if_fail (src != NULL, NULL);
+ g_return_val_if_fail (parseoptions != NULL, NULL);
+
+ line = g_ptr_array_new ();
+ if (parseoptions->trim_seps)
+ stf_parse_eat_separators (src, parseoptions);
+
+ while (1) {
+ GString *text = g_string_sized_new (30);
+ StfParseCellRes res =
+ stf_parse_csv_cell (text, src, parseoptions);
+ trim_spaces_inplace (text->str, parseoptions);
+ switch (res) {
+ case STF_CELL_FIELD_NO_SEP:
+ g_ptr_array_add (line, g_string_free (text, FALSE));
+ cont = FALSE;
+ break;
+
+ case STF_CELL_FIELD_SEP:
+ g_ptr_array_add (line, g_string_free (text, FALSE));
+ cont = TRUE; /* Make sure we see one more field. */
+ break;
+
+ default:
+ if (cont)
+ g_ptr_array_add (line, g_string_free (text, FALSE));
+ else
+ g_string_free (text, TRUE);
+ return line;
+ }
+ }
+}
+
+/**
+ * stf_parse_fixed_cell:
+ *
+ * returns a pointer to the parsed cell contents.
+ **/
+static char *
+stf_parse_fixed_cell (Source_t *src, StfParseOptions_t *parseoptions)
+{
+ char *res;
+ char const *cur;
+ int splitval;
+
+ g_return_val_if_fail (src != NULL, NULL);
+ g_return_val_if_fail (parseoptions != NULL, NULL);
+
+ cur = src->position;
+
+ if (src->splitpos < my_garray_len (parseoptions->splitpositions))
+ splitval = (int) g_array_index (parseoptions->splitpositions, int, src->splitpos);
+ else
+ splitval = -1;
+
+ while (*cur != 0 && !compare_terminator (cur, parseoptions) && splitval != src->linepos) {
+ src->linepos++;
+ cur = g_utf8_next_char (cur);
+ }
+
+ res = g_string_chunk_insert_len (src->chunk,
+ src->position,
+ cur - src->position);
+
+ src->position = cur;
+
+ return res;
+}
+
+/**
+ * stf_parse_fixed_line:
+ *
+ * This will parse one line from the current @src->position.
+ * It will return a GPtrArray with the cell contents as strings.
+
+ * NOTE: The calling routine is responsible for freeing result.
+ **/
+static GPtrArray *
+stf_parse_fixed_line (Source_t *src, StfParseOptions_t *parseoptions)
+{
+ GPtrArray *line;
+
+ g_return_val_if_fail (src != NULL, NULL);
+ g_return_val_if_fail (parseoptions != NULL, NULL);
+
+ src->linepos = 0;
+ src->splitpos = 0;
+
+ line = g_ptr_array_new ();
+ while (*src->position != '\0' && !compare_terminator (src->position, parseoptions)) {
+ char *field = stf_parse_fixed_cell (src, parseoptions);
+
+ trim_spaces_inplace (field, parseoptions);
+ g_ptr_array_add (line, field);
+
+ src->splitpos++;
+ }
+
+ return line;
+}
+
+void
+stf_parse_general_free (GPtrArray *lines)
+{
+ unsigned lineno;
+ for (lineno = 0; lineno < lines->len; lineno++) {
+ GPtrArray *line = g_ptr_array_index (lines, lineno);
+ /* Fields are not free here. */
+ g_ptr_array_free (line, TRUE);
+ }
+ g_ptr_array_free (lines, TRUE);
+}
+
+
+/**
+ * stf_parse_general:
+ *
+ * Returns a GPtrArray of lines, where each line is itself a
+ * GPtrArray of strings.
+ *
+ * The caller must free this entire structure, for example by calling
+ * stf_parse_general_free.
+ **/
+GPtrArray *
+stf_parse_general (StfParseOptions_t *parseoptions,
+ GStringChunk *lines_chunk,
+ char const *data, char const *data_end)
+{
+ GPtrArray *lines;
+ Source_t src;
+ int row;
+
+ g_return_val_if_fail (parseoptions != NULL, NULL);
+ g_return_val_if_fail (data != NULL, NULL);
+ g_return_val_if_fail (data_end != NULL, NULL);
+ g_return_val_if_fail (stf_parse_options_valid (parseoptions), NULL);
+ g_return_val_if_fail (g_utf8_validate (data, -1, NULL), NULL);
+
+ src.chunk = lines_chunk;
+ src.position = data;
+ row = 0;
+
+ lines = g_ptr_array_new ();
+ while (*src.position != '\0' && src.position < data_end) {
+ GPtrArray *line;
+
+ line = parseoptions->parsetype == PARSE_TYPE_CSV
+ ? stf_parse_csv_line (&src, parseoptions)
+ : stf_parse_fixed_line (&src, parseoptions);
+
+ g_ptr_array_add (lines, line);
+ if (parseoptions->parsetype != PARSE_TYPE_CSV)
+ src.position += compare_terminator (src.position, parseoptions);
+
+ if (++row == SHEET_MAX_ROWS)
+ break;
+ }
+
+ return lines;
+}
+
+GPtrArray *
+stf_parse_lines (StfParseOptions_t *parseoptions,
+ GStringChunk *lines_chunk,
+ char const *data,
+ int maxlines, gboolean with_lineno)
+{
+ GPtrArray *lines;
+ int lineno = 1;
+
+ g_return_val_if_fail (data != NULL, NULL);
+
+ lines = g_ptr_array_new ();
+ while (*data) {
+ char const *data0 = data;
+ GPtrArray *line = g_ptr_array_new ();
+
+ if (with_lineno) {
+ char buf[4 * sizeof (int)];
+ sprintf (buf, "%d", lineno);
+ g_ptr_array_add (line,
+ g_string_chunk_insert (lines_chunk, buf));
+ }
+
+ while (1) {
+ int termlen = compare_terminator (data, parseoptions);
+ if (termlen > 0 || *data == 0) {
+ g_ptr_array_add (line,
+ g_string_chunk_insert_len (lines_chunk,
+ data0,
+ data - data0));
+ data += termlen;
+ break;
+ } else
+ data = g_utf8_next_char (data);
+ }
+
+ g_ptr_array_add (lines, line);
+
+ lineno++;
+ if (lineno >= maxlines)
+ break;
+ }
+ return lines;
+}
+
+char const *
+stf_parse_find_line (StfParseOptions_t *parseoptions,
+ char const *data,
+ int line)
+{
+ while (line > 0) {
+ int termlen = compare_terminator (data, parseoptions);
+ if (termlen > 0) {
+ data += termlen;
+ line--;
+ } else if (*data == 0) {
+ return data;
+ } else {
+ data = g_utf8_next_char (data);
+ }
+ }
+ return data;
+}
+
+
+/**
+ * stf_parse_options_fixed_autodiscover:
+ * @parseoptions: a Parse options struct.
+ * @data_lines : The number of lines to look at in @data.
+ * @data : The actual data.
+ *
+ * Automatically try to discover columns in the text to be parsed.
+ * We ignore empty lines (only containing parseoptions->terminator)
+ *
+ * FIXME: This is so extremely ugly that I am too tired to rewrite it right now.
+ * Think hard of a better more flexible solution...
+ **/
+void
+stf_parse_options_fixed_autodiscover (StfParseOptions_t *parseoptions,
+ char const *data, char const *data_end)
+{
+ char const *iterator = data;
+ GSList *list = NULL;
+ GSList *list_start = NULL;
+ int lines = 0;
+ int effective_lines = 0;
+ int max_line_length = 0;
+ int *line_begin_hits = NULL;
+ int *line_end_hits = NULL;
+ int i;
+
+ stf_parse_options_fixed_splitpositions_clear (parseoptions);
+
+ /*
+ * First take a look at all possible white space combinations
+ */
+ while (*iterator && iterator < data_end) {
+ gboolean begin_recorded = FALSE;
+ AutoDiscovery_t *disc = NULL;
+ int position = 0;
+ int termlen = 0;
+
+ while (*iterator && (termlen = compare_terminator (iterator, parseoptions)) == 0) {
+ if (!begin_recorded && *iterator == ' ') {
+ disc = g_new0 (AutoDiscovery_t, 1);
+
+ disc->start = position;
+
+ begin_recorded = TRUE;
+ } else if (begin_recorded && *iterator != ' ') {
+ disc->stop = position;
+ list = g_slist_prepend (list, disc);
+
+ begin_recorded = FALSE;
+ disc = NULL;
+ }
+
+ position++;
+ iterator++;
+ }
+
+ if (position > max_line_length)
+ max_line_length = position;
+
+ /*
+ * If there are excess spaces at the end of
+ * the line : ignore them
+ */
+ g_free (disc);
+
+ /*
+ * Hop over the terminator
+ */
+ iterator += termlen;
+
+ if (position != 0)
+ effective_lines++;
+
+ lines++;
+ }
+
+ list = g_slist_reverse (list);
+ list_start = list;
+
+ /*
+ * Kewl stuff :
+ * Look at the number of hits at each line position
+ * if the number of hits equals the number of lines
+ * we can be pretty sure this is the start or end
+ * of a column, we filter out empty columns
+ * later
+ */
+ line_begin_hits = g_new0 (int, max_line_length + 1);
+ line_end_hits = g_new0 (int, max_line_length + 1);
+
+ while (list) {
+ AutoDiscovery_t *disc = list->data;
+
+ line_begin_hits[disc->start]++;
+ line_end_hits[disc->stop]++;
+
+ g_free (disc);
+
+ list = g_slist_next (list);
+ }
+ g_slist_free (list_start);
+
+ for (i = 0; i < max_line_length + 1; i++)
+ if (line_begin_hits[i] == effective_lines || line_end_hits[i] == effective_lines)
+ stf_parse_options_fixed_splitpositions_add (parseoptions, i);
+
+ /*
+ * Do some corrections to the initial columns
+ * detected here, we obviously don't need to
+ * do this if there are no columns at all.
+ */
+ if (my_garray_len (parseoptions->splitpositions) > 0) {
+ /*
+ * Try to find columns that look like :
+ *
+ * Example 100
+ * Example2 9
+ *
+ * (In other words : Columns with left & right justification with
+ * a minimum of 2 spaces in the middle)
+ * Split these columns in 2
+ */
+
+ for (i = 0; i < my_garray_len (parseoptions->splitpositions) - 1; i++) {
+ int begin = g_array_index (parseoptions->splitpositions, int, i);
+ int end = g_array_index (parseoptions->splitpositions, int, i + 1);
+ int num_spaces = -1;
+ int spaces_start = 0;
+ gboolean right_aligned = TRUE;
+ gboolean left_aligned = TRUE;
+ gboolean has_2_spaces = TRUE;
+
+ iterator = data;
+ lines = 0;
+ while (*iterator && iterator < data_end) {
+ gboolean trigger = FALSE;
+ gboolean space_trigger = FALSE;
+ int pos = 0;
+
+ num_spaces = -1;
+ spaces_start = 0;
+ while (*iterator && !compare_terminator (iterator, parseoptions)) {
+ if (pos == begin) {
+ if (*iterator == ' ')
+ left_aligned = FALSE;
+
+ trigger = TRUE;
+ } else if (pos == end - 1) {
+ if (*iterator == ' ')
+ right_aligned = FALSE;
+
+ trigger = FALSE;
+ }
+
+ if (trigger || pos == end - 1) {
+ if (!space_trigger && *iterator == ' ') {
+ space_trigger = TRUE;
+ spaces_start = pos;
+ } else if (space_trigger && *iterator != ' ') {
+ space_trigger = FALSE;
+ num_spaces = pos - spaces_start;
+ }
+ }
+
+ iterator++;
+ pos++;
+ }
+
+ if (num_spaces < 2)
+ has_2_spaces = FALSE;
+
+ if (*iterator)
+ iterator++;
+
+ lines++;
+ }
+
+ /*
+ * If this column meets all the criteria
+ * split it into two at the last measured
+ * spaces_start + num_spaces
+ */
+ if (has_2_spaces && right_aligned && left_aligned) {
+ int val = (((spaces_start + num_spaces) - spaces_start) / 2) + spaces_start;
+
+ g_array_insert_val (parseoptions->splitpositions, i + 1, val);
+
+ /*
+ * Skip over the inserted column
+ */
+ i++;
+ }
+ }
+
+ /*
+ * Remove empty columns here if needed
+ */
+ for (i = 0; i < my_garray_len (parseoptions->splitpositions) - 1; i++) {
+ int begin = g_array_index (parseoptions->splitpositions, int, i);
+ int end = g_array_index (parseoptions->splitpositions, int, i + 1);
+ gboolean only_spaces = TRUE;
+
+ iterator = data;
+ lines = 0;
+ while (*iterator && iterator < data_end) {
+ gboolean trigger = FALSE;
+ int pos = 0;
+
+ while (*iterator && !compare_terminator (iterator, parseoptions)) {
+ if (pos == begin)
+ trigger = TRUE;
+ else if (pos == end)
+ trigger = FALSE;
+
+ if (trigger) {
+ if (*iterator != ' ')
+ only_spaces = FALSE;
+ }
+
+ iterator++;
+ pos++;
+ }
+
+ if (*iterator)
+ iterator++;
+
+ lines++;
+ }
+
+ /*
+ * The column only contains spaces
+ * remove it
+ */
+ if (only_spaces) {
+ g_array_remove_index (parseoptions->splitpositions, i);
+
+ /*
+ * We HAVE to make sure that the next column (end) also
+ * gets checked out. If we don't decrease "i" here, we
+ * will skip over it as the indexes shift down after
+ * the removal
+ */
+ i--;
+ }
+ }
+ }
+
+ g_free (line_begin_hits);
+ g_free (line_end_hits);
+}
+
+/*******************************************************************************************************
+ * STF PARSE HL: high-level functions that dump the raw data returned by the low-level parsing
+ * functions into something meaningful (== application specific)
+ *******************************************************************************************************/
+
+/* gboolean */
+/* stf_parse_sheet (StfParseOptions_t *parseoptions, */
+/* char const *data, char const *data_end, */
+/* Sheet *sheet, int start_col, int start_row) */
+/* { */
+/* int row, col; */
+/* unsigned int lrow, lcol; */
+/* GODateConventions const *date_conv; */
+/* GStringChunk *lines_chunk; */
+/* GPtrArray *lines, *line; */
+
+/* SETUP_LOCALE_SWITCH; */
+
+/* g_return_val_if_fail (parseoptions != NULL, FALSE); */
+/* g_return_val_if_fail (data != NULL, FALSE); */
+/* g_return_val_if_fail (IS_SHEET (sheet), FALSE); */
+
+/* START_LOCALE_SWITCH; */
+
+/* date_conv = workbook_date_conv (sheet->workbook); */
+
+/* if (!data_end) */
+/* data_end = data + strlen (data); */
+/* lines_chunk = g_string_chunk_new (100 * 1024); */
+/* lines = stf_parse_general (parseoptions, lines_chunk, data, data_end); */
+/* if (lines == NULL) */
+/* return FALSE; */
+/* for (row = start_row, lrow = 0; lrow < lines->len ; row++, lrow++) { */
+/* col = start_col; */
+/* line = g_ptr_array_index (lines, lrow); */
+
+/* for (lcol = 0; lcol < line->len; lcol++) */
+/* if (parseoptions->col_import_array == NULL || */
+/* parseoptions->col_import_array_len <= lcol || */
+/* parseoptions->col_import_array[lcol]) { */
+/* if (col >= SHEET_MAX_COLS) { */
+/* if (!parseoptions->cols_exceeded) { */
+/* g_warning (_("There are more columns of data than " */
+/* "there is room for in the sheet. Extra " */
+/* "columns will be ignored.")); */
+/* parseoptions->cols_exceeded = TRUE; */
+/* } */
+/* } else { */
+/* char const *text = g_ptr_array_index (line, lcol); */
+/* if (text && *text) */
+/* gnm_cell_set_text ( */
+/* sheet_cell_fetch (sheet, col, row), */
+/* text); */
+/* } */
+/* col++; */
+/* } */
+/* } */
+
+/* stf_parse_general_free (lines); */
+/* g_string_chunk_free (lines_chunk); */
+/* END_LOCALE_SWITCH; */
+/* return TRUE; */
+/* } */
+
+/* GnmCellRegion * */
+/* stf_parse_region (StfParseOptions_t *parseoptions, char const *data, char const *data_end, */
+/* Workbook const *wb) */
+/* { */
+/* static GODateConventions const default_conv = {FALSE}; */
+/* GODateConventions const *date_conv = wb ? workbook_date_conv (wb) : &default_conv; */
+
+/* GnmCellRegion *cr; */
+/* unsigned int row, colhigh = 0; */
+/* char *text; */
+/* GStringChunk *lines_chunk; */
+/* GPtrArray *lines; */
+/* GnmCellCopy *cc; */
+/* GOFormat *fmt; */
+/* GnmValue *v; */
+
+/* SETUP_LOCALE_SWITCH; */
+
+/* g_return_val_if_fail (parseoptions != NULL, NULL); */
+/* g_return_val_if_fail (data != NULL, NULL); */
+
+/* START_LOCALE_SWITCH; */
+
+/* cr = cellregion_new (NULL); */
+
+/* if (!data_end) */
+/* data_end = data + strlen (data); */
+/* lines_chunk = g_string_chunk_new (100 * 1024); */
+/* lines = stf_parse_general (parseoptions, lines_chunk, data, data_end); */
+/* for (row = 0; row < lines->len; row++) { */
+/* GPtrArray *line = g_ptr_array_index (lines, row); */
+/* unsigned int col, targetcol = 0; */
+/* for (col = 0; col < line->len; col++) { */
+/* if (parseoptions->col_import_array == NULL || */
+/* parseoptions->col_import_array_len <= col || */
+/* parseoptions->col_import_array[col]) { */
+/* if (NULL != (text = g_ptr_array_index (line, col))) { */
+/* fmt = g_ptr_array_index ( */
+/* parseoptions->formats, col); */
+/* if (NULL == (v = format_match (text, fmt, date_conv))) */
+/* v = value_new_string (text); */
+
+/* cc = gnm_cell_copy_new (cr, targetcol, row); */
+/* cc->val = v; */
+/* cc->texpr = NULL; */
+/* targetcol++; */
+/* if (targetcol > colhigh) */
+/* colhigh = targetcol; */
+/* } */
+/* } */
+/* } */
+/* } */
+/* stf_parse_general_free (lines); */
+/* g_string_chunk_free (lines_chunk); */
+
+/* END_LOCALE_SWITCH; */
+
+/* cr->cols = (colhigh > 0) ? colhigh : 1; */
+/* cr->rows = row; */
+
+/* return cr; */
+/* } */
+
+static int
+int_sort (void const *a, void const *b)
+{
+ return *(int const *)a - *(int const *)b;
+}
+
+static int
+count_character (GPtrArray *lines, gunichar c, double quantile)
+{
+ int *counts, res;
+ unsigned int lno, cno;
+
+ if (lines->len == 0)
+ return 0;
+
+ counts = g_new (int, lines->len);
+ for (lno = cno = 0; lno < lines->len; lno++) {
+ int count = 0;
+ GPtrArray *boxline = g_ptr_array_index (lines, lno);
+ char const *line = g_ptr_array_index (boxline, 0);
+
+ /* Ignore empty lines. */
+ if (*line == 0)
+ continue;
+
+ while (*line) {
+ if (g_utf8_get_char (line) == c)
+ count++;
+ line = g_utf8_next_char (line);
+ }
+
+ counts[cno++] = count;
+ }
+
+ if (cno == 0)
+ res = 0;
+ else {
+ unsigned int qi = (unsigned int)ceil (quantile * cno);
+ qsort (counts, cno, sizeof (counts[0]), int_sort);
+ if (qi == cno)
+ qi--;
+ res = counts[qi];
+ }
+
+ g_free (counts);
+
+ return res;
+}
+
+
+StfParseOptions_t *
+stf_parse_options_guess (char const *data)
+{
+ StfParseOptions_t *res;
+ GStringChunk *lines_chunk;
+ GPtrArray *lines;
+ int tabcount;
+ int sepcount;
+ /* TODO In the future, use the goffice 0.3. */
+ /* gunichar sepchar = go_locale_get_arg_sep (); */
+ gunichar sepchar = ',';
+
+ g_return_val_if_fail (data != NULL, NULL);
+
+ res = stf_parse_options_new ();
+ lines_chunk = g_string_chunk_new (100 * 1024);
+ lines = stf_parse_lines (res, lines_chunk, data, SHEET_MAX_ROWS, FALSE);
+
+ tabcount = count_character (lines, '\t', 0.2);
+ sepcount = count_character (lines, sepchar, 0.2);
+
+ /* At least one tab per line and enough to separate every
+ would-be sepchars. */
+ if (tabcount >= 1 && tabcount >= sepcount - 1)
+ stf_parse_options_csv_set_separators (res, "\t", NULL);
+ else {
+ gunichar c;
+
+ /*
+ * Try a few more or less likely characters and pick the first
+ * one that occurs on at least half the lines.
+ *
+ * The order is mostly random, although ' ' and '!' which
+ * could very easily occur in text are put last.
+ */
+ /* TODO Replace with the 0.3 goffice call in the future. */
+ if (count_character (lines, (c = sepchar), 0.5) > 0 ||
+ /* count_character (lines, (c = go_locale_get_col_sep ()), 0.5) > 0 || */
+ count_character (lines, (c = ','), 0.5) > 0 ||
+ count_character (lines, (c = ':'), 0.5) > 0 ||
+ count_character (lines, (c = ','), 0.5) > 0 ||
+ count_character (lines, (c = ';'), 0.5) > 0 ||
+ count_character (lines, (c = '|'), 0.5) > 0 ||
+ count_character (lines, (c = '!'), 0.5) > 0 ||
+ count_character (lines, (c = ' '), 0.5) > 0) {
+ char sep[7];
+ sep[g_unichar_to_utf8 (c, sep)] = 0;
+ if (c == ' ')
+ strcat (sep, "\t");
+ stf_parse_options_csv_set_separators (res, sep, NULL);
+ }
+ }
+
+ if (1) {
+ /* Separated */
+ gboolean dups =
+ res->sep.chr &&
+ strchr (res->sep.chr, ' ') != NULL;
+ gboolean trim =
+ res->sep.chr &&
+ strchr (res->sep.chr, ' ') != NULL;
+
+ stf_parse_options_set_type (res, PARSE_TYPE_CSV);
+ stf_parse_options_set_trim_spaces (res, TRIM_TYPE_LEFT | TRIM_TYPE_RIGHT);
+ stf_parse_options_csv_set_indicator_2x_is_single (res, TRUE);
+ stf_parse_options_csv_set_duplicates (res, dups);
+ stf_parse_options_csv_set_trim_seps (res, trim);
+
+ stf_parse_options_csv_set_stringindicator (res, '"');
+ } else {
+ /* Fixed-width */
+ }
+
+ stf_parse_general_free (lines);
+ g_string_chunk_free (lines_chunk);
+
+ return res;
+}
Added: gnucash/trunk/lib/stf/stf-parse.h
===================================================================
--- gnucash/trunk/lib/stf/stf-parse.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/lib/stf/stf-parse.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,112 @@
+#ifndef STF_PARSE_H
+#define STF_PARSE_H
+
+#include <glib.h>
+
+#define SHEET_MAX_ROWS (16*16*16*16) /* 0, 1, ... */
+#define SHEET_MAX_COLS (4*4*4*4) /* 0, 1, ... */
+
+typedef enum {
+ PARSE_TYPE_NOTSET = 1 << 0,
+ PARSE_TYPE_CSV = 1 << 1,
+ PARSE_TYPE_FIXED = 1 << 2
+} StfParseType_t;
+
+/* Additive. */
+typedef enum {
+ TRIM_TYPE_NEVER = 0,
+ TRIM_TYPE_LEFT = 1 << 0,
+ TRIM_TYPE_RIGHT = 2 << 1
+} StfTrimType_t;
+
+typedef struct {
+ StfParseType_t parsetype; /* The type of import to do */
+ StfTrimType_t trim_spaces; /* Trim spaces in fields? */
+
+ GSList * terminator; /* Line terminators */
+ char * locale;
+
+ struct {
+ guchar min, max;
+ } compiled_terminator;
+
+ /* CSV related */
+ struct {
+ GSList *str;
+ char *chr;
+ } sep;
+ gunichar stringindicator; /* String indicator */
+ gboolean indicator_2x_is_single;/* 2 quote chars form a single non-terminating quote */
+ gboolean duplicates; /* See two text separators as one? */
+ gboolean trim_seps; /* Ignore initial seps. */
+
+ /* Fixed width related */
+ GArray *splitpositions; /* Positions where text will be split vertically */
+
+ int rowcount; /* Number of rows parsed */
+ int colcount; /* Number of columns parsed */
+ gboolean *col_import_array; /* 0/1 array indicating */
+ /* which cols to import */
+ unsigned int col_import_array_len;
+ GPtrArray *formats ; /* Contains GnmFormat *s */
+ gboolean cols_exceeded; /* This is set to TRUE if */
+ /* we tried to import more than */
+ /* SHEET_MAX_COLS columns */
+} StfParseOptions_t;
+
+/* CREATION/DESTRUCTION of stf options struct */
+
+StfParseOptions_t *stf_parse_options_new (void);
+void stf_parse_options_free (StfParseOptions_t *parseoptions);
+
+StfParseOptions_t *stf_parse_options_guess (char const *data);
+
+/* MANIPULATION of stf options struct */
+
+void stf_parse_options_set_type (StfParseOptions_t *parseoptions,
+ StfParseType_t const parsetype);
+void stf_parse_options_clear_line_terminator (StfParseOptions_t *parseoptions);
+void stf_parse_options_add_line_terminator (StfParseOptions_t *parseoptions,
+ char const *terminator);
+void stf_parse_options_set_trim_spaces (StfParseOptions_t *parseoptions,
+ StfTrimType_t const trim_spaces);
+void stf_parse_options_csv_set_separators (StfParseOptions_t *parseoptions,
+ char const *character, GSList const *string);
+void stf_parse_options_csv_set_stringindicator (StfParseOptions_t *parseoptions,
+ gunichar const stringindicator);
+void stf_parse_options_csv_set_indicator_2x_is_single (StfParseOptions_t *parseoptions,
+ gboolean const indic_2x);
+void stf_parse_options_csv_set_duplicates (StfParseOptions_t *parseoptions,
+ gboolean const duplicates);
+void stf_parse_options_csv_set_trim_seps (StfParseOptions_t *parseoptions,
+ gboolean const trim_seps);
+void stf_parse_options_fixed_splitpositions_clear (StfParseOptions_t *parseoptions);
+void stf_parse_options_fixed_splitpositions_add (StfParseOptions_t *parseoptions,
+ int position);
+void stf_parse_options_fixed_splitpositions_remove (StfParseOptions_t *parseoptions,
+ int position);
+int stf_parse_options_fixed_splitpositions_count (StfParseOptions_t *parseoptions);
+int stf_parse_options_fixed_splitpositions_nth (StfParseOptions_t *parseoptions, int n);
+
+/* USING the stf structs to actually do some parsing, these are the lower-level functions and utility functions */
+
+GPtrArray *stf_parse_general (StfParseOptions_t *parseoptions,
+ GStringChunk *lines_chunk,
+ char const *data,
+ char const *data_end);
+void stf_parse_general_free (GPtrArray *lines);
+GPtrArray *stf_parse_lines (StfParseOptions_t *parseoptions,
+ GStringChunk *lines_chunk,
+ char const *data,
+ int maxlines,
+ gboolean with_lineno);
+
+void stf_parse_options_fixed_autodiscover (StfParseOptions_t *parseoptions,
+ char const *data,
+ char const *data_end);
+
+char const *stf_parse_find_line (StfParseOptions_t *parseoptions,
+ char const *data,
+ int line);
+
+#endif
Modified: gnucash/trunk/src/bin/gnucash-bin.c
===================================================================
--- gnucash/trunk/src/bin/gnucash-bin.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/bin/gnucash-bin.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -337,6 +337,7 @@
{ "gnucash/register/register-gnome", 0, FALSE },
{ "gnucash/import-export/qif-import", 0, FALSE },
{ "gnucash/import-export/ofx", 0, TRUE },
+ { "gnucash/import-export/csv", 0, TRUE },
{ "gnucash/import-export/log-replay", 0, TRUE },
{ "gnucash/import-export/hbci", 0, TRUE },
{ "gnucash/report/report-system", 0, FALSE },
Modified: gnucash/trunk/src/import-export/Makefile.am
===================================================================
--- gnucash/trunk/src/import-export/Makefile.am 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/Makefile.am 2007-10-12 22:51:34 UTC (rev 16561)
@@ -1,7 +1,7 @@
SUBDIRS = . schemas qif qif-import \
- ${OFX_DIR} ${HBCI_DIR} log-replay test
+ ${OFX_DIR} ${HBCI_DIR} log-replay test csv
DIST_SUBDIRS = schemas qif qif-import qif-io-core \
- ofx hbci log-replay test
+ ofx hbci log-replay test csv
pkglib_LTLIBRARIES=libgncmod-generic-import.la
Added: gnucash/trunk/src/import-export/csv/Makefile.am
===================================================================
--- gnucash/trunk/src/import-export/csv/Makefile.am 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/Makefile.am 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,59 @@
+SUBDIRS = .
+
+pkglib_LTLIBRARIES=libgncmod-csv.la
+
+libgncmod_csv_la_SOURCES = \
+ gncmod-csv-import.c \
+ gnc-plugin-csv.c \
+ gnc-csv-import.c \
+ gnc-csv-model.c \
+ gnc-csv-gnumeric-popup.c
+
+noinst_HEADERS = \
+ gnc-plugin-csv.h \
+ gnc-csv-import.h \
+ gnc-csv-model.h \
+ gnc-csv-gnumeric-popup.h
+
+libgncmod_csv_la_LDFLAGS = -avoid-version $(pkg-config --libs libgoffice-0.3)
+
+libgncmod_csv_la_LIBADD = \
+ ${top_builddir}/src/import-export/libgncmod-generic-import.la \
+ ${top_builddir}/src/gnome-utils/libgncmod-gnome-utils.la \
+ ${top_builddir}/src/app-utils/libgncmod-app-utils.la \
+ ${top_builddir}/src/engine/libgncmod-engine.la \
+ ${top_builddir}/src/core-utils/libgnc-core-utils.la \
+ ${top_builddir}/src/gnc-module/libgnc-module.la \
+ ${top_builddir}/lib/stf/libgnc-stf.la \
+ ${QOF_LIBS} \
+ ${GLIB_LIBS}
+
+AM_CFLAGS = \
+ -I${top_srcdir}/src \
+ -I${top_srcdir}/src/core-utils \
+ -I${top_srcdir}/src/engine \
+ -I${top_srcdir}/src/gnc-module \
+ -I${top_srcdir}/src/app-utils \
+ -I${top_srcdir}/src/gnome \
+ -I${top_srcdir}/src/gnome-utils \
+ -I${top_srcdir}/src/import-export \
+ -I${top_srcdir}/lib \
+ ${GNOME_CFLAGS} \
+ ${GTKHTML_CFLAGS} \
+ ${GLADE_CFLAGS} \
+ ${GUILE_INCS} \
+ ${QOF_CFLAGS} \
+ ${GLIB_CFLAGS} \
+ $(GOFFICE_CFLAGS)
+
+uidir = $(GNC_UI_DIR)
+ui_DATA = \
+ gnc-plugin-csv-ui.xml
+
+gladedir = ${GNC_GLADE_DIR}
+glade_DATA = \
+ gnc-csv-preview-dialog.glade
+
+EXTRA_DIST = $(ui_DATA)
+
+INCLUDES = -DG_LOG_DOMAIN=\"gnc.import.csv\"
Added: gnucash/trunk/src/import-export/csv/example-file.csv
===================================================================
--- gnucash/trunk/src/import-export/csv/example-file.csv 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/example-file.csv 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,4 @@
+"This file has colon separators":100:01/03/95
+"and the last line":-50:02.28.96
+"uses a different":-25.13:03/15/00
+"date format.":12.5:30-4-02
Added: gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,194 @@
+/* The following is code copied from Gnumeric 1.7.8 licensed under the
+ * GNU General Public License version 2. It is from the file
+ * gnumeric/src/gui-util.c, and it has been modified slightly to work
+ * within GnuCash. */
+
+/* Miguel de Icaza is not sure specifically who from the Gnumeric
+ * community is the copyright owner of the code below, so, on his
+ * recommendation, here is the full list of Gnumeric authors.
+ *
+ * Miguel de Icaza, creator.
+ * Jody Goldberg, maintainer.
+ * Harald Ashburner, Options pricers
+ * Sean Atkinson, functions and X-Base importing.
+ * Michel Berkelaar, Simplex algorithm for Solver (LP Solve).
+ * Jean Brefort, Core charting engine.
+ * Grandma Chema Celorio, Tester and sheet copy.
+ * Frank Chiulli, OLE support.
+ * Kenneth Christiansen, i18n, misc stuff.
+ * Zbigniew Chyla, plugin system, i18n.
+ * J.H.M. Dassen (Ray), debian packaging.
+ * Jeroen Dirks, Simplex algorithm for Solver (LP Solve).
+ * Tom Dyas, plugin support.
+ * Gergo Erdi, Gnumeric hacker.
+ * John Gotts, rpm packaging.
+ * Andreas J. Guelzow, Gnumeric hacker.
+ * Jon K. Hellan, Gnumeric hacker.
+ * Ross Ihaka, special functions.
+ * Jukka-Pekka Iivonen, numerous functions and tools.
+ * Jakub Jelinek, Gnumeric hacker.
+ * Chris Lahey, number format engine.
+ * Adrian Likins, documentation, debugging.
+ * Takashi Matsuda, original text plugin.
+ * Michael Meeks, Excel and OLE2 importing.
+ * Lutz Muller, SheetObject improvements.
+ * Emmanuel Pacaud, Many plot types for charting engine.
+ * Federico M. Quintero, canvas support.
+ * Mark Probst, Guile support.
+ * Rasca, HTML, troff, LaTeX exporters.
+ * Vincent Renardias, original CSV support, French localization.
+ * Ariel Rios, Guile support.
+ * Uwe Steinmann, Paradox Importer.
+ * Arturo Tena, OLE support.
+ * Almer S. Tigelaar, Gnumeric hacker.
+ * Bruno Unna, Excel bits.
+ * Daniel Veillard, XML support.
+ * Vladimir Vuksan, financial functions.
+ * Morten Welinder, Gnumeric hacker and leak plugging demi-god.
+ */
+
+#include "gnc-csv-gnumeric-popup.h"
+
+#include <glib/gi18n.h>
+
+static void
+popup_item_activate (GtkWidget *item, gpointer *user_data)
+{
+ GnumericPopupMenuElement const *elem =
+ g_object_get_data (G_OBJECT (item), "descriptor");
+ GnumericPopupMenuHandler handler =
+ g_object_get_data (G_OBJECT (item), "handler");
+
+ g_return_if_fail (elem != NULL);
+ g_return_if_fail (handler != NULL);
+
+ if (handler (elem, user_data))
+ gtk_widget_destroy (gtk_widget_get_toplevel (item));
+}
+
+static void
+gnumeric_create_popup_menu_list (GSList *elements,
+ GnumericPopupMenuHandler handler,
+ gpointer user_data,
+ int display_filter,
+ int sensitive_filter,
+ GdkEventButton *event)
+{
+ GtkWidget *menu, *item;
+ char const *trans;
+
+ menu = gtk_menu_new ();
+
+ for (; elements != NULL ; elements = elements->next) {
+ GnumericPopupMenuElement const *element = elements->data;
+ char const * const name = element->name;
+ char const * const pix_name = element->pixmap;
+
+ item = NULL;
+
+ if (element->display_filter != 0 &&
+ !(element->display_filter & display_filter))
+ continue;
+
+ if (name != NULL && *name != '\0') {
+ trans = _(name);
+ item = gtk_image_menu_item_new_with_mnemonic (trans);
+ if (element->sensitive_filter != 0 &&
+ (element->sensitive_filter & sensitive_filter))
+ gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
+ if (pix_name != NULL) {
+ GtkWidget *image = gtk_image_new_from_stock (pix_name,
+ GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_image_menu_item_set_image (
+ GTK_IMAGE_MENU_ITEM (item),
+ image);
+ }
+ } else {
+ /* separator */
+ item = gtk_menu_item_new ();
+ gtk_widget_set_sensitive (item, FALSE);
+ }
+
+ if (element->index != 0) {
+ g_signal_connect (G_OBJECT (item),
+ "activate",
+ G_CALLBACK (&popup_item_activate), user_data);
+ g_object_set_data (
+ G_OBJECT (item), "descriptor", (gpointer)(element));
+ g_object_set_data (
+ G_OBJECT (item), "handler", (gpointer)handler);
+ }
+
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ }
+
+ gnumeric_popup_menu (GTK_MENU (menu), event);
+}
+
+void
+gnumeric_create_popup_menu (GnumericPopupMenuElement const *elements,
+ GnumericPopupMenuHandler handler,
+ gpointer user_data,
+ int display_filter, int sensitive_filter,
+ GdkEventButton *event)
+{
+ int i;
+ GSList *tmp = NULL;
+
+ for (i = 0; elements [i].name != NULL; i++)
+ tmp = g_slist_prepend (tmp, (gpointer)(elements + i));
+
+ tmp = g_slist_reverse (tmp);
+ gnumeric_create_popup_menu_list (tmp, handler, user_data,
+ display_filter, sensitive_filter, event);
+ g_slist_free (tmp);
+}
+
+static void
+kill_popup_menu (GtkWidget *widget, GtkMenu *menu)
+{
+ g_return_if_fail (menu != NULL);
+ g_return_if_fail (GTK_IS_MENU (menu));
+
+ g_object_unref (G_OBJECT (menu));
+}
+
+/**
+ * gnumeric_popup_menu :
+ * @menu : #GtkMenu
+ * @event : #GdkEventButton optionally NULL
+ *
+ * Bring up a popup and if @event is non-NULL ensure that the popup is on the
+ * right screen.
+ **/
+void
+gnumeric_popup_menu (GtkMenu *menu, GdkEventButton *event)
+{
+ g_return_if_fail (menu != NULL);
+ g_return_if_fail (GTK_IS_MENU (menu));
+
+#if GLIB_CHECK_VERSION(2,10,0) && GTK_CHECK_VERSION(2,8,14)
+ g_object_ref_sink (menu);
+#else
+ g_object_ref (menu);
+ gtk_object_sink (GTK_OBJECT (menu));
+#endif
+
+ if (event)
+ gtk_menu_set_screen (menu,
+ gdk_drawable_get_screen (event->window));
+
+ g_signal_connect (G_OBJECT (menu),
+ "hide",
+ G_CALLBACK (kill_popup_menu), menu);
+
+ /* Do NOT pass the button used to create the menu.
+ * instead pass 0. Otherwise bringing up a menu with
+ * the right button will disable clicking on the menu with the left.
+ */
+ gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0,
+ (event != NULL) ? event->time
+ : gtk_get_current_event_time());
+}
Added: gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.h
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-gnumeric-popup.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,78 @@
+/* The following is code copied from Gnumeric 1.7.8 licensed under the
+ * GNU General Public License version 2. It is from the file
+ * gnumeric/src/gui-util.h, and it has been modified slightly to work
+ * within GnuCash. */
+
+/* Miguel de Icaza is not sure specifically who from the Gnumeric
+ * community is the copyright owner of the code below, so, on his
+ * recommendation, here is the full list of Gnumeric authors.
+ *
+ * Miguel de Icaza, creator.
+ * Jody Goldberg, maintainer.
+ * Harald Ashburner, Options pricers
+ * Sean Atkinson, functions and X-Base importing.
+ * Michel Berkelaar, Simplex algorithm for Solver (LP Solve).
+ * Jean Brefort, Core charting engine.
+ * Grandma Chema Celorio, Tester and sheet copy.
+ * Frank Chiulli, OLE support.
+ * Kenneth Christiansen, i18n, misc stuff.
+ * Zbigniew Chyla, plugin system, i18n.
+ * J.H.M. Dassen (Ray), debian packaging.
+ * Jeroen Dirks, Simplex algorithm for Solver (LP Solve).
+ * Tom Dyas, plugin support.
+ * Gergo Erdi, Gnumeric hacker.
+ * John Gotts, rpm packaging.
+ * Andreas J. Guelzow, Gnumeric hacker.
+ * Jon K. Hellan, Gnumeric hacker.
+ * Ross Ihaka, special functions.
+ * Jukka-Pekka Iivonen, numerous functions and tools.
+ * Jakub Jelinek, Gnumeric hacker.
+ * Chris Lahey, number format engine.
+ * Adrian Likins, documentation, debugging.
+ * Takashi Matsuda, original text plugin.
+ * Michael Meeks, Excel and OLE2 importing.
+ * Lutz Muller, SheetObject improvements.
+ * Emmanuel Pacaud, Many plot types for charting engine.
+ * Federico M. Quintero, canvas support.
+ * Mark Probst, Guile support.
+ * Rasca, HTML, troff, LaTeX exporters.
+ * Vincent Renardias, original CSV support, French localization.
+ * Ariel Rios, Guile support.
+ * Uwe Steinmann, Paradox Importer.
+ * Arturo Tena, OLE support.
+ * Almer S. Tigelaar, Gnumeric hacker.
+ * Bruno Unna, Excel bits.
+ * Daniel Veillard, XML support.
+ * Vladimir Vuksan, financial functions.
+ * Morten Welinder, Gnumeric hacker and leak plugging demi-god.
+ */
+
+#ifndef GNC_CSV_GNUMERIC_POPUP
+#define GNC_CSV_GNUMERIC_POPUP
+
+#include <gtk/gtk.h>
+
+typedef struct {
+ char const *name;
+ char const *pixmap;
+ int display_filter;
+ int sensitive_filter;
+
+ int index;
+} GnumericPopupMenuElement;
+
+typedef gboolean (*GnumericPopupMenuHandler) (GnumericPopupMenuElement const *e,
+ gpointer user_data);
+
+/* Use this on menus that are popped up */
+void gnumeric_popup_menu (GtkMenu *menu, GdkEventButton *event);
+
+void gnumeric_create_popup_menu (GnumericPopupMenuElement const *elements,
+ GnumericPopupMenuHandler handler,
+ gpointer user_data,
+ int display_filter,
+ int sensitive_filter,
+ GdkEventButton *event);
+
+
+#endif
Added: gnucash/trunk/src/import-export/csv/gnc-csv-import.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-import.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-import.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,1173 @@
+/*******************************************************************\
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+\********************************************************************/
+/** @file gnc-csv-import.c
+ @brief CSV Import GUI code
+ @author Copyright (c) 2007 Benny Sperisen <lasindi at gmail.com>
+*/
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+#include <goffice/gtk/go-charmap-sel.h>
+
+#include "import-account-matcher.h"
+#include "import-main-matcher.h"
+
+#include "gnc-file.h"
+#include "gnc-book.h"
+#include "gnc-ui-util.h"
+#include "gnc-glib-utils.h"
+#include "gnc-gui-query.h"
+#include "dialog-utils.h"
+
+#include "gnc-csv-import.h"
+#include "gnc-csv-model.h"
+#include "gnc-csv-gnumeric-popup.h"
+
+#define GCONF_SECTION "dialogs/import/csv"
+
+static QofLogModule log_module = GNC_MOD_IMPORT;
+
+/** Enumeration for separator checkbutton types. These are the
+ * different types of checkbuttons that the user can click to
+ * configure separators in a delimited file. */
+enum SEP_BUTTON_TYPES {SEP_SPACE, SEP_TAB, SEP_COMMA, SEP_COLON, SEP_SEMICOLON, SEP_HYPHEN,
+ SEP_NUM_OF_TYPES};
+
+/** Data for the preview dialog. This struct contains all of the data
+ * relevant to the preview dialog that lets the user configure an
+ * import. */
+typedef struct
+{
+ GncCsvParseData* parse_data; /**< The actual data we are previewing */
+ GtkDialog* dialog;
+ GOCharmapSel* encselector; /**< The widget for selecting the encoding */
+ GtkComboBox* date_format_combo; /**< The widget for selecting the date format */
+ GladeXML* xml; /**< The Glade file that contains the dialog. */
+ GtkTreeView* treeview; /**< The treeview containing the data */
+ GtkTreeView* ctreeview; /**< The treeview containing the column types */
+ GtkCheckButton* sep_buttons[SEP_NUM_OF_TYPES]; /**< Checkbuttons for common separators */
+ GtkCheckButton* custom_cbutton; /**< The checkbutton for a custom separator */
+ GtkEntry* custom_entry; /**< The entry for custom separators */
+ gboolean encoding_selected_called; /**< Before encoding_selected is first called, this is FALSE.
+ * (See description of encoding_selected.) */
+ gboolean not_empty; /**< FALSE initially, true after the first type gnc_csv_preview_update is called. */
+ gboolean previewing_errors; /**< TRUE if the dialog is displaying
+ * error lines, instead of all the file
+ * data. */
+ int code_encoding_calls; /**< Normally this is 0. If the computer
+ * changes encselector, this is set to
+ * 2. encoding_selected is called twice,
+ * each time decrementing this by 1. */
+ gboolean approved; /**< This is FALSE until the user clicks "OK". */
+ GtkWidget** treeview_buttons; /**< This array contains the header buttons in treeview */
+ int longest_line; /**< The length of the longest row */
+ int fixed_context_col; /**< The number of the column whose the user has clicked */
+ int fixed_context_dx; /**< The horizontal coordinate of the pixel in the header of the column
+ * the user has clicked */
+} GncCsvPreview;
+
+static void gnc_csv_preview_update(GncCsvPreview* preview);
+
+/** Event handler for separator changes. This function is called
+ * whenever one of the widgets for configuring the separators (the
+ * separator checkbuttons or the custom separator entry) is
+ * changed.
+ * @param widget The widget that was changed
+ * @param preview The data that is being configured
+ */
+static void sep_button_clicked(GtkWidget* widget, GncCsvPreview* preview)
+{
+ int i;
+ char* stock_separator_characters[] = {" ", "\t", ",", ":", ";", "-"};
+ GSList* checked_separators = NULL;
+ GError* error;
+
+ /* Add the corresponding characters to checked_separators for each
+ * button that is checked. */
+ for(i = 0; i < SEP_NUM_OF_TYPES; i++)
+ {
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(preview->sep_buttons[i])))
+ checked_separators = g_slist_append(checked_separators, stock_separator_characters[i]);
+ }
+
+ /* Add the custom separator if the user checked its button. */
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(preview->custom_cbutton)))
+ {
+ char* custom_sep = (char*)gtk_entry_get_text(preview->custom_entry);
+ if(custom_sep[0] != '\0') /* Don't add a blank separator (bad things will happen!). */
+ checked_separators = g_slist_append(checked_separators, custom_sep);
+ }
+
+ /* Set the parse options using the checked_separators list. */
+ stf_parse_options_csv_set_separators(preview->parse_data->options, NULL, checked_separators);
+ g_slist_free(checked_separators);
+
+ /* Parse the data using the new options. We don't want to reguess
+ * the column types because we want to leave the user's
+ * configurations in tact. */
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ /* Warn the user there was a problem and try to undo what caused
+ * the error. (This will cause a reparsing and ideally a usable
+ * configuration.) */
+ gnc_error_dialog(NULL, "Error in parsing");
+ /* If the user changed the custom separator, erase that custom separator. */
+ if(widget == GTK_WIDGET(preview->custom_entry))
+ {
+ gtk_entry_set_text(GTK_ENTRY(widget), "");
+ }
+ /* If the user checked a checkbutton, toggle that checkbutton back. */
+ else
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+ !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
+ }
+ return;
+ }
+
+ /* If we parsed successfully, redisplay the data. */
+ gnc_csv_preview_update(preview);
+}
+
+/** Event handler for clicking one of the format type radio
+ * buttons. This occurs if the format (Fixed-Width or CSV) is changed.
+ * @param csv_button The "Separated" radio button
+ * @param preview The display of the data being imported
+ */
+static void separated_or_fixed_selected(GtkToggleButton* csv_button, GncCsvPreview* preview)
+{
+ GError* error = NULL;
+ /* Set the parsing type correctly. */
+ if(gtk_toggle_button_get_active(csv_button)) /* If we're in CSV mode ... */
+ {
+ stf_parse_options_set_type(preview->parse_data->options, PARSE_TYPE_CSV);
+ }
+ else /* If we're in fixed-width mode ... */
+ {
+ stf_parse_options_set_type(preview->parse_data->options, PARSE_TYPE_FIXED);
+ }
+
+ /* Reparse the data. */
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ /* Show an error dialog explaining the problem. */
+ gnc_error_dialog(NULL, "%s", error->message);
+ return;
+ }
+
+ /* Show the new data. */
+ gnc_csv_preview_update(preview);
+}
+
+/** Event handler for a new encoding. This is called when the user
+ * selects a new encoding; the data is reparsed and shown to the
+ * user.
+ * @param selector The widget the user uses to select a new encoding
+ * @param encoding The encoding that the user selected
+ * @param preview The display of the data being imported
+ */
+static void encoding_selected(GOCharmapSel* selector, const char* encoding,
+ GncCsvPreview* preview)
+{
+ /* This gets called twice everytime a new encoding is selected. The
+ * second call actually passes the correct data; thus, we only do
+ * something the second time this is called. */
+
+ /* Prevent code-caused calls of this function from having an impact. */
+ if(preview->code_encoding_calls > 0)
+ {
+ preview->code_encoding_calls--;
+ return;
+ }
+
+ /* If this is the second time the function is called ... */
+ if(preview->encoding_selected_called)
+ {
+ const char* previous_encoding = preview->parse_data->encoding;
+ GError* error = NULL;
+ /* Try converting the new encoding and reparsing. */
+ if(gnc_csv_convert_encoding(preview->parse_data, encoding, &error) ||
+ gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ /* If it fails, change back to the old encoding. */
+ gnc_error_dialog(NULL, _("Invalid encoding selected"));
+ preview->encoding_selected_called = FALSE;
+ go_charmap_sel_set_encoding(selector, previous_encoding);
+ return;
+ }
+
+ gnc_csv_preview_update(preview);
+ preview->encoding_selected_called = FALSE;
+ }
+ else /* If this is the first call of the function ... */
+ {
+ preview->encoding_selected_called = TRUE; /* ... set the flag and wait for the next call. */
+ }
+}
+
+/** Event handler for selecting a new date format.
+ * @param format_selector The combo box for selecting date formats
+ * @param preview The display of the data being imported
+ */
+static void date_format_selected(GtkComboBox* format_selector, GncCsvPreview* preview)
+{
+ preview->parse_data->date_format = gtk_combo_box_get_active(format_selector);
+}
+
+/** Event handler for the "OK" button. When "OK" is clicked, this
+ * function updates the parse data with the user's column type
+ * configuration and closes the preview dialog.
+ * @param widget The "OK" button
+ * @param preview The display of the data being imported
+ */
+static void ok_button_clicked(GtkWidget* widget, GncCsvPreview* preview)
+{
+ /* Shorten the column_types identifier. */
+ GArray* column_types = preview->parse_data->column_types;
+ int i, ncols = column_types->len; /* ncols is the number of columns in the data. */
+ /* store contains the actual strings appearing in the column types treeview. */
+ GtkTreeModel* store = gtk_tree_view_get_model(preview->ctreeview);
+ GtkTreeIter iter;
+ /* Get an iterator for the first (and only) row. */
+ gtk_tree_model_get_iter_first(store, &iter);
+
+ /* Go through each of the columns. */
+ for(i = 0; i < ncols; i++)
+ {
+ int type; /* The column type contained in this column. */
+ gchar* contents; /* The column type string in this column. */
+ /* Get the type string first. (store is arranged so that every two
+ * columns is a pair of the model used for the combobox and the
+ * string that appears, so that store looks like:
+ * model 0, string 0, model 1, string 1, ..., model ncols, string ncols. */
+ gtk_tree_model_get(store, &iter, 2*i+1, &contents, -1);
+
+ /* Go through each column type until ... */
+ for(type = 0; type < GNC_CSV_NUM_COL_TYPES; type++)
+ {
+ /* ... we find one that matches with what's in the column. */
+ if(!strcmp(contents, _(gnc_csv_column_type_strs[type])))
+ {
+ /* Set the column_types array appropriately and quit. */
+ column_types->data[i] = type;
+ break;
+ }
+ }
+ }
+
+ /* Close the dialog. */
+ gtk_widget_hide((GtkWidget*)(preview->dialog));
+ preview->approved = TRUE; /* The user has wants to do the import. */
+}
+
+/** Event handler for the "Cancel" button. When the user clicks
+ * "Cancel", the dialog is simply closed.
+ * @param widget The "Cancel" button
+ * @param preview The display of the data being imported
+ */
+static void cancel_button_clicked(GtkWidget* widget, GncCsvPreview* preview)
+{
+ gtk_widget_hide((GtkWidget*)(preview->dialog));
+}
+
+/** Event handler for the data treeview being resized. When the data
+ * treeview is resized, the column types treeview's columns are also resized to
+ * match.
+ * @param widget The data treeview
+ * @param allocation The size of the data treeview
+ * @param preview The display of the data being imported
+ */
+static void treeview_resized(GtkWidget* widget, GtkAllocation* allocation, GncCsvPreview* preview)
+{
+ /* ncols is the number of columns in the data. */
+ int i, ncols = preview->parse_data->column_types->len;
+
+ /* Go through each column except for the last. (We don't want to set
+ * the width of the last column because the user won't be able to
+ * shrink the dialog back if it's expanded.) */
+ for(i = 0; i < ncols - 1; i++)
+ {
+ gint col_width; /* The width of the column in preview->treeview. */
+ GtkTreeViewColumn* ccol; /* The corresponding column in preview->ctreeview. */
+
+ /* Get the width. */
+ col_width = gtk_tree_view_column_get_width(gtk_tree_view_get_column(preview->treeview, i));
+
+ /* Set ccol's width the same. */
+ ccol = gtk_tree_view_get_column(preview->ctreeview, i);
+ gtk_tree_view_column_set_min_width(ccol, col_width);
+ gtk_tree_view_column_set_max_width(ccol, col_width);
+ }
+}
+
+/** Event handler for the user selecting a new column type. When the
+ * user selects a new column type, that column's text must be changed
+ * to that selection, and any other columns containing that selection
+ * must be changed to "None" because we don't allow duplicates.
+ * @param renderer The renderer of the column the user changed
+ * @param path There is only 1 row in preview->ctreeview, so this is always 0.
+ * @param new_text The text the user selected
+ * @param preview The display of the data being imported
+ */
+static void column_type_edited(GtkCellRenderer* renderer, gchar* path,
+ gchar* new_text, GncCsvPreview* preview)
+{
+ /* ncols is the number of columns in the data. */
+ int i, ncols = preview->parse_data->column_types->len;
+ /* store has the actual strings that appear in preview->ctreeview. */
+ GtkTreeModel* store = gtk_tree_view_get_model(preview->ctreeview);
+ GtkTreeIter iter;
+ /* Get an iterator for the first (and only) row. */
+ gtk_tree_model_get_iter_first(store, &iter);
+
+ /* Go through each column. */
+ for(i = 0; i < ncols; i++)
+ {
+ /* We need all this stuff so that we can find out whether or not
+ * this was the column that was edited. */
+ GtkCellRenderer* col_renderer; /* The renderer for this column. */
+ /* The column in the treeview we are looking at */
+ GtkTreeViewColumn* col = gtk_tree_view_get_column(preview->ctreeview, i);
+ /* The list of renderers for col */
+ GList* rend_list = gtk_tree_view_column_get_cell_renderers(col);
+ /* rend_list has only one entry, which we put in col_renderer. */
+ col_renderer = rend_list->data;
+ g_list_free(rend_list);
+
+ /* If this is not the column that was edited ... */
+ if(col_renderer != renderer)
+ {
+ /* The string that appears in the column */
+ gchar* contents;
+ /* Get the type string. (store is arranged so that every two
+ * columns is a pair of the model used for the combobox and the
+ * string that appears, so that store looks like:
+ * model 0, string 0, model 1, string 1, ..., model ncols, string ncols. */
+ gtk_tree_model_get(store, &iter, 2*i+1, &contents, -1);
+ /* If this column has the same string that the user selected ... */
+ if(!strcmp(contents, new_text))
+ {
+ /* ... set this column to the "None" type. (We can't allow duplicate types.) */
+ gtk_list_store_set(GTK_LIST_STORE(store), &iter, 2*i+1,
+ _(gnc_csv_column_type_strs[GNC_CSV_NONE]), -1);
+ }
+ }
+ else /* If this is the column that was edited ... */
+ {
+ /* Set the text for this column to what the user selected. (See
+ * comment above "Get the type string. ..." for why we set
+ * column 2*i+1 in store.) */
+ gtk_list_store_set(GTK_LIST_STORE(store), &iter, 2*i+1, new_text, -1);
+ }
+ }
+}
+
+/** Constructor for GncCsvPreview.
+ * @return A new GncCsvPreview* ready for use.
+ */
+static GncCsvPreview* gnc_csv_preview_new()
+{
+ int i;
+ GncCsvPreview* preview = g_new(GncCsvPreview, 1);
+ GtkWidget *ok_button, *cancel_button, *csv_button;
+ GtkContainer* date_format_container;
+ /* The names in the glade file for the sep buttons. */
+ char* sep_button_names[] = {"space_cbutton",
+ "tab_cbutton",
+ "comma_cbutton",
+ "colon_cbutton",
+ "semicolon_cbutton",
+ "hyphen_cbutton"};
+ /* The table containing preview->encselector and the separator configuration widgets */
+ GtkTable* enctable;
+ PangoContext* context; /* Used to set a monotype font on preview->treeview */
+
+ preview->encselector = GO_CHARMAP_SEL(go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8));
+ /* Connect the selector to the encoding_selected event handler. */
+ g_signal_connect(G_OBJECT(preview->encselector), "charmap_changed",
+ G_CALLBACK(encoding_selected), (gpointer)preview);
+
+ /* Load the Glade file. */
+ preview->xml = gnc_glade_xml_new("gnc-csv-preview-dialog.glade", "dialog");
+ /* Load the dialog. */
+ preview->dialog = GTK_DIALOG(glade_xml_get_widget(preview->xml, "dialog"));
+
+ /* Load the separator buttons from the glade file into the
+ * preview->sep_buttons array. */
+ for(i = 0; i < SEP_NUM_OF_TYPES; i++)
+ {
+ preview->sep_buttons[i]
+ = (GtkCheckButton*)glade_xml_get_widget(preview->xml, sep_button_names[i]);
+ /* Connect them to the sep_button_clicked event handler. */
+ g_signal_connect(G_OBJECT(preview->sep_buttons[i]), "toggled",
+ G_CALLBACK(sep_button_clicked), (gpointer)preview);
+ }
+
+ /* Load and connect the custom separator checkbutton in the same way
+ * as the other separator buttons. */
+ preview->custom_cbutton
+ = (GtkCheckButton*)glade_xml_get_widget(preview->xml, "custom_cbutton");
+ g_signal_connect(G_OBJECT(preview->custom_cbutton), "clicked",
+ G_CALLBACK(sep_button_clicked), (gpointer)preview);
+
+ /* Load the entry for the custom separator entry. Connect it to the
+ * sep_button_clicked event handler as well. */
+ preview->custom_entry = (GtkEntry*)glade_xml_get_widget(preview->xml, "custom_entry");
+ g_signal_connect(G_OBJECT(preview->custom_entry), "changed",
+ G_CALLBACK(sep_button_clicked), (gpointer)preview);
+
+ /* Get the table from the Glade file. */
+ enctable = GTK_TABLE(glade_xml_get_widget(preview->xml, "enctable"));
+ /* Put the selector in at the top. */
+ gtk_table_attach_defaults(enctable, GTK_WIDGET(preview->encselector), 1, 2, 0, 1);
+ /* Show the table in all its glory. */
+ gtk_widget_show_all(GTK_WIDGET(enctable));
+
+ /* Add in the date format combo box and hook it up to an event handler. */
+ preview->date_format_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
+ for(i = 0; i < num_date_formats; i++)
+ {
+ gtk_combo_box_append_text(preview->date_format_combo, _(date_format_user[i]));
+ }
+ gtk_combo_box_set_active(preview->date_format_combo, 0);
+ g_signal_connect(G_OBJECT(preview->date_format_combo), "changed",
+ G_CALLBACK(date_format_selected), (gpointer)preview);
+
+ /* Add it to the dialog. */
+ date_format_container = GTK_CONTAINER(glade_xml_get_widget(preview->xml,
+ "date_format_container"));
+ gtk_container_add(date_format_container, GTK_WIDGET(preview->date_format_combo));
+ gtk_widget_show_all(GTK_WIDGET(date_format_container));
+
+ /* Connect the "OK" and "Cancel" buttons to their event handlers. */
+ ok_button = glade_xml_get_widget(preview->xml, "ok_button");
+ g_signal_connect(G_OBJECT(ok_button), "clicked",
+ G_CALLBACK(ok_button_clicked), (gpointer)preview);
+
+ cancel_button = glade_xml_get_widget(preview->xml, "cancel_button");
+ g_signal_connect(G_OBJECT(cancel_button), "clicked",
+ G_CALLBACK(cancel_button_clicked), (gpointer)preview);
+
+ /* Connect the CSV/Fixed-Width radio button event handler. */
+ csv_button = glade_xml_get_widget(preview->xml, "csv_button");
+ g_signal_connect(csv_button, "toggled",
+ G_CALLBACK(separated_or_fixed_selected), (gpointer)preview);
+
+ /* Load the data treeview and connect it to its resizing event handler. */
+ preview->treeview = (GtkTreeView*)(glade_xml_get_widget(preview->xml, "treeview"));
+ g_signal_connect(G_OBJECT(preview->treeview), "size-allocate",
+ G_CALLBACK(treeview_resized), (gpointer)preview);
+ context = gtk_widget_create_pango_context(GTK_WIDGET(preview->treeview));
+
+ /* Load the column type treeview. */
+ preview->ctreeview = (GtkTreeView*)(glade_xml_get_widget(preview->xml, "ctreeview"));
+
+ /* This is TRUE only after encoding_selected is called, so we must
+ * set it initially to FALSE. */
+ preview->encoding_selected_called = FALSE;
+
+ /* It is empty at first. */
+ preview->not_empty = FALSE;
+
+ return preview;
+}
+
+/** Destructor for GncCsvPreview. This does not free
+ * preview->parse_data, which must be freed separately.
+ * @param preview The preview whose memory is freed.
+ */
+static void gnc_csv_preview_free(GncCsvPreview* preview)
+{
+ g_object_unref(preview->xml);
+ g_free(preview);
+}
+
+/** Returns the cell renderer from a column in the preview's treeview.
+ * @param preview The display of the data being imported
+ * @param col The number of the column whose cell renderer is being retrieved
+ * @return The cell renderer of column number col
+ */
+static GtkCellRenderer* gnc_csv_preview_get_cell_renderer(GncCsvPreview* preview, int col)
+{
+ GList* renderers = gtk_tree_view_column_get_cell_renderers(gtk_tree_view_get_column(preview->treeview, col));
+ GtkCellRenderer* cell = GTK_CELL_RENDERER(renderers->data);
+ g_list_free(renderers);
+ return cell;
+}
+
+/* The following is code copied from Gnumeric 1.7.8 licensed under the
+ * GNU General Public License version 2. It is from the file
+ * gnumeric/src/dialogs/dialog-stf-fixed-page.c, and it has been
+ * modified slightly to work within GnuCash. */
+
+/* ---- Beginning of Gnumeric Code ---- */
+
+/*
+ * Copyright 2001 Almer S. Tigelaar <almer at gnome.org>
+ * Copyright 2003 Morten Welinder <terra at gnome.org>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+enum {
+ CONTEXT_STF_IMPORT_MERGE_LEFT = 1,
+ CONTEXT_STF_IMPORT_MERGE_RIGHT = 2,
+ CONTEXT_STF_IMPORT_SPLIT = 3,
+ CONTEXT_STF_IMPORT_WIDEN = 4,
+ CONTEXT_STF_IMPORT_NARROW = 5
+};
+
+static GnumericPopupMenuElement const popup_elements[] = {
+ { N_("Merge with column on _left"), GTK_STOCK_REMOVE,
+ 0, 1 << CONTEXT_STF_IMPORT_MERGE_LEFT, CONTEXT_STF_IMPORT_MERGE_LEFT },
+ { N_("Merge with column on _right"), GTK_STOCK_REMOVE,
+ 0, 1 << CONTEXT_STF_IMPORT_MERGE_RIGHT, CONTEXT_STF_IMPORT_MERGE_RIGHT },
+ { "", NULL, 0, 0, 0 },
+ { N_("_Split this column"), NULL,
+ 0, 1 << CONTEXT_STF_IMPORT_SPLIT, CONTEXT_STF_IMPORT_SPLIT },
+ { "", NULL, 0, 0, 0 },
+ { N_("_Widen this column"), GTK_STOCK_GO_FORWARD,
+ 0, 1 << CONTEXT_STF_IMPORT_WIDEN, CONTEXT_STF_IMPORT_WIDEN },
+ { N_("_Narrow this column"), GTK_STOCK_GO_BACK,
+ 0, 1 << CONTEXT_STF_IMPORT_NARROW, CONTEXT_STF_IMPORT_NARROW },
+ { NULL, NULL, 0, 0, 0 },
+};
+
+static gboolean
+make_new_column (GncCsvPreview *preview, int col, int dx, gboolean test_only)
+{
+ PangoLayout *layout;
+ PangoFontDescription *font_desc;
+ int charindex, width;
+ GtkCellRenderer *cell = gnc_csv_preview_get_cell_renderer(preview, col);
+ int colstart, colend;
+ GError* error = NULL;
+
+ colstart = (col == 0)
+ ? 0
+ : stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col - 1);
+ colend = stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col);
+
+ g_object_get (G_OBJECT (cell), "font_desc", &font_desc, NULL);
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (preview->treeview), "x");
+ pango_layout_set_font_description (layout, font_desc);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+ if (width < 1) width = 1;
+ charindex = colstart + (dx + width / 2) / width;
+ g_object_unref (layout);
+ pango_font_description_free (font_desc);
+
+ if (charindex <= colstart || (colend != -1 && charindex >= colend))
+ return FALSE;
+
+ if (!test_only) {
+ stf_parse_options_fixed_splitpositions_add (preview->parse_data->options, charindex);
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ gnc_error_dialog(NULL, "%s", error->message);
+ return FALSE;
+ }
+ gnc_csv_preview_update (preview);
+ }
+
+ return TRUE;
+}
+
+
+static gboolean
+widen_column (GncCsvPreview *preview, int col, gboolean test_only)
+{
+ int colcount = stf_parse_options_fixed_splitpositions_count (preview->parse_data->options);
+ int nextstart, nextnextstart;
+ GError* error = NULL;
+
+ if (col >= colcount - 1)
+ return FALSE;
+
+ nextstart = stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col);
+
+ nextnextstart = (col == colcount - 2)
+ ? preview->longest_line
+ : stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col + 1);
+
+ if (nextstart + 1 >= nextnextstart)
+ return FALSE;
+
+ if (!test_only) {
+ stf_parse_options_fixed_splitpositions_remove (preview->parse_data->options, nextstart);
+ stf_parse_options_fixed_splitpositions_add (preview->parse_data->options, nextstart + 1);
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ gnc_error_dialog(NULL, "%s", error->message);
+ return FALSE;
+ }
+ gnc_csv_preview_update (preview);
+ }
+ return TRUE;
+}
+
+static gboolean
+narrow_column (GncCsvPreview *preview, int col, gboolean test_only)
+{
+ int colcount = stf_parse_options_fixed_splitpositions_count (preview->parse_data->options);
+ int thisstart, nextstart;
+ GError* error = NULL;
+
+ if (col >= colcount - 1)
+ return FALSE;
+
+ thisstart = (col == 0)
+ ? 0
+ : stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col - 1);
+ nextstart = stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col);
+
+ if (nextstart - 1 <= thisstart)
+ return FALSE;
+
+ if (!test_only) {
+ stf_parse_options_fixed_splitpositions_remove (preview->parse_data->options, nextstart);
+ stf_parse_options_fixed_splitpositions_add (preview->parse_data->options, nextstart - 1);
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ gnc_error_dialog(NULL, "%s", error->message);
+ return FALSE;
+ }
+ gnc_csv_preview_update (preview);
+ }
+ return TRUE;
+}
+
+static gboolean
+delete_column (GncCsvPreview *preview, int col, gboolean test_only)
+{
+ GError* error = NULL;
+ int colcount = stf_parse_options_fixed_splitpositions_count (preview->parse_data->options);
+ if (col < 0 || col >= colcount - 1)
+ return FALSE;
+
+ if (!test_only) {
+ int nextstart = stf_parse_options_fixed_splitpositions_nth (preview->parse_data->options, col);
+ stf_parse_options_fixed_splitpositions_remove (preview->parse_data->options, nextstart);
+ if(gnc_csv_parse(preview->parse_data, FALSE, &error))
+ {
+ gnc_error_dialog(NULL, "%s", error->message);
+ return FALSE;
+ }
+ gnc_csv_preview_update (preview);
+ }
+ return TRUE;
+}
+
+static void
+select_column (GncCsvPreview *preview, int col)
+{
+ GError* error = NULL;
+ int colcount = stf_parse_options_fixed_splitpositions_count (preview->parse_data->options);
+ GtkTreeViewColumn *column;
+
+ if (col < 0 || col >= colcount)
+ return;
+
+ column = gtk_tree_view_get_column (preview->treeview, col);
+ gtk_widget_grab_focus (column->button);
+}
+
+static gboolean
+fixed_context_menu_handler (GnumericPopupMenuElement const *element,
+ gpointer user_data)
+{
+ GncCsvPreview *preview = user_data;
+ int col = preview->fixed_context_col;
+
+ switch (element->index) {
+ case CONTEXT_STF_IMPORT_MERGE_LEFT:
+ delete_column (preview, col - 1, FALSE);
+ break;
+ case CONTEXT_STF_IMPORT_MERGE_RIGHT:
+ delete_column (preview, col, FALSE);
+ break;
+ case CONTEXT_STF_IMPORT_SPLIT:
+ make_new_column (preview, col, preview->fixed_context_dx, FALSE);
+ break;
+ case CONTEXT_STF_IMPORT_WIDEN:
+ widen_column (preview, col, FALSE);
+ break;
+ case CONTEXT_STF_IMPORT_NARROW:
+ narrow_column (preview, col, FALSE);
+ break;
+ default:
+ ; /* Nothing */
+ }
+ return TRUE;
+}
+
+static void
+fixed_context_menu (GncCsvPreview *preview, GdkEventButton *event,
+ int col, int dx)
+{
+ int sensitivity_filter = 0;
+
+ preview->fixed_context_col = col;
+ preview->fixed_context_dx = dx;
+
+ if (!delete_column (preview, col - 1, TRUE))
+ sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_LEFT);
+ if (!delete_column (preview, col, TRUE))
+ sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_RIGHT);
+ if (!make_new_column (preview, col, dx, TRUE))
+ sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_SPLIT);
+ if (!widen_column (preview, col, TRUE))
+ sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_WIDEN);
+ if (!narrow_column (preview, col, TRUE))
+ sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_NARROW);
+
+ select_column (preview, col);
+ gnumeric_create_popup_menu (popup_elements, &fixed_context_menu_handler,
+ preview, 0,
+ sensitivity_filter, event);
+}
+
+/* ---- End of Gnumeric Code ---- */
+
+/** Event handler for clicking on column headers. This function is
+ * called whenever the user clicks on column headers in
+ * preview->treeview to modify columns when in fixed-width mode.
+ * @param button The button at the top of a column of the treeview
+ * @param event The event that happened (where the user clicked)
+ * @param preview The data being configured
+ */
+static void header_button_press_handler(GtkWidget* button, GdkEventButton* event,
+ GncCsvPreview* preview)
+{
+ /* col is the number of the column that was clicked, and offset is
+ to correct for the indentation of button. */
+ int i, col = 0, offset = GTK_BIN(button)->child->allocation.x - button->allocation.x,
+ ncols = preview->parse_data->column_types->len;
+ /* Find the column that was clicked. */
+ for(i = 0; i < ncols; i++)
+ {
+ if(preview->treeview_buttons[i] == button)
+ {
+ col = i;
+ break;
+ }
+ }
+
+ /* Don't let the user affect the last column if it has error messages. */
+ if(preview->parse_data->orig_max_row < ncols && ncols - col == 1)
+ {
+ return;
+ }
+
+ /* Double clicks can split columns. */
+ if(event->type == GDK_2BUTTON_PRESS && event->button == 1)
+ {
+ make_new_column(preview, col, (int)event->x - offset, FALSE);
+ }
+ /* Right clicking brings up a context menu. */
+ else if(event->type == GDK_BUTTON_PRESS && event->button == 3)
+ {
+ fixed_context_menu(preview, event, col, (int)event->x - offset);
+ }
+}
+
+/* Loads the preview's data into its data treeview. notEmpty is TRUE
+ * when the data treeview already contains data, FALSE otherwise
+ * (e.g. the first time this function is called on a preview).
+ * @param preview The data being previewed
+ * @param notEmpty Whether this function has been called before or not
+ */
+static void gnc_csv_preview_update(GncCsvPreview* preview)
+{
+ /* store has the data from the file being imported. cstores is an
+ * array of stores that hold the combo box entries for each
+ * column. ctstore contains both pointers to models in cstore and
+ * the actual text that appears in preview->ctreeview. */
+ GtkListStore *store, **cstores, *ctstore;
+ GtkTreeIter iter;
+ /* ncols is the number of columns in the file data. */
+ int i, j, ncols = preview->parse_data->column_types->len,
+ max_str_len = preview->parse_data->file_str.end - preview->parse_data->file_str.begin;
+
+ /* store contains only strings. */
+ GType* types = g_new(GType, 2 * ncols);
+ for(i = 0; i < ncols; i++)
+ types[i] = G_TYPE_STRING;
+ store = gtk_list_store_newv(ncols, types);
+
+ /* ctstore is arranged as follows:
+ * model 0, text 0, model 1, text 1, ..., model ncols, text ncols. */
+ for(i = 0; i < 2*ncols; i += 2)
+ {
+ types[i] = GTK_TYPE_TREE_MODEL;
+ types[i+1] = G_TYPE_STRING;
+ }
+ ctstore = gtk_list_store_newv(2*ncols, types);
+
+ g_free(types);
+
+ /* Each element in cstores is a single column model. */
+ cstores = g_new(GtkListStore*, ncols);
+ for(i = 0; i < ncols; i++)
+ {
+ cstores[i] = gtk_list_store_new(1, G_TYPE_STRING);
+ /* Add all of the possible entries to the combo box. */
+ for(j = 0; j < GNC_CSV_NUM_COL_TYPES; j++)
+ {
+ gtk_list_store_append(cstores[i], &iter);
+ gtk_list_store_set(cstores[i], &iter, 0, _(gnc_csv_column_type_strs[j]), -1);
+ }
+ }
+
+ if(preview->not_empty)
+ {
+ GList *children, *children_begin;
+ GList *tv_columns, *tv_columns_begin, *ctv_columns, *ctv_columns_begin;
+ tv_columns = tv_columns_begin = gtk_tree_view_get_columns(preview->treeview);
+ ctv_columns = ctv_columns_begin = gtk_tree_view_get_columns(preview->ctreeview);
+ /* Clear out exisiting columns in preview->treeview. */
+ while(tv_columns != NULL)
+ {
+ gtk_tree_view_remove_column(preview->treeview, GTK_TREE_VIEW_COLUMN(tv_columns->data));
+ tv_columns = g_list_next(tv_columns);
+ }
+ /* Do the same in preview->ctreeview. */
+ while(ctv_columns != NULL)
+ {
+ gtk_tree_view_remove_column(preview->ctreeview, GTK_TREE_VIEW_COLUMN(ctv_columns->data));
+ ctv_columns = g_list_next(ctv_columns);
+ }
+ g_list_free(tv_columns_begin);
+ g_list_free(ctv_columns_begin);
+ g_free(preview->treeview_buttons);
+ }
+
+ /* Fill the data treeview with data from the file. */
+ /* Also, update the longest line value within the following loop (whichever is executed). */
+ preview->longest_line = 0;
+ if(preview->previewing_errors) /* If we are showing only errors ... */
+ {
+ /* ... only pick rows that are in preview->error_lines. */
+ GList* error_lines = preview->parse_data->error_lines;
+ while(error_lines != NULL)
+ {
+ int this_line_length = 0;
+ i = GPOINTER_TO_INT(error_lines->data);
+ gtk_list_store_append(store, &iter);
+ for(j = 0; j < ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->len; j++)
+ {
+ /* Add this cell's length to the row's length and set the value of the list store. */
+ gchar* cell_string = (gchar*)((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->pdata[j];
+ this_line_length += g_utf8_strlen(cell_string, max_str_len);
+ gtk_list_store_set(store, &iter, j, cell_string, -1);
+ }
+
+ if(this_line_length > preview->longest_line)
+ preview->longest_line = this_line_length;
+
+ error_lines = g_list_next(error_lines);
+ }
+ }
+ else /* Otherwise, put in all of the data. */
+ {
+ for(i = 0; i < preview->parse_data->orig_lines->len; i++)
+ {
+ int this_line_length = 0;
+ gtk_list_store_append(store, &iter);
+ for(j = 0; j < ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->len; j++)
+ {
+ /* Add this cell's length to the row's length and set the value of the list store. */
+ gchar* cell_string = (gchar*)((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->pdata[j];
+ this_line_length += g_utf8_strlen(cell_string, max_str_len);
+ gtk_list_store_set(store, &iter, j, cell_string, -1);
+ }
+
+ if(this_line_length > preview->longest_line)
+ preview->longest_line = this_line_length;
+ }
+ }
+
+ /* Set all the column types to what's in the parse data. */
+ gtk_list_store_append(ctstore, &iter);
+ for(i = 0; i < ncols; i++)
+ {
+ gtk_list_store_set(ctstore, &iter, 2*i, cstores[i], 2*i+1,
+ _(gnc_csv_column_type_strs[(int)(preview->parse_data->column_types->data[i])]),
+ -1);
+ }
+
+ preview->treeview_buttons = g_new(GtkWidget*, ncols);
+ /* Insert columns into the data and column type treeviews. */
+ for(i = 0; i < ncols; i++)
+ {
+ GtkTreeViewColumn* col; /* The column we add to preview->treeview. */
+ /* Create renderers for the data treeview (renderer) and the
+ * column type treeview (crenderer). */
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new(),
+ *crenderer = gtk_cell_renderer_combo_new();
+ /* We want a monospace font for the data in case of fixed-width data. */
+ g_object_set(G_OBJECT(renderer), "family", "monospace", NULL);
+ /* We are using cstores for the combo box entries, and we don't
+ * want the user to be able to manually enter their own column
+ * types. */
+ g_object_set(G_OBJECT(crenderer), "model", cstores[i], "text-column", 0,
+ "editable", TRUE, "has-entry", FALSE, NULL);
+ g_signal_connect(G_OBJECT(crenderer), "edited",
+ G_CALLBACK(column_type_edited), (gpointer)preview);
+
+ /* Add a single column for the treeview. */
+ col = gtk_tree_view_column_new_with_attributes("", renderer, "text", i, NULL);
+ gtk_tree_view_insert_column(preview->treeview, col, -1);
+ /* Use the alternating model and text entries from ctstore in
+ * preview->ctreeview. */
+ gtk_tree_view_insert_column_with_attributes(preview->ctreeview,
+ -1, "", crenderer, "model", 2*i,
+ "text", 2*i+1, NULL);
+
+ /* We need to allow clicking on the column headers for fixed-width
+ * column splitting and merging. */
+ g_object_set(G_OBJECT(col), "clickable", TRUE, NULL);
+ g_signal_connect(G_OBJECT(col->button), "button_press_event",
+ G_CALLBACK(header_button_press_handler), (gpointer)preview);
+ preview->treeview_buttons[i] = col->button;
+ }
+
+ /* Set the treeviews to use the models. */
+ gtk_tree_view_set_model(preview->treeview, GTK_TREE_MODEL(store));
+ gtk_tree_view_set_model(preview->ctreeview, GTK_TREE_MODEL(ctstore));
+
+ /* Free the memory for the stores. */
+ g_object_unref(GTK_TREE_MODEL(store));
+ g_object_unref(GTK_TREE_MODEL(ctstore));
+ for(i = 0; i < ncols; i++)
+ g_object_unref(GTK_TREE_MODEL(cstores[i]));
+
+ /* Make the things actually appear. */
+ gtk_widget_show_all(GTK_WIDGET(preview->treeview));
+ gtk_widget_show_all(GTK_WIDGET(preview->ctreeview));
+
+ /* Set the encoding selector to the right encoding. */
+ preview->code_encoding_calls = 2;
+ go_charmap_sel_set_encoding(preview->encselector, preview->parse_data->encoding);
+
+ /* Set the date format to what's in the combo box (since we don't
+ * necessarily know if this will always be the same). */
+ preview->parse_data->date_format = gtk_combo_box_get_active(preview->date_format_combo);
+
+ /* It's now been filled with some stuff. */
+ preview->not_empty = TRUE;
+}
+
+/** A function that lets the user preview a file's data. This function
+ * is used to let the user preview and configure the data parsed from
+ * the file. It doesn't return until the user clicks "OK" or "Cancel"
+ * on the dialog.
+ * @param preview The GUI for previewing the data
+ * @param parse_data The data we want to preview
+ * @return 0 if the user approved the import; 1 if the user didn't.
+ */
+static int gnc_csv_preview(GncCsvPreview* preview, GncCsvParseData* parse_data)
+{
+ /* Set the preview's parse_data to the one we're getting passed. */
+ preview->parse_data = parse_data;
+ preview->previewing_errors = FALSE; /* We're looking at all the data. */
+ preview->approved = FALSE; /* This is FALSE until the user clicks "OK". */
+
+ /* Load the data into the treeview. (This is the first time we've
+ * called gnc_csv_preview_update on this preview, so we use
+ * FALSE.) */
+ gnc_csv_preview_update(preview);
+ /* Wait until the user clicks "OK" or "Cancel". */
+ gtk_dialog_run(GTK_DIALOG(preview->dialog));
+
+ if(preview->approved)
+ return 0;
+ else
+ return 1;
+}
+
+/** A function that lets the user preview rows with errors. This
+ * function must only be called after calling gnc_csv_preview. It is
+ * essentially identical in behavior to gnc_csv_preview except that it
+ * displays lines with errors instead of all of the data.
+ * @param preview The GUI for previewing the data (and the data being previewed)
+ * @return 0 if the user approved of importing the lines; 1 if the user didn't.
+ */
+/* TODO Let the user manually edit cells' data? */
+static int gnc_csv_preview_errors(GncCsvPreview* preview)
+{
+ GtkLabel* instructions_label = GTK_LABEL(glade_xml_get_widget(preview->xml, "instructions_label"));
+ GtkImage* instructions_image = GTK_IMAGE(glade_xml_get_widget(preview->xml, "instructions_image"));
+ gchar* name;
+ GtkIconSize size;
+ GtkTreeViewColumn* last_col;
+
+ gtk_image_get_stock(instructions_image, &name, &size);
+ gtk_image_set_from_stock(instructions_image, GTK_STOCK_DIALOG_ERROR, size);
+ gtk_label_set_text(instructions_label,
+ _("The rows displayed below had errors. You can attempt to correct these errors by changing the configuration."));
+ gtk_widget_show(GTK_WIDGET(instructions_image));
+ gtk_widget_show(GTK_WIDGET(instructions_label));
+
+ preview->previewing_errors = TRUE;
+ preview->approved = FALSE; /* This is FALSE until the user clicks "OK". */
+
+ /* Wait until the user clicks "OK" or "Cancel". */
+ gnc_csv_preview_update(preview);
+
+ /* Set the last column to have the header "Errors" so that the user
+ * doesn't find the extra column confusing. */
+ last_col = gtk_tree_view_get_column(preview->treeview,
+ preview->parse_data->column_types->len - 1);
+ gtk_tree_view_column_set_title(last_col, _("Errors"));
+
+ gtk_dialog_run(GTK_DIALOG(preview->dialog));
+
+ if(preview->approved)
+ return 0;
+ else
+ return 1;
+}
+
+/** Lets the user import a CSV/Fixed-Width file. */
+void gnc_file_csv_import(void)
+{
+ /* The name of the file the user selected. */
+ char* selected_filename;
+ /* The default directory for the user to select files. */
+ char* default_dir= gnc_get_default_directory(GCONF_SECTION);
+ /* The generic GUI for importing transactions. */
+ GNCImportMainMatcher* gnc_csv_importer_gui = NULL;
+
+ /* Let the user select a file. */
+ selected_filename = gnc_file_dialog(_("Select an CSV/Fixed-Width file to import"),
+ NULL, default_dir, GNC_FILE_DIALOG_IMPORT);
+ g_free(default_dir); /* We don't need default_dir anymore. */
+
+ /* If the user actually selected a file ... */
+ if(selected_filename!=NULL)
+ {
+ int i, user_canceled = 0;
+ Account* account; /* The account the user will select */
+ GError* error = NULL;
+ GList* transactions; /* A list of the transactions we create */
+ GncCsvParseData* parse_data;
+ GncCsvPreview* preview;
+
+ /* Remember the directory of the selected file as the default. */
+ default_dir = g_path_get_dirname(selected_filename);
+ gnc_set_default_directory(GCONF_SECTION, default_dir);
+ g_free(default_dir);
+
+ /* Load the file into parse_data. */
+ parse_data = gnc_csv_new_parse_data();
+ if(gnc_csv_load_file(parse_data, selected_filename, &error))
+ {
+ /* If we couldn't load the file ... */
+ gnc_error_dialog(NULL, "%s", error->message);
+ if(error->code == GNC_CSV_FILE_OPEN_ERR)
+ {
+ gnc_csv_parse_data_free(parse_data);
+ g_free(selected_filename);
+ return;
+ }
+ /* If we couldn't guess the encoding, we are content with just
+ * displaying an error message and move on with a blank
+ * display. */
+ }
+ /* Parse the data. */
+ if(gnc_csv_parse(parse_data, TRUE, &error))
+ {
+ /* If we couldn't parse the data ... */
+ gnc_error_dialog(NULL, "%s", error->message);
+ }
+
+ /* Preview the data. */
+ preview = gnc_csv_preview_new();
+ if(gnc_csv_preview(preview, parse_data))
+ {
+ /* If the user clicked "Cancel", free everything and quit. */
+ gnc_csv_preview_free(preview);
+ gnc_csv_parse_data_free(parse_data);
+ g_free(selected_filename);
+ return;
+ }
+
+ /* Let the user select an account to put the transactions in. */
+ account = gnc_import_select_account(NULL, NULL, 1, NULL, NULL, 0, NULL, NULL);
+ if(account == NULL) /* Quit if the user canceled. */
+ {
+ gnc_csv_preview_free(preview);
+ gnc_csv_parse_data_free(parse_data);
+ g_free(selected_filename);
+ return;
+ }
+
+ /* Create transactions from the parsed data. */
+ gnc_csv_parse_to_trans(parse_data, account, FALSE);
+
+ /* If there are errors, let the user try and eliminate them by
+ * previewing them. Repeat until either there are no errors or the
+ * user gives up. */
+ while(!((parse_data->error_lines == NULL) || user_canceled))
+ {
+ user_canceled = gnc_csv_preview_errors(preview);
+ gnc_csv_parse_to_trans(parse_data, account, TRUE);
+ }
+
+ /* Create the genereic transaction importer GUI. */
+ gnc_csv_importer_gui = gnc_gen_trans_list_new(NULL, NULL, FALSE, 42);
+
+ /* Get the list of the transactions that were created. */
+ transactions = parse_data->transactions;
+ /* Copy all of the transactions to the importer GUI. */
+ while(transactions != NULL)
+ {
+ GncCsvTransLine* trans_line = transactions->data;
+ gnc_gen_trans_list_add_trans(gnc_csv_importer_gui,
+ trans_line->trans);
+ transactions = g_list_next(transactions);
+ }
+ /* Let the user load those transactions into the account, so long
+ * as there is at least one transaction to be loaded. */
+ if(parse_data->transactions != NULL)
+ gnc_gen_trans_list_run(gnc_csv_importer_gui);
+ else
+ gnc_gen_trans_list_delete(gnc_csv_importer_gui);
+
+ /* Free the memory we allocated. */
+ gnc_csv_preview_free(preview);
+ gnc_csv_parse_data_free(parse_data);
+ g_free(selected_filename);
+ }
+}
Added: gnucash/trunk/src/import-export/csv/gnc-csv-import.h
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-import.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-import.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,33 @@
+/********************************************************************\
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+\********************************************************************/
+ /** @file
+ @brief CSV import GUI
+ *
+ gnc-csv-import.h
+ @author Copyright (c) 2007 Benny Sperisen <lasindi at gmail.com>
+ */
+#ifndef CSV_IMPORT_H
+#define CSV_IMPORT_H
+
+/** The gnc_file_csv_import() will let the user select a
+ * CSV/Fixed-Width file to open, select an account to import it to,
+ * and import the transactions into the account. It also allows the
+ * user to configure how the file is parsed. */
+void gnc_file_csv_import (void);
+#endif
Added: gnucash/trunk/src/import-export/csv/gnc-csv-model.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-model.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-model.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,1199 @@
+#include "gnc-csv-model.h"
+
+#include "gnc-book.h"
+
+#include <glib/gi18n.h>
+#include <goffice/utils/go-glib-extras.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <regex.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <time.h>
+
+static QofLogModule log_module = GNC_MOD_IMPORT;
+
+const int num_date_formats = 5;
+
+const gchar* date_format_user[] = {N_("y-m-d"),
+ N_("d-m-y"),
+ N_("m-d-y"),
+ N_("d-m"),
+ N_("m-d")};
+
+/* This array contains all of the different strings for different column types. */
+gchar* gnc_csv_column_type_strs[GNC_CSV_NUM_COL_TYPES] = {N_("None"),
+ N_("Date"),
+ N_("Description"),
+ N_("Balance"),
+ N_("Deposit"),
+ N_("Withdrawal"),
+ N_("Num")};
+
+/** A set of sensible defaults for parsing CSV files.
+ * @return StfParseOptions_t* for parsing a file with comma separators
+ */
+static StfParseOptions_t* default_parse_options(void)
+{
+ StfParseOptions_t* options = stf_parse_options_new();
+ stf_parse_options_set_type(options, PARSE_TYPE_CSV);
+ stf_parse_options_csv_set_separators(options, ",", NULL);
+ return options;
+}
+
+/** Parses a string into a date, given a format. The format must
+ * include the year. This function should only be called by
+ * parse_date.
+ * @param date_str The string containing a date being parsed
+ * @param format An index specifying a format in date_format_user
+ * @return The parsed value of date_str on success or -1 on failure
+ */
+static time_t parse_date_with_year(const char* date_str, int format)
+{
+ time_t rawtime; /* The integer time */
+ struct tm retvalue, test_retvalue; /* The time in a broken-down structure */
+
+ int i, j, mem_length, orig_year = -1, orig_month = -1, orig_day = -1;
+
+ /* Buffer for containing individual parts (e.g. year, month, day) of a date */
+ char date_segment[5];
+
+ /* The compiled regular expression */
+ regex_t preg = {0};
+
+ /* An array containing indices specifying the matched substrings in date_str */
+ regmatch_t pmatch[4] = { {0}, {0}, {0}, {0} };
+
+ /* The regular expression for parsing dates */
+ const char* regex = "^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$";
+
+ /* We get our matches using the regular expression. */
+ regcomp(&preg, regex, REG_EXTENDED);
+ regexec(&preg, date_str, 4, pmatch, 0);
+ regfree(&preg);
+
+ /* If there wasn't a match, there was an error. */
+ if(pmatch[0].rm_eo == 0)
+ return -1;
+
+ /* If this is a string without separators ... */
+ if(pmatch[1].rm_so == -1)
+ {
+ /* ... we will fill in the indices based on the user's selection. */
+ int k = 0; /* k traverses date_str by keeping track of where separators "should" be. */
+ j = 1; /* j traverses pmatch. */
+ for(i = 0; date_format_user[format][i]; i++)
+ {
+ char segment_type = date_format_user[format][i];
+ /* Only do something if this is a meaningful character */
+ if(segment_type == 'y' || segment_type == 'm' || segment_type == 'd')
+ {
+ pmatch[j].rm_so = k;
+ switch(segment_type)
+ {
+ case 'm':
+ case 'd':
+ k += 2;
+ break;
+
+ case 'y':
+ k += 4;
+ break;
+ }
+
+ pmatch[j].rm_eo = k;
+ j++;
+ }
+ }
+ }
+
+ /* Put some sane values in retvalue by using the current time for
+ * the non-year-month-day parts of the date. */
+ time(&rawtime);
+ localtime_r(&rawtime, &retvalue);
+
+ /* j traverses pmatch (index 0 contains the entire string, so we
+ * start at index 1 for the first meaningful match). */
+ j = 1;
+ /* Go through the date format and interpret the matches in order of
+ * the sections in the date format. */
+ for(i = 0; date_format_user[format][i]; i++)
+ {
+ char segment_type = date_format_user[format][i];
+ /* Only do something if this is a meaningful character */
+ if(segment_type == 'y' || segment_type == 'm' || segment_type == 'd')
+ {
+ /* Copy the matching substring into date_segment so that we can
+ * convert it into an integer. */
+ mem_length = pmatch[j].rm_eo - pmatch[j].rm_so;
+ memcpy(date_segment, date_str + pmatch[j].rm_so, mem_length);
+ date_segment[mem_length] = '\0';
+
+ /* Set the appropriate member of retvalue. Save the original
+ * values so that we can check if the change when we use mktime
+ * below. */
+ switch(segment_type)
+ {
+ case 'y':
+ retvalue.tm_year = atoi(date_segment);
+
+ /* Handle two-digit years. */
+ if(retvalue.tm_year < 100)
+ {
+ /* We allow two-digit years in the range 1969 - 2068. */
+ if(retvalue.tm_year < 69)
+ retvalue.tm_year += 100;
+ }
+ else
+ retvalue.tm_year -= 1900;
+ orig_year = retvalue.tm_year;
+ break;
+
+ case 'm':
+ orig_month = retvalue.tm_mon = atoi(date_segment) - 1;
+ break;
+
+ case 'd':
+ orig_day = retvalue.tm_mday = atoi(date_segment);
+ break;
+ }
+ j++;
+ }
+ }
+ /* Convert back to an integer. If mktime leaves retvalue unchanged,
+ * everything is okay; otherwise, an error has occurred. */
+ /* We have to use a "test" date value to account for changes in
+ * daylight savings time, which can cause a date change with mktime
+ * near midnight, causing the code to incorrectly think a date is
+ * incorrect. */
+ test_retvalue = retvalue;
+ mktime(&test_retvalue);
+ retvalue.tm_isdst = test_retvalue.tm_isdst;
+ rawtime = mktime(&retvalue);
+ if(retvalue.tm_mday == orig_day &&
+ retvalue.tm_mon == orig_month &&
+ retvalue.tm_year == orig_year)
+ {
+ return rawtime;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+/** Parses a string into a date, given a format. The format cannot
+ * include the year. This function should only be called by
+ * parse_date.
+ * @param date_str The string containing a date being parsed
+ * @param format An index specifying a format in date_format_user
+ * @return The parsed value of date_str on success or -1 on failure
+ */
+static time_t parse_date_without_year(const char* date_str, int format)
+{
+ time_t rawtime; /* The integer time */
+ struct tm retvalue, test_retvalue; /* The time in a broken-down structure */
+
+ int i, j, mem_length, orig_year = -1, orig_month = -1, orig_day = -1;
+
+ /* Buffer for containing individual parts (e.g. year, month, day) of a date */
+ gchar* date_segment;
+
+ /* The compiled regular expression */
+ regex_t preg = {0};
+
+ /* An array containing indices specifying the matched substrings in date_str */
+ regmatch_t pmatch[3] = { {0}, {0}, {0} };
+
+ /* The regular expression for parsing dates */
+ const char* regex = "^ *([0-9]+) *[-/.'] *([0-9]+).*$";
+
+ /* We get our matches using the regular expression. */
+ regcomp(&preg, regex, REG_EXTENDED);
+ regexec(&preg, date_str, 3, pmatch, 0);
+ regfree(&preg);
+
+ /* If there wasn't a match, there was an error. */
+ if(pmatch[0].rm_eo == 0)
+ return -1;
+
+ /* Put some sane values in retvalue by using the current time for
+ * the non-year-month-day parts of the date. */
+ time(&rawtime);
+ localtime_r(&rawtime, &retvalue);
+ orig_year = retvalue.tm_year;
+
+ /* j traverses pmatch (index 0 contains the entire string, so we
+ * start at index 1 for the first meaningful match). */
+ j = 1;
+ /* Go through the date format and interpret the matches in order of
+ * the sections in the date format. */
+ for(i = 0; date_format_user[format][i]; i++)
+ {
+ char segment_type = date_format_user[format][i];
+ /* Only do something if this is a meaningful character */
+ if(segment_type == 'm' || segment_type == 'd')
+ {
+ /* Copy the matching substring into date_segment so that we can
+ * convert it into an integer. */
+ mem_length = pmatch[j].rm_eo - pmatch[j].rm_so;
+ date_segment = g_new(gchar, mem_length);
+ memcpy(date_segment, date_str + pmatch[j].rm_so, mem_length);
+ date_segment[mem_length] = '\0';
+
+ /* Set the appropriate member of retvalue. Save the original
+ * values so that we can check if the change when we use mktime
+ * below. */
+ switch(segment_type)
+ {
+ case 'm':
+ orig_month = retvalue.tm_mon = atoi(date_segment) - 1;
+ break;
+
+ case 'd':
+ orig_day = retvalue.tm_mday = atoi(date_segment);
+ break;
+ }
+ g_free(date_segment);
+ j++;
+ }
+ }
+ /* Convert back to an integer. If mktime leaves retvalue unchanged,
+ * everything is okay; otherwise, an error has occurred. */
+ /* We have to use a "test" date value to account for changes in
+ * daylight savings time, which can cause a date change with mktime
+ * near midnight, causing the code to incorrectly think a date is
+ * incorrect. */
+ test_retvalue = retvalue;
+ mktime(&test_retvalue);
+ retvalue.tm_isdst = test_retvalue.tm_isdst;
+ rawtime = mktime(&retvalue);
+ if(retvalue.tm_mday == orig_day &&
+ retvalue.tm_mon == orig_month &&
+ retvalue.tm_year == orig_year)
+ {
+ return rawtime;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+/** Parses a string into a date, given a format. This function
+ * requires only knowing the order in which the year, month and day
+ * appear. For example, 01-02-2003 will be parsed the same way as
+ * 01/02/2003.
+ * @param date_str The string containing a date being parsed
+ * @param format An index specifying a format in date_format_user
+ * @return The parsed value of date_str on success or -1 on failure
+ */
+static time_t parse_date(const char* date_str, int format)
+{
+ if(strchr(date_format_user[format], 'y'))
+ return parse_date_with_year(date_str, format);
+ else
+ return parse_date_without_year(date_str, format);
+}
+
+/** Constructor for GncCsvParseData.
+ * @return Pointer to a new GncCSvParseData
+ */
+GncCsvParseData* gnc_csv_new_parse_data(void)
+{
+ GncCsvParseData* parse_data = g_new(GncCsvParseData, 1);
+ parse_data->encoding = "UTF-8";
+ /* All of the data pointers are initially NULL. This is so that, if
+ * gnc_csv_parse_data_free is called before all of the data is
+ * initialized, only the data that needs to be freed is freed. */
+ parse_data->raw_str.begin = parse_data->raw_str.end
+ = parse_data->file_str.begin = parse_data->file_str.end = NULL;
+ parse_data->orig_lines = NULL;
+ parse_data->orig_row_lengths = NULL;
+ parse_data->column_types = NULL;
+ parse_data->error_lines = parse_data->transactions = NULL;
+ parse_data->options = default_parse_options();
+ parse_data->date_format = -1;
+ parse_data->chunk = g_string_chunk_new(100 * 1024);
+ return parse_data;
+}
+
+/** Destructor for GncCsvParseData.
+ * @param parse_data Parse data whose memory will be freed
+ */
+void gnc_csv_parse_data_free(GncCsvParseData* parse_data)
+{
+ /* All non-NULL pointers have been initialized and must be freed. */
+
+ if(parse_data->raw_mapping != NULL)
+ g_mapped_file_free(parse_data->raw_mapping);
+
+ if(parse_data->file_str.begin != NULL)
+ g_free(parse_data->file_str.begin);
+
+ if(parse_data->orig_lines != NULL)
+ stf_parse_general_free(parse_data->orig_lines);
+
+ if(parse_data->orig_row_lengths != NULL)
+ g_array_free(parse_data->orig_row_lengths, FALSE);
+
+ if(parse_data->options != NULL)
+ stf_parse_options_free(parse_data->options);
+
+ if(parse_data->column_types != NULL)
+ g_array_free(parse_data->column_types, TRUE);
+
+ if(parse_data->error_lines != NULL)
+ g_list_free(parse_data->error_lines);
+
+ if(parse_data->transactions != NULL)
+ {
+ GList* transactions = parse_data->transactions;
+ /* We have to free the GncCsvTransLine's that are at each node in
+ * the list before freeing the entire list. */
+ do
+ {
+ g_free(transactions->data);
+ transactions = g_list_next(transactions);
+ } while(transactions != NULL);
+ g_list_free(parse_data->transactions);
+ }
+
+ g_free(parse_data->chunk);
+ g_free(parse_data);
+}
+
+/** Converts raw file data using a new encoding. This function must be
+ * called after gnc_csv_load_file only if gnc_csv_load_file guessed
+ * the wrong encoding.
+ * @param parse_data Data that is being parsed
+ * @param encoding Encoding that data should be translated using
+ * @param error Will point to an error on failure
+ * @return 0 on success, 1 on failure
+ */
+int gnc_csv_convert_encoding(GncCsvParseData* parse_data, const char* encoding,
+ GError** error)
+{
+ gsize bytes_read, bytes_written;
+
+ /* If parse_data->file_str has already been initialized it must be
+ * freed first. (This should always be the case, since
+ * gnc_csv_load_file should always be called before this
+ * function.) */
+ if(parse_data->file_str.begin != NULL)
+ g_free(parse_data->file_str.begin);
+
+ /* Do the actual translation to UTF-8. */
+ parse_data->file_str.begin = g_convert(parse_data->raw_str.begin,
+ parse_data->raw_str.end - parse_data->raw_str.begin,
+ "UTF-8", encoding, &bytes_read, &bytes_written,
+ error);
+ /* Handle errors that occur. */
+ if(parse_data->file_str.begin == NULL)
+ return 1;
+
+ /* On success, save the ending pointer of the translated data and
+ * the encoding type and return 0. */
+ parse_data->file_str.end = parse_data->file_str.begin + bytes_written;
+ parse_data->encoding = (gchar*)encoding;
+ return 0;
+}
+
+/** Loads a file into a GncCsvParseData. This is the first function
+ * that must be called after createing a new GncCsvParseData. If this
+ * fails because the file couldn't be opened, no more functions can be
+ * called on the parse data until this succeeds (or until it fails
+ * because of an encoding guess error). If it fails because the
+ * encoding could not be guessed, gnc_csv_convert_encoding must be
+ * called until it succeeds.
+ * @param parse_data Data that is being parsed
+ * @param filename Name of the file that should be opened
+ * @param error Will contain an error if there is a failure
+ * @return 0 on success, 1 on failure
+ */
+int gnc_csv_load_file(GncCsvParseData* parse_data, const char* filename,
+ GError** error)
+{
+ const char* guess_enc;
+
+ /* Get the raw data first and handle an error if one occurs. */
+ parse_data->raw_mapping = g_mapped_file_new(filename, FALSE, error);
+ if(parse_data->raw_mapping == NULL)
+ {
+ /* TODO Handle file opening errors more specifically,
+ * e.g. inexistent file versus no read permission. */
+ parse_data->raw_str.begin = NULL;
+ g_set_error(error, 0, GNC_CSV_FILE_OPEN_ERR, _("File opening failed."));
+ return 1;
+ }
+
+ /* Copy the mapping's contents into parse-data->raw_str. */
+ parse_data->raw_str.begin = g_mapped_file_get_contents(parse_data->raw_mapping);
+ parse_data->raw_str.end = parse_data->raw_str.begin + g_mapped_file_get_length(parse_data->raw_mapping);
+
+ /* Make a guess at the encoding of the data. */
+ guess_enc = go_guess_encoding((const char*)(parse_data->raw_str.begin),
+ (size_t)(parse_data->raw_str.end - parse_data->raw_str.begin),
+ "UTF-8", NULL);
+ if(guess_enc == NULL)
+ {
+ g_set_error(error, 0, GNC_CSV_ENCODING_ERR, _("Unknown encoding."));
+ return 1;
+ }
+
+ /* Convert using the guessed encoding into parse_data->file_str and
+ * handle any errors that occur. */
+ gnc_csv_convert_encoding(parse_data, guess_enc, error);
+ if(parse_data->file_str.begin == NULL)
+ {
+ g_set_error(error, 0, GNC_CSV_ENCODING_ERR, _("Unknown encoding."));
+ return 1;
+ }
+ else
+ return 0;
+}
+
+/** Parses a file into cells. This requires having an encoding that
+ * works (see gnc_csv_convert_encoding). parse_data->options should be
+ * set according to how the user wants before calling this
+ * function. (Note: this function must be called with guessColTypes as
+ * TRUE before it is ever called with it as FALSE.) (Note: if
+ * guessColTypes is TRUE, all the column types will be GNC_CSV_NONE
+ * right now.)
+ * @param parse_data Data that is being parsed
+ * @param guessColTypes TRUE to guess what the types of columns are based on the cell contents
+ * @error error Will contain an error if there is a failure
+ * @return 0 on success, 1 on failure
+ */
+int gnc_csv_parse(GncCsvParseData* parse_data, gboolean guessColTypes, GError** error)
+{
+ /* max_cols is the number of columns in the row with the most columns. */
+ int i, max_cols = 0;
+
+ if(parse_data->orig_lines != NULL)
+ {
+ stf_parse_general_free(parse_data->orig_lines);
+ }
+
+ /* If everything is fine ... */
+ if(parse_data->file_str.begin != NULL)
+ {
+ /* Do the actual parsing. */
+ parse_data->orig_lines = stf_parse_general(parse_data->options, parse_data->chunk,
+ parse_data->file_str.begin,
+ parse_data->file_str.end);
+ }
+ /* If we couldn't get the encoding right, we just want an empty array. */
+ else
+ {
+ parse_data->orig_lines = g_ptr_array_new();
+ }
+
+ /* Record the original row lengths of parse_data->orig_lines. */
+ if(parse_data->orig_row_lengths != NULL)
+ g_array_free(parse_data->orig_row_lengths, FALSE);
+
+ parse_data->orig_row_lengths =
+ g_array_sized_new(FALSE, FALSE, sizeof(int), parse_data->orig_lines->len);
+ g_array_set_size(parse_data->orig_row_lengths, parse_data->orig_lines->len);
+ parse_data->orig_max_row = 0;
+ for(i = 0; i < parse_data->orig_lines->len; i++)
+ {
+ int length = ((GPtrArray*)parse_data->orig_lines->pdata[i])->len;
+ parse_data->orig_row_lengths->data[i] = length;
+ if(length > parse_data->orig_max_row)
+ parse_data->orig_max_row = length;
+ }
+
+ /* If it failed, generate an error. */
+ if(parse_data->orig_lines == NULL)
+ {
+ g_set_error(error, 0, 0, "Parsing failed.");
+ return 1;
+ }
+
+ /* Now that we have data, let's set max_cols. */
+ for(i = 0; i < parse_data->orig_lines->len; i++)
+ {
+ if(max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len)
+ max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len;
+ }
+
+ if(guessColTypes)
+ {
+ /* Free parse_data->column_types if it's already been created. */
+ if(parse_data->column_types != NULL)
+ g_array_free(parse_data->column_types, TRUE);
+
+ /* Create parse_data->column_types and fill it with guesses based
+ * on the contents of each column. */
+ parse_data->column_types = g_array_sized_new(FALSE, FALSE, sizeof(int),
+ max_cols);
+ g_array_set_size(parse_data->column_types, max_cols);
+ /* TODO Make it actually guess. */
+ for(i = 0; i < parse_data->column_types->len; i++)
+ {
+ parse_data->column_types->data[i] = GNC_CSV_NONE;
+ }
+ }
+ else
+ {
+ /* If we don't need to guess column types, we will simply set any
+ * new columns that are created that didn't exist before to "None"
+ * since we don't want gibberish to appear. Note:
+ * parse_data->column_types should have already been
+ * initialized, so we don't check for it being NULL. */
+ int i = parse_data->column_types->len;
+ g_array_set_size(parse_data->column_types, max_cols);
+ for(; i < parse_data->column_types->len; i++)
+ {
+ parse_data->column_types->data[i] = GNC_CSV_NONE;
+ }
+ }
+
+ return 0;
+}
+
+/** A struct containing TransProperties that all describe a single transaction. */
+typedef struct
+{
+ int date_format; /**< The format for parsing dates */
+ Account* account; /**< The account the transaction belongs to */
+ GList* properties; /**< List of TransProperties */
+} TransPropertyList;
+
+/** A struct encapsulating a property of a transaction. */
+typedef struct
+{
+ int type; /**< A value from the GncCsvColumnType enum except
+ * GNC_CSV_NONE and GNC_CSV_NUM_COL_TYPES */
+ void* value; /**< Pointer to the data that will be used to configure a transaction */
+ TransPropertyList* list; /**< The list the property belongs to */
+} TransProperty;
+
+/** Constructor for TransProperty.
+ * @param type The type of the new property (see TransProperty.type for possible values)
+ */
+static TransProperty* trans_property_new(int type, TransPropertyList* list)
+{
+ TransProperty* prop = g_new(TransProperty, 1);
+ prop->type = type;
+ prop->list = list;
+ prop->value = NULL;
+ return prop;
+}
+
+/** Destructor for TransProperty.
+ * @param prop The property to be freed
+ */
+static void trans_property_free(TransProperty* prop)
+{
+ switch(prop->type)
+ {
+ /* The types for "Date" and "Balance" (time_t and gnc_numeric,
+ * respectively) are typically not pointed to, we have to free
+ * them, unlike types like char* ("Description"). */
+ case GNC_CSV_DATE:
+ case GNC_CSV_BALANCE:
+ case GNC_CSV_DEPOSIT:
+ case GNC_CSV_WITHDRAWAL:
+ if(prop->value != NULL)
+ g_free(prop->value);
+ break;
+ }
+ g_free(prop);
+}
+
+/** Sets the value of the property by parsing str. Note: this should
+ * only be called once on an instance of TransProperty, as calling it
+ * more than once can cause memory leaks.
+ * @param prop The property being set
+ * @param str The string to be parsed
+ * @return TRUE on success, FALSE on failure
+ */
+static gboolean trans_property_set(TransProperty* prop, char* str)
+{
+ char *endptr, *possible_currency_symbol, *str_dupe;
+ double value;
+ switch(prop->type)
+ {
+ case GNC_CSV_DATE:
+ prop->value = g_new(time_t, 1);
+ *((time_t*)(prop->value)) = parse_date(str, prop->list->date_format);
+ return *((time_t*)(prop->value)) != -1;
+
+ case GNC_CSV_DESCRIPTION:
+ case GNC_CSV_NUM:
+ prop->value = g_strdup(str);
+ return TRUE;
+
+ case GNC_CSV_BALANCE:
+ case GNC_CSV_DEPOSIT:
+ case GNC_CSV_WITHDRAWAL:
+ str_dupe = g_strdup(str); /* First, we make a copy so we can't mess up real data. */
+
+ /* Go through str_dupe looking for currency symbols. */
+ for(possible_currency_symbol = str_dupe; *possible_currency_symbol;
+ possible_currency_symbol = g_utf8_next_char(possible_currency_symbol))
+ {
+ if(g_unichar_type(g_utf8_get_char(possible_currency_symbol)) == G_UNICODE_CURRENCY_SYMBOL)
+ {
+ /* If we find a currency symbol, save the position just ahead
+ * of the currency symbol (next_symbol), and find the null
+ * terminator of the string (last_symbol). */
+ char *next_symbol = g_utf8_next_char(possible_currency_symbol), *last_symbol = next_symbol;
+ while(*last_symbol)
+ last_symbol = g_utf8_next_char(last_symbol);
+
+ /* Move all of the string (including the null byte, which is
+ * why we have +1 in the size parameter) following the
+ * currency symbol back one character, thereby overwriting the
+ * currency symbol. */
+ memmove(possible_currency_symbol, next_symbol, last_symbol - next_symbol + 1);
+ break;
+ }
+ }
+
+ /* Translate the string (now clean of currency symbols) into a number. */
+ value = strtod(str_dupe, &endptr);
+
+ /* If this isn't a valid numeric string, this is an error. */
+ if(endptr != str_dupe + strlen(str_dupe))
+ {
+ g_free(str_dupe);
+ return FALSE;
+ }
+
+ g_free(str_dupe);
+
+ if(abs(value) > 0.00001)
+ {
+ prop->value = g_new(gnc_numeric, 1);
+ *((gnc_numeric*)(prop->value)) =
+ double_to_gnc_numeric(value, xaccAccountGetCommoditySCU(prop->list->account),
+ GNC_RND_ROUND);
+ }
+ return TRUE;
+ }
+ return FALSE; /* We should never actually get here. */
+}
+
+/** Constructor for TransPropertyList.
+ * @param account The account with which transactions should be built
+ * @param date_format An index from date_format_user for how date properties should be parsed
+ * @return A pointer to a new TransPropertyList
+ */
+static TransPropertyList* trans_property_list_new(Account* account, int date_format)
+{
+ TransPropertyList* list = g_new(TransPropertyList, 1);
+ list->account = account;
+ list->date_format = date_format;
+ list->properties = NULL;
+ return list;
+}
+
+/** Destructor for TransPropertyList.
+ * @param list The list to be freed
+ */
+static void trans_property_list_free(TransPropertyList* list)
+{
+ /* Free all of the properties in this list before freeeing the list itself. */
+ GList* properties_begin = list->properties;
+ while(list->properties != NULL)
+ {
+ trans_property_free((TransProperty*)(list->properties->data));
+ list->properties = g_list_next(list->properties);
+ }
+ g_list_free(properties_begin);
+ g_free(list);
+}
+
+/** Adds a property to the list it's linked with.
+ * (The TransPropertyList is not passed as a parameter because the property is
+ * associated with a list when it's constructed.)
+ * @param property The property to be added to its list
+ */
+static void trans_property_list_add(TransProperty* property)
+{
+ property->list->properties = g_list_append(property->list->properties, property);
+}
+
+/** Adds a split to a transaction.
+ * @param trans The transaction to add a split to
+ * @param account The account used for the split
+ * @param book The book where the split should be stored
+ * @param amount The amount of the split
+ */
+static void trans_add_split(Transaction* trans, Account* account, GNCBook* book,
+ gnc_numeric amount)
+{
+ Split* split = xaccMallocSplit(book);
+ xaccSplitSetAccount(split, account);
+ xaccSplitSetParent(split, trans);
+ xaccSplitSetAmount(split, amount);
+ xaccSplitSetValue(split, amount);
+ xaccSplitSetAction(split, "Deposit");
+}
+
+/** Tests a TransPropertyList for having enough essential properties.
+ * Essential properties are "Date" and one of the following: "Balance", "Deposit", or
+ * "Withdrawal".
+ * @param list The list we are checking
+ * @param error Contains an error message on failure
+ * @return TRUE if there are enough essentials; FALSE otherwise
+ */
+static gboolean trans_property_list_verify_essentials(TransPropertyList* list, gchar** error)
+{
+ int i;
+ /* possible_errors lists the ways in which a list can fail this test. */
+ enum PossibleErrorTypes {NO_DATE, NO_AMOUNT, NUM_OF_POSSIBLE_ERRORS};
+ gchar* possible_errors[NUM_OF_POSSIBLE_ERRORS] = {N_("No date column."),
+ N_("No balance, deposit, or withdrawal column.")};
+ int possible_error_lengths[NUM_OF_POSSIBLE_ERRORS] = {0};
+ GList *properties_begin = list->properties, *errors_list = NULL;
+
+ /* Go through each of the properties and erase possible errors. */
+ while(list->properties)
+ {
+ switch(((TransProperty*)(list->properties->data))->type)
+ {
+ case GNC_CSV_DATE:
+ possible_errors[NO_DATE] = NULL;
+ break;
+
+ case GNC_CSV_BALANCE:
+ case GNC_CSV_DEPOSIT:
+ case GNC_CSV_WITHDRAWAL:
+ possible_errors[NO_AMOUNT] = NULL;
+ break;
+ }
+ list->properties = g_list_next(list->properties);
+ }
+ list->properties = properties_begin;
+
+ /* Accumulate a list of the actual errors. */
+ for(i = 0; i < NUM_OF_POSSIBLE_ERRORS; i++)
+ {
+ if(possible_errors[i] != NULL)
+ {
+ errors_list = g_list_append(errors_list, GINT_TO_POINTER(i));
+ /* Since we added an error, we want to also store its length for
+ * when we construct the full error string. */
+ possible_error_lengths[i] = strlen(_(possible_errors[i]));
+ }
+ }
+
+ /* If there are no errors, we can quit now. */
+ if(errors_list == NULL)
+ return TRUE;
+ else
+ {
+ /* full_error_size is the full length of the error message. */
+ int full_error_size = 0, string_length = 0;
+ GList* errors_list_begin = errors_list;
+ gchar *error_message, *error_message_begin;
+
+ /* Find the value for full_error_size. */
+ while(errors_list)
+ {
+ /* We add an extra 1 to account for spaces in between messages. */
+ full_error_size += possible_error_lengths[GPOINTER_TO_INT(errors_list->data)] + 1;
+ errors_list = g_list_next(errors_list);
+ }
+ errors_list = errors_list_begin;
+
+ /* Append the error messages one after another. */
+ error_message = error_message_begin = g_new(gchar, full_error_size);
+ while(errors_list)
+ {
+ i = GPOINTER_TO_INT(errors_list->data);
+ string_length = possible_error_lengths[i];
+
+ /* Copy the error message and put a space after it. */
+ strncpy(error_message, _(possible_errors[i]), string_length);
+ error_message += string_length;
+ *error_message = ' ';
+ error_message++;
+
+ errors_list = g_list_next(errors_list);
+ }
+ *error_message = '\0'; /* Replace the last space with the null byte. */
+ g_list_free(errors_list_begin);
+
+ *error = error_message_begin;
+ return FALSE;
+ }
+}
+
+/** Create a Transaction from a TransPropertyList.
+ * @param list The list of properties
+ * @param error Contains an error on failure
+ * @return On success, a GncCsvTransLine; on failure, the trans pointer is NULL
+ */
+static GncCsvTransLine* trans_property_list_to_trans(TransPropertyList* list, gchar** error)
+{
+ GncCsvTransLine* trans_line = g_new(GncCsvTransLine, 1);
+ GList* properties_begin = list->properties;
+ GNCBook* book = gnc_account_get_book(list->account);
+ gnc_commodity* currency = xaccAccountGetCommodity(list->account);
+ gnc_numeric amount = double_to_gnc_numeric(0.0, xaccAccountGetCommoditySCU(list->account),
+ GNC_RND_ROUND);
+
+ /* This flag is set to TRUE if we can use the "Deposit" or "Withdrawal" column. */
+ gboolean amount_set = FALSE;
+
+ /* The balance is 0 by default. */
+ trans_line->balance_set = FALSE;
+ trans_line->balance = amount;
+
+ /* We make the line_no -1 just to mark that it hasn't been set. We
+ * may get rid of line_no soon anyway, so it's not particularly
+ * important. */
+ trans_line->line_no = -1;
+
+ /* Make sure this is a transaction with all the columns we need. */
+ if(!trans_property_list_verify_essentials(list, error))
+ {
+ g_free(trans_line);
+ return NULL;
+ }
+
+ trans_line->trans = xaccMallocTransaction(book);
+ xaccTransBeginEdit(trans_line->trans);
+ xaccTransSetCurrency(trans_line->trans, currency);
+
+ /* Go through each of the properties and edit the transaction accordingly. */
+ list->properties = properties_begin;
+ while(list->properties != NULL)
+ {
+ TransProperty* prop = (TransProperty*)(list->properties->data);
+ switch(prop->type)
+ {
+ case GNC_CSV_DATE:
+ xaccTransSetDatePostedSecs(trans_line->trans, *((time_t*)(prop->value)));
+ break;
+
+ case GNC_CSV_DESCRIPTION:
+ xaccTransSetDescription(trans_line->trans, (char*)(prop->value));
+ break;
+
+ case GNC_CSV_NUM:
+ xaccTransSetNum(trans_line->trans, (char*)(prop->value));
+ break;
+
+ case GNC_CSV_DEPOSIT: /* Add deposits to the existing amount. */
+ if(prop->value != NULL)
+ {
+ amount = gnc_numeric_add(*((gnc_numeric*)(prop->value)),
+ amount,
+ xaccAccountGetCommoditySCU(list->account),
+ GNC_RND_ROUND);
+ amount_set = TRUE;
+ /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
+ trans_line->balance_set = FALSE;
+ }
+ break;
+
+ case GNC_CSV_WITHDRAWAL: /* Withdrawals are just negative deposits. */
+ if(prop->value != NULL)
+ {
+ amount = gnc_numeric_add(gnc_numeric_neg(*((gnc_numeric*)(prop->value))),
+ amount,
+ xaccAccountGetCommoditySCU(list->account),
+ GNC_RND_ROUND);
+ amount_set = TRUE;
+ /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
+ trans_line->balance_set = FALSE;
+ }
+ break;
+
+ case GNC_CSV_BALANCE: /* The balance gets stored in a separate field in trans_line. */
+ /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
+ if(!amount_set && prop->value != NULL)
+ {
+ /* This gets put into the actual transaction at the end of gnc_csv_parse_to_trans. */
+ trans_line->balance = *((gnc_numeric*)(prop->value));
+ trans_line->balance_set = TRUE;
+ }
+ break;
+ }
+ list->properties = g_list_next(list->properties);
+ }
+
+ /* Add a split with the cumulative amount value. */
+ trans_add_split(trans_line->trans, list->account, book, amount);
+
+ return trans_line;
+}
+
+/** Creates a list of transactions from parsed data. Transactions that
+ * could be created from rows are placed in parse_data->transactions;
+ * rows that fail are placed in parse_data->error_lines. (Note: there
+ * is no way for this function to "fail," i.e. it only returns 0, so
+ * it may be changed to a void function in the future.)
+ * @param parse_data Data that is being parsed
+ * @param account Account with which transactions are created
+ * @param redo_errors TRUE to convert only error data, FALSE for all data
+ * @return 0 on success, 1 on failure
+ */
+int gnc_csv_parse_to_trans(GncCsvParseData* parse_data, Account* account,
+ gboolean redo_errors)
+{
+ gboolean hasBalanceColumn;
+ int i, j, max_cols = 0;
+ GArray* column_types = parse_data->column_types;
+ GList *error_lines = NULL, *begin_error_lines = NULL;
+
+ /* last_transaction points to the last element in
+ * parse_data->transactions, or NULL if it's empty. */
+ GList* last_transaction = NULL;
+
+ /* Free parse_data->error_lines and parse_data->transactions if they
+ * already exist. */
+ if(redo_errors) /* If we're redoing errors, we save freeing until the end. */
+ {
+ begin_error_lines = error_lines = parse_data->error_lines;
+ }
+ else
+ {
+ if(parse_data->error_lines != NULL)
+ {
+ g_list_free(parse_data->error_lines);
+ }
+ if(parse_data->transactions != NULL)
+ {
+ g_list_free(parse_data->transactions);
+ }
+ }
+ parse_data->error_lines = NULL;
+
+ if(redo_errors) /* If we're looking only at error data ... */
+ {
+ if(parse_data->transactions == NULL)
+ {
+ last_transaction = NULL;
+ }
+ else
+ {
+ /* Move last_transaction to the end. */
+ last_transaction = parse_data->transactions;
+ while(g_list_next(last_transaction) != NULL)
+ {
+ last_transaction = g_list_next(last_transaction);
+ }
+ }
+ /* ... we use only the lines in error_lines. */
+ if(error_lines == NULL)
+ i = parse_data->orig_lines->len; /* Don't go into the for loop. */
+ else
+ i = GPOINTER_TO_INT(error_lines->data);
+ }
+ else /* Otherwise, we look at all the data. */
+ {
+ /* The following while-loop effectively behaves like the following for-loop:
+ * for(i = 0; i < parse_data->orig_lines->len; i++). */
+ i = 0;
+ last_transaction = NULL;
+ }
+ while(i < parse_data->orig_lines->len)
+ {
+ GPtrArray* line = parse_data->orig_lines->pdata[i];
+ /* This flag is TRUE if there are any errors in this row. */
+ gboolean errors = FALSE;
+ gchar* error_message = NULL;
+ TransPropertyList* list = trans_property_list_new(account, parse_data->date_format);
+ GncCsvTransLine* trans_line = NULL;
+
+ for(j = 0; j < line->len; j++)
+ {
+ /* We do nothing in "None" columns. */
+ if(column_types->data[j] != GNC_CSV_NONE)
+ {
+ /* Affect the transaction appropriately. */
+ TransProperty* property = trans_property_new(column_types->data[j], list);
+ gboolean succeeded = trans_property_set(property, line->pdata[j]);
+ /* TODO Maybe move error handling to within TransPropertyList functions? */
+ if(succeeded)
+ {
+ trans_property_list_add(property);
+ }
+ else
+ {
+ errors = TRUE;
+ error_message = g_strdup_printf(_("%s column could not be understood."),
+ _(gnc_csv_column_type_strs[property->type]));
+ trans_property_free(property);
+ break;
+ }
+ }
+ }
+
+ /* If we had success, add the transaction to parse_data->transaction. */
+ if(!errors)
+ {
+ trans_line = trans_property_list_to_trans(list, &error_message);
+ errors = trans_line == NULL;
+ }
+
+ trans_property_list_free(list);
+
+ /* If there were errors, add this line to parse_data->error_lines. */
+ if(errors)
+ {
+ parse_data->error_lines = g_list_append(parse_data->error_lines,
+ GINT_TO_POINTER(i));
+ /* If there's already an error message, we need to replace it. */
+ if(line->len > (int)(parse_data->orig_row_lengths->data[i]))
+ {
+ g_free(line->pdata[line->len - 1]);
+ line->pdata[line->len - 1] = error_message;
+ }
+ else
+ {
+ /* Put the error message at the end of the line. */
+ g_ptr_array_add(line, error_message);
+ }
+ }
+ else
+ {
+ /* If all went well, add this transaction to the list. */
+ trans_line->line_no = i;
+
+ /* We keep the transactions sorted by date. We start at the end
+ * of the list and go backward, simply because the file itself
+ * is probably also sorted by date (but we need to handle the
+ * exception anyway). */
+
+ /* If we can just put it at the end, do so and increment last_transaction. */
+ if(last_transaction == NULL ||
+ xaccTransGetDate(((GncCsvTransLine*)(last_transaction->data))->trans) <= xaccTransGetDate(trans_line->trans))
+ {
+ parse_data->transactions = g_list_append(parse_data->transactions, trans_line);
+ /* If this is the first transaction, we need to get last_transaction on track. */
+ if(last_transaction == NULL)
+ last_transaction = parse_data->transactions;
+ else /* Otherwise, we can just continue. */
+ last_transaction = g_list_next(last_transaction);
+ }
+ /* Otherwise, search backward for the correct spot. */
+ else
+ {
+ GList* insertion_spot = last_transaction;
+ while(insertion_spot != NULL &&
+ xaccTransGetDate(((GncCsvTransLine*)(insertion_spot->data))->trans) > xaccTransGetDate(trans_line->trans))
+ {
+ insertion_spot = g_list_previous(insertion_spot);
+ }
+ /* Move insertion_spot one location forward since we have to
+ * use the g_list_insert_before function. */
+ if(insertion_spot == NULL) /* We need to handle the case of inserting at the beginning of the list. */
+ insertion_spot = parse_data->transactions;
+ else
+ insertion_spot = g_list_next(insertion_spot);
+
+ parse_data->transactions = g_list_insert_before(parse_data->transactions, insertion_spot, trans_line);
+ }
+ }
+
+ /* Increment to the next row. */
+ if(redo_errors)
+ {
+ /* Move to the next error line in the list. */
+ error_lines = g_list_next(error_lines);
+ if(error_lines == NULL)
+ i = parse_data->orig_lines->len; /* Don't continue the for loop. */
+ else
+ i = GPOINTER_TO_INT(error_lines->data);
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ /* If we have a balance column, set the appropriate amounts on the transactions. */
+ hasBalanceColumn = FALSE;
+ for(i = 0; i < parse_data->column_types->len; i++)
+ {
+ if(parse_data->column_types->data[i] == GNC_CSV_BALANCE)
+ {
+ hasBalanceColumn = TRUE;
+ break;
+ }
+ }
+
+ if(hasBalanceColumn)
+ {
+ GList* transactions = parse_data->transactions;
+
+ /* balance_offset is how much the balance currently in the account
+ * differs from what it will be after the transactions are
+ * imported. This will be sum of all the previous transactions for
+ * any given transaction. */
+ gnc_numeric balance_offset = double_to_gnc_numeric(0.0,
+ xaccAccountGetCommoditySCU(account),
+ GNC_RND_ROUND);
+ while(transactions != NULL)
+ {
+ GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data;
+ if(trans_line->balance_set)
+ {
+ time_t date = xaccTransGetDate(trans_line->trans);
+ /* Find what the balance should be by adding the offset to the actual balance. */
+ gnc_numeric existing_balance = gnc_numeric_add(balance_offset,
+ xaccAccountGetBalanceAsOfDate(account, date),
+ xaccAccountGetCommoditySCU(account),
+ GNC_RND_ROUND);
+
+ /* The amount of the transaction is the difference between the new and existing balance. */
+ gnc_numeric amount = gnc_numeric_sub(trans_line->balance,
+ existing_balance,
+ xaccAccountGetCommoditySCU(account),
+ GNC_RND_ROUND);
+
+ SplitList* splits = xaccTransGetSplitList(trans_line->trans);
+ while(splits)
+ {
+ SplitList* next_splits = g_list_next(splits);
+ xaccSplitDestroy((Split*)splits->data);
+ splits = next_splits;
+ }
+
+ trans_add_split(trans_line->trans, account, gnc_account_get_book(account), amount);
+
+ /* This new transaction needs to be added to the balance offset. */
+ balance_offset = gnc_numeric_add(balance_offset,
+ amount,
+ xaccAccountGetCommoditySCU(account),
+ GNC_RND_ROUND);
+ }
+ transactions = g_list_next(transactions);
+ }
+ }
+
+ if(redo_errors) /* Now that we're at the end, we do the freeing. */
+ {
+ g_list_free(begin_error_lines);
+ }
+
+ /* We need to resize parse_data->column_types since errors may have added columns. */
+ for(i = 0; i < parse_data->orig_lines->len; i++)
+ {
+ if(max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len)
+ max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len;
+ }
+ i = parse_data->column_types->len;
+ parse_data->column_types = g_array_set_size(parse_data->column_types, max_cols);
+ for(; i < max_cols; i++)
+ {
+ parse_data->column_types->data[i] = GNC_CSV_NONE;
+ }
+
+ return 0;
+}
Added: gnucash/trunk/src/import-export/csv/gnc-csv-model.h
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-model.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-model.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,122 @@
+/********************************************************************\
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+\********************************************************************/
+
+/** @file
+ @brief CSV import GUI
+ *
+ gnc-csv-import.h
+ @author Copyright (c) 2007 Benny Sperisen <lasindi at gmail.com>
+ */
+
+#ifndef GNC_CSV_MODEL_H
+#define GNC_CSV_MODEL_H
+
+#include "config.h"
+
+#include "Account.h"
+#include "Transaction.h"
+
+#include "stf/stf-parse.h"
+
+/** Enumeration for column types. These are the different types of
+ * columns that can exist in a CSV/Fixed-Width file. There should be
+ * no two columns with the same type except for the GNC_CSV_NONE
+ * type. */
+enum GncCsvColumnType {GNC_CSV_NONE,
+ GNC_CSV_DATE,
+ GNC_CSV_DESCRIPTION,
+ GNC_CSV_BALANCE,
+ GNC_CSV_DEPOSIT,
+ GNC_CSV_WITHDRAWAL,
+ GNC_CSV_NUM,
+ GNC_CSV_NUM_COL_TYPES};
+
+/** Enumeration for error types. These are the different types of
+ * errors that various functions used for the CSV/Fixed-Width importer
+ * can have. */
+enum GncCsvErrorType {GNC_CSV_FILE_OPEN_ERR,
+ GNC_CSV_ENCODING_ERR};
+
+/** Struct for containing a string. This struct simply contains
+ * pointers to the beginning and end of a string. We need this because
+ * the STF code that gnc_csv_parse calls requires these pointers. */
+typedef struct
+{
+ char* begin;
+ char* end;
+} GncCsvStr;
+
+/* TODO We now sort transactions by date, not line number, so we
+ * should probably get rid of this struct and uses of it. */
+
+/** Struct pairing a transaction with a line number. This struct is
+ * used to keep the transactions in order. When rows are separated
+ * into "valid" and "error" lists (in case some of the rows have cells
+ * that are unparseable), we want the user to still be able to
+ * "correct" the error list. If we keep the line numbers of valid
+ * transactions, we can then put transactions created from the newly
+ * corrected rows into the right places. */
+typedef struct
+{
+ int line_no;
+ Transaction* trans;
+ gnc_numeric balance; /**< The (supposed) balance after this transaction takes place */
+ gboolean balance_set; /**< TRUE if balance has been set from user data, FALSE otherwise */
+} GncCsvTransLine;
+
+extern const int num_date_formats;
+/* A set of date formats that the user sees. */
+extern const gchar* date_format_user[];
+
+/* This array contains all of the different strings for different column types. */
+extern gchar* gnc_csv_column_type_strs[];
+
+/** Struct containing data for parsing a CSV/Fixed-Width file. */
+typedef struct
+{
+ gchar* encoding;
+ GMappedFile* raw_mapping; /**< The mapping containing raw_str */
+ GncCsvStr raw_str; /**< Untouched data from the file as a string */
+ GncCsvStr file_str; /**< raw_str translated into UTF-8 */
+ GPtrArray* orig_lines; /**< file_str parsed into a two-dimensional array of strings */
+ GArray* orig_row_lengths; /**< The lengths of rows in orig_lines
+ * before error messages are appended */
+ int orig_max_row; /**< Holds the maximum value in orig_row_lengths */
+ GStringChunk* chunk; /**< A chunk of memory in which the contents of orig_lines is stored */
+ StfParseOptions_t* options; /**< Options controlling how file_str should be parsed */
+ GArray* column_types; /**< Array of values from the GncCsvColumnType enumeration */
+ GList* error_lines; /**< List of row numbers in orig_lines that have errors */
+ GList* transactions; /**< List of GncCsvTransLine*s created using orig_lines and column_types */
+ int date_format; /**< The format of the text in the date columns from date_format_internal. */
+} GncCsvParseData;
+
+GncCsvParseData* gnc_csv_new_parse_data(void);
+
+void gnc_csv_parse_data_free(GncCsvParseData* parse_data);
+
+int gnc_csv_load_file(GncCsvParseData* parse_data, const char* filename,
+ GError** error);
+
+int gnc_csv_convert_encoding(GncCsvParseData* parse_data, const char* encoding, GError** error);
+
+int gnc_csv_parse(GncCsvParseData* parse_data, gboolean guessColTypes, GError** error);
+
+int gnc_csv_parse_to_trans(GncCsvParseData* parse_data, Account* account, gboolean redo_errors);
+
+#endif
Added: gnucash/trunk/src/import-export/csv/gnc-csv-preview-dialog.glade
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv-preview-dialog.glade 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv-preview-dialog.glade 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,496 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.2.0 on Tue Jul 17 09:11:23 2007 by lasindi at pi-->
+<glade-interface>
+ <widget class="GtkDialog" id="dialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Import CSV/Fixed-Width File</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkTable" id="enctable">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ <property name="y_padding">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ <property name="y_padding">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkRadioButton" id="csv_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Separated</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="radiobutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Fixed-Width</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">csv_button</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Data type: </property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ <property name="y_padding">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Encoding: </property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">3</property>
+ <property name="row_spacing">3</property>
+ <child>
+ <widget class="GtkCheckButton" id="space_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Space</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="tab_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Tab</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="comma_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Comma (,)</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="colon_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Colon (:)</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="semicolon_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Semicolon (;)</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="hyphen_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Hyphen (-)</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="custom_cbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Custom</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="custom_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Separators</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator6">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">3</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="date_format_container">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Date Format</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHSeparator" id="hseparator3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">3</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkImage" id="instructions_image">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock">gtk-dialog-info</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="instructions_label">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Select the type of each column below.</property>
+ <property name="wrap">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTreeView" id="ctreeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">True</property>
+ <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_BOTH</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="resize_mode">GTK_RESIZE_QUEUE</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="headers_clickable">True</property>
+ <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_BOTH</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">9</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">gtk-ok</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
Deleted: gnucash/trunk/src/import-export/csv/gnc-csv2glist.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv2glist.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv2glist.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -1,187 +0,0 @@
-
-/*
- Copyright 2004 Kevin dot Hammack at comcast dot net
-
- 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 2 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, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA
-
-
- */
-
-//#include "config.h"
-
-#include <glib.h>
-#include <glib/gstdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <fcntl.h>
-
-//#include "file-utils.h"
-
-#define TEST_CSV
-
-/*
- <warlord> Yea, just returning a GList would be fine.. Or a GList of
- GLists, one per row..
-*/
-
-
-/*
- split_csv_list - Take a string, split on commas into a GList.
- Tricky part: honor quotes.
- Quotes aren't stripped.
- This would have been both easier and cleaner in scheme, and / or
- using regular expressions.
- I was thinking small and fast when I wrote it. :-P
-*/
-
-static GList *
-split_csv_line(char *line) {
- GList *csvlist = NULL;
- gchar *begin;
- gchar *current;
- gchar *cell;
- gchar quote=0;
- gboolean eol = FALSE;
-
- current = line;
- begin = current;
-
- while (!eol) {
-
- if (quote &&
- (*current == quote) && (*(current-1) != '\\') &&
- (current != begin)) {
-
- quote = 0;
- }
- else if (!quote && (*current == '"')) { quote = '"'; }
- else if (!quote && (*current == '\'')) { quote = '\''; }
-
- if (!quote && (*current == ',')) { *current = 0; }
-
- if (*current == '\n') {
- *current = 0;
- eol = TRUE;
- }
-
- if (*current == 0) {
- cell = g_strdup( begin );
- csvlist = g_list_prepend(csvlist, cell);
- current++;
- begin = current;
- quote = 0;
- }
- else {
- current++;
- }
-
- }
-
- return g_list_reverse(csvlist);
-}
-
-
-#if 1
-gint64
-gnc_getline (gchar **line, FILE *file)
-{
- char str[BUFSIZ];
- gint64 len;
- GString *gs = g_string_new("");
-
- if (!line || !file) return 0;
-
- while (fgets(str, sizeof(str), file) != NULL) {
- g_string_append(gs, str);
-
- len = strlen(str);
- if (str[len-1] == '\n')
- break;
- }
-
- len = gs->len;
- *line = g_string_free(gs, FALSE);
- return len;
-}
-#endif
-
-
-GList *
-gnc_csv_parse (FILE *handle)
-{
- GList *csvlists = NULL;
- ssize_t bytes_read;
- char *line;
-
- while (bytes_read = gnc_getline (&line, handle) > 0) {
- csvlists = g_list_prepend(csvlists, split_csv_line(line));
- g_free(line);
- }
-
- return g_list_reverse(csvlists);
-}
-
-
-
-#ifdef TEST_CSV
-
-static void print_glist_rec (GList *list) {
-
-}
-// print_glist_rec(list);
-
-static void print_glist(GList *list, gpointer dummy) {
-
- printf("%d: (", g_list_length(list));
-
- while (list != NULL) {
- if ((list->data != NULL) && ( *((char *) list->data) != 0)) {
- printf( "%s", list->data) ;
- }
- else {
- printf( "\"\"" );
- }
- list = list->next;
- if (list) {
- printf(" ");
- }
- }
-
- printf(")\n");
-}
-
-int main (int argc, char **argv) {
-
- FILE *fp;
- int result;
- GList *parsed_csv;
- GList *current;
- int dummy = 1;
-
- if (argc < 2) {
- printf("usage:\n\tcsv2glist fname.csv\n");
- }
-
- fp = g_fopen (argv[1], "r");
- if (fp == NULL) return 1;
-
- parsed_csv = gnc_csv_parse(fp);
-
- g_list_foreach(parsed_csv, (GFunc)print_glist, &dummy);
-
-}
-
-#endif
Deleted: gnucash/trunk/src/import-export/csv/gnc-csv2glist.h
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-csv2glist.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-csv2glist.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -1,39 +0,0 @@
-/*
- * gnc-csv2glist.h -- Parse Comma Separated Variable files
- *
- * Created by: Derek Atkins <derek at ihtfp.com>
- * Copyright (c) 2004 Derek Atkins <derek at ihtfp.com>
- *
- * 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 2 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, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu at gnu.org
- */
-
-#ifndef GNC_IMPORT_CSV_2GLIST_H
-#define GNC_IMPORT_CSV_2GLIST_H
-
-/**
- * gnc_csv_parse -- parse a Comma Separated Variable FILE into
- * a list of lists of values.
- *
- * Args: file : the input file to read
- * Returns : a list of lists of strings. Both lists and all strings
- * must be g_free()'d by the caller.
- */
-
-GList * gnc_csv_parse (FILE *file);
-
-#endif
Added: gnucash/trunk/src/import-export/csv/gnc-plugin-csv-ui.xml
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-plugin-csv-ui.xml 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-plugin-csv-ui.xml 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,11 @@
+<ui>
+ <menubar>
+ <menu name="File" action="FileAction">
+ <menu name="FileImport" action="FileImportAction">
+ <placeholder name="FileImportPlaceholder">
+ <menuitem name="FileCsvImport" action="CsvImportAction"/>
+ </placeholder>
+ </menu>
+ </menu>
+ </menubar>
+</ui>
Added: gnucash/trunk/src/import-export/csv/gnc-plugin-csv.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-plugin-csv.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-plugin-csv.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,160 @@
+/*
+ * gnc-plugin-csv.c --
+ * Copyright (C) 2003 David Hampton <hampton at employees.org>
+ *
+ * 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 2 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, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "gnc-plugin-csv.h"
+#include "gnc-plugin-manager.h"
+
+#include "gnc-csv-import.h"
+
+static void gnc_plugin_csv_class_init (GncPluginCsvClass *klass);
+static void gnc_plugin_csv_init (GncPluginCsv *plugin);
+static void gnc_plugin_csv_finalize (GObject *object);
+
+/* Command callbacks */
+static void gnc_plugin_csv_cmd_import (GtkAction *action, GncMainWindowActionData *data);
+
+
+#define PLUGIN_ACTIONS_NAME "gnc-plugin-csv-actions"
+#define PLUGIN_UI_FILENAME "gnc-plugin-csv-ui.xml"
+
+static GtkActionEntry gnc_plugin_actions [] = {
+ { "CsvImportAction", GTK_STOCK_CONVERT, N_("Import _CSV/Fixed-Width..."), NULL,
+ N_(" a CSV/Fixed-Width file"),
+ G_CALLBACK (gnc_plugin_csv_cmd_import) },
+};
+static guint gnc_plugin_n_actions = G_N_ELEMENTS (gnc_plugin_actions);
+
+typedef struct GncPluginCsvPrivate
+{
+ gpointer dummy;
+} GncPluginCsvPrivate;
+
+#define GNC_PLUGIN_CSV_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_PLUGIN_CSV, GncPluginCsvPrivate))
+
+static GObjectClass *parent_class = NULL;
+
+GType
+gnc_plugin_csv_get_type (void)
+{
+ static GType gnc_plugin_csv_type = 0;
+
+ if (gnc_plugin_csv_type == 0) {
+ static const GTypeInfo our_info = {
+ sizeof (GncPluginCsvClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gnc_plugin_csv_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GncPluginCsv),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gnc_plugin_csv_init,
+ };
+
+ gnc_plugin_csv_type = g_type_register_static (GNC_TYPE_PLUGIN,
+ "GncPluginCsv",
+ &our_info, 0);
+ }
+
+ return gnc_plugin_csv_type;
+}
+
+GncPlugin *
+gnc_plugin_csv_new (void)
+{
+ return GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_CSV, NULL));
+}
+
+static void
+gnc_plugin_csv_class_init (GncPluginCsvClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GncPluginClass *plugin_class = GNC_PLUGIN_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gnc_plugin_csv_finalize;
+
+ /* plugin info */
+ plugin_class->plugin_name = GNC_PLUGIN_CSV_NAME;
+
+ /* widget addition/removal */
+ plugin_class->actions_name = PLUGIN_ACTIONS_NAME;
+ plugin_class->actions = gnc_plugin_actions;
+ plugin_class->n_actions = gnc_plugin_n_actions;
+ plugin_class->ui_filename = PLUGIN_UI_FILENAME;
+
+ g_type_class_add_private(klass, sizeof(GncPluginCsvPrivate));
+}
+
+static void
+gnc_plugin_csv_init (GncPluginCsv *plugin)
+{
+}
+
+static void
+gnc_plugin_csv_finalize (GObject *object)
+{
+ GncPluginCsv *plugin;
+ GncPluginCsvPrivate *priv;
+
+ g_return_if_fail (GNC_IS_PLUGIN_CSV (object));
+
+ plugin = GNC_PLUGIN_CSV (object);
+ priv = GNC_PLUGIN_CSV_GET_PRIVATE(plugin);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/************************************************************
+ * Plugin Function Implementation *
+ ************************************************************/
+
+/************************************************************
+ * Command Callbacks *
+ ************************************************************/
+
+static void
+gnc_plugin_csv_cmd_import (GtkAction *action,
+ GncMainWindowActionData *data)
+{
+ gnc_file_csv_import();
+}
+
+
+/************************************************************
+ * Plugin Bootstrapping *
+ ************************************************************/
+
+void
+gnc_plugin_csv_create_plugin (void)
+{
+ GncPlugin *plugin = gnc_plugin_csv_new ();
+
+ gnc_plugin_manager_add_plugin (gnc_plugin_manager_get (), plugin);
+}
Added: gnucash/trunk/src/import-export/csv/gnc-plugin-csv.h
===================================================================
--- gnucash/trunk/src/import-export/csv/gnc-plugin-csv.h 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gnc-plugin-csv.h 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,60 @@
+/*
+ * gnc-plugin-csv.h --
+ * Copyright (C) 2003 David Hampton <hampton at employees.org>
+ *
+ * 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 2 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, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+#ifndef __GNC_PLUGIN_CSV_H
+#define __GNC_PLUGIN_CSV_H
+
+#include <gtk/gtkwindow.h>
+
+#include "gnc-plugin.h"
+
+G_BEGIN_DECLS
+
+/* type macros */
+#define GNC_TYPE_PLUGIN_CSV (gnc_plugin_csv_get_type ())
+#define GNC_PLUGIN_CSV(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_PLUGIN_CSV, GncPluginCsv))
+#define GNC_PLUGIN_CSV_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_PLUGIN_CSV, GncPluginCsvClass))
+#define GNC_IS_PLUGIN_CSV(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_PLUGIN_CSV))
+#define GNC_IS_PLUGIN_CSV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_PLUGIN_CSV))
+#define GNC_PLUGIN_CSV_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_PLUGIN_CSV, GncPluginCsvClass))
+
+#define GNC_PLUGIN_CSV_NAME "gnc-plugin-csv"
+
+/* typedefs & structures */
+typedef struct {
+ GncPlugin gnc_plugin;
+} GncPluginCsv;
+
+typedef struct {
+ GncPluginClass gnc_plugin;
+} GncPluginCsvClass;
+
+/* function prototypes */
+GType gnc_plugin_csv_get_type (void);
+
+GncPlugin *gnc_plugin_csv_new (void);
+
+void gnc_plugin_csv_create_plugin (void);
+
+G_END_DECLS
+
+#endif /* __GNC_PLUGIN_CSV_H */
Added: gnucash/trunk/src/import-export/csv/gncmod-csv-import.c
===================================================================
--- gnucash/trunk/src/import-export/csv/gncmod-csv-import.c 2007-10-12 22:24:05 UTC (rev 16560)
+++ gnucash/trunk/src/import-export/csv/gncmod-csv-import.c 2007-10-12 22:51:34 UTC (rev 16561)
@@ -0,0 +1,91 @@
+/********************************************************************\
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+\********************************************************************/
+/** @addtogroup Import_Export
+ @{ */
+ /**@internal
+ @file gncmod-csv-import.c
+ @brief module definition/initialization for the csv importer
+ @author Copyright (c) 2002 Benoit Grégoire bock at step.polymtl.ca
+ */
+#include "config.h"
+
+#include <gmodule.h>
+
+#include "gnc-module.h"
+#include "gnc-module-api.h"
+#include "gnc-plugin-csv.h"
+
+GNC_MODULE_API_DECL(libgncmod_csv)
+
+/* version of the gnc module system interface we require */
+int libgncmod_csv_gnc_module_system_interface = 0;
+
+/* module versioning uses libtool semantics. */
+int libgncmod_csv_gnc_module_current = 0;
+int libgncmod_csv_gnc_module_revision = 0;
+int libgncmod_csv_gnc_module_age = 0;
+
+//static GNCModule bus_core;
+//static GNCModule file;
+
+
+char *
+libgncmod_csv_gnc_module_path(void)
+{
+ return g_strdup("gnucash/import-export/csv");
+}
+
+char *
+libgncmod_csv_gnc_module_description(void)
+{
+ return g_strdup("Gnome GUI and C code for CSV importer using libcsv");
+}
+
+int
+libgncmod_csv_gnc_module_init(int refcount)
+{
+ if(!gnc_module_load("gnucash/engine", 0))
+ {
+ return FALSE;
+ }
+ if(!gnc_module_load("gnucash/app-utils", 0))
+ {
+ return FALSE;
+ }
+ if(!gnc_module_load("gnucash/gnome-utils", 0))
+ {
+ return FALSE;
+ }
+ if(!gnc_module_load("gnucash/import-export", 0))
+ {
+ return FALSE;
+ }
+
+ /* Add menu items with C callbacks */
+ gnc_plugin_csv_create_plugin();
+
+ return TRUE;
+}
+
+int
+libgncmod_csv_gnc_module_end(int refcount)
+{
+ return TRUE;
+}
+/** @}*/
More information about the gnucash-changes
mailing list