summaryrefslogtreecommitdiff
path: root/src/gnucash.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2007-04-30 06:26:38 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 03:38:33 -0400
commitc8899addfd2deed3d84be2de234681db64987722 (patch)
tree07f9a5eb603ff4ec83fe18c83083575d2b7a439a /src/gnucash.cc
parentaa9cc125796711afcaa459898e95527fdae8e912 (diff)
downloadfork-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.cc366
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 = &in;
+ 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