-
Notifications
You must be signed in to change notification settings - Fork 2
/
page.cpp
371 lines (304 loc) · 15.7 KB
/
page.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/****************************************************************************************
* (C) Copyright 2009-2020
* Jessica Mulein <jessica@mulein.com>
*
* Others will be credited if more developers join.
*
* License
*
* This code is licensed under the GPLv3.
* Please see COPYING in the root of this package for details.
*
* The following libraries are only linked in, and no code is based directly from them:
* htmlcxx is under the Apache 2.0 License
* CGICC is under GPLv3
* Boost is under the Boost license
****************************************************************************************
* Page Class
* ------------------
*
* All controls on a page must register with this.
* Handles parsing and rendering of control classes
* All objects on a page must register with this one
*
* Most control class derivatives expect to find their data in an html node
* supplied by a Page class's parsing operation.
***************************************************************************************/
#include <iostream>
#include <fstream>
#include <gridiron/base_classes/page.hpp>
#include <gridiron/base_classes/gridiron.hpp>
#include <gridiron/base_classes/exceptions.hpp>
// TEMP
#include <typeinfo>
using namespace GridIron;
// Give a referenceable handle to the namespace string
const std::string Page::_namespace = GRIDIRON_XHTML_NS;
Page::Page(const char *frontpage) : Control(frontpage, NULL) {
char *buffer = NULL;
int filesize = 0;
int bytesread = 0;
int i = 0;
// save name for access
if (frontpage != NULL) _htmlfile = std::string(frontpage);
else _htmlfile = std::string("");
_id = std::string("_Page_").append(_htmlfile); // default id = "_Page_" or "_Page_foobar.html"
_enableviewstate = false; // whether to output the viewstate
_autonomous = Page::AllowAutonomous(); // not applicable, page classes cannot be autonomous
_classtype = std::string(Page::ClassType()); // make our class type available in the base class
if (frontpage == NULL) {
// if no front-page is given, the caller will have to render everything themselves
// this is fine, but we're done here.
return;
}
// open the front-page
const std::string fullPagePath = GridIron::pathToPage(frontpage);
_htmlfilepath = fullPagePath;
std::ifstream file(fullPagePath, std::ios_base::in);
if (!file.is_open()) throw GridException(101, std::string("unable to open front-end page: ").append(
fullPagePath).c_str());
// base (control) class does not attempt to parse at all
// get length
file.seekg(0, std::ios_base::end);
filesize = file.tellg();
file.seekg(0, std::ios_base::beg);
if (filesize == 0) {
file.close();
throw GridException(103, "front-end file is empty");
}
// allocate mem
buffer = new char[filesize];
while (bytesread < filesize) {
file.read(buffer + bytesread, filesize - bytesread);
if (file.gcount() == 0) break;
bytesread += file.gcount();
}
// close the file
file.close();
// copy buffer to our store
_data.insert(0, buffer, filesize);
// free buffer
delete[] buffer;
buffer = NULL;
if (bytesread < filesize) {
throw GridException(104, "unable to read front-end page");
}
// add default registered variables
_regvars["__frontpage"] = &_htmlfilepath;
_regvars["__frontpage_file"] = &_htmlfile;
// we sort of have a problem here. _namespace is constant. We don't want it to change
// but we can't make the right hand side of the map constant
// arguably _htmlfile should be a constant too.
// do we need to change the type of the right hand side to handle constant and non constant
// types with some sort of wrapper class?
//_regvars["__namespace"] = &_namespace;
this->Page::parse(); // TODO: call parse up the control chain
}
Page::~Page() {
}
// TODO: all the std::cerr's are either debug printing or need to be converted to throws
//
// This function uses htmlcxx to parse the html front page into a node tree
//
// Parsing happens in two passes- one automatically at instantiation of the page that searches for autos
// and the second when render is called.
// if the tree object has already been filled in, we know this is the second (or later) pass
void
Page::parse() {
int controlcount = 0; // how many custom controls we find
bool firstpass = (_tree.size() == 0); // have we already parsed this page?
// _data contains the entire .html file, if given
if (_data.size() == 0) throw GridException(105, "parse called when front-end page not given or empty");
// now get the htmlcxx parse tree of the page if we need it
if (firstpass) {
htmlcxx::HTML::ParserDom parser;
parser.parse(_data);
_tree = parser.getTree();
}
std::cerr << std::endl << (firstpass ? "First " : "Second ") << "parsing pass starting" << std::endl;
// now go through the tags on the page looking only for gridiron auto tags at instantiation
// if not firstpass, ignore autos and look for regular tags, then search instantiated controls for one with the correct id
tree<htmlnode>::iterator it = _tree.begin();
tree<htmlnode>::iterator end = _tree.end();
while (it != end) {
// tags we're interested in are <gridiron::* id="foo"></gridiron::*>
// a quirk of htmlcxx is helpful to us, tagName only has up to the ::'s
// if the current element is a tag (not a comment, etc)
// and it's one we're supposed to interpret:
if (it->isTag() && (it->tagName() == Page::_namespace)) {
// get the full tag string
std::string tagData = it->text();
// parse out the "MyType" in gridiron::MyType or gi::MyType (based on XHTML NS
std::string tagType = gridiron_get_type(tagData);
if (tagType.empty()) {
std::cerr << "ERROR: Control tag is missing control type" << std::endl;
} else {
// use the htmlcxx parsing routine to get all of the attributes and values
it->parseAttributes();
// the first part of the result pair indicates whether the attribute was found
// the second has the actual id, if present
std::pair<bool, std::string> idresult = it->attribute("id");
if (!idresult.first) {
std::cerr << "ERROR: Control tag is missing id" << std::endl;
} else {
// same story here, bool will be true if tag contained auto
std::pair<bool, std::string> autoresult = it->attribute("auto");
bool isauto = (autoresult.first && (autoresult.second == "true"));
// look for any controls on the page with specified ID
Control *instance = Find(idresult.second);
// if we found an auto tag and it's the first pass, and the id was already registered (earlier in the while loop, by another tag)
if ((instance != NULL) && isauto && firstpass) {
std::cerr << "found auto tag (" << tagData
<< ") and the specified ID was already in use by a control of type "
<< instance->Type() << std::endl;
// if we found a standard tag, the id was registered (as it should be, by the client code) and it's not the first pass
} else if ((instance != NULL) && !isauto && !firstpass) {
std::cerr << "found tag (" << tagData << ") and instance with type " << instance->Type()
<< ", id=" << idresult.second << std::endl;
// make sure the instance with that ID is the same type as the control tag
if (!(instance->Is(tagType))) {
std::cerr << "ERROR: instance with that id is not a " << tagType << std::endl;
} else {
// if it's the right type and id, but it already has an html node associated, we've already seen this tag in the file- duplicate
if (instance->HTMLNodeRegistered()) {
std::cerr << "Control instance already bound to another tag." << std::endl;
// otherwise, we've found the instance that's supposed to match this tag
} else {
std::cerr << "Control tag and instance match up." << std::endl;
// set the associated node pointer
instance->SetHTMLNode(&(*it));
// add to nodemap
_nodemap[&(*it)] = instance;
// count how many controls we found
controlcount++;
}
}
// if we found an auto tag on the first pass and no one is using the specified id (at this point instance is guaranteed == NULL, given the other two)
// we've previously handled (instance != NULL, auto, firstpass) and (instance != NULL, !auto, !firstpass)
// if auto and firstpass, instance has to be NULL- meaning the tag's requested id is available
} else if (isauto && firstpass) {
std::cerr << "found auto tag, specified ID is available" << std::endl;
// try to create a control of this type. The control class must be registered with the factory.
// only classes that support autos should register.
instance = g_controlfactory.CreateByType(tagType.c_str(), idresult.second.c_str(), (Control *)
this);
//If we get an instance, it worked, if it didn't tough luck.
if (instance == NULL) {
std::cerr << "unable to create autonomous control of type " << tagType << std::endl;
} else {
std::cerr << "autonomous tag of type=" << tagType << ", id=" << idresult.second
<< " was created." << std::endl;
// set the associated node pointer
instance->SetHTMLNode(&(*it));
// add to nodemap
_nodemap[&(*it)] = instance;
// add to the count of registered controls
controlcount++;
}
// if still auto tag, by elimination, this isn't the first pass- we're not interested. No error here.
} else if (isauto) {
std::cerr << "found auto tag, skipping for second pass" << std::endl;
}
}
}
}
++it;
}
std::cerr << (firstpass ? "First " : "Second ") << "parsing pass complete." << std::endl << std::endl;
}
// helper class to output recursively render all tags we're in control of and output the stuff in the template inbetween
// called by render
void
Page::renderNode(tree<htmlnode>::sibling_iterator *thisnode, int level, std::string &rendered) {
tree<htmlnode>::sibling_iterator sib = thisnode->begin();
while (sib != thisnode->end()) {
htmlnode *tmpnode = &(sib.node->data);
if (tmpnode->isTag() && (tmpnode->tagName() == Page::_namespace)) {
// retrieve the control instance using the html node instance
Control *tmpcontrol = _nodemap[tmpnode];
// if we found the control associated with this node, tell it to render
// otherwise, print an error in its place
if (tmpcontrol != NULL) rendered.append(tmpcontrol->render());
else rendered.append("<!-- ERROR rendering control: no instance found -->");
} else {
// otherwise, stick in the html
rendered.append(sib->text());
// any children of this node
if (sib->isTag()) renderNode(&sib, level + 1, rendered);
// and the stuff after
rendered.append(sib->closingText());
}
++sib; // next!
}
}
// NOTE: if a custom control can have children, it's up to that control to implement the recursive rendering
std::string
Page::render() {
std::string rendered; // buffer for the rendered page
int lastpos = 0, startpos = 0, endpos = -1, testpos = -1, i = 0;
bool replace = false;
std::string *datacontents;
var_map::iterator m;
if (_data.size() == 0) throw GridException(104, "render called when front-end page not given or empty");
this->Page::parse(); // call 2nd pass
// assemble page: start by rendering the first node.
tree<htmlnode>::sibling_iterator sib = _tree.begin();
renderNode(&sib, 1, rendered);
// handle variable tag replacement- this happens in render so that if the client updated any control values
// we get them into the output instead of the defaults from instantiation
while (true) {
startpos = rendered.find("<%=", startpos);
if (startpos == std::string::npos) break; // no tags found, abort
endpos = rendered.find("%>", startpos);
if (endpos == std::string::npos) break; // no end tag to be found, abort
// have a <%=varname%> from startpos up to (but not including) endpos
std::string vartag = std::string(rendered.begin() + startpos + 3, rendered.begin() + endpos);
#ifdef GRIDIRON_VARTAG_SPACES
// in place trim
string_trunc_trim(vartag);
#else
// must be no spaces to help avoid replacing huge sections
// if tags are broken
if (vartag.find(' ') != std::string::npos) {
startpos++; // increment startpos so we don't find the same one
continue; // skip that tag
}
#endif
// loop through regvars, if match with vartag, substitute and set replace=true;
replace = false;
for (m = _regvars.begin(); m != _regvars.end(); ++m) {
if (m->first == vartag) {
datacontents = m->second;
replace = true;
}
}
// do the actual replace if necessary
if (replace) {
// delete tag from original data, since it's being replaced
rendered.erase(rendered.begin() + startpos, rendered.begin() + endpos + 2);
// copy the requested variable contents to where we deleted the variable tag
rendered.insert(startpos, *datacontents);
} else {
startpos++; // variable wasn't registered, move ahead one so we don't keep hitting it
}
lastpos = endpos + 2;
}
return rendered;
}
// for controls to make variables available for HTML replacement. alphanumeric and _ only.
bool
Page::RegisterVariable(const std::string name, std::string *data) {
// NOTE: tags starting with __ should be system generated vars only, but we won't check
// make sure name has a length
if (name.length() == 0) return false;
// validate name characters
int pos = name.find_first_not_of("_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."); // allow dots
if (pos != std::string::npos) return false;
// search through variables, make sure name isn't already registered
for (var_map::iterator m = _regvars.begin(); m != _regvars.end(); ++m) {
if (m->first == name) return false;
}
// no existing var with same name found, register it
_regvars[name] = data;
return true;
}