/* XML support for diagnostics. Copyright (C) 2024-2025 Free Software Foundation, Inc. Contributed by David Malcolm . 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 . */ #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" #include "selftest-xml.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, "'"); break; case '"': pp_string (pp, """); break; case '&': pp_string (pp, "&"); break; case '<': pp_string (pp, "<"); break; case '>': pp_string (pp, ">"); 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) { 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 (std::move (str))); } void node_with_children::add_text_from_pp (pretty_printer &pp) { add_text (pp_formatted_text (&pp)); } void node_with_children::add_comment (std::string str) { add_child (std::make_unique (std::move (str))); } element * node_with_children::find_child_element (std::string kind) const { for (auto &iter : m_children) if (element *e = iter->dyn_cast_element ()) if (e->m_kind == kind) return e; return nullptr; } /* struct document : public node_with_children. */ void document::write_as_xml (pretty_printer *pp, int depth, bool indent) const { pp_string (pp, "\n"); if (m_doctypedecl) m_doctypedecl->write_as_xml (pp, depth, indent); 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, "", 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); } const char * element::get_attr (const char *name) const { auto iter = m_attributes.find (name); if (iter == m_attributes.end ()) return nullptr; return iter->second.c_str (); } // struct comment : public node void comment::write_as_xml (pretty_printer *pp, int depth, bool indent) const { if (indent) { for (int i = 0; i < depth; ++i) pp_string (pp, " "); } pp_string (pp, ""); if (indent) pp_newline (pp); } // 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 ()); } // class printer printer::printer (element &insertion_point, bool check_popped_tags) : m_check_popped_tags (check_popped_tags) { m_open_tags.push_back (&insertion_point); } void printer::push_tag (std::string name, bool preserve_whitespace) { push_element (std::make_unique (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 (std::move (name), preserve_whitespace); new_element->set_attr ("class", class_); push_element (std::move (new_element)); } /* Pop the current topmost tag. If m_check_popped_tags, assert that the tag we're popping is EXPECTED_NAME. */ void printer::pop_tag (const char *expected_name ATTRIBUTE_UNUSED) { gcc_assert (!m_open_tags.empty ()); if (m_check_popped_tags) gcc_assert (expected_name == get_insertion_point ()->m_kind); 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_text_from_pp (pretty_printer &pp) { element *parent = m_open_tags.back (); parent->add_text_from_pp (pp); } void printer::add_raw (std::string text) { element *parent = m_open_tags.back (); parent->add_child (std::make_unique (std::move (text))); } void printer::push_element (std::unique_ptr new_element) { gcc_assert (new_element.get ()); 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 new_node) { gcc_assert (new_node.get ()); element *parent = m_open_tags.back (); parent->add_child (std::move (new_node)); } element * printer::get_insertion_point () const { return m_open_tags.back (); } void printer::dump () const { pretty_printer pp; pp.set_output_stream (stderr); pp_printf (&pp, "open tags: %i:", (int)m_open_tags.size ()); for (auto iter : m_open_tags) pp_printf (&pp, " <%s>", iter->m_kind.c_str ()); pp_newline (&pp); pp_printf (&pp, "xml:"); pp_newline (&pp); m_open_tags[0]->write_as_xml (&pp, 1, true); pp_flush (&pp); } #if __GNUC__ >= 10 # pragma GCC diagnostic pop #endif } // namespace xml #if CHECKING_P namespace selftest { void assert_xml_print_eq (const location &loc, const xml::node &node, const char *expected_value) { pretty_printer pp; node.write_as_xml (&pp, 0, true); ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_value); } static void test_no_dtd () { xml::document doc; ASSERT_XML_PRINT_EQ (doc, "\n"); } 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 ("baz"); xp.pop_tag ("bar"); xp.pop_tag ("foo"); ASSERT_XML_PRINT_EQ (top, "\n" " \n" " hello\n" " \n" " world\n" " \n" " \n" " \n" "\n"); xml::element *foo = top.find_child_element ("foo"); ASSERT_TRUE (foo); ASSERT_EQ (top.find_child_element ("not-foo"), nullptr); xml::element *bar = foo->find_child_element ("bar"); ASSERT_TRUE (bar); ASSERT_STREQ (bar->get_attr ("size"), "3"); ASSERT_STREQ (bar->get_attr ("color"), "red"); ASSERT_EQ (bar->get_attr ("airspeed-velocity"), nullptr); } // 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 ("chronological"); 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 ("alphabetical"); ASSERT_XML_PRINT_EQ (top, "\n" " \n" " \n" "\n"); } static void test_comment () { xml::document doc; doc.add_comment ("hello"); doc.add_comment ("world"); ASSERT_XML_PRINT_EQ (doc, "\n" "\n" "\n"); } /* Run all of the selftests within this file. */ void xml_cc_tests () { test_no_dtd (); test_printer (); test_attribute_ordering (); test_comment (); } } // namespace selftest #endif /* CHECKING_P */