// file      : xsd/cxx/xml/dom/elements.hxx
// author    : Boris Kolpackov <boris@codesynthesis.com>
// copyright : Copyright (c) 2005-2006 Code Synthesis Tools CC
// license   : GNU GPL v2 + exceptions; see accompanying LICENSE file

#ifndef XSD_CXX_XML_DOM_ELEMENTS_HXX
#define XSD_CXX_XML_DOM_ELEMENTS_HXX

#include <string>

#include <xsd/cxx/xml/string.hxx>
#include <xsd/cxx/xml/elements.hxx>
#include <xsd/cxx/xml/error-handler.hxx>
#include <xsd/cxx/xml/bits/literals.hxx>

#include <xercesc/dom/DOM.hpp>

namespace xsd
{
  namespace cxx
  {
    namespace xml
    {
      namespace dom
      {
        // Simple auto_ptr version that calls release() instead of delete.
        //

        template <typename X>
        struct remove_c
        {
          typedef X r;
        };

        template <typename X>
        struct remove_c<X const>
        {
          typedef X r;
        };

        template <typename X>
        struct auto_ptr_ref
        {
          X* x_;

          explicit
          auto_ptr_ref (X* x)
              : x_ (x)
          {
          }
        };

        template <typename X>
        struct auto_ptr
        {
          ~auto_ptr ()
          {
            reset ();
          }

          explicit
          auto_ptr (X* x = 0)
              : x_ (x)
          {
          }

          auto_ptr (auto_ptr& y)
              : x_ (y.release ())
          {
          }

          template <typename Y>
          auto_ptr (auto_ptr<Y>& y)
              : x_ (y.release ())
          {
          }

          auto_ptr (auto_ptr_ref<X> r)
              : x_ (r.x_)
          {
          }

          auto_ptr&
          operator= (auto_ptr& y)
          {
            if (x_ != y.x_)
            {
              reset (y.release ());
            }

            return *this;
          }

          template <typename Y>
          auto_ptr&
          operator= (auto_ptr<Y>& y)
          {
            if (x_ != y.x_)
            {
              reset (y.release ());
            }

            return *this;
          }

          auto_ptr&
          operator= (auto_ptr_ref<X> r)
          {
            if (r.x_ != x_)
            {
              reset (r.x_);
            }

            return *this;
          }

          template <typename Y>
          operator auto_ptr_ref<Y> ()
          {
            return auto_ptr_ref<Y> (release ());
          }

          template <typename Y>
          operator auto_ptr<Y> ()
          {
            return auto_ptr<Y> (release ());
          }

        public:
          X&
          operator* () const
          {
            return *x_;
          }

          X*
          operator-> () const
          {
            return x_;
          }

          X*
          get () const
          {
            return x_;
          }

          X*
          release ()
          {
            X* x (x_);
            x_ = 0;
            return x;
          }

          void
          reset (X* x = 0)
          {
            if (x_)
              const_cast<typename remove_c<X>::r*> (x_)->release ();

            x_ = x;
          }

          // Conversion to bool.
          //
          typedef X* (auto_ptr::*boolean_convertible)() const;

          operator boolean_convertible () const throw ()
          {
            return x_ ? &auto_ptr<X>::operator-> : 0;
          }

        private:
          X* x_;
        };


        //
        //
        template <typename C>
        class element;


        template <typename C>
        std::basic_string<C>
        prefix (std::basic_string<C> const& ns, element<C> const& e);

        using xml::prefix;

        //
        //
        template <typename C>
        class element
        {
          typedef std::basic_string<C> string_type;

        public:
          element (xercesc::DOMElement const* e)
              : e_ (0),
                ce_ (e),
                name_ (transcode<C> (e->getLocalName ())),
                namespace__ (transcode<C> (e->getNamespaceURI ()))
          {
          }

          element (xercesc::DOMElement* e)
              : e_ (e),
                ce_ (e),
                name_ (transcode<C> (e->getLocalName ())),
                namespace__ (transcode<C> (e->getNamespaceURI ()))
          {
          }

