diff options
Diffstat (limited to 'src/format.cc')
-rw-r--r-- | src/format.cc | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..774af6ca --- /dev/null +++ b/src/format.cc @@ -0,0 +1,235 @@ +#include "format.h" +#include "pyinterp.h" + +namespace ledger { + +void format_t::parse(const string& fmt) +{ + element_t * current = NULL; + + char buf[1024]; + char * q = buf; + + if (elements.size() > 0) + clear_elements(); + format_string = fmt; + + for (const char * p = fmt.c_str(); *p; p++) { + if (*p != '%' && *p != '\\') { + *q++ = *p; + continue; + } + else if (*p == '\\') { + p++; + switch (*p) { + case 'b': *q++ = '\b'; break; + case 'f': *q++ = '\f'; break; + case 'n': *q++ = '\n'; break; + case 'r': *q++ = '\r'; break; + case 't': *q++ = '\t'; break; + case 'v': *q++ = '\v'; break; + default: + *q++ = *p; + break; + } + continue; + } + else { + assert(*p == '%'); + if (*(p + 1) == '%') { + p++; // %% is the same as \% + *q++ = *p; + continue; + } + } + + current = new element_t; + elements.push_back(current); + + if (q != buf) { + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + q = buf; + + current = new element_t; + elements.push_back(current); + } + + ++p; + if (*p == '-') { + current->align_left = true; + ++p; + } + + if (*p && std::isdigit(*p)) { + int num = *p++ - '0'; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; + } + + if (*p == '.') { + ++p; + int num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + + current->max_width = num; + if (current->min_width == -1) + current->min_width = current->max_width; + } + + if (current->max_width != -1 && current->min_width != -1 && + current->max_width < current->min_width) + throw_(format_exception, "Maximum width is less than the minimum width"); + + switch (*p) { + case '|': + current->kind = element_t::COLUMN; + break; + + case '{': + case '(': { + char open = *p; + char close = *p == '{' ? '}' : ')'; + ++p; + const char * b = p; + int depth = 1; + while (*p) { + if (*p == close && --depth == 0) + break; + else if (*p == open) + ++depth; + p++; + } + if (*p != close) + throw_(format_exception, "Missing '" << close << "'"); + + if (open == '{') { + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(b, p)); + } else { + assert(! current->format); + current->kind = element_t::GROUP; + current->format = new format_t(string(b, p)); + } + break; + } + + default: + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(p, p + 1)); + break; + } + } + + if (q != buf) { + current = new element_t; + elements.push_back(current); + + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + } +} + +void format_t::compile(xml::node_t * context) +{ + for (std::list<element_t *>::iterator i = elements.begin(); + i != elements.end(); + i++) + switch ((*i)->kind) { + case element_t::XPATH: + assert((*i)->xpath); + (*i)->xpath->compile(context); + break; + case element_t::GROUP: + assert((*i)->format); + (*i)->format->compile(context); + break; + default: + break; + } +} + +int format_t::element_formatter_t::operator() + (std::ostream& out_str, element_t * elem, xml::node_t * context, + int column) const +{ + if (elem->kind == element_t::COLUMN) { + if (elem->max_width != -1 && elem->max_width < column) { + out_str << '\n'; + column = 0; + } + + if (elem->min_width != -1 && elem->min_width > column) { + out_str << string(elem->min_width - column, ' '); + column = elem->min_width; + } + return column; + } + + std::ostringstream out; + + if (elem->align_left) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + + int start_column = column; + + if (elem->kind == element_t::XPATH) + elem->xpath->calc(context).strip_annotations() + .write(out, elem->min_width, elem->max_width); + else if (elem->kind == element_t::GROUP) + column = elem->format->format(out, context, column); + else if (elem->kind == element_t::TEXT) + out << *elem->chars; + else + assert(0); + + string temp = out.str(); + for (string::const_iterator i = temp.begin(); + i != temp.end(); + i++) + if (*i == '\n' || *i == '\r') + column = 0; + else + column++; + + int virtual_width = column - start_column; + + if (elem->min_width != -1 && virtual_width < elem->min_width) { + out_str << temp << string(' ', elem->min_width - virtual_width); + } + else if (elem->max_width != -1 && virtual_width > elem->max_width) { + temp.erase(temp.length() - (virtual_width - elem->max_width)); + out_str << temp; + } + else { + out_str << temp; + } + + return column; +} + +int format_t::format(std::ostream& out, xml::node_t * context, + int column, const element_formatter_t& formatter) const +{ + for (std::list<element_t *>::const_iterator i = elements.begin(); + i != elements.end(); + i++) + column = formatter(out, *i, context, column); + + return column; +} + +} // namespace ledger |