ledger/format.cc
John Wiegley 94e76ae87e two major changes
Complete changed the way format strings are handled.  They are now
compiled first, which is far more efficient than what was being done
before.

Also, there is now a global ledger::commodity_t::commodities map,
which saves me from having to pass the current journal around to a
zillion different functions, for the sole purpose of making sure that
all commodity symbols that are parsed refer to the same commodity
object.
2004-07-30 21:57:02 -04:00

241 lines
5.3 KiB
C++

#include "format.h"
#include "error.h"
namespace ledger {
std::string truncated(const std::string& str, unsigned int width)
{
char buf[256];
std::memset(buf, '\0', 255);
assert(width < 256);
std::strncpy(buf, str.c_str(), str.length());
if (buf[width])
std::strcpy(&buf[width - 2], "..");
return buf;
}
std::string maximal_account_name(const item_t * item,
const item_t * parent)
{
std::string name = item->account->name;
for (const item_t * i = item->parent;
i && i->account && i != parent;
i = i->parent)
name = i->account->name + ":" + name;
return name;
}
node_t * format_t::value_expr = NULL;
node_t * format_t::total_expr = NULL;
element_t * format_t::parse_elements(const std::string& fmt)
{
element_t * result = NULL;
element_t * current = NULL;
std::string str;
for (const char * p = fmt.c_str(); *p; p++) {
if (*p == '%') {
if (! result) {
current = result = new element_t;
} else {
current->next = new element_t;
current = current->next;
}
if (! str.empty()) {
current->type = element_t::STRING;
current->chars = str;
str = "";
current->next = new element_t;
current = current->next;
}
++p;
if (*p == '-') {
current->align_left = true;
++p;
}
std::string num;
while (*p && std::isdigit(*p))
num += *p++;
if (! num.empty())
current->min_width = std::atol(num.c_str());
if (*p == '.') {
++p;
num = "";
while (*p && std::isdigit(*p))
num += *p++;
if (! num.empty()) {
current->max_width = std::atol(num.c_str());
if (current->min_width == 0)
current->min_width = current->max_width;
}
}
switch (*p) {
case '%':
current->type = element_t::STRING;
current->chars = "%";
break;
case '(':
++p;
num = "";
while (*p && *p != ')')
num += *p++;
if (*p != ')')
throw format_error("Missing ')'");
current->type = element_t::VALUE_EXPR;
current->val_expr = parse_expr(num);
break;
case '[':
++p;
num = "";
while (*p && *p != ']')
num += *p++;
if (*p != ']')
throw format_error("Missing ']'");
current->type = element_t::DATE_STRING;
current->chars = num;
break;
case 'd':
current->type = element_t::DATE_STRING;
current->chars = "%Y/%m/%d";
break;
case 'p': current->type = element_t::PAYEE; break;
case 'n': current->type = element_t::ACCOUNT_NAME; break;
case 'N': current->type = element_t::ACCOUNT_FULLNAME; break;
case 't': current->type = element_t::VALUE; break;
case 'T': current->type = element_t::TOTAL; break;
case '_': current->type = element_t::SPACER; break;
}
} else {
str += *p;
}
}
if (! str.empty()) {
if (! result) {
current = result = new element_t;
} else {
current->next = new element_t;
current = current->next;
}
current->type = element_t::STRING;
current->chars = str;
}
return result;
}
void format_t::format_elements(std::ostream& out, const item_t * item,
const item_t * displayed_parent) const
{
std::string result;
for (const element_t * elem = elements;
elem;
elem = elem->next) {
if (elem->align_left)
out << std::left;
else
out << std::right;
if (elem->min_width > 0)
out.width(elem->min_width);
switch (elem->type) {
case element_t::STRING:
out << elem->chars;;
break;
case element_t::VALUE_EXPR: {
balance_t value = elem->val_expr->compute(item);
value.write(out, elem->min_width,
elem->max_width > 0 ? elem->max_width : elem->min_width);
break;
}
case element_t::DATE_STRING:
if (item->date != -1) {
char buf[256];
std::strftime(buf, 255, elem->chars.c_str(), std::gmtime(&item->date));
out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width));
} else {
out << " ";
}
break;
case element_t::PAYEE:
out << (elem->max_width == 0 ?
item->payee : truncated(item->payee, elem->max_width));
break;
case element_t::ACCOUNT_NAME:
if (item->account) {
std::string name = maximal_account_name(item, displayed_parent);
out << (elem->max_width == 0 ? name : truncated(name, elem->max_width));
} else {
out << " ";
}
break;
case element_t::ACCOUNT_FULLNAME:
if (item->account)
out << (elem->max_width == 0 ?
item->account->fullname() :
truncated(item->account->fullname(), elem->max_width));
else
out << " ";
break;
case element_t::VALUE: {
balance_t value = compute_value(item);
value.write(out, elem->min_width,
elem->max_width > 0 ? elem->max_width : elem->min_width);
break;
}
case element_t::TOTAL: {
balance_t value = compute_total(item);
value.write(out, elem->min_width,
elem->max_width > 0 ? elem->max_width : elem->min_width);
break;
}
case element_t::SPACER: {
int depth = 0;
for (const item_t * i = item; i->parent; i = i->parent)
depth++;
for (const item_t * i = item->parent;
i && i->account && i != displayed_parent;
i = i->parent)
depth--;
while (--depth >= 0) {
if (elem->min_width > 0 || elem->max_width > 0)
out.width(elem->min_width > elem->max_width ?
elem->min_width : elem->max_width);
out << " ";
}
break;
}
default:
assert(0);
break;
}
}
}
} // namespace ledger