ledger/gnucash.cc
2003-10-01 20:47:10 +00:00

274 lines
6.8 KiB
C++

#include <sstream>
#include <cstring>
#include "ledger.h"
extern "C" {
#include <xmlparse.h> // expat XML parser
}
namespace ledger {
static account * curr_account;
static std::string curr_account_id;
static entry * curr_entry;
static commodity * entry_comm;
static commodity * curr_comm;
static amount * curr_value;
static std::string curr_quant;
static XML_Parser current_parser;
static bool do_compute;
static accounts_t accounts_by_id;
static enum {
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) {
assert(! curr_account);
curr_account = new account(name);
}
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) {
assert(! curr_comm);
curr_comm = new commodity;
}
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:transaction") == 0) {
assert(! curr_entry);
curr_entry = new entry;
}
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->xacts.push_back(new transaction());
}
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)
main_ledger.accounts.insert(accounts_entry(curr_account->name,
curr_account));
accounts_by_id.insert(accounts_entry(curr_account_id, curr_account));
curr_account = NULL;
}
else if (std::strcmp(name, "gnc:commodity") == 0) {
assert(curr_comm);
main_ledger.commodities.insert(commodities_entry(curr_comm->symbol,
curr_comm));
curr_comm = NULL;
}
else if (std::strcmp(name, "gnc:transaction") == 0) {
assert(curr_entry);
if (! curr_entry->validate()) {
std::cerr << "Failed to balance the following transaction, "
<< "ending on line "
<< XML_GetCurrentLineNumber(current_parser) << std::endl;
curr_entry->print(std::cerr);
} else {
main_ledger.entries.push_back(curr_entry);
}
curr_entry = NULL;
}
action = NO_ACTION;
}
static void dataHandler(void *userData, const char *s, int len)
{
switch (action) {
case ACCOUNT_NAME:
curr_account->name = std::string(s, len);
break;
case ACCOUNT_ID:
curr_account_id = std::string(s, len);
break;
case ACCOUNT_PARENT: {
accounts_iterator i = accounts_by_id.find(std::string(s, len));
assert(i != accounts_by_id.end());
curr_account->parent = (*i).second;
(*i).second->children.insert(account::pair(curr_account->name,
curr_account));
break;
}
case COMM_SYM:
if (curr_comm)
curr_comm->symbol = std::string(s, len);
else if (curr_account)
curr_account->comm = main_ledger.commodities[std::string(s, len)];
else if (curr_entry)
entry_comm = main_ledger.commodities[std::string(s, len)];
break;
case COMM_NAME:
curr_comm->name = std::string(s, len);
break;
case COMM_PREC:
curr_comm->precision = len - 1;
break;
case ENTRY_NUM:
curr_entry->code = std::string(s, len);
break;
case ENTRY_DATE: {
struct tm when;
strptime(std::string(s, len).c_str(), "%Y-%m-%d %H:%M:%S %z", &when);
curr_entry->date = std::mktime(&when);
break;
}
case ENTRY_DESC:
curr_entry->desc = std::string(s, len);
break;
case XACT_STATE:
curr_entry->cleared = (*s == 'y' || *s == 'c');
break;
case XACT_VALUE: {
assert(entry_comm);
std::string value = std::string(s, len) + " " + entry_comm->symbol;
curr_value = create_amount(value.c_str());
break;
}
case XACT_QUANTITY:
curr_quant = std::string(s, len);
break;
case XACT_ACCOUNT: {
accounts_iterator i = accounts_by_id.find(std::string(s, len));
if (i == accounts_by_id.end()) {
std::cerr << "Could not find account " << std::string(s, len)
<< " at line " << XML_GetCurrentLineNumber(current_parser)
<< std::endl;
std::exit(1);
}
transaction * xact = curr_entry->xacts.back();
xact->acct = (*i).second;
std::string value = curr_quant + " " + (*i).second->comm->symbol;
if (curr_value->comm() == (*i).second->comm) {
// assert: value must be equal to curr_value.
delete curr_value;
curr_value = NULL;
}
xact->cost = create_amount(value.c_str(), curr_value);
if (curr_value)
delete curr_value;
if (do_compute)
xact->acct->balance.credit(xact->cost);
break;
}
case XACT_NOTE:
curr_entry->xacts.back()->note = std::string(s, len);
break;
case NO_ACTION:
case ALMOST_ENTRY_DATE:
case XACT_AMOUNT:
break;
default:
assert(0);
break;
}
}
bool parse_gnucash(std::istream& in, bool compute_balances)
{
char buf[BUFSIZ];
curr_account = NULL;
curr_entry = NULL;
curr_comm = NULL;
do_compute = compute_balances;
action = NO_ACTION;
XML_Parser parser = XML_ParserCreate(NULL);
current_parser = parser;
XML_SetElementHandler(parser, startElement, endElement);
XML_SetCharacterDataHandler(parser, dataHandler);
while (! in.eof()) {
in.getline(buf, BUFSIZ - 1);
if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) {
std::cerr << XML_ErrorString(XML_GetErrorCode(parser))
<< " at line " << XML_GetCurrentLineNumber(parser)
<< std::endl;
return false;
}
}
XML_ParserFree(parser);
accounts_by_id.clear();
curr_account_id.clear();
curr_quant.clear();
return true;
}
} // namespace ledger