2 * Copyright 2001-2009 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Simple template replacement engine.
24 #include "io/GenericRequest.h"
25 #include "util/TemplateEngine.h"
27 using namespace xmltooling;
31 static const pair<const string,string> emptyPair;
34 const char* TemplateEngine::TemplateParameters::getParameter(const char* name) const
36 map<string,string>::const_iterator i=m_map.find(name);
37 return (i!=m_map.end() ? i->second.c_str() : (m_request ? m_request->getParameter(name) : NULL));
40 const multimap<string,string>* TemplateEngine::TemplateParameters::getLoopCollection(const char* name) const
42 map< string,multimap<string,string> >::const_iterator i=m_collectionMap.find(name);
43 return (i!=m_collectionMap.end() ? &(i->second) : NULL);
46 void TemplateEngine::setTagPrefix(const char* tagPrefix)
48 keytag = string("<") + tagPrefix + " ";
49 iftag = string("<") + tagPrefix + "if ";
50 ifnottag = string("<") + tagPrefix + "ifnot ";
51 ifendtag = string("</") + tagPrefix + "if>";
52 ifnotendtag = string("</") + tagPrefix + "ifnot>";
53 fortag = string("<") + tagPrefix + "for ";
54 forendtag = string("</") + tagPrefix + "for>";
57 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
59 void TemplateEngine::html_encode(ostream& os, const char* start) const
61 while (start && *start) {
63 case '<': os << "<"; break;
64 case '>': os << ">"; break;
65 case '"': os << """; break;
66 case '&': os << "&"; break;
67 case '\'': os << "'"; break;
70 if (unsafe_chars.find_first_of(*start) != string::npos)
71 os << "&#" << static_cast<short>(*start) << ';';
76 case '#': os << "#"; break;
77 case '%': os << "%"; break;
78 case '(': os << "("; break;
79 case ')': os << ")"; break;
80 case ':': os << ":"; break;
81 case '[': os << "["; break;
82 case '\\': os << "\"; break;
83 case ']': os << "]"; break;
84 case '`': os << "`"; break;
85 case '{': os << "{"; break;
86 case '}': os << "}"; break;
87 default: os << *start;
94 void TemplateEngine::trimspace(string& s) const
96 string::size_type end = s.size() - 1, start = 0;
98 // Trim stuff on right.
99 while (end > 0 && !isgraph(s[end])) end--;
101 // Trim stuff on left.
102 while (start < end && !isgraph(s[start])) start++;
104 // Modify the string.
105 s = s.substr(start, end - start + 1);
108 void TemplateEngine::process(
111 const char*& lastpos,
113 const TemplateParameters& parameters,
114 const std::pair<const std::string,std::string>& loopentry,
115 const XMLToolingException* e
118 const char* line = buf.c_str();
121 while ((thispos = strchr(lastpos, '<')) != NULL) {
122 // Output the string up to this token.
124 os << buf.substr(lastpos-line, thispos-lastpos);
126 // Make sure this token matches our tokens.
127 #ifdef HAVE_STRCASECMP
128 if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
130 if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
133 // Save this position off.
134 lastpos = thispos + keytag.length();
136 // search for the end-tag
137 if ((thispos = strstr(lastpos, "/>")) != NULL) {
138 string key = buf.substr(lastpos-line, thispos-lastpos);
141 if (key == "$name" && !loopentry.first.empty())
142 html_encode(os,loopentry.first.c_str());
143 else if (key == "$value" && !loopentry.second.empty())
144 html_encode(os,loopentry.second.c_str());
146 const char* p = parameters.getParameter(key.c_str());
148 p = e->getProperty(key.c_str());
152 lastpos = thispos + 2; // strlen("/>")
155 #ifdef HAVE_STRCASECMP
156 else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
158 else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
161 // Save this position off.
162 lastpos = thispos + iftag.length();
164 // search for the end of this tag
165 if ((thispos = strchr(lastpos, '>')) != NULL) {
166 string key = buf.substr(lastpos-line, thispos-lastpos);
170 cond = parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str()));
171 lastpos = thispos + 1; // strlen(">")
172 process(cond, buf, lastpos, os, parameters, loopentry, e);
175 #ifdef HAVE_STRCASECMP
176 else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
178 else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
181 // Save this position off and pop the stack.
182 lastpos = thispos + ifendtag.length();
185 #ifdef HAVE_STRCASECMP
186 else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
188 else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
191 // Save this position off.
192 lastpos = thispos + ifnottag.length();
194 // search for the end of this tag
195 if ((thispos = strchr(lastpos, '>')) != NULL) {
196 string key = buf.substr(lastpos-line, thispos-lastpos);
200 cond = !(parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str())));
201 lastpos = thispos + 1; // strlen(">")
202 process(cond, buf, lastpos, os, parameters, loopentry, e);
205 #ifdef HAVE_STRCASECMP
206 else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
208 else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
211 // Save this position off and pop the stack.
212 lastpos = thispos + ifnotendtag.length();
216 #ifdef HAVE_STRCASECMP
217 else if (!strncasecmp(thispos, fortag.c_str(), fortag.length()))
219 else if (!_strnicmp(thispos, fortag.c_str(), fortag.length()))
222 // Save this position off.
223 lastpos = thispos + iftag.length();
227 // search for the end of this tag
228 if ((thispos = strchr(lastpos, '>')) != NULL) {
229 key = buf.substr(lastpos-line, thispos-lastpos);
231 lastpos = thispos + 1; // strlen(">")
234 const multimap<string,string>* forParams = parameters.getLoopCollection(key.c_str());
235 if (!forParams || forParams->size() == 0) {
236 process(false, buf, lastpos, os, parameters, emptyPair, e);
239 const char* savlastpos = lastpos;
240 for (multimap<string,string>::const_iterator i=forParams->begin(); i!=forParams->end(); ++i) {
241 lastpos = savlastpos;
242 process(cond, buf, lastpos, os, parameters, *i, e);
247 #ifdef HAVE_STRCASECMP
248 else if (!strncasecmp(thispos, forendtag.c_str(), forendtag.length()))
250 else if (!_strnicmp(thispos, forendtag.c_str(), forendtag.length()))
253 // Save this position off and pop the stack.
254 lastpos = thispos + forendtag.length();
262 lastpos = thispos + 1;
266 os << buf.substr(lastpos-line);
269 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
272 while (getline(is, line))
275 const char* pos=buf.c_str();
276 process(true, buf, pos, os, parameters, emptyPair, e);