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