          element (string_type const& name, element& parent)
              : e_ (0),
                ce_ (0),
                name_ (name)
          {
            xercesc::DOMDocument* doc (
              parent.dom_element ()->getOwnerDocument ());

            e_ = doc->createElement (string (name).c_str ());

            parent.dom_element ()->appendChild (e_);

            ce_ = e_;
          }

          element (string_type const& name,
                   string_type const& ns,
                   element& parent)
              : e_ (0),
                ce_ (0),
                name_ (name),
                namespace__ (ns)
          {
            string_type p (prefix (ns, parent));

            xercesc::DOMDocument* doc (
              parent.dom_element ()->getOwnerDocument ());

            e_ = doc->createElementNS (
              string (ns).c_str (),
              string (p.empty ()
                      ? name
                      : p + string_type (1, ':') + name).c_str ());

            parent.dom_element ()->appendChild (e_);

            ce_ = e_;
          }

        public:
          string_type
          name () const
          {
            return name_;
          }

          string_type
          namespace_ () const
          {
            return namespace__;
          }

        public:
          element<C>
          parent () const
          {
            return
              dynamic_cast<xercesc::DOMElement const*> (ce_->getParentNode ());
          }

        public:
          string_type
          value () const
          {
            return transcode<C> (dom_element ()->getTextContent ());
          }

          void
          value (string_type const& v)
          {
            xercesc::DOMText* text (
              dom_element ()->getOwnerDocument ()->createTextNode(
                string (v).c_str ()));

            dom_element ()->appendChild (text);
          }

        public:
          string_type
          operator[] (string_type const& name) const
          {
            XMLCh const* value (ce_->getAttribute (string (name).c_str ()));

            return transcode<C> (value);
          }

          string_type
          attribute (string_type const& name, string_type const& ns) const
          {
            XMLCh const* value (
              ce_->getAttributeNS (string (ns).c_str (),
                                   string (name).c_str ()));

            return transcode<C> (value);
          }

        public:
          xercesc::DOMElement const*
          dom_element () const
          {
            return ce_;
          }

          xercesc::DOMElement*
          dom_element ()
          {
            return e_;
          }

        private:
          xercesc::DOMElement* e_;
          xercesc::DOMElement const* ce_;

          string_type name_;
          string_type namespace__;
        };


        template <typename C>
        class attribute
        {
          typedef std::basic_string<C> string_type;

        public:
          attribute (xercesc::DOMAttr const* a)
              : a_ (0),
                ca_ (a),
                name_ (transcode<C> (a->getLocalName ())),
                namespace__ (transcode<C> (a->getNamespaceURI ())),
                value_ (transcode<C> (a->getValue ()))
          {
          }

          attribute (xercesc::DOMAttr* a)
              : a_ (a),
                ca_ (a),
                name_ (transcode<C> (a->getLocalName ())),
                namespace__ (transcode<C> (a->getNamespaceURI ())),
                value_ (transcode<C> (a->getValue ()))
          {
          }

          attribute (string_type const& name,
                     element<C>& parent,
                     string_type const& v = string_type ())
              : a_ (0),
                ca_ (0),
                name_ (name),
                value_ ()
          {
            xercesc::DOMDocument* doc (
              parent.dom_element ()->getOwnerDocument ());

            a_ = doc->createAttribute (string (name).c_str ());

            if (!v.empty ())
              value (v);

            parent.dom_element ()->setAttributeNode (a_);

            ca_ = a_;
          }

          attribute (string_type const& name,
                     string_type const& ns,
                     element<C>& parent,
                     string_type const& v = string_type ())
              : a_ (0),
                ca_ (0),
                name_ (name),
                namespace__ (ns),
                value_ ()
          {
            string_type p (prefix (ns, parent));

            xercesc::DOMDocument* doc (
              parent.dom_element ()->getOwnerDocument ());

            a_ = doc->createAttributeNS (
              string (ns).c_str (),
              string (p.empty ()
                      ? name
                      : p + string_type (1, ':') + name).c_str ());

            if (!v.empty ())
              value (v);

            parent.dom_element ()->setAttributeNodeNS (a_);

            ca_ = a_;
          }

