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

#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) ;
}
}
}