Line data Source code
1 : #include "oks/xml.hpp"
2 : #include "oks/defs.hpp"
3 : #include "oks/exceptions.hpp"
4 : #include "oks/kernel.hpp"
5 : #include "oks/object.hpp"
6 : #include "oks/cstring.hpp"
7 :
8 : #include <iostream>
9 : #include <sstream>
10 :
11 : #include <boost/spirit/include/karma.hpp>
12 :
13 : #include <errno.h>
14 : #include <stdlib.h>
15 :
16 : #include "ers/ers.hpp"
17 : #include "logging/Logging.hpp"
18 :
19 : namespace dunedaq {
20 : namespace oks {
21 : OksXmlTokenPool OksXmlInputStream::s_tokens_pool;
22 :
23 :
24 : namespace xml {
25 :
26 : const char left_angle_bracket[] = "<";
27 : const char right_angle_bracket[] = ">";
28 : const char ampersand[] = "&";
29 : const char carriage_return[] = "
";
30 : const char new_line[] = "
";
31 : const char tabulation[] = "	";
32 : const char single_quote[] = "'";
33 : const char double_quote[] = """;
34 :
35 : }
36 :
37 518 : exception::exception(const std::string& what_arg, int level_arg) noexcept : p_level (level_arg)
38 : {
39 518 : std::ostringstream s;
40 518 : s << "oks[" << p_level << "] ***: " << what_arg << std::ends;
41 518 : p_what = s.str();
42 518 : }
43 :
44 0 : void throw_validate_not_empty(const char * name)
45 : {
46 0 : std::string text(name);
47 0 : text += " is not set";
48 0 : throw std::runtime_error(text.c_str());
49 0 : }
50 :
51 : std::string
52 0 : BadFileData::fill(const std::string& item, unsigned long line_no, unsigned long line_pos) noexcept
53 : {
54 0 : std::ostringstream s;
55 0 : s << item << " (line " << line_no << ", char " << line_pos << ')';
56 0 : return s.str();
57 0 : }
58 :
59 : std::string
60 0 : FailedRead::fill(const std::string& item, const std::string& reason) noexcept
61 : {
62 0 : return (std::string("Failed to read \'") + item + "\'\n" + reason);
63 : }
64 :
65 : std::string
66 0 : FailedSave::fill(const std::string& item, const std::string& name, const std::string& reason) noexcept
67 : {
68 0 : return (std::string("Failed to save ") + item + " \'" + name + "\'\n" + reason);
69 : }
70 :
71 : std::string
72 140 : EndOfXmlStream::fill(const std::string& tag)
73 : {
74 140 : return (std::string("Read end-of-stream tag \'") + tag + '\'');
75 : }
76 :
77 :
78 : static std::string
79 0 : report_unexpected(const char * what, const char expected, const char read)
80 : {
81 0 : std::string s;
82 :
83 0 : if(what) {
84 0 : s += "Failed to read ";
85 0 : s += what;
86 0 : s += ": ";
87 : }
88 :
89 0 : s += "symbol \'";
90 0 : s += expected;
91 0 : s += "\' is expected instead of \'";
92 0 : s += read;
93 0 : s += '\'';
94 :
95 0 : return s;
96 0 : }
97 :
98 :
99 : static void
100 0 : __throw_runtime_error_unexpected_symbol(const char expected, const char read)
101 : {
102 0 : throw std::runtime_error(report_unexpected(0, expected, read));
103 : }
104 :
105 : static void
106 0 : __throw_bad_file_data_unexpected_symbol(const char * what, const char expected, const char read, unsigned long l, unsigned long p)
107 : {
108 0 : throw oks::BadFileData(report_unexpected(what, expected, read), l, p);
109 : }
110 :
111 : void
112 0 : OksXmlOutputStream::__throw_write_failed()
113 : {
114 0 : throw std::runtime_error("write to file failed");
115 : }
116 :
117 :
118 : //
119 : // Save char to xml output stream
120 : // Throw std::exception if failed
121 : //
122 :
123 : void
124 0 : OksXmlOutputStream::put(char c)
125 : {
126 0 : if(c == '<') put_raw(oks::xml::left_angle_bracket, sizeof(oks::xml::left_angle_bracket)-1);
127 0 : else if(c == '>') put_raw(oks::xml::right_angle_bracket, sizeof(oks::xml::right_angle_bracket)-1);
128 0 : else if(c == '&') put_raw(oks::xml::ampersand, sizeof(oks::xml::ampersand)-1);
129 0 : else if(c == '\'') put_raw(oks::xml::single_quote, sizeof(oks::xml::single_quote)-1);
130 0 : else if(c == '\"') put_raw(oks::xml::double_quote, sizeof(oks::xml::double_quote)-1);
131 0 : else if(c == '\r') put_raw(oks::xml::carriage_return, sizeof(oks::xml::carriage_return)-1);
132 0 : else if(c == '\n') put_raw(oks::xml::new_line, sizeof(oks::xml::new_line)-1);
133 0 : else if(c == '\t') put_raw(oks::xml::tabulation, sizeof(oks::xml::tabulation)-1);
134 0 : else put_raw(c);
135 :
136 0 : }
137 :
138 :
139 : //
140 : // Save string to xml output stream
141 : // Throw std::exception if failed
142 : //
143 :
144 : void
145 0 : OksXmlOutputStream::put(const char * str)
146 : {
147 0 : while(*str) put(*str++);
148 0 : }
149 :
150 :
151 : //
152 : // Save quoted string to xml output stream
153 : // Throw std::exception if failed
154 : //
155 :
156 : void
157 0 : OksXmlOutputStream::put_quoted(const char *str)
158 : {
159 0 : put_raw('\"');
160 0 : put(str);
161 0 : put_raw('\"');
162 0 : }
163 :
164 :
165 : //
166 : // Save xml start tag and element name, i.e. '<element-name'
167 : // Throw std::exception if failed
168 : //
169 :
170 : void
171 0 : OksXmlOutputStream::put_start_tag(const char *name, size_t len)
172 : {
173 0 : put_raw('<');
174 0 : put_raw(name, len);
175 0 : }
176 :
177 :
178 :
179 : //
180 : // Save xml end tag, i.e. '/>\n'
181 : // Throw std::exception if failed
182 : //
183 :
184 : void
185 0 : OksXmlOutputStream::put_end_tag()
186 : {
187 0 : static const char __end_tag[] = "/>\n";
188 0 : put_raw(__end_tag, sizeof(__end_tag)-1);
189 0 : }
190 :
191 : void
192 0 : OksXmlOutputStream::put_eol()
193 : {
194 0 : static const char __eol_tag[] = ">\n";
195 0 : put_raw(__eol_tag, sizeof(__eol_tag)-1);
196 0 : }
197 :
198 : //
199 : // Save xml last tag, i.e. '</tag-name>\n'
200 : // Throw std::exception if failed
201 : //
202 :
203 : void
204 0 : OksXmlOutputStream::put_last_tag(const char * name, size_t len)
205 : {
206 0 : put_raw('<');
207 0 : put_raw('/');
208 0 : put_raw(name, len);
209 0 : put_raw('>');
210 0 : put_raw('\n');
211 0 : }
212 :
213 :
214 : //
215 : // Save xml element attribute with value, i.e. ' name="value"'
216 : // Throw std::exception if failed
217 : //
218 :
219 : void
220 0 : OksXmlOutputStream::put_attribute(const char * name, size_t len, const char *value)
221 : {
222 0 : put_raw(' ');
223 0 : put_raw(name, len);
224 0 : put_raw('=');
225 0 : put_raw('\"');
226 0 : put(value);
227 0 : put_raw('\"');
228 0 : }
229 :
230 : void
231 0 : OksXmlOutputStream::put_attribute(const char * name, size_t len, uint32_t value)
232 : {
233 0 : put_raw(' ');
234 0 : put_raw(name, len);
235 0 : put_raw('=');
236 0 : put_raw('\"');
237 0 : if(value == 0) {
238 0 : put_raw('0');
239 : }
240 : else {
241 0 : char buf[12];
242 0 : char * ptr = buf;
243 0 : boost::spirit::karma::generate(ptr, boost::spirit::uint_, (unsigned int)value);
244 0 : put_raw(buf, ptr - buf);
245 : }
246 0 : put_raw('\"');
247 0 : }
248 :
249 : void
250 0 : OksXmlOutputStream::put_attribute(const char * name, size_t len, const OksData& value)
251 : {
252 0 : put_raw(' ');
253 0 : put_raw(name, len);
254 0 : put_raw('=');
255 0 : put_raw('\"');
256 0 : value.WriteTo(*this);
257 0 : put_raw('\"');
258 0 : }
259 :
260 : void
261 0 : OksXmlInputStream::throw_unexpected_tag(const char * what, const char * expected)
262 : {
263 0 : std::ostringstream s;
264 :
265 0 : if(!*what) {
266 0 : if(expected) {
267 0 : s << "expected tag \'" << expected << '\'';
268 : }
269 : else {
270 0 : s << "empty tag";
271 : }
272 : }
273 : else {
274 0 : s << "unexpected tag \'" << what << '\'' ;
275 0 : if(expected) {
276 0 : s << " instead of \'" << expected << '\'' ;
277 : }
278 : }
279 :
280 0 : throw oks::BadFileData(s.str(), line_no, line_pos);
281 0 : }
282 :
283 : void
284 0 : OksXmlInputStream::throw_unexpected_attribute(const char * what)
285 : {
286 0 : throw oks::BadFileData(std::string("Value \'") + what + "\' is not a valid attribute name", line_no, line_pos);
287 : }
288 :
289 : size_t
290 0 : OksXmlInputStream::get_quoted()
291 : {
292 0 : size_t pos(0);
293 :
294 0 : try {
295 0 : char c = get_first_non_empty();
296 :
297 0 : if( __builtin_expect((c != '\"'), 0) ) {
298 0 : __throw_runtime_error_unexpected_symbol('\"', c);
299 : }
300 :
301 0 : while(true) {
302 0 : c = get();
303 :
304 0 : if( __builtin_expect((c == '\"'), 0) ) {
305 0 : m_v1->m_buf[pos] = 0;
306 0 : return pos;
307 : }
308 0 : else if( __builtin_expect((c == '&'), 0) ) {
309 0 : c = cvt_char();
310 : }
311 :
312 0 : m_v1->realloc(pos);
313 0 : m_v1->m_buf[pos++] = c;
314 : }
315 :
316 : throw std::runtime_error("cannot find closing quote character");
317 : }
318 0 : catch (std::exception & ex) {
319 0 : m_v1->m_buf[pos] = 0;
320 0 : throw oks::BadFileData(std::string("Failed to read quoted string: ") + ex.what(), line_no, line_pos);
321 0 : }
322 : }
323 :
324 :
325 : // check for start of comment
326 :
327 466228 : inline bool __is_comment(const char * s) {
328 466228 : return oks::cmp_str4n(s, "<!--");
329 : }
330 :
331 :
332 : //
333 : // Read xml tag while number of closing angles (i.e. '>') will not be equal
334 : // number of opening ones (i.e. '<')
335 : //
336 :
337 : const char *
338 512 : OksXmlInputStream::get_tag()
339 : {
340 512 : m_v1->m_buf[0] = 0;
341 :
342 512 : unsigned long start_line_no = line_no;
343 512 : unsigned long start_line_pos = line_pos;
344 :
345 512 : size_t pos(0);
346 :
347 768 : try {
348 768 : while(true) {
349 768 : char c(get_first_non_empty());
350 :
351 768 : if( __builtin_expect((c != '<'), 0) ) {
352 0 : __throw_bad_file_data_unexpected_symbol("start-of-tag", '<', c, line_no, line_pos);
353 : }
354 :
355 768 : start_line_no = line_no;
356 768 : start_line_pos = line_pos;
357 :
358 768 : m_v1->m_buf[0] = c;
359 768 : pos = 1;
360 :
361 768 : int count(1);
362 :
363 467764 : while(true) {
364 467764 : char c2 = get();
365 :
366 467764 : if(c2 == '<') count++;
367 462620 : else if(c2 == '>') count--;
368 :
369 467764 : m_v1->realloc(pos);
370 467764 : m_v1->m_buf[pos++] = c2;
371 :
372 467764 : if(pos > 3) {
373 466228 : if(__is_comment(m_v1->m_buf + pos - 4)) {
374 256 : char buf[3] = {0, 0, 0};
375 256 : unsigned long start_comment_line_no = line_no;
376 6680 : try {
377 6680 : while(true) {
378 6680 : buf[0] = buf[1];
379 6680 : buf[1] = buf[2];
380 6680 : buf[2] = get();
381 :
382 6680 : if(oks::cmp_str3n(buf, "-->")) {
383 : break; // end of comment = '-->'
384 : }
385 : }
386 : }
387 0 : catch(std::exception& failure) {
388 0 : std::ostringstream what;
389 0 : what << "Cannot find end of comment started on line " << start_comment_line_no << ": " << failure.what();
390 0 : throw oks::BadFileData(what.str(), line_no, line_pos);
391 0 : }
392 :
393 256 : pos -= 4;
394 256 : count--;
395 : }
396 : }
397 :
398 467764 : if(count == 0) {
399 768 : m_v1->m_buf[pos] = '\0';
400 768 : break;
401 : }
402 : }
403 :
404 768 : if(pos > 0) {
405 512 : if(count != 0) { throw std::runtime_error("unbalanced tags"); }
406 512 : else { return m_v1->m_buf; }
407 : }
408 : }
409 : }
410 0 : catch(std::exception& ex) {
411 0 : m_v1->m_buf[pos] = '\0';
412 0 : throw oks::BadFileData(std::string(ex.what()) + " when read xml tag started at", start_line_no, start_line_pos);
413 0 : }
414 : }
415 :
416 :
417 : //
418 : // Reads token from stream until any symbol from 'separator' will be found.
419 : // If 'first_symbol' is not 0, if will be first symbol of token.
420 : //
421 :
422 : inline size_t
423 163026 : OksXmlInputStream::get_token(const char __s1, OksXmlToken& token)
424 : {
425 163026 : size_t pos(0);
426 :
427 4769798 : while(true) {
428 2466412 : last_read_c = get();
429 :
430 2466487 : if(last_read_c == __s1) { token.m_buf[pos] = '\0'; return pos; }
431 2303317 : else if( __builtin_expect((last_read_c == '&'), 0) ) last_read_c = cvt_char();
432 :
433 2303317 : token.realloc(pos);
434 2303386 : token.m_buf[pos++] = last_read_c;
435 : }
436 :
437 : token.m_buf[pos] = 0;
438 :
439 : throw std::runtime_error("cannot find closing separator while read token");
440 : }
441 :
442 : inline size_t
443 198651 : OksXmlInputStream::get_token2(char __fs, const char __s1, const char __s2, OksXmlToken& token)
444 : {
445 198651 : size_t pos(1);
446 198651 : token.m_buf[0] = __fs;
447 198651 : if(__fs == __s1 || __fs == __s2) { token.m_buf[1] = '\0'; return 1; }
448 :
449 1832857 : while(true) {
450 1008652 : last_read_c = get();
451 :
452 1008638 : if(last_read_c == __s1 || last_read_c == __s2) { token.m_buf[pos] = '\0'; return pos; }
453 824183 : else if( __builtin_expect((last_read_c == '&'), 0) ) last_read_c = cvt_char();
454 :
455 824183 : token.realloc(pos);
456 824205 : token.m_buf[pos++] = last_read_c;
457 : }
458 :
459 : token.m_buf[pos] = 0;
460 :
461 : throw std::runtime_error("cannot find closing separator while read token");
462 : }
463 :
464 : inline void
465 64513 : OksXmlInputStream::get_token5(const char __s1, const char __s2, const char __s3, const char __s4, const char __s5, OksXmlToken& token)
466 : {
467 64513 : size_t pos(0);
468 :
469 920625 : while(true) {
470 492569 : last_read_c = get();
471 :
472 492573 : if(last_read_c == __s1 || last_read_c == __s2 || last_read_c == __s3 || last_read_c == __s4 || last_read_c == __s5) { token.m_buf[pos] = '\0'; return; }
473 428050 : else if( __builtin_expect((last_read_c == '&'), 0) ) last_read_c = cvt_char();
474 :
475 428050 : token.realloc(pos);
476 428056 : token.m_buf[pos++] = last_read_c;
477 : }
478 :
479 : token.m_buf[pos] = 0;
480 :
481 : throw std::runtime_error("cannot find closing separator while read token");
482 : }
483 :
484 : const char *
485 64497 : OksXmlInputStream::get_tag_start()
486 : {
487 64515 : try {
488 64515 : while(true) {
489 64515 : char c = get_first_non_empty();
490 :
491 64512 : if( __builtin_expect((c != '<'), 0) ) {
492 0 : __throw_runtime_error_unexpected_symbol('<', c);
493 : }
494 :
495 64512 : get_token5(' ', '>', '\t', '\n', '\r', *m_v1);
496 64518 : TLOG_DEBUG(8) << "read tag \'" << m_v1->m_buf << '\'';
497 :
498 64499 : if( __builtin_expect((oks::cmp_str3n(m_v1->m_buf, "!--")), 0) ) {
499 18 : char buf[3] = {0, 0, 0};
500 18 : unsigned long start_comment_line_no = line_no;
501 18 : auto tag_len = strlen(m_v1->m_buf);
502 18 : if(tag_len < 5 || !oks::cmp_str2n(m_v1->m_buf + tag_len- 2, "--")) // special case for comments without empty symbols like <!--foo-->
503 3231 : try {
504 3231 : while(true) {
505 3231 : buf[0] = buf[1];
506 3231 : buf[1] = buf[2];
507 3231 : c = buf[2] = get();
508 3231 : if(oks::cmp_str3n(buf, "-->")) {
509 : break; // end of comment = '-->'
510 : }
511 : }
512 : }
513 0 : catch(std::exception& failure) {
514 0 : std::ostringstream what;
515 0 : what << "Cannot find end of comment started on line " << start_comment_line_no << ": " << failure.what();
516 0 : throw oks::BadFileData(what.str(), line_no, line_pos);
517 0 : }
518 : }
519 :
520 64499 : if( __builtin_expect((c != '>'), 1) ) return m_v1->m_buf;
521 : }
522 : }
523 0 : catch(std::exception& ex) {
524 0 : throw oks::BadFileData(std::string("Failed to read start-of-tag: ") + ex.what(), line_no, line_pos);
525 0 : }
526 :
527 : return 0; // never reach this line
528 : }
529 :
530 :
531 198649 : OksXmlAttribute::OksXmlAttribute(OksXmlInputStream& s) : p_name(*s.m_v1), p_value(*s.m_v2)
532 : {
533 198649 : unsigned long start_line_no = s.line_no;
534 198649 : unsigned long start_line_pos = s.line_pos;
535 :
536 198649 : try {
537 198649 : char c = s.get_first_non_empty();
538 :
539 198647 : start_line_no = s.line_no;
540 198647 : start_line_pos = s.line_pos;
541 :
542 198647 : size_t pos = s.get_token2(c, '=', '>', p_name);
543 :
544 198465 : char * m_buf_ptr(p_name.m_buf);
545 :
546 : // skip empty symbols
547 :
548 198465 : for(char * str(m_buf_ptr + pos - 1); str >= m_buf_ptr; --str) {
549 198316 : if(*str == ' ' || *str == '\n' || *str == '\r' || *str == '\t') {
550 0 : *str = 0;
551 : }
552 : else {
553 : break;
554 : }
555 : }
556 :
557 198465 : if(c == '>' || c == '/' || c == '?') return; // end of attribute
558 :
559 149387 : c = s.get_first_non_empty();
560 :
561 149415 : if( __builtin_expect((c != '\"'), 0) ) {
562 0 : __throw_bad_file_data_unexpected_symbol("start-of-attribute-value", '\"', c, s.line_no, s.line_pos);
563 : }
564 :
565 149415 : p_value_len = s.get_token('\"', p_value);
566 :
567 149570 : TLOG_DEBUG(8) << "read attribute \'" << m_buf_ptr << "\' and value \'" << p_value.m_buf << '\'';
568 : }
569 0 : catch(std::exception& ex) {
570 0 : throw oks::BadFileData(std::string(ex.what()) + " when read xml attribute started at", start_line_no, start_line_pos);
571 0 : }
572 : }
573 :
574 :
575 : //
576 : // This method is used to convert special xml symbols:
577 : // "<" -> <
578 : // ">" -> >
579 : // "&" -> &
580 : // "'" -> '
581 : // """ -> "
582 : //
583 :
584 : char
585 13602 : OksXmlInputStream::cvt_char()
586 : {
587 13602 : get_token(';', *m_cvt_char);
588 :
589 13602 : if(oks::cmp_str2n(m_cvt_char->m_buf, oks::xml::left_angle_bracket + 1)) return '<';
590 11402 : else if(oks::cmp_str2n(m_cvt_char->m_buf, oks::xml::right_angle_bracket + 1)) return '>';
591 9060 : else if(oks::cmp_str3n(m_cvt_char->m_buf, oks::xml::ampersand + 1)) return '&';
592 7876 : else if(oks::cmp_str3n(m_cvt_char->m_buf, oks::xml::carriage_return + 1)) return '\r';
593 7876 : else if(oks::cmp_str3n(m_cvt_char->m_buf, oks::xml::new_line + 1)) return '\n';
594 1944 : else if(oks::cmp_str3n(m_cvt_char->m_buf, oks::xml::tabulation + 1)) return '\t';
595 1944 : else if(oks::cmp_str4n(m_cvt_char->m_buf, oks::xml::single_quote + 1)) return '\'';
596 1395 : else if(oks::cmp_str4n(m_cvt_char->m_buf, oks::xml::double_quote + 1)) return '\"';
597 : else {
598 0 : std::string text("bad symbol \'&");
599 0 : text += m_cvt_char->m_buf;
600 0 : text += '\'';
601 0 : throw std::runtime_error(text.c_str());
602 0 : }
603 : }
604 :
605 :
606 : std::ostream&
607 0 : OksXmlInputStream::error_msg(const char *msg)
608 : {
609 0 : return Oks::error_msg(msg)
610 0 : << "(line " << line_no << ", char " << line_pos << ")\n";
611 : }
612 :
613 : void
614 0 : OksXmlInputStream::__throw_eof() const
615 : {
616 0 : throw std::runtime_error("unexpected end of file");
617 : }
618 :
619 : void
620 0 : OksXmlInputStream::__throw_strto(const char * f, const char * where, const char * what, unsigned long line_no, unsigned long line_pos)
621 : {
622 0 : std::ostringstream text;
623 0 : text << "function " << f << "(\'" << what << "\') has failed";
624 0 : if(*where) text << " on unrecognized characters \'" << where << "\'";
625 0 : if(errno) text << " with code " << errno << ", reason = \'" << oks::strerror(errno) << '\'';
626 0 : throw oks::BadFileData(text.str(), line_no, line_pos);
627 0 : }
628 :
629 :
630 : } // namespace oks
631 : } // namespace dunedaq
|