tags:

views:

414

answers:

8

I want simple C++ string based template library to replace strings at runtime.

For example, I will use

string template = "My name is {{name}}";

At runtime, I want the name to be changed based on actual one.

I found one example, www.stringtemplate.org but I little scared when its talks about antlr etc.

+4  A: 

Can you use sprintf?

There's also boost::format if you want to include boost.

strager
I'm suprised you're not getting death threats for even mentioning `sprintf`. But that's what I use, so +1 :)
John Dibling
`snprintf` to avoid buffer overruns. See http://libslack.org/manpages/snprintf.3.html
Atmocreations
\*death threat\* @John: There, now this answer is complete.
GMan
@John Dibling, Sadly `snprintf` isn't standard C++. It is standard C99, though, and is an extension probably all major standard library implementations out there for C++ which also handle C99.
strager
Neither of these solutions implements any lookup. The arguments must be in the same order as the replacements. There is no equivalent to `{{name}}`; there's just `%s`.
Potatoswatter
+3  A: 

If you have a function that replaces all occurrences of a string with another string:

std::string replace_all(std::string str, const std::string &remove, const std::string &insert) 
{
    std::string::size_type pos = 0;
    while ((pos = str.find(remove, pos)) != std::string::npos)
    {
        str.replace(pos, remove.size(), insert);
        pos++;
    }

    return str;
}

Then you can do this:

std::string pattern = "My name is {{first_name}} {{last_name}} and I live in {{location}}";

std::string str = replace_all(replace_all(replace_all(pattern, 
                       "{{first_name}}", "Homer"), 
                       "{{last_name}}", "Simpson"), 
                       "{{location}}", "Springfield");
Daniel Earwicker
A: 
string skeleton = "My name is {{name}}";
string placeholder = "{{name}}";
string::size_type pos = skeleton.find(placeholder);
while( pos != string::npos ) {
    skeleton.replace(pos, placeholder.length(), "Gopalakrishnan");
    pos = skeleton.find(placeholder, ++pos);
}
wilhelmtell
If you have other tags then you can store then in a container and then wrap up the loop with another loop that traverses the container, thus applying the replace loop with each of the tags on the input string. Let me know if you have other tags and I'll update the sample code.
wilhelmtell
A: 

It might be overkill, but you can also take a look at boost::spirit, and more specifically, the 'karma' part which is a text generator.

Alexandre Hamez
A: 

If you have many place holders, e.g if you have a macro template for a letter you want to be automatically expanded, some basic tokenization would be easier to maintain, implement and extend later. E.g

//pseudocode
foreach word in line
{
  if word=={{name}} print getFromDB(name,entry)
  else if word=={{address}} print getFromDB(address,entry)
  ..
  ..
  else print word

/*
* to get rid of if-else-if tree, you can just check if starts with {{ and ends with }} and directly query the string against a db/map/hash
*/

}

However, if the problem is a simple enough, and the template is small enough, just go for one of the answers mentioned above.

Sridhar Iyer
+1  A: 

Have you considered a set of inline functions that use ostringstram instead of "string templates"?

inline std::string name_template(const std::string& name)
{
    std::ostringstream os;
    os << "My name is " << name;
    return os.str();
}

There are other alternate approaches if you need more generality. For example a class hierarchy where the base provides a "format" interface and child classes implement it with the appropriate varying implementation.

Mark B
+6  A: 

Have you tried google's CTemplate library ? It seems to be exactly what you are looking for: http://code.google.com/p/google-ctemplate/

Your example would be implemented like this:

In example.tpl:

My name is {{name}}

In example.cc:

#include <stdlib.h>
#include <string>
#include <iostream>
#include <google/template.h>

int main(int argc, char** argv)
{
  google::TemplateDictionary dict("example");
  dict.SetValue("name", "John Smith");
  google::Template* tpl = google::Template::GetTemplate("example.tpl",
                                                        google::DO_NOT_STRIP);
  std::string output;
  tpl->Expand(&output, &dict);
  std::cout << output;
  return 0;
}

Then:

$ gcc example.cc -lctemplate -pthread

$ ./a.out

My name is John Smith

Note that there is also a way to write templates as const strings if you don't want to bother writting your templates in separate files.

matias
A: 

If you are new to C++, adding new libraries (template or otherwise) to your installation will only increase the learning curve. This is something you can do simply, elegantly, and efficiently with the built-in features.

Unlike similar answers, this code makes only one pass over the input and scales well with large dictionaries:

// header
#include <map>
#include <sstream>

typedef std::map< std::string, std::string > subst_map;

// implementation
using namespace std;

string do_substitutions( string const &in, subst_map const &subst ) {
    ostringstream out;
    size_t pos = 0;
    for (;;) {
        size_t subst_pos = in.find( "{{", pos );
        size_t end_pos = in.find( "}}", subst_pos );
        if ( end_pos == string::npos ) break;

        out.write( &* in.begin() + pos, subst_pos - pos );

        subst_pos += strlen( "{{" );
        subst_map::const_iterator subst_it
            = subst.find( in.substr( subst_pos, end_pos - subst_pos ) );
        if ( subst_it == subst.end() ) throw runtime_error( "undefined substitution" );

        out << subst_it->second;
        pos = end_pos + strlen( "}}" );
    }
    out << in.substr( pos, string::npos );
    return out.str();
}

// usage
pair< string, string > substitutions_init[] = {
    make_pair( "firstname", "homer" ),
    make_pair( "lastname", "simpson" )
};
subst_map substitutions
    ( substitutions_init, substitutions_init + sizeof(substitutions_init)/sizeof(*substitutions_init) );

int main() {
    cerr << do_substitutions( "Mr. {{lastname}}, {{firstname}} esquire", substitutions ) << endl;
}
Potatoswatter