FEAT 3
Finite Element Analysis Toolbox
Loading...
Searching...
No Matches
xml_scanner.cpp
1// FEAT3: Finite Element Analysis Toolbox, Version 3
2// Copyright (C) 2010 by Stefan Turek & the FEAT group
3// FEAT3 is released under the GNU General Public License version 3,
4// see the file 'copyright.txt' in the top level directory for details.
5
7#include <kernel/util/xml_scanner.hpp>
8#include <cctype>
9
10namespace FEAT
11{
12 namespace Xml
13 {
14 DummyParser::DummyParser()
15 {
16 }
17
18 bool DummyParser::attribs(std::map<String,bool>&) const
19 {
20 return false;
21 }
22
23 void DummyParser::create(int, const String&, const String&, const std::map<String, String>&, bool)
24 {
25 }
26
27 void DummyParser::close(int, const String&)
28 {
29 }
30
31 std::shared_ptr<MarkupParser> DummyParser::markup(int, const String&, const String&)
32 {
33 // return a new DummyParser node
34 return std::make_shared<DummyParser>();
35 }
36
37 bool DummyParser::content(int, const String&)
38 {
39 return true;
40 }
41
42 /* ***************************************************************************************** */
43 /* ***************************************************************************************** */
44 /* ***************************************************************************************** */
45
46 DumpParser::DumpParser(const String& name, bool lines, std::size_t indent) :
47 _name(name), _lines(lines), _indent(indent)
48 {
49 }
50
51 bool DumpParser::attribs(std::map<String,bool>&) const
52 {
53 return false;
54 }
55
56 void DumpParser::create(int iline, const String&, const String& name, const std::map<String, String>& attrs, bool closed)
57 {
58 if(_lines) std::cout << std::setw(5) << iline << ":";
59 std::cout << std::string(_indent, ' ');
60 std::cout << "<" << name;
61 for(auto it = attrs.begin(); it != attrs.end(); ++it)
62 {
63 std::cout << " " << it->first << "=\"" << it->second << "\"";
64 }
65 std::cout << (closed ? " />" : ">") << "\n";
66 }
67
68 void DumpParser::close(int iline, const String&)
69 {
70 if(_lines) std::cout << std::setw(5) << iline << ":";
71 std::cout << std::string(_indent, ' ');
72 std::cout << "</" << _name << ">\n";
73 }
74
75 std::shared_ptr<MarkupParser> DumpParser::markup(int, const String&, const String& name)
76 {
77 return std::make_shared<DumpParser>(name, _lines, _indent+2);
78 }
79
80 bool DumpParser::content(int iline, const String& sline)
81 {
82 if(_lines) std::cout << std::setw(5) << iline << ":";
83 std::cout << std::string(_indent+2, ' ');
84 std::cout << sline << "\n";
85 return true;
86 }
87
88 /* ***************************************************************************************** */
89 /* ***************************************************************************************** */
90 /* ***************************************************************************************** */
91
93 {
94 // ensure that the markup stack is empty
95 XASSERTM(_markups.empty(), "markup stack is not empty");
96
97 // make sure that we did not yet read a line
98 XASSERTM(!_have_read_root, "root markup already read");
99
100 // try to read first line
101 if(!read_next_line())
102 throw_syntax("Root markup is missing");
103
104 // ensure that it is a markup
105 if(!scan_markup())
106 throw_syntax("First line is not a markup");
107
108 // ensure that it is not a bogus markup
110 throw_syntax("Invalid closed root markup");
112 throw_syntax("Invalid root terminator markup");
113
114 // we have read a root markup
115 _have_read_root = true;
116 }
117
118 void Scanner::set_root_parser(std::shared_ptr<MarkupParser> parser)
119 {
120 // avoid bogus
121 XASSERTM(parser != nullptr, "root parser must not be nullptr");
122
123 // make sure that we do not have any markups yet
124 XASSERTM(_markups.empty(), "root parser already exists");
125
126 // make sure that we did read a root parser
127 XASSERTM(_have_read_root, "root markup not read yet");
128
129 // push our parser to the markup stack
130 _markups.push_back(MarkupInfo(_cur_iline, _cur_name, parser));
131
132 // try to create the root node
133 //_markups.back().parser()->create(_cur_iline, _cur_sline, _cur_attribs, false);
135 }
136
138 {
139 // ensure that we have a root parser
140 XASSERTM(!_markups.empty(), "root parser does not yet exist");
141
142 // scan through all lines
143 while(read_next_line())
144 {
145 // is it a comment?
146 if(_cur_sline.starts_with("<!--"))
147 {
148 if (!_cur_sline.ends_with("-->"))
149 throw_syntax("Missing '-->' at end of comment");
150 else
151 continue;
152 }
153
154 // is it an XML markup line?
155 if(scan_markup())
157 else
159
160 // all markups processed?
161 if(_markups.empty())
162 return;
163 }
164
165 // ensure that all _markups have been closed
166 if(!_markups.empty())
167 {
168 String msg = String("Expected '</") + _markups.back().name() + ">' but found end-of-file";
169 throw SyntaxError(_cur_iline, "", msg);
170 }
171 }
172
173 void Scanner::scan(std::shared_ptr<MarkupParser> root_parser)
174 {
175 read_root();
176 set_root_parser(root_parser);
177 scan();
178 }
179
181 {
182 while(_stream.good())
183 {
184 // read another line
185 std::getline(_stream, _cur_sline);
187 ++_cur_iline;
188
189 // non-empty line?
190 if(!_cur_sline.empty())
191 return true;
192 }
193
194 // no more lines
195 return false;
196 }
197
199 {
200 // basic assumptions
202 _cur_name.clear();
203 _cur_attribs.clear();
204
205 // XML markers?
206 bool xhead = _cur_sline.starts_with('<');
207 bool xtail = _cur_sline.ends_with('>');
208
209 // Not a markup?
210 if((!xhead) && (!xtail))
211 return false;
212
213 // Invalid combination?
214 if(xhead && !xtail)
215 throw_syntax("Expected '>' at end of line");
216 if(!xhead && xtail)
217 throw_syntax("Expected '<' at beginning of line");
218
219 // both xhead and xtail are true: we have a markup here
220 _cur_is_markup = true;
221
222 // extract line data; that's everything between '<' and '>'
223 String sdata = String(_cur_sline.substr(1, _cur_sline.length()-2)).trim();
224
225 // make sure that it is not empty
226 if(sdata.empty())
227 throw_syntax("Empty markup");
228
229 // check whether we have additional '<' or '>' inside the line; we do not support that
230 if(sdata.find('<') != sdata.npos)
231 throw_syntax("Invalid '<' in line");
232 if (sdata.find('>') != sdata.npos)
233 throw_syntax("Invalid '>' in line");
234
235 // closed or a terminator?
236 _cur_is_termin = (sdata.front() == '/');
237 _cur_is_closed = (sdata.back() == '/');
238
239 // make sure we have no bogus here
241 throw_syntax("Invalid markup");
242
243 // remove '/'
245 sdata.pop_front();
247 sdata.pop_back();
248
249 // empty?
250 if((sdata = sdata.trim()).empty())
251 throw_syntax("Empty markup");
252
253 // extract markup name
254 {
255 size_t n0 = sdata.find_first_of(sdata.whitespaces());
256 if(n0 != sdata.npos)
257 {
258 // markup name followed by whitespace
259 _cur_name = sdata.substr(0, n0);
260 sdata = String(sdata.substr(n0)).trim();
261 }
262 else
263 {
264 _cur_name = sdata;
265 sdata.clear();
266 }
267
268 // ensure that the name is not empty
269 if(_cur_name.empty())
270 throw_syntax("Empty markup name");
271
272 // ensure that it contains only alphanumeric chars
273 if(std::isalpha(_cur_name.front()) == 0)
274 throw_syntax("Invalid first character in markup name");
275 for(std::size_t i(1); i < _cur_name.size(); ++i)
276 {
277 if(std::isalnum(_cur_name.at(i)) == 0)
278 throw_syntax(String("Invalid character '") + _cur_name.at(i) + "' in markup name");
279 }
280 }
281
282 // is this a terminator?
284 {
285 // make sure that we have no trailing data
286 if(!sdata.empty())
287 throw_syntax("Invalid terminator");
288
289 // okay, it's a terminator
290 return true;
291 }
292
293 // extract the attributes
294 while(!sdata.empty())
295 {
296 // try to find a '='
297 size_t p = sdata.find_first_of('=');
298 if(p == sdata.npos)
299 throw_syntax("Invalid attribute list");
300
301 // extract attribute name
302 String akey = String(sdata.substr(0, p)).trim();
303 sdata = String(sdata.substr(p+1)).trim();
304
305 // no key name?
306 if(akey.empty())
307 throw_syntax("Attribute key name missing");
308
309 // ensure that it contains only alphanumeric chars
310 if(std::isalpha(akey.front()) == 0)
311 throw_syntax("Invalid first character in attribute name '" + akey + "'");
312 for(std::size_t i(1); i < akey.size(); ++i)
313 {
314 if(std::isalnum(akey.at(i)) == 0)
315 throw_syntax(String("Invalid character '") + akey.at(i) + "' in attribute name '" + akey + "'");
316 }
317
318 // parse attribute
319 if(sdata.empty() || sdata.front() != '"')
320 throw_syntax("Expected '\"'");
321
322 // find next '"'
323 size_t q = sdata.find_first_of('"', 1);
324 if(q == sdata.npos)
325 throw_syntax("Missing '\"'");
326
327 // extract attribute value
328 String aval = String(sdata.substr(1, q-1)).trim();
329
330 // push attribute
331 _cur_attribs.emplace(akey, aval);
332
333 // remove attribute
334 sdata = String(sdata.substr(q+1)).trim();
335 }
336
337 // okay
338 return true;
339 }
340
342 {
343 // not a markup?
344 XASSERTM(_cur_is_markup, "invalid markup process call");
345
346 // is it a terminator?
348 {
349 // ensure that we have something to terminate
350 if(_markups.empty())
351 throw_syntax("Unexpected terminator");
352
353 // make sure the terminator matches the top marker
354 if(_cur_name != _markups.back().name())
355 throw_syntax(String("Expected '</") + _markups.back().name() + String(">'"));
356
357 // destroy parser
358 _markups.back().parser()->close(_cur_iline, _cur_sline);
359
360 // pop the last marker
361 _markups.pop_back();
362
363 // we're done
364 return;
365 }
366
367 // try to create a parser
368 std::shared_ptr<MarkupParser> parser = _markups.back().parser()->markup(_cur_iline, _cur_sline, _cur_name);
369
370 // make sure this markup is supported
371 if(parser == nullptr)
372 throw_grammar("Unsupported markup");
373
374 // push new markup
375 _markups.push_back(MarkupInfo(_cur_iline, _cur_name, parser));
376
377 // create top markup parser
379
380 // pop markup if closed
382 {
383 _markups.back().parser()->close(_cur_iline, _cur_sline);
384 _markups.pop_back();
385 }
386 }
387
389 {
390 XASSERTM(!_markups.empty(), "Empty markup stack");
391 XASSERTM(_cur_is_markup, "no markup to create");
392
393 // get the parser attributes
394 std::map<String, bool> parser_attribs;
395 if(_markups.back().parser()->attribs(parser_attribs))
396 {
397 // first of all, check whether all arguments are valid
398 for (auto it = _cur_attribs.begin(); it != _cur_attribs.end(); ++it)
399 {
400 if(parser_attribs.find(it->first) == parser_attribs.end())
401 {
402 throw_grammar(String("Unexpected attribute '") + it->first + "'");
403 }
404 }
405
406 // now let's check if all mandatory attributes are given
407 for(auto jt = parser_attribs.begin(); jt != parser_attribs.end(); ++jt)
408 {
409 if(jt->second)
410 {
411 if(_cur_attribs.find(jt->first) == _cur_attribs.end())
412 {
413 throw_grammar(String("Mandatory attribute '") + jt->first + "' missing");
414 }
415 }
416 }
417 }
418
419 // create the markup
421 }
422
424 {
425 // process content
426 if(!_markups.back().parser()->content(_cur_iline, _cur_sline))
427 throw_grammar("Invalid content line");
428 }
429 } // namespace Xml
430} // namespace FEAT
#define XASSERTM(expr, msg)
Assertion macro definition with custom message.
Definition: assertion.hpp:263
String class implementation.
Definition: string.hpp:46
void pop_front()
Removes the first character from the string.
Definition: string.hpp:239
static const char * whitespaces()
Returns a null-terminated char string containing all white-space characters.
Definition: string.hpp:222
bool ends_with(const String &tail) const
Checks whether this string ends with another string.
Definition: string.hpp:754
String & trim_me(const String &charset)
Trims this string.
Definition: string.hpp:358
void pop_back()
Removes the last character from the string.
Definition: string.hpp:245
bool starts_with(const String &head) const
Checks whether this string starts with another string.
Definition: string.hpp:731
String trim(const String &charset) const
Trims the string.
Definition: string.hpp:327
virtual bool attribs(std::map< String, bool > &) const override
Specifies the mandatory and optional attributes.
Definition: xml_scanner.cpp:18
virtual bool content(int, const String &) override
Called to process a content line.
Definition: xml_scanner.cpp:37
virtual void create(int, const String &, const String &, const std::map< String, String > &, bool) override
Creates this markup parser node.
Definition: xml_scanner.cpp:23
virtual void close(int, const String &) override
Closes this markup parser node.
Definition: xml_scanner.cpp:27
virtual std::shared_ptr< MarkupParser > markup(int, const String &, const String &) override
Called to process a child markup node.
Definition: xml_scanner.cpp:31
virtual std::shared_ptr< MarkupParser > markup(int, const String &, const String &name) override
Called to process a child markup node.
Definition: xml_scanner.cpp:75
virtual void create(int iline, const String &, const String &, const std::map< String, String > &attrs, bool closed) override
Creates this markup parser node.
Definition: xml_scanner.cpp:56
bool _lines
specifies whether to write line numbers
std::size_t _indent
indentation for this parser node
String _name
the name of this markup parser node
virtual bool attribs(std::map< String, bool > &) const override
Specifies the mandatory and optional attributes.
Definition: xml_scanner.cpp:51
virtual bool content(int iline, const String &sline) override
Called to process a content line.
Definition: xml_scanner.cpp:80
virtual void close(int iline, const String &) override
Closes this markup parser node.
Definition: xml_scanner.cpp:68
void set_root_parser(std::shared_ptr< MarkupParser > parser)
Sets the root markup parser node.
String _cur_sline
current line string
bool read_next_line()
Reads the next non-empty line from the stream.
bool _cur_is_termin
specifies whether the markup is a terminator
bool scan_markup()
Tries to interpret the current line as a XML markup line.
int _cur_iline
current line number
std::map< String, String > _cur_attribs
specifies the markup attributes of the current line
void read_root()
Tries to read the XML root markup.
Definition: xml_scanner.cpp:92
bool _have_read_root
specifies whether the root has been read
void throw_grammar(const String &msg) const
Throws a GrammarError for the current line.
String _cur_name
specifies the markup name of the current line
void create_top_parser()
Creates the top parser node.
void process_markup()
Tries to process the current XML markup line.
void throw_syntax(const String &msg) const
Throws a SyntaxError for the current line.
bool _cur_is_markup
specifies whether this line is a markup
std::vector< MarkupInfo > _markups
the markup stack
void process_content()
Tries to process the current XML content line.
bool _cur_is_closed
specifies whether the markup is closed
void scan()
Scans the stream.
std::istream & _stream
the input stream
Xml syntax error class.
Definition: xml_scanner.hpp:94
FEAT namespace.
Definition: adjactor.hpp:12