You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
9.8 KiB
411 lines
9.8 KiB
#ifdef _MSC_VER
|
|
#include "stdafx.h"
|
|
#endif
|
|
|
|
#include "cpptempl.h"
|
|
|
|
#include <sstream>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
namespace cpptempl
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Data classes
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// data_map
|
|
data_ptr& data_map::operator [](const std::string& key) {
|
|
return data[key];
|
|
}
|
|
bool data_map::empty() {
|
|
return data.empty();
|
|
}
|
|
bool data_map::has(const std::string& key) {
|
|
return data.find(key) != data.end();
|
|
}
|
|
|
|
// data_ptr
|
|
data_ptr::data_ptr(DataValue* data) : ptr(data) {}
|
|
data_ptr::data_ptr(DataList* data) : ptr(data) {}
|
|
data_ptr::data_ptr(DataMap* data) : ptr(data) {}
|
|
|
|
template<>
|
|
inline void data_ptr::operator = (const data_ptr& data) {
|
|
ptr = data.ptr;
|
|
}
|
|
|
|
template<>
|
|
void data_ptr::operator = (const std::string& data) {
|
|
ptr.reset(new DataValue(data));
|
|
}
|
|
|
|
template<>
|
|
void data_ptr::operator = (const data_map& data) {
|
|
ptr.reset(new DataMap(data));
|
|
}
|
|
|
|
void data_ptr::push_back(const data_ptr& data) {
|
|
if (!ptr) {
|
|
ptr.reset(new DataList(data_list()));
|
|
}
|
|
data_list& list = ptr->getlist();
|
|
list.push_back(data);
|
|
}
|
|
|
|
// base data
|
|
std::string Data::getvalue()
|
|
{
|
|
throw TemplateException("Data item is not a value") ;
|
|
}
|
|
|
|
data_list& Data::getlist()
|
|
{
|
|
throw TemplateException("Data item is not a list") ;
|
|
}
|
|
data_map& Data::getmap()
|
|
{
|
|
throw TemplateException("Data item is not a dictionary") ;
|
|
}
|
|
// data value
|
|
std::string DataValue::getvalue()
|
|
{
|
|
return m_value ;
|
|
}
|
|
bool DataValue::empty()
|
|
{
|
|
return m_value.empty();
|
|
}
|
|
// data list
|
|
data_list& DataList::getlist()
|
|
{
|
|
return m_items ;
|
|
}
|
|
|
|
bool DataList::empty()
|
|
{
|
|
return m_items.empty();
|
|
}
|
|
// data map
|
|
data_map& DataMap:: getmap()
|
|
{
|
|
return m_items ;
|
|
}
|
|
bool DataMap::empty()
|
|
{
|
|
return m_items.empty();
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// parse_val
|
|
//////////////////////////////////////////////////////////////////////////
|
|
data_ptr parse_val(std::string key, data_map &data)
|
|
{
|
|
// quoted string
|
|
if (key[0] == '\"')
|
|
{
|
|
return make_data(boost::trim_copy_if(key, boost::is_any_of("\""))) ;
|
|
}
|
|
// check for dotted notation, i.e [foo.bar]
|
|
size_t index = key.find(".") ;
|
|
if (index == std::string::npos)
|
|
{
|
|
if (!data.has(key))
|
|
{
|
|
return make_data("{$" + key + "}") ;
|
|
}
|
|
return data[key] ;
|
|
}
|
|
|
|
std::string sub_key = key.substr(0, index) ;
|
|
if (!data.has(sub_key))
|
|
{
|
|
return make_data("{$" + key + "}") ;
|
|
}
|
|
data_ptr item = data[sub_key] ;
|
|
return parse_val(key.substr(index+1), item->getmap()) ;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Token classes
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// defaults, overridden by subclasses with children
|
|
void Token::set_children( token_vector & )
|
|
{
|
|
throw TemplateException("This token type cannot have children") ;
|
|
}
|
|
|
|
token_vector & Token::get_children()
|
|
{
|
|
throw TemplateException("This token type cannot have children") ;
|
|
}
|
|
|
|
// TokenText
|
|
TokenType TokenText::gettype()
|
|
{
|
|
return TOKEN_TYPE_TEXT ;
|
|
}
|
|
|
|
void TokenText::gettext( std::ostream &stream, data_map & )
|
|
{
|
|
stream << m_text ;
|
|
}
|
|
|
|
// TokenVar
|
|
TokenType TokenVar::gettype()
|
|
{
|
|
return TOKEN_TYPE_VAR ;
|
|
}
|
|
|
|
void TokenVar::gettext( std::ostream &stream, data_map &data )
|
|
{
|
|
stream << parse_val(m_key, data)->getvalue() ;
|
|
}
|
|
|
|
// TokenFor
|
|
TokenFor::TokenFor(std::string expr)
|
|
{
|
|
std::vector<std::string> elements ;
|
|
boost::split(elements, expr, boost::is_space()) ;
|
|
if (elements.size() != 4u)
|
|
{
|
|
throw TemplateException("Invalid syntax in for statement") ;
|
|
}
|
|
m_val = elements[1] ;
|
|
m_key = elements[3] ;
|
|
}
|
|
|
|
TokenType TokenFor::gettype()
|
|
{
|
|
return TOKEN_TYPE_FOR ;
|
|
}
|
|
|
|
void TokenFor::gettext( std::ostream &stream, data_map &data )
|
|
{
|
|
data_ptr value = parse_val(m_key, data) ;
|
|
data_list &items = value->getlist() ;
|
|
for (size_t i = 0 ; i < items.size() ; ++i)
|
|
{
|
|
data_map loop ;
|
|
loop["index"] = make_data(boost::lexical_cast<std::string>(i+1)) ;
|
|
loop["index0"] = make_data(boost::lexical_cast<std::string>(i)) ;
|
|
data["loop"] = make_data(loop);
|
|
data[m_val] = items[i] ;
|
|
for(size_t j = 0 ; j < m_children.size() ; ++j)
|
|
{
|
|
m_children[j]->gettext(stream, data) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TokenFor::set_children( token_vector &children )
|
|
{
|
|
m_children.assign(children.begin(), children.end()) ;
|
|
}
|
|
|
|
token_vector & TokenFor::get_children()
|
|
{
|
|
return m_children;
|
|
}
|
|
|
|
// TokenIf
|
|
TokenType TokenIf::gettype()
|
|
{
|
|
return TOKEN_TYPE_IF ;
|
|
}
|
|
|
|
void TokenIf::gettext( std::ostream &stream, data_map &data )
|
|
{
|
|
if (is_true(m_expr, data))
|
|
{
|
|
for(size_t j = 0 ; j < m_children.size() ; ++j)
|
|
{
|
|
m_children[j]->gettext(stream, data) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TokenIf::is_true( std::string expr, data_map &data )
|
|
{
|
|
std::vector<std::string> elements ;
|
|
boost::split(elements, expr, boost::is_space()) ;
|
|
|
|
if (elements[1] == "not")
|
|
{
|
|
return parse_val(elements[2], data)->empty() ;
|
|
}
|
|
if (elements.size() == 2)
|
|
{
|
|
return ! parse_val(elements[1], data)->empty() ;
|
|
}
|
|
data_ptr lhs = parse_val(elements[1], data) ;
|
|
data_ptr rhs = parse_val(elements[3], data) ;
|
|
if (elements[2] == "==")
|
|
{
|
|
return lhs->getvalue() == rhs->getvalue() ;
|
|
}
|
|
return lhs->getvalue() != rhs->getvalue() ;
|
|
}
|
|
|
|
void TokenIf::set_children( token_vector &children )
|
|
{
|
|
m_children.assign(children.begin(), children.end()) ;
|
|
}
|
|
|
|
token_vector & TokenIf::get_children()
|
|
{
|
|
return m_children;
|
|
}
|
|
|
|
// TokenEnd
|
|
TokenType TokenEnd::gettype()
|
|
{
|
|
return m_type == "endfor" ? TOKEN_TYPE_ENDFOR : TOKEN_TYPE_ENDIF ;
|
|
}
|
|
|
|
void TokenEnd::gettext( std::ostream &, data_map &)
|
|
{
|
|
throw TemplateException("End-of-control statements have no associated text") ;
|
|
}
|
|
|
|
// gettext
|
|
// generic helper for getting text from tokens.
|
|
|
|
std::string gettext(token_ptr token, data_map &data)
|
|
{
|
|
std::ostringstream stream ;
|
|
token->gettext(stream, data) ;
|
|
return stream.str() ;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// parse_tree
|
|
// recursively parses list of tokens into a tree
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void parse_tree(token_vector &tokens, token_vector &tree, TokenType until)
|
|
{
|
|
while(! tokens.empty())
|
|
{
|
|
// 'pops' first item off list
|
|
token_ptr token = tokens[0] ;
|
|
tokens.erase(tokens.begin()) ;
|
|
|
|
if (token->gettype() == TOKEN_TYPE_FOR)
|
|
{
|
|
token_vector children ;
|
|
parse_tree(tokens, children, TOKEN_TYPE_ENDFOR) ;
|
|
token->set_children(children) ;
|
|
}
|
|
else if (token->gettype() == TOKEN_TYPE_IF)
|
|
{
|
|
token_vector children ;
|
|
parse_tree(tokens, children, TOKEN_TYPE_ENDIF) ;
|
|
token->set_children(children) ;
|
|
}
|
|
else if (token->gettype() == until)
|
|
{
|
|
return ;
|
|
}
|
|
tree.push_back(token) ;
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// tokenize
|
|
// parses a template into tokens (text, for, if, variable)
|
|
//////////////////////////////////////////////////////////////////////////
|
|
token_vector & tokenize(std::string text, token_vector &tokens)
|
|
{
|
|
while(! text.empty())
|
|
{
|
|
size_t pos = text.find("{") ;
|
|
if (pos == std::string::npos)
|
|
{
|
|
if (! text.empty())
|
|
{
|
|
tokens.push_back(token_ptr(new TokenText(text))) ;
|
|
}
|
|
return tokens ;
|
|
}
|
|
std::string pre_text = text.substr(0, pos) ;
|
|
if (! pre_text.empty())
|
|
{
|
|
tokens.push_back(token_ptr(new TokenText(pre_text))) ;
|
|
}
|
|
text = text.substr(pos+1) ;
|
|
if (text.empty())
|
|
{
|
|
tokens.push_back(token_ptr(new TokenText("{"))) ;
|
|
return tokens ;
|
|
}
|
|
|
|
// variable
|
|
if (text[0] == '$')
|
|
{
|
|
pos = text.find("}") ;
|
|
if (pos != std::string::npos)
|
|
{
|
|
tokens.push_back(token_ptr (new TokenVar(text.substr(1, pos-1)))) ;
|
|
text = text.substr(pos+1) ;
|
|
}
|
|
}
|
|
// control statement
|
|
else if (text[0] == '%')
|
|
{
|
|
pos = text.find("}") ;
|
|
if (pos != std::string::npos)
|
|
{
|
|
std::string expression = boost::trim_copy(text.substr(1, pos-2)) ;
|
|
text = text.substr(pos+1) ;
|
|
if (boost::starts_with(expression, "for"))
|
|
{
|
|
tokens.push_back(token_ptr (new TokenFor(expression))) ;
|
|
}
|
|
else if (boost::starts_with(expression, "if"))
|
|
{
|
|
tokens.push_back(token_ptr (new TokenIf(expression))) ;
|
|
}
|
|
else
|
|
{
|
|
tokens.push_back(token_ptr (new TokenEnd(boost::trim_copy(expression)))) ;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tokens.push_back(token_ptr(new TokenText("{"))) ;
|
|
}
|
|
}
|
|
return tokens ;
|
|
}
|
|
|
|
/************************************************************************
|
|
* parse
|
|
*
|
|
* 1. tokenizes template
|
|
* 2. parses tokens into tree
|
|
* 3. resolves template
|
|
* 4. returns converted text
|
|
************************************************************************/
|
|
std::string parse(std::string templ_text, data_map &data)
|
|
{
|
|
std::ostringstream stream ;
|
|
parse(stream, templ_text, data) ;
|
|
return stream.str() ;
|
|
}
|
|
void parse(std::ostream &stream, std::string templ_text, data_map &data)
|
|
{
|
|
token_vector tokens ;
|
|
tokenize(templ_text, tokens) ;
|
|
token_vector tree ;
|
|
parse_tree(tokens, tree) ;
|
|
|
|
for (size_t i = 0 ; i < tree.size() ; ++i)
|
|
{
|
|
// Recursively calls gettext on each node in the tree.
|
|
// gettext returns the appropriate text for that node.
|
|
// for text, itself;
|
|
// for variable, substitution;
|
|
// for control statement, recursively gets kids
|
|
tree[i]->gettext(stream, data) ;
|
|
}
|
|
}
|
|
}
|