/*******************************************************************\

              Copyright (C) 2003 Joseph Coffland

    This program 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 2
        of the License, or (at your option) any later version.

   This program 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 this program; if not, write to the Free Software
      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
                           02111-1307, USA.

            For information regarding this software email
                   jcofflan@users.sourceforge.net

\*******************************************************************/

#include "XMLSerializerImpl.h"
#include "XMLSerializable.h"

#include <BasicUtils/BasicException.h>
#include <XercesUtils/StreamFormatTarget.h>
#include <XercesUtils/XercesStr.h>

#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/framework/XMLFormatter.hpp>
XERCES_CPP_NAMESPACE_USE;

#include <iostream>
using namespace std;

#define CHECK_ESC(x) (escaping ? XMLFormatter::x : XMLFormatter::NoEscapes)


static const XMLCh  gEndElement[] = {chOpenAngle, chForwardSlash, chNull};
static const XMLCh  gEndPI[] = {chQuestion, chCloseAngle, chNull};
static const XMLCh  gStartPI[] = {chOpenAngle, chQuestion, chNull};
static const XMLCh  gXMLDecl1[] = {
  chOpenAngle, chQuestion, chLatin_x, chLatin_m, chLatin_l,
  chSpace, chLatin_v, chLatin_e, chLatin_r, chLatin_s, chLatin_i,
  chLatin_o, chLatin_n, chEqual, chDoubleQuote, chDigit_1, chPeriod,
  chDigit_0, chDoubleQuote, chSpace, chLatin_e, chLatin_n, chLatin_c,
  chLatin_o, chLatin_d, chLatin_i, chLatin_n, chLatin_g, chEqual,
  chDoubleQuote, chNull
};
static const XMLCh  gXMLDecl2[] = {
  chDoubleQuote, chQuestion, chCloseAngle, chLF, chNull
};
static const XMLCh gStartComment[] = {
  chOpenAngle, chDash, chDash, chNull
};
static const XMLCh gEndComment[] = {
  chDash, chDash, chCloseAngle, chNull
};

// Begin XMLSerializer interface
void XMLSerializerImpl::serialize(const string targetNamespace,
				  const string topElementName,
				  XMLSerializable &serializable) {
  startDocument();
  attributes_t attrs;
  attrs["xmlns"] = targetNamespace;
  startElement(topElementName, attrs);
  serializable.writeXML(*this);
  endElement();
  endDocument();
}

void XMLSerializerImpl::startDocument() {
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("Document already started!", !started);

  *formatter << XMLFormatter::NoEscapes
	     << gXMLDecl1 << formatter->getEncodingName() << gXMLDecl2;

  started = true;
  lastEvent = START_DOCUMENT;
}

void XMLSerializerImpl::startElement(const string qname) {
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("Document not started!", started);

  switch (lastEvent) {
  case START_ELEMENT:
    *formatter << chCloseAngle;

  case COMMENT:
  case PROCESSING_INSTRUCTION:
  case END_ELEMENT:
    newLine();

  case END_LINE:
    indent(getDepth());
  }

  elements.push(qname);
  *formatter << XMLFormatter::NoEscapes << chOpenAngle
	     << XercesStr(qname);

  lastEvent = START_ELEMENT;
}


void XMLSerializerImpl::startElement(const std::string qname, 
				     attributes_t &attributes) {

  int base = qname.length() + getDepth() * getTabSize() + 1;
  int len = base;

  startElement(qname);

  attributes_t::iterator it;
  for (it = attributes.begin(); it != attributes.end(); it++) {
    if (len > 80) {
      newLine();
      if (isPrettyPrinting())
	for (int i = 0; i < base; i++)
	  *formatter << chSpace;
      
      len = base;
    }

    *formatter << chSpace << XercesStr((*it).first)
	       << chEqual << chDoubleQuote << CHECK_ESC(AttrEscapes)
	       << XercesStr((*it).second) << XMLFormatter::NoEscapes
	       << chDoubleQuote;

    len += (*it).first.length() + (*it).second.length() + 4;
  }
}

