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

#include <xsde/cxx/config.hxx>

#include <assert.h>
#include <string.h> // strchr, strlen

#ifdef XSDE_EXCEPTIONS
#  include <new> // std::bad_alloc
#endif

#ifdef XSDE_IOSTREAM
#  include <iostream>
#  include <fstream>
#endif

#ifdef XSDE_EXCEPTIONS
#  include <xsde/cxx/parser/exceptions.hxx>
#endif

#include <xsde/cxx/parser/expat/elements.hxx>

namespace xsde
{
  namespace cxx
  {
    namespace parser
    {
      namespace expat
      {
        // document
        //

	document::
	~document ()
	{
	  // Prevents auto-generation of this dtor.
        }

#ifdef XSDE_STL
        document::
        document (parser_base& parser, const std::string& name)
            : xml_parser_ (0),
              parser_ (&parser),
              depth_ (0)
        {
#ifdef XSDE_EXCEPTIONS
          root_ns_.assign ("", 0);
          root_name_.assign (name.c_str (), name.size ());
#else
          if (root_ns_.assign ("", 0) ||
              root_name_.assign (name.c_str (), name.size ()))
            error_ = parser::error (sys_error::no_memory);
#endif
        }

        document::
        document (parser_base& parser,
                  const std::string& ns,
                  const std::string& name)
            : xml_parser_ (0),
              parser_ (&parser),
              depth_ (0)
        {
#ifdef XSDE_EXCEPTIONS
          root_ns_.assign (ns.c_str (), ns.size ());
          root_name_.assign (name.c_str (), name.size ());
#else
          if (root_ns_.assign (ns.c_str (), ns.size ()) ||
              root_name_.assign (name.c_str (), name.size ()))
            error_ = parser::error (sys_error::no_memory);
#endif
        }
#endif // XSDE_STL

        document::
        document (parser_base& parser, const char* name)
            : xml_parser_ (0),
              parser_ (&parser),
              depth_ (0)
        {
#ifdef XSDE_EXCEPTIONS
          root_ns_.assign ("", 0);
          root_name_.assign (name);
#else
          if (root_ns_.assign ("", 0) || root_name_.assign (name))
            error_ = parser::error (sys_error::no_memory);
#endif
        }

        document::
        document (parser_base& parser,
                  const char* ns,
                  const char* name)
            : xml_parser_ (0),
              parser_ (&parser),
              depth_ (0)
        {
#ifdef XSDE_EXCEPTIONS
          root_ns_.assign (ns);
          root_name_.assign (name);
#else
          if (root_ns_.assign (ns) || root_name_.assign (name))
            error_ = parser::error (sys_error::no_memory);
#endif
        }

        document::
        document ()
            : xml_parser_ (0), parser_ (0), depth_ (0)
        {
        }

        parser_base* document::
        start_root_element (const ro_string& ns, const ro_string& name)
        {
          if (root_name_ == name && root_ns_ == ns)
          {
            return parser_;
          }
          else
          {
#ifdef XSDE_VALIDATION
            parser_->schema_error (schema_error::unexpected_element);
#endif
            return parser_; // Need a valid object to store error code.
          }
        }

        void document::
        end_root_element (const ro_string&, const ro_string&, parser_base*)
        {
        }

        // file
        //

#ifdef XSDE_IOSTREAM
        void document::
        parse (const char* file)
        {
          std::ifstream ifs;

#ifdef XSDE_EXCEPTIONS
          ifs.exceptions (std::ifstream::badbit | std::ifstream::failbit);
#endif

          ifs.open (file, std::ifstream::in | std::ifstream::binary);

#ifndef XSDE_EXCEPTIONS
          if (ifs.fail ())
          {
            error_ = parser::error (sys_error::open_failed);
            return;
          }
#endif
          parse (ifs);
        }

#ifdef XSDE_STL
        void document::
        parse (const std::string& file)
        {
          parse (file.c_str ());
        }
#endif

        // istream
        //

#ifdef XSDE_EXCEPTIONS
        namespace bits
        {
          struct stream_exception_controller
          {
            ~stream_exception_controller ()
            {
              if (is_.fail () && is_.eof ())
                is_.clear (is_.rdstate () & ~std::istream::failbit);

              is_.exceptions (old_state_);
            }

            stream_exception_controller (std::istream& is)
                : is_ (is), old_state_ (is_.exceptions ())
            {
              is_.exceptions (old_state_ & ~std::istream::failbit);
            }

          private:
            stream_exception_controller (const stream_exception_controller&);

            stream_exception_controller&
            operator= (const stream_exception_controller&);

