The Battle for Wesnoth  1.19.8+dev
test_help_markup.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2024
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-test"
16 
17 #include <boost/test/unit_test.hpp>
18 
19 #include "config.hpp"
20 #include "serialization/markup.hpp"
21 
22 BOOST_AUTO_TEST_SUITE( help_markup )
23 
24 BOOST_AUTO_TEST_CASE( test_simple_help_markup )
25 {
26  // This tests markup with no tags.
27  config output;
28 
29  // Text parses as text
30  output = markup::parse_text("Hello World");
31  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
32  BOOST_CHECK(output.has_child("text"));
33  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
34  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Hello World");
35 
36  // Backslashes protect the following character even if it's special
37  output = markup::parse_text(R"==(<not_a_tag>)==");
38  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
39  BOOST_CHECK(output.has_child("text"));
40  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
41  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "<not_a_tag>");
42 
43  // Simple named character entities are substituted
44  output = markup::parse_text("Me &amp; You");
45  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
46  BOOST_CHECK(output.has_child("text"));
47  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
48  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Me & You");
49 
50  // Decimal character entities work for single-byte characters
51  output = markup::parse_text("&#198;");
52  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
53  BOOST_CHECK(output.has_child("text"));
54  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
55  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\u00c6");
56 
57  // Hex character entities work for single-byte characters
58  output = markup::parse_text("&#xc6;");
59  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
60  BOOST_CHECK(output.has_child("text"));
61  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
62  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\u00c6");
63 
64  // Decimal character entities work for two-byte characters
65  output = markup::parse_text("&#5792;");
66  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
67  BOOST_CHECK(output.has_child("text"));
68  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
69  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\u16a0");
70 
71  // Hex character entities work for two-byte characters
72  output = markup::parse_text("&#x16a0;");
73  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
74  BOOST_CHECK(output.has_child("text"));
75  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
76  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\u16a0");
77 
78  // Decimal character entities work for non-BMP characters
79  output = markup::parse_text("&#128519;");
80  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
81  BOOST_CHECK(output.has_child("text"));
82  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
83  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\U0001f607");
84 
85  // Hex character entities work for non-BMP characters
86  output = markup::parse_text("&#x1f607;");
87  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
88  BOOST_CHECK(output.has_child("text"));
89  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
90  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\U0001f607");
91 
92  // Single newlines are taken literally
93  output = markup::parse_text("Line One\nLine Two");
94  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
95  BOOST_CHECK(output.has_child("text"));
96  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
97  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Line One\nLine Two");
98 
99  // Double newlines split into paragraphs
100  output = markup::parse_text("Paragraph One\n\nParagraph Two");
101  BOOST_CHECK_EQUAL(output.all_children_count(), 2);
102  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
103  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
104  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Paragraph One");
105  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "Paragraph Two");
106 
107  // TODO: What about triple, quadruple newlines?
108 
109  // Unknown named character entities are processed but not substituted
110  output = markup::parse_text("This &entity; is unknown!");
111  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
112  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
113  BOOST_CHECK(output.has_child("character_entity"));
114  BOOST_CHECK_EQUAL(output.find_total_first_of("character_entity"), 1);
115  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
116  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "This ");
117  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
118  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " is unknown!");
119  BOOST_CHECK(output.mandatory_child("character_entity").has_attribute("name"));
120  BOOST_CHECK_EQUAL(output.mandatory_child("character_entity")["name"], "entity");
121 
122  // A backslash at end-of-stream is literal
123  output = markup::parse_text(R"==(Ending with backslash\)==");
124  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
125  BOOST_CHECK(output.has_child("text"));
126  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
127  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], R"==(Ending with backslash\)==");
128 
129  // A backslash can escape itself
130  output = markup::parse_text(R"==(Backslash\\in middle)==");
131  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
132  BOOST_CHECK(output.has_child("text"));
133  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
134  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], R"==(Backslash\in middle)==");
135 
136  // A backslash is removed even if the escaped character is not special
137  output = markup::parse_text(R"==(\T\h\i\s is \p\o\i\n\t\l\e\s\s)==");
138  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
139  BOOST_CHECK(output.has_child("text"));
140  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
141  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "This is pointless");
142 
143  // The other four simple named character entities are substituted
144  output = markup::parse_text("&quot;&lt;tag attr=&apos;val&apos;&gt;&quot;");
145  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
146  BOOST_CHECK(output.has_child("text"));
147  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
148  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], R"==("<tag attr='val'>")==");
149 }
150 
151 BOOST_AUTO_TEST_CASE( test_help_markup_old )
152 {
153  // This tests strings using old-style help markup tags
154  config output;
155 
156  // A simple tag with text content
157  // This format is both old-style and new-style.
158  output = markup::parse_text("<tt>some text</tt>");
159  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
160  BOOST_CHECK(output.has_child("tt"));
161  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
162  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
163 
164  // With explicit text attribute
165  output = markup::parse_text("<tt>text='some text'</tt>");
166  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
167  BOOST_CHECK(output.has_child("tt"));
168  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
169  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
170 
171  // With implicit text attribute and another attribute
172  output = markup::parse_text("<tt>attr='value' some text</tt>");
173  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
174  BOOST_CHECK(output.has_child("tt"));
175  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
176  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
177  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
178  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
179 
180  // With explict text attribute and another attribute
181  output = markup::parse_text("<tt>attr='value' text='some text'</tt>");
182  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
183  BOOST_CHECK(output.has_child("tt"));
184  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
185  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
186  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
187  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
188 
189  // A tag in a larger span of text
190  output = markup::parse_text("Here we have <tt>attr='value' text='some text'</tt> with an unknown style applied!");
191  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
192  BOOST_CHECK(output.has_child("tt"));
193  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
194  BOOST_CHECK_EQUAL(output.find_total_first_of("tt"), 1);
195  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
196  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
197  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
198  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
199  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
200  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Here we have ");
201  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
202  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " with an unknown style applied!");
203 
204  // The attribute value can be unquoted
205  output = markup::parse_text("<tt>attr=value</tt>");
206  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
207  BOOST_CHECK(output.has_child("tt"));
208  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
209  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
210 
211  // Nonalphanumeric characters don't need to be quoted as long as they're not special
212  output = markup::parse_text("<tt>attr=!@#$%^</tt>");
213  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
214  BOOST_CHECK(output.has_child("tt"));
215  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
216  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "!@#$%^");
217 
218  // Quoting with single quotes
219  output = markup::parse_text("<tt>attr='value with spaces'</tt>");
220  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
221  BOOST_CHECK(output.has_child("tt"));
222  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
223  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value with spaces");
224 
225  // Quoting with double quotes
226  output = markup::parse_text(R"==(<tt>attr="value with spaces"</tt>)==");
227  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
228  BOOST_CHECK(output.has_child("tt"));
229  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
230  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value with spaces");
231 
232  // Quotes only count as quotes if they're the first non-whitespace character after the =
233  output = markup::parse_text("<tt>attr=O'Brien</tt>");
234  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
235  BOOST_CHECK(output.has_child("tt"));
236  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
237  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "O'Brien");
238 
239  // Single quotes in double-quoted value
240  output = markup::parse_text(R"==(<tt>attr="'tis futile"</tt>)==");
241  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
242  BOOST_CHECK(output.has_child("tt"));
243  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
244  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "'tis futile");
245 
246  // Double quotes in single-quoted value
247  output = markup::parse_text(R"==(<tt>attr='the "mega" test'</tt>)==");
248  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
249  BOOST_CHECK(output.has_child("tt"));
250  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
251  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], R"==(the "mega" test)==");
252 
253  // Spaces around the equals are allowed
254  output = markup::parse_text("<tt>attr = value</tt>");
255  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
256  BOOST_CHECK(output.has_child("tt"));
257  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
258  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
259 
260  // Newlines are also allowed
261  output = markup::parse_text("<tt>attr=\nvalue\nthat=\nthis</tt>");
262  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
263  BOOST_CHECK(output.has_child("tt"));
264  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
265  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
266  BOOST_CHECK(output.mandatory_child("tt").has_attribute("that"));
267  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["that"], "this");
268 
269  // Escaping a single quote in a single-quoted value
270  output = markup::parse_text(R"==(<tt>attr='Let\'s go?'</tt>)==");
271  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
272  BOOST_CHECK(output.has_child("tt"));
273  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
274  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "Let's go?");
275 
276  // Using a simple character entity in a single-quoted value
277  output = markup::parse_text("<tt>attr='&apos;tis good'</tt>");
278  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
279  BOOST_CHECK(output.has_child("tt"));
280  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
281  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "'tis good");
282 
283  // A newline in a single-quoted value
284  output = markup::parse_text("<tt>attr='Line 1\nLine 2'</tt>");
285  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
286  BOOST_CHECK(output.has_child("tt"));
287  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
288  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "Line 1\nLine 2");
289 
290  // Using a simple character entity in a double-quoted value
291  output = markup::parse_text(R"==(<tt>attr="&quot;Yes!&quot;"</tt>)==");
292  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
293  BOOST_CHECK(output.has_child("tt"));
294  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
295  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], R"==("Yes!")==");
296 }
297 
298 BOOST_AUTO_TEST_CASE( test_help_markup_new )
299 {
300  // This tests strings using new-style help markup tags
301  config output;
302 
303  // A simple tag with text content
304  // This format is both old-style and new-style.
305  output = markup::parse_text("<tt>some text</tt>");
306  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
307  BOOST_CHECK(output.has_child("tt"));
308  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
309  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
310 
311  // A simple auto-closed tag
312  output = markup::parse_text("<tt/>");
313  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
314  BOOST_CHECK(output.has_child("tt"));
315  BOOST_CHECK(!output.mandatory_child("tt").has_attribute("text"));
316 
317  // Auto-closed tag can have a space before the slash
318  output = markup::parse_text("<tt />");
319  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
320  BOOST_CHECK(output.has_child("tt"));
321  BOOST_CHECK(!output.mandatory_child("tt").has_attribute("text"));
322 
323  // With an attribute
324  output = markup::parse_text("<tt attr='value'>some text</tt>");
325  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
326  BOOST_CHECK(output.has_child("tt"));
327  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
328  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
329  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
330  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
331 
332  // With an attribute that has no value specified
333  output = markup::parse_text("<tt attr>some text</tt>");
334  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
335  BOOST_CHECK(output.has_child("tt"));
336  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
337  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
338  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
339  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "");
340 
341  // A tag in a larger span of text
342  output = markup::parse_text("Here we have <tt attr='value'>some text</tt> with an unknown style applied!");
343  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
344  BOOST_CHECK(output.has_child("tt"));
345  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
346  BOOST_CHECK_EQUAL(output.find_total_first_of("tt"), 1);
347  BOOST_CHECK(output.mandatory_child("tt").has_attribute("text"));
348  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["text"], "some text");
349  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
350  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
351  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
352  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Here we have ");
353  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
354  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " with an unknown style applied!");
355 
356  // The attribute value can be unquoted
357  output = markup::parse_text("<tt attr=value/>");
358  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
359  BOOST_CHECK(output.has_child("tt"));
360  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
361  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
362 
363  // Nonalphanumeric characters don't need to be quoted as long as they're not special
364  output = markup::parse_text("<tt attr=!@#$%^/>");
365  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
366  BOOST_CHECK(output.has_child("tt"));
367  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
368  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "!@#$%^");
369 
370  // Quoting with single quotes
371  output = markup::parse_text("<tt attr='value with spaces'/>");
372  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
373  BOOST_CHECK(output.has_child("tt"));
374  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
375  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value with spaces");
376 
377  // Quoting with double quotes
378  output = markup::parse_text(R"==(<tt attr="value with spaces"/>)==");
379  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
380  BOOST_CHECK(output.has_child("tt"));
381  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
382  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value with spaces");
383 
384  // Quotes only count as quotes if they're the first non-whitespace character after the =
385  output = markup::parse_text("<tt attr=O'Brien/>");
386  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
387  BOOST_CHECK(output.has_child("tt"));
388  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
389  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "O'Brien");
390 
391  // Single quotes in double-quoted value
392  output = markup::parse_text(R"==(<tt attr="'tis futile"/>)==");
393  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
394  BOOST_CHECK(output.has_child("tt"));
395  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
396  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "'tis futile");
397 
398  // Double quotes in single-quoted value
399  output = markup::parse_text(R"==(<tt attr='the "mega" test'/>)==");
400  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
401  BOOST_CHECK(output.has_child("tt"));
402  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
403  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], R"==(the "mega" test)==");
404 
405  // Spaces around the equals are allowed
406  output = markup::parse_text("<tt attr = value/>");
407  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
408  BOOST_CHECK(output.has_child("tt"));
409  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
410  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
411 
412  // Newlines are also allowed
413  output = markup::parse_text("<tt attr=\nvalue\nthat=\nthis/>");
414  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
415  BOOST_CHECK(output.has_child("tt"));
416  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
417  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "value");
418  BOOST_CHECK(output.mandatory_child("tt").has_attribute("that"));
419  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["that"], "this");
420 
421  // Escaping a single quote in a single-quoted value
422  output = markup::parse_text(R"==(<tt attr='Let\'s go?'/>)==");
423  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
424  BOOST_CHECK(output.has_child("tt"));
425  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
426  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "Let's go?");
427 
428  // Using a simple character entity in a single-quoted value
429  output = markup::parse_text("<tt attr='&apos;tis good'/>");
430  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
431  BOOST_CHECK(output.has_child("tt"));
432  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
433  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "'tis good");
434 
435  // A newline in a single-quoted value
436  output = markup::parse_text("<tt attr='Line 1\nLine 2'/>");
437  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
438  BOOST_CHECK(output.has_child("tt"));
439  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
440  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], "Line 1\nLine 2");
441 
442  // Using a simple character entity in a double-quoted value
443  output = markup::parse_text(R"==(<tt attr="&quot;Yes!&quot;"/>)==");
444  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
445  BOOST_CHECK(output.has_child("tt"));
446  BOOST_CHECK(output.mandatory_child("tt").has_attribute("attr"));
447  BOOST_CHECK_EQUAL(output.mandatory_child("tt")["attr"], R"==("Yes!")==");
448 
449  // Using an unknown character entity in a tag
450  output = markup::parse_text("<tt>what &ndash; no</tt>");
451  BOOST_CHECK_EQUAL(output.all_children_count(), 1);
452  BOOST_CHECK(output.has_child("tt"));
453  // Simplify the remaining tests for this one by eliminating the outer layer
454  output = output.mandatory_child("tt");
455  BOOST_CHECK(output.has_child("character_entity"));
456  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
457  BOOST_CHECK_EQUAL(output.find_total_first_of("character_entity"), 1);
458  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
459  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "what ");
460  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
461  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " no");
462  BOOST_CHECK(output.mandatory_child("character_entity").has_attribute("name"));
463  BOOST_CHECK_EQUAL(output.mandatory_child("character_entity")["name"], "ndash");
464 
465  // Tags can be nested
466  output = markup::parse_text("We like to <tt>nest <abc>various</abc> <def>tags</def> within</tt> each other!");
467  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
468  BOOST_CHECK(output.has_child("tt"));
469  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
470  BOOST_CHECK_EQUAL(output.find_total_first_of("tt"), 1);
471  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
472  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "We like to ");
473  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
474  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " each other!");
475  // Simplify the remaining tests for this one by eliminating the outer layer
476  output = output.mandatory_child("tt");
477  BOOST_CHECK_EQUAL(output.all_children_count(), 5);
478  BOOST_CHECK(output.has_child("abc"));
479  BOOST_CHECK(output.has_child("def"));
480  BOOST_CHECK_EQUAL(output.child_count("text"), 3);
481  BOOST_CHECK_EQUAL(output.find_total_first_of("abc"), 1);
482  BOOST_CHECK_EQUAL(output.find_total_first_of("def"), 3);
483  BOOST_CHECK(output.mandatory_child("text").has_attribute("text"));
484  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "nest ");
485  BOOST_CHECK(output.mandatory_child("abc").has_attribute("text"));
486  BOOST_CHECK_EQUAL(output.mandatory_child("abc")["text"], "various");
487  BOOST_CHECK(output.mandatory_child("text", 1).has_attribute("text"));
488  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], " ");
489  BOOST_CHECK(output.mandatory_child("def").has_attribute("text"));
490  BOOST_CHECK_EQUAL(output.mandatory_child("def")["text"], "tags");
491  BOOST_CHECK(output.mandatory_child("text", 2).has_attribute("text"));
492  BOOST_CHECK_EQUAL(output.mandatory_child("text", 2)["text"], " within");
493 
494  // Two tags with nothing between them shouldn't have an intervening text span.
495  output = markup::parse_text("<img src=help/orb-green.png align=here/><img src=help/orb-green.png align=there/>");
496  BOOST_CHECK_EQUAL(output.all_children_count(), 2);
497  BOOST_CHECK_EQUAL(output.child_count("img"), 2);
498  BOOST_CHECK(output.mandatory_child("img").has_attribute("src"));
499  BOOST_CHECK_EQUAL(output.mandatory_child("img")["src"], "help/orb-green.png");
500  BOOST_CHECK(output.mandatory_child("img").has_attribute("align"));
501  BOOST_CHECK_EQUAL(output.mandatory_child("img")["align"], "here");
502  BOOST_CHECK(output.mandatory_child("img", 1).has_attribute("src"));
503  BOOST_CHECK_EQUAL(output.mandatory_child("img", 1)["src"], "help/orb-green.png");
504  BOOST_CHECK(output.mandatory_child("img", 1).has_attribute("align"));
505  BOOST_CHECK_EQUAL(output.mandatory_child("img", 1)["align"], "there");
506 
507  // Two tags with a newline between them should have an intervening text span containing exactly that.
508  output = markup::parse_text("<link dst=foo>First</link>\n<link dst=bar>Second</link>");
509  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
510  BOOST_CHECK(output.has_child("text"));
511  BOOST_CHECK_EQUAL(output.child_count("link"), 2);
512  BOOST_CHECK_EQUAL(output.find_total_first_of("text"), 1);
513  BOOST_CHECK(output.mandatory_child("link").has_attribute("dst"));
514  BOOST_CHECK_EQUAL(output.mandatory_child("link")["dst"], "foo");
515  BOOST_CHECK(output.mandatory_child("link").has_attribute("text"));
516  BOOST_CHECK_EQUAL(output.mandatory_child("link")["text"], "First");
517  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
518  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "\n");
519  BOOST_CHECK(output.mandatory_child("link", 1).has_attribute("dst"));
520  BOOST_CHECK_EQUAL(output.mandatory_child("link", 1)["dst"], "bar");
521  BOOST_CHECK(output.mandatory_child("link", 1).has_attribute("text"));
522  BOOST_CHECK_EQUAL(output.mandatory_child("link", 1)["text"], "Second");
523 
524  // Tag at the end of a paragraph produces an empty text span.
525  output = markup::parse_text("See also: <link dst=details>Details</link>\n\nAnd here's an extra paragraph!");
526  BOOST_CHECK_EQUAL(output.all_children_count(), 4);
527  BOOST_CHECK(output.has_child("link"));
528  BOOST_CHECK_EQUAL(output.child_count("text"), 3);
529  BOOST_CHECK_EQUAL(output.find_total_first_of("link"), 1);
530  BOOST_CHECK(output.mandatory_child("link").has_attribute("dst"));
531  BOOST_CHECK_EQUAL(output.mandatory_child("link")["dst"], "details");
532  BOOST_CHECK(output.mandatory_child("link").has_attribute("text"));
533  BOOST_CHECK_EQUAL(output.mandatory_child("link")["text"], "Details");
534  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
535  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "See also: ");
536  BOOST_CHECK((output.mandatory_child("text", 1).has_attribute("text")));
537  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "");
538  BOOST_CHECK((output.mandatory_child("text", 2).has_attribute("text")));
539  BOOST_CHECK_EQUAL(output.mandatory_child("text", 2)["text"], "And here's an extra paragraph!");
540 
541  // Tag at the beginning of a paragraph produces an empty text span.
542  output = markup::parse_text("This is some information.\n\n<img src='help/orb-red.png'/>And some more info!");
543  BOOST_CHECK_EQUAL(output.all_children_count(), 4);
544  BOOST_CHECK(output.has_child("img"));
545  BOOST_CHECK_EQUAL(output.child_count("text"), 3);
546  BOOST_CHECK_EQUAL(output.find_total_first_of("img"), 2);
547  BOOST_CHECK(output.mandatory_child("img").has_attribute("src"));
548  BOOST_CHECK_EQUAL(output.mandatory_child("img")["src"], "help/orb-red.png");
549  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
550  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "This is some information.");
551  BOOST_CHECK((output.mandatory_child("text", 1).has_attribute("text")));
552  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "");
553  BOOST_CHECK((output.mandatory_child("text", 2).has_attribute("text")));
554  BOOST_CHECK_EQUAL(output.mandatory_child("text", 2)["text"], "And some more info!");
555 
556  // Paragraph break between two tags produces two empty text spans.
557  output = markup::parse_text("<link dst=toc>Table of Contents</link>\n\n<img src='fancy-bullet.png'/>First...");
558  BOOST_CHECK_EQUAL(output.all_children_count(), 5);
559  BOOST_CHECK(output.has_child("link"));
560  BOOST_CHECK(output.has_child("img"));
561  BOOST_CHECK_EQUAL(output.child_count("text"), 3);
562  BOOST_CHECK_EQUAL(output.find_total_first_of("link"), 0);
563  BOOST_CHECK_EQUAL(output.find_total_first_of("img"), 3);
564  BOOST_CHECK(output.mandatory_child("link").has_attribute("dst"));
565  BOOST_CHECK_EQUAL(output.mandatory_child("link")["dst"], "toc");
566  BOOST_CHECK(output.mandatory_child("link").has_attribute("text"));
567  BOOST_CHECK_EQUAL(output.mandatory_child("link")["text"], "Table of Contents");
568  BOOST_CHECK(output.mandatory_child("img").has_attribute("src"));
569  BOOST_CHECK_EQUAL(output.mandatory_child("img")["src"], "fancy-bullet.png");
570  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
571  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "");
572  BOOST_CHECK((output.mandatory_child("text", 1).has_attribute("text")));
573  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "");
574  BOOST_CHECK((output.mandatory_child("text", 2).has_attribute("text")));
575  BOOST_CHECK_EQUAL(output.mandatory_child("text", 2)["text"], "First...");
576 
577  // Three consecutive newlines produces a paragraph beginning with a newline.
578  output = markup::parse_text("Let's have...\n\n\n...three consecutive newlines!");
579  BOOST_CHECK_EQUAL(output.all_children_count(), 2);
580  BOOST_CHECK_EQUAL(output.child_count("text"), 2);
581  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
582  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Let's have...");
583  BOOST_CHECK((output.mandatory_child("text", 1).has_attribute("text")));
584  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "\n...three consecutive newlines!");
585 
586  // Four consecutive newlines produces an empty paragraph.
587  output = markup::parse_text("Let's have...\n\n\n\n...four consecutive newlines!");
588  BOOST_CHECK_EQUAL(output.all_children_count(), 3);
589  BOOST_CHECK_EQUAL(output.child_count("text"), 3);
590  BOOST_CHECK((output.mandatory_child("text").has_attribute("text")));
591  BOOST_CHECK_EQUAL(output.mandatory_child("text")["text"], "Let's have...");
592  BOOST_CHECK((output.mandatory_child("text", 1).has_attribute("text")));
593  BOOST_CHECK_EQUAL(output.mandatory_child("text", 1)["text"], "");
594  BOOST_CHECK((output.mandatory_child("text", 2).has_attribute("text")));
595  BOOST_CHECK_EQUAL(output.mandatory_child("text", 2)["text"], "...four consecutive newlines!");
596 }
597 
598 BOOST_AUTO_TEST_SUITE_END()
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
Definitions for the interface to Wesnoth Markup Language (WML).
static std::ostream & output()
Definition: log.cpp:75
config parse_text(const std::string &text)
Parse a xml style marked up text string.
Definition: markup.cpp:403
BOOST_AUTO_TEST_SUITE(filesystem)
BOOST_AUTO_TEST_CASE(test_simple_help_markup)