#include "ledger.h" #include #include #include namespace ledger { static char * next_element(char * buf, bool variable = false) { char * p; // Convert any tabs to spaces, for simplicity's sake for (p = buf; *p; p++) if (*p == '\t') *p = ' '; if (variable) p = std::strstr(buf, " "); else p = std::strchr(buf, ' '); if (! p) return NULL; *p++ = '\0'; while (std::isspace(*p)) p++; return p; } static int linenum = 0; static void finalize_entry(entry * curr, bool compute_balances) { assert(curr); // If there were no transactions, it's definitely an error! if (curr->xacts.empty()) { std::cerr << "Error, line " << (linenum - 1) << ": Entry has no transactions!" << std::endl; return; } // Scan through and compute the total balance for the entry. totals balance; for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) if ((*x)->cost) balance.credit((*x)->cost->value()); // If one transaction is of a different commodity than the others, // and it has no per-unit price, determine its price by dividing // the unit count into the value of the balance. // // NOTE: We don't do this for prefix-style or joined-symbol // commodities. Also, do it for the last eligible commodity first, // if it meets the criteria. if (! balance.amounts.empty() && balance.amounts.size() == 2) { for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) { if (! (*x)->cost->has_price() && ! (*x)->cost->comm()->prefix && (*x)->cost->comm()->separate) { for (totals::iterator i = balance.amounts.begin(); i != balance.amounts.end(); i++) { if ((*i).second->comm() != (*x)->cost->comm()) { (*x)->cost->set_value((*i).second); break; } } break; } } } // Walk through each of the transactions, fixing up any that we // can, and performing any on-the-fly calculations. bool empty_allowed = true; for (std::list::iterator x = curr->xacts.begin(); x != curr->xacts.end(); x++) { if (! (*x)->cost) { if (empty_allowed && ! balance.amounts.empty() && balance.amounts.size() == 1) { empty_allowed = false; // If one transaction gives no value at all -- and all the // rest are of the same commodity -- then its value is the // inverse of the computed value of the others. totals::iterator i = balance.amounts.begin(); (*x)->cost = (*i).second->value(); (*x)->cost->negate(); if (compute_balances) (*x)->acct->balance.credit((*x)->cost); } else { std::cerr << "Error, line " << (linenum - 1) << ": Transaction entry is lacking an amount." << std::endl; return; } } #ifdef HUQUQULLAH if (! main_ledger.compute_huquq || ! (*x)->exempt_or_necessary) continue; // Reflect 19% of the exempt or necessary transaction in the // Huququ'llah account. amount * divisor = create_amount("0.19"); amount * temp = (*x)->cost->value(); transaction * t = new transaction(main_ledger.huquq_account, temp->value(divisor)); curr->xacts.push_back(t); if (compute_balances) t->acct->balance.credit(t->cost); // Balance the above transaction by recording the inverse in // Expenses:Huququ'llah. t = new transaction(main_ledger.huquq_expenses_account, temp->value(divisor)); t->cost->negate(); curr->xacts.push_back(t); if (compute_balances) t->acct->balance.credit(t->cost); delete temp; delete divisor; #endif } // Compute the balances again, just to make sure it all comes out // right (i.e., to zero for every commodity). if (! curr->validate()) { std::cerr << "Error, line " << (linenum - 1) << ": Failed to balance the following transaction:" << std::endl; curr->print(std::cerr); curr->validate(true); return; } // If it's OK, add it to the general ledger's list of entries. main_ledger.entries.push_back(curr); } ////////////////////////////////////////////////////////////////////// // // Ledger parser // bool parse_ledger(std::istream& in, bool compute_balances) { std::time_t now = std::time(NULL); struct std::tm * now_tm = std::localtime(&now); int current_year = now_tm->tm_year + 1900; char line[1024]; struct std::tm moment; entry * curr = NULL; // Compile the regular expression used for parsing amounts const char *error; int erroffset; static const std::string regexp = "^(([0-9]{4})[./])?([0-9]+)[./]([0-9]+)\\s+(\\*\\s+)?" "(\\(([^)]+)\\)\\s+)?(.+)"; pcre * entry_re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL); while (! in.eof()) { in.getline(line, 1023); linenum++; if (line[0] == '\n') { continue; } else if (std::isdigit(line[0])) { static char buf[256]; int ovector[60]; int matched = pcre_exec(entry_re, NULL, line, std::strlen(line), 0, 0, ovector, 60); if (! matched) { std::cerr << "Error, line " << linenum << ": Failed to parse: " << line << std::endl; continue; } // If we haven't finished with the last entry yet, do so now if (curr) finalize_entry(curr, compute_balances); curr = new entry; // Parse the date int year = current_year; if (ovector[1 * 2] >= 0) { pcre_copy_substring(line, ovector, matched, 2, buf, 255); year = std::atoi(buf); } assert(ovector[3 * 2] >= 0); pcre_copy_substring(line, ovector, matched, 3, buf, 255); int mon = std::atoi(buf); assert(ovector[4 * 2] >= 0); pcre_copy_substring(line, ovector, matched, 4, buf, 255); int mday = std::atoi(buf); memset(&moment, 0, sizeof(struct std::tm)); moment.tm_mday = mday; moment.tm_mon = mon - 1; moment.tm_year = year - 1900; curr->date = std::mktime(&moment); // Parse the remaining entry details if (ovector[5 * 2] >= 0) curr->cleared = true; if (ovector[6 * 2] >= 0) { pcre_copy_substring(line, ovector, matched, 7, buf, 255); curr->code = buf; } if (ovector[8 * 2] >= 0) { pcre_copy_substring(line, ovector, matched, 8, buf, 255); curr->desc = buf; } } else if (curr && std::isspace(line[0])) { transaction * xact = new transaction(); char * p = line; while (std::isspace(*p)) p++; // The call to `next_element' will skip past the account name, // and return a pointer to the beginning of the amount. Once // we know where the amount is, we can strip off any // transaction note, and parse it. char * cost_str = next_element(p, true); char * note_str; // If there is no amount given, it is intended as an implicit // amount; we must use the opposite of the value of the // preceding transaction. if (! cost_str || ! *cost_str || *cost_str == ';') { if (cost_str && *cost_str) { while (*cost_str == ';' || std::isspace(*cost_str)) cost_str++; xact->note = cost_str; } xact->cost = NULL; } else { note_str = std::strchr(cost_str, ';'); if (note_str) { *note_str++ = '\0'; while (std::isspace(*note_str)) note_str++; xact->note = note_str; } for (char * t = cost_str + (std::strlen(cost_str) - 1); std::isspace(*t); t--) *t = '\0'; xact->cost = create_amount(cost_str); } #ifdef HUQUQULLAH if (*p == '!') { xact->exempt_or_necessary = true; p++; } #endif xact->acct = main_ledger.find_account(p); #ifdef HUQUQULLAH if (xact->acct->exempt_or_necessary) xact->exempt_or_necessary = true; #endif if (compute_balances && xact->cost) xact->acct->balance.credit(xact->cost); curr->xacts.push_back(xact); } else if (line[0] == 'Y') { current_year = std::atoi(line + 2); } } if (curr) finalize_entry(curr, compute_balances); return true; } } // namespace ledger