From edae25d41d89564a21e0e0a11b4cde2287ba889e Mon Sep 17 00:00:00 2001 From: Pixel Date: Fri, 25 Nov 2011 00:06:34 -0800 Subject: Adding a simple 'Mustache' template processor in C++. --- src/SimpleMustache.cc | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/SimpleMustache.cc (limited to 'src/SimpleMustache.cc') diff --git a/src/SimpleMustache.cc b/src/SimpleMustache.cc new file mode 100644 index 0000000..8dffc74 --- /dev/null +++ b/src/SimpleMustache.cc @@ -0,0 +1,362 @@ +#include "SimpleMustache.h" +#include "BStream.h" +#include "BRegex.h" + +Balau::SimpleMustache::Context & Balau::SimpleMustache::Context::Proxy::operator[](const char * str) { + Assert(m_parent->m_type == CONTEXTLIST); + String key = str; + ContextList & ctxLst = m_parent->m_contextList; + if (m_idx <= 0) + m_idx = ctxLst.size() + m_idx + 1; + if (m_idx <= 0) + m_idx = 1; + if (ctxLst.size() < m_idx) + ctxLst.resize(m_idx); + SubContext & subCtx = ctxLst[m_idx - 1]; + SubContext::iterator s = subCtx.find(key); + Context * ctx; + if (s == subCtx.end()) { + ctx = subCtx[key] = new Context(UNKNOWN); + } else { + ctx = s->second; + } + return *ctx; +} + +Balau::SimpleMustache::Context & Balau::SimpleMustache::Context::operator[](const char * str) { + ensureList(true); + String key = str; + SubContext & sCtx = m_contextList[0]; + SubContext::iterator s = sCtx.find(str); + Context * ctx; + if (s == sCtx.end()) { + ctx = sCtx[key] = new Context(UNKNOWN); + } else { + ctx = s->second; + } + return *ctx; +} + +void Balau::SimpleMustache::Context::ensureList(bool single) { + if (m_type == CONTEXTLIST) { + if (single) + empty(true); + } else { + empty(); + m_type = CONTEXTLIST; + } +} + +void Balau::SimpleMustache::Context::empty(bool skipFirst) { + for (ContextList::iterator i = m_contextList.begin(); i != m_contextList.end(); i++) { + if (skipFirst && i == m_contextList.begin()) + continue; + for (SubContext::iterator j = i->begin(); j != i->end(); j++) { + delete j->second; + } + } + m_contextList.resize(skipFirst ? 1 : 0); +} + +static const Balau::Regex changing("^(.*) +(.*)$"); + +void Balau::SimpleMustache::setTemplate(IO _h) { + empty(); + IO h(new BStream(_h)); + h->detach(); + String srtMarker = "{{"; + String endMarker = "}}"; + enum { + PLAIN, + READING_SRT, + READING_INNER, + READING_END, + } state = PLAIN; + enum { + NORMAL, + SECTION, + NOESCAPE, + ENDSECTION, + INVERTED, + PARTIAL, + CHANGING, + COMMENT, + } tagType = NORMAL; + int dist = 0; + char buf[16 * 1024 + 1]; + char * p = buf; + static const int bufLen = 16 * 1024; + Fragment * curFragment = new Fragment(); + curFragment->type = Fragment::STRING; + bool stupidMarker = false; + bool readFirstEnd = false; + bool beginning = false; + + while (!h->isEOF()) { + uint8_t c = h->readU8(); + switch (state) { + case PLAIN: + if (c != srtMarker[0]) { + *p++ = static_cast(c); + if (p == (buf + bufLen)) { + *p = 0; + curFragment->str += String(buf, p - buf); + p = buf; + } + break; + } else { + if (p != buf) { + *p = 0; + curFragment->str += String(buf, p - buf); + p = buf; + } + state = READING_SRT; + dist = 0; + } + case READING_SRT: + if (c != srtMarker[dist]) { + curFragment->str += srtMarker.extract(0, dist) + String((const char *) &c, 1); + state = PLAIN; + } else { + if (++dist == srtMarker.strlen()) { + if (curFragment->str.strlen() != 0) { + m_fragments.push_back(curFragment); + curFragment = new Fragment(); + } + curFragment->type = Fragment::UNKNOWN; + state = READING_INNER; + beginning = true; + } + } + break; + case READING_INNER: + if (beginning) { + Assert(p == buf); + beginning = false; + tagType = NORMAL; + stupidMarker = false; + bool skip = true; + switch (c) { + case '#': + tagType = SECTION; + break; + case '&': + tagType = NOESCAPE; + break; + case '/': + tagType = ENDSECTION; + break; + case '^': + tagType = INVERTED; + break; + case '>': + tagType = PARTIAL; + break; + case '=': + tagType = CHANGING; + break; + case '!': + tagType = COMMENT; + break; + case '{': + if (srtMarker == "{{") { + stupidMarker = true; + tagType = NOESCAPE; + break; + } + default: + skip = false; + break; + } + if (skip) + continue; + } + if (c != endMarker[0]) { + *p++ = static_cast(c); + if (p == (buf + bufLen)) { + *p = 0; + curFragment->str += String(buf, p - buf); + p = buf; + } + break; + } else { + if (p != buf) { + *p = 0; + curFragment->str += String(buf, p - buf); + p = buf; + } + state = READING_END; + readFirstEnd = true; + dist = 0; + } + case READING_END: + if (c != endMarker[dist]) { + curFragment->str += endMarker.extract(0, dist) + String((const char *) &c, 1); + state = READING_INNER; + } else { + if (stupidMarker && readFirstEnd) { + readFirstEnd = 0; + dist--; + } + if (++dist == endMarker.strlen()) { + bool pushIt = true; + String str = curFragment->str; + Assert(str.strlen() != 0); + Assert(tagType != PARTIAL); // not yet supported + Regex::Captures c; + switch (tagType) { + case NORMAL: + curFragment->type = Fragment::VARIABLE; + break; + case SECTION: + curFragment->type = Fragment::SECTION; + break; + case NOESCAPE: + curFragment->type = Fragment::NOESCAPE; + break; + case ENDSECTION: + curFragment->type = Fragment::END_SECTION; + break; + case INVERTED: + curFragment->type = Fragment::INVERTED; + break; + case PARTIAL: + Assert(0); + break; + case CHANGING: + pushIt = false; + Assert(str[0] == '='); + Assert(str[-1] == '='); + c = changing.match(str.to_charp()); + Assert(c.size() == 3); + srtMarker = c[1]; + endMarker = c[2]; + Assert(srtMarker.strlen() != 0); + Assert(endMarker.strlen() != 0); + Assert(srtMarker[0] != endMarker[0]); + Assert(srtMarker.strchr(' ') < 0); + Assert(srtMarker.strchr('=') < 0); + Assert(endMarker.strchr(' ') < 0); + Assert(endMarker.strchr('=') < 0); + break; + case COMMENT: + pushIt = false; + break; + } + if (pushIt) { + Assert(curFragment->type != Fragment::UNKNOWN); + m_fragments.push_back(curFragment); + curFragment = new Fragment(); + } + curFragment->type = Fragment::STRING; + curFragment->str = ""; + state = PLAIN; + } + } + break; + } + } + + Assert(state == PLAIN); + + if (p != buf) { + *p = 0; + curFragment->str += String(buf, p - buf); + } + + if (curFragment->str.strlen() == 0) + delete curFragment; + else + m_fragments.push_back(curFragment); +} + +Balau::SimpleMustache::Fragments::iterator Balau::SimpleMustache::render_r(IO h, Context * ctx, const String & endSection, Fragments::iterator begin, bool noWrite, int forceIdx) { + Fragments::iterator cur; + Fragments::iterator end = m_fragments.end(); + + if (endSection.strlen() != 0) { + int depth = 0; + for (cur = begin; cur != end; cur++) { + Fragment * fr = *cur; + if (fr->type == Fragment::END_SECTION) { + if (depth == 0) { + Assert(fr->str == endSection); + end = cur; + break; + } else { + depth--; + } + } + if ((fr->type == Fragment::SECTION) || (fr->type == Fragment::INVERTED)) + depth++; + } + Assert(end != m_fragments.end()); + } + + if (!ctx) { + if (noWrite) + return end; + for (cur = begin; cur != end; cur++) { + Fragment * fr = *cur; + if(fr->type == Fragment::STRING); + h->write(fr->str); + } + return end; + } + + Assert(!noWrite); + + Context::ContextList::iterator sCtx; + int idx = 0; + for (sCtx = ctx->m_contextList.begin(); sCtx != ctx->m_contextList.end(); sCtx++, idx++) { + if ((forceIdx >= 0) && (idx != forceIdx)) + continue; + Context::SubContext::iterator f; + for (cur = begin; cur != end; cur++) { + Fragment * fr = *cur; + Assert(fr->type != Fragment::UNKNOWN); + Assert(fr->type != Fragment::END_SECTION); + switch (fr->type) { + case Fragment::STRING: + h->write(fr->str); + break; + case Fragment::VARIABLE: + f = sCtx->find(fr->str); + if (f != sCtx->end()) { + Context * var = f->second; + Assert(var->m_type == Context::STRING); + h->write(escape(var->m_str)); + } + break; + case Fragment::NOESCAPE: + f = sCtx->find(fr->str); + if (f != sCtx->end()) { + Context * var = f->second; + Assert(var->m_type == Context::STRING); + h->write(var->m_str); + } + break; + case Fragment::SECTION: + f = sCtx->find(fr->str); + if (f != sCtx->end()) { + Context * var = f->second; + Assert((var->m_type == Context::BOOLSEC) || (var->m_type == Context::CONTEXTLIST)); + if ((var->m_type == Context::BOOLSEC) && (var->m_bool)) { + cur = render_r(h, ctx, fr->str, ++cur, false, idx); + break; + } else if (var->m_type == Context::CONTEXTLIST) { + cur = render_r(h, var, fr->str, ++cur, false, -1); + break; + } + } + cur = render_r(h, NULL, fr->str, ++cur, true, -1); + break; + case Fragment::INVERTED: + cur = render_r(h, NULL, fr->str, ++cur, sCtx->find(fr->str) != sCtx->end(), -1); + break; + } + } + } + + return end; +} -- cgit v1.2.3