summaryrefslogtreecommitdiffstats
path: root/src/parser/xml_writer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser/xml_writer.cpp')
-rw-r--r--src/parser/xml_writer.cpp326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/parser/xml_writer.cpp b/src/parser/xml_writer.cpp
new file mode 100644
index 0000000..a2f6c2f
--- /dev/null
+++ b/src/parser/xml_writer.cpp
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <orcus/xml_writer.hpp>
+#include <orcus/xml_namespace.hpp>
+#include <orcus/string_pool.hpp>
+
+#include <vector>
+
+/** To markup code that coverity warns might throw exceptions
+ which won't throw in practice, or where std::terminate is
+ an acceptable response if they do
+*/
+#if defined(__COVERITY__)
+#define suppress_fun_call_w_exception(expr) \
+ do \
+ { \
+ try \
+ { \
+ expr; \
+ } \
+ catch (const std::exception& e) \
+ { \
+ std::cerr << "Fatal exception: " << e.what() << std::endl; \
+ std::terminate(); \
+ } \
+ } while (false)
+#else
+#define suppress_fun_call_w_exception(expr) \
+ do \
+ { \
+ expr; \
+ } while (false)
+#endif
+
+namespace orcus {
+
+namespace {
+
+struct _elem
+{
+ xml_name_t name;
+ std::vector<std::string_view> ns_aliases;
+ bool open;
+
+ _elem(const xml_name_t& _name) : name(_name), open(true) {}
+};
+
+struct _attr
+{
+ xml_name_t name;
+ std::string_view value;
+
+ _attr(const xml_name_t& _name, std::string_view _value) :
+ name(_name),
+ value(_value)
+ {}
+};
+
+void write_content_encoded(std::string_view content, std::ostream& os)
+{
+ auto _flush = [&os](const char*& p0, const char* p)
+ {
+ size_t n = std::distance(p0, p);
+ os.write(p0, n);
+ p0 = nullptr;
+ };
+
+ constexpr std::string_view cv_lt = "&lt;";
+ constexpr std::string_view cv_gt = "&gt;";
+ constexpr std::string_view cv_amp = "&amp;";
+ constexpr std::string_view cv_apos = "&apos;";
+ constexpr std::string_view cv_quot = "&quot;";
+
+ const char* p = content.data();
+ const char* p_end = p + content.size();
+ const char* p0 = nullptr;
+
+ for (; p != p_end; ++p)
+ {
+ if (!p0)
+ p0 = p;
+
+ switch (*p)
+ {
+ case '<':
+ _flush(p0, p);
+ os.write(cv_lt.data(), cv_lt.size());
+ break;
+ case '>':
+ _flush(p0, p);
+ os.write(cv_gt.data(), cv_gt.size());
+ break;
+ case '&':
+ _flush(p0, p);
+ os.write(cv_amp.data(), cv_amp.size());
+ break;
+ case '\'':
+ _flush(p0, p);
+ os.write(cv_apos.data(), cv_apos.size());
+ break;
+ case '"':
+ _flush(p0, p);
+ os.write(cv_quot.data(), cv_quot.size());
+ break;
+ }
+ }
+
+ if (p0)
+ _flush(p0, p);
+}
+
+} // anonymous namespace
+
+struct xml_writer::scope::impl
+{
+ xml_writer* parent;
+ xml_name_t elem;
+
+ impl() : parent(nullptr) {}
+
+ impl(xml_writer* _parent, const xml_name_t& _elem) :
+ parent(_parent),
+ elem(_elem)
+ {
+ parent->push_element(elem);
+ }
+
+ ~impl()
+ {
+ suppress_fun_call_w_exception(parent->pop_element());
+ }
+};
+
+xml_writer::scope::scope(xml_writer* parent, const xml_name_t& elem) :
+ mp_impl(std::make_unique<impl>(parent, elem))
+{
+}
+
+xml_writer::scope::scope(scope&& other) :
+ mp_impl(std::move(other.mp_impl))
+{
+ // NB: we shouldn't have to create an impl instance for the other object
+ // since everything happens in the impl, and the envelop class doesn't
+ // access the impl internals.
+}
+
+xml_writer::scope::~scope() {}
+
+xml_writer::scope& xml_writer::scope::operator= (scope&& other)
+{
+ scope tmp(std::move(other));
+ mp_impl.swap(tmp.mp_impl);
+ return *this;
+}
+
+struct xml_writer::impl
+{
+ xmlns_repository& ns_repo;
+ std::ostream& os;
+ std::vector<_elem> elem_stack;
+ std::vector<std::string_view> ns_decls;
+ std::vector<_attr> attrs;
+
+ string_pool str_pool;
+ xmlns_repository repo;
+ xmlns_context cxt;
+
+ impl(xmlns_repository& _ns_repo, std::ostream& _os) :
+ ns_repo(_ns_repo),
+ os(_os),
+ cxt(ns_repo.create_context())
+ {}
+
+ void print(const xml_name_t& name)
+ {
+ std::string_view alias = cxt.get_alias(name.ns);
+ if (!alias.empty())
+ os << alias << ':';
+ os << name.name;
+ }
+
+ xml_name_t intern(const xml_name_t& name)
+ {
+ xml_name_t interned = name;
+ interned.name = str_pool.intern(interned.name).first;
+ return interned;
+ }
+
+ std::string_view intern(std::string_view value)
+ {
+ return str_pool.intern(value).first;
+ }
+};
+
+xml_writer::xml_writer(xmlns_repository& ns_repo, std::ostream& os) :
+ mp_impl(std::make_unique<impl>(ns_repo, os))
+{
+ os << "<?xml version=\"1.0\"?>";
+}
+
+xml_writer::xml_writer(xml_writer&& other) :
+ mp_impl(std::move(other.mp_impl))
+{
+ other.mp_impl = std::make_unique<impl>(mp_impl->ns_repo, mp_impl->os);
+}
+
+xml_writer& xml_writer::operator= (xml_writer&& other)
+{
+ xml_writer tmp(std::move(other));
+ mp_impl.swap(tmp.mp_impl);
+ return *this;
+}
+
+void xml_writer::pop_elements()
+{
+ // Pop all the elements currently on the stack.
+ while (!mp_impl->elem_stack.empty())
+ pop_element();
+}
+
+xml_writer::~xml_writer()
+{
+ suppress_fun_call_w_exception(pop_elements());
+}
+
+void xml_writer::close_current_element()
+{
+ if (!mp_impl->elem_stack.empty() && mp_impl->elem_stack.back().open)
+ {
+ mp_impl->os << '>';
+ mp_impl->elem_stack.back().open = false;
+ }
+}
+
+xml_writer::scope xml_writer::push_element_scope(const xml_name_t& name)
+{
+ return scope(this, name);
+}
+
+void xml_writer::push_element(const xml_name_t& _name)
+{
+ close_current_element();
+
+ auto& os = mp_impl->os;
+ xml_name_t name = mp_impl->intern(_name);
+
+ os << '<';
+ mp_impl->print(name);
+
+ for (std::string_view alias : mp_impl->ns_decls)
+ {
+ os << " xmlns";
+ if (!alias.empty())
+ os << ':' << alias;
+ os << "=\"";
+ xmlns_id_t ns = mp_impl->cxt.get(alias);
+ os << ns << '"';
+ }
+
+ for (const _attr& attr : mp_impl->attrs)
+ {
+ os << ' ';
+ mp_impl->print(attr.name);
+ os << "=\"";
+ os << attr.value << '"';
+ }
+
+ mp_impl->attrs.clear();
+ mp_impl->ns_decls.clear();
+
+ mp_impl->elem_stack.emplace_back(name);
+}
+
+xmlns_id_t xml_writer::add_namespace(std::string_view alias, std::string_view value)
+{
+ std::string_view alias_safe = mp_impl->intern(alias);
+ xmlns_id_t ns = mp_impl->cxt.push(alias_safe, mp_impl->intern(value));
+ mp_impl->ns_decls.push_back(alias_safe);
+ return ns;
+}
+
+void xml_writer::add_attribute(const xml_name_t& name, std::string_view value)
+{
+ mp_impl->attrs.emplace_back(mp_impl->intern(name), mp_impl->intern(value));
+}
+
+void xml_writer::add_content(std::string_view content)
+{
+ close_current_element();
+ write_content_encoded(content, mp_impl->os);
+}
+
+xml_name_t xml_writer::pop_element()
+{
+ auto& os = mp_impl->os;
+
+ const _elem& elem = mp_impl->elem_stack.back();
+ auto name = elem.name;
+
+ if (elem.open)
+ {
+ // self-closing element.
+ os << "/>";
+ }
+ else
+ {
+ os << "</";
+ mp_impl->print(name);
+ os << '>';
+ }
+
+ for (std::string_view alias : mp_impl->elem_stack.back().ns_aliases)
+ mp_impl->cxt.pop(alias);
+
+ mp_impl->elem_stack.pop_back();
+ return name;
+}
+
+} // namespace orcus
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */