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