One reason this example is so simple is that it doesn't even
require a separate template file, but instead uses
StringToTemplateCache()
. It also doesn't use sections or
template-includes.
int main() { static const char template_text[] = "ERROR: {{FUNCTION}}({{ARGS}}) returned {{ERROR_CODE}}: {{ERROR_MESSAGE}}\n"; ctemplate::StringToTemplateCache("mytpl", template_text, ctemplate::DO_NOT_STRIP); FILE* fp = fopen(argv[1], "r"); if (fp == NULL) { int err_no = errno; // squirrel this away ctemplate::TemplateDictionary dict("error_msg: fopen()"); dict.SetValue("FUNCTION", "fopen"); dict.SetValue("ARGS", argv[1]); dict.SetIntValue("ERROR_CODE", err_no); dict.SetValue("ERROR_MESSAGE", strerror(err_no)); string error_text; ctemplate::ExpandTemplate("mytpl", ctemplate::DO_NOT_STRIP, &dict, &error_text); puts(error_text.c_str()); } }Note: If this template was intended to run in a web application, you can leverage the functionality provided by the auto-escape mode. Simply add the AUTOESCAPE pragma directive at the top of the template text and your variables will be automatically escaped for the context you specify. For example, if your template is returned in an HTML context, change the
template_text
declaration as follows:
static const char template_text[] = "{{%AUTOESCAPE context=\"HTML\"}}" "ERROR: {{FUNCTION}}({{ARGS}}) returned {{ERROR_CODE}}: {{ERROR_MESSAGE}}\n";
This example is only slightly more complicated: we only print the ": <error message>" part when the error message isn't the empty string.
int main() { static const char template_text[] = "ERROR: {{FUNCTION}}({{ARGS}}) returned {{ERROR_CODE}}" "{{#MSG_SECTION}}: {{ERROR_MESSAGE}}{{/MSG_SECTION}}\n"; ctemplate::StringToTemplateCache("mytpl", template_text, ctemplate::DO_NOT_STRIP); FILE* fp = fopen(argv[1], "r"); if (fp == NULL) { int err_no = errno; // squirrel this away ctemplate::TemplateDictionary dict("file_error_message"); dict.SetValue("FUNCTION", "fopen"); dict.SetValue("ARGS", argv[1]); dict.SetIntValue("ERROR_CODE", err_no); if (err_no > 0) dict.SetValueAndShowSection("ERROR_MESSAGE", strerror(err_no), "MSG_SECTION"); string error_text; ctemplate::ExpandTemplate("mytpl", ctemplate::DO_NOT_STRIP, &dict, &error_text); puts(error_text.c_str()); } delete tpl; }
This maybe-show-text functionality is one way the template
machinery is more powerful than just using printf
.
Another nice property of templates is you can reuse the same variable
multiple times in your template string. You can also define the
variable values in any order.
Here is an example template that could be used to format a Google search results page:
{{>HEADER}} <body bgcolor=white> {{>PAGE_HEADING}}{{!The following div must be on the same line}}<div> {{!The ONE_RESULT section displays a single search item}} {{#ONE_RESULT}} {{! Note: there are two SUBITEM_SECTIONs. They both show or hide together}} {{#SUBITEM_SECTION}}<blockquote>{{/SUBITEM_SECTION}} {{! LEAD_LINE is received HTML-escaped from the backend.}} <p><a href="{{JUMP_TO_URL:html_escape}}" target=nw>{{LEAD_LINE}}</a><font size=-1> {{! SNIPPET1, SNIPPET2 are HTML-escaped in the snippet generator.}} {{#SNIPPET1_SECTION}} <br>{{SNIPPET1}} {{/SNIPPET1_SECTION}} {{#SNIPPET2_SECTION}} <br>{{SNIPPET2}} {{/SNIPPET2_SECTION}} {{#DESCRIPTION_SECTION}} {{! DESC is received HTML-escaped from the backend.}} <br><span class=f>Description:</span> {{DESC}} {{/DESCRIPTION_SECTION}} {{#CATEGORY_SECTION}} <br><span class=f>Category:</span> <a href="{{CAT_URL:html_escape}}" class=f> {{CATEGORY:html_escape}}</a> {{/CATEGORY_SECTION}} {{#LASTLINE_SECTION}} <br><font color="{{ALT_TEXT_COLOR:h}}">{{URL:h}} {{#KS_SECTION}}} - {{KSIZE:h}}{{/KS_SECTION}}} {{#CACHE_SECTION}}} - <a href="{{CACHE_URL:h}}" class=f>Cached</A> {{/CACHE_SECTION}}} {{#SIM_SECTION}}} - <a href="{{SIM_PAGES_URL:h}}" class=f>Similar pages</A> {{/SIM_SECTION}}} {{#STOCK_SECTION}} - <a href="{{STOCK_URL:h}}" class=f>Stock quotes: {{STOCK_SYMBOL:h}}</a> {{/STOCK_SECTION}} </font> {{/LASTLINE_SECTION}} {{#MORE_SECTION}} <br>[ <a href="{{MORE_URL:h}}" class=f>More results from {{MORE_LABEL:h}}</a> ] {{/MORE_SECTION}} </font><br> {{! Note: there are two SUBITEM_SECTIONs. They both show or hide together}} {{#SUBITEM_SECTION}}</blockquote>{{/SUBITEM_SECTION}} {{/ONE_RESULT}} </div> {{! this /div closes the div at the top of this file}} {{>PAGE_FOOTING}}
Here is a sample procedure that could populate a dictionary for
expanding that template. The "one procedure" entry point is
fill_search_results_dictionary
. The
SetTemplateValues
function is a separate entry point for
initializing each top-level template with some standard values.
#include "template.h" RegisterTemplateFilename(SEARCH_RESULTS_FN, "search_results.tpl"); #include "search_results.tpl.varnames.h" // defines ksr_HEADER, etc. using ctemplate::TemplateDictionary; using ctemplate::ExpandTemplate; using ctemplate::STRIP_WHITESPACE; // IsEmpty // A simple utility function static bool IsEmpty(const string &str) { return str.empty(); } // SetTemplateValues // Use the TemplateDictionary object to set template-wide values that // may be used in the top-level template and all its sub-sections // and included templates. The template-wide values are all // colors from the Palette object void SetTemplateValues(TemplateDictionary *dictionary, const Palette* colors) { // better would be to use ksr_LINK_COLOR, etc, assuming those are // defined in search_results.tpl.varnames.h. But using literal // text, as here, is legal as well. dictionary->SetValue("LINK_COLOR", colors->link_color); dictionary->SetValue("BAR_TEXT_COLOR", colors->bar_text_color); dictionary->SetValue("TEXT_COLOR", colors->text_color); dictionary->SetValue("FAINT_COLOR", colors->faint_color); dictionary->SetValue("IMPORTANT_COLOR", colors->important_color); dictionary->SetValue("BAR_COLOR", colors->bar_color); dictionary->SetValue("ALT_TEXT_COLOR", colors->alt_text_color); dictionary->SetValue("ALINK_COLOR", colors->alink_color); dictionary->SetValue("VLINK_COLOR", colors->vlink_color); } // fill_search_results_dictionary // Iterates through all the QueryResults contained in the Query object. // For each one, it sets corresponding template dictionary values // (or hides sections containing their variables, if appropriate) in // a sub-dictionary and then adds that dictionary to the parent void fill_search_results_dictionary(TemplateDictionary *dictionary, const Query *query) { dictionary->SetFilename(SEARCH_RESULTS_FN); // These two functions are defined elsewhere fill_header_dictionary(dictionary->AddIncludeDictionary(ksr_HEADER)); fill_page_heading_dictionary(dictionary->AddIncludeDictionary(ksr_PAGE_HEADING), query); ResultsList *results = query->GetResults(); int resCount = 0; for (ResultsList::const_iterator iter = results->begin(); iter != results->end(); ++iter) { QueryResult *qr = (*iter); // Create a new sub-dictionary named "Result Dict <n>" for this entry ++resCount; TemplateDictionary *result_dictionary = dictionary->AddSectionDictionary(ksr_ONE_RESULT); result_dictionary->SetValue(ksr_JUMP_TO_URL, qr->GetUrl()); if (qr->IsSubItem()) { result_dictionary->ShowSection(ksr_SUBITEM_SECTION); } result_dictionary->SetValue(ksr_LEAD_LINE, qr->GetLeadLine()); result_dictionary->SetValueAndShowSection(ksr_SNIPPET1, qr->GetSnippet1(), ksr_SNIPPET1_SECTION); result_dictionary->SetValueAndShowSection(ksr_SNIPPET2, qr->GetSnippet2(), ksr_SNIPPET2_SECTION); result_dictionary->SetValueAndShowSection(ksr_DESC, qr->GetDescription(), ksr_DESCRIPTION_SECTION); result_dictionary->SetValueAndShowSection(ksr_CAT_URL, qr->GetCategoryUrl(), ksr_CATEGORY_SECTION); result_dictionary->SetValueAndShowSection("CATEGORY", qr->GetCategoryName(), "CATEGORY_SECTION"); if (IsEmpty(qr->GetDisplayUrl()) && IsEmpty(qr->GetPageSize()) && IsEmpty(qr->GetCachedUrl()) && IsEmpty(qr->GetSimilarPagesUrl()) && (IsEmpty(qr->GetStockUrl()) || IsEmpty(qr->GetStockSymbol())) ) { // there is nothing on the last line, so hide it altogether } else { result_dictionary->ShowSection("LASTLINE_SECTION"); result_dictionary->SetValue(ksr_URL, qr->GetDisplayUrl()); result_dictionary->SetValueAndShowSection(ksr_KSIZE, qr->GetPageSize(), ksr_KS_SECTION); result_dictionary->SetValueAndShowSection(ksr_CACHE_URL, qr->GetCachedUrl(), ksr_CACHE_SECTION); result_dictionary->SetValueAndShowSection(ksr_SIM_PAGES_URL, qr->GetSimilarPagesUrl(), ksr_SIM_SECTION); result_dictionary->SetValueAndShowSection(ksr_STOCK_URL, qr->GetStockUrl(), ksr_STOCK_SECTION); result_dictionary->SetValueAndShowSection(ksr_STOCK_SYMBOL, qr->GetStockSymbol(), ksr_STOCK_SECTION); } result_dictionary->SetValueAndShowSection(ksr_MORE_URL, qr->GetMoreUrl(), ksr_MORE_SECTION); result_dictionary->SetValueAndShowSection(ksr_MORE_LABEL, qr->GetMoreLabel(), ksr_MORE_SECTION); } fill_page_footing_dictionary(dictionary->AddIncludeDictionary(ksr_PAGE_FOOTING), query); } void output_page(const Query* query) { TemplateDictionary dict("search-results dict"); string output; fill_search_results_dictionary(&dict, query); ctemplate::ExpandTemplate(SEARCH_RESULTS_FN, STRIP_WHITESPACE, &dict, &output); // output now holds the expanded template }