1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2021 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 static void
41 hb_qif_parser_parse(ImportContext *ctx, GenFile *genfile);
42
43 /* = = = = = = = = = = = = = = = = */
44
homebank_qif_import(ImportContext * ictx,GenFile * genfile)45 GList *homebank_qif_import(ImportContext *ictx, GenFile *genfile)
46 {
47 DB( g_print("\n[import] homebank QIF\n") );
48
49 hb_qif_parser_parse(ictx, genfile);
50
51 return ictx->gen_lst_txn;;
52 }
53
54
55 /* = = = = = = = = = = = = = = = = */
56
57 gdouble
hb_qif_parser_get_amount(gchar * string)58 hb_qif_parser_get_amount(gchar *string)
59 {
60 gdouble amount;
61 gint l, i;
62 gchar *new_str, *p;
63 gint ndcount = 0;
64 gchar dc;
65
66 //DB( g_print("\n[qif] hb_qif_parser_get_amount\n") );
67
68
69 amount = 0.0;
70 dc = '?';
71
72 //TODO: we should use here ==> hb_string_dup_raw_amount_clean(const gchar *string, gint digits)
73
74 l = strlen(string) - 1;
75
76 // the first non-digit is a grouping, or a decimal separator
77 // if the non-digit is after a 3 digit serie, it might be a grouping
78
79 for(i=l;i>=0;i--)
80 {
81 //DB( g_print(" %d :: %c :: ds='%c' ndcount=%d\n", i, string[i], dc, ndcount) );
82
83 if( string[i] == '-' || string[i] == '+' ) continue;
84
85 if( g_ascii_isdigit( string[i] ))
86 {
87 ndcount++;
88 }
89 else
90 {
91 if( (ndcount != 3) && (string[i] == '.' || string[i]==',') )
92 {
93 dc = string[i];
94 }
95 ndcount = 0;
96 }
97 }
98
99 //DB( g_print(" s='%s' :: ds='%c'\n", string, dc) );
100
101
102 new_str = g_malloc (l+3); //#1214077
103 p = new_str;
104 for(i=0;i<=l;i++)
105 {
106 if( g_ascii_isdigit( string[i] ) || string[i] == '-' )
107 {
108 *p++ = string[i];
109 }
110 else
111 if( string[i] == dc )
112 *p++ = '.';
113 }
114 *p++ = '\0';
115 amount = g_ascii_strtod(new_str, NULL);
116
117 //DB( g_print(" -> amount was='%s' => to='%s' double='%f'\n", string, new_str, amount) );
118
119 g_free(new_str);
120
121 return amount;
122 }
123
124 /* O if m-d-y (american)
125 1 if d-m-y (european) */
126 /* obsolete 4.5
127 static gint
128 hb_qif_parser_guess_datefmt(ImportContext *ctx)
129 {
130 gboolean retval = TRUE;
131 GList *qiflist;
132 gboolean r, valid;
133 gint d, m, y;
134
135 DB( g_print("(qif) get_datetype\n") );
136
137 qiflist = g_list_first(ctx->gen_lst_txn);
138 while (qiflist != NULL)
139 {
140 GenTxn *item = qiflist->data;
141
142 r = hb_qif_parser_get_dmy(item->date, &d, &m, &y);
143 valid = g_date_valid_dmy(d, m, y);
144
145 DB( g_print(" -> date: %s :: %d %d %d :: %d\n", item->date, d, m, y, valid ) );
146
147 if(valid == FALSE)
148 {
149 retval = FALSE;
150 break;
151 }
152
153 qiflist = g_list_next(qiflist);
154 }
155
156 return retval;
157 }
158 */
159
160
161 static gint
hb_qif_parser_get_block_type(gchar * qif_line)162 hb_qif_parser_get_block_type(gchar *qif_line)
163 {
164 gchar **typestr;
165 gint type = QIF_NONE;
166
167 DB( g_print("--------\n[qif] block type\n") );
168
169 //DB( g_print(" -> str: %s type: %d\n", qif_line, type) );
170
171
172 if(g_str_has_prefix(qif_line, "!Account") || g_str_has_prefix(qif_line, "!account"))
173 {
174 type = QIF_ACCOUNT;
175 }
176 else
177 {
178 typestr = g_strsplit(qif_line, ":", 2);
179
180 if( g_strv_length(typestr) == 2 )
181 {
182 gchar *qif_line = g_utf8_casefold(typestr[1], -1);
183
184 //DB( g_print(" -> str[1]: %s\n", typestr[1]) );
185
186 if( g_str_has_prefix(qif_line, "bank") )
187 {
188 type = QIF_TRANSACTION;
189 }
190 else
191 if( g_str_has_prefix(qif_line, "cash") )
192 {
193 type = QIF_TRANSACTION;
194 }
195 else
196 if( g_str_has_prefix(qif_line, "ccard") )
197 {
198 type = QIF_TRANSACTION;
199 }
200 else
201 if( g_str_has_prefix(qif_line, "invst") )
202 {
203 type = QIF_TRANSACTION;
204 }
205 else
206 if( g_str_has_prefix(qif_line, "oth a") )
207 {
208 type = QIF_TRANSACTION;
209 }
210 else
211 if( g_str_has_prefix(qif_line, "oth l") )
212 {
213 type = QIF_TRANSACTION;
214 }
215 else
216 if( g_str_has_prefix(qif_line, "security") )
217 {
218 type = QIF_SECURITY;
219 }
220 else
221 if( g_str_has_prefix(qif_line, "prices") )
222 {
223 type = QIF_PRICES;
224 }
225
226 g_free(qif_line);
227 }
228 g_strfreev(typestr);
229 }
230
231 //DB( g_print(" -> return type: %d\n", type) );
232
233
234 return type;
235 }
236
237 static void
hb_qif_parser_parse(ImportContext * ctx,GenFile * genfile)238 hb_qif_parser_parse(ImportContext *ctx, GenFile *genfile)
239 {
240 GIOChannel *io;
241 GenTxn tran = { 0 };
242
243 DB( g_print("\n[qif] hb_qif_parser_parse\n") );
244
245 io = g_io_channel_new_file(genfile->filepath, "r", NULL);
246 if(io != NULL)
247 {
248 gchar *qif_line;
249 GError *err = NULL;
250 gint io_stat;
251 gint type = QIF_NONE;
252 gchar *value = NULL;
253 GenAcc tmpgenacc = { 0 };
254 GenAcc *genacc;
255
256 DB( g_print(" -> encoding should be %s\n", genfile->encoding) );
257 if( genfile->encoding != NULL )
258 {
259 g_io_channel_set_encoding(io, genfile->encoding, NULL);
260 }
261
262 DB( g_print(" -> encoding is %s\n", g_io_channel_get_encoding(io)) );
263
264 // within a single qif file, if there is no accoutn data
265 // then txn are related to a single account
266 genacc = NULL;
267
268 for(;;)
269 {
270 io_stat = g_io_channel_read_line(io, &qif_line, NULL, NULL, &err);
271
272 if( io_stat == G_IO_STATUS_EOF )
273 break;
274 if( io_stat == G_IO_STATUS_ERROR )
275 {
276 DB (g_print(" + ERROR %s\n",err->message));
277 break;
278 }
279 if( io_stat == G_IO_STATUS_NORMAL )
280 {
281 hb_string_strip_crlf(qif_line);
282
283 //DB (g_print("** new QIF line: '%s' **\n", qif_line));
284
285 //start qif parsing
286 if(g_str_has_prefix(qif_line, "!")) /* !Type: or !Option: or !Account otherwise ignore */
287 {
288 type = hb_qif_parser_get_block_type(qif_line);
289 DB ( g_print("-> ---- QIF block: '%s' (type = %d) ----\n", qif_line, type) );
290 }
291
292 value = &qif_line[1];
293
294 if( type == QIF_ACCOUNT )
295 {
296 switch(qif_line[0])
297 {
298 case 'N': // Name
299 {
300 g_strstrip(value);
301 tmpgenacc.name = g_strdup(value);
302 DB ( g_print(" name: '%s'\n", value) );
303 break;
304 }
305
306 case 'T': // Type of account
307 {
308 DB ( g_print(" type: '%s'\n", value) );
309 // added for 5.0.1
310 if( g_ascii_strcasecmp("CCard", value) == 0 )
311 {
312 tmpgenacc.is_ccard = TRUE;
313 }
314 break;
315 }
316 /*
317 case 'D': // Description
318 {
319
320 DB ( g_print(" description: '%s'\n", value) );
321 break;
322 }
323
324 case 'L': // Credit limit (only for credit card accounts)
325 if(g_str_has_prefix(qif_line, "L"))
326 {
327
328 DB ( g_print(" credit limit: '%s'\n", value) );
329 break;
330 }
331
332 case '$': // Statement balance amount
333 {
334
335 DB ( g_print(" balance: '%s'\n", value) );
336 break;
337 }*/
338
339 case '^': // end
340 {
341 Account *dst_acc;
342
343 genacc = hb_import_gen_acc_get_next (ctx, FILETYPE_QIF, tmpgenacc.name, NULL);
344 // number is null for QIF because it is not a QIF account field into specification
345 dst_acc = hb_import_acc_find_existing(tmpgenacc.name, NULL );
346 if( dst_acc != NULL )
347 {
348 DB( g_print(" - set dst_acc to %d\n", dst_acc->key) );
349 genacc->kacc = dst_acc->key;
350 }
351 genacc->is_ccard = tmpgenacc.is_ccard;
352
353 g_free(tmpgenacc.name);
354 tmpgenacc.name = NULL;
355 tmpgenacc.is_ccard = FALSE;
356
357 DB ( g_print(" ----------------\n") );
358 break;
359 }
360 }
361 }
362
363 if( type == QIF_TRANSACTION )
364 {
365 switch(qif_line[0])
366 {
367 case 'D': //date
368 {
369 gchar *ptr;
370
371 // US Quicken seems to be using the ' to indicate post-2000 two-digit years
372 //(such as 01/01'00 for Jan 1 2000)
373 ptr = g_strrstr (value, "\'");
374 if(ptr != NULL) { *ptr = '/'; }
375
376 ptr = g_strrstr (value, " ");
377 if(ptr != NULL) { *ptr = '0'; }
378
379 g_free(tran.date);
380 tran.date = g_strdup(value);
381 break;
382 }
383
384 case 'T': // amount
385 {
386 tran.amount = hb_qif_parser_get_amount(value);
387 break;
388 }
389
390 case 'C': // cleared status
391 {
392 tran.reconciled = FALSE;
393 if(g_str_has_prefix(value, "X") || g_str_has_prefix(value, "R") )
394 {
395 tran.reconciled = TRUE;
396 }
397 tran.cleared = FALSE;
398 if(g_str_has_prefix(value, "*") || g_str_has_prefix(value, "c") )
399 {
400 tran.cleared = TRUE;
401 }
402 break;
403 }
404
405 case 'N': // check num or reference number
406 {
407 if(*value != '\0')
408 {
409 g_free(tran.info);
410 g_strstrip(value);
411 tran.info = g_strdup(value);
412 }
413 break;
414 }
415
416 case 'P': // payee
417 {
418 if(*value != '\0')
419 {
420 g_free(tran.payee);
421 g_strstrip(value);
422 tran.rawpayee = g_strdup(value);
423 }
424 break;
425 }
426
427 case 'M': // memo
428 {
429 if(*value != '\0')
430 {
431 g_free(tran.memo);
432 tran.rawmemo = g_strdup(value);
433 }
434 break;
435 }
436
437 case 'L': // category
438 {
439 // LCategory of transaction
440 // L[Transfer account name]
441 // LCategory of transaction/Class of transaction
442 // L[Transfer account]/Class of transaction
443 // this is managed at insertion
444 if(*value != '\0')
445 {
446 g_free(tran.category);
447 g_strstrip(value);
448 tran.category = g_strdup(value);
449 }
450 break;
451 }
452
453 case 'S':
454 case 'E':
455 case '$':
456 {
457 if(tran.nb_splits < TXN_MAX_SPLIT)
458 {
459 switch(qif_line[0])
460 {
461 case 'S': // split category
462 {
463 GenSplit *s = &tran.splits[tran.nb_splits];
464 if(*value != '\0')
465 {
466 g_free(s->category);
467 g_strstrip(value);
468 s->category = g_strdup(value);
469 }
470 break;
471 }
472
473 case 'E': // split memo
474 {
475 GenSplit *s = &tran.splits[tran.nb_splits];
476 if(*value != '\0')
477 {
478 g_free(s->memo);
479 s->memo = g_strdup(value);
480 }
481 break;
482 }
483
484 case '$': // split amount
485 {
486 GenSplit *s = &tran.splits[tran.nb_splits];
487
488 s->amount = hb_qif_parser_get_amount(value);
489 // $ line normally end a split
490 #if MYDEBUG == 1
491 g_print(" -> new split added: [%d] S=%s, E=%s, $=%.2f\n", tran.nb_splits, s->category, s->memo, s->amount);
492 #endif
493
494 tran.nb_splits++;
495 break;
496 }
497 }
498
499 }
500 // end split
501 break;
502 }
503
504 case '^': // end of line
505 {
506 GenTxn *newitem;
507
508 //fix: 380550
509 if( tran.date )
510 {
511 //ensure we have an account
512 //todo: check this
513 if(genacc == NULL)
514 {
515 genacc = hb_import_gen_acc_get_next (ctx, FILETYPE_QIF, NULL, NULL);
516 }
517
518 tran.account = g_strdup(genacc->name);
519
520 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) );
521
522 newitem = da_gen_txn_malloc();
523 da_gen_txn_move(&tran, newitem);
524 da_gen_txn_append(ctx, newitem);
525 }
526
527 //unvalid tran
528 tran.date = 0;
529 //todo: should clear mem alloc here
530
531 tran.nb_splits = 0;
532 break;
533 }
534
535 }
536 // end of switch
537
538 }
539 // end QIF_TRANSACTION
540 }
541 // end of stat normal
542 g_free(qif_line);
543 }
544 // end of for loop
545
546 g_io_channel_unref (io);
547 }
548
549 }
550
551
552
553