          string_type
          name () const
          {
            return name_;
          }

          string_type
          namespace_ () const
          {
            return namespace__;
          }

          string_type
          value () const
          {
            return value_;
          }

          void
          value (string_type const& v)
          {
            value_ = v;
            a_->setValue (string (v).c_str ());
          }

          dom::element<C>
          element () const
          {
            return dom::element<C> (
              static_cast<xercesc::DOMElement const*> (
                ca_->getOwnerElement ()));
          }

          dom::element<C>
          element ()
          {
            return dom::element<C> (a_->getOwnerElement ());
          }


        public:
          xercesc::DOMAttr const*
          dom_attribute () const
          {
            return ca_;
          }

          xercesc::DOMAttr*
          dom_attribute ()
          {
            return a_;
          }

        private:
          xercesc::DOMAttr* a_;
          xercesc::DOMAttr const* ca_;

          string_type name_;
          string_type namespace__;
          string_type value_;
        };

        struct no_mapping {};

        template <typename C>
        std::basic_string<C>
        ns_name (element<C> const& e, std::basic_string<C> const& n)
        {
          std::basic_string<C> p (prefix (n));

          // 'xml' prefix requires special handling and Xerces folks refuse
          // to handle this in DOM so I have to do it myself.
          //
          if (p == xml::bits::xml_prefix<C> ())
            return xml::bits::xml_namespace<C> ();

          XMLCh const* xns (e.dom_element ()->lookupNamespaceURI (
                              p.empty () ? 0 : string (p).c_str ()));

          if (xns == 0)
            throw no_mapping ();

          std::basic_string<C> ns (
            xns ? transcode<C> (xns) : std::basic_string<C> ());

          return ns;

        }


        template <typename C>
        std::basic_string<C>
        fq_name (element<C> const& e, std::basic_string<C> const& n)
        {
          std::basic_string<C> ns (ns_name (e, n));
          std::basic_string<C> un (uq_name (n));

          return ns.empty () ? un : (ns + C ('#') + un);
        }

        class no_prefix {};

        template <typename C>
        std::basic_string<C>
        prefix (std::basic_string<C> const& ns, element<C> const& e)
        {
          string xns (ns);

          XMLCh const* p (
            e.dom_element ()->lookupNamespacePrefix (xns.c_str (), false));

          if (p == 0)
          {
            bool r (e.dom_element ()->isDefaultNamespace (xns.c_str ()));

            if (r)
              return std::basic_string<C> ();
            else
            {
              // 'xml' prefix requires special handling and Xerces folks
              // refuse to handle this in DOM so I have to do it myself.
              //
              if (ns == xml::bits::xml_namespace<C> ())
                return xml::bits::xml_prefix<C> ();

              throw no_prefix ();
            }
          }

          return transcode<C> (p);
        }

        template <typename C>
        xml::dom::auto_ptr<xercesc::DOMDocument>
        dom (xercesc::DOMInputSource const&,
             error_handler<C>&,
             properties<C> const&,
             bool validate = true);

        template <typename C>
        xml::dom::auto_ptr<xercesc::DOMDocument>
        dom (xercesc::DOMInputSource const&,
             xercesc::DOMErrorHandler&,
             properties<C> const&,
             bool validate = true);

        template <typename C>
        xml::dom::auto_ptr<xercesc::DOMDocument>
        dom (std::basic_string<C> const& uri,
             error_handler<C>&,
             properties<C> const&,
             bool validate = true);

        template <typename C>
        xml::dom::auto_ptr<xercesc::DOMDocument>
        dom (std::basic_string<C> const& uri,
             xercesc::DOMErrorHandler&,
             properties<C> const&,
             bool validate = true);
      }
    }
  }
}

#include <xsd/cxx/xml/dom/elements.txx>

#endif  // XSD_CXX_XML_DOM_ELEMENTS_HXX