          private:
            std::istream& is_;
            std::istream::iostate old_state_;
          };
        }
#endif

        void document::
        parse (std::istream& is)
        {
#ifdef XSDE_EXCEPTIONS
          // Temporarily unset the exception failbit. Also clear the
          // fail bit when we reset the old state if it was caused
          // by eof.
          //
          bits::stream_exception_controller sec (is);
#endif

          char buf[4096]; // Page size.

          do
          {
            is.read (buf, sizeof (buf));

#ifndef XSDE_EXCEPTIONS
            if (is.bad () || (is.fail () && !is.eof ()))
            {
              error_ = parser::error (sys_error::read_failed);
              break;
            }
#endif
            parse (buf, is.gcount (), is.eof ());

#ifndef XSDE_EXCEPTIONS
            if (error_)
              break;
#endif
          } while (!is.eof ());
        }
#endif // XSDE_IOSTREAM


        void document::
        parse (const void* data, size_t size, bool last)
        {
          // First call.
          //
          if (auto_xml_parser_ == 0)
          {
            auto_xml_parser_ = XML_ParserCreateNS (0, XML_Char (' '));

            if (auto_xml_parser_ == 0)
            {
#ifdef XSDE_EXCEPTIONS
              throw std::bad_alloc ();
#else
              error_ = parser::error (sys_error::no_memory);
              return;
#endif
            }

            parse_begin (auto_xml_parser_);
          }


          if (XML_Parse (xml_parser_,
                         static_cast<const char*> (data),
                         static_cast<int> (size),
                         last) == XML_STATUS_ERROR ||
              last)
          {
            parse_end ();
          }
        }


        // XML_Parser
        //

        void document::
        parse_begin (XML_Parser parser)
        {
          xml_parser_ = parser;
          set ();
        }

        void document::
        parse_end ()
        {
          XML_Error e (XML_GetErrorCode (xml_parser_));

          if (e == XML_ERROR_NONE)
          {
            clear ();
            xml_parser_ = 0;
            auto_xml_parser_ = 0;
          }
          else
          {
            unsigned long l = XML_GetCurrentLineNumber (xml_parser_);
            unsigned long c = XML_GetCurrentColumnNumber (xml_parser_);

            clear ();
            xml_parser_ = 0;
            auto_xml_parser_ = 0;

            // See if the parser was aborted.
            //
            if (e == XML_ERROR_ABORTED)
            {
              // Got to be either a system, schema, or application
              // level error.
              //
              if (parser_)
              {
#if defined(XSDE_VALIDATION) || !defined (XSDE_EXCEPTIONS)
                switch (parser_->error_type ())
                {
#ifndef XSDE_EXCEPTIONS
                case parser_base::error_sys:
                  {
                    error_ = parser::error (parser_->sys_error ());
                    break;
                  }
#endif
#ifdef XSDE_VALIDATION
                case parser_base::error_schema:
                  {
#ifdef XSDE_EXCEPTIONS
                    throw schema (parser_->schema_error (), l, c);
#else
                    error_ = parser::error (parser_->schema_error (), l, c);
                    break;
#endif
                  }
#endif
#ifndef XSDE_EXCEPTIONS
                case parser_base::error_app:
                  {
                    error_ = parser::error (parser_->app_error (), l, c);
                    break;
                  }
#endif
                default:
                  break;
                }
#endif // XSDE_VALIDATION || !XSDE_EXCEPTIONS
              }
            }
            else if (e == XML_ERROR_NO_MEMORY)
            {
#ifdef XSDE_EXCEPTIONS
              throw std::bad_alloc ();
#else
              error_ = parser::error (sys_error::no_memory);
#endif
            }
            else
            {
#ifdef XSDE_EXCEPTIONS
              throw xml (e, l, c);
#else
              error_ = parser::error (e, l, c);
#endif
            }
          }
        }


        //
        //
        void document::
        set ()
        {
          assert (xml_parser_ != 0);

          XML_SetUserData(xml_parser_, this);

          XML_SetStartElementHandler (xml_parser_, &start_element);
          XML_SetEndElementHandler (xml_parser_, &end_element);
          XML_SetCharacterDataHandler (xml_parser_, &characters);
        }

        void document::
        clear ()
        {
          assert (xml_parser_ != 0);

          XML_SetUserData (xml_parser_, 0);
          XML_SetStartElementHandler (xml_parser_, 0);
          XML_SetEndElementHandler (xml_parser_, 0);
          XML_SetCharacterDataHandler (xml_parser_, 0);
        }


        // Expat thunks.
        //

