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