/* 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" 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))); } /* 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); } // 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 (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)); } 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 (std::move (text))); } void printer::push_element (std::unique_ptr 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 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_no_dtd () { xml::document doc; pretty_printer pp; doc.write_as_xml (&pp, 0, true); ASSERT_STREQ (pp_formatted_text (&pp), "\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 (); xp.pop_tag (); xp.pop_tag (); pretty_printer pp; top.write_as_xml (&pp, 0, true); ASSERT_STREQ (pp_formatted_text (&pp), "\n" " \n" " hello\n" " \n" " world\n" " \n" " \n" " \n" "\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), "\n" " \n" " \n" "\n"); } /* Run all of the selftests within this file. */ void xml_cc_tests () { test_no_dtd (); test_printer (); test_attribute_ordering (); } } // namespace selftest #endif /* CHECKING_P */