More boostisms, add string tokenizer
[shibboleth/cpp-xmltooling.git] / xmltooling / util / TemplateEngine.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * TemplateEngine.cpp
23  *
24  * Simple template replacement engine.
25  */
26
27 #include "internal.h"
28 #include "io/GenericRequest.h"
29 #include "util/TemplateEngine.h"
30
31 #include <boost/algorithm/string/trim.hpp>
32
33 using namespace xmltooling;
34 using namespace std;
35 using boost::trim;
36
37 namespace {
38     static const pair<const string,string> emptyPair;
39 }
40
41 TemplateEngine::TemplateEngine()
42 {
43     setTagPrefix("mlp");
44 }
45
46 TemplateEngine::~TemplateEngine()
47 {
48 }
49
50 TemplateEngine::TemplateParameters::TemplateParameters() : m_request(nullptr)
51 {
52 }
53
54 TemplateEngine::TemplateParameters::~TemplateParameters()
55 {
56 }
57
58 const char* TemplateEngine::TemplateParameters::getParameter(const char* name) const
59 {
60     map<string,string>::const_iterator i=m_map.find(name);
61     return (i!=m_map.end() ? i->second.c_str() : (m_request ? m_request->getParameter(name) : nullptr));
62 }
63
64 const multimap<string,string>* TemplateEngine::TemplateParameters::getLoopCollection(const char* name) const
65 {
66     map< string,multimap<string,string> >::const_iterator i=m_collectionMap.find(name);
67     return (i!=m_collectionMap.end() ? &(i->second) : nullptr);
68 }
69
70 void TemplateEngine::setTagPrefix(const char* tagPrefix)
71 {
72     keytag = string("<") + tagPrefix + " ";
73     iftag = string("<") + tagPrefix + "if ";
74     ifnottag = string("<") + tagPrefix + "ifnot ";
75     ifendtag = string("</") + tagPrefix + "if>";
76     ifnotendtag = string("</") + tagPrefix + "ifnot>";
77     fortag = string("<") + tagPrefix + "for ";
78     forendtag = string("</") + tagPrefix + "for>";
79 }
80
81 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
82
83 void TemplateEngine::html_encode(ostream& os, const char* start) const
84 {
85     while (start && *start) {
86         switch (*start) {
87             case '<':   os << "&lt;";       break;
88             case '>':   os << "&gt;";       break;
89             case '"':   os << "&quot;";     break;
90             case '&':   os << "&#38;";      break;
91             case '\'':  os << "&#39;";      break;
92
93             default:
94                 if (unsafe_chars.find_first_of(*start) != string::npos)
95                     os << "&#" << static_cast<short>(*start) << ';';
96                 else
97                     os << *start;
98
99             /*
100             case '#':   os << "&#35;";      break;
101             case '%':   os << "&#37;";      break;
102             case '(':   os << "&#40;";      break;
103             case ')':   os << "&#41;";      break;
104             case ':':   os << "&#58;";      break;
105             case '[':   os << "&#91;";      break;
106             case '\\':  os << "&#92;";      break;
107             case ']':   os << "&#93;";      break;
108             case '`':   os << "&#96;";      break;
109             case '{':   os << "&#123;";     break;
110             case '}':   os << "&#125;";     break;
111             default:    os << *start;
112             */
113         }
114         start++;
115     }
116 }
117
118 void TemplateEngine::process(
119     bool visible,
120     const string& buf,
121     const char*& lastpos,
122     ostream& os,
123     const TemplateParameters& parameters,
124     const pair<const string,string>& loopentry,
125     const XMLToolingException* e
126     ) const
127 {
128     const char* line = buf.c_str();
129     const char* thispos;
130
131     while ((thispos = strchr(lastpos, '<')) != nullptr) {
132         // Output the string up to this token.
133         if (visible)
134             os << buf.substr(lastpos-line, thispos-lastpos);
135
136         // Make sure this token matches our tokens.
137 #ifdef HAVE_STRCASECMP
138         if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
139 #else
140         if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
141 #endif
142         {
143             // Save this position off.
144             lastpos = thispos + keytag.length();
145
146             // search for the end-tag
147             if ((thispos = strstr(lastpos, "/>")) != nullptr) {
148                 string key = buf.substr(lastpos-line, thispos-lastpos);
149                 trim(key);
150
151                 if (key == "$name" && !loopentry.first.empty())
152                     html_encode(os,loopentry.first.c_str());
153                 else if (key == "$value" && !loopentry.second.empty())
154                     html_encode(os,loopentry.second.c_str());
155                 else {
156                     const char* p = parameters.getParameter(key.c_str());
157                     if (!p && e)
158                         p = e->getProperty(key.c_str());
159                     if (p)
160                         html_encode(os,p);
161                 }
162                 lastpos = thispos + 2; // strlen("/>")
163             }
164         }
165 #ifdef HAVE_STRCASECMP
166         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
167 #else
168         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
169 #endif
170         {
171             // Save this position off.
172             lastpos = thispos + iftag.length();
173
174             // search for the end of this tag
175             if ((thispos = strchr(lastpos, '>')) != nullptr) {
176                 string key = buf.substr(lastpos-line, thispos-lastpos);
177                 trim(key);
178                 bool cond=false;
179                 if (visible)
180                     cond = parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str()));
181                 lastpos = thispos + 1; // strlen(">")
182                 process(cond, buf, lastpos, os, parameters, loopentry, e);
183             }
184         }
185 #ifdef HAVE_STRCASECMP
186         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
187 #else
188         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
189 #endif
190         {
191             // Save this position off and pop the stack.
192             lastpos = thispos + ifendtag.length();
193             return;
194         }
195 #ifdef HAVE_STRCASECMP
196         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
197 #else
198         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
199 #endif
200         {
201             // Save this position off.
202             lastpos = thispos + ifnottag.length();
203
204             // search for the end of this tag
205             if ((thispos = strchr(lastpos, '>')) != nullptr) {
206                 string key = buf.substr(lastpos-line, thispos-lastpos);
207                 trim(key);
208                 bool cond=visible;
209                 if (visible)
210                     cond = !(parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str())));
211                 lastpos = thispos + 1; // strlen(">")
212                 process(cond, buf, lastpos, os, parameters, loopentry, e);
213             }
214         }
215 #ifdef HAVE_STRCASECMP
216         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
217 #else
218         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
219 #endif
220         {
221             // Save this position off and pop the stack.
222             lastpos = thispos + ifnotendtag.length();
223             return;
224         }
225
226 #ifdef HAVE_STRCASECMP
227         else if (!strncasecmp(thispos, fortag.c_str(), fortag.length()))
228 #else
229         else if (!_strnicmp(thispos, fortag.c_str(), fortag.length()))
230 #endif
231         {
232             // Save this position off.
233             lastpos = thispos + iftag.length();
234             string key;
235             bool cond = visible;
236
237             // search for the end of this tag
238             if ((thispos = strchr(lastpos, '>')) != nullptr) {
239                 key = buf.substr(lastpos-line, thispos-lastpos);
240                 trim(key);
241                 lastpos = thispos + 1; // strlen(">")
242             }
243
244             const multimap<string,string>* forParams = parameters.getLoopCollection(key.c_str());
245             if (!forParams || forParams->size() == 0) {
246                 process(false, buf, lastpos, os, parameters, emptyPair, e);
247             }
248             else {
249                 const char* savlastpos = lastpos;
250                 for (multimap<string,string>::const_iterator i=forParams->begin(); i!=forParams->end(); ++i) {
251                     lastpos = savlastpos;
252                     process(cond, buf, lastpos, os, parameters, *i, e);
253                 }
254             }
255         }
256
257 #ifdef HAVE_STRCASECMP
258         else if (!strncasecmp(thispos, forendtag.c_str(), forendtag.length()))
259 #else
260         else if (!_strnicmp(thispos, forendtag.c_str(), forendtag.length()))
261 #endif
262         {
263             // Save this position off and pop the stack.
264             lastpos = thispos + forendtag.length();
265             return;
266         }
267
268         else {
269             // Skip it.
270             if (visible)
271                 os << '<';
272             lastpos = thispos + 1;
273         }
274     }
275     if (visible)
276         os << buf.substr(lastpos-line);
277 }
278
279 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
280 {
281     string buf,line;
282     while (getline(is, line))
283         buf += line + '\n';
284
285     const char* pos=buf.c_str();
286     process(true, buf, pos, os, parameters, emptyPair, e);
287 }