diff options
Diffstat (limited to 'xml.cc')
-rw-r--r-- | xml.cc | 220 |
1 files changed, 220 insertions, 0 deletions
@@ -0,0 +1,220 @@ +#include "xml.h" +#include "journal.h" +#include "datetime.h" +#include "error.h" + +#include <iostream> +#include <sstream> +#include <cstring> + +extern "C" { +#include <xmlparse.h> // expat XML parser +} + +namespace ledger { + +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 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; + } + else if (std::strcmp(name, "transaction") == 0) { + assert(curr_entry); + curr_entry->add_transaction(new transaction_t); + } + 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 does not balance"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + quick_parse_date(data.c_str(), &curr_entry->date); + } + else if (std::strcmp(name, "en:cleared") == 0) { + curr_entry->state = entry_t::CLEARED; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_entry->state = entry_t::PENDING; + } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } + 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, "commodity") == 0) { + assert(! curr_comm); + curr_comm = commodity_t::find_commodity(data, true); + curr_comm->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->flags &= ~COMMODITY_STYLE_SUFFIXED; break; + case 'S': curr_comm->flags |= COMMODITY_STYLE_SEPARATED; break; + case 'T': curr_comm->flags |= COMMODITY_STYLE_THOUSANDS; break; + case 'E': curr_comm->flags |= COMMODITY_STYLE_EUROPEAN; break; + } + } + 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->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[256]; + + in.getline(buf, 255); + if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) != 0) { + in.seekg(0, std::ios::beg); + return false; + } + + in.getline(buf, 255); + if (! std::strstr(buf, "<ledger>")) { + in.seekg(0, std::ios::beg); + return false; + } + + 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; + + 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); + bool result; + try { + result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); + } + catch (const std::exception& err) { + unsigned long line = XML_GetCurrentLineNumber(parser); + XML_ParserFree(parser); + throw parse_error(original_file ? *original_file : "<xml>", line, + err.what()); + } + + if (! have_error.empty()) { + unsigned long line = XML_GetCurrentLineNumber(parser); + parse_error err(original_file ? *original_file : "<xml>", line, + have_error); + std::cerr << "Error: " << err.what() << std::endl; + } + + if (! result) { + unsigned long line = XML_GetCurrentLineNumber(parser); + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw parse_error(original_file ? *original_file : "<xml>", line, err); + } + } + + XML_ParserFree(parser); + + return count; +} + +} // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#include <boost/python.hpp> + +using namespace boost::python; +using namespace ledger; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xml_parse_overloads, + xml_parser_t::parse, 2, 4) + +void export_xml() { + class_< xml_parser_t, bases<parser_t> > ("XmlParser") + .def("test", &xml_parser_t::test) + .def("parse", &xml_parser_t::parse, xml_parse_overloads()) + ; +} + +#endif // USE_BOOST_PYTHON |