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