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