4062fca7c98c832ebae7c50f5e464411b41ab078
[shibboleth/cpp-xmltooling.git] / xmltooling / util / TemplateEngine.cpp
1 /*
2  *  Copyright 2001-2009 Internet2
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /**
18  * TemplateEngine.cpp
19  *
20  * Simple template replacement engine.
21  */
22
23 #include "internal.h"
24 #include "io/GenericRequest.h"
25 #include "util/TemplateEngine.h"
26
27 using namespace xmltooling;
28 using namespace std;
29
30 namespace {
31     static const pair<const string,string> emptyPair;
32 }
33
34 const char* TemplateEngine::TemplateParameters::getParameter(const char* name) const
35 {
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));
38 }
39
40 const multimap<string,string>* TemplateEngine::TemplateParameters::getLoopCollection(const char* name) const
41 {
42     map< string,multimap<string,string> >::const_iterator i=m_collectionMap.find(name);
43     return (i!=m_collectionMap.end() ? &(i->second) : NULL);
44 }
45
46 void TemplateEngine::setTagPrefix(const char* tagPrefix)
47 {
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>";
55 }
56
57 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
58
59 void TemplateEngine::html_encode(ostream& os, const char* start) const
60 {
61     while (start && *start) {
62         switch (*start) {
63             case '<':   os << "&lt;";       break;
64             case '>':   os << "&gt;";       break;
65             case '"':   os << "&quot;";     break;
66             case '&':   os << "&#38;";      break;
67             case '\'':  os << "&#39;";      break;
68
69             default:
70                 if (unsafe_chars.find_first_of(*start) != string::npos)
71                     os << "&#" << static_cast<short>(*start) << ';';
72                 else
73                     os << *start;
74
75             /*
76             case '#':   os << "&#35;";      break;
77             case '%':   os << "&#37;";      break;
78             case '(':   os << "&#40;";      break;
79             case ')':   os << "&#41;";      break;
80             case ':':   os << "&#58;";      break;
81             case '[':   os << "&#91;";      break;
82             case '\\':  os << "&#92;";      break;
83             case ']':   os << "&#93;";      break;
84             case '`':   os << "&#96;";      break;
85             case '{':   os << "&#123;";     break;
86             case '}':   os << "&#125;";     break;
87             default:    os << *start;
88             */
89         }
90         start++;
91     }
92 }
93
94 void TemplateEngine::trimspace(string& s) const
95 {
96   string::size_type end = s.size() - 1, start = 0;
97
98   // Trim stuff on right.
99   while (end > 0 && !isgraph(s[end])) end--;
100
101   // Trim stuff on left.
102   while (start < end && !isgraph(s[start])) start++;
103
104   // Modify the string.
105   s = s.substr(start, end - start + 1);
106 }
107
108 void TemplateEngine::process(
109     bool visible,
110     const string& buf,
111     const char*& lastpos,
112     ostream& os,
113     const TemplateParameters& parameters,
114     const std::pair<const std::string,std::string>& loopentry,
115     const XMLToolingException* e
116     ) const
117 {
118     const char* line = buf.c_str();
119     const char* thispos;
120
121     while ((thispos = strchr(lastpos, '<')) != NULL) {
122         // Output the string up to this token.
123         if (visible)
124             os << buf.substr(lastpos-line, thispos-lastpos);
125
126         // Make sure this token matches our tokens.
127 #ifdef HAVE_STRCASECMP
128         if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
129 #else
130         if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
131 #endif
132         {
133             // Save this position off.
134             lastpos = thispos + keytag.length();
135
136             // search for the end-tag
137             if ((thispos = strstr(lastpos, "/>")) != NULL) {
138                 string key = buf.substr(lastpos-line, thispos-lastpos);
139                 trimspace(key);
140
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());
145                 else {
146                     const char* p = parameters.getParameter(key.c_str());
147                     if (!p && e)
148                         p = e->getProperty(key.c_str());
149                     if (p)
150                         html_encode(os,p);
151                 }
152                 lastpos = thispos + 2; // strlen("/>")
153             }
154         }
155 #ifdef HAVE_STRCASECMP
156         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
157 #else
158         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
159 #endif
160         {
161             // Save this position off.
162             lastpos = thispos + iftag.length();
163
164             // search for the end of this tag
165             if ((thispos = strchr(lastpos, '>')) != NULL) {
166                 string key = buf.substr(lastpos-line, thispos-lastpos);
167                 trimspace(key);
168                 bool cond=false;
169                 if (visible)
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);
173             }
174         }
175 #ifdef HAVE_STRCASECMP
176         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
177 #else
178         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
179 #endif
180         {
181             // Save this position off and pop the stack.
182             lastpos = thispos + ifendtag.length();
183             return;
184         }
185 #ifdef HAVE_STRCASECMP
186         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
187 #else
188         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
189 #endif
190         {
191             // Save this position off.
192             lastpos = thispos + ifnottag.length();
193
194             // search for the end of this tag
195             if ((thispos = strchr(lastpos, '>')) != NULL) {
196                 string key = buf.substr(lastpos-line, thispos-lastpos);
197                 trimspace(key);
198                 bool cond=visible;
199                 if (visible)
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);
203             }
204         }
205 #ifdef HAVE_STRCASECMP
206         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
207 #else
208         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
209 #endif
210         {
211             // Save this position off and pop the stack.
212             lastpos = thispos + ifnotendtag.length();
213             return;
214         }
215
216 #ifdef HAVE_STRCASECMP
217         else if (!strncasecmp(thispos, fortag.c_str(), fortag.length()))
218 #else
219         else if (!_strnicmp(thispos, fortag.c_str(), fortag.length()))
220 #endif
221         {
222             // Save this position off.
223             lastpos = thispos + iftag.length();
224             string key;
225             bool cond = visible;
226
227             // search for the end of this tag
228             if ((thispos = strchr(lastpos, '>')) != NULL) {
229                 key = buf.substr(lastpos-line, thispos-lastpos);
230                 trimspace(key);
231                 lastpos = thispos + 1; // strlen(">")
232             }
233
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);
237             }
238             else {
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);
243                 }
244             }
245         }
246
247 #ifdef HAVE_STRCASECMP
248         else if (!strncasecmp(thispos, forendtag.c_str(), forendtag.length()))
249 #else
250         else if (!_strnicmp(thispos, forendtag.c_str(), forendtag.length()))
251 #endif
252         {
253             // Save this position off and pop the stack.
254             lastpos = thispos + forendtag.length();
255             return;
256         }
257
258         else {
259             // Skip it.
260             if (visible)
261                 os << '<';
262             lastpos = thispos + 1;
263         }
264     }
265     if (visible)
266         os << buf.substr(lastpos-line);
267 }
268
269 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
270 {
271     string buf,line;
272     while (getline(is, line))
273         buf += line + '\n';
274
275     const char* pos=buf.c_str();
276     process(true, buf, pos, os, parameters, emptyPair, e);
277 }