48e90a91163f9bcaaeef1af627a539d1c68c5b92
[shibboleth/cpp-xmltooling.git] / xmltooling / util / TemplateEngine.cpp
1 /*
2  *  Copyright 2001-2010 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 TemplateEngine::TemplateEngine()
35 {
36     setTagPrefix("mlp");
37 }
38
39 TemplateEngine::~TemplateEngine()
40 {
41 }
42
43 TemplateEngine::TemplateParameters::TemplateParameters() : m_request(nullptr)
44 {
45 }
46
47 TemplateEngine::TemplateParameters::~TemplateParameters()
48 {
49 }
50
51 const char* TemplateEngine::TemplateParameters::getParameter(const char* name) const
52 {
53     map<string,string>::const_iterator i=m_map.find(name);
54     return (i!=m_map.end() ? i->second.c_str() : (m_request ? m_request->getParameter(name) : nullptr));
55 }
56
57 const multimap<string,string>* TemplateEngine::TemplateParameters::getLoopCollection(const char* name) const
58 {
59     map< string,multimap<string,string> >::const_iterator i=m_collectionMap.find(name);
60     return (i!=m_collectionMap.end() ? &(i->second) : nullptr);
61 }
62
63 void TemplateEngine::setTagPrefix(const char* tagPrefix)
64 {
65     keytag = string("<") + tagPrefix + " ";
66     iftag = string("<") + tagPrefix + "if ";
67     ifnottag = string("<") + tagPrefix + "ifnot ";
68     ifendtag = string("</") + tagPrefix + "if>";
69     ifnotendtag = string("</") + tagPrefix + "ifnot>";
70     fortag = string("<") + tagPrefix + "for ";
71     forendtag = string("</") + tagPrefix + "for>";
72 }
73
74 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
75
76 void TemplateEngine::html_encode(ostream& os, const char* start) const
77 {
78     while (start && *start) {
79         switch (*start) {
80             case '<':   os << "&lt;";       break;
81             case '>':   os << "&gt;";       break;
82             case '"':   os << "&quot;";     break;
83             case '&':   os << "&#38;";      break;
84             case '\'':  os << "&#39;";      break;
85
86             default:
87                 if (unsafe_chars.find_first_of(*start) != string::npos)
88                     os << "&#" << static_cast<short>(*start) << ';';
89                 else
90                     os << *start;
91
92             /*
93             case '#':   os << "&#35;";      break;
94             case '%':   os << "&#37;";      break;
95             case '(':   os << "&#40;";      break;
96             case ')':   os << "&#41;";      break;
97             case ':':   os << "&#58;";      break;
98             case '[':   os << "&#91;";      break;
99             case '\\':  os << "&#92;";      break;
100             case ']':   os << "&#93;";      break;
101             case '`':   os << "&#96;";      break;
102             case '{':   os << "&#123;";     break;
103             case '}':   os << "&#125;";     break;
104             default:    os << *start;
105             */
106         }
107         start++;
108     }
109 }
110
111 void TemplateEngine::trimspace(string& s) const
112 {
113   string::size_type end = s.size() - 1, start = 0;
114
115   // Trim stuff on right.
116   while (end > 0 && !isgraph(s[end])) end--;
117
118   // Trim stuff on left.
119   while (start < end && !isgraph(s[start])) start++;
120
121   // Modify the string.
122   s = s.substr(start, end - start + 1);
123 }
124
125 void TemplateEngine::process(
126     bool visible,
127     const string& buf,
128     const char*& lastpos,
129     ostream& os,
130     const TemplateParameters& parameters,
131     const std::pair<const std::string,std::string>& loopentry,
132     const XMLToolingException* e
133     ) const
134 {
135     const char* line = buf.c_str();
136     const char* thispos;
137
138     while ((thispos = strchr(lastpos, '<')) != nullptr) {
139         // Output the string up to this token.
140         if (visible)
141             os << buf.substr(lastpos-line, thispos-lastpos);
142
143         // Make sure this token matches our tokens.
144 #ifdef HAVE_STRCASECMP
145         if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
146 #else
147         if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
148 #endif
149         {
150             // Save this position off.
151             lastpos = thispos + keytag.length();
152
153             // search for the end-tag
154             if ((thispos = strstr(lastpos, "/>")) != nullptr) {
155                 string key = buf.substr(lastpos-line, thispos-lastpos);
156                 trimspace(key);
157
158                 if (key == "$name" && !loopentry.first.empty())
159                     html_encode(os,loopentry.first.c_str());
160                 else if (key == "$value" && !loopentry.second.empty())
161                     html_encode(os,loopentry.second.c_str());
162                 else {
163                     const char* p = parameters.getParameter(key.c_str());
164                     if (!p && e)
165                         p = e->getProperty(key.c_str());
166                     if (p)
167                         html_encode(os,p);
168                 }
169                 lastpos = thispos + 2; // strlen("/>")
170             }
171         }
172 #ifdef HAVE_STRCASECMP
173         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
174 #else
175         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
176 #endif
177         {
178             // Save this position off.
179             lastpos = thispos + iftag.length();
180
181             // search for the end of this tag
182             if ((thispos = strchr(lastpos, '>')) != nullptr) {
183                 string key = buf.substr(lastpos-line, thispos-lastpos);
184                 trimspace(key);
185                 bool cond=false;
186                 if (visible)
187                     cond = parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str()));
188                 lastpos = thispos + 1; // strlen(">")
189                 process(cond, buf, lastpos, os, parameters, loopentry, e);
190             }
191         }
192 #ifdef HAVE_STRCASECMP
193         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
194 #else
195         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
196 #endif
197         {
198             // Save this position off and pop the stack.
199             lastpos = thispos + ifendtag.length();
200             return;
201         }
202 #ifdef HAVE_STRCASECMP
203         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
204 #else
205         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
206 #endif
207         {
208             // Save this position off.
209             lastpos = thispos + ifnottag.length();
210
211             // search for the end of this tag
212             if ((thispos = strchr(lastpos, '>')) != nullptr) {
213                 string key = buf.substr(lastpos-line, thispos-lastpos);
214                 trimspace(key);
215                 bool cond=visible;
216                 if (visible)
217                     cond = !(parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str())));
218                 lastpos = thispos + 1; // strlen(">")
219                 process(cond, buf, lastpos, os, parameters, loopentry, e);
220             }
221         }
222 #ifdef HAVE_STRCASECMP
223         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
224 #else
225         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
226 #endif
227         {
228             // Save this position off and pop the stack.
229             lastpos = thispos + ifnotendtag.length();
230             return;
231         }
232
233 #ifdef HAVE_STRCASECMP
234         else if (!strncasecmp(thispos, fortag.c_str(), fortag.length()))
235 #else
236         else if (!_strnicmp(thispos, fortag.c_str(), fortag.length()))
237 #endif
238         {
239             // Save this position off.
240             lastpos = thispos + iftag.length();
241             string key;
242             bool cond = visible;
243
244             // search for the end of this tag
245             if ((thispos = strchr(lastpos, '>')) != nullptr) {
246                 key = buf.substr(lastpos-line, thispos-lastpos);
247                 trimspace(key);
248                 lastpos = thispos + 1; // strlen(">")
249             }
250
251             const multimap<string,string>* forParams = parameters.getLoopCollection(key.c_str());
252             if (!forParams || forParams->size() == 0) {
253                 process(false, buf, lastpos, os, parameters, emptyPair, e);
254             }
255             else {
256                 const char* savlastpos = lastpos;
257                 for (multimap<string,string>::const_iterator i=forParams->begin(); i!=forParams->end(); ++i) {
258                     lastpos = savlastpos;
259                     process(cond, buf, lastpos, os, parameters, *i, e);
260                 }
261             }
262         }
263
264 #ifdef HAVE_STRCASECMP
265         else if (!strncasecmp(thispos, forendtag.c_str(), forendtag.length()))
266 #else
267         else if (!_strnicmp(thispos, forendtag.c_str(), forendtag.length()))
268 #endif
269         {
270             // Save this position off and pop the stack.
271             lastpos = thispos + forendtag.length();
272             return;
273         }
274
275         else {
276             // Skip it.
277             if (visible)
278                 os << '<';
279             lastpos = thispos + 1;
280         }
281     }
282     if (visible)
283         os << buf.substr(lastpos-line);
284 }
285
286 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
287 {
288     string buf,line;
289     while (getline(is, line))
290         buf += line + '\n';
291
292     const char* pos=buf.c_str();
293     process(true, buf, pos, os, parameters, emptyPair, e);
294 }