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/node.h>
28 : #include <basic-xml/type.h>
29 :
30 :
31 :
32 4 : CATCH_TEST_CASE("node", "[node][valid]")
33 : {
34 4 : CATCH_START_SECTION("basic node")
35 : {
36 2 : basic_xml::node n("root");
37 1 : CATCH_REQUIRE(n.tag_name() == "root");
38 :
39 1 : CATCH_REQUIRE(n.text() == "");
40 1 : n.append_text("some text");
41 1 : CATCH_REQUIRE(n.text() == "some text");
42 1 : n.append_text(" more text");
43 1 : CATCH_REQUIRE(n.text() == "some text more text");
44 1 : n.set_text(" this new text ");
45 1 : CATCH_REQUIRE(n.text() == "this new text");
46 1 : CATCH_REQUIRE(n.text(false) == " this new text ");
47 1 : n.set_text(" this new text \r\n");
48 1 : CATCH_REQUIRE(n.text() == "this new text");
49 1 : CATCH_REQUIRE(n.text(false) == " this new text \r\n");
50 :
51 1 : CATCH_REQUIRE(n.all_attributes().empty());
52 1 : CATCH_REQUIRE(n.attribute("unknown").empty());
53 1 : n.set_attribute("test", "me");
54 1 : CATCH_REQUIRE(n.attribute("test") == "me");
55 1 : CATCH_REQUIRE(n.attribute("unknown").empty());
56 1 : CATCH_REQUIRE(n.all_attributes().size() == 1);
57 :
58 1 : CATCH_REQUIRE(n.parent() == nullptr);
59 1 : CATCH_REQUIRE(n.first_child() == nullptr);
60 1 : CATCH_REQUIRE(n.last_child() == nullptr);
61 1 : CATCH_REQUIRE(n.next() == nullptr);
62 1 : CATCH_REQUIRE(n.previous() == nullptr);
63 : }
64 : CATCH_END_SECTION()
65 :
66 4 : CATCH_START_SECTION("node tree")
67 : {
68 2 : basic_xml::node::pointer_t root(std::make_shared<basic_xml::node>("root"));
69 1 : CATCH_REQUIRE(root->tag_name() == "root");
70 :
71 : // level 1
72 : //
73 2 : basic_xml::node::pointer_t l1c1(std::make_shared<basic_xml::node>("l1c1"));
74 1 : CATCH_REQUIRE(l1c1->tag_name() == "l1c1");
75 :
76 2 : basic_xml::node::pointer_t l1c2(std::make_shared<basic_xml::node>("l1c2"));
77 1 : CATCH_REQUIRE(l1c2->tag_name() == "l1c2");
78 :
79 2 : basic_xml::node::pointer_t l1c3(std::make_shared<basic_xml::node>("l1c3"));
80 1 : CATCH_REQUIRE(l1c3->tag_name() == "l1c3");
81 :
82 : // append l1c1
83 : //
84 1 : root->append_child(l1c1);
85 1 : CATCH_REQUIRE(l1c1->parent() == root);
86 1 : CATCH_REQUIRE(l1c1->first_child() == nullptr);
87 1 : CATCH_REQUIRE(l1c1->last_child() == nullptr);
88 1 : CATCH_REQUIRE(l1c1->next() == nullptr);
89 1 : CATCH_REQUIRE(l1c1->previous() == nullptr);
90 :
91 1 : CATCH_REQUIRE(root->parent() == nullptr);
92 1 : CATCH_REQUIRE(root->first_child() == l1c1);
93 1 : CATCH_REQUIRE(root->last_child() == l1c1);
94 1 : CATCH_REQUIRE(root->next() == nullptr);
95 1 : CATCH_REQUIRE(root->previous() == nullptr);
96 :
97 : // append l1c2
98 : //
99 1 : root->append_child(l1c2);
100 1 : CATCH_REQUIRE(l1c2->parent() == root);
101 1 : CATCH_REQUIRE(l1c2->first_child() == nullptr);
102 1 : CATCH_REQUIRE(l1c2->last_child() == nullptr);
103 1 : CATCH_REQUIRE(l1c2->next() == nullptr);
104 1 : CATCH_REQUIRE(l1c2->previous() == l1c1);
105 :
106 1 : CATCH_REQUIRE(l1c1->next() == l1c2);
107 1 : CATCH_REQUIRE(l1c1->previous() == nullptr);
108 :
109 1 : CATCH_REQUIRE(root->parent() == nullptr);
110 1 : CATCH_REQUIRE(root->first_child() == l1c1);
111 1 : CATCH_REQUIRE(root->last_child() == l1c2);
112 1 : CATCH_REQUIRE(root->next() == nullptr);
113 1 : CATCH_REQUIRE(root->previous() == nullptr);
114 :
115 : // append l1c3
116 : //
117 1 : root->append_child(l1c3);
118 1 : CATCH_REQUIRE(l1c3->parent() == root);
119 1 : CATCH_REQUIRE(l1c3->first_child() == nullptr);
120 1 : CATCH_REQUIRE(l1c3->last_child() == nullptr);
121 1 : CATCH_REQUIRE(l1c3->next() == nullptr);
122 1 : CATCH_REQUIRE(l1c3->previous() == l1c2);
123 :
124 1 : CATCH_REQUIRE(l1c1->next() == l1c2);
125 1 : CATCH_REQUIRE(l1c1->previous() == nullptr);
126 :
127 1 : CATCH_REQUIRE(l1c2->next() == l1c3);
128 1 : CATCH_REQUIRE(l1c2->previous() == l1c1);
129 :
130 1 : CATCH_REQUIRE(root->parent() == nullptr);
131 1 : CATCH_REQUIRE(root->first_child() == l1c1);
132 1 : CATCH_REQUIRE(root->last_child() == l1c3);
133 1 : CATCH_REQUIRE(root->next() == nullptr);
134 1 : CATCH_REQUIRE(root->previous() == nullptr);
135 :
136 : // level 2
137 : //
138 2 : basic_xml::node::pointer_t l2c1(std::make_shared<basic_xml::node>("l2c1"));
139 1 : CATCH_REQUIRE(l2c1->tag_name() == "l2c1");
140 :
141 2 : basic_xml::node::pointer_t l2c2(std::make_shared<basic_xml::node>("l2c2"));
142 1 : CATCH_REQUIRE(l2c2->tag_name() == "l2c2");
143 :
144 2 : basic_xml::node::pointer_t l2c3(std::make_shared<basic_xml::node>("l2c3"));
145 1 : CATCH_REQUIRE(l2c3->tag_name() == "l2c3");
146 :
147 : // append l2c1
148 : //
149 1 : l1c2->append_child(l2c1);
150 1 : CATCH_REQUIRE(l2c1->parent() == l1c2);
151 1 : CATCH_REQUIRE(l2c1->parent()->parent() == root);
152 1 : CATCH_REQUIRE(l2c1->first_child() == nullptr);
153 1 : CATCH_REQUIRE(l2c1->last_child() == nullptr);
154 1 : CATCH_REQUIRE(l2c1->next() == nullptr);
155 1 : CATCH_REQUIRE(l2c1->previous() == nullptr);
156 :
157 1 : CATCH_REQUIRE(root->parent() == nullptr);
158 1 : CATCH_REQUIRE(root->first_child() == l1c1);
159 1 : CATCH_REQUIRE(root->last_child() == l1c3);
160 1 : CATCH_REQUIRE(root->next() == nullptr);
161 1 : CATCH_REQUIRE(root->previous() == nullptr);
162 :
163 : // append l2c2
164 : //
165 1 : l1c2->append_child(l2c2);
166 1 : CATCH_REQUIRE(l2c2->parent() == l1c2);
167 1 : CATCH_REQUIRE(l2c2->parent()->parent() == root);
168 1 : CATCH_REQUIRE(l2c2->first_child() == nullptr);
169 1 : CATCH_REQUIRE(l2c2->last_child() == nullptr);
170 1 : CATCH_REQUIRE(l2c2->next() == nullptr);
171 1 : CATCH_REQUIRE(l2c2->previous() == l2c1);
172 :
173 1 : CATCH_REQUIRE(l2c1->next() == l2c2);
174 1 : CATCH_REQUIRE(l2c1->previous() == nullptr);
175 :
176 1 : CATCH_REQUIRE(root->parent() == nullptr);
177 1 : CATCH_REQUIRE(root->first_child() == l1c1);
178 1 : CATCH_REQUIRE(root->last_child() == l1c3);
179 1 : CATCH_REQUIRE(root->next() == nullptr);
180 1 : CATCH_REQUIRE(root->previous() == nullptr);
181 :
182 : // append l2c3
183 : //
184 1 : l1c2->append_child(l2c3);
185 1 : CATCH_REQUIRE(l2c3->parent() == l1c2);
186 1 : CATCH_REQUIRE(l2c3->parent()->parent() == root);
187 1 : CATCH_REQUIRE(l2c3->first_child() == nullptr);
188 1 : CATCH_REQUIRE(l2c3->last_child() == nullptr);
189 1 : CATCH_REQUIRE(l2c3->next() == nullptr);
190 1 : CATCH_REQUIRE(l2c3->previous() == l2c2);
191 :
192 1 : CATCH_REQUIRE(l2c1->next() == l2c2);
193 1 : CATCH_REQUIRE(l2c1->previous() == nullptr);
194 :
195 1 : CATCH_REQUIRE(l2c2->next() == l2c3);
196 1 : CATCH_REQUIRE(l2c2->previous() == l2c1);
197 :
198 1 : CATCH_REQUIRE(root->parent() == nullptr);
199 1 : CATCH_REQUIRE(root->first_child() == l1c1);
200 1 : CATCH_REQUIRE(root->last_child() == l1c3);
201 1 : CATCH_REQUIRE(root->next() == nullptr);
202 1 : CATCH_REQUIRE(root->previous() == nullptr);
203 : }
204 : CATCH_END_SECTION()
205 2 : }
206 :
207 :
208 :
209 5 : CATCH_TEST_CASE("node_output", "[node][valid]")
210 : {
211 6 : CATCH_START_SECTION("convert string with entities")
212 : {
213 1 : CATCH_REQUIRE(basic_xml::convert_to_entity("nothing to convert", "<>&") == "nothing to convert");
214 :
215 1 : CATCH_REQUIRE(basic_xml::convert_to_entity("try < and > and & and \" and ' all should change", "<>&\"'") == "try < and > and & and " and ' all should change");
216 :
217 1 : CATCH_REQUIRE(basic_xml::convert_to_entity("try < and > and & but keep \" and '", "<>&") == "try < and > and & but keep \" and '");
218 : }
219 : CATCH_END_SECTION()
220 :
221 6 : CATCH_START_SECTION("node output")
222 : {
223 2 : basic_xml::node n("root");
224 1 : CATCH_REQUIRE(n.tag_name() == "root");
225 :
226 1 : n.append_text("with text");
227 1 : n.set_attribute("simple-attribute", "with some value");
228 :
229 2 : std::stringstream ss;
230 1 : ss << n;
231 1 : CATCH_REQUIRE(ss.str() == "<root simple-attribute=\"with some value\">with text</root>");
232 : }
233 : CATCH_END_SECTION()
234 :
235 6 : CATCH_START_SECTION("node tree output")
236 : {
237 2 : basic_xml::node::pointer_t root(std::make_shared<basic_xml::node>("root"));
238 1 : root->set_attribute("even-root", "can have an \"attribute\"");
239 2 : basic_xml::node::pointer_t l1c1(std::make_shared<basic_xml::node>("l1c1"));
240 2 : basic_xml::node::pointer_t l1c2(std::make_shared<basic_xml::node>("l1c2"));
241 1 : l1c2->append_text("wierd text + sub-tags");
242 2 : basic_xml::node::pointer_t l1c3(std::make_shared<basic_xml::node>("l1c3"));
243 1 : l1c3->append_text("<\"it's not empty & it works\">");
244 1 : root->append_child(l1c1);
245 1 : root->append_child(l1c2);
246 1 : root->append_child(l1c3);
247 2 : basic_xml::node::pointer_t l2c1(std::make_shared<basic_xml::node>("l2c1"));
248 1 : l2c1->set_attribute("color", "l'escargot");
249 1 : l2c1->append_text("sub-node has data");
250 2 : basic_xml::node::pointer_t l2c2(std::make_shared<basic_xml::node>("l2c2"));
251 2 : basic_xml::node::pointer_t l2c3(std::make_shared<basic_xml::node>("l2c3"));
252 1 : l2c3->set_attribute("i33", "\"it's both this time\"");
253 1 : l2c3->append_text("last-node has data too");
254 1 : l1c2->append_child(l2c1);
255 1 : l1c2->append_child(l2c2);
256 1 : l1c2->append_child(l2c3);
257 :
258 2 : std::stringstream ss;
259 1 : ss << *root;
260 1 : CATCH_REQUIRE(ss.str() == "<root even-root='can have an \"attribute\"'>"
261 : "<l1c1/><l1c2><l2c1 color=\"l'escargot\">sub-node has data</l2c1>"
262 : "<l2c2/><l2c3 i33=\""it's both this time"\">"
263 : "last-node has data too</l2c3>wierd text + sub-tags</l1c2>"
264 : "<l1c3><\"it's not empty & it works\"></l1c3></root>");
265 : }
266 : CATCH_END_SECTION()
267 3 : }
268 :
269 :
270 :
271 6 : CATCH_TEST_CASE("node_errors", "[node][invalid]")
272 : {
273 8 : CATCH_START_SECTION("bad tag name: empty")
274 : {
275 1 : CATCH_REQUIRE_THROWS_MATCHES(
276 : basic_xml::node(std::string())
277 : , basic_xml::invalid_token
278 : , Catch::Matchers::ExceptionMessage(
279 : "xml_error: \"\" is not a valid token for a tag name."));
280 : }
281 : CATCH_END_SECTION()
282 :
283 8 : CATCH_START_SECTION("bad tag name: invalid character in name")
284 : {
285 11 : for(int count(0); count < 10; ++count)
286 : {
287 20 : std::string tag_name;
288 10 : int max(rand() % 10 + 10);
289 160 : for(int len(0); len < max; ++len)
290 : {
291 150 : switch(rand() % (tag_name.empty() ? 3 : 5))
292 : {
293 40 : case 0:
294 40 : tag_name += static_cast<char>(rand() % 26 + 'a');
295 40 : break;
296 :
297 29 : case 1:
298 29 : tag_name += static_cast<char>(rand() % 26 + 'A');
299 29 : break;
300 :
301 28 : case 2:
302 28 : tag_name += '_';
303 28 : break;
304 :
305 27 : case 3:
306 27 : tag_name += static_cast<char>(rand() % 10 + '0');
307 27 : break;
308 :
309 26 : case 4:
310 26 : tag_name += '-';
311 26 : break;
312 :
313 : }
314 : }
315 :
316 : // invalid if it does not start with an alpha character
317 : //
318 10 : char start(rand() % 255 + 1);
319 16 : while(basic_xml::is_alpha(start))
320 : {
321 3 : start = rand() % 255 + 1;
322 : }
323 10 : CATCH_REQUIRE_THROWS_MATCHES(
324 : basic_xml::node(start + tag_name)
325 : , basic_xml::invalid_token
326 : , Catch::Matchers::ExceptionMessage(
327 : "xml_error: \""
328 : + (start + tag_name)
329 : + "\" is not a valid token for a tag name."));
330 :
331 : // invalid if it ends with '-'
332 : //
333 10 : CATCH_REQUIRE_THROWS_MATCHES(
334 : basic_xml::node(tag_name + '-')
335 : , basic_xml::invalid_token
336 : , Catch::Matchers::ExceptionMessage(
337 : "xml_error: \""
338 : + tag_name
339 : + "-\" is not a valid token for a tag name."));
340 :
341 : // invalid if invalid char anywhere within
342 : //
343 170 : for(size_t pos(0); pos <= tag_name.length(); ++pos)
344 : {
345 160 : char mid(rand() % 255 + 1);
346 266 : while(basic_xml::is_alpha(mid) || basic_xml::is_digit(mid))
347 : {
348 53 : mid = rand() % 255 + 1;
349 : }
350 160 : std::string bad_name(
351 320 : tag_name.substr(0, pos)
352 480 : + mid
353 640 : + tag_name.substr(pos));
354 160 : CATCH_REQUIRE_THROWS_MATCHES(
355 : basic_xml::node(bad_name)
356 : , basic_xml::invalid_token
357 : , Catch::Matchers::ExceptionMessage(
358 : "xml_error: \""
359 : + bad_name
360 : + "\" is not a valid token for a tag name."));
361 : }
362 : }
363 : }
364 : CATCH_END_SECTION()
365 :
366 8 : CATCH_START_SECTION("bad attribute name: invalid character in name")
367 : {
368 2 : basic_xml::node n("tag");
369 :
370 11 : for(int count(0); count < 10; ++count)
371 : {
372 20 : std::string attribute_name;
373 10 : int max(rand() % 10 + 10);
374 146 : for(int len(0); len < max; ++len)
375 : {
376 136 : switch(rand() % (attribute_name.empty() ? 3 : 5))
377 : {
378 25 : case 0:
379 25 : attribute_name += static_cast<char>(rand() % 26 + 'a');
380 25 : break;
381 :
382 37 : case 1:
383 37 : attribute_name += static_cast<char>(rand() % 26 + 'A');
384 37 : break;
385 :
386 25 : case 2:
387 25 : attribute_name += '_';
388 25 : break;
389 :
390 27 : case 3:
391 27 : attribute_name += static_cast<char>(rand() % 10 + '0');
392 27 : break;
393 :
394 22 : case 4:
395 22 : attribute_name += '-';
396 22 : break;
397 :
398 : }
399 : }
400 :
401 : // invalid if it does not start with an alpha character
402 : //
403 10 : char start(rand() % 255 + 1);
404 14 : while(basic_xml::is_alpha(start))
405 : {
406 2 : start = rand() % 255 + 1;
407 : }
408 10 : CATCH_REQUIRE_THROWS_MATCHES(
409 : n.set_attribute(start + attribute_name, "value")
410 : , basic_xml::invalid_token
411 : , Catch::Matchers::ExceptionMessage(
412 : "xml_error: \""
413 : + (start + attribute_name)
414 : + "\" is not a valid token for an attribute name."));
415 :
416 : // invalid if it ends with '-'
417 : //
418 10 : CATCH_REQUIRE_THROWS_MATCHES(
419 : n.set_attribute(attribute_name + '-', "value")
420 : , basic_xml::invalid_token
421 : , Catch::Matchers::ExceptionMessage(
422 : "xml_error: \""
423 : + attribute_name
424 : + "-\" is not a valid token for an attribute name."));
425 :
426 : // invalid if invalid char anywhere within
427 : //
428 156 : for(size_t pos(0); pos <= attribute_name.length(); ++pos)
429 : {
430 146 : char mid(rand() % 255 + 1);
431 248 : while(basic_xml::is_alpha(mid) || basic_xml::is_digit(mid))
432 : {
433 51 : mid = rand() % 255 + 1;
434 : }
435 146 : std::string bad_name(
436 292 : attribute_name.substr(0, pos)
437 438 : + mid
438 584 : + attribute_name.substr(pos));
439 146 : CATCH_REQUIRE_THROWS_MATCHES(
440 : n.set_attribute(bad_name, "value")
441 : , basic_xml::invalid_token
442 : , Catch::Matchers::ExceptionMessage(
443 : "xml_error: \""
444 : + bad_name
445 : + "\" is not a valid token for an attribute name."));
446 : }
447 : }
448 : }
449 : CATCH_END_SECTION()
450 :
451 8 : CATCH_START_SECTION("re-append a child fails")
452 : {
453 2 : basic_xml::node::pointer_t root(std::make_shared<basic_xml::node>("top"));
454 :
455 2 : basic_xml::node::pointer_t l1c1(std::make_shared<basic_xml::node>("l1c1"));
456 2 : basic_xml::node::pointer_t l1c2(std::make_shared<basic_xml::node>("l1c2"));
457 2 : basic_xml::node::pointer_t l1c3(std::make_shared<basic_xml::node>("l1c3"));
458 :
459 1 : root->append_child(l1c1);
460 1 : root->append_child(l1c2);
461 1 : root->append_child(l1c3);
462 :
463 1 : CATCH_REQUIRE_THROWS_MATCHES(
464 : l1c1->append_child(l1c2)
465 : , basic_xml::node_already_in_tree
466 : , Catch::Matchers::ExceptionMessage(
467 : "xml_error: Somehow you are trying to add a child node of a node that was already added to a tree of nodes."));
468 :
469 1 : CATCH_REQUIRE_THROWS_MATCHES(
470 : l1c2->append_child(l1c1)
471 : , basic_xml::node_already_in_tree
472 : , Catch::Matchers::ExceptionMessage(
473 : "xml_error: Somehow you are trying to add a child node of a node that was already added to a tree of nodes."));
474 :
475 1 : CATCH_REQUIRE_THROWS_MATCHES(
476 : l1c1->append_child(l1c3)
477 : , basic_xml::node_already_in_tree
478 : , Catch::Matchers::ExceptionMessage(
479 : "xml_error: Somehow you are trying to add a child node of a node that was already added to a tree of nodes."));
480 :
481 1 : CATCH_REQUIRE_THROWS_MATCHES(
482 : l1c1->append_child(root)
483 : , basic_xml::node_is_root
484 : , Catch::Matchers::ExceptionMessage(
485 : "xml_error: Trying to append the root node within the sub-tree."));
486 :
487 2 : basic_xml::node::pointer_t l2c1(std::make_shared<basic_xml::node>("l2c1"));
488 2 : basic_xml::node::pointer_t l2c2(std::make_shared<basic_xml::node>("l2c2"));
489 2 : basic_xml::node::pointer_t l2c3(std::make_shared<basic_xml::node>("l2c3"));
490 :
491 1 : l1c3->append_child(l2c1);
492 1 : l1c3->append_child(l2c2);
493 1 : l1c3->append_child(l2c3);
494 :
495 1 : CATCH_REQUIRE_THROWS_MATCHES(
496 : l2c1->append_child(root)
497 : , basic_xml::node_is_root
498 : , Catch::Matchers::ExceptionMessage(
499 : "xml_error: Trying to append the root node within the sub-tree."));
500 :
501 1 : CATCH_REQUIRE_THROWS_MATCHES(
502 : l2c2->append_child(root)
503 : , basic_xml::node_is_root
504 : , Catch::Matchers::ExceptionMessage(
505 : "xml_error: Trying to append the root node within the sub-tree."));
506 :
507 1 : CATCH_REQUIRE_THROWS_MATCHES(
508 : l2c3->append_child(root)
509 : , basic_xml::node_is_root
510 : , Catch::Matchers::ExceptionMessage(
511 : "xml_error: Trying to append the root node within the sub-tree."));
512 : }
513 : CATCH_END_SECTION()
514 10 : }
515 :
516 :
517 : // vim: ts=4 sw=4 et
|