Template replacement engine ported from Shib, added conditional nesting.
[shibboleth/cpp-xmltooling.git] / xmltooling / util / TemplateEngine.cpp
1 /*
2  *  Copyright 2001-2006 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 #include <ctime>
27
28 using namespace xmltooling;
29 using namespace std;
30
31 void TemplateEngine::setTagPrefix(const char* tagPrefix)
32 {
33     keytag = string("<") + tagPrefix + " ";
34     iftag = string("<") + tagPrefix + "if ";
35     ifnottag = string("<") + tagPrefix + "ifnot ";
36     ifendtag = string("</") + tagPrefix + "if>";
37     ifnotendtag = string("</") + tagPrefix + "ifnot>";
38 }
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 << "&#35;";      break;
48             case '%':   os << "&#37;";      break;
49             case '&':   os << "&#38;";      break;
50             case '\'':  os << "&#39;";      break;
51             case '(':   os << "&#40;";      break;
52             case ')':   os << "&#41;";      break;
53             case ':':   os << "&#58;";      break;
54             case '[':   os << "&#91;";      break;
55             case '\\':  os << "&#92;";      break;
56             case ']':   os << "&#93;";      break;
57             case '`':   os << "&#96;";      break;
58             case '{':   os << "&#123;";     break;
59             case '}':   os << "&#125;";     break;
60             default:    os << *start;
61         }
62         start++;
63     }
64 }
65
66 void TemplateEngine::trimspace(string& s) const
67 {
68   string::size_type end = s.size() - 1, start = 0;
69
70   // Trim stuff on right.
71   while (end > 0 && !isgraph(s[end])) end--;
72
73   // Trim stuff on left.
74   while (start < end && !isgraph(s[start])) start++;
75
76   // Modify the string.
77   s = s.substr(start, end - start + 1);
78 }
79
80 void TemplateEngine::process(
81     bool visible,
82     const string& buf,
83     const char*& lastpos,
84     ostream& os,
85     const map<string,string>& parameters,
86     const XMLToolingException* e
87     ) const
88 {
89     // Create a timestamp
90     time_t now = time(NULL);
91 #ifdef HAVE_CTIME_R
92     char nowbuf[32];
93     ctime_r(&now);
94 #else
95     const char* nowbuf = ctime(&now);
96 #endif
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                 map<string,string>::const_iterator i=parameters.find(key);
122                 if (i != parameters.end()) {
123                     html_encode(os,i->second.c_str());
124                 }
125                 else if (e) {
126                     const char* ep = e->getProperty(key.c_str());
127                     if (ep)
128                         html_encode(os,ep);
129                 }
130                 lastpos = thispos + 2; // strlen("/>")
131             }
132         }
133 #ifdef HAVE_STRCASECMP
134         else if (!strncasecmp(thispos, iftag.c_str(), iftag.length()))
135 #else
136         else if (!_strnicmp(thispos, iftag.c_str(), iftag.length()))
137 #endif
138         {
139             // Save this position off.
140             lastpos = thispos + iftag.length();
141     
142             // search for the end of this tag
143             if ((thispos = strchr(lastpos, '>')) != NULL) {
144                 string key = buf.substr(lastpos-line, thispos-lastpos);
145                 trimspace(key);
146                 map<string,string>::const_iterator i=parameters.find(key);
147                 bool cond=false;
148                 if (visible) {
149                     if (i != parameters.end())
150                         cond=true;
151                     else if (e && e->getProperty(key.c_str()))
152                         cond=true;
153                 }
154                 lastpos = thispos + 1; // strlen(">")
155                 process(cond, buf, lastpos, os, parameters, e);
156             }
157         }
158 #ifdef HAVE_STRCASECMP
159         else if (!strncasecmp(thispos, ifendtag.c_str(), ifendtag.length()))
160 #else
161         else if (!_strnicmp(thispos, ifendtag.c_str(), ifendtag.length()))
162 #endif
163         {
164             // Save this position off and pop the stack.
165             lastpos = thispos + ifendtag.length();
166             return;
167         }
168 #ifdef HAVE_STRCASECMP
169         else if (!strncasecmp(thispos, ifnottag.c_str(), ifnottag.length()))
170 #else
171         else if (!_strnicmp(thispos, ifnottag.c_str(), ifnottag.length()))
172 #endif
173         {
174             // Save this position off.
175             lastpos = thispos + ifnottag.length();
176     
177             // search for the end of this tag
178             if ((thispos = strchr(lastpos, '>')) != NULL) {
179                 string key = buf.substr(lastpos-line, thispos-lastpos);
180                 trimspace(key);
181                 map<string,string>::const_iterator i=parameters.find(key);
182                 bool cond=visible;
183                 if (visible) {
184                     if (i != parameters.end())
185                         cond=false;
186                     else if (e && e->getProperty(key.c_str()))
187                         cond=false;
188                 }
189                 lastpos = thispos + 1; // strlen(">")
190                 process(cond, buf, lastpos, os, parameters, e);
191             }
192         }
193 #ifdef HAVE_STRCASECMP
194         else if (!strncasecmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
195 #else
196         else if (!_strnicmp(thispos, ifnotendtag.c_str(), ifnotendtag.length()))
197 #endif
198         {
199             // Save this position off and pop the stack.
200             lastpos = thispos + ifnotendtag.length();
201             return;
202         }
203         else {
204             // Skip it.
205             if (visible)
206                 os << '<';
207             lastpos = thispos + 1;
208         }
209     }
210     if (visible)
211         os << buf.substr(lastpos-line);
212 }
213
214 void TemplateEngine::run(istream& is, ostream& os, const map<string,string>& parameters, const XMLToolingException* e) const
215 {
216     string buf,line;
217     while (getline(is, line))
218         buf += line + '\n';
219     
220     const char* pos=buf.c_str();
221     process(true, buf, pos, os, parameters, e);
222 }