GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
qif-file.c
1 /*
2  * qif-file.c -- parse a QIF File into its pieces
3  *
4  * Written by: Derek Atkins <derek@@ihtfp.com>
5  * Copyright (c) 2003 Derek Atkins <[email protected]>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, contact:
19  *
20  * Free Software Foundation Voice: +1-617-542-5942
21  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
22  * Boston, MA 02110-1301, USA [email protected]
23  */
24 
25 
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 
30 #include <glib.h>
31 #include <glib/gstdio.h>
32 #include <string.h>
33 
34 #include "gnc-engine.h"
35 
36 #include "qif-import-p.h"
37 #include "qif-objects-p.h"
38 
39 static QofLogModule log_module = GNC_MOD_IMPORT;
40 
41 
42 static QifLine
43 qif_make_line(const char* buf, gint lineno)
44 {
45  QifLine line;
46  g_return_val_if_fail(buf && *buf, NULL);
47 
48  line = g_new0(struct _QifLine, 1);
49  line->type = *buf;
50  line->lineno = lineno;
51  line->line = g_strdup(buf + 1);
52 
53  return line;
54 }
55 
56 void
57 qif_record_destroy(GList *record)
58 {
59  GList *node;
60  QifLine line;
61 
62  for (node = record; node; node = node->next)
63  {
64  line = node->data;
65  g_free(line->line);
66  g_free(line);
67  }
68 
69  g_list_free(record);
70 }
71 
72 /* This returns a record, which is a bunch of QifLines, ending
73  * with a line with just a '^'. If it finds a line that begins
74  * with a !, then destroy the current record state, set the "found_bangtype",
75  * and return NULL.
76  */
77 static GList *
78 qif_make_record(QifContext ctx, char *buf, size_t bufsiz, gboolean *found_bangtype)
79 {
80  GList *record = NULL;
81  QifLine line;
82 
83  g_return_val_if_fail(ctx, NULL);
84  g_return_val_if_fail(buf, NULL);
85  g_return_val_if_fail(found_bangtype, NULL);
86 
87  *found_bangtype = FALSE;
88 
89  while (fgets(buf, bufsiz, ctx->fp) != NULL)
90  {
91 
92  /* increment the line number */
93  ctx->lineno++;
94 
95  /* strip start/end whitespace */
96  g_strstrip(buf);
97 
98  /* if there is nothing left in the string, ignore it */
99  if (strlen(buf) == 0)
100  continue;
101 
102  /* If this is a bangline, then set the flag, clear our state, and return NULL */
103  if (*buf == '!')
104  {
105  *found_bangtype = TRUE;
106  break;
107  }
108 
109  /* See if this is an End of Record marker */
110  if (*buf == '^')
111  {
112  /* Yep. If we've got a record then break and return ... */
113  if (record)
114  break;
115  /* ... otherwise just continue reading (i.e. ignore empty records) */
116  else
117  continue;
118  }
119 
120  /* otherwise, add the line to the list */
121  line = qif_make_line(buf, ctx->lineno);
122  if (line)
123  record = g_list_prepend(record, line);
124 
125  /* and continue... */
126  }
127 
128  /* If we found a bangtype, destroy anything we've collected */
129  if (*found_bangtype)
130  {
131  if (record)
132  PERR("error loading file: incomplete record at line %d", ctx->lineno);
133 
134  qif_record_destroy(record);
135  record = NULL;
136  }
137 
138  return g_list_reverse(record);
139 }
140 
141 /* read a qif file and parse it, line by line
142  * return QIF_E_OK on success or some other QIF Error.
143  */
144 static QifError
145 qif_read_file(QifContext ctx, FILE *f)
146 {
147  char buf[BUFSIZ];
148  GList *record;
149  gboolean found_bang;
150  QifError err = QIF_E_OK;
151 
152  g_return_val_if_fail(ctx, QIF_E_BADARGS);
153  g_return_val_if_fail(f, QIF_E_BADARGS);
154 
155  ctx->fp = f;
156  ctx->lineno = -1;
157 
158  do
159  {
160  found_bang = FALSE;
161  record = qif_make_record(ctx, buf, sizeof(buf), &found_bang);
162 
163  /* If we got a record, process it */
164  if (record)
165  {
166  if (!ctx->handler || !ctx->handler->parse_record)
167  {
168  PERR("Trying to process QIF record without a handler at %d", ctx->lineno);
169  }
170  else
171  {
172  err = ctx->handler->parse_record(ctx, record);
173  }
174 
175  /* Now destroy it; we don't need it anymore */
176  qif_record_destroy(record);
177  }
178 
179  /* if we found a bangtype, process that */
180  if (found_bang)
181  {
182  g_assert(*buf == '!');
183 
184  /* First, process the end of the last handler. This could possibly
185  * merge items into the context or perform some other operation
186  */
187  if (ctx->handler && ctx->handler->end)
188  {
189  err = ctx->handler->end(ctx);
190  if (err != QIF_E_OK)
191  break;
192  }
193 
194  /* Now process the bangtype (stored in buf) to set the new handler */
195  qif_parse_bangtype(ctx, buf);
196  }
197 
198  }
199  while ((record || found_bang) && err == QIF_E_OK);
200 
201  /* Make sure to run any end processor */
202  if (err == QIF_E_OK && ctx->handler && ctx->handler->end)
203  err = ctx->handler->end(ctx);
204 
205  if (err == QIF_E_OK)
206  qif_object_list_reverse(ctx, QIF_O_TXN);
207 
208  return err;
209 }
210 
211 static QifError
212 qif_import_file(QifContext ctx, const char *filename)
213 {
214  QifError err;
215  FILE *fp;
216 
217  g_return_val_if_fail(ctx, QIF_E_BADARGS);
218  g_return_val_if_fail(filename, QIF_E_BADARGS);
219  g_return_val_if_fail(*filename, QIF_E_BADARGS);
220 
221  /* Open the file */
222  fp = g_fopen(filename, "r");
223  if (fp == NULL)
224  return QIF_E_NOFILE;
225 
226  ctx->filename = g_strdup(filename);
227 
228  /* read the file */
229  err = qif_read_file(ctx, fp);
230 
231  /* close the file */
232  fclose(fp);
233 
234  return err;
235 }
236 
237 
239 qif_file_new(QifContext ctx, const char *filename)
240 {
241  QifContext fctx;
242 
243  g_return_val_if_fail(ctx, NULL);
244  g_return_val_if_fail(filename, NULL);
245 
246  fctx = qif_context_new();
247 
248  /* we should assume that we've got a bank account... just in case.. */
249  qif_parse_bangtype(fctx, "!type:bank");
250 
251  /* Open the file */
252  if (qif_import_file(fctx, filename) != QIF_E_OK)
253  {
254  qif_context_destroy(fctx);
255  fctx = NULL;
256  }
257 
258  /* Return the new context */
259  if (fctx)
260  {
261  ctx->files = g_list_prepend(ctx->files, fctx);
262  fctx->parent = ctx;
263 
264  /* Make sure the file gets merged into the parent */
265  ctx->parsed = FALSE;
266  }
267 
268  return fctx;
269 }
270 
271 QifError
272 qif_file_parse(QifContext ctx, gpointer ui_args)
273 {
274  g_return_val_if_fail(ctx, QIF_E_BADARGS);
275  g_return_val_if_fail(!qif_file_needs_account(ctx), QIF_E_BADSTATE);
276 
277  qif_parse_all(ctx, ui_args);
278  ctx->parsed = TRUE;
279 
280  return QIF_E_OK;
281 }
282 
283 gboolean
284 qif_file_needs_account(QifContext ctx)
285 {
286  g_return_val_if_fail(ctx, FALSE);
287 
288  return ((ctx->parse_flags & QIF_F_TXN_NEEDS_ACCT) ||
289  (ctx->parse_flags & QIF_F_ITXN_NEEDS_ACCT));
290 }
291 
292 const char *
293 qif_file_filename(QifContext ctx)
294 {
295  g_return_val_if_fail(ctx, NULL);
296  return ctx->filename;
297 }
298 
299 static void
300 set_txn_acct(gpointer obj, gpointer arg)
301 {
302  QifTxn txn = obj;
303  QifAccount acct = arg;
304 
305  if (!txn->from_acct)
306  txn->from_acct = acct;
307 }
308 
309 void
310 qif_file_set_default_account(QifContext ctx, const char *acct_name)
311 {
312  QifAccount acct;
313 
314  g_return_if_fail(ctx);
315  g_return_if_fail(acct_name);
316 
317  if (! qif_file_needs_account(ctx)) return;
318 
319  acct = find_or_make_acct(ctx, g_strdup(acct_name),
320  qif_parse_acct_type_guess(ctx->parse_type));
321 
322  qif_object_list_foreach(ctx, QIF_O_TXN, set_txn_acct, acct);
323 
324  qif_clear_flag(ctx->parse_flags, QIF_F_TXN_NEEDS_ACCT);
325  qif_clear_flag(ctx->parse_flags, QIF_F_ITXN_NEEDS_ACCT);
326 }
#define PERR(format, args...)
Definition: qoflog.h:237
All type declarations for the whole Gnucash engine.
const gchar * QofLogModule
Definition: qofid.h:89