diff options
Diffstat (limited to 'gnucash.cc')
-rw-r--r-- | gnucash.cc | 258 |
1 files changed, 172 insertions, 86 deletions
@@ -1,24 +1,38 @@ +#include "gnucash.h" +#include "journal.h" +#include "format.h" +#include "error.h" + +#include <iostream> #include <sstream> #include <cstring> -#include "ledger.h" - extern "C" { #include <xmlparse.h> // expat XML parser } namespace ledger { -static account * curr_account; -static std::string curr_account_id; -static entry * curr_entry; -static commodity * entry_comm; -static commodity * curr_comm; -static amount * curr_value; -static std::string curr_quant; -static XML_Parser current_parser; -static accounts_map accounts_by_id; -static bool do_compute; +typedef std::map<const std::string, account_t *> accounts_map; +typedef std::pair<const std::string, account_t *> accounts_pair; + +typedef std::map<account_t *, commodity_t *> account_comm_map; +typedef std::pair<account_t *, commodity_t *> account_comm_pair; + +static journal_t * curr_journal; +static account_t * master_account; +static account_t * curr_account; +static std::string curr_account_id; +static entry_t * curr_entry; +static commodity_t * entry_comm; +static commodity_t * curr_comm; +static amount_t curr_value; +static amount_t curr_quant; +static XML_Parser current_parser; +static accounts_map accounts_by_id; +static account_comm_map account_comms; +static unsigned int count; +static std::string have_error; static enum { NO_ACTION, @@ -43,8 +57,7 @@ static enum { static void startElement(void *userData, const char *name, const char **atts) { if (std::strcmp(name, "gnc:account") == 0) { - assert(! curr_account); - curr_account = new account; + curr_account = new account_t(master_account); } else if (std::strcmp(name, "act:name") == 0) action = ACCOUNT_NAME; @@ -54,7 +67,7 @@ static void startElement(void *userData, const char *name, const char **atts) action = ACCOUNT_PARENT; else if (std::strcmp(name, "gnc:commodity") == 0) { assert(! curr_comm); - curr_comm = new commodity; + curr_comm = new commodity_t(""); } else if (std::strcmp(name, "cmdty:id") == 0) action = COMM_SYM; @@ -64,7 +77,7 @@ static void startElement(void *userData, const char *name, const char **atts) action = COMM_PREC; else if (std::strcmp(name, "gnc:transaction") == 0) { assert(! curr_entry); - curr_entry = new entry(main_ledger); + curr_entry = new entry_t; } else if (std::strcmp(name, "trn:num") == 0) action = ENTRY_NUM; @@ -76,7 +89,7 @@ static void startElement(void *userData, const char *name, const char **atts) action = ENTRY_DESC; else if (std::strcmp(name, "trn:split") == 0) { assert(curr_entry); - curr_entry->xacts.push_back(new transaction()); + curr_entry->add_transaction(new transaction_t(curr_account)); } else if (std::strcmp(name, "split:reconciled-state") == 0) action = XACT_STATE; @@ -92,32 +105,52 @@ static void startElement(void *userData, const char *name, const char **atts) action = XACT_NOTE; } - static void endElement(void *userData, const char *name) { if (std::strcmp(name, "gnc:account") == 0) { assert(curr_account); - if (! curr_account->parent) - main_ledger->accounts.insert(accounts_map_pair(curr_account->name, - curr_account)); - accounts_by_id.insert(accounts_map_pair(curr_account_id, curr_account)); + if (curr_account->parent == master_account) + curr_journal->add_account(curr_account); + accounts_by_id.insert(accounts_pair(curr_account_id, curr_account)); curr_account = NULL; } else if (std::strcmp(name, "gnc:commodity") == 0) { assert(curr_comm); - main_ledger->commodities.insert(commodities_map_pair(curr_comm->symbol, - curr_comm)); + commodity_t::add_commodity(curr_comm); curr_comm = NULL; } else if (std::strcmp(name, "gnc:transaction") == 0) { assert(curr_entry); - assert(curr_entry->validate()); - main_ledger->entries.push_back(curr_entry); + if (! curr_journal->add_entry(curr_entry)) { + print_entry(std::cerr, *curr_entry); + have_error = "The above entry does not balance"; + delete curr_entry; + } else { + count++; + } curr_entry = NULL; } action = NO_ACTION; } + +static amount_t convert_number(const std::string& number) +{ + const char * num = number.c_str(); + + if (char * p = std::strchr(num, '/')) { + std::string numer_str(num, p - num); + std::string denom_str(p + 1); + + amount_t amt(numer_str); + amount_t den(denom_str); + + return amt / den; + } else { + return amount_t(number); + } +} + static void dataHandler(void *userData, const char *s, int len) { switch (action) { @@ -130,21 +163,31 @@ static void dataHandler(void *userData, const char *s, int len) break; case ACCOUNT_PARENT: { - accounts_map_iterator i = accounts_by_id.find(std::string(s, len)); + accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); assert(i != accounts_by_id.end()); curr_account->parent = (*i).second; - (*i).second->children.insert(accounts_map_pair(curr_account->name, - curr_account)); + curr_account->depth = curr_account->parent->depth + 1; + (*i).second->add_account(curr_account); break; } case COMM_SYM: - if (curr_comm) - curr_comm->symbol = std::string(s, len); - else if (curr_account) - curr_account->comm = main_ledger->commodities[std::string(s, len)]; - else if (curr_entry) - entry_comm = main_ledger->commodities[std::string(s, len)]; + if (curr_comm) { + curr_comm->set_symbol(std::string(s, len)); + } + else if (curr_account) { + std::string symbol(s, len); + commodity_t * comm = commodity_t::find_commodity(symbol, true); + if (symbol != "$" && symbol != "USD") + comm->flags |= COMMODITY_STYLE_SEPARATED; + account_comms.insert(account_comm_pair(curr_account, comm)); + } + else if (curr_entry) { + std::string symbol(s, len); + entry_comm = commodity_t::find_commodity(symbol, true); + if (symbol != "$" && symbol != "USD") + entry_comm->flags |= COMMODITY_STYLE_SEPARATED; + } break; case COMM_NAME: @@ -167,57 +210,62 @@ static void dataHandler(void *userData, const char *s, int len) } case ENTRY_DESC: - curr_entry->desc = std::string(s, len); + curr_entry->payee = std::string(s, len); break; case XACT_STATE: - curr_entry->cleared = (*s == 'y' || *s == 'c'); + if (*s == 'y') + curr_entry->state = entry_t::PENDING; + else + curr_entry->state = entry_t::CLEARED; break; - case XACT_VALUE: { + case XACT_VALUE: assert(entry_comm); - std::string value = std::string(s, len) + " " + entry_comm->symbol; - curr_value = create_amount(value.c_str()); + curr_value = convert_number(std::string(s, len)); + curr_value.set_commodity(*entry_comm); break; - } case XACT_QUANTITY: - curr_quant = std::string(s, len); + curr_quant = convert_number(std::string(s, len)); break; case XACT_ACCOUNT: { - accounts_map_iterator i = accounts_by_id.find(std::string(s, len)); - if (i == accounts_by_id.end()) { - std::cerr << "Could not find account " << std::string(s, len) - << std::endl; - std::exit(1); - } + transaction_t * xact = curr_entry->transactions.back(); - transaction * xact = curr_entry->xacts.back(); - xact->acct = (*i).second; + accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); + if (i != accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = curr_journal->find_account("<Unknown>"); - std::string value = curr_quant + " " + xact->acct->comm->symbol; - - if (curr_value->commdty() == xact->acct->comm) { - // assert: value must be equal to curr_value. - delete curr_value; - curr_value = NULL; + have_error = (std::string("Could not find account ") + + std::string(s, len)); } - xact->cost = create_amount(value.c_str(), curr_value); + amount_t value; + + account_comm_map::iterator ac = account_comms.find(xact->account); + if (ac != account_comms.end()) { + commodity_t * default_commodity = (*ac).second; - if (curr_value) { - delete curr_value; - curr_value = NULL; + curr_quant.set_commodity(*default_commodity); + value = curr_quant.round(default_commodity->precision); + + if (curr_value.commodity() == *default_commodity) + curr_value = value; + } else { + value = curr_quant; } - if (do_compute) - xact->acct->balance.credit(xact->cost); + xact->amount = value; + if (value != curr_value) + xact->cost = new amount_t(curr_value); break; } case XACT_NOTE: - curr_entry->xacts.back()->note = std::string(s, len); + curr_entry->transactions.back()->note = std::string(s, len); break; case NO_ACTION: @@ -231,25 +279,38 @@ static void dataHandler(void *userData, const char *s, int len) } } -book * parse_gnucash(std::istream& in, bool compute_balances) +bool gnucash_parser_t::test(std::istream& in) const { - char buf[BUFSIZ]; + char buf[128]; + in.getline(buf, 127); + in.seekg(0, std::ios::beg); - book * ledger = new book; + return std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0; +} - main_ledger = ledger; - do_compute = compute_balances; - action = NO_ACTION; - curr_account = NULL; - curr_entry = NULL; - curr_value = NULL; - curr_comm = NULL; - entry_comm = NULL; +unsigned int gnucash_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const std::string * original_file) +{ + char buf[BUFSIZ]; + + count = 0; + action = NO_ACTION; + curr_journal = journal; + master_account = master ? master : journal->master; + curr_account = NULL; + curr_entry = NULL; + curr_comm = NULL; + entry_comm = NULL; // GnuCash uses the USD commodity without defining it, which really - // means to use $. - commodity * usd = new commodity("$", true, false, true, false, 2); - main_ledger->commodities.insert(commodities_map_pair("USD", usd)); + // means $. + commodity_t * usd; + usd = new commodity_t("$", 2, COMMODITY_STYLE_THOUSANDS); + commodity_t::add_commodity(usd, "$"); + usd = new commodity_t("$", 2, COMMODITY_STYLE_THOUSANDS); + commodity_t::add_commodity(usd, "USD"); XML_Parser parser = XML_ParserCreate(NULL); current_parser = parser; @@ -259,23 +320,48 @@ book * parse_gnucash(std::istream& in, bool compute_balances) while (! in.eof()) { in.getline(buf, BUFSIZ - 1); - if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) { - std::cerr << XML_ErrorString(XML_GetErrorCode(parser)) - << " at line " << XML_GetCurrentLineNumber(parser) - << std::endl; - return NULL; + unsigned long line = XML_GetCurrentLineNumber(parser); + const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw parse_error(original_file ? *original_file : "<gnucash>", line, + msg); + } + + if (! have_error.empty()) { + unsigned long line = XML_GetCurrentLineNumber(parser); + parse_error err(original_file ? *original_file : "<gnucash>", line, + have_error); + std::cerr << "Error: " << err.what() << std::endl; + have_error = ""; } } + XML_ParserFree(parser); accounts_by_id.clear(); curr_account_id.clear(); - curr_quant.clear(); - - main_ledger->commodities.erase("USD"); - return ledger; + return count; } } // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#include <boost/python.hpp> + +using namespace boost::python; +using namespace ledger; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(gnucash_parse_overloads, + gnucash_parser_t::parse, 2, 4) + +void export_gnucash() { + class_< gnucash_parser_t, bases<parser_t> > ("GnucashParser") + .def("test", &gnucash_parser_t::test) + .def("parse", &gnucash_parser_t::parse, gnucash_parse_overloads()) + ; +} + +#endif // USE_BOOST_PYTHON |