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