GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-csv-model.c
1 #include "gnc-csv-model.h"
2 
3 
4 #include <glib/gi18n.h>
5 
6 #include <goffice/goffice-features.h>
7 #if (GO_VERSION_EPOCH == 0) && (GO_VERSION_MAJOR == 7) && (GO_VERSION_MINOR == 8)
8 /* For libgoffice-0.7.8, disable its internal inclusion of <regutf8.h>
9  so to avoid clashing symbol definitions with <regex.h> */
10 # define GO_REGUTF8_H
11 #endif
12 #include <goffice/utils/go-glib-extras.h>
13 
14 #include "gnc-ui-util.h"
15 #include "engine-helpers.h"
16 
17 #include <string.h>
18 
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <regex.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <stdlib.h>
25 #include <math.h>
26 
27 G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_IMPORT;
28 
29 const int num_date_formats = 5;
30 const gchar* date_format_user[] = {N_("y-m-d"),
31  N_("d-m-y"),
32  N_("m-d-y"),
33  N_("d-m"),
34  N_("m-d")
35  };
36 
37 const int num_currency_formats = 3;
38 const gchar* currency_format_user[] = {N_("Locale"),
39  N_("Period: 123,456.78"),
40  N_("Comma: 123.456,78")
41  };
42 
43 /* This array contains all of the different strings for different column types. */
44 gchar* gnc_csv_column_type_strs[GNC_CSV_NUM_COL_TYPES] = {N_("None"),
45  N_("Date"),
46  N_("Num"),
47  N_("Description"),
48  N_("Notes"),
49  N_("Account"),
50  N_("Deposit"),
51  N_("Withdrawal"),
52  N_("Balance")
53  };
54 
58 static StfParseOptions_t* default_parse_options (void)
59 {
60  StfParseOptions_t* options = stf_parse_options_new();
61  stf_parse_options_set_type (options, PARSE_TYPE_CSV);
62  stf_parse_options_csv_set_separators (options, ",", NULL);
63  return options;
64 }
65 
73 static time64 parse_date_with_year (const char* date_str, int format)
74 {
75  time64 rawtime; /* The integer time */
76  struct tm retvalue, test_retvalue; /* The time in a broken-down structure */
77 
78  int i, j, mem_length, orig_year = -1, orig_month = -1, orig_day = -1;
79 
80  /* Buffer for containing individual parts (e.g. year, month, day) of a date */
81  char date_segment[5];
82 
83  /* The compiled regular expression */
84  regex_t preg = {0};
85 
86  /* An array containing indices specifying the matched substrings in date_str */
87  regmatch_t pmatch[4] = { {0}, {0}, {0}, {0} };
88 
89  /* The regular expression for parsing dates */
90  const char* regex = "^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$";
91 
92  /* We get our matches using the regular expression. */
93  regcomp (&preg, regex, REG_EXTENDED);
94  regexec (&preg, date_str, 4, pmatch, 0);
95  regfree (&preg);
96 
97  /* If there wasn't a match, there was an error. */
98  if (pmatch[0].rm_eo == 0)
99  return -1;
100 
101  /* If this is a string without separators ... */
102  if (pmatch[1].rm_so == -1)
103  {
104  /* ... we will fill in the indices based on the user's selection. */
105  int k = 0; /* k traverses date_str by keeping track of where separators "should" be. */
106  j = 1; /* j traverses pmatch. */
107  for (i = 0; date_format_user[format][i]; i++)
108  {
109  char segment_type = date_format_user[format][i];
110  /* Only do something if this is a meaningful character */
111  if (segment_type == 'y' || segment_type == 'm' || segment_type == 'd')
112  {
113  pmatch[j].rm_so = k;
114  switch (segment_type)
115  {
116  case 'm':
117  case 'd':
118  k += 2;
119  break;
120 
121  case 'y':
122  k += 4;
123  break;
124  }
125 
126  pmatch[j].rm_eo = k;
127  j++;
128  }
129  }
130  }
131 
132  /* Put some sane values in retvalue by using the current time for
133  * the non-year-month-day parts of the date. */
134  gnc_time (&rawtime);
135  gnc_localtime_r (&rawtime, &retvalue);
136 
137  /* j traverses pmatch (index 0 contains the entire string, so we
138  * start at index 1 for the first meaningful match). */
139  j = 1;
140  /* Go through the date format and interpret the matches in order of
141  * the sections in the date format. */
142  for (i = 0; date_format_user[format][i]; i++)
143  {
144  char segment_type = date_format_user[format][i];
145  /* Only do something if this is a meaningful character */
146  if (segment_type == 'y' || segment_type == 'm' || segment_type == 'd')
147  {
148  /* Copy the matching substring into date_segment so that we can
149  * convert it into an integer. */
150  mem_length = pmatch[j].rm_eo - pmatch[j].rm_so;
151  memcpy (date_segment, date_str + pmatch[j].rm_so, mem_length);
152  date_segment[mem_length] = '\0';
153 
154  /* Set the appropriate member of retvalue. Save the original
155  * values so that we can check if the change when we use gnc_mktime
156  * below. */
157  switch (segment_type)
158  {
159  case 'y':
160  retvalue.tm_year = atoi (date_segment);
161 
162  /* Handle two-digit years. */
163  if (retvalue.tm_year < 100)
164  {
165  /* We allow two-digit years in the range 1969 - 2068. */
166  if (retvalue.tm_year < 69)
167  retvalue.tm_year += 100;
168  }
169  else
170  retvalue.tm_year -= 1900;
171  orig_year = retvalue.tm_year;
172  break;
173 
174  case 'm':
175  orig_month = retvalue.tm_mon = atoi (date_segment) - 1;
176  break;
177 
178  case 'd':
179  orig_day = retvalue.tm_mday = atoi (date_segment);
180  break;
181  }
182  j++;
183  }
184  }
185  /* Convert back to an integer. If gnc_mktime leaves retvalue unchanged,
186  * everything is okay; otherwise, an error has occurred. */
187  /* We have to use a "test" date value to account for changes in
188  * daylight savings time, which can cause a date change with gnc_mktime
189  * near midnight, causing the code to incorrectly think a date is
190  * incorrect. */
191  test_retvalue = retvalue;
192  gnc_mktime (&test_retvalue);
193  retvalue.tm_isdst = test_retvalue.tm_isdst;
194  rawtime = gnc_mktime (&retvalue);
195  if (retvalue.tm_mday == orig_day &&
196  retvalue.tm_mon == orig_month &&
197  retvalue.tm_year == orig_year)
198  {
199  return rawtime;
200  }
201  else
202  {
203  return -1;
204  }
205 }
206 
214 static time64 parse_date_without_year (const char* date_str, int format)
215 {
216  time64 rawtime; /* The integer time */
217  struct tm retvalue, test_retvalue; /* The time in a broken-down structure */
218 
219  int i, j, mem_length, orig_year = -1, orig_month = -1, orig_day = -1;
220 
221  /* Buffer for containing individual parts (e.g. year, month, day) of a date */
222  gchar* date_segment;
223 
224  /* The compiled regular expression */
225  regex_t preg = {0};
226 
227  /* An array containing indices specifying the matched substrings in date_str */
228  regmatch_t pmatch[3] = { {0}, {0}, {0} };
229 
230  /* The regular expression for parsing dates */
231  const char* regex = "^ *([0-9]+) *[-/.'] *([0-9]+).*$";
232 
233  /* We get our matches using the regular expression. */
234  regcomp (&preg, regex, REG_EXTENDED);
235  regexec (&preg, date_str, 3, pmatch, 0);
236  regfree (&preg);
237 
238  /* If there wasn't a match, there was an error. */
239  if (pmatch[0].rm_eo == 0)
240  return -1;
241 
242  /* Put some sane values in retvalue by using the current time for
243  * the non-year-month-day parts of the date. */
244  gnc_time (&rawtime);
245  gnc_localtime_r (&rawtime, &retvalue);
246  orig_year = retvalue.tm_year;
247 
248  /* j traverses pmatch (index 0 contains the entire string, so we
249  * start at index 1 for the first meaningful match). */
250  j = 1;
251  /* Go through the date format and interpret the matches in order of
252  * the sections in the date format. */
253  for (i = 0; date_format_user[format][i]; i++)
254  {
255  char segment_type = date_format_user[format][i];
256  /* Only do something if this is a meaningful character */
257  if (segment_type == 'm' || segment_type == 'd')
258  {
259  /* Copy the matching substring into date_segment so that we can
260  * convert it into an integer. */
261  mem_length = pmatch[j].rm_eo - pmatch[j].rm_so;
262  date_segment = g_new (gchar, mem_length);
263  memcpy(date_segment, date_str + pmatch[j].rm_so, mem_length);
264  date_segment[mem_length] = '\0';
265 
266  /* Set the appropriate member of retvalue. Save the original
267  * values so that we can check if the change when we use gnc_mktime
268  * below. */
269  switch (segment_type)
270  {
271  case 'm':
272  orig_month = retvalue.tm_mon = atoi (date_segment) - 1;
273  break;
274 
275  case 'd':
276  orig_day = retvalue.tm_mday = atoi (date_segment);
277  break;
278  }
279  g_free (date_segment);
280  j++;
281  }
282  }
283  /* Convert back to an integer. If gnc_mktime leaves retvalue unchanged,
284  * everything is okay; otherwise, an error has occurred. */
285  /* We have to use a "test" date value to account for changes in
286  * daylight savings time, which can cause a date change with gnc_mktime
287  * near midnight, causing the code to incorrectly think a date is
288  * incorrect. */
289  test_retvalue = retvalue;
290  gnc_mktime (&test_retvalue);
291  retvalue.tm_isdst = test_retvalue.tm_isdst;
292  rawtime = gnc_mktime (&retvalue);
293  if (retvalue.tm_mday == orig_day &&
294  retvalue.tm_mon == orig_month &&
295  retvalue.tm_year == orig_year)
296  {
297  return rawtime;
298  }
299  else
300  {
301  return -1;
302  }
303 }
304 
313 time64 parse_date (const char* date_str, int format)
314 {
315  if (strchr (date_format_user[format], 'y'))
316  return parse_date_with_year (date_str, format);
317  else
318  return parse_date_without_year (date_str, format);
319 }
320 
325 {
326  GncCsvParseData* parse_data = g_new(GncCsvParseData, 1);
327  parse_data->encoding = "UTF-8";
328  /* All of the data pointers are initially NULL. This is so that, if
329  * gnc_csv_parse_data_free is called before all of the data is
330  * initialized, only the data that needs to be freed is freed. */
331  parse_data->raw_str.begin = parse_data->raw_str.end
332  = parse_data->file_str.begin = parse_data->file_str.end = NULL;
333  parse_data->orig_lines = NULL;
334  parse_data->orig_row_lengths = NULL;
335  parse_data->column_types = NULL;
336  parse_data->error_lines = parse_data->transactions = NULL;
337  parse_data->options = default_parse_options();
338  parse_data->date_format = -1;
339  parse_data->currency_format = 0;
340  parse_data->chunk = g_string_chunk_new(100 * 1024);
341  parse_data->start_row = 0;
342  parse_data->end_row = 1000;
343  parse_data->skip_rows = FALSE;
344  return parse_data;
345 }
346 
351 {
352  /* All non-NULL pointers have been initialized and must be freed. */
353 
354  if (parse_data->raw_mapping != NULL)
355  {
356  g_mapped_file_unref (parse_data->raw_mapping);
357  }
358 
359  if (parse_data->file_str.begin != NULL)
360  g_free (parse_data->file_str.begin);
361 
362  if (parse_data->orig_lines != NULL)
363  stf_parse_general_free (parse_data->orig_lines);
364 
365  if (parse_data->orig_row_lengths != NULL)
366  g_array_free (parse_data->orig_row_lengths, FALSE);
367 
368  if (parse_data->options != NULL)
369  stf_parse_options_free (parse_data->options);
370 
371  if (parse_data->column_types != NULL)
372  g_array_free (parse_data->column_types, TRUE);
373 
374  if (parse_data->error_lines != NULL)
375  g_list_free (parse_data->error_lines);
376 
377  if (parse_data->transactions != NULL)
378  {
379  GList* transactions = parse_data->transactions;
380  /* We have to free the GncCsvTransLine's that are at each node in
381  * the list before freeing the entire list. */
382  do
383  {
384  g_free (transactions->data);
385  transactions = g_list_next (transactions);
386  }
387  while (transactions != NULL);
388  g_list_free (parse_data->transactions);
389  }
390 
391  g_free (parse_data->chunk);
392  g_free (parse_data);
393 }
394 
403 int gnc_csv_convert_encoding (GncCsvParseData* parse_data, const char* encoding,
404  GError** error)
405 {
406  gsize bytes_read, bytes_written;
407 
408  /* If parse_data->file_str has already been initialized it must be
409  * freed first. (This should always be the case, since
410  * gnc_csv_load_file should always be called before this
411  * function.) */
412  if (parse_data->file_str.begin != NULL)
413  g_free(parse_data->file_str.begin);
414 
415  /* Do the actual translation to UTF-8. */
416  parse_data->file_str.begin = g_convert (parse_data->raw_str.begin,
417  parse_data->raw_str.end - parse_data->raw_str.begin,
418  "UTF-8", encoding, &bytes_read, &bytes_written,
419  error);
420  /* Handle errors that occur. */
421  if (parse_data->file_str.begin == NULL)
422  return 1;
423 
424  /* On success, save the ending pointer of the translated data and
425  * the encoding type and return 0. */
426  parse_data->file_str.end = parse_data->file_str.begin + bytes_written;
427  parse_data->encoding = (gchar*)encoding;
428  return 0;
429 }
430 
443 int gnc_csv_load_file (GncCsvParseData* parse_data, const char* filename,
444  GError** error)
445 {
446  const char* guess_enc = NULL;
447 
448  /* Get the raw data first and handle an error if one occurs. */
449  parse_data->raw_mapping = g_mapped_file_new (filename, FALSE, error);
450  if (parse_data->raw_mapping == NULL)
451  {
452  /* TODO Handle file opening errors more specifically,
453  * e.g. inexistent file versus no read permission. */
454  parse_data->raw_str.begin = NULL;
455  g_clear_error (error);
456  g_set_error (error, 0, GNC_CSV_FILE_OPEN_ERR, "%s", _("File opening failed."));
457  return 1;
458  }
459 
460  /* Copy the mapping's contents into parse-data->raw_str. */
461  parse_data->raw_str.begin = g_mapped_file_get_contents (parse_data->raw_mapping);
462  parse_data->raw_str.end = parse_data->raw_str.begin + g_mapped_file_get_length (parse_data->raw_mapping);
463 
464  /* Make a guess at the encoding of the data. */
465  if (!g_mapped_file_get_length (parse_data->raw_mapping) == 0)
466  guess_enc = go_guess_encoding ((const char*)(parse_data->raw_str.begin),
467  (size_t)(parse_data->raw_str.end - parse_data->raw_str.begin),
468  "UTF-8", NULL);
469  if (guess_enc == NULL)
470  {
471  g_set_error (error, 0, GNC_CSV_ENCODING_ERR, "%s", _("Unknown encoding."));
472  return 1;
473  }
474  /* Convert using the guessed encoding into parse_data->file_str and
475  * handle any errors that occur. */
476  gnc_csv_convert_encoding (parse_data, guess_enc, error);
477  if (parse_data->file_str.begin == NULL)
478  {
479  g_set_error (error, 0, GNC_CSV_ENCODING_ERR, "%s", _("Unknown encoding."));
480  return 1;
481  }
482  else
483  return 0;
484 }
485 
498 int gnc_csv_parse (GncCsvParseData* parse_data, gboolean guessColTypes, GError** error)
499 {
500  /* max_cols is the number of columns in the row with the most columns. */
501  int i, max_cols = 0;
502 
503  if (parse_data->orig_lines != NULL)
504  {
505  stf_parse_general_free (parse_data->orig_lines);
506  }
507 
508  /* If everything is fine ... */
509  if (parse_data->file_str.begin != NULL)
510  {
511  /* Do the actual parsing. */
512  parse_data->orig_lines = stf_parse_general (parse_data->options, parse_data->chunk,
513  parse_data->file_str.begin,
514  parse_data->file_str.end);
515  }
516  /* If we couldn't get the encoding right, we just want an empty array. */
517  else
518  {
519  parse_data->orig_lines = g_ptr_array_new();
520  }
521 
522  /* Record the original row lengths of parse_data->orig_lines. */
523  if (parse_data->orig_row_lengths != NULL)
524  g_array_free (parse_data->orig_row_lengths, FALSE);
525 
526  parse_data->orig_row_lengths =
527  g_array_sized_new (FALSE, FALSE, sizeof(int), parse_data->orig_lines->len);
528 
529  g_array_set_size (parse_data->orig_row_lengths, parse_data->orig_lines->len);
530  parse_data->orig_max_row = 0;
531  for (i = 0; i < parse_data->orig_lines->len; i++)
532  {
533  int length = ((GPtrArray*)parse_data->orig_lines->pdata[i])->len;
534  parse_data->orig_row_lengths->data[i] = length;
535  if (length > parse_data->orig_max_row)
536  parse_data->orig_max_row = length;
537  }
538 
539  /* If it failed, generate an error. */
540  if (parse_data->orig_lines == NULL)
541  {
542  g_set_error (error, 0, 0, "Parsing failed.");
543  return 1;
544  }
545 
546  /* Now that we have data, let's set max_cols. */
547  for (i = 0; i < parse_data->orig_lines->len; i++)
548  {
549  if (max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len)
550  max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len;
551  }
552 
553  if (guessColTypes)
554  {
555  /* Free parse_data->column_types if it's already been created. */
556  if (parse_data->column_types != NULL)
557  g_array_free (parse_data->column_types, TRUE);
558 
559  /* Create parse_data->column_types and fill it with guesses based
560  * on the contents of each column. */
561  parse_data->column_types = g_array_sized_new (FALSE, FALSE, sizeof(int),
562  max_cols);
563  g_array_set_size (parse_data->column_types, max_cols);
564  /* TODO Make it actually guess. */
565  for (i = 0; i < parse_data->column_types->len; i++)
566  {
567  parse_data->column_types->data[i] = GNC_CSV_NONE;
568  }
569  }
570  else
571  {
572  /* If we don't need to guess column types, we will simply set any
573  * new columns that are created that didn't exist before to "None"
574  * since we don't want gibberish to appear. Note:
575  * parse_data->column_types should have already been
576  * initialized, so we don't check for it being NULL. */
577  int i = parse_data->column_types->len;
578  g_array_set_size (parse_data->column_types, max_cols);
579  for (; i < parse_data->column_types->len; i++)
580  {
581  parse_data->column_types->data[i] = GNC_CSV_NONE;
582  }
583  }
584 
585  return 0;
586 }
587 
589 typedef struct
590 {
594  GList* properties;
596 
598 typedef struct
599 {
600  int type;
602  void* value;
604 } TransProperty;
605 
609 static TransProperty* trans_property_new (int type, TransPropertyList* list)
610 {
611  TransProperty* prop = g_new (TransProperty, 1);
612  prop->type = type;
613  prop->list = list;
614  prop->value = NULL;
615  return prop;
616 }
617 
621 static void trans_property_free (TransProperty* prop)
622 {
623  switch (prop->type)
624  {
625  /* The types for "Date" and "Balance" (time64 and gnc_numeric,
626  * respectively) are typically not pointed to, we have to free
627  * them, unlike types like char* ("Description"). */
628  case GNC_CSV_DATE:
629  case GNC_CSV_BALANCE:
630  case GNC_CSV_DEPOSIT:
631  case GNC_CSV_WITHDRAWAL:
632  if (prop->value != NULL)
633  g_free(prop->value);
634  break;
635  }
636  g_free (prop);
637 }
638 
646 static gboolean trans_property_set (TransProperty* prop, char* str)
647 {
648  char *endptr, *possible_currency_symbol, *str_dupe;
649  gnc_numeric val;
650  int reti;
651  regex_t regex;
652  switch (prop->type)
653  {
654  case GNC_CSV_DATE:
655  prop->value = g_new(time64, 1);
656  *((time64*)(prop->value)) = parse_date(str, prop->list->date_format);
657  return *((time64*)(prop->value)) != -1;
658 
659  case GNC_CSV_DESCRIPTION:
660  case GNC_CSV_NOTES:
661  case GNC_CSV_NUM:
662  prop->value = g_strdup (str);
663  return TRUE;
664 
665  case GNC_CSV_BALANCE:
666  case GNC_CSV_DEPOSIT:
667  case GNC_CSV_WITHDRAWAL:
668  str_dupe = g_strdup (str); /* First, we make a copy so we can't mess up real data. */
669  /* If a cell is empty or just spaces make its value = "0" */
670  reti = regcomp(&regex, "[0-9]", 0);
671  reti = regexec(&regex, str_dupe, 0, NULL, 0);
672  if (reti == REG_NOMATCH)
673  {
674  g_free (str_dupe);
675  str_dupe = g_strdup ("0");
676  }
677  /* Go through str_dupe looking for currency symbols. */
678  for (possible_currency_symbol = str_dupe; *possible_currency_symbol;
679  possible_currency_symbol = g_utf8_next_char (possible_currency_symbol))
680  {
681  if (g_unichar_type (g_utf8_get_char (possible_currency_symbol)) == G_UNICODE_CURRENCY_SYMBOL)
682  {
683  /* If we find a currency symbol, save the position just ahead
684  * of the currency symbol (next_symbol), and find the null
685  * terminator of the string (last_symbol). */
686  char *next_symbol = g_utf8_next_char (possible_currency_symbol), *last_symbol = next_symbol;
687  while (*last_symbol)
688  last_symbol = g_utf8_next_char (last_symbol);
689 
690  /* Move all of the string (including the null byte, which is
691  * why we have +1 in the size parameter) following the
692  * currency symbol back one character, thereby overwriting the
693  * currency symbol. */
694  memmove (possible_currency_symbol, next_symbol, last_symbol - next_symbol + 1);
695  break;
696  }
697  }
698 
699  /* Currency format */
700  switch (prop->list->currency_format)
701  {
702  case 0:
703  /* Currancy locale */
704  if (!(xaccParseAmount (str_dupe, TRUE, &val, &endptr)))
705  {
706  g_free (str_dupe);
707  return FALSE;
708  }
709  break;
710  case 1:
711  /* Currancy decimal period */
712  if (!(xaccParseAmountExtended (str_dupe, TRUE, '-', '.', ',', "\003\003", "$+", &val, &endptr)))
713  {
714  g_free (str_dupe);
715  return FALSE;
716  }
717  break;
718  case 2:
719  /* Currancy decimal comma */
720  if (!(xaccParseAmountExtended (str_dupe, TRUE, '-', ',', '.', "\003\003", "$+", &val, &endptr)))
721  {
722  g_free (str_dupe);
723  return FALSE;
724  }
725  break;
726  }
727 
728  prop->value = g_new (gnc_numeric, 1);
729  *((gnc_numeric*)(prop->value)) = val;
730  g_free (str_dupe);
731  return TRUE;
732 
733  }
734  return FALSE; /* We should never actually get here. */
735 }
736 
742 static TransPropertyList* trans_property_list_new (Account* account, int date_format, int currency_format)
743 {
744  TransPropertyList* list = g_new (TransPropertyList, 1);
745  list->account = account;
746  list->date_format = date_format;
747  list->currency_format = currency_format;
748  list->properties = NULL;
749  return list;
750 }
751 
755 static void trans_property_list_free (TransPropertyList* list)
756 {
757  /* Free all of the properties in this list before freeeing the list itself. */
758  GList* properties_begin = list->properties;
759  while (list->properties != NULL)
760  {
761  trans_property_free ((TransProperty*)(list->properties->data));
762  list->properties = g_list_next (list->properties);
763  }
764  g_list_free (properties_begin);
765  g_free (list);
766 }
767 
773 static void trans_property_list_add (TransProperty* property)
774 {
775  property->list->properties = g_list_append (property->list->properties, property);
776 }
777 
784 static void trans_add_split (Transaction* trans, Account* account, QofBook* book,
785  gnc_numeric amount, const char *num)
786 {
787  Split* split = xaccMallocSplit (book);
788  xaccSplitSetAccount (split, account);
789  xaccSplitSetParent (split, trans);
790  xaccSplitSetAmount (split, amount);
791  xaccSplitSetValue (split, amount);
792  /* set tran-num and/or split-action per book option */
793  gnc_set_num_action (trans, split, num, NULL);
794 }
795 
803 static gboolean trans_property_list_verify_essentials (TransPropertyList* list, gchar** error)
804 {
805  int i;
806  /* possible_errors lists the ways in which a list can fail this test. */
807  enum PossibleErrorTypes {NO_DATE, NO_AMOUNT, NUM_OF_POSSIBLE_ERRORS};
808  gchar* possible_errors[NUM_OF_POSSIBLE_ERRORS] =
809  {
810  N_("No date column."),
811  N_("No balance, deposit, or withdrawal column.")
812  };
813  int possible_error_lengths[NUM_OF_POSSIBLE_ERRORS] = {0};
814  GList *properties_begin = list->properties, *errors_list = NULL;
815 
816  /* Go through each of the properties and erase possible errors. */
817  while (list->properties)
818  {
819  switch (((TransProperty*)(list->properties->data))->type)
820  {
821  case GNC_CSV_DATE:
822  possible_errors[NO_DATE] = NULL;
823  break;
824 
825  case GNC_CSV_BALANCE:
826  case GNC_CSV_DEPOSIT:
827  case GNC_CSV_WITHDRAWAL:
828  possible_errors[NO_AMOUNT] = NULL;
829  break;
830  }
831  list->properties = g_list_next (list->properties);
832  }
833  list->properties = properties_begin;
834 
835  /* Accumulate a list of the actual errors. */
836  for (i = 0; i < NUM_OF_POSSIBLE_ERRORS; i++)
837  {
838  if (possible_errors[i] != NULL)
839  {
840  errors_list = g_list_append (errors_list, GINT_TO_POINTER(i));
841  /* Since we added an error, we want to also store its length for
842  * when we construct the full error string. */
843  possible_error_lengths[i] = strlen (_(possible_errors[i]));
844  }
845  }
846 
847  /* If there are no errors, we can quit now. */
848  if (errors_list == NULL)
849  return TRUE;
850  else
851  {
852  /* full_error_size is the full length of the error message. */
853  int full_error_size = 0, string_length = 0;
854  GList* errors_list_begin = errors_list;
855  gchar *error_message, *error_message_begin;
856 
857  /* Find the value for full_error_size. */
858  while (errors_list)
859  {
860  /* We add an extra 1 to account for spaces in between messages. */
861  full_error_size += possible_error_lengths[GPOINTER_TO_INT(errors_list->data)] + 1;
862  errors_list = g_list_next (errors_list);
863  }
864  errors_list = errors_list_begin;
865 
866  /* Append the error messages one after another. */
867  error_message = error_message_begin = g_new (gchar, full_error_size);
868  while (errors_list)
869  {
870  i = GPOINTER_TO_INT(errors_list->data);
871  string_length = possible_error_lengths[i];
872 
873  /* Copy the error message and put a space after it. */
874  strncpy(error_message, _(possible_errors[i]), string_length);
875  error_message += string_length;
876  *error_message = ' ';
877  error_message++;
878 
879  errors_list = g_list_next (errors_list);
880  }
881  *error_message = '\0'; /* Replace the last space with the null byte. */
882  g_list_free (errors_list_begin);
883 
884  *error = error_message_begin;
885  return FALSE;
886  }
887 }
888 
894 static GncCsvTransLine* trans_property_list_to_trans (TransPropertyList* list, gchar** error)
895 {
896  GncCsvTransLine* trans_line = g_new (GncCsvTransLine, 1);
897  GList* properties_begin = list->properties;
898  QofBook* book = gnc_account_get_book (list->account);
899  gnc_commodity* currency = xaccAccountGetCommodity (list->account);
902  gchar *num = NULL;
903 
904  /* This flag is set to TRUE if we can use the "Deposit" or "Withdrawal" column. */
905  gboolean amount_set = FALSE;
906 
907  /* The balance is 0 by default. */
908  trans_line->balance_set = FALSE;
909  trans_line->balance = amount;
910  trans_line->num = NULL;
911 
912  /* We make the line_no -1 just to mark that it hasn't been set. We
913  * may get rid of line_no soon anyway, so it's not particularly
914  * important. */
915  trans_line->line_no = -1;
916 
917  /* Make sure this is a transaction with all the columns we need. */
918  if (!trans_property_list_verify_essentials (list, error))
919  {
920  g_free(trans_line);
921  return NULL;
922  }
923 
924  trans_line->trans = xaccMallocTransaction (book);
925  xaccTransBeginEdit (trans_line->trans);
926  xaccTransSetCurrency (trans_line->trans, currency);
927 
928  /* Go through each of the properties and edit the transaction accordingly. */
929  list->properties = properties_begin;
930  while (list->properties != NULL)
931  {
932  TransProperty* prop = (TransProperty*)(list->properties->data);
933  switch (prop->type)
934  {
935  case GNC_CSV_DATE:
936  xaccTransSetDatePostedSecsNormalized (trans_line->trans, *((time64*)(prop->value)));
937  break;
938 
939  case GNC_CSV_DESCRIPTION:
940  xaccTransSetDescription (trans_line->trans, (char*)(prop->value));
941  break;
942 
943  case GNC_CSV_NOTES:
944  xaccTransSetNotes (trans_line->trans, (char*)(prop->value));
945  break;
946 
947  case GNC_CSV_NUM:
948  /* the 'num' is saved and passed to 'trans_add_split' below where
949  * 'gnc_set_num_action' is used to set tran-num and/or split-action
950  * per book option */
951  num = g_strdup ((char*)(prop->value));
952  /* the 'num' is also saved and used in 'gnc_csv_parse_to_trans' when
953  * it calls 'trans_add_split' after deleting the splits added below
954  * when a balance is used by the user */
955  trans_line->num = g_strdup ((char*)(prop->value));
956  break;
957 
958  case GNC_CSV_DEPOSIT: /* Add deposits to the existing amount. */
959  if (prop->value != NULL)
960  {
961  amount = gnc_numeric_add (*((gnc_numeric*)(prop->value)),
962  amount,
965  amount_set = TRUE;
966  /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
967  trans_line->balance_set = FALSE;
968  }
969  break;
970 
971  case GNC_CSV_WITHDRAWAL: /* Withdrawals are just negative deposits. */
972  if (prop->value != NULL)
973  {
974  amount = gnc_numeric_add (gnc_numeric_neg(*((gnc_numeric*)(prop->value))),
975  amount,
978  amount_set = TRUE;
979  /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
980  trans_line->balance_set = FALSE;
981  }
982  break;
983 
984  case GNC_CSV_BALANCE: /* The balance gets stored in a separate field in trans_line. */
985  /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */
986  if (!amount_set && prop->value != NULL)
987  {
988  /* This gets put into the actual transaction at the end of gnc_csv_parse_to_trans. */
989  trans_line->balance = *((gnc_numeric*)(prop->value));
990  trans_line->balance_set = TRUE;
991  }
992  break;
993  }
994  list->properties = g_list_next (list->properties);
995  }
996 
997  /* Add a split with the cumulative amount value. */
998  trans_add_split (trans_line->trans, list->account, book, amount, num);
999  if (num)
1000  g_free (num);
1001 
1002  return trans_line;
1003 }
1004 
1016  gboolean redo_errors)
1017 {
1018  gboolean hasBalanceColumn;
1019  int i, j, max_cols = 0;
1020  GArray* column_types = parse_data->column_types;
1021  GList *error_lines = NULL, *begin_error_lines = NULL;
1022 
1023  /* last_transaction points to the last element in
1024  * parse_data->transactions, or NULL if it's empty. */
1025  GList* last_transaction = NULL;
1026 
1027  /* Free parse_data->error_lines and parse_data->transactions if they
1028  * already exist. */
1029  if (redo_errors) /* If we're redoing errors, we save freeing until the end. */
1030  {
1031  begin_error_lines = error_lines = parse_data->error_lines;
1032  }
1033  else
1034  {
1035  if (parse_data->error_lines != NULL)
1036  {
1037  g_list_free(parse_data->error_lines);
1038  }
1039  if (parse_data->transactions != NULL)
1040  {
1041  g_list_free (parse_data->transactions);
1042  }
1043  }
1044  parse_data->error_lines = NULL;
1045 
1046  if (redo_errors) /* If we're looking only at error data ... */
1047  {
1048  if (parse_data->transactions == NULL)
1049  {
1050  last_transaction = NULL;
1051  }
1052  else
1053  {
1054  /* Move last_transaction to the end. */
1055  last_transaction = parse_data->transactions;
1056  while (g_list_next (last_transaction) != NULL)
1057  {
1058  last_transaction = g_list_next (last_transaction);
1059  }
1060  }
1061  /* ... we use only the lines in error_lines. */
1062  if (error_lines == NULL)
1063  i = parse_data->orig_lines->len; /* Don't go into the for loop. */
1064  else
1065  i = GPOINTER_TO_INT(error_lines->data);
1066  }
1067  else /* Otherwise, we look at all the data. */
1068  {
1069  /* The following while-loop effectively behaves like the following for-loop:
1070  * for(i = 0; i < parse_data->orig_lines->len; i++). */
1071  i = parse_data->start_row;
1072  last_transaction = NULL;
1073  }
1074 
1075  /* set parse_data->end_row to number of lines */
1076  if (parse_data->end_row > parse_data->orig_lines->len)
1077  parse_data->end_row = parse_data->orig_lines->len;
1078 
1079  while (i < parse_data->end_row)
1080  {
1081  GPtrArray* line = parse_data->orig_lines->pdata[i];
1082  /* This flag is TRUE if there are any errors in this row. */
1083  gboolean errors = FALSE;
1084  gchar* error_message = NULL;
1085  TransPropertyList* list = trans_property_list_new (account, parse_data->date_format, parse_data->currency_format);
1086  GncCsvTransLine* trans_line = NULL;
1087 
1088  for (j = 0; j < line->len; j++)
1089  {
1090  /* We do nothing in "None" or "Account" columns. */
1091  if ((column_types->data[j] != GNC_CSV_NONE) && (column_types->data[j] != GNC_CSV_ACCOUNT))
1092  {
1093  /* Affect the transaction appropriately. */
1094  TransProperty* property = trans_property_new (column_types->data[j], list);
1095  gboolean succeeded = trans_property_set (property, line->pdata[j]);
1096 
1097  /* TODO Maybe move error handling to within TransPropertyList functions? */
1098  if (succeeded)
1099  {
1100  trans_property_list_add (property);
1101  }
1102  else
1103  {
1104  errors = TRUE;
1105  error_message = g_strdup_printf (_("%s column could not be understood."),
1106  _(gnc_csv_column_type_strs[property->type]));
1107  trans_property_free (property);
1108  break;
1109  }
1110  }
1111  }
1112 
1113  /* If we had success, add the transaction to parse_data->transaction. */
1114  if (!errors)
1115  {
1116  trans_line = trans_property_list_to_trans (list, &error_message);
1117  errors = trans_line == NULL;
1118  }
1119 
1120  trans_property_list_free (list);
1121 
1122  /* If there were errors, add this line to parse_data->error_lines. */
1123  if (errors)
1124  {
1125  parse_data->error_lines = g_list_append (parse_data->error_lines,
1126  GINT_TO_POINTER(i));
1127  /* If there's already an error message, we need to replace it. */
1128  if (line->len > (int)(parse_data->orig_row_lengths->data[i]))
1129  {
1130  g_free(line->pdata[line->len - 1]);
1131  line->pdata[line->len - 1] = error_message;
1132  }
1133  else
1134  {
1135  /* Put the error message at the end of the line. */
1136  g_ptr_array_add (line, error_message);
1137  }
1138  }
1139  else
1140  {
1141  /* If all went well, add this transaction to the list. */
1142  trans_line->line_no = i;
1143 
1144  /* We keep the transactions sorted by date. We start at the end
1145  * of the list and go backward, simply because the file itself
1146  * is probably also sorted by date (but we need to handle the
1147  * exception anyway). */
1148 
1149  /* If we can just put it at the end, do so and increment last_transaction. */
1150  if (last_transaction == NULL ||
1151  xaccTransGetDate (((GncCsvTransLine*)(last_transaction->data))->trans) <= xaccTransGetDate (trans_line->trans))
1152  {
1153  parse_data->transactions = g_list_append (parse_data->transactions, trans_line);
1154  /* If this is the first transaction, we need to get last_transaction on track. */
1155  if (last_transaction == NULL)
1156  last_transaction = parse_data->transactions;
1157  else /* Otherwise, we can just continue. */
1158  last_transaction = g_list_next (last_transaction);
1159  }
1160  /* Otherwise, search backward for the correct spot. */
1161  else
1162  {
1163  GList* insertion_spot = last_transaction;
1164  while (insertion_spot != NULL &&
1165  xaccTransGetDate (((GncCsvTransLine*)(insertion_spot->data))->trans) > xaccTransGetDate (trans_line->trans))
1166  {
1167  insertion_spot = g_list_previous (insertion_spot);
1168  }
1169  /* Move insertion_spot one location forward since we have to
1170  * use the g_list_insert_before function. */
1171  if (insertion_spot == NULL) /* We need to handle the case of inserting at the beginning of the list. */
1172  insertion_spot = parse_data->transactions;
1173  else
1174  insertion_spot = g_list_next (insertion_spot);
1175 
1176  parse_data->transactions = g_list_insert_before (parse_data->transactions, insertion_spot, trans_line);
1177  }
1178  }
1179 
1180  /* Increment to the next row. */
1181  if (redo_errors)
1182  {
1183  /* Move to the next error line in the list. */
1184  error_lines = g_list_next (error_lines);
1185  if (error_lines == NULL)
1186  i = parse_data->orig_lines->len; /* Don't continue the for loop. */
1187  else
1188  i = GPOINTER_TO_INT(error_lines->data);
1189  }
1190  else
1191  {
1192  if (parse_data->skip_rows == FALSE)
1193  i++;
1194  else
1195  i = i + 2;
1196  }
1197  }
1198 
1199  /* If we have a balance column, set the appropriate amounts on the transactions. */
1200  hasBalanceColumn = FALSE;
1201  for (i = 0; i < parse_data->column_types->len; i++)
1202  {
1203  if (parse_data->column_types->data[i] == GNC_CSV_BALANCE)
1204  {
1205  hasBalanceColumn = TRUE;
1206  break;
1207  }
1208  }
1209 
1210  if (hasBalanceColumn)
1211  {
1212  GList* transactions = parse_data->transactions;
1213 
1214  /* balance_offset is how much the balance currently in the account
1215  * differs from what it will be after the transactions are
1216  * imported. This will be sum of all the previous transactions for
1217  * any given transaction. */
1218  gnc_numeric balance_offset = double_to_gnc_numeric (0.0,
1219  xaccAccountGetCommoditySCU (account),
1221  while (transactions != NULL)
1222  {
1223  GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data;
1224  if (trans_line->balance_set)
1225  {
1226  time64 date = xaccTransGetDate (trans_line->trans);
1227  /* Find what the balance should be by adding the offset to the actual balance. */
1228  gnc_numeric existing_balance = gnc_numeric_add (balance_offset,
1229  xaccAccountGetBalanceAsOfDate (account, date),
1230  xaccAccountGetCommoditySCU (account),
1232 
1233  /* The amount of the transaction is the difference between the new and existing balance. */
1234  gnc_numeric amount = gnc_numeric_sub (trans_line->balance,
1235  existing_balance,
1236  xaccAccountGetCommoditySCU (account),
1238 
1239  SplitList* splits = xaccTransGetSplitList (trans_line->trans);
1240  while (splits)
1241  {
1242  SplitList* next_splits = g_list_next (splits);
1243  xaccSplitDestroy ((Split*)splits->data);
1244  splits = next_splits;
1245  }
1246 
1247  trans_add_split (trans_line->trans, account,
1248  gnc_account_get_book (account), amount, trans_line->num);
1249  if (trans_line->num)
1250  g_free (trans_line->num);
1251 
1252  /* This new transaction needs to be added to the balance offset. */
1253  balance_offset = gnc_numeric_add (balance_offset,
1254  amount,
1255  xaccAccountGetCommoditySCU (account),
1257  }
1258  transactions = g_list_next (transactions);
1259  }
1260  }
1261 
1262  if (redo_errors) /* Now that we're at the end, we do the freeing. */
1263  {
1264  g_list_free (begin_error_lines);
1265  }
1266 
1267  /* We need to resize parse_data->column_types since errors may have added columns. */
1268  for (i = 0; i < parse_data->orig_lines->len; i++)
1269  {
1270  if (max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len)
1271  max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len;
1272  }
1273  i = parse_data->column_types->len;
1274  parse_data->column_types = g_array_set_size (parse_data->column_types, max_cols);
1275  for (; i < max_cols; i++)
1276  {
1277  parse_data->column_types->data[i] = GNC_CSV_NONE;
1278  }
1279 
1280  return 0;
1281 }
void xaccSplitSetValue(Split *s, gnc_numeric amt)
Definition: Split.c:1294
gnc_numeric balance
Definition: gnc-csv-model.h:83
GMappedFile * raw_mapping
Transaction * xaccMallocTransaction(QofBook *book)
Definition: Transaction.c:513
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
Definition: Transaction.c:1920
time64 xaccTransGetDate(const Transaction *trans)
Definition: Transaction.c:2215
GPtrArray * orig_lines
gnc_numeric double_to_gnc_numeric(double n, gint64 denom, gint how)
int gnc_csv_convert_encoding(GncCsvParseData *parse_data, const char *encoding, GError **error)
utility functions for the GnuCash UI
gboolean xaccSplitDestroy(Split *split)
Definition: Split.c:1492
int xaccAccountGetCommoditySCU(const Account *acc)
Definition: Account.c:2458
gnc_numeric gnc_numeric_neg(gnc_numeric a)
void xaccTransSetNotes(Transaction *trans, const char *notes)
Definition: Transaction.c:2115
int gnc_csv_parse_to_trans(GncCsvParseData *parse_data, Account *account, gboolean redo_errors)
void xaccTransSetDescription(Transaction *trans, const char *desc)
Definition: Transaction.c:2085
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
GArray * orig_row_lengths
time64 parse_date(const char *date_str, int format)
gboolean balance_set
Definition: gnc-csv-model.h:84
struct tm * gnc_localtime_r(const time64 *secs, struct tm *time)
fill out a time struct from a 64-bit time value adjusted for the current time zone.
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Definition: Transaction.c:1354
void gnc_csv_parse_data_free(GncCsvParseData *parse_data)
GList SplitList
Definition: gnc-engine.h:203
void xaccSplitSetAmount(Split *s, gnc_numeric amt)
Definition: Split.c:1258
GncCsvStr file_str
StfParseOptions_t * options
GncCsvParseData * gnc_csv_new_parse_data(void)
time64 gnc_mktime(struct tm *time)
calculate seconds from the epoch given a time struct
void xaccTransBeginEdit(Transaction *trans)
Definition: Transaction.c:1380
gnc_numeric xaccAccountGetBalanceAsOfDate(Account *acc, time64 date)
Definition: Account.c:3288
int gnc_csv_parse(GncCsvParseData *parse_data, gboolean guessColTypes, GError **error)
Split * xaccMallocSplit(QofBook *book)
Definition: Split.c:582
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
GList * transactions
Definition: SplitP.h:71
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Definition: Account.c:3148
GStringChunk * chunk
CSV import GUI.
time64 gnc_time(time64 *tbuf)
get the current local time
TransPropertyList * list
gint64 time64
Definition: gnc-date.h:83
GArray * column_types
gboolean skip_rows
GncCsvStr raw_str
int gnc_csv_load_file(GncCsvParseData *parse_data, const char *filename, GError **error)
SplitList * xaccTransGetSplitList(const Transaction *trans)
Definition: Transaction.c:2164
const gchar * QofLogModule
Definition: qofid.h:89