ledger/gnucash.cc

383 lines
12 KiB
C++

#include "gnucash.h"
namespace ledger {
void startElement(void *userData, const char *name, const char **atts)
{
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) {
assert(! parser->curr_comm);
#if 0
// jww (2006-03-02): !!!
parser->curr_comm = new commodity_t("");
#endif
}
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) {
assert(parser->curr_comm);
#if 0
// jww (2006-03-02): !!!
commodity_t::add_commodity(parser->curr_comm);
#endif
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<gnucash_parser_t *>(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:
if (parser->curr_comm) {
#if 0
// jww (2006-03-02): !!!
parser->curr_comm->set_symbol(std::string(s, len));
#endif
}
else if (parser->curr_account) {
std::string symbol(s, len);
commodity_t * comm = commodity_t::find_or_create(symbol);
assert(comm);
if (symbol != "$" && symbol != "USD")
comm->add_flags(COMMODITY_STYLE_SEPARATED);
parser->account_comms.insert
(gnucash_parser_t::account_comm_pair(parser->curr_account, comm));
}
else if (parser->curr_entry) {
std::string symbol(s, len);
parser->entry_comm = commodity_t::find_or_create(symbol);
assert(parser->entry_comm);
if (symbol != "$" && symbol != "USD")
parser->entry_comm->add_flags(COMMODITY_STYLE_SEPARATED);
}
break;
case gnucash_parser_t::COMM_NAME:
parser->curr_comm->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 = 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("<Unknown>");
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, "<?xml", 5) == 0;
}
unsigned int gnucash_parser_t::parse(std::istream& in,
journal_t * journal,
account_t * master,
const std::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);
#if 0
//jww (2006-03-02): !!! make an alias here
commodity_t::add_commodity(usd, "USD");
#endif
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