#include "gnucash.h" namespace ledger { void startElement(void *userData, const char *name, const char **atts) { gnucash_parser_t * parser = static_cast(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(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 std::string& number, int * precision) { 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); 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(userData); switch (parser->action) { case gnucash_parser_t::ACCOUNT_NAME: parser->curr_account->name = std::string(s, len); break; case gnucash_parser_t::ACCOUNT_ID: parser->curr_account_id = std::string(s, len); break; case gnucash_parser_t::ACCOUNT_PARENT: { accounts_map::iterator i = parser->accounts_by_id.find(std::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: { std::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(std::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 = std::string(s, len); break; case gnucash_parser_t::ENTRY_DATE: parser->curr_entry->_date = ptime_from_local_date_string(std::string(s, len)); break; case gnucash_parser_t::ENTRY_DESC: parser->curr_entry->payee = std::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(std::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(std::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(std::string(s, len)); if (i != parser->accounts_by_id.end()) { xact->account = (*i).second; } else { xact->account = parser->curr_journal->find_account(""); parser->have_error = (std::string("Could not find account ") + std::string(s, len)); } break; } case gnucash_parser_t::XACT_NOTE: parser->curr_entry->transactions.back()->note = std::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, "master; curr_account = NULL; curr_entry = NULL; curr_comm = NULL; entry_comm = NULL; curr_state = transaction_t::UNCLEARED; instreamp = ∈ path = original_file ? *original_file : ""; 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 new 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