diff options
author | John Wiegley <johnw@newartisans.com> | 2008-08-05 13:17:04 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-08-05 18:05:49 -0400 |
commit | f6f4a46cf5b14f9a2170cd6475958efbf320caec (patch) | |
tree | 05bc1defcdebc201de3dd10477483d906a842821 /src/gnucash.cc | |
parent | b7970b29855563e4c67f85af8b31233eda80c22a (diff) | |
download | fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.gz fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.bz2 fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.zip |
Moved around most of the files so that source code is in src/, documentation
is in doc/, etc.
Diffstat (limited to 'src/gnucash.cc')
-rw-r--r-- | src/gnucash.cc | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/src/gnucash.cc b/src/gnucash.cc new file mode 100644 index 00000000..41990cb0 --- /dev/null +++ b/src/gnucash.cc @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gnucash.h" +#include "account.h" + +namespace ledger { + +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const 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 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 string have_error; + +static std::istream * instreamp; +static unsigned int offset; +static XML_Parser parser; +static path pathname; +static unsigned int src_idx; +static istream_pos_type beg_pos; +static unsigned long beg_line; + +static xact_t::state_t curr_state; + +static enum action_t { + NO_ACTION, + ACCOUNT_NAME, + ACCOUNT_ID, + ACCOUNT_PARENT, + COMM_SYM, + COMM_NAME, + COMM_PREC, + ENTRY_NUM, + ALMOST_ENTRY_DATE, + ENTRY_DATE, + ENTRY_DESC, + XACT_STATE, + XACT_AMOUNT, + XACT_VALUE, + XACT_QUANTITY, + XACT_ACCOUNT, + XACT_NOTE +} action; + +static void startElement(void *userData, const char *name, const char **atts) +{ + if (std::strcmp(name, "gnc:account") == 0) { + curr_account = new account_t(master_account); + } + else if (std::strcmp(name, "act:name") == 0) + action = ACCOUNT_NAME; + else if (std::strcmp(name, "act:id") == 0) + action = ACCOUNT_ID; + else if (std::strcmp(name, "act:parent") == 0) + action = ACCOUNT_PARENT; + else if (std::strcmp(name, "gnc:commodity") == 0) + curr_comm = NULL; + else if (std::strcmp(name, "cmdty:id") == 0) + action = COMM_SYM; + else if (std::strcmp(name, "cmdty:name") == 0) + action = COMM_NAME; + else if (std::strcmp(name, "cmdty:fraction") == 0) + action = COMM_PREC; + else if (std::strcmp(name, "gnc:xact") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + } + else if (std::strcmp(name, "trn:num") == 0) + action = ENTRY_NUM; + else if (std::strcmp(name, "trn:date-posted") == 0) + action = ALMOST_ENTRY_DATE; + else if (action == ALMOST_ENTRY_DATE && std::strcmp(name, "ts:date") == 0) + action = ENTRY_DATE; + else if (std::strcmp(name, "trn:description") == 0) + action = ENTRY_DESC; + else if (std::strcmp(name, "trn:split") == 0) { + assert(curr_entry); + curr_entry->add_xact(new xact_t(curr_account)); + } + else if (std::strcmp(name, "split:reconciled-state") == 0) + action = XACT_STATE; + else if (std::strcmp(name, "split:amount") == 0) + action = XACT_AMOUNT; + else if (std::strcmp(name, "split:value") == 0) + action = XACT_VALUE; + else if (std::strcmp(name, "split:quantity") == 0) + action = XACT_QUANTITY; + else if (std::strcmp(name, "split:account") == 0) + action = XACT_ACCOUNT; + else if (std::strcmp(name, "split:memo") == 0) + 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 == 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) { + curr_comm = NULL; + } + else if (std::strcmp(name, "gnc:xact") == 0) { + assert(curr_entry); + + // Add the new entry (what gnucash calls a 'xact') to the + // journal + if (! curr_journal->add_entry(curr_entry)) { +#if 0 + print_entry(std::cerr, *curr_entry); +#endif + have_error = "The above entry does not balance"; + checked_delete(curr_entry); + } else { + curr_entry->src_idx = src_idx; + curr_entry->beg_pos = beg_pos; + curr_entry->beg_line = beg_line; + curr_entry->end_pos = instreamp->tellg(); + curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset; + count++; + } + + // Clear the relevant variables for the next run + curr_entry = NULL; + entry_comm = NULL; + } + else if (std::strcmp(name, "trn:split") == 0) { + xact_t * xact = curr_entry->xacts.back(); + + // Identify the commodity to use for the value of this + // xact. The quantity indicates how many times that value + // the xact is worth. + amount_t value; + commodity_t * default_commodity = NULL; + account_comm_map::iterator ac = account_comms.find(xact->account); + if (ac != account_comms.end()) + default_commodity = (*ac).second; + + if (default_commodity) { + curr_quant.set_commodity(*default_commodity); + value = curr_quant.round(); + + if (curr_value.commodity() == *default_commodity) + curr_value = value; + } else { + value = curr_quant; + } + + xact->state = curr_state; + xact->amount = value; + if (value != curr_value) + xact->cost = curr_value; + + xact->beg_pos = beg_pos; + xact->beg_line = beg_line; + xact->end_pos = instreamp->tellg(); + xact->end_line = XML_GetCurrentLineNumber(parser) - offset; + + // Clear the relevant variables for the next run + curr_state = xact_t::UNCLEARED; + curr_value = amount_t(); + curr_quant = amount_t(); + } + + action = NO_ACTION; +} + + +static amount_t convert_number(const string& number, + int * precision = NULL) +{ + 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); + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + switch (action) { + case ACCOUNT_NAME: + curr_account->name = string(s, len); + break; + + case ACCOUNT_ID: + curr_account_id = string(s, len); + break; + + case ACCOUNT_PARENT: { + accounts_map::iterator i = accounts_by_id.find(string(s, len)); + assert(i != accounts_by_id.end()); + curr_account->parent = (*i).second; + curr_account->depth = curr_account->parent->depth + 1; + (*i).second->add_account(curr_account); + break; + } + + case COMM_SYM: { + string symbol(s, len); + if (symbol == "USD") symbol = "$"; + + curr_comm = amount_t::current_pool->find_or_create(symbol); + assert(curr_comm); + + if (symbol != "$") + curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); + + if (curr_account) + account_comms.insert(account_comm_pair(curr_account, curr_comm)); + else if (curr_entry) + entry_comm = curr_comm; + break; + } + + case COMM_NAME: + curr_comm->set_name(string(s, len)); + break; + + case COMM_PREC: + curr_comm->set_precision(len - 1); + break; + + case ENTRY_NUM: + curr_entry->code = string(s, len); + break; + + case ENTRY_DATE: + curr_entry->_date = parse_date(string(s, len)); + break; + + case ENTRY_DESC: + curr_entry->payee = string(s, len); + break; + + case XACT_STATE: + if (*s == 'y') + curr_state = xact_t::CLEARED; + else if (*s == 'n') + curr_state = xact_t::UNCLEARED; + else + curr_state = xact_t::PENDING; + break; + + case XACT_VALUE: { + int precision; + assert(entry_comm); + curr_value = convert_number(string(s, len), &precision); + curr_value.set_commodity(*entry_comm); + + if (precision > entry_comm->precision()) + entry_comm->set_precision(precision); + break; + } + + case XACT_QUANTITY: + curr_quant = convert_number(string(s, len)); + break; + + case XACT_ACCOUNT: { + xact_t * xact = curr_entry->xacts.back(); + + accounts_map::iterator i = accounts_by_id.find(string(s, len)); + if (i != accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = curr_journal->find_account("<Unknown>"); + + have_error = (string("Could not find account ") + + string(s, len)); + } + break; + } + + case XACT_NOTE: + curr_entry->xacts.back()->note = string(s, len); + break; + + case NO_ACTION: + case ALMOST_ENTRY_DATE: + case XACT_AMOUNT: + break; + + default: + assert(false); + 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, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + char buf[BUFSIZ]; + +#if 0 + // jww (2008-05-08): Replace this + // This is the date format used by Gnucash, so override whatever the + // user specified. + date_t::input_format = "%Y-%m-%d %H:%M:%S %z"; +#endif + + 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 = xact_t::UNCLEARED; + + instreamp = ∈ + pathname = 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 = amount_t::current_pool->find_or_create("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); + + offset = 2; + parser = current_parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + + 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_error(msg); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; + have_error = ""; + } + } + + XML_ParserFree(parser); + + accounts_by_id.clear(); + curr_account_id.clear(); + + return count; +} + +} // namespace ledger |