Branch data Line data Source code
1 : : /* XML support for diagnostics.
2 : : Copyright (C) 2024-2025 Free Software Foundation, Inc.
3 : : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 : :
5 : : This file is part of GCC.
6 : :
7 : : GCC is free software; you can redistribute it and/or modify it under
8 : : the terms of the GNU General Public License as published by the Free
9 : : Software Foundation; either version 3, or (at your option) any later
10 : : version.
11 : :
12 : : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 : : WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 : : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 : : for more details.
16 : :
17 : : You should have received a copy of the GNU General Public License
18 : : along with GCC; see the file COPYING3. If not see
19 : : <http://www.gnu.org/licenses/>. */
20 : :
21 : : #include "config.h"
22 : : #define INCLUDE_MAP
23 : : #define INCLUDE_STRING
24 : : #define INCLUDE_VECTOR
25 : : #include "system.h"
26 : : #include "coretypes.h"
27 : : #include "xml.h"
28 : : #include "xml-printer.h"
29 : : #include "pretty-print.h"
30 : : #include "selftest.h"
31 : : #include "selftest-xml.h"
32 : :
33 : : namespace xml {
34 : :
35 : : /* Disable warnings about quoting issues in the pp_xxx calls below
36 : : that (intentionally) don't follow GCC diagnostic conventions. */
37 : : #if __GNUC__ >= 10
38 : : # pragma GCC diagnostic push
39 : : # pragma GCC diagnostic ignored "-Wformat-diag"
40 : : #endif
41 : :
42 : :
43 : : /* Implementation. */
44 : :
45 : : static void
46 : 10205 : write_escaped_text (pretty_printer *pp, const char *text)
47 : : {
48 : 10205 : gcc_assert (text);
49 : :
50 : 140394 : for (const char *p = text; *p; ++p)
51 : : {
52 : 130189 : char ch = *p;
53 : 130189 : switch (ch)
54 : : {
55 : 128559 : default:
56 : 128559 : pp_character (pp, ch);
57 : 128559 : break;
58 : 798 : case '\'':
59 : 798 : pp_string (pp, "'");
60 : 798 : break;
61 : 830 : case '"':
62 : 830 : pp_string (pp, """);
63 : 830 : break;
64 : 0 : case '&':
65 : 0 : pp_string (pp, "&");
66 : 0 : break;
67 : 2 : case '<':
68 : 2 : pp_string (pp, "<");
69 : 2 : break;
70 : 0 : case '>':
71 : 0 : pp_string (pp, ">");
72 : 0 : break;
73 : : }
74 : : }
75 : 10205 : }
76 : :
77 : : /* struct node. */
78 : :
79 : : void
80 : 10 : node::dump (FILE *out) const
81 : : {
82 : 10 : pretty_printer pp;
83 : 10 : pp.set_output_stream (out);
84 : 10 : write_as_xml (&pp, 0, true);
85 : 10 : pp_flush (&pp);
86 : 10 : }
87 : :
88 : : /* struct text : public node. */
89 : :
90 : : void
91 : 4868 : text::write_as_xml (pretty_printer *pp, int depth, bool indent) const
92 : : {
93 : 4868 : if (indent)
94 : : {
95 : 28 : for (int i = 0; i < depth; ++i)
96 : 20 : pp_string (pp, " ");
97 : : }
98 : 4868 : write_escaped_text (pp, m_str.c_str ());
99 : 4868 : if (indent)
100 : 8 : pp_newline (pp);
101 : 4868 : }
102 : :
103 : : /* struct node_with_children : public node. */
104 : :
105 : : void
106 : 11779 : node_with_children::add_child (std::unique_ptr<node> node)
107 : : {
108 : 11779 : gcc_assert (node.get ());
109 : 11779 : m_children.push_back (std::move (node));
110 : 11779 : }
111 : :
112 : : void
113 : 30493 : node_with_children::add_text (std::string str)
114 : : {
115 : : // Consolidate runs of text
116 : 30493 : if (!m_children.empty ())
117 : 26112 : if (text *t = m_children.back ()->dyn_cast_text ())
118 : : {
119 : 25603 : t->m_str += std::move (str);
120 : 25603 : return;
121 : : }
122 : 4890 : add_child (std::make_unique <text> (std::move (str)));
123 : : }
124 : :
125 : : void
126 : 7399 : node_with_children::add_text_from_pp (pretty_printer &pp)
127 : : {
128 : 7399 : add_text (pp_formatted_text (&pp));
129 : 7399 : }
130 : :
131 : : void
132 : 8 : node_with_children::add_comment (std::string str)
133 : : {
134 : 8 : add_child (std::make_unique <comment> (std::move (str)));
135 : 8 : }
136 : :
137 : : element *
138 : 12 : node_with_children::find_child_element (std::string kind) const
139 : : {
140 : 20 : for (auto &iter : m_children)
141 : 16 : if (element *e = iter->dyn_cast_element ())
142 : 12 : if (e->m_kind == kind)
143 : 12 : return e;
144 : : return nullptr;
145 : : }
146 : :
147 : : /* struct document : public node_with_children. */
148 : :
149 : : void
150 : 22 : document::write_as_xml (pretty_printer *pp, int depth, bool indent) const
151 : : {
152 : 22 : pp_string (pp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
153 : 22 : if (m_doctypedecl)
154 : 14 : m_doctypedecl->write_as_xml (pp, depth, indent);
155 : 44 : for (auto &iter : m_children)
156 : 22 : iter->write_as_xml (pp, depth, indent);
157 : 22 : }
158 : :
159 : : /* struct element : public node_with_children. */
160 : :
161 : : void
162 : 6884 : element::write_as_xml (pretty_printer *pp, int depth, bool indent) const
163 : : {
164 : 6884 : if (indent)
165 : : {
166 : 11926 : for (int i = 0; i < depth; ++i)
167 : 9316 : pp_string (pp, " ");
168 : : }
169 : :
170 : 6884 : pp_printf (pp, "<%s", m_kind.c_str ());
171 : 12213 : for (auto &key : m_key_insertion_order)
172 : : {
173 : 5329 : auto iter = m_attributes.find (key);
174 : 5329 : if (iter != m_attributes.end ())
175 : : {
176 : 5329 : pp_printf (pp, " %s=\"", key.c_str ());
177 : 5329 : write_escaped_text (pp, iter->second.c_str ());
178 : 5329 : pp_string (pp, "\"");
179 : : }
180 : : }
181 : 6884 : if (m_children.empty ())
182 : 43 : pp_string (pp, "/>");
183 : : else
184 : : {
185 : 6841 : const bool indent_children = m_preserve_whitespace ? false : indent;
186 : 6841 : pp_string (pp, ">");
187 : 6841 : if (indent_children)
188 : 861 : pp_newline (pp);
189 : 18364 : for (auto &child : m_children)
190 : 11523 : child->write_as_xml (pp, depth + 1, indent_children);
191 : 6841 : if (indent_children)
192 : : {
193 : 3114 : for (int i = 0; i < depth; ++i)
194 : 2253 : pp_string (pp, " ");
195 : : }
196 : 6841 : pp_printf (pp, "</%s>", m_kind.c_str ());
197 : : }
198 : :
199 : 6884 : if (indent)
200 : 2610 : pp_newline (pp);
201 : 6884 : }
202 : :
203 : : void
204 : 5428 : element::set_attr (const char *name, std::string value)
205 : : {
206 : 5428 : auto iter = m_attributes.find (name);
207 : 5428 : if (iter == m_attributes.end ())
208 : 5339 : m_key_insertion_order.push_back (name);
209 : 5428 : m_attributes[name] = std::move (value);
210 : 5428 : }
211 : :
212 : : const char *
213 : 12 : element::get_attr (const char *name) const
214 : : {
215 : 12 : auto iter = m_attributes.find (name);
216 : 12 : if (iter == m_attributes.end ())
217 : : return nullptr;
218 : 8 : return iter->second.c_str ();
219 : : }
220 : :
221 : : // struct comment : public node
222 : :
223 : : void
224 : 8 : comment::write_as_xml (pretty_printer *pp,
225 : : int depth, bool indent) const
226 : : {
227 : 8 : if (indent)
228 : : {
229 : 8 : for (int i = 0; i < depth; ++i)
230 : 0 : pp_string (pp, " ");
231 : : }
232 : 8 : pp_string (pp, "<!-- ");
233 : 8 : write_escaped_text (pp, m_text.c_str ());
234 : 8 : pp_string (pp, " -->");
235 : 8 : if (indent)
236 : 8 : pp_newline (pp);
237 : 8 : }
238 : :
239 : : // struct raw : public node
240 : :
241 : : void
242 : 18 : raw::write_as_xml (pretty_printer *pp,
243 : : int /*depth*/, bool /*indent*/) const
244 : : {
245 : 18 : pp_string (pp, m_xml_src.c_str ());
246 : 18 : }
247 : :
248 : : // class printer
249 : :
250 : 492 : printer::printer (element &insertion_point,
251 : 492 : bool check_popped_tags)
252 : 492 : : m_check_popped_tags (check_popped_tags)
253 : : {
254 : 492 : m_open_tags.push_back (&insertion_point);
255 : 492 : }
256 : :
257 : : void
258 : 2424 : printer::push_tag (std::string name,
259 : : bool preserve_whitespace)
260 : : {
261 : 2424 : push_element
262 : 2424 : (std::make_unique<element> (std::move (name),
263 : : preserve_whitespace));
264 : 2424 : }
265 : :
266 : : void
267 : 4131 : printer::push_tag_with_class (std::string name, std::string class_,
268 : : bool preserve_whitespace)
269 : : {
270 : 4131 : auto new_element
271 : : = std::make_unique<element> (std::move (name),
272 : 4131 : preserve_whitespace);
273 : 8262 : new_element->set_attr ("class", class_);
274 : 4131 : push_element (std::move (new_element));
275 : 4131 : }
276 : :
277 : : /* Pop the current topmost tag.
278 : : If m_check_popped_tags, assert that the tag we're popping is
279 : : EXPECTED_NAME. */
280 : :
281 : : void
282 : 6523 : printer::pop_tag (const char *expected_name ATTRIBUTE_UNUSED)
283 : : {
284 : 6523 : gcc_assert (!m_open_tags.empty ());
285 : 6523 : if (m_check_popped_tags)
286 : 6242 : gcc_assert (expected_name == get_insertion_point ()->m_kind);
287 : 6523 : m_open_tags.pop_back ();
288 : 6523 : }
289 : :
290 : : void
291 : 782 : printer::set_attr (const char *name, std::string value)
292 : : {
293 : 1564 : m_open_tags.back ()->set_attr (name, value);
294 : 782 : }
295 : :
296 : : void
297 : 22856 : printer::add_text (std::string text)
298 : : {
299 : 22856 : element *parent = m_open_tags.back ();
300 : 22856 : parent->add_text (std::move (text));
301 : 22856 : }
302 : :
303 : : void
304 : 7399 : printer::add_text_from_pp (pretty_printer &pp)
305 : : {
306 : 7399 : element *parent = m_open_tags.back ();
307 : 7399 : parent->add_text_from_pp (pp);
308 : 7399 : }
309 : :
310 : : void
311 : 14 : printer::add_raw (std::string text)
312 : : {
313 : 14 : element *parent = m_open_tags.back ();
314 : 14 : parent->add_child (std::make_unique<xml::raw> (std::move (text)));
315 : 14 : }
316 : :
317 : : void
318 : 6555 : printer::push_element (std::unique_ptr<element> new_element)
319 : : {
320 : 6555 : gcc_assert (new_element.get ());
321 : 6555 : element *parent = m_open_tags.back ();
322 : 6555 : m_open_tags.push_back (new_element.get ());
323 : 6555 : parent->add_child (std::move (new_element));
324 : 6555 : }
325 : :
326 : : void
327 : 131 : printer::append (std::unique_ptr<node> new_node)
328 : : {
329 : 131 : gcc_assert (new_node.get ());
330 : 131 : element *parent = m_open_tags.back ();
331 : 131 : parent->add_child (std::move (new_node));
332 : 131 : }
333 : :
334 : : element *
335 : 8715 : printer::get_insertion_point () const
336 : : {
337 : 8715 : return m_open_tags.back ();
338 : : }
339 : :
340 : : void
341 : 0 : printer::dump () const
342 : : {
343 : 0 : pretty_printer pp;
344 : 0 : pp.set_output_stream (stderr);
345 : 0 : pp_printf (&pp, "open tags: %i:", (int)m_open_tags.size ());
346 : 0 : for (auto iter : m_open_tags)
347 : 0 : pp_printf (&pp, " <%s>", iter->m_kind.c_str ());
348 : 0 : pp_newline (&pp);
349 : 0 : pp_printf (&pp, "xml:");
350 : 0 : pp_newline (&pp);
351 : 0 : m_open_tags[0]->write_as_xml (&pp, 1, true);
352 : 0 : pp_flush (&pp);
353 : 0 : }
354 : :
355 : : #if __GNUC__ >= 10
356 : : # pragma GCC diagnostic pop
357 : : #endif
358 : :
359 : : } // namespace xml
360 : :
361 : : #if CHECKING_P
362 : :
363 : : namespace selftest {
364 : :
365 : : void
366 : 40 : assert_xml_print_eq (const location &loc,
367 : : const xml::node &node,
368 : : const char *expected_value)
369 : : {
370 : 40 : pretty_printer pp;
371 : 40 : node.write_as_xml (&pp, 0, true);
372 : 40 : ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_value);
373 : 40 : }
374 : :
375 : : static void
376 : 4 : test_no_dtd ()
377 : : {
378 : 4 : xml::document doc;
379 : 4 : ASSERT_XML_PRINT_EQ
380 : : (doc,
381 : : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
382 : 4 : }
383 : :
384 : : static void
385 : 4 : test_printer ()
386 : : {
387 : 4 : xml::element top ("top", false);
388 : 4 : xml::printer xp (top);
389 : 4 : xp.push_tag ("foo");
390 : 4 : xp.add_text ("hello");
391 : 4 : xp.push_tag ("bar");
392 : 4 : xp.set_attr ("size", "3");
393 : 4 : xp.set_attr ("color", "red");
394 : 4 : xp.add_text ("world");
395 : 4 : xp.push_tag ("baz");
396 : 4 : xp.pop_tag ("baz");
397 : 4 : xp.pop_tag ("bar");
398 : 4 : xp.pop_tag ("foo");
399 : :
400 : 4 : ASSERT_XML_PRINT_EQ
401 : : (top,
402 : : "<top>\n"
403 : : " <foo>\n"
404 : : " hello\n"
405 : : " <bar size=\"3\" color=\"red\">\n"
406 : : " world\n"
407 : : " <baz/>\n"
408 : : " </bar>\n"
409 : : " </foo>\n"
410 : : "</top>\n");
411 : :
412 : 4 : xml::element *foo = top.find_child_element ("foo");
413 : 4 : ASSERT_TRUE (foo);
414 : 4 : ASSERT_EQ (top.find_child_element ("not-foo"), nullptr);
415 : 4 : xml::element *bar = foo->find_child_element ("bar");
416 : 4 : ASSERT_TRUE (bar);
417 : 4 : ASSERT_STREQ (bar->get_attr ("size"), "3");
418 : 4 : ASSERT_STREQ (bar->get_attr ("color"), "red");
419 : 4 : ASSERT_EQ (bar->get_attr ("airspeed-velocity"), nullptr);
420 : 4 : }
421 : :
422 : : // Verify that element attributes preserve insertion order.
423 : :
424 : : static void
425 : 4 : test_attribute_ordering ()
426 : : {
427 : 4 : xml::element top ("top", false);
428 : 4 : xml::printer xp (top);
429 : 4 : xp.push_tag ("chronological");
430 : 4 : xp.set_attr ("maldon", "991");
431 : 4 : xp.set_attr ("hastings", "1066");
432 : 4 : xp.set_attr ("edgehill", "1642");
433 : 4 : xp.set_attr ("naseby", "1645");
434 : 4 : xp.pop_tag ("chronological");
435 : 4 : xp.push_tag ("alphabetical");
436 : 4 : xp.set_attr ("edgehill", "1642");
437 : 4 : xp.set_attr ("hastings", "1066");
438 : 4 : xp.set_attr ("maldon", "991");
439 : 4 : xp.set_attr ("naseby", "1645");
440 : 4 : xp.pop_tag ("alphabetical");
441 : :
442 : 4 : ASSERT_XML_PRINT_EQ
443 : : (top,
444 : : "<top>\n"
445 : : " <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n"
446 : : " <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n"
447 : : "</top>\n");
448 : 4 : }
449 : :
450 : : static void
451 : 4 : test_comment ()
452 : : {
453 : 4 : xml::document doc;
454 : 4 : doc.add_comment ("hello");
455 : 4 : doc.add_comment ("world");
456 : 4 : ASSERT_XML_PRINT_EQ
457 : : (doc,
458 : : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
459 : : "<!-- hello -->\n"
460 : : "<!-- world -->\n");
461 : :
462 : 4 : }
463 : : /* Run all of the selftests within this file. */
464 : :
465 : : void
466 : 4 : xml_cc_tests ()
467 : : {
468 : 4 : test_no_dtd ();
469 : 4 : test_printer ();
470 : 4 : test_attribute_ordering ();
471 : 4 : test_comment ();
472 : 4 : }
473 : :
474 : : } // namespace selftest
475 : :
476 : : #endif /* CHECKING_P */
|