diagnostics: move xml defs to a new xml.cc

While prototyping new features I'm finding it helpful to use XML
beyond the "experimental-html" diagnostics sink.  Move the
implementation of the xml classes to their own file.

No functional change intended.

gcc/ChangeLog:
	* Makefile.in (OBJS-libcommon): Add xml.o.
	* diagnostic-format-html.cc (namespace xml): Move implementation
	to xml.cc
	(selftest::test_printer): Likewise.
	(selftest::test_attribute_ordering): Likewise.
	(selftest::diagnostic_format_html_cc_tests): Don't call the moved
	tests here; they will be called from xml_cc_tests in xml.cc.
	* selftest-run-tests.cc (selftest::run_tests): Call xml_cc_tests.
	* selftest.h (selftest::xml_cc_tests): New decl.
	* xml.cc: New file, based on material from
	diagnostic-format-html.cc.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm
2025-06-06 13:41:27 -04:00
parent 0401957b86
commit 1233d79c19
5 changed files with 361 additions and 313 deletions

View File

@@ -1862,6 +1862,7 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
edit-context.o \
pretty-print.o intl.o \
json.o json-parsing.o \
xml.o \
sbitmap.o \
vec.o input.o hash-table.o ggc-none.o memory-block.o \
selftest.o selftest-diagnostic.o sort.o \

View File

@@ -49,256 +49,6 @@ html_generation_options::html_generation_options ()
{
}
namespace xml {
/* Disable warnings about quoting issues in the pp_xxx calls below
that (intentionally) don't follow GCC diagnostic conventions. */
#if __GNUC__ >= 10
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-diag"
#endif
/* Implementation. */
static void
write_escaped_text (pretty_printer *pp, const char *text)
{
gcc_assert (text);
for (const char *p = text; *p; ++p)
{
char ch = *p;
switch (ch)
{
default:
pp_character (pp, ch);
break;
case '\'':
pp_string (pp, "&apos;");
break;
case '"':
pp_string (pp, "&quot;");
break;
case '&':
pp_string (pp, "&amp;");
break;
case '<':
pp_string (pp, "&lt;");
break;
case '>':
pp_string (pp, "&gt;");
break;
}
}
}
/* struct node. */
void
node::dump (FILE *out) const
{
pretty_printer pp;
pp.set_output_stream (out);
write_as_xml (&pp, 0, true);
pp_flush (&pp);
}
/* struct text : public node. */
void
text::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
if (indent)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
write_escaped_text (pp, m_str.c_str ());
if (indent)
pp_newline (pp);
}
/* struct node_with_children : public node. */
void
node_with_children::add_child (std::unique_ptr<node> node)
{
gcc_assert (node.get ());
m_children.push_back (std::move (node));
}
void
node_with_children::add_text (std::string str)
{
// Consolidate runs of text
if (!m_children.empty ())
if (text *t = m_children.back ()->dyn_cast_text ())
{
t->m_str += std::move (str);
return;
}
add_child (std::make_unique <text> (std::move (str)));
}
/* struct document : public node_with_children. */
void
document::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
pp_string (pp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
pp_string (pp, "<!DOCTYPE html\n"
" PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
if (indent)
pp_newline (pp);
for (auto &iter : m_children)
iter->write_as_xml (pp, depth, indent);
}
/* struct element : public node_with_children. */
void
element::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
if (indent)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
pp_printf (pp, "<%s", m_kind.c_str ());
for (auto &key : m_key_insertion_order)
{
auto iter = m_attributes.find (key);
if (iter != m_attributes.end ())
{
pp_printf (pp, " %s=\"", key.c_str ());
write_escaped_text (pp, iter->second.c_str ());
pp_string (pp, "\"");
}
}
if (m_children.empty ())
pp_string (pp, "/>");
else
{
const bool indent_children = m_preserve_whitespace ? false : indent;
pp_string (pp, ">");
if (indent_children)
pp_newline (pp);
for (auto &child : m_children)
child->write_as_xml (pp, depth + 1, indent_children);
if (indent_children)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
pp_printf (pp, "</%s>", m_kind.c_str ());
}
if (indent)
pp_newline (pp);
}
void
element::set_attr (const char *name, std::string value)
{
auto iter = m_attributes.find (name);
if (iter == m_attributes.end ())
m_key_insertion_order.push_back (name);
m_attributes[name] = std::move (value);
}
// struct raw : public node
void
raw::write_as_xml (pretty_printer *pp,
int /*depth*/, bool /*indent*/) const
{
pp_string (pp, m_xml_src.c_str ());
}
#if __GNUC__ >= 10
# pragma GCC diagnostic pop
#endif
// class printer
printer::printer (element &insertion_point)
{
m_open_tags.push_back (&insertion_point);
}
void
printer::push_tag (std::string name,
bool preserve_whitespace)
{
push_element
(std::make_unique<element> (std::move (name),
preserve_whitespace));
}
void
printer::push_tag_with_class (std::string name, std::string class_,
bool preserve_whitespace)
{
auto new_element
= std::make_unique<element> (std::move (name),
preserve_whitespace);
new_element->set_attr ("class", class_);
push_element (std::move (new_element));
}
void
printer::pop_tag ()
{
m_open_tags.pop_back ();
}
void
printer::set_attr (const char *name, std::string value)
{
m_open_tags.back ()->set_attr (name, value);
}
void
printer::add_text (std::string text)
{
element *parent = m_open_tags.back ();
parent->add_text (std::move (text));
}
void
printer::add_raw (std::string text)
{
element *parent = m_open_tags.back ();
parent->add_child (std::make_unique<xml::raw> (std::move (text)));
}
void
printer::push_element (std::unique_ptr<element> new_element)
{
element *parent = m_open_tags.back ();
m_open_tags.push_back (new_element.get ());
parent->add_child (std::move (new_element));
}
void
printer::append (std::unique_ptr<node> new_node)
{
element *parent = m_open_tags.back ();
parent->add_child (std::move (new_node));
}
element *
printer::get_insertion_point () const
{
return m_open_tags.back ();
}
} // namespace xml
class html_builder;
/* Concrete buffering implementation subclass for HTML output. */
@@ -1288,67 +1038,6 @@ test_metadata ()
}
}
static void
test_printer ()
{
xml::element top ("top", false);
xml::printer xp (top);
xp.push_tag ("foo");
xp.add_text ("hello");
xp.push_tag ("bar");
xp.set_attr ("size", "3");
xp.set_attr ("color", "red");
xp.add_text ("world");
xp.push_tag ("baz");
xp.pop_tag ();
xp.pop_tag ();
xp.pop_tag ();
pretty_printer pp;
top.write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
"<top>\n"
" <foo>\n"
" hello\n"
" <bar size=\"3\" color=\"red\">\n"
" world\n"
" <baz/>\n"
" </bar>\n"
" </foo>\n"
"</top>\n");
}
// Verify that element attributes preserve insertion order.
static void
test_attribute_ordering ()
{
xml::element top ("top", false);
xml::printer xp (top);
xp.push_tag ("chronological");
xp.set_attr ("maldon", "991");
xp.set_attr ("hastings", "1066");
xp.set_attr ("edgehill", "1642");
xp.set_attr ("naseby", "1645");
xp.pop_tag ();
xp.push_tag ("alphabetical");
xp.set_attr ("edgehill", "1642");
xp.set_attr ("hastings", "1066");
xp.set_attr ("maldon", "991");
xp.set_attr ("naseby", "1645");
xp.pop_tag ();
pretty_printer pp;
top.write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
"<top>\n"
" <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n"
" <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n"
"</top>\n");
}
/* Run all of the selftests within this file. */
void
@@ -1357,8 +1046,6 @@ diagnostic_format_html_cc_tests ()
auto_fix_quotes fix_quotes;
test_simple_log ();
test_metadata ();
test_printer ();
test_attribute_ordering ();
}
} // namespace selftest

