1 #ident "Kalopa: $Id: ofxread.c,v 1.6 2008/10/15 15:11:13 dtynan Exp $"
2
3 /*
4 * $Id: ofxread.c,v 1.6 2008/10/15 15:11:13 dtynan Exp $
5 *
6 * Copyright (c) 2005, Kalopa Research Limited. All rights reserved.
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published
9 * by the Free Software Foundation; either version 2, or (at your
10 * option) any later version.
11 *
12 * It is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this product; see the file COPYING. If not, write to
19 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
20 * USA.
21 *
22 * THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH LIMITED "AS IS" AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KALOPA
26 * RESEARCH LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
29 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * ABSTRACT
36 *
37 * $Log: ofxread.c,v $
38 * Revision 1.6 2008/10/15 15:11:13 dtynan
39 * Mammoth change to convert from old-style database configuration to new
40 * schema where an RC file defines the database access.
41 *
42 * Revision 1.5 2008/10/13 20:59:37 dtynan
43 * Extensive changes (part 1) for change to database configuration.
44 *
45 * Revision 1.4 2008/01/15 18:10:43 dtynan
46 * Changed to use new company name and copyright, also fixed a few old
47 * files with the wrong license.
48 *
49 * Revision 1.3 2006/07/31 10:17:51 dtynan
50 * Massive changeover to a more Ruby/Rails primary key naming convention.
51 *
52 * Revision 1.2 2005/03/22 09:41:20 dtynan
53 * Extensive upgrade to make sure the copyright block was
54 * consistent and GPL'ed.
55 *
56 * Revision 1.1 2005/03/07 16:51:13 dtynan
57 * New utility to parse an OFX file and populate the database.
58 */
59
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <unistd.h>
63 #include <syslog.h>
64 #include <string.h>
65 #include <time.h>
66 #include <ctype.h>
67
68 #include "beanie.h"
69 #include "hacks.h"
70
71 #define PROGNAME "ofxread"
72
73 #define OFXHDR_HEADER 0
74 #define OFXHDR_DTYPE 1
75 #define OFXHDR_VERSION 2
76 #define OFXHDR_SECURITY 3
77 #define OFXHDR_ENCODE 4
78 #define OFXHDR_CHARSET 5
79 #define OFXHDR_COMPR 6
80 #define OFXHDR_OLDUID 7
81 #define OFXHDR_NEWUID 8
82
83 #define OFXBODY_OFX 9
84 #define OFXBODY_SIGNON 10
85 #define OFXBODY_SONRS 11
86 #define OFXBODY_STATUS 12
87 #define OFXBODY_CCMSG 13
88 #define OFXBODY_CCTNRS 14
89 #define OFXBODY_CCSTMT 15
90 #define OFXBODY_CCFROM 16
91 #define OFXBODY_BANKTL 17
92 #define OFXBODY_STMNT 18
93 #define OFXBODY_LEDGER 19
94
95 #define OFXTAG_ACCTID 20
96 #define OFXTAG_BALAMT 21
97 #define OFXTAG_CODE 22
98 #define OFXTAG_CURDEF 23
99 #define OFXTAG_DTACCTUP 24
100 #define OFXTAG_DTASOF 25
101 #define OFXTAG_DTEND 26
102 #define OFXTAG_DTPOSTED 27
103 #define OFXTAG_DTSERVER 28
104 #define OFXTAG_DTSTART 29
105 #define OFXTAG_DTUSER 30
106 #define OFXTAG_FITID 31
107 #define OFXTAG_LANGUAGE 32
108 #define OFXTAG_NAME 33
109 #define OFXTAG_SEVERITY 34
110 #define OFXTAG_TRNAMT 35
111 #define OFXTAG_TRNTYPE 36
112 #define OFXTAG_TRNUID 37
113
114 /*
115 * Header types...
116 */
117 struct header {
118 char *name;
119 int type;
120 } headers[] = {
121 {"ACCTID", OFXTAG_ACCTID},
122 {"BALAMT", OFXTAG_BALAMT},
123 {"BANKTRANLIST", OFXBODY_BANKTL},
124 {"CCACCTFROM", OFXBODY_CCFROM},
125 {"CCSTMTRS", OFXBODY_CCSTMT},
126 {"CCSTMTTRNRS", OFXBODY_CCTNRS},
127 {"CHARSET", OFXHDR_CHARSET},
128 {"CODE", OFXTAG_CODE},
129 {"COMPRESSION", OFXHDR_COMPR},
130 {"CREDITCARDMSGSRSV1", OFXBODY_CCMSG},
131 {"CURDEF", OFXTAG_CURDEF},
132 {"DATA", OFXHDR_DTYPE},
133 {"DTACCTUP", OFXTAG_DTACCTUP},
134 {"DTASOF", OFXTAG_DTASOF},
135 {"DTEND", OFXTAG_DTEND},
136 {"DTPOSTED", OFXTAG_DTPOSTED},
137 {"DTSERVER", OFXTAG_DTSERVER},
138 {"DTSTART", OFXTAG_DTSTART},
139 {"DTUSER", OFXTAG_DTUSER},
140 {"ENCODING", OFXHDR_ENCODE},
141 {"FITID", OFXTAG_FITID},
142 {"LANGUAGE", OFXTAG_LANGUAGE},
143 {"LEDGERBAL", OFXBODY_LEDGER},
144 {"NAME", OFXTAG_NAME},
145 {"NEWFILEUID", OFXHDR_NEWUID},
146 {"OFX", OFXBODY_OFX},
147 {"OFXHEADER", OFXHDR_HEADER},
148 {"OLDFILEUID", OFXHDR_OLDUID},
149 {"SECURITY", OFXHDR_SECURITY},
150 {"SEVERITY", OFXTAG_SEVERITY},
151 {"SIGNONMSGSRSV1", OFXBODY_SIGNON},
152 {"SONRS", OFXBODY_SONRS},
153 {"STATUS", OFXBODY_STATUS},
154 {"STMTTRN", OFXBODY_STMNT},
155 {"TRNAMT", OFXTAG_TRNAMT},
156 {"TRNTYPE", OFXTAG_TRNTYPE},
157 {"TRNUID", OFXTAG_TRNUID},
158 {"VERSION", OFXHDR_VERSION},
159 {NULL, 0}
160 };
161
162 /*
163 *
164 */
165 struct ofxhdr {
166 int headerid;
167 int dtype;
168 int version;
169 int security;
170 int encoding;
171 int charset;
172 int compression;
173 int oldfileuid;
174 int newfileuid;
175 };
176
177 int lineno;
178 int nerrors;
179 int verbose;
180 int nomacct;
181 char *fname;
182
183 struct db_creditcard *ccp = NULL;
184
185 /*
186 *
187 */
188 int handlestate(FILE *, int, int);
189 void findaccount(char *);
190 void error(char *);
191 void usage();
192
193 /*
194 *
195 */
196 int
main(int argc,char * argv[])197 main(int argc, char *argv[])
198 {
199 int i;
200 char *cname;
201 struct db_company *comp;
202 FILE *fp;
203
204 cname = NULL;
205 opterr = verbose = 0;
206 nomacct = 0;
207 while ((i = getopt(argc, argv, "c:qv")) != EOF) {
208 switch (i) {
209 case 'c':
210 cname = optarg;
211 break;
212
213 case 'v':
214 verbose = 1;
215 break;
216
217 default:
218 usage();
219 }
220 }
221 /*
222 * Find the company that we're processing...
223 */
224 if (loadconfig(cname) < 0)
225 berror(PROGNAME, "cannot load configuration file");
226 if ((argc - optind) < 1)
227 usage();
228 while (optind < argc) {
229 lineno = nerrors = 0;
230 fname = argv[optind++];
231 if ((fp = fopen(fname, "r")) == NULL) {
232 fprintf(stderr, "?ofxload: ");
233 perror(fname);
234 } else {
235 handlestate(fp, 0, 0);
236 fclose(fp);
237 }
238 }
239 exit(0);
240 }
241
242 /*
243 *
244 */
245 int
handlestate(FILE * fp,int indent,int type)246 handlestate(FILE *fp, int indent, int type)
247 {
248 int i, isend;
249 char *cp, *xp, input[512];
250 struct header *hp;
251 dbow_conn *dbp;
252
253 while (fgets(input, sizeof(input), fp) != NULL) {
254 lineno++;
255 if ((cp = strpbrk(input, "\n\r")) != NULL)
256 *cp = '\0';
257 if (indent == 0) {
258 if (strcmp(input, "<OFX>") == 0) {
259 handlestate(fp, 1, OFXBODY_OFX);
260 return(0);
261 }
262 if ((cp = strchr(input, ':')) == NULL) {
263 error("invalid syntax");
264 continue;
265 }
266 *cp++ = '\0';
267 for (hp = headers; hp->name != NULL; hp++)
268 if (strcmp(input, hp->name) == 0)
269 break;
270 if (hp->name == NULL) {
271 error("invalid syntax");
272 continue;
273 }
274 } else {
275 cp = input;
276 if (*cp++ != '<') {
277 error("invalid syntax");
278 continue;
279 }
280 if (*cp == '/') {
281 isend = 1;
282 cp++;
283 } else
284 isend = 0;
285 if ((xp = strchr(cp, '>')) != NULL)
286 *xp++ = '\0';
287 for (hp = headers; hp->name != NULL; hp++)
288 if (strcmp(cp, hp->name) == 0)
289 break;
290 if (hp->name == NULL) {
291 error("invalid OFX symbol");
292 return(0);
293 }
294 if (isend)
295 return(hp->type);
296 switch (hp->type) {
297 case OFXBODY_SIGNON:
298 case OFXBODY_SONRS:
299 case OFXBODY_STATUS:
300 case OFXBODY_CCMSG:
301 case OFXBODY_CCTNRS:
302 case OFXBODY_CCSTMT:
303 case OFXBODY_CCFROM:
304 case OFXBODY_BANKTL:
305 case OFXBODY_LEDGER:
306 i = handlestate(fp, indent + 1, hp->type);
307 break;
308
309 case OFXTAG_ACCTID:
310 findaccount(xp);
311 break;
312
313 case OFXBODY_STMNT:
314 if (nomacct == 0) {
315 error("cannot find card account");
316 break;
317 }
318 if ((ccp = db_creditcardalloc()) == NULL)
319 break;
320 if (verbose)
321 printf(">>> Transaction:-\n");
322 ccp->nomacct_id = nomacct;
323 i = handlestate(fp, indent + 1, hp->type);
324 if (ccp != NULL) {
325 ccp->journal_id = 0;
326 ccp->state = 0;
327 db_insertcreditcard(dbp, ccp);
328 db_creditcardfree(ccp);
329 ccp = NULL;
330 }
331 break;
332
333 case OFXTAG_TRNTYPE:
334 if (ccp == NULL)
335 break;
336 if (verbose)
337 printf("TX type:\t%s\n", xp);
338 if (strcmp(xp, "DEBIT") == 0)
339 ccp->crtype = DEBIT;
340 else if (strcmp(xp, "CREDIT") == 0)
341 ccp->crtype = CREDIT;
342 break;
343
344 case OFXTAG_DTPOSTED:
345 if (ccp == NULL)
346 break;
347 parsedate(xp, &ccp->pdate);
348 if (verbose)
349 printf("Date posted:\t%s", ctime(&ccp->pdate));
350 break;
351
352 case OFXTAG_DTUSER:
353 if (ccp == NULL)
354 break;
355 parsedate(xp, &ccp->udate);
356 if (verbose)
357 printf("Date of TX:\t%s", ctime(&ccp->udate));
358 break;
359
360 case OFXTAG_TRNAMT:
361 if (ccp == NULL)
362 break;
363 ccp->amount = atof(xp);
364 if (verbose)
365 printf("Amount:\t\t%.2f\n", ccp->amount);
366 break;
367
368 case OFXTAG_FITID:
369 if (ccp == NULL)
370 break;
371 if (db_findcreditcardbyfitid(dbp,xp) != NULL) {
372 error("duplicate transaction");
373 db_creditcardfree(ccp);
374 ccp = NULL;
375 break;
376 }
377 ccp->fitid = strdup(xp);
378 if (verbose)
379 printf("FITID:\t\t%s\n", ccp->fitid);
380 break;
381
382 case OFXTAG_NAME:
383 if (ccp == NULL)
384 break;
385 ccp->descr = strdup(xp);
386 if (verbose)
387 printf("Description:\t%s\n", ccp->descr);
388 break;
389 }
390 }
391 }
392 return(0);
393 }
394
395 /*
396 *
397 */
398 void
findaccount(char * acctno)399 findaccount(char *acctno)
400 {
401 char *cp, *xp;
402 struct db_bankacct *bap;
403 dbow_conn *dbp = getdbase();
404
405 for (bap = db_findbankacctfirst(dbp); bap != NULL;
406 bap = db_findbankacctnext(dbp, bap)) {
407 for (cp = acctno, xp =bap->number; *cp != '\0'; cp++, xp++) {
408 if (*xp == ' ')
409 xp++;
410 if (*cp != 'X' && *cp != 'x' && *cp != *xp)
411 break;
412 }
413 if (*cp == '\0') {
414 nomacct = bap->nomacct_id;
415 if (verbose)
416 printf("Account: %s\n", bap->name);
417 break;
418 }
419 }
420 if (bap != NULL)
421 db_bankacctfree(bap);
422 }
423
424 /*
425 *
426 */
427 void
error(char * msg)428 error(char *msg)
429 {
430 fprintf(stderr, "\"%s\", line %d: %s.\n", fname, lineno, msg);
431 nerrors++;
432 }
433
434 /*
435 *
436 */
437 void
usage()438 usage()
439 {
440 fprintf(stderr, "Usage: ofxload [-c <comp>] [-v] file.ofx ...\n");
441 exit(2);
442 }
443