diff options
Diffstat (limited to 'src/qif.cc')
-rw-r--r-- | src/qif.cc | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/src/qif.cc b/src/qif.cc new file mode 100644 index 00000000..5e567ea0 --- /dev/null +++ b/src/qif.cc @@ -0,0 +1,243 @@ +#include "qif.h" +#include "journal.h" + +namespace ledger { + +#define MAX_LINE 1024 + +static char line[MAX_LINE + 1]; +static string path; +static unsigned int src_idx; +static unsigned int linenum; + +static inline char * get_line(std::istream& in) { + in.getline(line, MAX_LINE); + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[len - 1] = '\0'; + linenum++; + return line; +} + +bool qif_parser_t::test(std::istream& in) const +{ + char magic[sizeof(unsigned int) + 1]; + in.read(magic, sizeof(unsigned int)); + magic[sizeof(unsigned int)] = '\0'; + in.clear(); + in.seekg(0, std::ios::beg); + + return (std::strcmp(magic, "!Typ") == 0 || + std::strcmp(magic, "\n!Ty") == 0 || + std::strcmp(magic, "\r\n!T") == 0); +} + +unsigned int qif_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string *) +{ + std::auto_ptr<entry_t> entry; + std::auto_ptr<amount_t> amount; + + transaction_t * xact; + unsigned int count = 0; + account_t * misc = NULL; + commodity_t * def_commodity = NULL; + bool saw_splits = false; + bool saw_category = false; + transaction_t * total = NULL; + + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + path = journal->sources.back(); + src_idx = journal->sources.size() - 1; + linenum = 1; + + unsigned long beg_pos = 0; + unsigned long beg_line = 0; + +#define SET_BEG_POS_AND_LINE() \ + if (! beg_line) { \ + beg_pos = in.tellg(); \ + beg_line = linenum; \ + } + + while (in.good() && ! in.eof()) { + char c; + in.get(c); + switch (c) { + case ' ': + case '\t': + if (peek_next_nonws(in) != '\n') { + get_line(in); + throw_(parse_exception, "Line begins with whitespace"); + } + // fall through... + + case '\n': + linenum++; + case '\r': // skip blank lines + break; + + case '!': + get_line(in); + + if (std::strcmp(line, "Type:Invst") == 0 || + std::strcmp(line, "Account") == 0 || + std::strcmp(line, "Type:Cat") == 0 || + std::strcmp(line, "Type:Class") == 0 || + std::strcmp(line, "Type:Memorized") == 0) + throw_(parse_exception, + "QIF files of type " << line << " are not supported."); + break; + + case 'D': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->_date = parse_datetime(line); + break; + + case 'T': + case '$': { + SET_BEG_POS_AND_LINE(); + get_line(in); + xact->amount.parse(line); + + unsigned char flags = xact->amount.commodity().flags(); + unsigned char prec = xact->amount.commodity().precision(); + + if (! def_commodity) { + def_commodity = commodity_t::find_or_create("$"); + assert(def_commodity); + } + xact->amount.set_commodity(*def_commodity); + + def_commodity->add_flags(flags); + if (prec > def_commodity->precision()) + def_commodity->set_precision(prec); + + if (c == '$') { + saw_splits = true; + xact->amount.in_place_negate(); + } else { + total = xact; + } + break; + } + + case 'C': + SET_BEG_POS_AND_LINE(); + c = in.peek(); + if (c == '*' || c == 'X') { + in.get(c); + xact->state = transaction_t::CLEARED; + } + break; + + case 'N': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->code = line; + break; + + case 'P': + case 'M': + case 'L': + case 'S': + case 'E': { + SET_BEG_POS_AND_LINE(); + get_line(in); + + switch (c) { + case 'P': + entry->payee = line; + break; + + case 'S': + xact = new transaction_t(NULL); + entry->add_transaction(xact); + // fall through... + case 'L': { + int len = std::strlen(line); + if (line[len - 1] == ']') + line[len - 1] = '\0'; + xact->account = journal->find_account(line[0] == '[' ? + line + 1 : line); + if (c == 'L') + saw_category = true; + break; + } + + case 'M': + case 'E': + xact->note = line; + break; + } + break; + } + + case 'A': + SET_BEG_POS_AND_LINE(); + // jww (2004-08-19): these are ignored right now + get_line(in); + break; + + case '^': { + account_t * other; + if (xact->account == master) { + if (! misc) + misc = journal->find_account("Miscellaneous"); + other = misc; + } else { + other = master; + } + + if (total && saw_category) { + if (! saw_splits) + total->amount.in_place_negate(); // negate, to show correct flow + else + total->account = other; + } + + if (! saw_splits) { + transaction_t * nxact = new transaction_t(other); + // The amount doesn't need to be set because the code below + // will balance this transaction against the other. + entry->add_transaction(nxact); + } + + if (journal->add_entry(entry.get())) { + entry->src_idx = src_idx; + entry->beg_pos = beg_pos; + entry->beg_line = beg_line; + entry->end_pos = in.tellg(); + entry->end_line = linenum; + entry.release(); + count++; + } + + // reset things for the next entry + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + saw_splits = false; + saw_category = false; + total = NULL; + beg_line = 0; + break; + } + + default: + get_line(in); + break; + } + } + + return count; +} + +} // namespace ledger |