GnuCash  5.6-150-g038405b370+
csv-account-import.c
1 /*******************************************************************\
2  * csv-account-import.c -- Account importing from file *
3  * *
4  * Copyright (C) 2012 Robert Fewell *
5  * *
6  * Based on code from bi_import written by Sebastian Held and *
7  * Mike Evans. *
8  * *
9  * This program is free software; you can redistribute it and/or *
10  * modify it under the terms of the GNU General Public License as *
11  * published by the Free Software Foundation; either version 2 of *
12  * the License, or (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License*
20  * along with this program; if not, contact: *
21  * *
22  * Free Software Foundation Voice: +1-617-542-5942 *
23  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
24  * Boston, MA 02110-1301, USA gnu@gnu.org *
25 \********************************************************************/
26 
27 #include <config.h>
28 
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <glib/gstdio.h>
32 
33 #include "gnc-ui-util.h"
34 #include <regex.h>
35 #include "Account.h"
36 #include "gnc-component-manager.h"
37 #include "csv-account-import.h"
38 
39 /* This static indicates the debugging module that this .o belongs to. */
40 static QofLogModule log_module = GNC_MOD_ASSISTANT;
41 
42 /* This helper function takes a regexp match and fills the model */
43 static void
44 fill_model_with_match(GMatchInfo *match_info,
45  const gchar *match_name,
46  GtkListStore *store,
47  GtkTreeIter *iterptr,
48  gint column)
49 {
50  gchar *temp;
51 
52  if (!match_info || !match_name)
53  return;
54 
55  temp = g_match_info_fetch_named (match_info, match_name);
56  if (temp)
57  {
58  g_strstrip (temp);
59  if (g_str_has_prefix (temp, "\""))
60  {
61  if (strlen (temp) >= 2)
62  {
63  gchar *toptail = g_strndup (temp + 1, strlen (temp)-2);
64  gchar **parts = g_strsplit (toptail, "\"\"", -1);
65  temp = g_strjoinv ("\"", parts);
66  g_strfreev (parts);
67  g_free (toptail);
68  }
69  }
70  gtk_list_store_set (store, iterptr, column, temp, -1);
71  g_free (temp);
72  }
73 }
74 
75 /*******************************************************
76  * csv_import_read_file
77  *
78  * Parse the file for a correctly formatted file
79  *******************************************************/
80 csv_import_result
81 csv_import_read_file (GtkWindow *window, const gchar *filename,
82  const gchar *parser_regexp,
83  GtkListStore *store, guint max_rows)
84 {
85  gchar *locale_cont, *contents;
86  GMatchInfo *match_info = NULL;
87  GRegex *regexpat = NULL;
88  GError *err;
89  gint row = 0;
90  gboolean match_found = FALSE;
91 
92  // model
93  GtkTreeIter iter;
94 
95  if (!g_file_get_contents (filename, &locale_cont, NULL, NULL))
96  {
97  //gnc_error_dialog (NULL, _("File %s cannot be opened."), filename );
98  return RESULT_OPEN_FAILED;
99  }
100 
101  contents = g_locale_to_utf8 (locale_cont, -1, NULL, NULL, NULL);
102  g_free (locale_cont);
103 
104  // compile the regular expression and check for errors
105  err = NULL;
106  regexpat =
107  g_regex_new (parser_regexp, G_REGEX_OPTIMIZE, 0, &err);
108  if (err != NULL)
109  {
110  GtkWidget *dialog;
111  gchar *errmsg;
112 
113  errmsg = g_strdup_printf (_("Error in regular expression '%s':\n%s"),
114  parser_regexp, err->message);
115  g_error_free (err);
116 
117  dialog = gtk_message_dialog_new (window,
118  GTK_DIALOG_MODAL,
119  GTK_MESSAGE_ERROR,
120  GTK_BUTTONS_OK, "%s", errmsg);
121  gtk_dialog_run (GTK_DIALOG (dialog));
122  gtk_widget_destroy (dialog);
123  g_free (errmsg);
124  g_free (contents);
125 
126  return RESULT_ERROR_IN_REGEXP;
127  }
128 
129  g_regex_match (regexpat, contents, 0, &match_info);
130  while (g_match_info_matches (match_info))
131  {
132  // fill in the values, pattern match names must match those defined in
133  // regular expression
134  gtk_list_store_append (store, &iter);
135  fill_model_with_match (match_info, "type", store, &iter, TYPE);
136  fill_model_with_match (match_info, "full_name", store, &iter, FULL_NAME);
137  fill_model_with_match (match_info, "name", store, &iter, NAME);
138  fill_model_with_match (match_info, "code", store, &iter, CODE);
139  fill_model_with_match (match_info, "description", store, &iter, DESCRIPTION);
140  fill_model_with_match (match_info, "color", store, &iter, COLOR);
141  fill_model_with_match (match_info, "notes", store, &iter, NOTES);
142  fill_model_with_match (match_info, "symbol", store, &iter, SYMBOL);
143  fill_model_with_match (match_info, "namespace", store, &iter, NAMESPACE);
144  fill_model_with_match (match_info, "hidden", store, &iter, HIDDEN);
145  fill_model_with_match (match_info, "tax", store, &iter, TAX);
146  fill_model_with_match (match_info, "placeholder", store, &iter, PLACE_HOLDER);
147  gtk_list_store_set (store, &iter, ROW_COLOR, NULL, -1);
148 
149  if (row == 0)
150  {
151  gchar *str_type;
152  gtk_tree_model_get (GTK_TREE_MODEL(store), &iter, TYPE, &str_type, -1);
153 
154  if (g_strcmp0 (_("Type"), str_type) == 0)
155  match_found = TRUE;
156  g_free (str_type);
157  }
158 
159  row++;
160  if (row == max_rows)
161  break;
162  g_match_info_next (match_info, &err);
163  }
164 
165  g_match_info_free (match_info);
166  g_regex_unref (regexpat);
167  g_free (contents);
168 
169  if (err != NULL)
170  {
171  g_printerr ("Error while matching: %s\n", err->message);
172  g_error_free (err);
173  }
174 
175  if (match_found == TRUE)
176  return MATCH_FOUND;
177  else
178  return RESULT_OK;
179 }
180 
181 
182 /*******************************************************
183  * csv_account_import
184  *
185  * Parse the liststore for account updates
186  *******************************************************/
187 void
188 csv_account_import (CsvImportInfo *info)
189 {
190  QofBook *book;
191  Account *acc, *parent, *root;
192  gboolean valid;
193  GdkRGBA testcolor;
194  GtkTreeIter iter;
195  gchar *type, *full_name, *name, *code, *description, *color;
196  gchar *notes, *symbol, *namespace, *hidden, *tax, *place_holder;
197  int row;
198 
199  ENTER("");
200  book = gnc_get_current_book();
201  root = gnc_book_get_root_account (book);
202 
203  info->num_new = 0;
204  info->num_updates = 0;
205 
206  /* Move to the first valid entry in store */
207  row = info->header_rows;
208  valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(info->store), &iter, NULL, row );
209  while (valid)
210  {
211  /* Walk through the list, reading each row */
212  gtk_tree_model_get (GTK_TREE_MODEL (info->store), &iter,
213  TYPE, &type,
214  FULL_NAME, &full_name,
215  NAME, &name,
216  CODE, &code,
217  DESCRIPTION, &description,
218  COLOR, &color,
219  NOTES, &notes,
220  SYMBOL, &symbol,
221  NAMESPACE, &namespace,
222  HIDDEN, &hidden,
223  TAX, &tax,
224  PLACE_HOLDER, &place_holder, -1);
225 
226  /* See if we can find the account by full name */
227  acc = gnc_account_lookup_by_full_name (root, full_name);
228 
229  DEBUG("Row is %u and full name is %s", row, full_name);
230  if (acc == NULL)
231  {
232  /* Account does not exist, Lets try and add it */
233  if (g_strrstr (full_name, name) != NULL)
234  {
235  gint string_position;
236  gnc_commodity *commodity;
237  gnc_commodity_table *table;
238  gchar *full_parent;
239 
240  /* Get full name of parent account, allow for separator */
241  string_position = strlen (full_name) - strlen (name) - 1;
242 
243  if (string_position == -1)
244  full_parent = g_strdup (full_name);
245  else
246  full_parent = g_strndup (full_name, string_position);
247 
248  parent = gnc_account_lookup_by_full_name (root, full_parent);
249  g_free (full_parent);
250 
251  if (parent == NULL && string_position != -1)
252  {
253  gchar *text = g_strdup_printf (gettext("Row %u, path to account %s not found, added as top level\n"), row + 1, name);
254  info->error = g_strconcat (info->error, text, NULL);
255  g_free (text);
256  PINFO("Unable to import Row %u for account %s, path not found!", row, name);
257  }
258 
259  if (parent == NULL)
260  parent = root;
261 
262  /* Do we have a valid commodity */
264  commodity = gnc_commodity_table_lookup (table, namespace, symbol);
265 
266  if (commodity)
267  {
268  DEBUG("We have a valid commodity and will add account %s", full_name);
269  info->num_new = info->num_new + 1;
270  gnc_suspend_gui_refresh ();
271  acc = xaccMallocAccount (book);
272  xaccAccountBeginEdit (acc);
273  xaccAccountSetName (acc, name);
275 
276  if (g_strcmp0 (notes, "") != 0)
277  xaccAccountSetNotes (acc, notes);
278  if (g_strcmp0 (description, "") != 0)
279  xaccAccountSetDescription (acc, description);
280  if (g_strcmp0 (code, "") != 0)
281  xaccAccountSetCode (acc, code);
282 
283  if (g_strcmp0 (color, "") != 0)
284  {
285  if (gdk_rgba_parse (&testcolor, color))
286  xaccAccountSetColor (acc, color);
287  else
288  xaccAccountSetColor (acc, "");
289  }
290 
291  if (g_strcmp0 (hidden, "T") == 0)
292  xaccAccountSetHidden (acc, TRUE);
293  if (g_strcmp0 (place_holder, "T") == 0)
294  xaccAccountSetPlaceholder (acc, TRUE);
295 
296  xaccAccountSetCommodity (acc, commodity);
297  xaccAccountBeginEdit (parent);
298  gnc_account_append_child (parent, acc);
299  xaccAccountCommitEdit (parent);
300  xaccAccountCommitEdit (acc);
301  gnc_resume_gui_refresh ();
302  }
303  else
304  {
305  gchar *err_string = g_strdup_printf (gettext("Row %u, commodity %s / %s not found\n"), row + 1,
306  symbol, namespace);
307  info->error = g_strconcat (info->error, err_string, NULL);
308  g_free (err_string);
309  PINFO("Unable to import Row %u for account %s, commodity!", row, full_name);
310  }
311  }
312  else
313  {
314  gchar *err_string = g_strdup_printf (gettext("Row %u, account %s not in %s\n"), row + 1, name, full_name);
315  info->error = g_strconcat (info->error, err_string, NULL);
316  g_free (err_string);
317  PINFO("Unable to import Row %u for account %s, name!", row, full_name);
318  }
319  }
320  else
321  {
322  /* Lets try and update the color, notes, description, code entries */
323  DEBUG("Existing account, will try and update account %s", full_name);
324  info->num_updates = info->num_updates + 1;
325  if (g_strcmp0 (color, "") != 0)
326  {
327  if (gdk_rgba_parse (&testcolor, color))
328  xaccAccountSetColor (acc, color);
329  else
330  xaccAccountSetColor (acc, "");
331  }
332 
333  if (g_strcmp0 (notes, "") != 0)
334  xaccAccountSetNotes (acc, notes);
335 
336  if (g_strcmp0 (description, "") != 0)
337  xaccAccountSetDescription (acc, description);
338 
339  if (g_strcmp0 (code, "") != 0)
340  xaccAccountSetCode (acc, code);
341  }
342  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (info->store), &iter);
343  row++;
344 
345  /* free resources */
346  g_free (type);
347  g_free (full_name);
348  g_free (name);
349  g_free (code);
350  g_free (description);
351  g_free (color);
352  g_free (notes);
353  g_free (symbol);
354  g_free (namespace);
355  g_free (hidden);
356  g_free (tax);
357  g_free (place_holder);
358  }
359  LEAVE("");
360 }
void xaccAccountSetType(Account *acc, GNCAccountType tip)
Set the account&#39;s type.
Definition: Account.cpp:2422
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship, and will then add the account as a child of the new parent.
Definition: Account.cpp:2807
void xaccAccountSetNotes(Account *acc, const char *str)
Set the account&#39;s notes.
Definition: Account.cpp:2633
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
STRUCTS.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
void xaccAccountSetCode(Account *acc, const char *str)
Set the account&#39;s accounting code.
Definition: Account.cpp:2463
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
GNCAccountType xaccAccountStringToEnum(const char *str)
Conversion routines for the account types to/from strings that are used in persistent storage...
Definition: Account.cpp:4273
Account handling public routines.
void xaccAccountSetPlaceholder(Account *acc, gboolean val)
Set the "placeholder" flag for an account.
Definition: Account.cpp:4080
void xaccAccountSetColor(Account *acc, const char *str)
Set the account&#39;s Color.
Definition: Account.cpp:2591
Account * gnc_account_lookup_by_full_name(const Account *any_acc, const gchar *name)
The gnc_account_lookup_full_name() subroutine works like gnc_account_lookup_by_name, but uses fully-qualified names using the given separator.
Definition: Account.cpp:3133
void xaccAccountSetHidden(Account *acc, gboolean val)
Set the "hidden" flag for an account.
Definition: Account.cpp:4151
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1477
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition: Account.cpp:1273
void xaccAccountSetDescription(Account *acc, const char *str)
Set the account&#39;s description.
Definition: Account.cpp:2482
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1518
void xaccAccountSetName(Account *acc, const char *str)
Set the account&#39;s name.
Definition: Account.cpp:2443
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account&#39;s commodity.
Definition: Account.cpp:2649