https://issues.shibboleth.net/jira/browse/SSPCPP-185
[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 }
37
38 string TemplateEngine::unsafe_chars = "#%&():[]\\`{}";
39
40 void TemplateEngine::html_encode(ostream& os, const char* start) const
41 {
42     while (start && *start) {
43         switch (*start) {
44             case '<':   os << "&lt;";       break;
45             case '>':   os << "&gt;";       break;
46             case '"':   os << "&quot;";     break;
47             case '&':   os << "&#38;";      break;
48             case '\'':  os << "&#39;";      break;
49
50             default:
51                 if (unsafe_chars.find_first_of(*start) != string::npos)
52                     os << "&#" << static_cast<short>(*start) << ';';
53                 else
54                     os << *start;
55
56             /*
57             case '#':   os << "&#35;";      break;
58             case '%':   os << "&#37;";      break;
59             case '(':   os << "&#40;";      break;
60             case ')':   os << "&#41;";      break;
61             case ':':   os << "&#58;";      break;
62             case '[':   os << "&#91;";      break;
63             case '\\':  os << "&#92;";      break;
64             case ']':   os << "&#93;";      break;
65             case '`':   os << "&#96;";      break;
66             case '{':   os << "&#123;";     break;
67             case '}':   os << "&#125;";     break;
68             default:    os << *start;
69             */
70         }
71         start++;
72     }
73 }
74
75 void TemplateEngine::trimspace(string& s) const
76 {
77   string::size_type end = s.size() - 1, start = 0;
78
79   // Trim stuff on right.
80   while (end > 0 && !isgraph(s[end])) end--;
81
82   // Trim stuff on left.
83   while (start < end && !isgraph(s[start])) start++;
84
85   // Modify the string.
86   s = s.substr(start, end - start + 1);
87 }
88
89 void TemplateEngine::process(
90     bool visible,
91     const string& buf,
92     const char*& lastpos,
93     ostream& os,
94     const TemplateParameters& parameters,
95     const XMLToolingException* e
96     ) const
97 {
98     const char* line = buf.c_str();
99     const char* thispos;
100
101     while ((thispos = strchr(lastpos, '<')) != NULL) {
102         // Output the string up to this token.
103         if (visible)
104             os << buf.substr(lastpos-line, thispos-lastpos);
105
106         // Make sure this token matches our tokens.
107 #ifdef HAVE_STRCASECMP
108         if (visible && !strncasecmp(thispos, keytag.c_str(), keytag.length()))
109 #else
110         if (visible && !_strnicmp(thispos, keytag.c_str(), keytag.length()))
111 #endif
112         {
113             // Save this position off.
114             lastpos = thispos + keytag.length();
115
116             // search for the end-tag
117             if ((thispos = strstr(lastpos, "/>")) != NULL) {
118                 string key = buf.substr(lastpos-line, thispos-lastpos);
119                 trimspace(key);
120
121                 const char* p = parameters.getParameter(key.c_str());
122                 if (!p && e)
123                     p = e->getProperty(key.c_str());
124                 if (p)
125                     html_encode(os,p);
126                 lastpos = thispos + 2; // strlen("/>")
127             }
128         }
129 #ifdef HAVE_STRCASECMP
130         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
131 #else
132         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
133 #endif
134         {
135             // Save this position off.
136             lastpos = thispos + iftag.length();
137
138             // search for the end of this tag
139             if ((thispos = strchr(lastpos, '>')) != NULL) {
140                 string key = buf.substr(lastpos-line, thispos-lastpos);
141                 trimspace(key);
142                 bool cond=false;
143                 if (visible)
144                     cond = parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str()));
145                 lastpos = thispos + 1; // strlen(">")
146                 process(cond, buf, lastpos, os, parameters, e);
147             }
148         }
149 #ifdef HAVE_STRCASECMP
150         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
151 #else
152         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
153 #endif
154         {
155             // Save this position off and pop the stack.
156             lastpos = thispos + ifendtag.length();
157             return;
158         }
159 #ifdef HAVE_STRCASECMP
160         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
161 #else
162         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
163 #endif
164         {
165             // Save this position off.
166             lastpos = thispos + ifnottag.length();
167
168             // search for the end of this tag
169             if ((thispos = strchr(lastpos, '>')) != NULL) {
170                 string key = buf.substr(lastpos-line, thispos-lastpos);
171                 trimspace(key);
172                 bool cond=visible;
173                 if (visible)
174                     cond = !(parameters.getParameter(key.c_str()) || (e && e->getProperty(key.c_str())));
175                 lastpos = thispos + 1; // strlen(">")
176                 process(cond, buf, lastpos, os, parameters, e);
177             }
178         }
179 #ifdef HAVE_STRCASECMP
180         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
181 #else
182         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
183 #endif
184         {
185             // Save this position off and pop the stack.
186             lastpos = thispos + ifnotendtag.length();
187             return;
188         }
189         else {
190             // Skip it.
191             if (visible)
192                 os << '<';
193             lastpos = thispos + 1;
194         }
195     }
196     if (visible)
197         os << buf.substr(lastpos-line);
198 }
199
200 void TemplateEngine::run(istream& is, ostream& os, const TemplateParameters& parameters, const XMLToolingException* e) const
201 {
202     string buf,line;
203     while (getline(is, line))
204         buf += line + '\n';
205
206     const char* pos=buf.c_str();
207     process(true, buf, pos, os, parameters, e);
208 }