Line data Source code
1 : // Copyright (c) 2019-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/basic-xml
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "catch_main.h"
22 :
23 :
24 : // basic-xml
25 : //
26 : #include <basic-xml/exception.h>
27 : #include <basic-xml/parser.h>
28 :
29 :
30 : // C++
31 : //
32 : #include <sstream>
33 :
34 :
35 :
36 10 : CATCH_TEST_CASE("parser", "[parser][valid]")
37 : {
38 16 : CATCH_START_SECTION("empty root")
39 : {
40 2 : std::string const filename("empty-tag.xml");
41 :
42 2 : std::stringstream ss;
43 1 : ss << "<empty></empty>";
44 :
45 2 : basic_xml::node::pointer_t root;
46 2 : basic_xml::parser p(filename, ss, root);
47 1 : CATCH_REQUIRE(root != nullptr);
48 1 : CATCH_REQUIRE(root->tag_name() == "empty");
49 1 : CATCH_REQUIRE(root->text().empty());
50 1 : CATCH_REQUIRE(root->all_attributes().empty());
51 1 : CATCH_REQUIRE(root->parent() == nullptr);
52 1 : CATCH_REQUIRE(root->first_child() == nullptr);
53 1 : CATCH_REQUIRE(root->last_child() == nullptr);
54 1 : CATCH_REQUIRE(root->next() == nullptr);
55 1 : CATCH_REQUIRE(root->previous() == nullptr);
56 : }
57 : CATCH_END_SECTION()
58 :
59 16 : CATCH_START_SECTION("empty root with processor")
60 : {
61 2 : std::string const filename("empty-tag.xml");
62 :
63 2 : std::stringstream ss;
64 1 : ss << "<?xml version=\"1.0\"?><still-empty></still-empty>";
65 :
66 2 : basic_xml::node::pointer_t root;
67 2 : basic_xml::parser p(filename, ss, root);
68 1 : CATCH_REQUIRE(root != nullptr);
69 1 : CATCH_REQUIRE(root->tag_name() == "still-empty");
70 1 : CATCH_REQUIRE(root->text().empty());
71 1 : CATCH_REQUIRE(root->all_attributes().empty());
72 1 : CATCH_REQUIRE(root->parent() == nullptr);
73 1 : CATCH_REQUIRE(root->first_child() == nullptr);
74 1 : CATCH_REQUIRE(root->last_child() == nullptr);
75 1 : CATCH_REQUIRE(root->next() == nullptr);
76 1 : CATCH_REQUIRE(root->previous() == nullptr);
77 : }
78 : CATCH_END_SECTION()
79 :
80 16 : CATCH_START_SECTION("empty root with comment & processor")
81 : {
82 2 : std::string const filename("quite-empty.xml");
83 :
84 2 : std::stringstream ss;
85 1 : ss << "<!-- name='rotor' --><?xml version=\"1.0\"?><quite-empty></quite-empty>";
86 :
87 2 : basic_xml::node::pointer_t root;
88 2 : basic_xml::parser p(filename, ss, root);
89 1 : CATCH_REQUIRE(root != nullptr);
90 1 : CATCH_REQUIRE(root->tag_name() == "quite-empty");
91 1 : CATCH_REQUIRE(root->text().empty());
92 1 : CATCH_REQUIRE(root->all_attributes().empty());
93 1 : CATCH_REQUIRE(root->parent() == nullptr);
94 1 : CATCH_REQUIRE(root->first_child() == nullptr);
95 1 : CATCH_REQUIRE(root->last_child() == nullptr);
96 1 : CATCH_REQUIRE(root->next() == nullptr);
97 1 : CATCH_REQUIRE(root->previous() == nullptr);
98 : }
99 : CATCH_END_SECTION()
100 :
101 16 : CATCH_START_SECTION("empty root with processor & comment")
102 : {
103 2 : std::string const filename("swapped-empty.xml");
104 :
105 2 : std::stringstream ss;
106 1 : ss << "<?xml version=\"1.0\"?><!-- name='rotor' --><swapped-empty></swapped-empty>";
107 :
108 2 : basic_xml::node::pointer_t root;
109 2 : basic_xml::parser p(filename, ss, root);
110 1 : CATCH_REQUIRE(root != nullptr);
111 1 : CATCH_REQUIRE(root->tag_name() == "swapped-empty");
112 1 : CATCH_REQUIRE(root->text().empty());
113 1 : CATCH_REQUIRE(root->all_attributes().empty());
114 1 : CATCH_REQUIRE(root->parent() == nullptr);
115 1 : CATCH_REQUIRE(root->first_child() == nullptr);
116 1 : CATCH_REQUIRE(root->last_child() == nullptr);
117 1 : CATCH_REQUIRE(root->next() == nullptr);
118 1 : CATCH_REQUIRE(root->previous() == nullptr);
119 : }
120 : CATCH_END_SECTION()
121 :
122 16 : CATCH_START_SECTION("empty root with comment & processor & attributes")
123 : {
124 2 : std::string const filename("root-attributes.xml");
125 :
126 2 : std::stringstream ss;
127 1 : ss << "<!--\n"
128 : "name='next level'\n"
129 : "-->\n"
130 : "\n"
131 : "<?xml version=\"1.0\"?>\n"
132 : "<root-canal quite=\"quite\" size='123' very=\"true\">"
133 : " \t \t \t "
134 : "</root-canal>";
135 :
136 2 : basic_xml::node::pointer_t root;
137 2 : basic_xml::parser p(filename, ss, root);
138 1 : CATCH_REQUIRE(root != nullptr);
139 1 : CATCH_REQUIRE(root->tag_name() == "root-canal");
140 1 : CATCH_REQUIRE(root->text() == "");
141 1 : CATCH_REQUIRE(root->text(true) == "");
142 1 : CATCH_REQUIRE(root->text(false) == " \t \t \t ");
143 1 : CATCH_REQUIRE(root->all_attributes().size() == 3);
144 1 : CATCH_REQUIRE(root->attribute("quite") == "quite");
145 1 : CATCH_REQUIRE(root->attribute("size") == "123");
146 1 : CATCH_REQUIRE(root->attribute("very") == "true");
147 1 : CATCH_REQUIRE(root->parent() == nullptr);
148 1 : CATCH_REQUIRE(root->first_child() == nullptr);
149 1 : CATCH_REQUIRE(root->last_child() == nullptr);
150 1 : CATCH_REQUIRE(root->next() == nullptr);
151 1 : CATCH_REQUIRE(root->previous() == nullptr);
152 : }
153 : CATCH_END_SECTION()
154 :
155 16 : CATCH_START_SECTION("entities test")
156 : {
157 2 : std::string const filename("entities.xml");
158 :
159 2 : std::stringstream ss;
160 1 : ss << "<!--\n"
161 : "name='entities'\n"
162 : "-->\n"
163 : "\n"
164 : "<?xml version=\"1.0\"?>\n"
165 : "<entity-a-gogo quite=\"quite\" size='123'"
166 : " very=\""true"\" special-entry=\""<it's special & weird>"\">"
167 : "</entity-a-gogo><?php echo 'why am I ignoring these here?'; ?????>";
168 :
169 2 : basic_xml::node::pointer_t root;
170 2 : basic_xml::parser p(filename, ss, root);
171 1 : CATCH_REQUIRE(root != nullptr);
172 1 : CATCH_REQUIRE(root->tag_name() == "entity-a-gogo");
173 1 : CATCH_REQUIRE(root->all_attributes().size() == 4);
174 1 : CATCH_REQUIRE(root->attribute("quite") == "quite");
175 1 : CATCH_REQUIRE(root->attribute("size") == "123");
176 1 : CATCH_REQUIRE(root->attribute("very") == "\"true\"");
177 1 : CATCH_REQUIRE(root->attribute("special-entry") == "\"<it's special & weird>\"");
178 1 : CATCH_REQUIRE(root->parent() == nullptr);
179 1 : CATCH_REQUIRE(root->first_child() == nullptr);
180 1 : CATCH_REQUIRE(root->last_child() == nullptr);
181 1 : CATCH_REQUIRE(root->next() == nullptr);
182 1 : CATCH_REQUIRE(root->previous() == nullptr);
183 : }
184 : CATCH_END_SECTION()
185 :
186 16 : CATCH_START_SECTION("parse tree")
187 : {
188 2 : std::string const filename("tree.xml");
189 :
190 2 : std::stringstream ss;
191 1 : ss << "<!--\n"
192 : "name='tree'\n"
193 : "-->\n"
194 : "\n"
195 : "<?xml version=\"1.0\"?>\n"
196 : "<tree \t \f >"
197 : "< branch level='1'>"
198 : "<leaf level = '2'>"
199 : "with \f funny \t text \v and \r white \n spaces"
200 : "</ leaf >"
201 : "</branch >"
202 : "</ tree>\n \t \t \n";
203 :
204 2 : basic_xml::node::pointer_t root;
205 2 : basic_xml::parser p(filename, ss, root);
206 1 : CATCH_REQUIRE(root != nullptr);
207 1 : CATCH_REQUIRE(root->tag_name() == "tree");
208 1 : CATCH_REQUIRE(root->all_attributes().empty());
209 :
210 2 : basic_xml::node::pointer_t branch(root->first_child());
211 1 : CATCH_REQUIRE(branch->parent() == root);
212 1 : CATCH_REQUIRE(branch->root() == root);
213 1 : CATCH_REQUIRE(branch->all_attributes().size() == 1);
214 1 : CATCH_REQUIRE(branch->attribute("level") == "1");
215 :
216 2 : basic_xml::node::pointer_t leaf(branch->first_child());
217 1 : CATCH_REQUIRE(leaf->parent() == branch);
218 1 : CATCH_REQUIRE(leaf->root() == root);
219 1 : CATCH_REQUIRE(leaf->all_attributes().size() == 1);
220 1 : CATCH_REQUIRE(leaf->attribute("level") == "2");
221 1 : CATCH_REQUIRE(leaf->text() == "with \f funny \t text \v and \n white \n spaces");
222 : }
223 : CATCH_END_SECTION()
224 :
225 16 : CATCH_START_SECTION("parse tree (CDATA)")
226 : {
227 2 : std::string const filename("tree.xml");
228 :
229 2 : std::stringstream ss;
230 1 : ss << "<tree>"
231 : "<branch level='1'>"
232 : "<leaf level = '2'>"
233 : "with \f funny \t text \v and \r white \n spaces and a > for fun"
234 : "</ leaf >"
235 : "<leaf level = '2'>"
236 : "<![CDATA[-> with \f funny \t text \v and \r white \n spaces <-]]>"
237 : "</ leaf >"
238 : "</branch >"
239 : "<branch level=\"1\">"
240 : "<leaf level=\"2\">"
241 : "This one too has <![CDATA[some <need< for CDATA >and> such]]]]]>"
242 : "</leaf>"
243 : "<leaf level='2' ushes='warning'>=== text starts with equal ===</leaf>"
244 : "<leaf level='2' feature='on'>>>> here it is</leaf>"
245 : "<leaf level='2' message='perfect div'>/// next one ///</leaf>"
246 : "<leaf level='2' hire=\"me\"><![CDATA[we have a case with ]] within the data]]></leaf>"
247 : "<leaf level='2' width=\"250px\"><![CDATA[we have a case with one ] within the data too]]></leaf>"
248 : "<leaf level='2' height=\"350px\">\"string data\"</leaf>"
249 : "<leaf level='2' depth=\"35px\">'string data'</leaf>"
250 : "<leaf level='2' z=\"-9\">bad & but we keep it quiet</leaf>"
251 : "</ branch >"
252 : "<branch level=\"1\" color='orange' />"
253 : "<branch level=\"1\" color='purple'>"
254 : "here is an equal = which I think works"
255 : "</ branch >"
256 : "</ tree>\n \t \t \n";
257 :
258 2 : basic_xml::node::pointer_t root;
259 2 : basic_xml::parser p(filename, ss, root);
260 1 : CATCH_REQUIRE(root != nullptr);
261 1 : CATCH_REQUIRE(root->tag_name() == "tree");
262 1 : CATCH_REQUIRE(root->all_attributes().empty());
263 :
264 2 : basic_xml::node::pointer_t branch1(root->first_child());
265 1 : CATCH_REQUIRE(branch1->parent() == root);
266 1 : CATCH_REQUIRE(branch1->root() == root);
267 1 : CATCH_REQUIRE(branch1->all_attributes().size() == 1);
268 1 : CATCH_REQUIRE(branch1->attribute("level") == "1");
269 :
270 2 : basic_xml::node::pointer_t leaf1_1(branch1->first_child());
271 1 : CATCH_REQUIRE(leaf1_1->parent() == branch1);
272 1 : CATCH_REQUIRE(leaf1_1->root() == root);
273 1 : CATCH_REQUIRE(leaf1_1->all_attributes().size() == 1);
274 1 : CATCH_REQUIRE(leaf1_1->attribute("level") == "2");
275 1 : CATCH_REQUIRE(leaf1_1->text() == "with \f funny \t text \v and \n white \n spaces and a > for fun");
276 :
277 2 : basic_xml::node::pointer_t leaf1_2(leaf1_1->next());
278 1 : CATCH_REQUIRE(leaf1_2->parent() == branch1);
279 1 : CATCH_REQUIRE(leaf1_2->root() == root);
280 1 : CATCH_REQUIRE(leaf1_2->all_attributes().size() == 1);
281 1 : CATCH_REQUIRE(leaf1_2->attribute("level") == "2");
282 1 : CATCH_REQUIRE(leaf1_2->text() == "-> with \f funny \t text \v and \n white \n spaces <-");
283 :
284 2 : basic_xml::node::pointer_t branch2(branch1->next());
285 1 : CATCH_REQUIRE(branch2->parent() == root);
286 1 : CATCH_REQUIRE(branch2->root() == root);
287 1 : CATCH_REQUIRE(branch2->all_attributes().size() == 1);
288 1 : CATCH_REQUIRE(branch2->attribute("level") == "1");
289 :
290 2 : basic_xml::node::pointer_t leaf2_1(branch2->first_child());
291 1 : CATCH_REQUIRE(leaf2_1->parent() == branch2);
292 1 : CATCH_REQUIRE(leaf2_1->root() == root);
293 1 : CATCH_REQUIRE(leaf2_1->all_attributes().size() == 1);
294 1 : CATCH_REQUIRE(leaf2_1->attribute("level") == "2");
295 1 : CATCH_REQUIRE(leaf2_1->text() == "This one too has some <need< for CDATA >and> such]]]");
296 :
297 2 : basic_xml::node::pointer_t leaf2_2(leaf2_1->next());
298 1 : CATCH_REQUIRE(leaf2_2->parent() == branch2);
299 1 : CATCH_REQUIRE(leaf2_2->root() == root);
300 1 : CATCH_REQUIRE(leaf2_2->all_attributes().size() == 2);
301 1 : CATCH_REQUIRE(leaf2_2->attribute("level") == "2");
302 1 : CATCH_REQUIRE(leaf2_2->attribute("ushes") == "warning");
303 1 : CATCH_REQUIRE(leaf2_2->text() == "=== text starts with equal ===");
304 :
305 2 : basic_xml::node::pointer_t leaf2_3(leaf2_2->next());
306 1 : CATCH_REQUIRE(leaf2_3->parent() == branch2);
307 1 : CATCH_REQUIRE(leaf2_3->root() == root);
308 1 : CATCH_REQUIRE(leaf2_3->all_attributes().size() == 2);
309 1 : CATCH_REQUIRE(leaf2_3->attribute("level") == "2");
310 1 : CATCH_REQUIRE(leaf2_3->attribute("feature") == "on");
311 1 : CATCH_REQUIRE(leaf2_3->text() == ">>> here it is");
312 :
313 2 : basic_xml::node::pointer_t leaf2_4(leaf2_3->next());
314 1 : CATCH_REQUIRE(leaf2_4->parent() == branch2);
315 1 : CATCH_REQUIRE(leaf2_4->root() == root);
316 1 : CATCH_REQUIRE(leaf2_4->all_attributes().size() == 2);
317 1 : CATCH_REQUIRE(leaf2_4->attribute("level") == "2");
318 1 : CATCH_REQUIRE(leaf2_4->attribute("message") == "perfect div");
319 1 : CATCH_REQUIRE(leaf2_4->text() == "/// next one ///");
320 :
321 2 : basic_xml::node::pointer_t leaf2_5(leaf2_4->next());
322 1 : CATCH_REQUIRE(leaf2_5->parent() == branch2);
323 1 : CATCH_REQUIRE(leaf2_5->root() == root);
324 1 : CATCH_REQUIRE(leaf2_5->all_attributes().size() == 2);
325 1 : CATCH_REQUIRE(leaf2_5->attribute("level") == "2");
326 1 : CATCH_REQUIRE(leaf2_5->attribute("hire") == "me");
327 1 : CATCH_REQUIRE(leaf2_5->text() == "we have a case with ]] within the data");
328 :
329 2 : basic_xml::node::pointer_t leaf2_6(leaf2_5->next());
330 1 : CATCH_REQUIRE(leaf2_6->parent() == branch2);
331 1 : CATCH_REQUIRE(leaf2_6->root() == root);
332 1 : CATCH_REQUIRE(leaf2_6->all_attributes().size() == 2);
333 1 : CATCH_REQUIRE(leaf2_6->attribute("level") == "2");
334 1 : CATCH_REQUIRE(leaf2_6->attribute("width") == "250px");
335 1 : CATCH_REQUIRE(leaf2_6->text() == "we have a case with one ] within the data too");
336 :
337 2 : basic_xml::node::pointer_t leaf2_7(leaf2_6->next());
338 1 : CATCH_REQUIRE(leaf2_7->parent() == branch2);
339 1 : CATCH_REQUIRE(leaf2_7->root() == root);
340 1 : CATCH_REQUIRE(leaf2_7->all_attributes().size() == 2);
341 1 : CATCH_REQUIRE(leaf2_7->attribute("level") == "2");
342 1 : CATCH_REQUIRE(leaf2_7->attribute("height") == "350px");
343 1 : CATCH_REQUIRE(leaf2_7->text() == "\"string data\"");
344 :
345 2 : basic_xml::node::pointer_t leaf2_8(leaf2_7->next());
346 1 : CATCH_REQUIRE(leaf2_8->parent() == branch2);
347 1 : CATCH_REQUIRE(leaf2_8->root() == root);
348 1 : CATCH_REQUIRE(leaf2_8->all_attributes().size() == 2);
349 1 : CATCH_REQUIRE(leaf2_8->attribute("level") == "2");
350 1 : CATCH_REQUIRE(leaf2_8->attribute("depth") == "35px");
351 1 : CATCH_REQUIRE(leaf2_8->text() == "'string data'");
352 :
353 2 : basic_xml::node::pointer_t leaf2_9(leaf2_8->next());
354 1 : CATCH_REQUIRE(leaf2_9->parent() == branch2);
355 1 : CATCH_REQUIRE(leaf2_9->root() == root);
356 1 : CATCH_REQUIRE(leaf2_9->all_attributes().size() == 2);
357 1 : CATCH_REQUIRE(leaf2_9->attribute("level") == "2");
358 1 : CATCH_REQUIRE(leaf2_9->attribute("z") == "-9");
359 1 : CATCH_REQUIRE(leaf2_9->text() == "bad & but we keep it quiet");
360 :
361 2 : basic_xml::node::pointer_t branch3(branch2->next());
362 1 : CATCH_REQUIRE(branch3->parent() == root);
363 1 : CATCH_REQUIRE(branch3->root() == root);
364 1 : CATCH_REQUIRE(branch3->all_attributes().size() == 2);
365 1 : CATCH_REQUIRE(branch3->attribute("level") == "1");
366 1 : CATCH_REQUIRE(branch3->attribute("color") == "orange");
367 1 : CATCH_REQUIRE(branch3->text().empty());
368 :
369 2 : basic_xml::node::pointer_t branch4(branch3->next());
370 1 : CATCH_REQUIRE(branch4->parent() == root);
371 1 : CATCH_REQUIRE(branch4->root() == root);
372 1 : CATCH_REQUIRE(branch4->all_attributes().size() == 2);
373 1 : CATCH_REQUIRE(branch4->attribute("level") == "1");
374 1 : CATCH_REQUIRE(branch4->attribute("color") == "purple");
375 1 : CATCH_REQUIRE(branch4->text() == "here is an equal = which I think works");
376 : }
377 : CATCH_END_SECTION()
378 8 : }
379 :
380 :
381 36 : CATCH_TEST_CASE("parser_errors", "[parser][invalid]")
382 : {
383 68 : CATCH_START_SECTION("parser does not like a completely empty input file")
384 : {
385 2 : std::stringstream ss;
386 2 : std::string const filename("empty.xml");
387 :
388 2 : basic_xml::node::pointer_t root;
389 1 : CATCH_REQUIRE_THROWS_MATCHES(
390 : basic_xml::parser(filename, ss, root)
391 : , basic_xml::unexpected_token
392 : , Catch::Matchers::ExceptionMessage(
393 : "xml_error: "
394 : + filename
395 : + ":1: cannot be empty or include anything other than a processor tag and comments before the root tag.", true));
396 : }
397 : CATCH_END_SECTION()
398 :
399 68 : CATCH_START_SECTION("file with only a processor tag")
400 : {
401 2 : std::stringstream ss;
402 2 : std::string const filename("processor.xml");
403 1 : ss << "<?xml version=\"1.0\"?>\n";
404 :
405 2 : basic_xml::node::pointer_t root;
406 1 : CATCH_REQUIRE_THROWS_MATCHES(
407 : basic_xml::parser(filename, ss, root)
408 : , basic_xml::unexpected_token
409 : , Catch::Matchers::ExceptionMessage(
410 : "xml_error: "
411 : + filename
412 : + ":2: cannot be empty or include anything other than a processor tag and comments before the root tag.", true));
413 : }
414 : CATCH_END_SECTION()
415 :
416 68 : CATCH_START_SECTION("file with only a comment tag")
417 : {
418 2 : std::stringstream ss;
419 2 : std::string const filename("comment.xml");
420 1 : ss << "<!-- only a comment... -->\n";
421 :
422 2 : basic_xml::node::pointer_t root;
423 1 : CATCH_REQUIRE_THROWS_MATCHES(
424 : basic_xml::parser(filename, ss, root)
425 : , basic_xml::unexpected_token
426 : , Catch::Matchers::ExceptionMessage(
427 : "xml_error: "
428 : + filename
429 : + ":2: cannot be empty or include anything other than a processor tag and comments before the root tag.", true));
430 : }
431 : CATCH_END_SECTION()
432 :
433 68 : CATCH_START_SECTION("file with only comment and preprocessor tags")
434 : {
435 2 : std::stringstream ss;
436 2 : std::string const filename("comment-and-processor.xml");
437 1 : ss << "<!-- comment at the start... -->\n"
438 : "<?xml version=\"1.0\"?>\n"
439 : "<!-- comment further down... -->\n";
440 :
441 2 : basic_xml::node::pointer_t root;
442 1 : CATCH_REQUIRE_THROWS_MATCHES(
443 : basic_xml::parser(filename, ss, root)
444 : , basic_xml::unexpected_token
445 : , Catch::Matchers::ExceptionMessage(
446 : "xml_error: "
447 : + filename
448 : + ":4: cannot be empty or include anything other than a processor tag and comments before the root tag.", true));
449 : }
450 : CATCH_END_SECTION()
451 :
452 68 : CATCH_START_SECTION("file with two processor tags and a root")
453 : {
454 2 : std::stringstream ss;
455 2 : std::string const filename("processor-and-root.xml");
456 1 : ss << "<?xml version=\"1.0\"?>\n"
457 : "<?php\necho 'invalid code';\n?>\n" // <-- err on this one
458 : "<root>too late</root>\n";
459 :
460 2 : basic_xml::node::pointer_t root;
461 1 : CATCH_REQUIRE_THROWS_MATCHES(
462 : basic_xml::parser(filename, ss, root)
463 : , basic_xml::unexpected_token
464 : , Catch::Matchers::ExceptionMessage(
465 : "xml_error: "
466 : + filename
467 : + ":4: cannot be empty or include anything other than a processor tag and comments before the root tag.", true));
468 : }
469 : CATCH_END_SECTION()
470 :
471 68 : CATCH_START_SECTION("empty root tag")
472 : {
473 2 : std::stringstream ss;
474 2 : std::string const filename("empty-root.xml");
475 1 : ss << "<!-- root can't be empty -->\n"
476 : "<root/>\n";
477 :
478 2 : basic_xml::node::pointer_t root;
479 1 : CATCH_REQUIRE_THROWS_MATCHES(
480 : basic_xml::parser(filename, ss, root)
481 : , basic_xml::unexpected_token
482 : , Catch::Matchers::ExceptionMessage(
483 : "xml_error: "
484 : + filename
485 : + ":2: root tag cannot be an empty tag.", true));
486 : }
487 : CATCH_END_SECTION()
488 :
489 68 : CATCH_START_SECTION("wrong closing tag")
490 : {
491 2 : std::stringstream ss;
492 2 : std::string const filename("empty-root.xml");
493 1 : ss << "<!-- tag mismatch -->\n"
494 : "<root><this>incorrect</that></root>\n";
495 :
496 2 : basic_xml::node::pointer_t root;
497 1 : CATCH_REQUIRE_THROWS_MATCHES(
498 : basic_xml::parser(filename, ss, root)
499 : , basic_xml::unexpected_token
500 : , Catch::Matchers::ExceptionMessage(
501 : "xml_error: "
502 : + filename
503 : + ":2: unexpected token \"that\" in this closing tag; expected \"this\" instead."));
504 : }
505 : CATCH_END_SECTION()
506 :
507 68 : CATCH_START_SECTION("text after closing root tag")
508 : {
509 2 : std::stringstream ss;
510 2 : std::string const filename("extra-text.xml");
511 1 : ss << "<!-- text at the end -->\n"
512 : "<root><sub-tag>all good here</sub-tag></root>but no there\n";
513 :
514 2 : basic_xml::node::pointer_t root;
515 1 : CATCH_REQUIRE_THROWS_MATCHES(
516 : basic_xml::parser(filename, ss, root)
517 : , basic_xml::unexpected_token
518 : , Catch::Matchers::ExceptionMessage(
519 : "xml_error: "
520 : + filename
521 : + ":3: cannot include text data before or after the root tag."));
522 : }
523 : CATCH_END_SECTION()
524 :
525 68 : CATCH_START_SECTION("tag after closing root tag")
526 : {
527 2 : std::stringstream ss;
528 2 : std::string const filename("extra-text.xml");
529 1 : ss << "<!-- text at the end -->\n"
530 : "<root><sub-tag>all good here</sub-tag></root><more>invalid</mode>\n";
531 :
532 2 : basic_xml::node::pointer_t root;
533 1 : CATCH_REQUIRE_THROWS_MATCHES(
534 : basic_xml::parser(filename, ss, root)
535 : , basic_xml::unexpected_token
536 : , Catch::Matchers::ExceptionMessage(
537 : "xml_error: "
538 : + filename
539 : + ":2: we reached the end of the XML file, but still"
540 : " found a token of type 6 after the closing root tag"
541 : " instead of the end of the file."));
542 : }
543 : CATCH_END_SECTION()
544 :
545 68 : CATCH_START_SECTION("not closing root tag")
546 : {
547 2 : std::stringstream ss;
548 2 : std::string const filename("root-still-open.xml");
549 1 : ss << "<!-- text at the end -->\n"
550 : "<root><sub-tag>all good here</sub-tag> <!-- invalid end -->\n";
551 :
552 2 : basic_xml::node::pointer_t root;
553 1 : CATCH_REQUIRE_THROWS_MATCHES(
554 : basic_xml::parser(filename, ss, root)
555 : , basic_xml::unexpected_token
556 : , Catch::Matchers::ExceptionMessage(
557 : "xml_error: "
558 : + filename
559 : + ":3: reached the end of the file without first"
560 : " closing the root tag."));
561 : }
562 : CATCH_END_SECTION()
563 :
564 68 : CATCH_START_SECTION("attribute expects identifier")
565 : {
566 2 : std::stringstream ss;
567 2 : std::string const filename("attribute-identifier.xml");
568 1 : ss << "<root><sub-tag attr=\"good\" =\"bad\">all good here</sub-tag></root>\n";
569 :
570 2 : basic_xml::node::pointer_t root;
571 1 : CATCH_REQUIRE_THROWS_MATCHES(
572 : basic_xml::parser(filename, ss, root)
573 : , basic_xml::invalid_xml
574 : , Catch::Matchers::ExceptionMessage(
575 : "xml_error: "
576 : + filename
577 : + ":1: expected the end of the tag (>) or an attribute name."));
578 : }
579 : CATCH_END_SECTION()
580 :
581 68 : CATCH_START_SECTION("attribute missing equal")
582 : {
583 2 : std::stringstream ss;
584 2 : std::string const filename("attribute-identifier.xml");
585 1 : ss << "<root><sub-tag attr \"bad\">all good here</sub-tag></root>\n";
586 :
587 2 : basic_xml::node::pointer_t root;
588 1 : CATCH_REQUIRE_THROWS_MATCHES(
589 : basic_xml::parser(filename, ss, root)
590 : , basic_xml::invalid_xml
591 : , Catch::Matchers::ExceptionMessage(
592 : "xml_error: "
593 : + filename
594 : + ":1: expected the '=' character between the attribute name and value."));
595 : }
596 : CATCH_END_SECTION()
597 :
598 68 : CATCH_START_SECTION("attribute value must be string")
599 : {
600 2 : std::stringstream ss;
601 2 : std::string const filename("attribute-identifier.xml");
602 1 : ss << "<root><sub-tag attr=bad>quotes are missing...</sub-tag></root>\n";
603 :
604 2 : basic_xml::node::pointer_t root;
605 1 : CATCH_REQUIRE_THROWS_MATCHES(
606 : basic_xml::parser(filename, ss, root)
607 : , basic_xml::invalid_xml
608 : , Catch::Matchers::ExceptionMessage(
609 : "xml_error: "
610 : + filename
611 : + ":1: expected a quoted value after the '=' sign."));
612 : }
613 : CATCH_END_SECTION()
614 :
615 68 : CATCH_START_SECTION("attribute defined twice")
616 : {
617 2 : std::stringstream ss;
618 2 : std::string const filename("attribute-duplicated.xml");
619 1 : ss << "<root><sub-tag attr=\"one\" attr=\"two\">attribute defined twice...</sub-tag></root>\n";
620 :
621 2 : basic_xml::node::pointer_t root;
622 1 : CATCH_REQUIRE_THROWS_MATCHES(
623 : basic_xml::parser(filename, ss, root)
624 : , basic_xml::invalid_xml
625 : , Catch::Matchers::ExceptionMessage(
626 : "xml_error: "
627 : + filename
628 : + ":1: attribute \"attr\" defined twice; we do not allow such."));
629 : }
630 : CATCH_END_SECTION()
631 :
632 68 : CATCH_START_SECTION("processor not ended")
633 : {
634 2 : std::stringstream ss;
635 2 : std::string const filename("attribute-duplicated.xml");
636 1 : ss << "<root><sub-tag><?php echo 'this is legal in PHP, but not for us...';\n";
637 :
638 2 : basic_xml::node::pointer_t root;
639 1 : CATCH_REQUIRE_THROWS_MATCHES(
640 : basic_xml::parser(filename, ss, root)
641 : , basic_xml::unexpected_eof
642 : , Catch::Matchers::ExceptionMessage(
643 : "xml_error: "
644 : + filename
645 : + ":2: reached the end of the file while reading a processor (\"<?...?>\") tag."));
646 : }
647 : CATCH_END_SECTION()
648 :
649 68 : CATCH_START_SECTION("element definitions not supported")
650 : {
651 2 : std::stringstream ss;
652 2 : std::string const filename("element.xml");
653 1 : ss << "<root><sub-tag><!ELEMENT abc></sub-tag></root>\n";
654 :
655 2 : basic_xml::node::pointer_t root;
656 1 : CATCH_REQUIRE_THROWS_MATCHES(
657 : basic_xml::parser(filename, ss, root)
658 : , basic_xml::invalid_xml
659 : , Catch::Matchers::ExceptionMessage(
660 : "xml_error: "
661 : + filename
662 : + ":1: found an element definition (such as an \"<!ELEMENT...>\" sequence), which is not supported."));
663 : }
664 : CATCH_END_SECTION()
665 :
666 68 : CATCH_START_SECTION("bad CDATA introducer")
667 : {
668 2 : std::stringstream ss;
669 2 : std::string const filename("element.xml");
670 1 : ss << "<root><sub-tag><![BAD[abc]]></sub-tag></root>\n";
671 :
672 2 : basic_xml::node::pointer_t root;
673 1 : CATCH_REQUIRE_THROWS_MATCHES(
674 : basic_xml::parser(filename, ss, root)
675 : , basic_xml::invalid_xml
676 : , Catch::Matchers::ExceptionMessage(
677 : "xml_error: "
678 : + filename
679 : + ":1: found an unexpected sequence of character in a \"<![CDATA[...\" sequence."));
680 : }
681 : CATCH_END_SECTION()
682 :
683 68 : CATCH_START_SECTION("EOF before closing CDATA tag")
684 : {
685 2 : std::stringstream ss;
686 2 : std::string const filename("element.xml");
687 1 : ss << "<root><sub-tag><![CDATA[abc]]\n";
688 :
689 2 : basic_xml::node::pointer_t root;
690 1 : CATCH_REQUIRE_THROWS_MATCHES(
691 : basic_xml::parser(filename, ss, root)
692 : , basic_xml::unexpected_eof
693 : , Catch::Matchers::ExceptionMessage(
694 : "xml_error: "
695 : + filename
696 : + ":2: found EOF while parsing a \"<![CDATA[...]]>\" sequence."));
697 : }
698 : CATCH_END_SECTION()
699 :
700 68 : CATCH_START_SECTION("EOF before closing comment tag")
701 : {
702 2 : std::stringstream ss;
703 2 : std::string const filename("element.xml");
704 1 : ss << "<root><sub-tag><!-- file\nends\nwith\ncomment\nopen\n...\n";
705 :
706 2 : basic_xml::node::pointer_t root;
707 1 : CATCH_REQUIRE_THROWS_MATCHES(
708 : basic_xml::parser(filename, ss, root)
709 : , basic_xml::unexpected_eof
710 : , Catch::Matchers::ExceptionMessage(
711 : "xml_error: "
712 : + filename
713 : + ":7: found EOF while parsing a comment (\"<!--...-->\") sequence."));
714 : }
715 : CATCH_END_SECTION()
716 :
717 68 : CATCH_START_SECTION("unknown <!... sequence")
718 : {
719 2 : std::stringstream ss;
720 2 : std::string const filename("element.xml");
721 1 : ss << "<root><sub-tag><!+ unknown +></sub-tag></root>\n";
722 :
723 2 : basic_xml::node::pointer_t root;
724 1 : CATCH_REQUIRE_THROWS_MATCHES(
725 : basic_xml::parser(filename, ss, root)
726 : , basic_xml::invalid_token
727 : , Catch::Matchers::ExceptionMessage(
728 : "xml_error: "
729 : + filename
730 : + ":1: character '+' was not expected after a \"<!\" sequence."));
731 : }
732 : CATCH_END_SECTION()
733 :
734 68 : CATCH_START_SECTION("closing tag missing name")
735 : {
736 2 : std::stringstream ss;
737 2 : std::string const filename("element.xml");
738 1 : ss << "<root><sub-tag>bad closing tag</\n";
739 :
740 2 : basic_xml::node::pointer_t root;
741 1 : CATCH_REQUIRE_THROWS_MATCHES(
742 : basic_xml::parser(filename, ss, root)
743 : , basic_xml::unexpected_eof
744 : , Catch::Matchers::ExceptionMessage(
745 : "xml_error: "
746 : + filename
747 : + ":2: expected a tag name after \"</\", not EOF."));
748 : }
749 : CATCH_END_SECTION()
750 :
751 68 : CATCH_START_SECTION("closing tag not starting with alpha character")
752 : {
753 2 : std::stringstream ss;
754 2 : std::string const filename("element.xml");
755 1 : ss << "<root><sub-tag>bad closing tag</-bad-name->\n";
756 :
757 2 : basic_xml::node::pointer_t root;
758 1 : CATCH_REQUIRE_THROWS_MATCHES(
759 : basic_xml::parser(filename, ss, root)
760 : , basic_xml::invalid_token
761 : , Catch::Matchers::ExceptionMessage(
762 : "xml_error: "
763 : + filename
764 : + ":1: character '-' is not valid for a tag name."));
765 : }
766 : CATCH_END_SECTION()
767 :
768 68 : CATCH_START_SECTION("closing tag missing >")
769 : {
770 2 : std::stringstream ss;
771 2 : std::string const filename("element.xml");
772 1 : ss << "<root><sub-tag>bad closing tag</good-name\n";
773 :
774 2 : basic_xml::node::pointer_t root;
775 1 : CATCH_REQUIRE_THROWS_MATCHES(
776 : basic_xml::parser(filename, ss, root)
777 : , basic_xml::unexpected_eof
778 : , Catch::Matchers::ExceptionMessage(
779 : "xml_error: "
780 : + filename
781 : + ":2: expected '>', not EOF."));
782 : }
783 : CATCH_END_SECTION()
784 :
785 68 : CATCH_START_SECTION("closing tag name followed by ';'")
786 : {
787 2 : std::stringstream ss;
788 2 : std::string const filename("element.xml");
789 1 : ss << "<root><sub-tag>bad closing tag</good-name;>\n";
790 :
791 2 : basic_xml::node::pointer_t root;
792 1 : CATCH_REQUIRE_THROWS_MATCHES(
793 : basic_xml::parser(filename, ss, root)
794 : , basic_xml::invalid_xml
795 : , Catch::Matchers::ExceptionMessage(
796 : "xml_error: "
797 : + filename
798 : + ":1: found an unexpected ';' in a closing tag, expected '>' instead."));
799 : }
800 : CATCH_END_SECTION()
801 :
802 68 : CATCH_START_SECTION("opening tag missing >")
803 : {
804 2 : std::stringstream ss;
805 2 : std::string const filename("element.xml");
806 1 : ss << "<root><\n";
807 :
808 2 : basic_xml::node::pointer_t root;
809 1 : CATCH_REQUIRE_THROWS_MATCHES(
810 : basic_xml::parser(filename, ss, root)
811 : , basic_xml::unexpected_eof
812 : , Catch::Matchers::ExceptionMessage(
813 : "xml_error: "
814 : + filename
815 : + ":2: expected a tag name after '<', not EOF."));
816 : }
817 : CATCH_END_SECTION()
818 :
819 68 : CATCH_START_SECTION("opening tag bad name")
820 : {
821 2 : std::stringstream ss;
822 2 : std::string const filename("element.xml");
823 1 : ss << "<root><{sub-tag>bad opening tag\n";
824 :
825 2 : basic_xml::node::pointer_t root;
826 1 : CATCH_REQUIRE_THROWS_MATCHES(
827 : basic_xml::parser(filename, ss, root)
828 : , basic_xml::invalid_token
829 : , Catch::Matchers::ExceptionMessage(
830 : "xml_error: "
831 : + filename
832 : + ":1: character '{' is not valid for a tag name."));
833 : }
834 : CATCH_END_SECTION()
835 :
836 68 : CATCH_START_SECTION("opening tag name followed by ';'")
837 : {
838 2 : std::stringstream ss;
839 2 : std::string const filename("element.xml");
840 1 : ss << "<root><sub-tag}>bad closing tag\n";
841 :
842 2 : basic_xml::node::pointer_t root;
843 1 : CATCH_REQUIRE_THROWS_MATCHES(
844 : basic_xml::parser(filename, ss, root)
845 : , basic_xml::invalid_token
846 : , Catch::Matchers::ExceptionMessage(
847 : "xml_error: "
848 : + filename
849 : + ":1: character '}' is not valid right after a tag name."));
850 : }
851 : CATCH_END_SECTION()
852 :
853 68 : CATCH_START_SECTION("attribute with '<'")
854 : {
855 2 : std::stringstream ss;
856 2 : std::string const filename("element.xml");
857 1 : ss << "<root><sub-tag name='perfect <tag>'>bad closing tag</sub-tag></root>\n";
858 :
859 2 : basic_xml::node::pointer_t root;
860 1 : CATCH_REQUIRE_THROWS_MATCHES(
861 : basic_xml::parser(filename, ss, root)
862 : , basic_xml::invalid_token
863 : , Catch::Matchers::ExceptionMessage(
864 : "xml_error: "
865 : + filename
866 : + ":1: character '>' not expected inside a tag value; please use \">\" instead."));
867 : }
868 : CATCH_END_SECTION()
869 :
870 68 : CATCH_START_SECTION("empty entity (&;)")
871 : {
872 2 : std::stringstream ss;
873 2 : std::string const filename("element.xml");
874 1 : ss << "<root><sub-tag name='empty entity: &;'>bad closing tag</sub-tag></root>\n";
875 :
876 2 : basic_xml::node::pointer_t root;
877 1 : CATCH_REQUIRE_THROWS_MATCHES(
878 : basic_xml::parser(filename, ss, root)
879 : , basic_xml::invalid_entity
880 : , Catch::Matchers::ExceptionMessage(
881 : "xml_error: "
882 : + filename
883 : + ":1: the name of an entity cannot be empty (\"&;\" is not valid XML)."));
884 : }
885 : CATCH_END_SECTION()
886 :
887 68 : CATCH_START_SECTION("empty numeric entity (&#;)")
888 : {
889 2 : std::stringstream ss;
890 2 : std::string const filename("element.xml");
891 1 : ss << "<root><sub-tag name='empty numeric entity: &#;'>bad closing tag</sub-tag></root>\n";
892 :
893 2 : basic_xml::node::pointer_t root;
894 1 : CATCH_REQUIRE_THROWS_MATCHES(
895 : basic_xml::parser(filename, ss, root)
896 : , basic_xml::invalid_entity
897 : , Catch::Matchers::ExceptionMessage(
898 : "xml_error: "
899 : + filename
900 : + ":1: a numeric entity must have a number (\"&#;\" is not valid XML)."));
901 : }
902 : CATCH_END_SECTION()
903 :
904 68 : CATCH_START_SECTION("invalid number of numeric entity (not decimal)")
905 : {
906 2 : std::stringstream ss;
907 2 : std::string const filename("element.xml");
908 1 : ss << "<root><sub-tag name='empty numeric entity: ab34;'>bad closing tag</sub-tag></root>\n";
909 :
910 2 : basic_xml::node::pointer_t root;
911 1 : CATCH_REQUIRE_THROWS_MATCHES(
912 : basic_xml::parser(filename, ss, root)
913 : , basic_xml::invalid_number
914 : , Catch::Matchers::ExceptionMessage(
915 : "xml_error: "
916 : + filename
917 : + ":1: the number found in numeric entity, \" 12ab34\", is not considered valid."));
918 : }
919 : CATCH_END_SECTION()
920 :
921 68 : CATCH_START_SECTION("invalid number of numeric entity (not hexadecimal)")
922 : {
923 2 : std::stringstream ss;
924 2 : std::string const filename("element.xml");
925 1 : ss << "<root><sub-tag name='empty numeric entity: Ⴋz4;'>bad closing tag</sub-tag></root>\n";
926 :
927 2 : basic_xml::node::pointer_t root;
928 1 : CATCH_REQUIRE_THROWS_MATCHES(
929 : basic_xml::parser(filename, ss, root)
930 : , basic_xml::invalid_number
931 : , Catch::Matchers::ExceptionMessage(
932 : "xml_error: "
933 : + filename
934 : + ":1: the number found in numeric entity, \"0x10abz4\", is not considered valid."));
935 : }
936 : CATCH_END_SECTION()
937 :
938 68 : CATCH_START_SECTION("\"unknown\" entity")
939 : {
940 2 : std::stringstream ss;
941 2 : std::string const filename("element.xml");
942 1 : ss << "<root><sub-tag name='empty numeric entity: &unknown;'>bad closing tag</sub-tag></root>\n";
943 :
944 2 : basic_xml::node::pointer_t root;
945 1 : CATCH_REQUIRE_THROWS_MATCHES(
946 : basic_xml::parser(filename, ss, root)
947 : , basic_xml::invalid_entity
948 : , Catch::Matchers::ExceptionMessage(
949 : "xml_error: "
950 : + filename
951 : + ":1: unsupported entity (\"&unknown;\")."));
952 : }
953 : CATCH_END_SECTION()
954 :
955 68 : CATCH_START_SECTION("separate attributes with a slash")
956 : {
957 2 : std::stringstream ss;
958 2 : std::string const filename("element.xml");
959 1 : ss << "<root><sub-tag name='one' / ignore='two'>bad closing tag</sub-tag></root>\n";
960 :
961 2 : basic_xml::node::pointer_t root;
962 1 : CATCH_REQUIRE_THROWS_MATCHES(
963 : basic_xml::parser(filename, ss, root)
964 : , basic_xml::invalid_xml
965 : , Catch::Matchers::ExceptionMessage(
966 : "xml_error: "
967 : + filename
968 : + ":1: expected the end of the tag (>) or an attribute name."));
969 : }
970 : CATCH_END_SECTION()
971 40 : }
972 :
973 :
974 :
975 : // vim: ts=4 sw=4 et
|