]> Dogcows Code - chaz/homebank/blob - src/hb-import-qif.c
Merge branch 'upstream'
[chaz/homebank] / src / hb-import-qif.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2016 Maxime DOYEN
3 *
4 * This file is part of HomeBank.
5 *
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "homebank.h"
21
22 //#include "ui-assist-import.h"
23 #include "hb-import.h"
24
25 /****************************************************************************/
26 /* Debug macros */
27 /****************************************************************************/
28 #define MYDEBUG 0
29
30 #if MYDEBUG
31 #define DB(x) (x);
32 #else
33 #define DB(x);
34 #endif
35
36 /* our global datas */
37 extern struct HomeBank *GLOBALS;
38 extern struct Preferences *PREFS;
39
40
41 /* = = = = = = = = = = = = = = = = */
42 static QIF_Tran *
43 da_qif_tran_malloc(void)
44 {
45 return g_malloc0(sizeof(QIF_Tran));
46 }
47
48
49 static void
50 da_qif_tran_free(QIF_Tran *item)
51 {
52 gint i;
53
54 if(item != NULL)
55 {
56 if(item->date != NULL)
57 g_free(item->date);
58 if(item->info != NULL)
59 g_free(item->info);
60 if(item->payee != NULL)
61 g_free(item->payee);
62 if(item->memo != NULL)
63 g_free(item->memo);
64 if(item->category != NULL)
65 g_free(item->category);
66 if(item->account != NULL)
67 g_free(item->account);
68
69 for(i=0;i<TXN_MAX_SPLIT;i++)
70 {
71 QIFSplit *s = &item->splits[i];
72
73 if(s->memo != NULL)
74 g_free(s->memo);
75 if(s->category != NULL)
76 g_free(s->category);
77 }
78
79 g_free(item);
80 }
81 }
82
83
84 static void
85 da_qif_tran_destroy(QifContext *ctx)
86 {
87 GList *qiflist = g_list_first(ctx->q_tra);
88
89 while (qiflist != NULL)
90 {
91 QIF_Tran *item = qiflist->data;
92 da_qif_tran_free(item);
93 qiflist = g_list_next(qiflist);
94 }
95 g_list_free(ctx->q_tra);
96 ctx->q_tra = NULL;
97 }
98
99
100 static void
101 da_qif_tran_new(QifContext *ctx)
102 {
103 ctx->q_tra = NULL;
104 }
105
106
107 static void
108 da_qif_tran_move(QIF_Tran *sitem, QIF_Tran *ditem)
109 {
110 if(sitem != NULL && ditem != NULL)
111 {
112 memcpy(ditem, sitem, sizeof(QIF_Tran));
113 memset(sitem, 0, sizeof(QIF_Tran));
114 }
115 }
116
117
118 static void
119 da_qif_tran_append(QifContext *ctx, QIF_Tran *item)
120 {
121 ctx->q_tra = g_list_append(ctx->q_tra, item);
122 }
123
124
125 /* = = = = = = = = = = = = = = = = */
126
127 gdouble
128 hb_qif_parser_get_amount(gchar *string)
129 {
130 gdouble amount;
131 gint l, i;
132 gchar *new_str, *p;
133 gint ndcount = 0;
134 gchar dc;
135
136 //DB( g_print("\n[qif] hb_qif_parser_get_amount\n") );
137
138
139 amount = 0.0;
140 dc = '?';
141
142 l = strlen(string) - 1;
143
144 // the first non-digit is a grouping, or a decimal separator
145 // if the non-digit is after a 3 digit serie, it might be a grouping
146
147 for(i=l;i>=0;i--)
148 {
149 //DB( g_print(" %d :: %c :: ds='%c' ndcount=%d\n", i, string[i], dc, ndcount) );
150
151 if( string[i] == '-' || string[i] == '+' ) continue;
152
153 if( g_ascii_isdigit( string[i] ))
154 {
155 ndcount++;
156 }
157 else
158 {
159 if( (ndcount != 3) && (string[i] == '.' || string[i]==',') )
160 {
161 dc = string[i];
162 }
163 ndcount = 0;
164 }
165 }
166
167 //DB( g_print(" s='%s' :: ds='%c'\n", string, dc) );
168
169
170 new_str = g_malloc (l+3); //#1214077
171 p = new_str;
172 for(i=0;i<=l;i++)
173 {
174 if( g_ascii_isdigit( string[i] ) || string[i] == '-' )
175 {
176 *p++ = string[i];
177 }
178 else
179 if( string[i] == dc )
180 *p++ = '.';
181 }
182 *p++ = '\0';
183 amount = g_ascii_strtod(new_str, NULL);
184
185 //DB( g_print(" -> amount was='%s' => to='%s' double='%f'\n", string, new_str, amount) );
186
187 g_free(new_str);
188
189 return amount;
190 }
191
192 /* O if m-d-y (american)
193 1 if d-m-y (european) */
194 /* obsolete 4.5
195 static gint
196 hb_qif_parser_guess_datefmt(QifContext *ctx)
197 {
198 gboolean retval = TRUE;
199 GList *qiflist;
200 gboolean r, valid;
201 gint d, m, y;
202
203 DB( g_print("(qif) get_datetype\n") );
204
205 qiflist = g_list_first(ctx->q_tra);
206 while (qiflist != NULL)
207 {
208 QIF_Tran *item = qiflist->data;
209
210 r = hb_qif_parser_get_dmy(item->date, &d, &m, &y);
211 valid = g_date_valid_dmy(d, m, y);
212
213 DB( g_print(" -> date: %s :: %d %d %d :: %d\n", item->date, d, m, y, valid ) );
214
215 if(valid == FALSE)
216 {
217 retval = FALSE;
218 break;
219 }
220
221 qiflist = g_list_next(qiflist);
222 }
223
224 return retval;
225 }
226 */
227
228 static Transaction *
229 account_qif_get_child_transfer(Transaction *src, GList *list)
230 {
231 Transaction *item;
232
233 DB( g_print("([qif] get_child_transfer\n") );
234
235 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->wording, src->amount, src->account, src->kxferacc) );
236
237 list = g_list_first(list);
238 while (list != NULL)
239 {
240 item = list->data;
241 if( item->paymode == PAYMODE_INTXFER)
242 {
243 if( src->date == item->date &&
244 src->kacc == item->kxferacc &&
245 src->kxferacc == item->kacc &&
246 ABS(src->amount) == ABS(item->amount) )
247 {
248 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->wording, item->amount, item->account, item->kxferacc) );
249
250 return item;
251 }
252 }
253 list = g_list_next(list);
254 }
255
256 //DB( g_print(" not found...\n") );
257
258 return NULL;
259 }
260
261
262 static gint
263 hb_qif_parser_get_block_type(gchar *qif_line)
264 {
265 gchar **typestr;
266 gint type = QIF_NONE;
267
268 DB( g_print("--------\n[qif] block type\n") );
269
270 //DB( g_print(" -> str: %s type: %d\n", qif_line, type) );
271
272
273 if(g_str_has_prefix(qif_line, "!Account") || g_str_has_prefix(qif_line, "!account"))
274 {
275 type = QIF_ACCOUNT;
276 }
277 else
278 {
279 typestr = g_strsplit(qif_line, ":", 2);
280
281 if( g_strv_length(typestr) == 2 )
282 {
283 gchar *qif_line = g_utf8_casefold(typestr[1], -1);
284
285 //DB( g_print(" -> str[1]: %s\n", typestr[1]) );
286
287 if( g_str_has_prefix(qif_line, "bank") )
288 {
289 type = QIF_TRANSACTION;
290 }
291 else
292 if( g_str_has_prefix(qif_line, "cash") )
293 {
294 type = QIF_TRANSACTION;
295 }
296 else
297 if( g_str_has_prefix(qif_line, "ccard") )
298 {
299 type = QIF_TRANSACTION;
300 }
301 else
302 if( g_str_has_prefix(qif_line, "invst") )
303 {
304 type = QIF_TRANSACTION;
305 }
306 else
307 if( g_str_has_prefix(qif_line, "oth a") )
308 {
309 type = QIF_TRANSACTION;
310 }
311 else
312 if( g_str_has_prefix(qif_line, "oth l") )
313 {
314 type = QIF_TRANSACTION;
315 }
316 else
317 if( g_str_has_prefix(qif_line, "security") )
318 {
319 type = QIF_SECURITY;
320 }
321 else
322 if( g_str_has_prefix(qif_line, "prices") )
323 {
324 type = QIF_PRICES;
325 }
326
327 g_free(qif_line);
328 }
329 g_strfreev(typestr);
330 }
331
332 //DB( g_print(" -> return type: %d\n", type) );
333
334
335 return type;
336 }
337
338 static void
339 hb_qif_parser_parse(QifContext *ctx, gchar *filename, const gchar *encoding)
340 {
341 GIOChannel *io;
342 QIF_Tran tran = { 0 };
343
344 DB( g_print("\n[qif] hb_qif_parser_parse\n") );
345
346 io = g_io_channel_new_file(filename, "r", NULL);
347 if(io != NULL)
348 {
349 gchar *qif_line;
350 GError *err = NULL;
351 gint io_stat;
352 gint type = QIF_NONE;
353 gchar *value = NULL;
354 gchar *cur_acc;
355
356 DB( g_print(" -> encoding should be %s\n", encoding) );
357 if( encoding != NULL )
358 {
359 g_io_channel_set_encoding(io, encoding, NULL);
360 }
361
362 DB( g_print(" -> encoding is %s\n", g_io_channel_get_encoding(io)) );
363
364 cur_acc = g_strdup(QIF_UNKNOW_ACCOUNT_NAME);
365
366 for(;;)
367 {
368 io_stat = g_io_channel_read_line(io, &qif_line, NULL, NULL, &err);
369
370 if( io_stat == G_IO_STATUS_EOF )
371 break;
372 if( io_stat == G_IO_STATUS_ERROR )
373 {
374 DB (g_print(" + ERROR %s\n",err->message));
375 break;
376 }
377 if( io_stat == G_IO_STATUS_NORMAL )
378 {
379 hb_string_strip_crlf(qif_line);
380
381 //DB (g_print("** new QIF line: '%s' **\n", qif_line));
382
383 //start qif parsing
384 if(g_str_has_prefix(qif_line, "!")) /* !Type: or !Option: or !Account otherwise ignore */
385 {
386 type = hb_qif_parser_get_block_type(qif_line);
387 DB ( g_print("-> ---- QIF block: '%s' (type = %d) ----\n", qif_line, type) );
388 }
389
390 value = &qif_line[1];
391
392 if( type == QIF_ACCOUNT )
393 {
394 switch(qif_line[0])
395 {
396 case 'N': // Name
397 {
398 g_free(cur_acc);
399 g_strstrip(value);
400 cur_acc = g_strdup(value);
401 DB ( g_print(" name: '%s'\n", value) );
402 break;
403 }
404
405 case 'T': // Type of account
406 {
407
408 DB ( g_print(" type: '%s'\n", value) );
409 break;
410 }
411
412 case 'L': // Credit limit (only for credit card accounts)
413 if(g_str_has_prefix(qif_line, "L"))
414 {
415
416 DB ( g_print(" credit limit: '%s'\n", value) );
417 break;
418 }
419
420 case '$': // Statement balance amount
421 {
422
423 DB ( g_print(" balance: '%s'\n", value) );
424 break;
425 }
426
427 case '^': // end
428 {
429 DB ( g_print("should create account '%s' here\n", cur_acc) );
430
431 DB ( g_print(" ----------------\n") );
432 break;
433 }
434 }
435 }
436
437 if( type == QIF_TRANSACTION )
438 {
439 switch(qif_line[0])
440 {
441 case 'D': //date
442 {
443 gchar *ptr;
444
445 // US Quicken seems to be using the ' to indicate post-2000 two-digit years
446 //(such as 01/01'00 for Jan 1 2000)
447 ptr = g_strrstr (value, "\'");
448 if(ptr != NULL) { *ptr = '/'; }
449
450 ptr = g_strrstr (value, " ");
451 if(ptr != NULL) { *ptr = '0'; }
452
453 g_free(tran.date);
454 tran.date = g_strdup(value);
455 break;
456 }
457
458 case 'T': // amount
459 {
460 tran.amount = hb_qif_parser_get_amount(value);
461 break;
462 }
463
464 case 'C': // cleared status
465 {
466 tran.reconciled = FALSE;
467 if(g_str_has_prefix(value, "X") || g_str_has_prefix(value, "R") )
468 {
469 tran.reconciled = TRUE;
470 }
471 tran.cleared = FALSE;
472 if(g_str_has_prefix(value, "*") || g_str_has_prefix(value, "c") )
473 {
474 tran.cleared = TRUE;
475 }
476 break;
477 }
478
479 case 'N': // check num or reference number
480 {
481 if(*value != '\0')
482 {
483 g_free(tran.info);
484 g_strstrip(value);
485 tran.info = g_strdup(value);
486 }
487 break;
488 }
489
490 case 'P': // payee
491 {
492 if(*value != '\0')
493 {
494 g_free(tran.payee);
495 g_strstrip(value);
496 tran.payee = g_strdup(value);
497 }
498 break;
499 }
500
501 case 'M': // memo
502 {
503 if(*value != '\0')
504 {
505 g_free(tran.memo);
506 tran.memo = g_strdup(value);
507 }
508 break;
509 }
510
511 case 'L': // category
512 {
513 // LCategory of transaction
514 // L[Transfer account name]
515 // LCategory of transaction/Class of transaction
516 // L[Transfer account]/Class of transaction
517 // this is managed at insertion
518 if(*value != '\0')
519 {
520 g_free(tran.category);
521 g_strstrip(value);
522 tran.category = g_strdup(value);
523 }
524 break;
525 }
526
527 case 'S':
528 case 'E':
529 case '$':
530 {
531 if(tran.nb_splits < TXN_MAX_SPLIT)
532 {
533 switch(qif_line[0])
534 {
535 case 'S': // split category
536 {
537 QIFSplit *s = &tran.splits[tran.nb_splits];
538 if(*value != '\0')
539 {
540 g_free(s->category);
541 g_strstrip(value);
542 s->category = g_strdup(value);
543 }
544 break;
545 }
546
547 case 'E': // split memo
548 {
549 QIFSplit *s = &tran.splits[tran.nb_splits];
550 if(*value != '\0')
551 {
552 g_free(s->memo);
553 s->memo = g_strdup(value);
554 }
555 break;
556 }
557
558 case '$': // split amount
559 {
560 QIFSplit *s = &tran.splits[tran.nb_splits];
561
562 s->amount = hb_qif_parser_get_amount(value);
563 // $ line normally end a split
564 #if MYDEBUG == 1
565 g_print(" -> new split added: [%d] S=%s, E=%s, $=%.2f\n", tran.nb_splits, s->category, s->memo, s->amount);
566 #endif
567
568 tran.nb_splits++;
569 break;
570 }
571 }
572
573 }
574 // end split
575 break;
576 }
577
578 case '^': // end of line
579 {
580 QIF_Tran *newitem;
581
582 //fix: 380550
583 if( tran.date )
584 {
585 tran.account = g_strdup(cur_acc);
586
587 DB ( g_print(" -> store qif txn: dat:'%s' amt:%.2f pay:'%s' mem:'%s' cat:'%s' acc:'%s' nbsplit:%d\n", tran.date, tran.amount, tran.payee, tran.memo, tran.category, tran.account, tran.nb_splits) );
588
589 newitem = da_qif_tran_malloc();
590 da_qif_tran_move(&tran, newitem);
591 da_qif_tran_append(ctx, newitem);
592 }
593
594 //unvalid tran
595 tran.date = 0;
596 //todo: should clear mem alloc here
597
598 tran.nb_splits = 0;
599 break;
600 }
601
602 }
603 // end of switch
604
605 }
606 // end QIF_TRANSACTION
607 }
608 // end of stat normal
609 g_free(qif_line);
610 }
611 // end of for loop
612
613 g_free(cur_acc);
614 g_io_channel_unref (io);
615 }
616
617 }
618
619
620 /*
621 ** this is our main qif entry point
622 */
623 GList *
624 account_import_qif(gchar *filename, ImportContext *ictx)
625 {
626 QifContext ctx = { 0 };
627 GList *qiflist;
628 GList *list = NULL;
629
630 DB( g_print("\n[qif] account import qif\n") );
631
632 // allocate our GLists
633 da_qif_tran_new(&ctx);
634 ctx.is_ccard = FALSE;
635
636 // parse !!
637 hb_qif_parser_parse(&ctx, filename, ictx->encoding);
638
639 // check iso date format in file
640 //isodate = hb_qif_parser_check_iso_date(&ctx);
641 //DB( g_print(" -> date is dd/mm/yy: %d\n", isodate) );
642
643 DB( g_print("\n\n -> transform to hb txn\n") );
644
645 DB( g_print(" -> %d qif txn\n", g_list_length(ctx.q_tra)) );
646
647 // transform our qif transactions to homebank ones
648 qiflist = g_list_first(ctx.q_tra);
649 while (qiflist != NULL)
650 {
651 QIF_Tran *item = qiflist->data;
652 Transaction *newope, *child;
653 Account *accitem;
654 Payee *payitem;
655 Category *catitem;
656 gchar *name, *tmpmemo, *tmppayee;
657 gint nsplit;
658
659 newope = da_transaction_malloc();
660
661 newope->date = hb_date_get_julian(item->date, ictx->datefmt);
662 if( newope->date == 0 )
663 ictx->cnt_err_date++;
664
665 //newope->paymode = atoi(str_array[1]);
666 //newope->info = g_strdup(str_array[2]);
667
668 //#916690 manage memo, swap memo/payee
669 tmpmemo = item->memo;
670 tmppayee = item->payee;
671 if( PREFS->dtex_qifswap )
672 {
673 tmpmemo = item->payee;
674 tmppayee = item->memo;
675 }
676
677 if( PREFS->dtex_qifmemo )
678 newope->wording = g_strdup(tmpmemo);
679
680 newope->info = g_strdup(item->info);
681 newope->amount = item->amount;
682
683 //#773282 invert amount for ccard accounts
684 if(ctx.is_ccard)
685 newope->amount *= -1;
686
687 // payee + append
688 if( tmppayee != NULL )
689 {
690 payitem = da_pay_get_by_name(tmppayee);
691 if(payitem == NULL)
692 {
693 //DB( g_print(" -> append pay: '%s'\n", tmppayee ) );
694
695 payitem = da_pay_malloc();
696 payitem->name = g_strdup(tmppayee);
697 payitem->imported = TRUE;
698 da_pay_append(payitem);
699
700 ictx->cnt_new_pay += 1;
701 }
702 newope->kpay = payitem->key;
703 }
704
705 // LCategory of transaction
706 // L[Transfer account name]
707 // LCategory of transaction/Class of transaction
708 // L[Transfer account]/Class of transaction
709 if( item->category != NULL )
710 {
711 if(g_str_has_prefix(item->category, "[")) // this is a transfer account name
712 {
713 gchar *accname;
714
715 //DB ( g_print(" -> transfer to: '%s'\n", item->category) );
716
717 //remove brackets
718 accname = hb_strdup_nobrackets(item->category);
719
720 accitem = import_create_account(accname, NULL);
721
722 newope->kxferacc = accitem->key;
723 newope->paymode = PAYMODE_INTXFER;
724
725 g_free(accname);
726 }
727 else
728 {
729 //DB ( g_print(" -> append cat: '%s'\n", item->category) );
730
731 catitem = da_cat_append_ifnew_by_fullname(item->category, TRUE );
732 if( catitem != NULL )
733 {
734 ictx->cnt_new_cat += 1;
735 newope->kcat = catitem->key;
736 }
737 }
738 }
739
740 // splits, if not a xfer
741 if( newope->paymode != PAYMODE_INTXFER )
742 {
743 for(nsplit=0;nsplit<item->nb_splits;nsplit++)
744 {
745 QIFSplit *s = &item->splits[nsplit];
746 Split *hbs;
747 guint32 kcat = 0;
748
749 DB( g_print(" -> append split %d: '%s' '%.2f' '%s'\n", nsplit, s->category, s->amount, s->memo) );
750
751 if( s->category != NULL )
752 {
753 catitem = da_cat_append_ifnew_by_fullname(s->category, TRUE ); // TRUE = imported
754 if( catitem != NULL )
755 {
756 kcat = catitem->key;
757 }
758 }
759
760 hbs = da_split_new(kcat, s->amount, s->memo);
761
762 da_splits_append(newope->splits, hbs);
763
764 //da_transaction_splits_append(newope, hbs);
765 hbs = NULL;
766 }
767 }
768
769 // account + append
770 name = strcmp(QIF_UNKNOW_ACCOUNT_NAME, item->account) == 0 ? "" : item->account;
771
772 DB( g_print(" -> account name is '%s'\n", name ) );
773
774 accitem = import_create_account(name, NULL);
775
776 newope->kacc = accitem->key;
777
778 newope->flags |= OF_ADDED;
779 if( newope->amount > 0 )
780 newope->flags |= OF_INCOME;
781
782 if( item->reconciled )
783 newope->status = TXN_STATUS_RECONCILED;
784 else
785 if( item->cleared )
786 newope->status = TXN_STATUS_CLEARED;
787
788 child = account_qif_get_child_transfer(newope, list);
789 if( child != NULL)
790 {
791 //DB( g_print(" -> transaction already exist\n" ) );
792
793 da_transaction_free(newope);
794 }
795 else
796 {
797 //DB( g_print(" -> append trans. acc:'%s', memo:'%s', val:%.2f\n", item->account, item->memo, item->amount ) );
798
799 list = g_list_append(list, newope);
800 }
801
802 qiflist = g_list_next(qiflist);
803 }
804
805 // destroy our GLists
806 da_qif_tran_destroy(&ctx);
807
808 DB( g_print(" -> %d txn converted\n", g_list_length(list)) );
809 DB( g_print(" -> %d errors\n", ictx->cnt_err_date) );
810
811
812
813
814 return list;
815 }
816
817
This page took 0.065952 seconds and 4 git commands to generate.