ledger/src/format.cc
2009-02-20 15:41:48 -04:00

366 lines
9.1 KiB
C++

/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "format.h"
#include "scope.h"
namespace ledger {
void format_t::element_t::dump(std::ostream& out) const
{
out << "Element: ";
switch (type) {
case STRING: out << " STRING"; break;
case EXPR: out << " EXPR"; break;
}
out << " flags: 0x" << std::hex << int(flags());
out << " min: ";
out << std::right;
out.width(2);
out << std::dec << int(min_width);
out << " max: ";
out << std::right;
out.width(2);
out << std::dec << int(max_width);
switch (type) {
case STRING: out << " str: '" << chars << "'" << std::endl; break;
case EXPR: out << " expr: " << expr << std::endl; break;
}
}
format_t::element_t * format_t::parse_elements(const string& fmt)
{
std::auto_ptr<element_t> result;
element_t * current = NULL;
char buf[1024];
char * q = buf;
// The following format codes need to be implemented as functions:
//
// d: COMPLETE_DATE_STRING
// D: DATE_STRING
// S: SOURCE; break
// B: ENTRY_BEG_POS
// b: ENTRY_BEG_LINE
// E: ENTRY_END_POS
// e: ENTRY_END_LINE
// X: CLEARED
// Y: ENTRY_CLEARED
// C: CODE
// P: PAYEE
// W: OPT_ACCOUNT
// a: ACCOUNT_NAME
// A: ACCOUNT_FULLNAME
// t: AMOUNT
// o: OPT_AMOUNT
// T: TOTAL
// N: NOTE
// n: OPT_NOTE
// _: DEPTH_SPACER
//
// xB: XACT_BEG_POS
// xb: XACT_BEG_LINE
// xE: XACT_END_POS
// xe: XACT_END_LINE
for (const char * p = fmt.c_str(); *p; p++) {
if (*p != '%' && *p != '\\') {
*q++ = *p;
continue;
}
if (! result.get()) {
result.reset(new element_t);
current = result.get();
} else {
current->next.reset(new element_t);
current = current->next.get();
}
if (q != buf) {
current->type = element_t::STRING;
current->chars = string(buf, q);
q = buf;
current->next.reset(new element_t);
current = current->next.get();
}
if (*p == '\\') {
p++;
current->type = element_t::STRING;
switch (*p) {
case 'b': current->chars = "\b"; break;
case 'f': current->chars = "\f"; break;
case 'n': current->chars = "\n"; break;
case 'r': current->chars = "\r"; break;
case 't': current->chars = "\t"; break;
case 'v': current->chars = "\v"; break;
case '\\': current->chars = "\\"; break;
default: current->chars = string(1, *p); break;
}
continue;
}
++p;
while (*p == '-') {
switch (*p) {
case '-':
current->add_flags(ELEMENT_ALIGN_LEFT);
break;
}
++p;
}
int num = 0;
while (*p && std::isdigit(*p)) {
num *= 10;
num += *p++ - '0';
}
current->min_width = num;
if (*p == '.') {
++p;
num = 0;
while (*p && std::isdigit(*p)) {
num *= 10;
num += *p++ - '0';
}
current->max_width = num;
if (current->min_width == 0)
current->min_width = current->max_width;
}
switch (*p) {
case '%':
current->type = element_t::STRING;
current->chars = "%";
break;
case '(':
case '[': {
std::istringstream str(p);
current->type = element_t::EXPR;
string temp(p);
current->expr.parse(str, expr_t::PARSE_SINGLE, &temp);
if (str.eof()) {
current->expr.set_text(p);
p += std::strlen(p);
} else {
assert(str.good());
istream_pos_type pos = str.tellg();
current->expr.set_text(string(p, p + long(pos)));
p += long(pos) - 1;
// Don't gobble up any whitespace
const char * base = p;
while (p >= base && std::isspace(*p))
p--;
}
break;
}
default:
current->type = element_t::EXPR;
current->chars = string(FMT_PREFIX) + *p;
current->expr.parse(current->chars);
break;
}
}
if (q != buf) {
if (! result.get()) {
result.reset(new element_t);
current = result.get();
} else {
current->next.reset(new element_t);
current = current->next.get();
}
current->type = element_t::STRING;
current->chars = string(buf, q);
}
return result.release();
}
void format_t::format(std::ostream& out_str, scope_t& scope)
{
for (element_t * elem = elements.get(); elem; elem = elem->next.get()) {
std::ostringstream out;
string name;
if (elem->has_flags(ELEMENT_ALIGN_LEFT))
out << std::left;
else
out << std::right;
switch (elem->type) {
case element_t::STRING:
if (elem->min_width > 0)
out.width(elem->min_width);
out << elem->chars;
break;
case element_t::EXPR:
try {
elem->expr.compile(scope);
value_t value;
if (elem->expr.is_function()) {
call_scope_t args(scope);
args.push_back(long(elem->max_width));
value = elem->expr.get_function()(args);
} else {
value = elem->expr.calc(scope);
}
DEBUG("format.expr", "value = (" << value << ")");
value.print(out, elem->min_width);
}
catch (const calc_error&) {
add_error_context("While calculating format expression:");
add_error_context(expr_context(elem->expr));
throw;
}
break;
default:
assert(false);
break;
}
if (elem->max_width > 0 || elem->min_width > 0) {
unistring temp(out.str());
string result;
if (elem->max_width > 0 && elem->max_width < temp.length()) {
result = truncate(temp, elem->max_width);
} else {
result = temp.extract();
for (int i = 0; i < (int)elem->min_width - (int)temp.length(); i++)
result += " ";
}
out_str << result;
} else {
out_str << out.str();
}
}
}
string format_t::truncate(const unistring& ustr, std::size_t width,
const int account_abbrev_length)
{
assert(width < 4095);
const std::size_t len = ustr.length();
if (len <= width)
return ustr.extract();
std::ostringstream buf;
elision_style_t style = TRUNCATE_TRAILING;
if (account_abbrev_length > 0)
style = ABBREVIATE;
switch (style) {
case TRUNCATE_LEADING:
// This method truncates at the beginning.
buf << ".." << ustr.extract(len - width, width);
break;
case TRUNCATE_MIDDLE:
// This method truncates in the middle.
buf << ustr.extract(0, width / 2)
<< ".."
<< ustr.extract(len - (width / 2 + width % 2),
width / 2 + width % 2);
break;
case ABBREVIATE:
if (account_abbrev_length > 0) {
std::list<string> parts;
string::size_type beg = 0;
string strcopy(ustr.extract());
for (string::size_type pos = strcopy.find(':');
pos != string::npos;
beg = pos + 1, pos = strcopy.find(':', beg))
parts.push_back(string(strcopy, beg, pos - beg));
parts.push_back(string(strcopy, beg));
std::ostringstream result;
std::size_t newlen = len;
for (std::list<string>::iterator i = parts.begin();
i != parts.end();
i++) {
// Don't contract the last element
std::list<string>::iterator x = i;
if (++x == parts.end()) {
result << *i;
break;
}
if (newlen > width) {
unistring temp(*i);
result << temp.extract(0, account_abbrev_length) << ":";
newlen -= temp.length() - account_abbrev_length;
} else {
result << *i << ":";
}
}
if (newlen > width) {
// Even abbreviated its too big to show the last account, so
// abbreviate all but the last and truncate at the beginning.
unistring temp(result.str());
buf << ".." << temp.extract(temp.length() - width - 2, width - 2);
} else {
buf << result.str();
}
break;
}
// fall through...
case TRUNCATE_TRAILING:
// This method truncates at the end (the default).
buf << ustr.extract(0, width - 2) << "..";
break;
}
return buf.str();
}
} // namespace ledger