#include "xml.h" #include "journal.h" #include "datetime.h" #include "error.h" #include #include #include extern "C" { #include // 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(""); 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) { 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, "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, "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, "", 21) != 0) { in.seekg(0, std::ios::beg); return false; } in.getline(buf, 255); if (! std::strstr(buf, "")) { 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; 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 parse_error(original_file ? *original_file : "", line, err.what()); } if (! have_error.empty()) { unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; parse_error err(original_file ? *original_file : "", line, 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 parse_error(original_file ? *original_file : "", line, err); } } XML_ParserFree(parser); return count; } } // namespace ledger #ifdef USE_BOOST_PYTHON #include 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 > ("XmlParser") .def("test", &xml_parser_t::test) .def("parse", &xml_parser_t::parse, xml_parse_overloads()) ; } #endif // USE_BOOST_PYTHON