void XMLSerializerImpl::characters(const std::string text) {
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("Document not started!", started);
  ASSERT_OR_THROW("Must have a root element!", !elements.empty());

  switch (lastEvent) {
  case START_ELEMENT:
    *formatter << chCloseAngle;
    break;

  case END_LINE:
    indent(getDepth());
  }

  *formatter << CHECK_ESC(CharEscapes) << XercesStr(text)
	     << XMLFormatter::NoEscapes;

  lastEvent = TEXT;
}

void XMLSerializerImpl::endElement() {
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("Document not started!", started);
  ASSERT_OR_THROW("No element not started!", !elements.empty());

  switch (lastEvent) {
  case COMMENT:
  case PROCESSING_INSTRUCTION:
  case END_ELEMENT:
    newLine();

  case END_LINE:
    indent(getDepth() - 1);
  }

  if (lastEvent == START_ELEMENT && isShortFormAllowed()) {
    *formatter << chForwardSlash << chCloseAngle;
  } else {
    if (lastEvent == START_ELEMENT) *formatter << chCloseAngle;
    *formatter << XMLFormatter::NoEscapes << gEndElement 
	       << XercesStr(elements.top()) << chCloseAngle;   
  }

  elements.pop();

  lastEvent = END_ELEMENT;
}

void XMLSerializerImpl::endDocument() {
  ASSERT_OR_THROW("Document not started!", started);

  started = false;

  lastEvent = END_DOCUMENT;
}

void XMLSerializerImpl::comment(const std::string text) {
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("Document not started!", started);
  ASSERT_OR_THROW("A comment cannot contain '--'!",
		  text.find("--") == text.npos);
  ASSERT_OR_THROW("A comment cannot end with a '-'!", 
		  text[text.length() - 1] != '-');

  switch (lastEvent) {
  case START_ELEMENT:
    *formatter << chCloseAngle;

  case COMMENT:
  case PROCESSING_INSTRUCTION:
  case END_ELEMENT:
    newLine();

  case END_LINE:
    indent(getDepth());
  }

  *formatter << XMLFormatter::NoEscapes << gStartComment
	     << XercesStr(text) << gEndComment;

  lastEvent = COMMENT;
}

void XMLSerializerImpl::processingInstruction(const string target, 
					      const string data) {  
  ASSERT_OR_THROW("formatter cannot be NULL!", formatter);
  ASSERT_OR_THROW("A processing instruction cannot contain '?>'!",
		  data.find("?>") == data.npos);

  switch (lastEvent) {
  case START_ELEMENT:
    *formatter << chCloseAngle;

  case COMMENT:
  case PROCESSING_INSTRUCTION:
  case END_ELEMENT:
    newLine();

  case END_LINE:
    indent(getDepth());
  }

  *formatter << XMLFormatter::NoEscapes << gStartPI << XercesStr(target);
  *formatter << chSpace << XercesStr(data) << gEndPI;

  lastEvent = PROCESSING_INSTRUCTION;
}

void XMLSerializerImpl::flush() {
  target->flush();
}

void XMLSerializerImpl::endl() {
  switch (lastEvent) {
  case START_ELEMENT:
    *formatter << chCloseAngle;
  }

  *formatter << chLF;
  flush();
  lastEvent = END_LINE;
}
// End XMLSerializer interface



void XMLSerializerImpl::createFormatter(ostream &out) {
  target = new StreamFormatTarget(&out);

  formatter = new XMLFormatter(DEFAULT_ENCODING, "1.0", target,
			       XMLFormatter::NoEscapes,
			       XMLFormatter::UnRep_CharRef);
  isMyFormatter = true;
}

void XMLSerializerImpl::destroyFormatter() {
  if (formatter && isMyFormatter) {
    delete formatter;
    formatter = NULL;
    delete target;
    target = NULL;
    isMyFormatter = false;
  }
}

void XMLSerializerImpl::indent(const int x) {
  if (isPrettyPrinting()) {
    for (int i = 0; i < x * getTabSize(); i++)
      *formatter << chSpace;
  }
}

void XMLSerializerImpl::newLine() {
  if (isPrettyPrinting())
    *formatter << chLF;
}
