
.. _program_listing_file_zeem_node.hpp:

Program Listing for File node.hpp
=================================

|exhale_lsh| :ref:`Return to documentation for file <file_zeem_node.hpp>` (``zeem/node.hpp``)

.. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS

.. code-block:: cpp

   /*-
    * SPDX-License-Identifier: BSD-2-Clause
    *
    * Copyright (c) 2024 Maarten L. Hekkelman
    *
    * Redistribution and use in source and binary forms, with or without
    * modification, are permitted provided that the following conditions are met:
    *
    * 1. Redistributions of source code must retain the above copyright notice, this
    *    list of conditions and the following disclaimer
    * 2. Redistributions in binary form must reproduce the above copyright notice,
    *    this list of conditions and the following disclaimer in the documentation
    *    and/or other materials provided with the distribution.
    *
    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
   
   #pragma once
   
   
   #include "zeem/error.hpp"
   #include "zeem/version.hpp"
   
   #include <algorithm>
   #include <cassert>
   #include <compare>
   #include <cstddef>
   #include <cstdint>
   #include <initializer_list>
   #include <iosfwd>
   #include <iterator>
   #include <string>
   #include <string_view>
   #include <type_traits>
   #include <utility>
   #include <vector>
   
   namespace zeem
   {
   
   // forward declarations
   class attribute;
   class element;
   class element_container;
   class node;
   class text;
   
   using node_set = std::vector<node *>;
   using element_set = std::vector<element *>;
   
   template <typename T>
   concept NodeType = std::is_base_of_v<zeem::node, std::remove_cvref_t<T>>;
   
   
   enum class node_type : uint8_t
   {
       element,
       text,
       attribute,
       comment,
       cdata,
       document,
       processing_instruction,
   
       header
   };
   
   // --------------------------------------------------------------------
   
   struct format_info
   {
       bool indent = false;
       bool indent_attributes = false;
       bool collapse_tags = true;
       bool suppress_comments = false;
       bool escape_white_space = false;
       bool escape_double_quote = true;
       bool html = false; 
       std::size_t indent_width = 0;
       std::size_t indent_level = 0;
       version_type version{ 1, 0 };
   };
   
   // --------------------------------------------------------------------
   
   
   class node
   {
     public:
   
       node &operator=(const node &n) = delete;
       node &operator=(node &&n) = delete;
   
       virtual ~node() = default;
   
   
       [[nodiscard]] virtual constexpr node_type type() const = 0;
   
       [[nodiscard]] virtual std::string lang() const;
   
       [[nodiscard]] virtual std::string get_qname() const;
   
       virtual void set_qname([[maybe_unused]] std::string qn) {} // NOLINT(performance-unnecessary-value-param)
   
   
       void set_qname(const std::string &prefix, std::string name)
       {
           set_qname(prefix.empty() ? std::move(name) : prefix + ':' + name);
       }
   
       [[nodiscard]] virtual std::string name() const;       
       [[nodiscard]] virtual std::string get_prefix() const; 
       [[nodiscard]] virtual std::string get_ns() const;     
   
       [[nodiscard]] virtual std::string namespace_for_prefix(std::string_view prefix) const;
   
       [[nodiscard]] virtual std::pair<std::string, bool> prefix_for_namespace(std::string_view uri) const;
   
       [[nodiscard]] virtual std::string prefix_tag(const std::string &tag, std::string_view uri) const;
   
       [[nodiscard]] virtual std::string str() const = 0;
   
       // --------------------------------------------------------------------
       // low level routines
   
       // basic access
   
       // All nodes should have a single root node
       virtual element_container *root();                           
       [[nodiscard]] virtual const element_container *root() const; 
   
       void parent(element_container *p) noexcept { m_parent = p; }               
       element_container *parent() { return m_parent; }                           
       [[nodiscard]] const element_container *parent() const { return m_parent; } 
   
       void next(const node *n) noexcept { m_next = const_cast<node *>(n); } 
       node *next() { return m_next; }                                       
       [[nodiscard]] const node *next() const { return m_next; }             
   
       void prev(const node *n) noexcept { m_prev = const_cast<node *>(n); } 
       node *prev() { return m_prev; }                                       
       [[nodiscard]] const node *prev() const { return m_prev; }             
   
       virtual bool equals(const node *n) const;
   
       virtual void write(std::ostream &os, format_info fmt) const = 0;
   
     protected:
   
       friend class basic_node_list;
       template <typename>
       friend class node_list;
       friend class element;
   
       node()
       {
           init();
       }
   
       node([[maybe_unused]] const node &n)
       {
           init();
       }
   
       node([[maybe_unused]] node &&n) noexcept
       {
           init();
       }
   
       friend void swap(node &a, node &b) noexcept
       {
           if (a.m_next == &a) // a empty?
           {
               if (b.m_next != &b) // b empty?
               {
                   a.m_next = b.m_next;
                   a.m_prev = b.m_prev;
                   b.init();
               }
           }
           else if (b.m_next == &b)
           {
               b.m_next = a.m_next;
               b.m_prev = a.m_prev;
               a.init();
           }
           else
           {
               std::swap(a.m_next, b.m_next);
               std::swap(a.m_prev, b.m_prev);
           }
   
           a.m_next->m_prev = a.m_prev->m_next = &a;
           b.m_next->m_prev = b.m_prev->m_next = &b;
       }
   
     protected:
       void init()
       {
           m_next = m_prev = this;
       }
   
       element_container *m_parent = nullptr;
       node *m_next{};
       node *m_prev{};
   
   };
   
   // --------------------------------------------------------------------
   // Basic node list is a private class, it is the base class
   // for node_list
   
   
   class basic_node_list
   {
     protected:
       struct node_list_header : public node
       {
           [[nodiscard]] constexpr node_type type() const override { return node_type::header; }
   
           void write(std::ostream & /* os */, format_info /* fmt */) const override {}
           [[nodiscard]] std::string str() const override { return {}; }
   
           friend void swap(node_list_header &a, node_list_header &b) noexcept
           {
               swap(static_cast<node &>(a), static_cast<node &>(b));
   
               for (node *n = a.m_next; n != &a; n = n->next())
                   n->parent(a.m_parent);
   
               for (node *n = b.m_next; n != &b; n = n->next())
                   n->parent(b.m_parent);
           }
       };
   
     private:
       node_list_header m_header_node;
       node *m_header = nullptr;
       bool m_owner = false;
   
       template <typename T>
       friend class node_list;
   
     protected:
       explicit basic_node_list(element_container *e)
           : m_header(&m_header_node)
       {
           m_header_node.parent(e);
       }
   
     public:
       basic_node_list(basic_node_list &&nl) = delete;
       basic_node_list &operator=(const basic_node_list &nl) = delete;
       basic_node_list &operator=(basic_node_list &&nl) = delete;
       virtual ~basic_node_list() = default;
   
       bool operator==(const basic_node_list &b) const;
   
       virtual void clear();
   
     protected:
       friend void swap(basic_node_list &a, basic_node_list &b) noexcept
       {
           if (a.m_header != &a.m_header_node and b.m_header != &b.m_header_node)
               std::swap(a.m_header, b.m_header);
           else
           {
               assert((a.m_header != &a.m_header_node) == (b.m_header != &b.m_header_node));
               swap(a.m_header_node, b.m_header_node);
           }
       }
   
     protected:
       // proxy methods for every insertion
   
       virtual node *insert_impl(const node *p, node *n);
   
       node *erase_impl(node *n);
   };
   
   
   // --------------------------------------------------------------------
   
   
   template <typename T>
   class iterator_impl
   {
     public:
   
       template <typename T2>
       friend class iterator_impl;
   
   
       using iterator_category = std::bidirectional_iterator_tag;
       using value_type = T;
       using pointer = value_type *;
       using reference = value_type &;
       using difference_type = std::ptrdiff_t;
   
       iterator_impl() = default;
   
       // NOLINTBEGIN(hicpp-explicit-conversions)
       iterator_impl(node *current)
           : m_current(current)
       {
           skip();
       }
   
       iterator_impl(const node *current)
           requires(std::is_const_v<value_type>)
           : iterator_impl(const_cast<node *>(current))
       {
       }
   
       iterator_impl(const iterator_impl &i) = default;
   
       template <typename Iterator>
           requires(std::is_base_of_v<value_type, typename Iterator::value_type>)
       iterator_impl(const Iterator &i)
           : m_current(const_cast<node *>(i.m_current))
       {
           skip();
       }
   
       // NOLINTEND(hicpp-explicit-conversions)
   
       iterator_impl &operator=(iterator_impl i)
       {
           m_current = i.m_current;
           return *this;
       }
   
       template <typename Iterator>
           requires(std::is_base_of_v<value_type, typename Iterator::value_type>)
       iterator_impl &operator=(const Iterator &i)
       {
           m_current = i.m_current;
           return *this;
       }
   
       reference operator*() const { return *static_cast<value_type *>(m_current); }
       pointer operator->() const { return static_cast<value_type *>(m_current); }
   
       iterator_impl &operator++()
       {
           m_current = m_current->next();
           skip();
           return *this;
       }
   
       iterator_impl operator++(int)
       {
           iterator_impl iter(*this);
           operator++();
           return iter;
       }
   
       iterator_impl &operator--()
       {
           m_current = m_current->prev();
           if constexpr (std::is_same_v<std::remove_cv_t<value_type>, element>)
           {
               while (m_current->type() != node_type::element and m_current->type() != node_type::header)
                   m_current = m_current->prev();
           }
   
           return *this;
       }
   
       iterator_impl operator--(int)
       {
           iterator_impl iter(*this);
           operator--();
           return iter;
       }
   
       template <typename IteratorType>
           requires NodeType<typename IteratorType::node_type>
       bool operator==(const IteratorType &other) const
       {
           return m_current == other.m_current;
       }
   
       bool operator==(const iterator_impl &other) const
       {
           return m_current == other.m_current;
       }
   
       template <NodeType T2>
       bool operator==(const T2 *n) const { return m_current == n; }
   
       explicit operator pointer() const { return static_cast<pointer>(m_current); }
   
     private:
       using node_base_type = std::conditional_t<std::is_const_v<T>, const node, node>;
   
       void skip()
       {
           if constexpr (std::is_same_v<std::remove_cv_t<value_type>, element>)
           {
               while (m_current->type() != node_type::element and m_current->type() != node_type::header)
                   m_current = m_current->next();
           }
       }
   
       node_base_type *m_current = nullptr;
   };
   
   // --------------------------------------------------------------------
   
   
   template <typename T = node>
   class node_list : public basic_node_list
   {
     public:
       using value_type = T;
       using allocator_type = std::allocator<value_type>;
       using size_type = size_t;
       using difference_type = std::ptrdiff_t;
       using reference = value_type &;
       using const_reference = const value_type &;
       using pointer = value_type *;
       using const_pointer = const value_type *;
   
     private:
       node_list(element_container *e); // NOLINT(hicpp-explicit-conversions)
       friend class element_container;
       friend class attribute_set;
   
     public:
       using iterator = iterator_impl<value_type>;
       static_assert(std::input_iterator<iterator>);
   
       using const_iterator = iterator_impl<const value_type>;
       static_assert(std::input_iterator<const_iterator>);
   
       [[nodiscard]] iterator begin() { return iterator(m_header->m_next); }
       [[nodiscard]] iterator end() { return iterator(m_header); }
   
       [[nodiscard]] const_iterator cbegin() { return const_iterator(m_header->m_next); }
       [[nodiscard]] const_iterator cend() { return const_iterator(m_header); }
   
       [[nodiscard]] const_iterator begin() const { return const_iterator(m_header->m_next); }
       [[nodiscard]] const_iterator end() const { return const_iterator(m_header); }
   
       [[nodiscard]] value_type &front() { return *begin(); }
       [[nodiscard]] const value_type &front() const { return *begin(); }
   
       [[nodiscard]] value_type &back() { return *std::prev(end()); }
       [[nodiscard]] const value_type &back() const { return *std::prev(end()); }
   
       [[nodiscard]] size_t size() const { return std::distance(begin(), end()); }
       [[nodiscard]] bool empty() const { return size() == 0; }
       explicit operator bool() const { return not empty(); }
   
       iterator insert(const_iterator pos, const value_type &e);
   
       iterator insert(const_iterator pos, value_type &&e);
   
   
       // TODO: maarten - When users try to emplace/insert e.g. a cdata node in an element
       // this will fail, since they need to use the nodes() variant. However,
       // a better error is required in that case. Perhaps using concepts?
   
       template <typename... Args>
       iterator insert(const_iterator p, Args &&...args)
           requires(sizeof...(Args) > 1 or not std::is_base_of_v<node, std::remove_cvref_t<Args>...>)
       {
           return insert_impl(p, new value_type(std::forward<Args>(args)...));
       }
   
       iterator insert(const_iterator pos, size_t count, const value_type &n)
       {
           iterator p(const_cast<value_type *>(&*pos));
           while (count-- > 0)
               p = insert(p, n);
           return p;
       }
   
       template <typename InputIter>
       iterator insert(const_iterator pos, InputIter first, InputIter last)
       {
           iterator p(const_cast<value_type *>(&*pos));
           iterator result = p;
           bool f = true;
           for (auto i = first; i != last; ++i, ++p)
           {
               p = insert(p, *i);
               if (std::exchange(f, false))
                   result = p;
           }
           return result;
       }
   
       iterator insert(const_iterator pos, std::initializer_list<value_type> nodes)
       {
           return insert(pos, nodes.begin(), nodes.end());
       }
   
       template <typename InputIter>
       void assign(InputIter first, InputIter last)
       {
           basic_node_list::clear();
           insert(begin(), first, last);
       }
   
       template <typename... Args>
       iterator emplace(const_iterator p, Args &&...args)
       {
           return insert(p, std::forward<Args>(args)...);
       }
   
       template <typename... Args>
       iterator emplace_front(Args &&...args)
       {
           return emplace(begin(), std::forward<Args>(args)...);
       }
   
       template <typename... Args>
       iterator emplace_back(Args &&...args)
       {
           return emplace(end(), std::forward<Args>(args)...);
       }
   
       iterator erase(const_iterator pos)
       {
           return erase_impl(pos);
       }
   
       iterator erase(iterator first, iterator last)
       {
           while (first != last)
           {
               auto next = first;
               ++next;
   
               erase(first);
               first = next;
           }
           return last;
       }
   
       void pop_front()
       {
           erase(begin());
       }
   
       void pop_back()
       {
           erase(std::prev(end()));
       }
   
       void push_front(value_type &&e)
       {
           emplace(begin(), std::forward<value_type>(e));
       }
   
       void push_front(const value_type &e)
       {
           emplace(begin(), e);
       }
   
       void push_back(value_type &&e)
       {
           emplace(end(), std::forward<value_type>(e));
       }
   
       void push_back(const value_type &e)
       {
           emplace(end(), e);
       }
   
       template <typename Pred>
       void sort(const Pred &pred);
   
     protected:
       using basic_node_list::insert_impl;
   
       node *insert_impl(const_iterator pos, node *n)
       {
           return insert_impl(&*pos, n);
       }
   
       using basic_node_list::erase_impl;
   
       node *erase_impl(iterator pos)
       {
           return basic_node_list::erase_impl(&*pos);
       }
       friend T;
   };
   
   // --------------------------------------------------------------------
   
   
   class element_container : public node, public node_list<element>
   {
     public:
       element_container()
           : node_list<element>(this)
       {
       }
   
       element_container(const element_container &e)
           : node(e)
           , node_list<element>(this)
       {
           auto a = nodes();
           auto b = e.nodes();
           a.assign(b.begin(), b.end());
       }
   
       ~element_container() override
       {
           clear();
       }
   
       // --------------------------------------------------------------------
   
       friend void swap(element_container &a, element_container &b) noexcept
       {
           swap(static_cast<node_list<element> &>(a), static_cast<node_list<element> &>(b));
       }
   
       // --------------------------------------------------------------------
       // children
   
       node_list<> nodes() { return { this }; }
   
       [[nodiscard]] const node_list<> nodes() const { return node_list<node>(const_cast<element_container *>(this)); }
   
       [[nodiscard]] std::string str() const override;
   
       [[nodiscard]] element_set find(std::string_view path) const;
   
       [[nodiscard]] iterator find_first(std::string_view path);
       [[nodiscard]] const_iterator find_first(std::string_view path) const;
   
       void write(std::ostream &os, format_info fmt) const override;
   };
   
   // --------------------------------------------------------------------
   // internal node base class for storing text
   
   
   class node_with_text : public node
   {
     protected:
   
       node_with_text() = default;
   
       explicit node_with_text(std::string s)
           : m_text(std::move(s))
       {
       }
   
       node_with_text(const node_with_text &n) = default;
       node_with_text(node_with_text &&n) = default;
   
     public:
       friend void swap(node_with_text &a, node_with_text &b) noexcept
       {
           std::swap(a.m_text, b.m_text);
       }
   
   
       [[nodiscard]] std::string str() const override { return m_text; }
   
       [[nodiscard]] virtual std::string get_text() const { return m_text; }
   
       virtual void set_text(std::string text) { m_text = std::move(text); }
   
       bool equals(const node *n) const override
       {
           return type() == n->type() and
                  static_cast<const node_with_text *>(n)->m_text == m_text;
       }
   
   
     protected:
       void append_text(std::string_view txt)
       {
           m_text += txt;
       }
   
       std::string m_text;
   };
   
   // --------------------------------------------------------------------
   
   
   class comment final : public node_with_text
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::comment; }
   
       explicit comment(std::string text = {})
           : node_with_text(std::move(text))
       {
       }
   
       comment(const comment &c) = default;
   
       comment(comment &&c) noexcept
       {
           swap(*this, c);
       }
   
       comment &operator=(comment c) noexcept
       {
           swap(*this, c);
           return *this;
       }
   
       bool equals(const node *n) const override
       {
           return this == n or (n->type() == node_type::comment and node_with_text::equals(n));
       }
   
       void write(std::ostream &os, format_info fmt) const override;
   };
   
   // --------------------------------------------------------------------
   
   class processing_instruction final : public node_with_text
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::processing_instruction; }
   
       processing_instruction() = default;
   
       processing_instruction(std::string target, std::string text)
           : node_with_text(std::move(text))
           , m_target(std::move(target))
       {
       }
   
       processing_instruction(const processing_instruction &pi) = default;
   
       processing_instruction(processing_instruction &&pi) noexcept = default;
   
       processing_instruction &operator=(processing_instruction pi) noexcept
       {
           swap(*this, pi);
           return *this;
       }
   
       friend void swap(processing_instruction &a, processing_instruction &b) noexcept
       {
           swap(static_cast<node_with_text &>(a), static_cast<node_with_text &>(b));
           std::swap(a.m_target, b.m_target);
       }
   
       [[nodiscard]] std::string get_qname() const override { return m_target; }
   
       [[nodiscard]] std::string get_target() const { return m_target; }
   
       void set_target(std::string target) { m_target = std::move(target); }
   
       bool equals(const node *n) const override
       {
           return this == n or (n->type() == node_type::processing_instruction and node_with_text::equals(n));
       }
   
       void write(std::ostream &os, format_info fmt) const override;
   
     private:
       std::string m_target;
   
   };
   
   // --------------------------------------------------------------------
   
   class text final : public node_with_text
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::text; }
   
       explicit text(std::string text = {})
           : node_with_text(std::move(text))
       {
       }
   
       text(const text &t) = default;
   
       text(text &&t) noexcept = default;
   
       text &operator=(text txt) noexcept
       {
           swap(*this, txt);
           return *this;
       }
   
       void append(std::string_view text) { append_text(text); }
   
       bool equals(const node *n) const override;
   
       [[nodiscard]] bool is_space() const;
   
       void write(std::ostream &os, format_info fmt) const override;
   };
   
   // --------------------------------------------------------------------
   
   class cdata final : public node_with_text
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::cdata; }
   
       explicit cdata(std::string s = {})
           : node_with_text(std::move(s))
       {
       }
   
       cdata(const cdata &cd) = default;
   
       cdata(cdata &&cd) noexcept = default;
   
       cdata &operator=(cdata cd) noexcept
       {
           swap(*this, cd);
           return *this;
       }
   
       void append(std::string_view text) { append_text(text); }
   
       bool equals(const node *n) const override
       {
           return this == n or (n->type() == node_type::cdata and node_with_text::equals(n));
       }
   
       void write(std::ostream &os, format_info fmt) const override;
   };
   
   // --------------------------------------------------------------------
   
   class attribute final : public node
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::attribute; }
   
       attribute(std::string_view qname, std::string_view value, bool id = false)
           : m_qname(qname)
           , m_value(value)
           , m_id(id)
       {
       }
   
       attribute(const attribute &attr) = default;
   
       attribute(attribute &&attr) noexcept
           : node(std::forward<attribute>(attr))
       {
           swap(*this, attr);
       }
   
       attribute &operator=(attribute attr) noexcept
       {
           swap(*this, attr);
           return *this;
       }
   
       friend void swap(attribute &a, attribute &b) noexcept
       {
           std::swap(a.m_qname, b.m_qname);
           std::swap(a.m_value, b.m_value);
           std::swap(a.m_id, b.m_id);
       }
   
       friend std::strong_ordering operator<=>(const attribute &a, const attribute &b)
       {
           if (auto cmp = (a.m_qname <=> b.m_qname); cmp != 0)
               return cmp;
           if (auto cmp = (a.m_id <=> b.m_id); cmp != 0)
               return cmp;
           return a.m_value <=> b.m_value;
       }
   
       bool operator==(const attribute &rhs) const
       {
           return equals(&rhs);
       }
   
       [[nodiscard]] std::string get_qname() const override { return m_qname; }
   
       void set_qname(std::string qn) override { m_qname = std::move(qn); }
   
       using node::set_qname;
   
       [[nodiscard]] bool is_namespace() const
       {
           return m_qname.starts_with("xmlns") and (m_qname.length() == 5 or m_qname[5] == ':');
       }
   
       [[nodiscard]] std::string value() const { return m_value; }
   
       void set_value(std::string v) { m_value = std::move(v); }
   
       void set_value(std::string_view v) { m_value = v; }
   
       [[nodiscard]] std::string uri() const;
   
       [[nodiscard]] std::string str() const override { return m_value; }
   
       bool equals(const node *n) const override
       {
           bool result = false;
           if (type() == n->type())
           {
               auto an = static_cast<const attribute *>(n);
               result = an->m_id == m_id and an->m_qname == m_qname and an->m_value == m_value;
           }
           return result;
       }
   
       [[nodiscard]] bool is_id() const { return m_id; }
   
       template <size_t N>
       [[nodiscard]] decltype(auto) get() const
       {
           if constexpr (N == 0)
               return name();
           else if constexpr (N == 1)
               return value();
       }
   
       void write(std::ostream &os, format_info fmt) const override;
   
     private:
       std::string m_qname, m_value;
       bool m_id{};
   };
   
   // --------------------------------------------------------------------
   
   class attribute_set : public node_list<attribute>
   {
     public:
       explicit attribute_set(element_container *el)
           : node_list(el)
       {
       }
   
       ~attribute_set() override
       {
           clear();
       }
   
       friend void swap(attribute_set &a, attribute_set &b) noexcept
       {
           swap(static_cast<node_list<attribute> &>(a), static_cast<node_list<attribute> &>(b));
       }
   
       [[nodiscard]] bool contains(std::string_view key) const
       {
           return find(key) != end();
       }
   
       [[nodiscard]] const_iterator find(std::string_view key) const
       {
           for (auto i = begin(); i != end(); ++i)
           {
               if (i->get_qname() == key)
                   return i;
           }
           return end();
       }
   
       iterator find(std::string_view key)
       {
           return iterator{ const_cast<const attribute_set &>(*this).find(key) };
       }
   
       template <typename... Args>
       std::pair<iterator, bool> emplace(Args &&...args)
       {
           return emplace(value_type{ std::forward<decltype(args)>(args)... });
       }
   
       std::pair<iterator, bool> emplace(value_type &&a)
       {
           bool inserted = false;
   
           auto i = find(a.get_qname());
   
           if (i != node_list::end())
               *i = std::forward<value_type>(a); // move assign value of a
           else
           {
               i = iterator{ node_list::insert_impl(node_list::end(), new attribute(std::forward<value_type>(a))) };
               inserted = true;
           }
   
           return std::make_pair(i, inserted);
       }
   
       using node_list::erase;
   
       size_type erase(std::string_view key)
       {
           size_type result = 0;
           auto i = find(key);
           if (i != node_list::end())
           {
               erase(i);
               result = 1;
           }
           return result;
       }
   };
   
   // --------------------------------------------------------------------
   
   class element final : public element_container
   {
     public:
       [[nodiscard]] constexpr node_type type() const override { return node_type::element; }
   
       element()
           : m_attributes(this)
       {
       }
   
       explicit element(std::string_view qname, std::initializer_list<attribute> attributes = {})
           : m_qname(qname)
           , m_attributes(this)
       {
           m_attributes.assign(attributes.begin(), attributes.end());
       }
   
       element(std::string_view qname, std::initializer_list<element> il)
           : m_qname(qname)
           , m_attributes(this)
       {
           assign(il.begin(), il.end());
       }
   
       element(const element &e)
           : element_container(e)
           , m_qname(e.m_qname)
           , m_attributes(this)
       {
           m_attributes.assign(e.m_attributes.begin(), e.m_attributes.end());
       }
   
       element(element &&e) noexcept
           : m_attributes(this)
       {
           swap(*this, e);
       }
   
       element &operator=(element e) noexcept
       {
           swap(*this, e);
           return *this;
       }
   
       friend void swap(element &a, element &b) noexcept
       {
           // swap(static_cast<node&>(a), static_cast<node&>(b));
           swap(static_cast<element_container &>(a), static_cast<element_container &>(b));
   
           std::swap(a.m_qname, b.m_qname);
           swap(a.m_attributes, b.m_attributes);
       }
   
       using node::set_qname;
   
       [[nodiscard]] std::string get_qname() const override { return m_qname; }
   
       void set_qname(std::string qn) override { m_qname = std::move(qn); }
   
       [[nodiscard]] std::string lang() const override;
   
       [[nodiscard]] std::string id() const;
   
       bool operator==(const element &e) const
       {
           return equals(&e);
       }
   
       bool equals(const node *n) const override;
   
       // --------------------------------------------------------------------
       // attribute support
   
       attribute_set &attributes() { return m_attributes; }
   
       [[nodiscard]] const attribute_set &attributes() const { return m_attributes; }
   
       // --------------------------------------------------------------------
   
       [[nodiscard]] std::string namespace_for_prefix(std::string_view prefix) const override;
   
       [[nodiscard]] std::pair<std::string, bool> prefix_for_namespace(std::string_view uri) const override;
   
       void move_to_name_space(const std::string &prefix, std::string_view uri,
           bool recursive, bool including_attributes);
   
       // --------------------------------------------------------------------
   
       friend std::ostream &operator<<(std::ostream &os, const element &e);
       //  friend class document;
   
       [[nodiscard]] std::string get_content() const;
   
       void set_content(std::string content);
   
       [[nodiscard]] std::string get_attribute(std::string_view qname) const;
   
       void set_attribute(std::string_view qname, std::string_view value);
   
       void set_text(std::string s);
   
       void add_text(std::string s);
   
       void flatten_text();
   
       void write(std::ostream &os, format_info fmt) const override;
   
     private:
       std::string m_qname;
       attribute_set m_attributes;
   };
   
   // --------------------------------------------------------------------
   template <typename T>
   inline node_list<T>::node_list(element_container *e)
       : basic_node_list(e)
   {
       if constexpr (std::is_same_v<value_type, node>)
           m_header = e->m_header;
   }
   
   template <>
   inline auto node_list<element>::insert(const_iterator pos, const element &e) -> iterator
   {
       return iterator{ insert_impl(pos, new element(e)) };
   }
   
   template <>
   inline auto node_list<element>::insert(const_iterator pos, element &&e) -> iterator
   {
       return iterator{ insert_impl(pos, new element(std::forward<value_type>(e))) };
   }
   
   template <>
   inline auto node_list<attribute>::insert(const_iterator pos, const attribute &e) -> iterator
   {
       return iterator{ insert_impl(pos, new attribute(e)) };
   }
   
   template <>
   inline auto node_list<attribute>::insert(const_iterator pos, attribute &&e) -> iterator
   {
       return iterator{ insert_impl(pos, new attribute(std::forward<value_type>(e))) };
   }
   
   // NOLINTBEGIN(cppcoreguidelines-owning-memory,cppcoreguidelines-pro-type-static-cast-downcast)
   template <>
   inline auto node_list<node>::insert(const_iterator pos, const value_type &e) -> iterator
   {
       switch (e.type())
       {
           case node_type::element:
               return insert_impl(pos, new element(static_cast<const element &>(e)));
               break;
           case node_type::text:
               return insert_impl(pos, new text(static_cast<const text &>(e)));
               break;
           case node_type::attribute:
               return insert_impl(pos, new attribute(static_cast<const attribute &>(e)));
               break;
           case node_type::comment:
               return insert_impl(pos, new comment(static_cast<const comment &>(e)));
               break;
           case node_type::cdata:
               return insert_impl(pos, new cdata(static_cast<const cdata &>(e)));
               break;
           case node_type::processing_instruction:
               return insert_impl(pos, new processing_instruction(static_cast<const processing_instruction &>(e)));
               break;
           default:
               throw exception("internal error");
       }
   }
   
   template <>
   inline auto node_list<node>::insert(const_iterator pos, value_type &&e) -> iterator
   {
       switch (e.type())
       {
           case node_type::element:
               return insert_impl(pos, new element(std::forward<element &&>(static_cast<element &&>(e))));
               break;
           case node_type::text:
               return insert_impl(pos, new text(std::forward<text &&>(static_cast<text &&>(e))));
               break;
           case node_type::attribute:
               return insert_impl(pos, new attribute(std::forward<attribute &&>(static_cast<attribute &&>(e))));
               break;
           case node_type::comment:
               return insert_impl(pos, new comment(std::forward<comment &&>(static_cast<comment &&>(e))));
               break;
           case node_type::cdata:
               return insert_impl(pos, new cdata(std::forward<cdata &&>(static_cast<cdata &&>(e))));
               break;
           case node_type::processing_instruction:
               return insert_impl(pos, new processing_instruction(std::forward<processing_instruction &&>(static_cast<processing_instruction &&>(e))));
               break;
           default:
               throw exception("internal error");
       }
   }
   
   // NOLINTEND(cppcoreguidelines-owning-memory,cppcoreguidelines-pro-type-static-cast-downcast)
   
   template <typename T>
   template <typename Pred>
   void node_list<T>::sort(const Pred &pred)
   {
       std::vector<node *> t;
       for (auto n = m_header->m_next; n != m_header; n = n->m_next)
           t.push_back(n);
   
       std::sort(t.begin(), t.end(), [pred](node *a, node *b)
           { return pred(static_cast<T &>(*a), static_cast<T &>(*b)); });
   
       auto p = m_header;
       for (auto n : t)
       {
           n->m_prev = p;
           p->m_next = n;
           p = n;
       }
       p->m_next = m_header;
       m_header->m_prev = p;
   }
   
   
   // --------------------------------------------------------------------
   
   
   void fix_namespaces(element &e, const element &source, const element &dest);
   
   } // namespace zeem
   
   // --------------------------------------------------------------------
   // structured binding support
   
   namespace std
   {
   
   template <>
   struct tuple_size<::zeem::attribute>
       : public std::integral_constant<std::size_t, 2>
   {
   };
   
   template <>
   struct tuple_element<0, ::zeem::attribute>
   {
       using type = decltype(std::declval<::zeem::attribute>().name());
   };
   
   template <>
   struct tuple_element<1, ::zeem::attribute>
   {
       using type = decltype(std::declval<::zeem::attribute>().value());
   };
   
   
   } // namespace std
