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