diff options
Diffstat (limited to 'gcc/xml.cc')
-rw-r--r-- | gcc/xml.cc | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/gcc/xml.cc b/gcc/xml.cc new file mode 100644 index 0000000..8e11c67 --- /dev/null +++ b/gcc/xml.cc @@ -0,0 +1,476 @@ +/* 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" +#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> 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))); +} + +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 <comment> (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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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, "</%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); +} + +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, "<!-- "); + write_escaped_text (pp, m_text.c_str ()); + 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<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)); +} + +/* 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<xml::raw> (std::move (text))); +} + +void +printer::push_element (std::unique_ptr<element> 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<node> 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, + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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, + "<top>\n" + " <foo>\n" + " hello\n" + " <bar size=\"3\" color=\"red\">\n" + " world\n" + " <baz/>\n" + " </bar>\n" + " </foo>\n" + "</top>\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, + "<top>\n" + " <chronological maldon=\"991\" hastings=\"1066\" edgehill=\"1642\" naseby=\"1645\"/>\n" + " <alphabetical edgehill=\"1642\" hastings=\"1066\" maldon=\"991\" naseby=\"1645\"/>\n" + "</top>\n"); +} + +static void +test_comment () +{ + xml::document doc; + doc.add_comment ("hello"); + doc.add_comment ("world"); + ASSERT_XML_PRINT_EQ + (doc, + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!-- hello -->\n" + "<!-- world -->\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 */ |