        void XMLCALL document::
        start_element (void* p, const XML_Char* name, const XML_Char** ats)
        {
          document& doc = *reinterpret_cast<document*> (p);
          doc.start_element_ (name, ats);
        }

        void XMLCALL document::
        end_element (void* p, const XML_Char* name)
        {
          document& doc = *reinterpret_cast<document*> (p);
          doc.end_element_ (name);
        }

        void XMLCALL document::
        characters (void* p, const XML_Char* s, int n)
        {
          document& doc = *reinterpret_cast<document*> (p);
          doc.characters_ (s, static_cast<size_t> (n));
        }

        namespace bits
        {
          inline void
          split_name (const XML_Char* s,
                      const char*& ns, size_t& ns_s,
                      const char*& name, size_t& name_s)
          {
            const char* p = strchr (s, ' ');

            if (p)
            {
              ns = s;
              ns_s = p - s;
              name = p + 1;
            }
            else
            {
              ns = s;
              ns_s = 0;
              name = s;
            }

            name_s = strlen (name);
          }
        }

        void document::
        start_element_ (const XML_Char* ns_name, const XML_Char** atts)
        {
          // Current Expat (2.0.0) has a (mis)-feature of a possibility
          // of calling hooks even after the non-resumable XML_StopParser
          // call. The following code accounts for this.
          //
          {
            XML_ParsingStatus s;
            XML_GetParsingStatus (xml_parser_, &s);
            if (s.parsing == XML_FINISHED)
              return;
          }

          const char* ns_p;
          const char* name_p;
          size_t ns_s, name_s;

          bits::split_name (ns_name, ns_p, ns_s, name_p, name_s);

          {
            const ro_string ns (ns_p, ns_s);
            const ro_string name (name_p, name_s);

            if (depth_++ > 0)
            {
              if (parser_)
                parser_->_start_element (ns, name);
            }
            else
            {
              parser_ = start_root_element (ns, name);

              if (parser_)
              {
                // pre() is called by the user.
                //
                parser_->_pre_impl ();
              }
            }

#if defined(XSDE_VALIDATION) || !defined(XSDE_EXCEPTIONS)
            if (parser_ && parser_->error_p_ ())
            {
              XML_StopParser (xml_parser_, false);
              return;
            }
#endif
          }

          if (parser_)
          {
            for (; *atts != 0; atts += 2)
            {
              bits::split_name (*atts, ns_p, ns_s, name_p, name_s);

              const ro_string ns (ns_p, ns_s), name (name_p, name_s);
              const ro_string value (*(atts + 1));

              parser_->_attribute (ns, name, value);

#if defined(XSDE_VALIDATION) || !defined(XSDE_EXCEPTIONS)
              if (parser_->error_p_ ())
              {
                XML_StopParser (xml_parser_, false);
                break;
              }
#endif
            }
          }
        }

        void document::
        end_element_ (const XML_Char* ns_name)
        {
          // See the comment in start_element_ for what's going on here.
          //
          {
            XML_ParsingStatus s;
            XML_GetParsingStatus (xml_parser_, &s);
            if (s.parsing == XML_FINISHED)
              return;
          }

          const char* ns_p;
          const char* name_p;
          size_t ns_s, name_s;

          bits::split_name (ns_name, ns_p, ns_s, name_p, name_s);

          const ro_string ns (ns_p, ns_s);
          const ro_string name (name_p, name_s);

          assert (depth_ > 0);

          if (--depth_ > 0)
          {
            if (parser_)
              parser_->_end_element (ns, name);
          }
          else
          {
            if (parser_)
            {
              parser_->_post_impl ();
              //
              // post() is called by the user.
            }

            end_root_element (ns, name, parser_);
          }

#if defined(XSDE_VALIDATION) || !defined(XSDE_EXCEPTIONS)
          if (parser_ && parser_->error_p_ ())
            XML_StopParser (xml_parser_, false);
#endif
        }

        void document::
        characters_ (const XML_Char* s, size_t n)
        {
          // See the comment in start_element_ for what's going on here.
          //
          {
            XML_ParsingStatus s;
            XML_GetParsingStatus (xml_parser_, &s);
            if (s.parsing == XML_FINISHED)
              return;
          }

          assert (depth_ > 0);

          if (n != 0 && parser_)
          {
            const ro_string str (s, n);
            parser_->_characters (str);

#if defined(XSDE_VALIDATION) || !defined(XSDE_EXCEPTIONS)
            if (parser_->error_p_ ())
              XML_StopParser (xml_parser_, false);
#endif
          }
        }
      }
    }
  }
}
