/src/import-export/qif/qif-parse.c
C | 935 lines | 642 code | 154 blank | 139 comment | 70 complexity | 0022d93a5aa64a9743ec54dbb522c3ad MD5 | raw file
Possible License(s): GPL-2.0
- /*
- * qif-parse.c -- parse QIF
- *
- * Written by: Derek Atkins <derek@ihtfp.com>
- * Copyright (c) 2003 Derek Atkins <warlord@MIT.EDU>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, contact:
- *
- * Free Software Foundation Voice: +1-617-542-5942
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
- * Boston, MA 02110-1301, USA gnu@gnu.org
- */
- #ifdef HAVE_CONFIG_H
- #include "config.h"
- #endif
- #include <glib.h>
- #include <glib/gi18n.h>
- #include <string.h>
- /* For regex */
- #include <sys/types.h>
- #include <regex.h>
- #include <stdarg.h>
- #include "gnc-engine.h"
- #include "gnc-ui-util.h"
- #include "qif-import-p.h"
- #include "qif-objects-p.h"
- #include "import-parse.h"
- static QofLogModule log_module = GNC_MOD_IMPORT;
- /* An array of handlers for the various bang-types */
- static QifHandler qif_handlers[QIF_TYPE_MAX+1] = { NULL };
- /* Parser Regular Expressions */
- static gboolean qifp_regex_compiled = FALSE;
- static regex_t category_regex;
- /* A Hash Table of bang-types */
- static GHashTable *qif_bangtype_map = NULL;
- /* A Hash Table of action strings */
- static GHashTable *qif_action_map = NULL;
- /* A Hash Table of account types */
- static GHashTable *qif_atype_map = NULL;
- /************************************************************************/
- /* Register a handler */
- void
- qif_register_handler(QifType type, QifHandler handler)
- {
- if (type <= 0 || type > QIF_TYPE_MAX)
- {
- PERR("Invalid type: %d", type);
- return;
- }
- qif_handlers[type] = handler;
- }
- static void
- compile_regex()
- {
- regcomp(&category_regex,
- "^ *(\\[)?([^]/|]*)(]?)(/([^|]*))?(\\|(\\[)?([^]/]*)(]?)(/(.*))?)? *$",
- REG_EXTENDED);
- qifp_regex_compiled = TRUE;
- }
- #define QIF_ADD_TYPE(ts,t) \
- g_hash_table_insert(qif_bangtype_map, ts, GINT_TO_POINTER(t)); \
- g_hash_table_insert(qif_bangtype_map, _(ts), GINT_TO_POINTER(t));
- static void
- build_bangtype_map()
- {
- g_return_if_fail(!qif_bangtype_map);
- qif_bangtype_map = g_hash_table_new(g_str_hash, g_str_equal);
- g_assert(qif_bangtype_map);
- /* Translators FIXME: It is unclear whether these strings should
- really be translated, and if yes, into which translation. */
- QIF_ADD_TYPE(N_("type:bank"), QIF_TYPE_BANK);
- QIF_ADD_TYPE(N_("type:cash"), QIF_TYPE_CASH);
- QIF_ADD_TYPE(N_("type:ccard"), QIF_TYPE_CCARD);
- QIF_ADD_TYPE(N_("type:invst"), QIF_TYPE_INVST);
- QIF_ADD_TYPE(N_("type:port"), QIF_TYPE_PORT);
- QIF_ADD_TYPE(N_("type:oth a"), QIF_TYPE_OTH_A);
- QIF_ADD_TYPE(N_("type:oth l"), QIF_TYPE_OTH_L);
- QIF_ADD_TYPE(N_("type:class"), QIF_TYPE_CLASS);
- QIF_ADD_TYPE(N_("type:cat"), QIF_TYPE_CAT);
- QIF_ADD_TYPE(N_("type:security"), QIF_TYPE_SECURITY);
- QIF_ADD_TYPE(N_("account"), QIF_ACCOUNT);
- QIF_ADD_TYPE(N_("option:autoswitch"), QIF_AUTOSWITCH);
- QIF_ADD_TYPE(N_("clear:autoswitch"), QIF_CLEAR_AUTOSWITCH);
- }
- #undef QIF_ADD_TYPE
- #define QIF_ADD_ACT(ts,t) \
- g_hash_table_insert(qif_action_map, ts, GINT_TO_POINTER(t));
- static void
- build_action_map()
- {
- g_return_if_fail(!qif_action_map);
- qif_action_map = g_hash_table_new(g_str_hash, g_str_equal);
- g_assert(qif_action_map);
- QIF_ADD_ACT("buy", QIF_A_BUY);
- QIF_ADD_ACT("cvrshrt", QIF_A_BUY);
- QIF_ADD_ACT("kauf", QIF_A_BUY);
- QIF_ADD_ACT("buyx", QIF_A_BUYX);
- QIF_ADD_ACT("cvrshrtx", QIF_A_BUYX);
- QIF_ADD_ACT("kaufx", QIF_A_BUYX);
- QIF_ADD_ACT("cglong", QIF_A_CGLONG);
- QIF_ADD_ACT("kapgew", QIF_A_CGLONG); /* Kapitalgewinnsteuer */
- QIF_ADD_ACT("cglongx", QIF_A_CGLONG);
- QIF_ADD_ACT("kapgewx", QIF_A_CGLONG);
- QIF_ADD_ACT("cgmid", QIF_A_CGMID);
- QIF_ADD_ACT("cgmidx", QIF_A_CGMIDX);
- QIF_ADD_ACT("cgshort", QIF_A_CGSHORT);
- QIF_ADD_ACT("k.gewsp", QIF_A_CGSHORT);
- QIF_ADD_ACT("cgshortx", QIF_A_CGSHORTX);
- QIF_ADD_ACT("k.gewspx", QIF_A_CGSHORTX);
- QIF_ADD_ACT("div", QIF_A_DIV); /* dividende */
- QIF_ADD_ACT("divx", QIF_A_DIVX);
- //QIF_ADD_ACT("exercise", QIF_A_EXERCISE);
- //QIF_ADD_ACT("exercisex", QIF_A_EXERCISEX);
- //QIF_ADD_ACT("expire", QIF_A_EXPIRE);
- //QIF_ADD_ACT("grant", QIF_A_GRANT);
- QIF_ADD_ACT("int", QIF_A_INTINC);
- QIF_ADD_ACT("intinc", QIF_A_INTINC);
- QIF_ADD_ACT("aktzu", QIF_A_INTINC); /* zinsen */
- QIF_ADD_ACT("intx", QIF_A_INTINCX);
- QIF_ADD_ACT("intincx", QIF_A_INTINCX);
- QIF_ADD_ACT("margint", QIF_A_MARGINT);
- QIF_ADD_ACT("margintx", QIF_A_MARGINTX);
- QIF_ADD_ACT("miscexp", QIF_A_MISCEXP);
- QIF_ADD_ACT("miscexpx", QIF_A_MISCEXPX);
- QIF_ADD_ACT("miscinc", QIF_A_MISCINC);
- QIF_ADD_ACT("cash", QIF_A_MISCINC);
- QIF_ADD_ACT("miscincx", QIF_A_MISCINCX);
- QIF_ADD_ACT("reinvdiv", QIF_A_REINVDIV);
- QIF_ADD_ACT("reinvint", QIF_A_REINVINT);
- QIF_ADD_ACT("reinvzin", QIF_A_REINVINT);
- QIF_ADD_ACT("reinvlg", QIF_A_REINVLG);
- QIF_ADD_ACT("reinvkur", QIF_A_REINVLG);
- QIF_ADD_ACT("reinvmd", QIF_A_REINVMD);
- QIF_ADD_ACT("reinvsg", QIF_A_REINVSG);
- QIF_ADD_ACT("reinvksp", QIF_A_REINVSG);
- QIF_ADD_ACT("reinvsh", QIF_A_REINVSH);
- QIF_ADD_ACT("reminder", QIF_A_REMINDER);
- QIF_ADD_ACT("erinnerg", QIF_A_REMINDER);
- QIF_ADD_ACT("rtrncap", QIF_A_RTRNCAP);
- QIF_ADD_ACT("rtrncapx", QIF_A_RTRNCAPX);
- QIF_ADD_ACT("sell", QIF_A_SELL);
- QIF_ADD_ACT("shtsell", QIF_A_SELL);
- QIF_ADD_ACT("verkauf", QIF_A_SELL); /* verkaufen */
- QIF_ADD_ACT("sellx", QIF_A_SELLX);
- QIF_ADD_ACT("shtsellx", QIF_A_SELLX);
- QIF_ADD_ACT("verkaufx", QIF_A_SELLX); /* verkaufen */
- QIF_ADD_ACT("shrsin", QIF_A_SHRSIN);
- QIF_ADD_ACT("aktzu", QIF_A_SHRSIN);
- QIF_ADD_ACT("shrsout", QIF_A_SHRSOUT);
- QIF_ADD_ACT("aktab", QIF_A_SHRSOUT);
- QIF_ADD_ACT("stksplit", QIF_A_STKSPLIT);
- QIF_ADD_ACT("aktsplit", QIF_A_STKSPLIT);
- //QIF_ADD_ACT("vest", QIF_A_VEST);
- QIF_ADD_ACT("xin", QIF_A_XIN);
- QIF_ADD_ACT("contribx", QIF_A_XIN);
- QIF_ADD_ACT("xout", QIF_A_XOUT);
- QIF_ADD_ACT("withdrwx", QIF_A_XOUT);
- }
- #undef QIF_ADD_ACT
- static GList *
- make_list(int count, ...)
- {
- GList *result = NULL;
- GNCAccountType type;
- va_list ap;
- va_start (ap, count);
- while (count--)
- {
- type = va_arg (ap, GNCAccountType);
- result = g_list_prepend (result, GINT_TO_POINTER(type));
- }
- va_end (ap);
- return g_list_reverse(result);
- }
- #define QIF_ADD_ATYPE(a,t) g_hash_table_insert(qif_atype_map, a, t);
- static void
- build_atype_map()
- {
- g_return_if_fail(!qif_atype_map);
- qif_atype_map = g_hash_table_new(g_str_hash, g_str_equal);
- g_assert(qif_atype_map);
- QIF_ADD_ATYPE("bank", make_list(1, ACCT_TYPE_BANK));
- QIF_ADD_ATYPE("port", make_list(1, ACCT_TYPE_BANK));
- QIF_ADD_ATYPE("cash", make_list(1, ACCT_TYPE_CASH));
- QIF_ADD_ATYPE("ccard", make_list(1, ACCT_TYPE_CREDIT));
- QIF_ADD_ATYPE("invst", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_STOCK,
- ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("oth a", make_list(3, ACCT_TYPE_ASSET, ACCT_TYPE_BANK,
- ACCT_TYPE_CASH));
- QIF_ADD_ATYPE("oth l", make_list(2, ACCT_TYPE_LIABILITY, ACCT_TYPE_CREDIT));
- QIF_ADD_ATYPE("mutual", make_list(3, ACCT_TYPE_BANK, ACCT_TYPE_MUTUAL,
- ACCT_TYPE_STOCK));
- /* Internal types */
- QIF_ADD_ATYPE("__any_bank__", make_list(5, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
- ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
- ACCT_TYPE_LIABILITY));
- QIF_ADD_ATYPE("__all__", make_list(7, ACCT_TYPE_BANK, ACCT_TYPE_CREDIT,
- ACCT_TYPE_CASH, ACCT_TYPE_ASSET,
- ACCT_TYPE_LIABILITY, ACCT_TYPE_STOCK,
- ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("__stock__", make_list(2, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL));
- QIF_ADD_ATYPE("__income__", make_list(1, ACCT_TYPE_INCOME));
- QIF_ADD_ATYPE("__expense__", make_list(1, ACCT_TYPE_EXPENSE));
- QIF_ADD_ATYPE("__equity__", make_list(1, ACCT_TYPE_EQUITY));
- }
- #undef QIF_ADD_ATYPE
- /************************************************************************/
- /*
- * We've got a !Type line. Parse the line into the appropriate
- * type and then initialize the handler.
- */
- void
- qif_parse_bangtype(QifContext ctx, const char *line)
- {
- QifType type;
- char *bangtype;
- gpointer result;
- g_return_if_fail(line && *line == '!');
- if (!qif_bangtype_map)
- build_bangtype_map();
- /* Make a local copy so we can manipulate it.
- * - strip off leading/trailing whitespace
- * - make it all lower case
- */
- bangtype = g_utf8_strdown(line + 1, -1);
- g_strstrip(bangtype);
- /* In some cases we get "!Type Bank" -- change the space to a colon */
- if (!strncmp(bangtype, "type ", 5))
- bangtype[5] = ':';
- /* Lookup the bangtype in the map and then destroy the local copy */
- result = g_hash_table_lookup(qif_bangtype_map, bangtype);
- g_free(bangtype);
- if (!result)
- {
- PWARN("Unknown bang-type at line %d: %s. Ignored", ctx->lineno, line);
- return;
- }
- type = GPOINTER_TO_INT(result);
- /* Set the current context parse type and handler */
- ctx->parse_type = type;
- ctx->handler = qif_handlers[type];
- /* now initialize this new parse type (if there's an init function) */
- if (ctx->handler && ctx->handler->init)
- ctx->handler->init(ctx);
- }
- /* returns TRUE if successful, FALSE if there is a problem */
- gboolean
- qif_parse_split_category(const char* str,
- char** cat, gboolean *cat_is_acct, char** cat_class,
- char** miscx_cat, gboolean *miscx_cat_is_acct,
- char **miscx_class)
- {
- /* This is a pretty f**ked up string. Basically it looks like:
- * ([)cat-or-acct(])(/(class))(|([)cat-of-acct(])(/ext))
- *
- * where data in parens is "optional" (depending on the context).
- *
- * examples from reality:
- *
- * category
- * category:subcategory
- * category/class
- * category:subcat/class
- * [account]
- * [account]/class
- *
- * cat/cat-class|miscx-cat/miscx-class
- */
- regmatch_t pmatch[12];
- g_return_val_if_fail(cat && cat_is_acct && cat_class &&
- miscx_cat && miscx_cat_is_acct && miscx_class, FALSE);
- if (!qifp_regex_compiled)
- compile_regex();
- if (regexec(&category_regex, str, 12, pmatch, 0) != 0)
- {
- PERR("category match failed");
- return FALSE;
- }
- /*
- * what the substrings mean:
- * 1 the opening [ for a transfer
- * 2 the category
- * 3 the closing ]
- * 4 the class /
- * 5 the class
- * 6 the miscx expression (whole thing)
- * 7 the opening [
- * 8 the miscx category
- * 9 the closing ]
- * 10 the class /
- * 11 the class
- */
- if (pmatch[2].rm_so == -1)
- {
- PERR("no category match found!");
- return FALSE;
- }
- /* catgory name */
- *cat = g_strndup(str + pmatch[2].rm_so, pmatch[2].rm_eo - pmatch[2].rm_so);
- /* category is account? */
- *cat_is_acct = (pmatch[1].rm_so != -1 && pmatch[3].rm_so != -1);
- /* category class */
- *cat_class = (pmatch[4].rm_so != -1 ?
- g_strndup(str + pmatch[5].rm_so, pmatch[5].rm_eo - pmatch[5].rm_so) :
- NULL);
- /* miscx category name */
- *miscx_cat = (pmatch[6].rm_so != -1 ?
- g_strndup(str + pmatch[8].rm_so, pmatch[8].rm_eo - pmatch[8].rm_so) :
- NULL);
- /* miscx cat is acct */
- *miscx_cat_is_acct = (pmatch[7].rm_so != -1 && pmatch[9].rm_so != -1);
- /* miscx class */
- *miscx_class = (pmatch[10].rm_so != -1 ?
- g_strndup(str + pmatch[11].rm_so,
- pmatch[11].rm_eo - pmatch[11].rm_so) : NULL);
- return TRUE;
- }
- /*
- * qif_parse_cleared -- parse the 'C'leared field of a QIF Transaction.
- * returns the QIF reconciled flag.
- *
- * * means cleared, x or X means reconciled, and ! or ? mean some
- * budget related stuff I don't understand.
- */
- QifRecnFlag
- qif_parse_cleared(QifLine line)
- {
- g_return_val_if_fail(line, QIF_R_NO);
- g_return_val_if_fail(line->line, QIF_R_NO);
- switch (*line->line)
- {
- case '*':
- return QIF_R_CLEARED;
- case 'x':
- case 'X':
- return QIF_R_RECONCILED;
- case '?':
- case '!':
- return QIF_R_BUDGETED;
- default:
- PERR("Unknown QIF Cleared flag at line %d: %s", line->lineno, line->line);
- return QIF_R_NO;
- }
- }
- QifAction qif_parse_action(QifLine line)
- {
- QifAction qaction;
- gpointer result;
- char *action;
- g_return_val_if_fail(line, QIF_A_NONE);
- g_return_val_if_fail(line->line, QIF_A_NONE);
- if (!qif_action_map)
- build_action_map();
- /* Duplicate the action and force it to lower case and strip any spaces */
- action = g_utf8_strdown(line->line, -1);
- g_strstrip(action);
- result = g_hash_table_lookup(qif_action_map, action);
- g_free(action);
- if (!result)
- {
- /* XXX: pop up a dialog? */
- PWARN("Unknown Action at line %d: %s. Some transactions may be discarded",
- line->lineno, line->line);
- return QIF_A_NONE;
- }
- qaction = GPOINTER_TO_INT(result);
- return qaction;
- }
- GList * qif_parse_acct_type(const char *str, gint lineno)
- {
- GList *result;
- char *type;
- if (!qif_atype_map)
- build_atype_map();
- /* Duplicate the type and force it to lower case and strip any spaces */
- type = g_utf8_strdown(str, -1);
- g_strstrip(type);
- result = g_hash_table_lookup(qif_atype_map, type);
- g_free(type);
- if (!result)
- {
- PWARN("Unknown account type at line %d: %s. ", lineno, str);
- result = g_hash_table_lookup(qif_atype_map, "bank");
- g_return_val_if_fail(result, NULL);
- }
- return result;
- }
- GList * qif_parse_acct_type_guess(QifType type)
- {
- const char *atype = NULL;
- switch (type)
- {
- case QIF_TYPE_BANK:
- atype = "bank";
- break;
- case QIF_TYPE_CASH:
- atype = "cash";
- break;
- case QIF_TYPE_CCARD:
- atype = "ccard";
- break;
- case QIF_TYPE_INVST:
- atype = "invst";
- break;
- case QIF_TYPE_PORT:
- atype = "port";
- break;
- case QIF_TYPE_OTH_A:
- atype = "oth a";
- break;
- case QIF_TYPE_OTH_L:
- atype = "oth l";
- break;
- default:
- return NULL;
- }
- return qif_parse_acct_type(atype, -1);
- }
- /***********************************************************************
- * Parsing numbers and dates...
- */
- typedef struct _parse_helper
- {
- QifContext ctx;
- GncImportFormat budget;
- GncImportFormat limit;
- GncImportFormat amount;
- GncImportFormat d_amount;
- GncImportFormat price;
- GncImportFormat shares;
- GncImportFormat commission;
- GncImportFormat date;
- } *parse_helper_t;
- #define QIF_PARSE_CHECK_NUMBER(str,help) { \
- if (str) (help) = gnc_import_test_numeric((str), (help)); \
- }
- #define QIF_PARSE_PARSE_NUMBER(str,fmt,val) { \
- if (str) gnc_import_parse_numeric((str), (fmt), (val)); \
- }
- static void
- qif_parse_check_account(gpointer key, gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifAccount acct = val;
- QIF_PARSE_CHECK_NUMBER(acct->limitstr, helper->limit);
- QIF_PARSE_CHECK_NUMBER(acct->budgetstr, helper->budget);
- }
- static void
- qif_parse_parse_account(gpointer key, gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifAccount acct = val;
- QIF_PARSE_PARSE_NUMBER(acct->limitstr, helper->limit, &acct->limit);
- QIF_PARSE_PARSE_NUMBER(acct->budgetstr, helper->budget, &acct->budget);
- }
- static void
- qif_parse_check_category(gpointer key, gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifCategory cat = val;
- QIF_PARSE_CHECK_NUMBER(cat->budgetstr, helper->budget);
- }
- static void
- qif_parse_parse_category(gpointer key, gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifCategory cat = val;
- QIF_PARSE_PARSE_NUMBER(cat->budgetstr, helper->budget, &cat->budget);
- }
- static void
- qif_parse_check_txn(gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifTxn txn = val;
- QifSplit split;
- QifInvstTxn itxn;
- GList *node;
- /* Check the date */
- helper->date = gnc_import_test_date(txn->datestr, helper->date);
- /* If this is an investment transaction, then all the info is in
- * the invst_info. Otherwise it's all in the splits.
- */
- itxn = txn->invst_info;
- if (itxn)
- {
- QIF_PARSE_CHECK_NUMBER(itxn->amountstr, helper->amount);
- QIF_PARSE_CHECK_NUMBER(itxn->d_amountstr, helper->d_amount);
- QIF_PARSE_CHECK_NUMBER(itxn->pricestr, helper->price);
- QIF_PARSE_CHECK_NUMBER(itxn->sharesstr, helper->shares);
- QIF_PARSE_CHECK_NUMBER(itxn->commissionstr, helper->commission);
- }
- else
- {
- split = txn->default_split;
- node = txn->splits;
- do
- {
- QIF_PARSE_CHECK_NUMBER(split->amountstr, helper->amount);
- if (node)
- {
- split = node->data;
- node = node->next;
- }
- else
- split = NULL;
- }
- while (split);
- }
- }
- static void
- qif_parse_parse_txn(gpointer val, gpointer data)
- {
- parse_helper_t helper = data;
- QifTxn txn = val;
- QifSplit split;
- QifInvstTxn itxn;
- GList *node;
- /* Parse the date */
- gnc_import_parse_date(txn->datestr, helper->date, &txn->date);
- /* If this is an investment transaction, then all the info is in
- * the invst_info. Otherwise it's all in the splits.
- */
- itxn = txn->invst_info;
- if (itxn)
- {
- QIF_PARSE_PARSE_NUMBER(itxn->amountstr, helper->amount, &itxn->amount);
- QIF_PARSE_PARSE_NUMBER(itxn->d_amountstr, helper->d_amount, &itxn->d_amount);
- QIF_PARSE_PARSE_NUMBER(itxn->pricestr, helper->price, &itxn->price);
- QIF_PARSE_PARSE_NUMBER(itxn->sharesstr, helper->shares, &itxn->shares);
- QIF_PARSE_PARSE_NUMBER(itxn->commissionstr, helper->commission,
- &itxn->commission);
- qif_invst_txn_setup_splits(helper->ctx, txn);
- }
- else
- {
- split = txn->default_split;
- node = txn->splits;
- do
- {
- QIF_PARSE_PARSE_NUMBER(split->amountstr, helper->amount, &split->amount);
- if (node)
- {
- split = node->data;
- node = node->next;
- }
- else
- split = NULL;
- }
- while (split);
- qif_txn_setup_splits(txn);
- }
- }
- void
- qif_parse_all(QifContext ctx, gpointer arg)
- {
- struct _parse_helper helper;
- helper.ctx = ctx;
- /* PARSE ACCOUNTS */
- /* First, figure out the formats */
- helper.limit = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.budget = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- qif_object_map_foreach(ctx, QIF_O_ACCOUNT, qif_parse_check_account, &helper);
- /* Make sure it's not ambiguous */
- if (helper.limit & (helper.limit - 1)) helper.limit = GNCIF_NUM_PERIOD;
- if (helper.budget & (helper.budget - 1)) helper.budget = GNCIF_NUM_PERIOD;
- /* Now convert the numbers */
- qif_object_map_foreach(ctx, QIF_O_ACCOUNT, qif_parse_parse_account, &helper);
- /* PARSE CATEGORIES */
- helper.budget = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- qif_object_map_foreach(ctx, QIF_O_CATEGORY, qif_parse_check_category, &helper);
- /* make sure it's not ambiguous */
- if (helper.budget & (helper.budget - 1)) helper.budget = GNCIF_NUM_PERIOD;
- /* Now convert the numbers */
- qif_object_map_foreach(ctx, QIF_O_CATEGORY, qif_parse_parse_category, &helper);
- /* PARSE TRANSACTIONS */
- helper.amount = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.d_amount = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.price = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.shares = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.commission = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA;
- helper.date = GNCIF_DATE_MDY | GNCIF_DATE_DMY | GNCIF_DATE_YMD | GNCIF_DATE_YDM;
- qif_object_list_foreach(ctx, QIF_O_TXN, qif_parse_check_txn, &helper);
- /* check/fix ambiguities */
- if (helper.amount & (helper.amount - 1)) helper.amount = GNCIF_NUM_PERIOD;
- if (helper.d_amount & (helper.d_amount - 1)) helper.d_amount = GNCIF_NUM_PERIOD;
- if (helper.price & (helper.price - 1)) helper.price = GNCIF_NUM_PERIOD;
- if (helper.shares & (helper.shares - 1)) helper.shares = GNCIF_NUM_PERIOD;
- if (helper.commission & (helper.commission - 1))
- helper.commission = GNCIF_NUM_PERIOD;
- if (helper.date & (helper.date - 1))
- {
- helper.date = gnc_import_choose_fmt(_("The Date format is ambiguous. "
- "Please choose the correct format."),
- helper.date, arg);
- }
- /* now parse it.. */
- qif_object_list_foreach(ctx, QIF_O_TXN, qif_parse_parse_txn, &helper);
- }
- typedef struct
- {
- QifContext ctx;
- GList * list;
- const char* type;
- } qif_merge_t;
- static void
- qif_merge_accts(gpointer key, gpointer value, gpointer data)
- {
- qif_merge_t *merge = data;
- QifAccount acct = value;
- /* Merge into the context. Remember items moved into the parent */
- if (qif_account_merge(merge->ctx, acct) == acct)
- merge->list = g_list_prepend(merge->list, acct->name);
- }
- static void
- qif_merge_cats(gpointer key, gpointer value, gpointer data)
- {
- qif_merge_t *merge = data;
- QifCategory cat = value;
- /* Merge into the context. Remember items moved into the parent */
- if (qif_cat_merge(merge->ctx, cat) == cat)
- merge->list = g_list_prepend(merge->list, cat->name);
- }
- static void
- qif_merge_classes(gpointer key, gpointer value, gpointer data)
- {
- qif_merge_t *merge = data;
- QifClass qclass = value;
- /* Merge into the context. Remember items moved into the parent */
- if (qif_class_merge(merge->ctx, qclass) == qclass)
- merge->list = g_list_prepend(merge->list, qclass->name);
- }
- static void
- qif_merge_secs(gpointer key, gpointer value, gpointer data)
- {
- qif_merge_t *merge = data;
- QifSecurity sec = value;
- /* Merge into the context. Remember items moved into the parent */
- if (qif_security_merge(merge->ctx, sec) == sec)
- merge->list = g_list_prepend(merge->list, sec->name);
- }
- static void
- qif_merge_del(gpointer obj, gpointer data)
- {
- qif_merge_t *merge = data;
- const char *name = obj;
- qif_object_map_remove(merge->ctx, merge->type, name);
- }
- static void
- qif_massage_split(QifSplit split, QifContext ctx)
- {
- const char *type = QIF_O_CATEGORY;
- char *name;
- if (split->cat.obj)
- {
- if (split->cat_is_acct)
- {
- type = QIF_O_ACCOUNT;
- name = split->cat.acct->name;
- }
- else
- name = split->cat.cat->name;
- split->cat.obj = qif_object_map_lookup(ctx, type, name);
- }
- if (split->cat_class)
- {
- split->cat_class = (QifClass) qif_object_map_lookup(ctx, QIF_O_CLASS,
- split->cat_class->name);
- }
- }
- static void
- qif_massage_itxn(QifInvstTxn itxn, QifContext ctx)
- {
- const char *type = QIF_O_CATEGORY;
- char *name;
- if (itxn->far_cat.obj)
- {
- if (itxn->far_cat_is_acct)
- {
- type = QIF_O_ACCOUNT;
- name = itxn->far_cat.acct->name;
- }
- else
- name = itxn->far_cat.cat->name;
- itxn->far_cat.obj = qif_object_map_lookup(ctx, type, name);
- }
- }
- static void
- qif_massage_txn(gpointer obj, gpointer data)
- {
- QifTxn txn = obj;
- QifContext ctx = data;
- QifSplit split;
- GList *node;
- if (txn->from_acct)
- txn->from_acct = (QifAccount) qif_object_map_lookup(ctx, QIF_O_ACCOUNT,
- txn->from_acct->name);
- if (txn->invst_info)
- qif_massage_itxn(txn->invst_info, ctx);
- if (txn->default_split)
- qif_massage_split(txn->default_split, ctx);
- for (node = txn->splits; node; node = node->next)
- {
- split = node->data;
- qif_massage_split(split, ctx);
- }
- }
- void
- qif_parse_merge_files(QifContext ctx)
- {
- GList *node;
- GList *accts = NULL;
- GList *cats = NULL;
- GList *classes = NULL;
- GList *securities = NULL;
- QifContext fctx;
- qif_merge_t merge;
- g_return_if_fail(ctx);
- /* Make sure each of the "file" contexts have been parsed.
- * note that we don't care about OUR context -- we can run this
- * process multiple times safely.
- */
- for (node = ctx->files; node; node = node->next)
- {
- fctx = node->data;
- g_return_if_fail(fctx->parsed);
- }
- /* Iterate over each file. Merge the Accounts, Categories, Classes,
- * Securities, and Transactions into the top-level context. Be sure
- * to re-point all Transaction/Split category/class/account pointers
- * to the new top-level item. Then be sure to remove the
- * "duplicated" items so we don't double-free (as we don't refcount,
- * either).
- */
- for (node = ctx->files; node; node = node->next)
- {
- fctx = node->data;
- /* Merge accts, categories, classes, and securities */
- merge.ctx = ctx;
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_ACCOUNT, qif_merge_accts, &merge);
- accts = merge.list;
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_CATEGORY, qif_merge_cats, &merge);
- cats = merge.list;
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_CLASS, qif_merge_classes, &merge);
- classes = merge.list;
- merge.list = NULL;
- qif_object_map_foreach(fctx, QIF_O_SECURITY, qif_merge_secs, &merge);
- securities = merge.list;
- /* repoint the transactions to the merged context data */
- qif_object_list_foreach(fctx, QIF_O_TXN, qif_massage_txn, ctx);
- /* then remove from the file context objects referenced in the top context */
- merge.ctx = fctx;
- merge.type = QIF_O_ACCOUNT;
- g_list_foreach(accts, qif_merge_del, &merge);
- g_list_free(accts);
- merge.type = QIF_O_CATEGORY;
- g_list_foreach(cats, qif_merge_del, &merge);
- g_list_free(cats);
- merge.type = QIF_O_CLASS;
- g_list_foreach(classes, qif_merge_del, &merge);
- g_list_free(classes);
- merge.type = QIF_O_SECURITY;
- g_list_foreach(securities, qif_merge_del, &merge);
- g_list_free(securities);
- }
- /* We've been parsed */
- ctx->parsed = TRUE;
- }