r16248 - gnucash/branches/csv-import/src/import-export/csv - Column type selection seems to work reasonably well;
Benjamin Sperisen
lasindi at cvs.gnucash.org
Wed Jul 4 06:42:58 EDT 2007
Author: lasindi
Date: 2007-07-04 06:42:57 -0400 (Wed, 04 Jul 2007)
New Revision: 16248
Trac: http://svn.gnucash.org/trac/changeset/16248
Modified:
gnucash/branches/csv-import/src/import-export/csv/gnc-csv-import.c
gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.c
gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.h
gnucash/branches/csv-import/src/import-export/csv/gnc-csv-preview-dialog.glade
Log:
Column type selection seems to work reasonably well;
added rudimentary error correction code, although really more skeleton
code than anything useful;
added date format selection, though it's barely tested and does not
handle errors yet.
Modified: gnucash/branches/csv-import/src/import-export/csv/gnc-csv-import.c
===================================================================
--- gnucash/branches/csv-import/src/import-export/csv/gnc-csv-import.c 2007-07-02 20:39:56 UTC (rev 16247)
+++ gnucash/branches/csv-import/src/import-export/csv/gnc-csv-import.c 2007-07-04 10:42:57 UTC (rev 16248)
@@ -34,13 +34,12 @@
#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 <stdlib.h> /* TODO Get rid of this */
-
#define GCONF_SECTION "dialogs/import/csv"
static QofLogModule log_module = GNC_MOD_IMPORT;
@@ -58,6 +57,8 @@
{
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 */
@@ -66,6 +67,14 @@
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.) */
+ /* TODO We might not need previewing_errors. */
+ 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". */
} GncCsvPreview;
@@ -83,6 +92,7 @@
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. */
@@ -104,8 +114,30 @@
stf_parse_options_csv_set_separators(preview->parse_data->options, NULL, checked_separators);
g_slist_free(checked_separators);
- /* TODO Handle error */
- gnc_csv_parse(preview->parse_data, FALSE, NULL);
+ /* 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_treeview(preview, TRUE);
}
@@ -123,13 +155,29 @@
* 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;
- /* TODO Handle errors and comment */
- gnc_csv_convert_encoding(preview->parse_data, encoding);
- gnc_csv_parse(preview->parse_data, FALSE, &error);
+ /* 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_treeview(preview, TRUE);
preview->encoding_selected_called = FALSE;
}
@@ -145,6 +193,15 @@
"Description",
"Amount"};
+/** Event handler for selecting a new date format.
+ * @param format_selector The combo box for selecting date formats
+ * @param preview The data that is being configured
+ */
+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.
@@ -301,6 +358,7 @@
int i;
GncCsvPreview* preview = g_malloc(sizeof(GncCsvPreview));
GtkWidget *ok_button, *cancel_button;
+ GtkContainer* date_format_container;
/* The names in the glade file for the sep buttons. */
char* sep_button_names[] = {"space_cbutton",
"tab_cbutton",
@@ -308,12 +366,12 @@
"colon_cbutton",
"semicolon_cbutton",
"hyphen_cbutton"};
- /* The table containing encselector and the separator configuration widgets */
+ /* The table containing preview->encselector and the separator configuration widgets */
GtkTable* enctable;
- /* The widget for selecting the encoding. (The default is UTF-8.) */
- GtkWidget* encselector = go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8);
- /* Connect it to the encoding_selected event handler. */
- g_signal_connect(G_OBJECT(encselector), "charmap_changed",
+
+ 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. */
@@ -348,10 +406,26 @@
/* 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, encselector, 1, 2, 0, 1);
+ 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",
@@ -373,9 +447,6 @@
* set it initially to FALSE. */
preview->encoding_selected_called = FALSE;
- /* TODO Free stuff */
- preview->approved = FALSE; /* This is FALSE until the user clicks "OK". */
-
return preview;
}
@@ -393,29 +464,41 @@
* 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). */
-/* TODO Something's probably screwing up with this function when
- * selecting a new encoding. */
-/* TODO Comment this function. */
/* TODO Look at getting rid of notEmpty */
static void gnc_csv_preview_treeview(GncCsvPreview* preview, gboolean notEmpty)
{
+ /* 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;
+
+ /* store contains only strings. */
GType* types = g_malloc(2 * ncols * sizeof(GType));
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_malloc(ncols * sizeof(GtkListStore*));
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);
@@ -423,16 +506,17 @@
}
}
- /* Clear out any exisiting columns. */
if(notEmpty)
{
GList *children, *children_begin;
int size;
+ /* Clear out exisiting columns in preview->treeview. */
do
{
GtkTreeViewColumn* col = gtk_tree_view_get_column(preview->treeview, 0);
size = gtk_tree_view_remove_column(preview->treeview, col);
} while(size);
+ /* Do the same in preview->ctreeview. */
do
{
GtkTreeViewColumn* col = gtk_tree_view_get_column(preview->ctreeview, 0);
@@ -440,49 +524,90 @@
} while(size);
}
- /* TODO free types */
-
- for(i = 0; i < preview->parse_data->orig_lines->len; i++)
+ /* Fill the data treeview with data from the file. */
+ if(preview->previewing_errors) /* If we are showing only errors ... */
{
- gtk_list_store_append(store, &iter);
- for(j = 0; j < ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->len; j++)
+ /* ... only pick rows that are in preview->error_lines. */
+ GList* error_lines = preview->parse_data->error_lines;
+ while(error_lines != NULL)
{
- gtk_list_store_set(store, &iter, j,
- ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->pdata[j],
- -1);
+ 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++)
+ {
+ gtk_list_store_set(store, &iter, j,
+ ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->pdata[j],
+ -1);
+ }
}
}
+ else /* Otherwise, put in all of the data. */
+ {
+ for(i = 0; i < preview->parse_data->orig_lines->len; i++)
+ {
+ gtk_list_store_append(store, &iter);
+ for(j = 0; j < ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->len; j++)
+ {
+ gtk_list_store_set(store, &iter, j,
+ ((GPtrArray*)(preview->parse_data->orig_lines->pdata[i]))->pdata[j],
+ -1);
+ }
+ }
+ }
+ /* 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, "None", -1);
+ gtk_list_store_set(ctstore, &iter, 2*i, cstores[i], 2*i+1,
+ "None", -1);
}
+ /* Insert columns into the data and column type treeviews. */
for(i = 0; i < ncols; i++)
{
+ /* 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 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. */
gtk_tree_view_insert_column_with_attributes(preview->treeview,
-1, "", renderer, "text", i, NULL);
+ /* 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);
}
+ /* 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));
- gtk_tree_view_set_model(preview->ctreeview, GTK_TREE_MODEL(ctstore));
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));
- g_debug("ctreeview is %p\n", preview->ctreeview);
- /* TODO free cstore and ctstore */
+
+ /* 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);
}
/** A function that lets the user preview a file's data. This function
@@ -497,6 +622,9 @@
{
/* 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_treeview on this preview, so we use
* FALSE. */
@@ -504,15 +632,36 @@
/* Wait until the user clicks "OK" or "Cancel". */
gtk_dialog_run(GTK_DIALOG(preview->dialog));
- /* Return 0 or 1 if preview->approved is TRUE or FALSE, respectively. */
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)
+{
+ /* TODO Implement */
+ preview->previewing_errors = TRUE;
+ preview->approved = FALSE; /* This is FALSE until the user clicks "OK". */
+
+ /* Wait until the user clicks "OK" or "Cancel". */
+ gtk_dialog_run(GTK_DIALOG(preview->dialog));
+
+ if(preview->approved)
+ return 0;
+ else
+ return 1;
+}
+
/** Lets the user import a CSV/Fixed-Width file. */
-/* TODO Comment this function. */
void gnc_file_csv_import(void)
{
/* The name of the file the user selected. */
@@ -524,15 +673,13 @@
/* 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);
+ 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;
+ 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. */
@@ -544,22 +691,24 @@
gnc_set_default_directory(GCONF_SECTION, default_dir);
g_free(default_dir);
- /* TODO Check for errors */
-
/* 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 ... */
- /* TODO Do real error handling */
- g_debug("Couldn't open file\n");
+ 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;
+ }
}
/* Parse the data. */
if(gnc_csv_parse(parse_data, TRUE, &error))
{
/* If we couldn't parse the data ... */
- /* TODO real error handling */
- g_debug("Error in parsing: %s\n", error->message);
+ gnc_error_dialog(NULL, "%s", error->message);
}
/* Preview the data. */
@@ -575,11 +724,26 @@
/* 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. */
- /* TODO Handle errors here. */
- gnc_parse_to_trans(parse_data, account);
+ gnc_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)
+ {
+ gnc_csv_preview_errors(preview);
+ user_canceled = gnc_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);
@@ -590,7 +754,7 @@
{
GncCsvTransLine* trans_line = transactions->data;
gnc_gen_trans_list_add_trans(gnc_csv_importer_gui,
- (Transaction*)(trans_line->trans));
+ trans_line->trans);
transactions = g_list_next(transactions);
}
/* Let the user load those transactions into the account. */
Modified: gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.c
===================================================================
--- gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.c 2007-07-02 20:39:56 UTC (rev 16247)
+++ gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.c 2007-07-04 10:42:57 UTC (rev 16248)
@@ -13,7 +13,29 @@
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
+#include <time.h>
+const int num_date_formats = 8;
+/* A set of date formats that the user sees. */
+const gchar* date_format_user[] = {"yyyy/mm/dd",
+ "yy/mm/dd",
+ "dd/mm/yyyy",
+ "dd/mm/yy",
+ "dd/mm/yyyy",
+ "dd/mm/yy",
+ "mm/dd/yyyy",
+ "mm/dd/yy"};
+
+/* Matching formats for date_format_user to be used with strptime. */
+const gchar* date_format_internal[] = {"%Y/%m/%d",
+ "%y/%m/%d",
+ "%d/%m/%Y",
+ "%d/%m/%y",
+ "%d/%m/%Y",
+ "%d/%m/%y",
+ "%m/%d/%Y",
+ "%m/%d/%y"};
+
/** A set of sensible defaults for parsing CSV files.
* @return StfParseOptions_t* for parsing a file with comma separators
*/
@@ -26,10 +48,21 @@
}
/* TODO This will be replaced by something more sophisticated. */
-static time_t parse_date(const char* date_str)
+static time_t parse_date(const char* date_str, int format)
{
struct tm retvalue;
char mstr[3], dstr[3], ystr[3];
+ strptime(date_str, date_format_internal[format], &retvalue);
+ /* TODO Handle error */
+ /* We have to set the hour, minute, second and daylight savings time
+ * flags to valid values. */
+ retvalue.tm_hour = 0;
+ retvalue.tm_min = 0;
+ retvalue.tm_sec = 1;
+ retvalue.tm_isdst = -1;
+ return mktime(&retvalue);
+
+ /* TODO Get rid of the old clumsy method */
strncpy(mstr, date_str, 2);
strncpy(dstr, date_str + 3, 2);
strncpy(ystr, date_str + 6, 2);
@@ -94,49 +127,64 @@
/** Constructor for GncCsvParseData.
* @return Pointer to a new GncCSvParseData
*/
-/* TODO Comment */
GncCsvParseData* gnc_csv_new_parse_data(void)
{
GncCsvParseData* parse_data = g_malloc(sizeof(GncCsvParseData));
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->column_types = NULL;
parse_data->error_lines = parse_data->transactions = NULL;
parse_data->options = default_parse_options();
+ parse_data->date_format = -1;
return parse_data;
}
/** Destructor for GncCsvParseData.
* @param parse_data Parse data whose memory will be freed
*/
-/* TODO Comment */
void gnc_csv_parse_data_free(GncCsvParseData* parse_data)
{
+ /* All non-NULL pointers have been initialized and must be freed. */
+
+ /* parse_data->raw_str is created using mmap (see file_to_string),
+ * so we free it using munmap. */
if(parse_data->raw_str.begin != NULL)
munmap(parse_data->raw_str.begin,
parse_data->raw_str.end - parse_data->raw_str.begin);
+
if(parse_data->file_str.begin != NULL)
g_free(parse_data->file_str.begin);
+
if(parse_data->orig_lines != NULL)
g_ptr_array_free(parse_data->orig_lines, TRUE);
+
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);
- /* TODO Find out if there's a potential memory leak here. */
+
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);
}
@@ -145,25 +193,32 @@
* 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
*/
-/* TODO Comment */
-int gnc_csv_convert_encoding(GncCsvParseData* parse_data, const char* encoding)
+int gnc_csv_convert_encoding(GncCsvParseData* parse_data, const char* encoding,
+ GError** error)
{
- 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);
+ "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;
@@ -181,26 +236,37 @@
* @param error Will contain an error if there is a failure
* @return 0 on success, 1 on failure
*/
-/* TODO Comment */
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_str = file_to_string(filename, error);
if(parse_data->raw_str.begin == NULL)
{
+ /* TODO Handle file opening errors more specifically,
+ * e.g. inexistent file versus no read permission. */
g_set_error(error, 0, GNC_CSV_FILE_OPEN_ERR, "File opening failed.");
return 1;
}
+
+ /* 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);
- g_debug("Guessed %s\n", guess_enc);
- /* TODO Handle error */
- gnc_csv_convert_encoding(parse_data, guess_enc);
+ 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, "Encoding conversion failed.");
+ g_set_error(error, 0, GNC_CSV_ENCODING_ERR, "Unknown encoding.");
return 1;
}
else
@@ -210,18 +276,20 @@
/** 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: if guessColTypes is TRUE, all the column types
- * will be GNC_CSV_NONE right now.)
+ * 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
*/
-/* TODO Comment. */
/* TODO Should we use 0 for domain and code in errors? */
int gnc_csv_parse(GncCsvParseData* parse_data, gboolean guessColTypes, GError** error)
{
GStringChunk* chunk; /* TODO Find out exactly what this is. */
+ /* max_cols is the number of columns in the row with the most columns. */
int i, max_cols = 0;
/* Do the actual parsing. */
@@ -232,13 +300,14 @@
parse_data->file_str.begin,
parse_data->file_str.end);
g_string_chunk_free(chunk);
- if(parse_data->orig_lines == NULL) /* If it failed, generate an error message. */
+ /* 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 update max_cols. */
+ /* 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)
@@ -248,11 +317,16 @@
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;
@@ -260,6 +334,11 @@
}
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++)
@@ -278,54 +357,95 @@
* 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
*/
-/* TODO Comment. */
-int gnc_parse_to_trans(GncCsvParseData* parse_data, Account* account)
+int gnc_parse_to_trans(GncCsvParseData* parse_data, Account* account,
+ gboolean redo_errors)
{
int i, j;
GArray* column_types = parse_data->column_types;
GNCBook* book = gnc_account_get_book(account);
+ GList *error_lines, *begin_error_lines;
+ gnc_commodity* currency = xaccAccountGetCommodity(account);
- if(parse_data->error_lines != 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);
- parse_data->error_lines = NULL;
}
+ parse_data->error_lines = NULL;
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);
parse_data->transactions = NULL;
}
-
- for(i = 0; i < parse_data->orig_lines->len; i++)
+
+ if(redo_errors) /* If we're looking only at error data ... */
{
+ /* ... 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;
+ }
+ while(i < parse_data->orig_lines->len)
+ {
Transaction* trans = xaccMallocTransaction(book);
+ GPtrArray* line = parse_data->orig_lines->pdata[i];
+ Split* split;
+ /* This flag is FALSE if there are any errors in this row. */
+ /* TODO This flag isn't used yet. */
gboolean noErrors = TRUE;
- GPtrArray* line = parse_data->orig_lines->pdata[i];
+
+ /* By the time we traverse the column, essential_properties_left
+ * should be 0; if it isn't the row is considered to have an
+ * error. Each time an "essential" property is set, we subtract 1
+ * from it. At the moment, the only essential properties are
+ * "Date" and "Amount". */
+ unsigned int essential_properties_left = 2;
+
+ /* The data types that are used to create transactions */
time_t date;
const char* description;
gnc_numeric amount;
- Split* split;
+
xaccTransBeginEdit(trans);
- for(j = column_types->len - 1; j >= 0; j--)
+ xaccTransSetCurrency(trans, currency);
+
+ /* TODO There should eventually be a specification of what errors
+ * actually occurred, not just that one happened. */
+
+ for(j = 0; j < line->len; j++)
{
+ /* We do nothing in "None" columns. */
if(column_types->data[j] != GNC_CSV_NONE)
{
- /* If this line is too short, it goes in errors list. */
- if(j >= line->len)
- {
- parse_data->error_lines = g_list_append(parse_data->error_lines,
- line);
- xaccTransDestroy(trans);
- noErrors = FALSE;
- break;
- }
/* Affect the transaction appropriately. */
if(column_types->data[j] == GNC_CSV_DATE)
{
- date = parse_date(line->pdata[j]);
+ date = parse_date(line->pdata[j], parse_data->date_format);
xaccTransSetDatePostedSecs(trans, date);
+ essential_properties_left--;
}
else if(column_types->data[j] == GNC_CSV_DESCRIPTION)
{
@@ -342,16 +462,45 @@
xaccSplitSetAmount(split, amount);
xaccSplitSetValue(split, amount);
xaccSplitSetAction(split, "Deposit");
+ essential_properties_left--;
}
}
}
- if(noErrors)
+
+ /* If we had success, add the transaction to parse_data->transaction. */
+ if(noErrors && essential_properties_left == 0)
{
GncCsvTransLine* trans_line = g_malloc(sizeof(GncCsvTransLine));
trans_line->line_no = i;
trans_line->trans = trans;
parse_data->transactions = g_list_append(parse_data->transactions, trans_line);
}
+ /* If there was no success, add this line to
+ * parse_data->error_lines and free the transaction. */
+ else
+ {
+ parse_data->error_lines = g_list_append(parse_data->error_lines,
+ GINT_TO_POINTER(i));
+ xaccTransDestroy(trans);
+ }
+
+ if(redo_errors)
+ {
+ /* Move to the next error line. */
+ 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(redo_errors)
+ {
+ g_list_free(begin_error_lines);
+ }
return 0;
}
Modified: gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.h
===================================================================
--- gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.h 2007-07-02 20:39:56 UTC (rev 16247)
+++ gnucash/branches/csv-import/src/import-export/csv/gnc-csv-model.h 2007-07-04 10:42:57 UTC (rev 16248)
@@ -72,17 +72,25 @@
Transaction* trans;
} GncCsvTransLine;
+extern const int num_date_formats;
+/* A set of date formats that the user sees. */
+extern const gchar* date_format_user[];
+
+/* Matching formats for date_format_user to be used with strptime. */
+extern const gchar* date_format_internal[];
+
/** Struct containing data for parsing a CSV/Fixed-Width file. */
typedef struct
{
gchar* encoding;
- 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 */
- 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 pointers to rows in orig_lines that have errors */
- GList* transactions; /**> List of GncCsvTransLine*s created using orig_lines and column_types */
+ 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 */
+ 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);
@@ -92,11 +100,11 @@
int gnc_csv_load_file(GncCsvParseData* parse_data, const char* filename,
GError** error);
-int gnc_csv_convert_encoding(GncCsvParseData* parse_data, const char* encoding);
+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_parse_to_trans(GncCsvParseData* parse_data, Account* account);
+int gnc_parse_to_trans(GncCsvParseData* parse_data, Account* account, gboolean redo_errors);
GncCsvStr file_to_string(const char* filename, GError** error);
Modified: gnucash/branches/csv-import/src/import-export/csv/gnc-csv-preview-dialog.glade
===================================================================
--- gnucash/branches/csv-import/src/import-export/csv/gnc-csv-preview-dialog.glade 2007-07-02 20:39:56 UTC (rev 16247)
+++ gnucash/branches/csv-import/src/import-export/csv/gnc-csv-preview-dialog.glade 2007-07-04 10:42:57 UTC (rev 16248)
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.2.0 on Fri Jun 22 02:03:24 2007 by lasindi at pi-->
+<!--Generated with glade3 3.2.0 on Tue Jul 3 04:32:16 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>
@@ -329,27 +329,88 @@
</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">4</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">5</property>
+ <property name="position">5</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="position">4</property>
+ <property name="position">6</property>
</packing>
</child>
<child>
- <widget class="GtkTreeView" id="treeview">
+ <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="headers_clickable">True</property>
- <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_BOTH</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</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">5</property>
+ <property name="position">7</property>
</packing>
</child>
</widget>
More information about the gnucash-changes
mailing list