diff options
author | John Wiegley <johnw@newartisans.com> | 2008-04-13 03:35:00 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 03:35:00 -0400 |
commit | 42f43b7686038e4cbca16d8d2118b139544e6de3 (patch) | |
tree | 52c5473401c57282242d66b8dd75f4c07bf41d07 /xmlparse.cc | |
parent | c7b4370ff9c8ab5c96f15b1e712e6db6bdab6324 (diff) | |
download | fork-ledger-42f43b7686038e4cbca16d8d2118b139544e6de3.tar.gz fork-ledger-42f43b7686038e4cbca16d8d2118b139544e6de3.tar.bz2 fork-ledger-42f43b7686038e4cbca16d8d2118b139544e6de3.zip |
Check in all changes made so far toward 3.0.
Diffstat (limited to 'xmlparse.cc')
-rw-r--r-- | xmlparse.cc | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/xmlparse.cc b/xmlparse.cc new file mode 100644 index 00000000..c036c007 --- /dev/null +++ b/xmlparse.cc @@ -0,0 +1,473 @@ +#include "xmlparse.h" +#include "journal.h" + +#include <cstring> + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#endif +} + +namespace ledger { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +static XML_Parser current_parser; +static unsigned int count; + +static journal_t * curr_journal; +static entry_t * curr_entry; +static commodity_t * curr_comm; +static std::string comm_flags; + +static transaction_t::state_t curr_state; + +static std::string data; +static bool ignore; +static std::string have_error; + +static void startElement(void *userData, const char *name, const char **attrs) +{ + if (ignore) + return; + + if (std::strcmp(name, "entry") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + curr_state = transaction_t::UNCLEARED; + } + else if (std::strcmp(name, "transaction") == 0) { + assert(curr_entry); + curr_entry->add_transaction(new transaction_t); + if (curr_state != transaction_t::UNCLEARED) + curr_entry->transactions.back()->state = curr_state; + } + else if (std::strcmp(name, "commodity") == 0) { + if (std::string(attrs[0]) == "flags") + comm_flags = attrs[1]; + } + else if (std::strcmp(name, "total") == 0) { + ignore = true; + } +} + +static void endElement(void *userData, const char *name) +{ + if (ignore) { + if (std::strcmp(name, "total") == 0) + ignore = false; + return; + } + + if (std::strcmp(name, "entry") == 0) { + assert(curr_entry); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + account_t * acct = curr_journal->find_account("<Unknown>"); + curr_entry->add_transaction(new transaction_t(acct)); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + delete curr_entry; + have_error = "Entry cannot be balanced"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + curr_entry->_date = data; + } + else if (std::strcmp(name, "en:date_eff") == 0) { + curr_entry->_date_eff = data; + } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } + else if (std::strcmp(name, "en:cleared") == 0) { + curr_state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_state = transaction_t::PENDING; + } + else if (std::strcmp(name, "en:payee") == 0) { + curr_entry->payee = data; + } + else if (std::strcmp(name, "tr:account") == 0) { + curr_entry->transactions.back()->account = curr_journal->find_account(data); + } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->transactions.back()->state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->transactions.back()->state = transaction_t::PENDING; + } + else if (std::strcmp(name, "tr:virtual") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; + } + else if (std::strcmp(name, "tr:generated") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; + } + else if (std::strcmp(name, "symbol") == 0) { + assert(! curr_comm); + curr_comm = commodity_t::find_or_create(data); + assert(curr_comm); + curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); + if (! comm_flags.empty()) { + for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { + switch (comm_flags[i]) { + case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; + case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; + case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; + case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; + } + } + } + } +#if 0 + // jww (2006-03-02): !!! + else if (std::strcmp(name, "price") == 0) { + assert(curr_comm); + amount_t * price = new amount_t(data); + std::ostringstream symstr; + symstr << curr_comm->symbol << " {" << *price << "}"; + commodity_t * priced_comm = + commodity_t::find_commodity(symstr.str(), true); + priced_comm->price = price; + priced_comm->base = curr_comm; + curr_comm = priced_comm; + } +#endif + else if (std::strcmp(name, "quantity") == 0) { + curr_entry->transactions.back()->amount.parse(data); + if (curr_comm) { + std::string::size_type i = data.find('.'); + if (i != std::string::npos) { + int precision = data.length() - i - 1; + if (precision > curr_comm->precision()) + curr_comm->set_precision(precision); + } + curr_entry->transactions.back()->amount.set_commodity(*curr_comm); + curr_comm = NULL; + } + } + else if (std::strcmp(name, "tr:amount") == 0) { + curr_comm = NULL; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + if (! ignore) + data = std::string(s, len); +} + +bool xml_parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, "<?xml", 5) != 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.getline(buf, 79); + if (! std::strstr(buf, "<ledger")) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.clear(); + in.seekg(0, std::ios::beg); + return true; +} + +unsigned int xml_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const std::string * original_file) +{ + char buf[BUFSIZ]; + + count = 0; + curr_journal = journal; + curr_entry = NULL; + curr_comm = NULL; + ignore = false; + + unsigned int offset = 2; + 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); + std::strcat(buf, "\n"); + bool result; + try { + result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); + } + catch (const std::exception& err) { + unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + XML_ParserFree(parser); + throw new parse_error(err.what()); + } + + 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 = ""; + } + + if (! result) { + unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw new parse_error(err); + } + } + + XML_ParserFree(parser); + + return count; +} + +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +#if 0 +void xml_write_amount(std::ostream& out, const amount_t& amount, + const int depth = 0) +{ + for (int i = 0; i < depth; i++) out << ' '; + out << "<amount>\n"; + + commodity_t& c = amount.commodity(); + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<commodity flags=\""; + if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P'; + if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S'; + if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T'; + if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; + out << "\">\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! + if (c.price) { + out << "<symbol>" << c.base->symbol << "</symbol>\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "<price>\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "</price>\n"; + } else { + out << "<symbol>" << c.symbol << "</symbol>\n"; + } +#endif + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</commodity>\n"; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<quantity>"; + out << amount.quantity_string() << "</quantity>\n"; + + for (int i = 0; i < depth; i++) out << ' '; + out << "</amount>\n"; +} + +void xml_write_value(std::ostream& out, const value_t& value, + const int depth = 0) +{ + balance_t * bal = NULL; + + for (int i = 0; i < depth; i++) out << ' '; + out << "<value type=\""; + switch (value.type) { + case value_t::BOOLEAN: out << "boolean"; break; + case value_t::INTEGER: out << "integer"; break; + case value_t::AMOUNT: out << "amount"; break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: out << "balance"; break; + } + out << "\">\n"; + + switch (value.type) { + case value_t::BOOLEAN: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<boolean>" << *((bool *) value.data) << "</boolean>\n"; + break; + + case value_t::INTEGER: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<integer>" << *((long *) value.data) << "</integer>\n"; + break; + + case value_t::AMOUNT: + xml_write_amount(out, *((amount_t *) value.data), depth + 2); + break; + + case value_t::BALANCE: + bal = (balance_t *) value.data; + // fall through... + + case value_t::BALANCE_PAIR: + if (! bal) + bal = &((balance_pair_t *) value.data)->quantity; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<balance>\n"; + + for (amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) + xml_write_amount(out, (*i).second, depth + 4); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</balance>\n"; + break; + + default: + assert(0); + break; + } + + for (int i = 0; i < depth; i++) out << ' '; + out << "</value>\n"; +} + +void output_xml_string(std::ostream& out, const std::string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +void format_xml_entries::format_last_entry() +{ + output_stream << " <entry>\n" + << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d") + << "</en:date>\n"; + + if (last_entry->_date_eff) + output_stream << " <en:date_eff>" + << last_entry->_date_eff.to_string("%Y/%m/%d") + << "</en:date_eff>\n"; + + if (! last_entry->code.empty()) { + output_stream << " <en:code>"; + output_xml_string(output_stream, last_entry->code); + output_stream << "</en:code>\n"; + } + + if (! last_entry->payee.empty()) { + output_stream << " <en:payee>"; + output_xml_string(output_stream, last_entry->payee); + output_stream << "</en:payee>\n"; + } + + bool first = true; + for (transactions_list::const_iterator i = last_entry->transactions.begin(); + i != last_entry->transactions.end(); + i++) { + if (transaction_has_xdata(**i) && + transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { + if (first) { + output_stream << " <en:transactions>\n"; + first = false; + } + + output_stream << " <transaction>\n"; + + if ((*i)->_date) + output_stream << " <tr:date>" + << (*i)->_date.to_string("%Y/%m/%d") + << "</tr:date>\n"; + + if ((*i)->_date_eff) + output_stream << " <tr:date_eff>" + << (*i)->_date_eff.to_string("%Y/%m/%d") + << "</tr:date_eff>\n"; + + if ((*i)->state == transaction_t::CLEARED) + output_stream << " <tr:cleared/>\n"; + else if ((*i)->state == transaction_t::PENDING) + output_stream << " <tr:pending/>\n"; + + if ((*i)->flags & TRANSACTION_VIRTUAL) + output_stream << " <tr:virtual/>\n"; + if ((*i)->flags & TRANSACTION_AUTO) + output_stream << " <tr:generated/>\n"; + + if ((*i)->account) { + std::string name = (*i)->account->fullname(); + if (name == "<Total>") + name = "[TOTAL]"; + else if (name == "<Unknown>") + name = "[UNKNOWN]"; + + output_stream << " <tr:account>"; + output_xml_string(output_stream, name); + output_stream << "</tr:account>\n"; + } + + output_stream << " <tr:amount>\n"; + if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) + xml_write_value(output_stream, + transaction_xdata_(**i).value, 10); + else + xml_write_value(output_stream, value_t((*i)->amount), 10); + output_stream << " </tr:amount>\n"; + + if ((*i)->cost) { + output_stream << " <tr:cost>\n"; + xml_write_value(output_stream, value_t(*(*i)->cost), 10); + output_stream << " </tr:cost>\n"; + } + + if (! (*i)->note.empty()) { + output_stream << " <tr:note>"; + output_xml_string(output_stream, (*i)->note); + output_stream << "</tr:note>\n"; + } + + if (show_totals) { + output_stream << " <total>\n"; + xml_write_value(output_stream, transaction_xdata_(**i).total, 10); + output_stream << " </total>\n"; + } + + output_stream << " </transaction>\n"; + + transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; + } + } + + if (! first) + output_stream << " </en:transactions>\n"; + + output_stream << " </entry>\n"; +} +#endif + +} // namespace ledger |