summaryrefslogtreecommitdiff
path: root/src/format.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/format.cc')
-rw-r--r--src/format.cc235
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