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