/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */

/* AbiSource Program Utilities
 * Copyright (C) 2001-2003 AbiSource, Inc.
 * 
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "ut_assert.h"
#include "ut_debugmsg.h"
#include "ut_string.h"
#include "ut_html.h"

#include "ut_string_class.h"

// Please keep the "/**/" to stop MSVC dependency generator complaining.
#include <libxml/parser.h>
#include <libxml/parserInternals.h>

// override typedef in ut_xml.h:
#ifdef XML_Char
#undef XML_Char
#endif
#define XML_Char xmlChar

UT_HTML::UT_HTML ()
{
	// 
}

UT_HTML::~UT_HTML ()
{
	// 
}

#ifdef __MRC__
extern "C" {
#endif

static void _startElement (void * userData, const XML_Char * name, const XML_Char ** atts)
{
	UT_HTML * pXML = static_cast<UT_HTML *>(userData);

	/* libxml2 can supply atts == 0, which is a little at variance to what is expected...
	 */
	const XML_Char * ptr = 0;
	const XML_Char ** new_atts = atts;
	if (atts == 0) new_atts = &ptr;

	pXML->startElement (reinterpret_cast<const char *>(name),
						reinterpret_cast<const char **>(new_atts));
}

static void _endElement (void * userData, const XML_Char * name)
{
	UT_HTML * pXML = static_cast<UT_HTML *>(userData);
	pXML->endElement (reinterpret_cast<const char *>(name));
}

static xmlEntityPtr _getEntity (void * userData, const XML_Char * name)
{
	return xmlGetPredefinedEntity (name);
}

static void _charData (void * userData, const XML_Char * buffer, int length)
{
	UT_HTML * pXML = static_cast<UT_HTML *>(userData);
	pXML->charData (reinterpret_cast<const char *>(buffer), length);
}

static void _errorSAXFunc (void *ctx, const char *msg, ...)
{
	va_list args;
	va_start (args, msg);

	UT_String errorMessage(UT_String_vprintf (msg, args));
	va_end (args);
  
	UT_DEBUGMSG(("libxml2/html: error: %s", errorMessage.c_str()));
}

static void _fatalErrorSAXFunc (void *ctx, const char *msg, ...)
{
	va_list args;
	va_start (args, msg);

	UT_String errorMessage(UT_String_vprintf (msg, args));

	va_end (args);
  
	UT_DEBUGMSG(("libxml2/html: fatal: %s", errorMessage.c_str()));
}

#ifdef __MRC__
};
#endif

UT_Error UT_HTML::parse (const char * szFilename)
{
	if ((szFilename == 0) || (m_pListener == 0)) return UT_ERROR;

	if (!reset_all ()) return UT_OUTOFMEM;

	UT_Error ret = UT_OK;

	DefaultReader defaultReader;
	Reader * reader = &defaultReader;
	if (m_pReader)
		reader = m_pReader;

	if (!reader->openFile (szFilename))
		{
			UT_DEBUGMSG (("Could not open file %s\n", szFilename));
			return UT_errnoToUTError ();
		}

	char buffer[2048];

	m_bStopped = false;

	htmlSAXHandler hdl;
	htmlParserCtxtPtr ctxt = 0;

	memset (&hdl, 0, sizeof (hdl));

	hdl.getEntity    = _getEntity;
	hdl.startElement = _startElement;
	hdl.endElement   = _endElement;
	hdl.characters   = _charData;
	hdl.error        = _errorSAXFunc;
	hdl.fatalError   = _fatalErrorSAXFunc;

	size_t length = reader->readBytes (buffer, sizeof (buffer));
	int done = (length < sizeof (buffer));

	if (length != 0)
		{
			ctxt = htmlCreatePushParserCtxt (&hdl, static_cast<void *>(this),
											 buffer, static_cast<int>(length),
											 szFilename, XML_CHAR_ENCODING_NONE);
			if (ctxt == NULL)
				{
					UT_DEBUGMSG (("Unable to create libxml2 push-parser context!\n"));
					reader->closeFile ();
					return UT_ERROR;
				}
			xmlSubstituteEntitiesDefault (1);

			while (!done && !m_bStopped)
				{
					length = reader->readBytes (buffer, sizeof (buffer));
					done = (length < sizeof (buffer));
	  
					if (htmlParseChunk (ctxt, buffer, static_cast<int>(length), 0))
						{
							UT_DEBUGMSG (("Error parsing '%s' (Line: %d, Column: %d)\n",
										  szFilename, getLineNumber(ctxt),
										  getColumnNumber(ctxt)));
							ret = UT_IE_IMPORTERROR;
							break;
						}
				}
			if (ret == UT_OK)
				if (!m_bStopped)
					{
						if (htmlParseChunk (ctxt, 0, 0, 1))
							{
								UT_DEBUGMSG (("Error parsing '%s' (Line: %d, Column: %d)\n",
											  szFilename, getLineNumber(ctxt),
											  getColumnNumber(ctxt)));
								ret = UT_IE_IMPORTERROR;
							}
					}
			if (ret == UT_OK)
				if (!ctxt->wellFormed && !m_bStopped)
					ret = UT_IE_IMPORTERROR; // How does stopping mid-file affect wellFormed?

			ctxt->sax = NULL;
			htmlFreeParserCtxt (ctxt);
		}
	else
		{
			UT_DEBUGMSG(("Empty file to parse - not sure how to proceed\n"));
		}

	reader->closeFile ();

	return ret;
}

UT_Error UT_HTML::parse (const char * buffer, UT_uint32 length)
{
	if ((buffer == 0) || (length < 6) || (m_pListener == 0)) return UT_ERROR;

	UT_XML::Reader * reader = m_pReader;

	UT_XML_BufReader wrapper(buffer,length);
	setReader (&wrapper);

	UT_Error ret = parse ("");

	setReader (reader);

	return ret;
}
