diff options
author | John Wiegley <johnw@newartisans.com> | 2007-04-30 06:26:38 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 03:38:33 -0400 |
commit | c8899addfd2deed3d84be2de234681db64987722 (patch) | |
tree | 07f9a5eb603ff4ec83fe18c83083575d2b7a439a /src/gnucash.cc | |
parent | aa9cc125796711afcaa459898e95527fdae8e912 (diff) | |
download | fork-ledger-c8899addfd2deed3d84be2de234681db64987722.tar.gz fork-ledger-c8899addfd2deed3d84be2de234681db64987722.tar.bz2 fork-ledger-c8899addfd2deed3d84be2de234681db64987722.zip |
Rearranged the sources a bit.
Diffstat (limited to 'src/gnucash.cc')
-rw-r--r-- | src/gnucash.cc | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/src/gnucash.cc b/src/gnucash.cc new file mode 100644 index 00000000..abe8c555 --- /dev/null +++ b/src/gnucash.cc @@ -0,0 +1,366 @@ +#include "gnucash.h" + +namespace ledger { + +void startElement(void *userData, const char *name, const char ** /* attrs */) +{ + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + parser->curr_account = new account_t(parser->master_account); + } + else if (std::strcmp(name, "act:name") == 0) + parser->action = gnucash_parser_t::ACCOUNT_NAME; + else if (std::strcmp(name, "act:id") == 0) + parser->action = gnucash_parser_t::ACCOUNT_ID; + else if (std::strcmp(name, "act:parent") == 0) + parser->action = gnucash_parser_t::ACCOUNT_PARENT; + else if (std::strcmp(name, "gnc:commodity") == 0) + parser->curr_comm = NULL; + else if (std::strcmp(name, "cmdty:id") == 0) + parser->action = gnucash_parser_t::COMM_SYM; + else if (std::strcmp(name, "cmdty:name") == 0) + parser->action = gnucash_parser_t::COMM_NAME; + else if (std::strcmp(name, "cmdty:fraction") == 0) + parser->action = gnucash_parser_t::COMM_PREC; + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(! parser->curr_entry); + parser->curr_entry = new entry_t; + } + else if (std::strcmp(name, "trn:num") == 0) + parser->action = gnucash_parser_t::ENTRY_NUM; + else if (std::strcmp(name, "trn:date-posted") == 0) + parser->action = gnucash_parser_t::ALMOST_ENTRY_DATE; + else if (parser->action == gnucash_parser_t::ALMOST_ENTRY_DATE && + std::strcmp(name, "ts:date") == 0) + parser->action = gnucash_parser_t::ENTRY_DATE; + else if (std::strcmp(name, "trn:description") == 0) + parser->action = gnucash_parser_t::ENTRY_DESC; + else if (std::strcmp(name, "trn:split") == 0) { + assert(parser->curr_entry); + parser->curr_entry->add_transaction(new transaction_t(parser->curr_account)); + } + else if (std::strcmp(name, "split:reconciled-state") == 0) + parser->action = gnucash_parser_t::XACT_STATE; + else if (std::strcmp(name, "split:amount") == 0) + parser->action = gnucash_parser_t::XACT_AMOUNT; + else if (std::strcmp(name, "split:value") == 0) + parser->action = gnucash_parser_t::XACT_VALUE; + else if (std::strcmp(name, "split:quantity") == 0) + parser->action = gnucash_parser_t::XACT_QUANTITY; + else if (std::strcmp(name, "split:account") == 0) + parser->action = gnucash_parser_t::XACT_ACCOUNT; + else if (std::strcmp(name, "split:memo") == 0) + parser->action = gnucash_parser_t::XACT_NOTE; +} + +void endElement(void *userData, const char *name) +{ + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + assert(parser->curr_account); + if (parser->curr_account->parent == parser->master_account) + parser->curr_journal->add_account(parser->curr_account); + parser->accounts_by_id.insert(accounts_pair(parser->curr_account_id, + parser->curr_account)); + parser->curr_account = NULL; + } + else if (std::strcmp(name, "gnc:commodity") == 0) { + parser->curr_comm = NULL; + } + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(parser->curr_entry); + + // Add the new entry (what gnucash calls a 'transaction') to the + // journal + if (! parser->curr_journal->add_entry(parser->curr_entry)) { + print_entry(std::cerr, *parser->curr_entry); + parser->have_error = "The above entry does not balance"; + delete parser->curr_entry; + } else { + parser->curr_entry->src_idx = parser->src_idx; + parser->curr_entry->beg_pos = parser->beg_pos; + parser->curr_entry->beg_line = parser->beg_line; + parser->curr_entry->end_pos = parser->instreamp->tellg(); + parser->curr_entry->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + parser->count++; + } + + // Clear the relevant variables for the next run + parser->curr_entry = NULL; + parser->entry_comm = NULL; + } + else if (std::strcmp(name, "trn:split") == 0) { + transaction_t * xact = parser->curr_entry->transactions.back(); + + // Identify the commodity to use for the value of this + // transaction. The quantity indicates how many times that value + // the transaction is worth. + amount_t value; + commodity_t * default_commodity = NULL; + if (parser->entry_comm) { + default_commodity = parser->entry_comm; + } else { + gnucash_parser_t::account_comm_map::iterator ac = + parser->account_comms.find(xact->account); + if (ac != parser->account_comms.end()) + default_commodity = (*ac).second; + } + + if (default_commodity) { + parser->curr_quant.set_commodity(*default_commodity); + value = parser->curr_quant.round(); + + if (parser->curr_value.commodity() == *default_commodity) + parser->curr_value = value; + } else { + value = parser->curr_quant; + } + + xact->state = parser->curr_state; + xact->amount = value; + if (value != parser->curr_value) + xact->cost = new amount_t(parser->curr_value); + + xact->beg_pos = parser->beg_pos; + xact->beg_line = parser->beg_line; + xact->end_pos = parser->instreamp->tellg(); + xact->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + + // Clear the relevant variables for the next run + parser->curr_state = transaction_t::UNCLEARED; + parser->curr_value = amount_t(); + parser->curr_quant = amount_t(); + } + + parser->action = gnucash_parser_t::NO_ACTION; +} + +amount_t gnucash_parser_t::convert_number(const string& number, + int * precision) +{ + const char * num = number.c_str(); + + if (char * p = std::strchr(num, '/')) { + string numer_str(num, p - num); + string denom_str(p + 1); + + amount_t amt(numer_str); + amount_t den(denom_str); + + if (precision) + *precision = denom_str.length() - 1; + + if (! den) { + have_error = "Denominator in entry is zero!"; + return amt; + } else { + return amt / den; + } + } else { + return amount_t(number); + } +} + +void dataHandler(void *userData, const char *s, int len) +{ + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + + switch (parser->action) { + case gnucash_parser_t::ACCOUNT_NAME: + parser->curr_account->name = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_ID: + parser->curr_account_id = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_PARENT: { + accounts_map::iterator i = parser->accounts_by_id.find(string(s, len)); + assert(i != parser->accounts_by_id.end()); + parser->curr_account->parent = (*i).second; + parser->curr_account->depth = parser->curr_account->parent->depth + 1; + (*i).second->add_account(parser->curr_account); + break; + } + + case gnucash_parser_t::COMM_SYM: { + string symbol(s, len); + if (symbol == "USD") symbol = "$"; + + parser->curr_comm = commodity_t::find_or_create(symbol); + assert(parser->curr_comm); + + if (symbol != "$") + parser->curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); + + if (parser->curr_account) + parser->account_comms.insert + (gnucash_parser_t::account_comm_pair(parser->curr_account, + parser->curr_comm)); + else if (parser->curr_entry) + parser->entry_comm = parser->curr_comm; + break; + } + + case gnucash_parser_t::COMM_NAME: + parser->curr_comm->set_name(string(s, len)); + break; + + case gnucash_parser_t::COMM_PREC: + parser->curr_comm->set_precision(len - 1); + break; + + case gnucash_parser_t::ENTRY_NUM: + parser->curr_entry->code = string(s, len); + break; + + case gnucash_parser_t::ENTRY_DATE: + parser->curr_entry->_date = parse_datetime(string(s, len)); + break; + + case gnucash_parser_t::ENTRY_DESC: + parser->curr_entry->payee = string(s, len); + break; + + case gnucash_parser_t::XACT_STATE: + if (*s == 'y') + parser->curr_state = transaction_t::CLEARED; + else if (*s == 'n') + parser->curr_state = transaction_t::UNCLEARED; + else + parser->curr_state = transaction_t::PENDING; + break; + + case gnucash_parser_t::XACT_VALUE: { + int precision; + assert(parser->entry_comm); + parser->curr_value = parser->convert_number(string(s, len), &precision); + parser->curr_value.set_commodity(*parser->entry_comm); + + if (precision > parser->entry_comm->precision()) + parser->entry_comm->set_precision(precision); + break; + } + + case gnucash_parser_t::XACT_QUANTITY: + parser->curr_quant = parser->convert_number(string(s, len)); + break; + + case gnucash_parser_t::XACT_ACCOUNT: { + transaction_t * xact = parser->curr_entry->transactions.back(); + + accounts_map::iterator i = + parser->accounts_by_id.find(string(s, len)); + if (i != parser->accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = parser->curr_journal->find_account("<Unknown>"); + + parser->have_error = (string("Could not find account ") + + string(s, len)); + } + break; + } + + case gnucash_parser_t::XACT_NOTE: + parser->curr_entry->transactions.back()->note = string(s, len); + break; + + case gnucash_parser_t::NO_ACTION: + case gnucash_parser_t::ALMOST_ENTRY_DATE: + case gnucash_parser_t::XACT_AMOUNT: + break; + + default: + assert(0); + break; + } +} + +bool gnucash_parser_t::test(std::istream& in) const +{ + char buf[5]; + in.read(buf, 5); + in.clear(); + in.seekg(0, std::ios::beg); + + return std::strncmp(buf, "<?xml", 5) == 0; +} + +unsigned int gnucash_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ + char buf[BUFSIZ]; + + // This is the date format used by Gnucash, so override whatever the + // user specified. + // + // jww (2006-09-13): Make this parser local somehow. + //date_t::input_format = "%Y-%m-%d %H:%M:%S %z"; + + 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; + curr_state = transaction_t::UNCLEARED; + + instreamp = ∈ + path = original_file ? *original_file : "<gnucash>"; + src_idx = journal->sources.size() - 1; + + // GnuCash uses the USD commodity without defining it, which really + // means $. + commodity_t * usd = commodity_t::find_or_create("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); + + offset = 2; + expat_parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + + while (in.good() && ! in.eof()) { + beg_pos = in.tellg(); + beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1; + + in.getline(buf, BUFSIZ - 1); + std::strcat(buf, "\n"); + if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw_(parse_exception, msg); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; +#if 0 + // jww (2007-04-26): What is this doing? + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; +#endif + have_error = ""; + } + } + + XML_ParserFree(parser); + + accounts_by_id.clear(); + curr_account_id.clear(); + + return count; +} + +} // namespace ledger |