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