View File

@@ -80,6 +80,7 @@ selftest::run_tests ()
optinfo_emit_json_cc_tests ();
ordered_hash_map_tests_cc_tests ();
splay_tree_cc_tests ();
xml_cc_tests ();
/* Mid-level data structures. */
input_cc_tests ();

View File

@@ -276,6 +276,7 @@ extern void typed_splay_tree_cc_tests ();
extern void vec_cc_tests ();
extern void vec_perm_indices_cc_tests ();
extern void wide_int_cc_tests ();
extern void xml_cc_tests ();
extern int num_passes;

358
gcc/xml.cc Normal file
View File

@@ -0,0 +1,358 @@
/* XML support for diagnostics.
Copyright (C) 2024-2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#define INCLUDE_MAP
#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "xml.h"
#include "xml-printer.h"
#include "pretty-print.h"
#include "selftest.h"
namespace xml {
/* Disable warnings about quoting issues in the pp_xxx calls below
that (intentionally) don't follow GCC diagnostic conventions. */
#if __GNUC__ >= 10
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-diag"
#endif
/* Implementation. */
static void
write_escaped_text (pretty_printer *pp, const char *text)
{
gcc_assert (text);
for (const char *p = text; *p; ++p)
{
char ch = *p;
switch (ch)
{
default:
pp_character (pp, ch);
break;
case '\'':
pp_string (pp, "&apos;");
break;
case '"':
pp_string (pp, "&quot;");
break;
case '&':
pp_string (pp, "&amp;");
break;
case '<':
pp_string (pp, "&lt;");
break;
case '>':
pp_string (pp, "&gt;");
break;
}
}
}
/* struct node. */
void
node::dump (FILE *out) const
{
pretty_printer pp;
pp.set_output_stream (out);
write_as_xml (&pp, 0, true);
pp_flush (&pp);
}
/* struct text : public node. */
void
text::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
if (indent)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
write_escaped_text (pp, m_str.c_str ());
if (indent)
pp_newline (pp);
}
/* struct node_with_children : public node. */
void
node_with_children::add_child (std::unique_ptr<node> node)
{
gcc_assert (node.get ());
m_children.push_back (std::move (node));
}
void
node_with_children::add_text (std::string str)
{
// Consolidate runs of text
if (!m_children.empty ())
if (text *t = m_children.back ()->dyn_cast_text ())
{
t->m_str += std::move (str);
return;
}
add_child (std::make_unique <text> (std::move (str)));
}
/* struct document : public node_with_children. */
void
document::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
pp_string (pp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
pp_string (pp, "<!DOCTYPE html\n"
" PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
if (indent)
pp_newline (pp);
for (auto &iter : m_children)
iter->write_as_xml (pp, depth, indent);
}
/* struct element : public node_with_children. */
void
element::write_as_xml (pretty_printer *pp, int depth, bool indent) const
{
if (indent)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
pp_printf (pp, "<%s", m_kind.c_str ());
for (auto &key : m_key_insertion_order)
{
auto iter = m_attributes.find (key);
if (iter != m_attributes.end ())
{
pp_printf (pp, " %s=\"", key.c_str ());
write_escaped_text (pp, iter->second.c_str ());
pp_string (pp, "\"");
}
}
if (m_children.empty ())
pp_string (pp, "/>");
else
{
const bool indent_children = m_preserve_whitespace ? false : indent;
pp_string (pp, ">");
if (indent_children)
pp_newline (pp);
for (auto &child : m_children)
child->write_as_xml (pp, depth + 1, indent_children);
if (indent_children)
{
for (int i = 0; i < depth; ++i)
pp_string (pp, " ");
}
pp_printf (pp, "</%s>", m_kind.c_str ());
}
if (indent)
pp_newline (pp);
}
void
element::set_attr (const char *name, std::string value)
{
auto iter = m_attributes.find (name);
if (iter == m_attributes.end ())
m_key_insertion_order.push_back (name);
m_attributes[name] = std::move (value);
}
// struct raw : public node
void
raw::write_as_xml (pretty_printer *pp,
int /*depth*/, bool /*indent*/) const
{
pp_string (pp, m_xml_src.c_str ());
}
#if __GNUC__ >= 10
# pragma GCC diagnostic pop
#endif
// class printer
printer::printer (element &insertion_point)
{
m_open_tags.push_back (&insertion_point);
}
void
printer::push_tag (std::string name,
bool preserve_whitespace)
{
push_element
(std::make_unique<element> (std::move (name),
preserve_whitespace));
}
void
printer::push_tag_with_class (std::string name, std::string class_,
bool preserve_whitespace)
{
auto new_element
= std::make_unique<element> (std::move (name),
preserve_whitespace);
new_element->set_attr ("class", class_);
push_element (std::move (new_element));
}
void
printer::pop_tag ()
{
m_open_tags.pop_back ();
}
void
printer::set_attr (const char *name, std::string value)
{
m_open_tags.back ()->set_attr (name, value);
}
void
printer::add_text (std::string text)
{
element *parent = m_open_tags.back ();
parent->add_text (std::move (text));
}
void
printer::add_raw (std::string text)
{
element *parent = m_open_tags.back ();
parent->add_child (std::make_unique<xml::raw> (std::move (text)));
}
void
printer::push_element (std::unique_ptr<element> new_element)
{
element *parent = m_open_tags.back ();
m_open_tags.push_back (new_element.get ());
parent->add_child (std::move (new_element));
}
void
printer::append (std::unique_ptr<node> new_node)
{
element *parent = m_open_tags.back ();
parent->add_child (std::move (new_node));
}
element *
printer::get_insertion_point () const
{
return m_open_tags.back ();
}
} // namespace xml
#if CHECKING_P
namespace selftest {
static void
test_printer ()
{
xml::element top ("top", false);
xml::printer xp (top);
xp.push_tag ("foo");
xp.add_text ("hello");
xp.push_tag ("bar");
xp.set_attr ("size", "3");
xp.set_attr ("color", "red");
xp.add_text ("world");
xp.push_tag ("baz");
xp.pop_tag ();
xp.pop_tag ();
xp.pop_tag ();
pretty_printer pp;
top.write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
"<top>\n"
" <foo>\n"
" hello\n"
" <bar size=\"3\" color=\"red\">\n"
" world\n"
" <baz/>\n"
" </bar>\n"
" </foo>\n"
"</top>\n");
}
// Verify that element attributes preserve insertion order.
static void
test_attribute_ordering ()
{
xml::element top ("top", false);
xml::printer xp (top);
xp.push_tag ("chronological");
xp.set_attr ("maldon", "991");
xp.set_attr ("hastings", "1066");
xp.set_attr ("edgehill", "1642");
xp.set_attr ("naseby", "1645");
xp.pop_tag ();
xp.push_tag ("alphabetical");
xp.set_attr ("edgehill", "1642");
xp.set_attr ("hastings", "1066");
xp.set_attr ("maldon", "991");
xp.set_attr ("naseby", "1645");
xp.pop_tag ();
pretty_printer pp;
top.write_as_xml (&pp, 0, true);
ASSERT_STREQ
(pp_formatted_text (&pp),
"<top>\n"
" <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n"
" <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n"
"</top>\n");
}
/* Run all of the selftests within this file. */
void
xml_cc_tests ()
{
test_printer ();
test_attribute_ordering ();
}
} // namespace selftest
#endif /* CHECKING_P */