/***************************************************************************
                           cxml.cpp  -  description
                             -------------------
    begin                : Wed May 15 2002
    copyright            : (C) 2002-2005 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cxml.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>

/* for _open() modes */
#ifdef WIN32
#include <fcntl.h>
#endif

/* for close, mkstemp is in stdlib.h */
#include <unistd.h>

// for strlen, which is used instead of creating a temp CString
#include <string.h>

#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif

#include <libxml/xmlversion.h>
#if LIBXML_VERSION > 20406
#include <libxml/globals.h>
#endif
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlsave.h>

#define XML_FALSE			"false"
#define XML_TRUE			"true"

#include "ciconv.h"
#include "cfile.h"
#include "cbytearray.h"

#if LIBXML_VERSION < 20617
#define XML_SAVE_FORMAT 1<<0
#endif

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif

/** */
CXml::CXml()
{
	pDoc = 0;
	pNode = 0;

#if defined(__APPLE__)
	CString local = "UTF-8";
#elif defined(WIN32)
	CString local = "WINDOWS-1252";
#elif defined(HAVE_NL_LANGINFO)
	CString local = nl_langinfo(CODESET);
	if ( local.IsEmpty() )
	{
		local = "UTF-8";
	}
#else
	CString local = getenv("LANG");
	int i = local.Find('.');
	if ( i == -1 )
	{
		local = "UTF-8";
	}
	else
	{
		local = local.Mid(i + 1);
	}
#endif

	/*
	 * FIXME Bypassing the CIconv objects entirely may be better
	 * since 99% of the time local is UTF-8 ... although
	 * it's an inline function which checks a bool and then returns
	 * the input, which should be just as fast as checking if
	 * the object is null and then not calling anything.
	 */
	pToUTF8   = new CIconv( local, "UTF-8" );
	pFromUTF8 = new CIconv( "UTF-8", local );
}

/** */
CXml::~CXml()
{
	FreeDoc();

	delete pToUTF8;
	delete pFromUTF8;
}

/** */
void CXml::FreeDoc()
{
	if (pDoc)
	{
		xmlFreeDoc(pDoc);
		pDoc = 0;
		pNode = 0;
		
#ifdef HAVE_MALLOC_TRIM
		malloc_trim(0);
#endif
	}
}

/** */
bool CXml::NewDoc( const char * docnodename )
{
	FreeDoc();

	pDoc = xmlNewDoc((const xmlChar*)"1.0");
	
	if ( pDoc )
	{
		pNode = xmlNewDocNode( pDoc, 0, (const xmlChar*) docnodename, 0 );
		
		if ( pNode )
		{
			pDoc->children = pNode;
			return true;
		}
		else
		{
			/* nothing works without pNode */
			FreeDoc();
		}
	}
	
	return false;
}

/** */
bool CXml::ParseFile( CString name )
{
	FreeDoc();

	if ( (pDoc = xmlRecoverFile(name.Data())) == 0 )
	{
		return false;
	}

	return true;
}

/** */
bool CXml::ParseMemory( const char * s, int size )
{
	FreeDoc();

	if ( (pDoc = xmlRecoverMemory(s,size)) == 0 )
	{
		return false;
	}

	return true;
}

/** */
bool CXml::ParseFixMemory( CByteArray * ba )
{
	FreeDoc();
	
	int tried = 0;
	xmlParserCtxt * cp = NULL;
	
	while ( tried < 100 )
	{
		cp = xmlNewParserCtxt();
		if ( cp == NULL )
		{
			return false;
		}
		
		pDoc = xmlCtxtReadMemory( cp, (const char*) ba->Data(), ba->Size(), NULL, NULL, 0 );
		
		if ( pDoc == NULL )
		{
			long pos = xmlByteConsumed(cp);
			
			if ( (pos < 0) || (pos > ba->Size()) )
			{
				tried = 300;
			}
			else if ( pos == ba->Size() )
			{
				pDoc = xmlCtxtReadMemory( cp, (const char*) ba->Data(), ba->Size(), NULL, NULL, XML_PARSE_RECOVER );
				tried = 200;
			}
			else
			{
				ba->Data()[pos] = '_';
				++tried;
			}
		}
		
		xmlFreeParserCtxt( cp );
		
		if ( pDoc != NULL )
		{
			break;
		}
	}
	
	return (pDoc != NULL);
}

/** */
int CXml::SaveConfigXmlViaTemp( CString filename )
{
	if ( pDoc == 0 )
	{
		return -1;
	}
	
	CString tempname = filename + ".XXXXXX";

#ifdef WIN32
	int fd = _open( tempname.Data(), _O_BINARY | _O_WRONLY | _O_CREAT | _O_TRUNC );
#else	
	int fd = mkstemp( tempname.Data() );
#endif	
	if ( fd == -1 )
	{
		perror("CXml::SaveConfigXmlViaTemp: mkstemp");
		return -1;
	}
	
	xmlSaveCtxt * savecontext = xmlSaveToFd( fd, "utf-8", XML_SAVE_FORMAT );
	
	if ( savecontext == NULL )
	{
		printf("CXml::SaveConfigXmlViaTemp: xmlSaveToFd failed\n");
		return -1;
	}
	
	long ret1 = xmlSaveDoc( savecontext, pDoc );
	int ret2 = xmlSaveClose( savecontext );
	
#ifdef WIN32
	if ( _close( fd ) == -1 )
#else	
	if ( close( fd ) == -1 )
#endif
	{
		perror("CXml::SaveConfigXmlViaTemp: close");
		return -1;
	}
	
	if ( (ret1 != -1) && (ret2 != -1) )
	{
		CFile::UnLink( filename );
		if ( CFile::Rename( tempname, filename ) )
		{
			return 1000;
		}
		else
		{
			/*
			 * This is highly unlikely because being unable to rename a file
			 * implies no write access to the directory, so mkstemp would have failed.
			 */
			return -1;
		}
	}
	else
	{
		printf("CXml::SaveConfigXmlViaTemp: xmlSaveDoc/xmlSaveClose failed\n");
		return -1;
	}
}

/** */
CString CXml::Name()
{
	if ( pNode )
	{
		return CString( (const char*) pNode->name );
	}
	else
	{
		return CString();
	}
}

/** */
CString CXml::Content()
{
	xmlChar * c;
	CString s;

	if ( pNode && ((c = xmlNodeGetContent(pNode)) != 0) )
	{
		s = FromUtf8((char*)c);
		xmlFree(c);
	}

	return s;
}

/** */
CString CXml::Prop( const CString & prop )
{
	xmlChar *c;
	CString s;

	if ( pNode && ((c = xmlGetProp(pNode,(const xmlChar*)prop.Data())) != 0) )
	{
		s = (char*)c;
		xmlFree(c);
	}

	return s;
}

/** */
CString CXml::Prop( const char * prop )
{
	xmlChar *c;
	CString s;

	if ( pNode && ((c = xmlGetProp(pNode,(const xmlChar*)prop)) != 0) )
	{
		s = (char*)c;
		xmlFree(c);
	}

	return s;
}

/** */
bool CXml::GetBoolChild()
{
	if ( Content() == XML_TRUE )
		return true;
	else
		return false;
}

/** */
bool CXml::DocFirstChild()
{
	if ( pDoc )
	{
		xmlNode * node = pDoc->children;
		
		if ( node )
		{
			pNode = node;
			return true;
		}
	}

	return false;
}

/** */
bool CXml::NextNode()
{
	if ( pNode )
	{
		xmlNode * node = pNode->next;
		
		if ( node )
		{
			pNode = node;
			return true;
		}
	}
	
	return false;
}

/** */
bool CXml::FirstChild()
{
	if ( pNode )
	{
		xmlNode * node = pNode->children;
		
		if ( node )
		{
			pNode = node;
			return true;
		}
	}
	
	return false;
}

/** */
bool CXml::Parent()
{
	if ( pNode )
	{
		xmlNode * node = pNode->parent;
		
		if ( node )
		{
			pNode = node;
			return true;
		}
	}
	
	return false;
}

/** */
CString CXml::ToUTF8( const char * s )
{
	if ( (s == 0) || (s[0] == 0) )
	{
		return CString();
	}
	else
	{
		CString r = pToUTF8->encode(s);
		
		if ( xmlCheckUTF8( (const unsigned char*) r.Data() ) == false )
		{
			printf("CXml::ToUTF8: iconv returned invalid UTF-8, doing ISO-8859-1 to UTF-8\n");
			printf("input='%s' (char *) iconv='%s'\n",s,r.Data());
			
			int inlen = strlen(s);
			int outlen = inlen * 4;
			
			unsigned char * b = (unsigned char*) calloc(1,outlen);
			
			if ( b != 0 )
			{
				int res = isolat1ToUTF8( b, &outlen, (unsigned char*)s, &inlen );
				
				if ( res >= 0 )
					r = (char*)b;
				else
					printf("CXml::ToUTF8 isolat1ToUTF8 fail: '%s'\n",s);
				
				free(b);
			}
		}
		
		return EscapeSpecials(r);
	}
}

/** */
CString CXml::FromUtf8( const char * s )
{
	if ( (s == 0) || (s[0] == 0) )
	{
		return CString();
	}
	else
	{
		return UnEscapeSpecials( pFromUTF8->encode(s) );
	}
}

/** */
CString CXml::ToUTF8( const CString & s )
{
	if ( s.IsEmpty() )
	{
		return CString();
	}
	else
	{
		CString r = pToUTF8->encode(s);
		
		if ( xmlCheckUTF8( (const unsigned char*) r.Data() ) == false )
		{
			printf("CXml::ToUTF8: iconv returned invalid UTF-8, doing ISO-8859-1 to UTF-8\n");
			printf("input='%s' (CString) iconv='%s'\n",s.Data(),r.Data());
			
			int inlen = s.Length();
			int outlen = inlen * 4;
			
			unsigned char * b = (unsigned char*) calloc(1,outlen);
			
			if ( b != 0 )
			{
				int res = isolat1ToUTF8( b, &outlen, (unsigned char*)s.Data(), &inlen );
				
				if ( res >= 0 )
					r = (char*)b;
				else
					printf("CXml::ToUTF8 isolat1ToUTF8 fail: '%s'\n",s.Data());
				
				free(b);
			}
		}
		
		return EscapeSpecials(r);
	}
}

/** */
CString CXml::FromUtf8( const CString & s )
{
	if ( s.IsEmpty() )
	{
		return CString();
	}
	else
	{
		return UnEscapeSpecials( pFromUTF8->encode(s) );
	}

}

CString CXml::EscapeSpecials( const CString & s )
{
	int i;
	CString dst1;
	
	if ( s.IsEmpty() )
	{
		return dst1;
	}

	// sorry, CString::Replace is too slow
	/* dst = s.Replace("&","&amp;");
	dst = dst.Replace("\'","&apos;");
	dst = dst.Replace("\"","&quot;");
	dst = dst.Replace("<","&lt;");
	dst = dst.Replace(">","&gt;"); */

	/* 0x00 - 0x08, 0x0B, 0x0C, 0x0E - 0x1F have been declared to
	 * be illegal in XML documents
	*/
	for(i=0;i<s.Length();i++)
	{
		int x = s.Data()[i]&0xff;
		
		if ( ((x <= 0x08) && (x >= 0x00)) ||
		     (x == 0x0B) ||
		     (x <= 0x0C) ||
		     ((x <= 0x1F) && (x >= 0x0E)) )
		{
			dst1 += "&#0";
			dst1 += CString::number(x);
			dst1 += ';';
		}
		else if ( x == '&' )
		{
			dst1 += "&amp;";
		}
		else if ( x == '\'' )
		{
			dst1 += "&apos;";
		}
		else if ( x == '"' )
		{
			dst1 += "&quot;";
		}
		else if ( x == '<' )
		{
			dst1 += "&lt;";
		}
		else if ( x == '>' )
		{
			dst1 += "&gt;";
		}
		else
		{
			dst1 += s.Data()[i];
		}
	}

	return dst1;

}

CString CXml::UnEscapeSpecials( const CString & s )
{
	int i,i1;
	CString res;
	CString temp;
	CString four,six;

	for(i=0;i<s.Length();i++)
	{
		if ( s.Data()[i] == '&' )
		{
			if ( s.Mid(i,2) == "&#" )
			{
				if ( (i1 = s.Find(';',i)) != -1 )
				{
					if ( (i1-i) <= 5 )
					{
						temp = s.Mid(i,i1-i);				
						//printf("%s\n",t.Data());
						
						temp = temp.Mid(2);
						
						if ( temp.Left(1) == "x" )
						{
							temp = temp.Mid(1);
							
							res += temp.asINT(16);
						}
						else
						{
							res += temp.asINT();
						}
						
						//printf("%s\n",temp.Data());
						i += i1-i;
						continue;
					}
				}
			}
			else
			{
				four = s.Mid(i,4);
				if ( four == "&lt;" )
				{
					res += '<';
					i += 3;
					continue;
				}
				else if ( four == "&gt;" )
				{
					res += '>';
					i += 3;
					continue;
				}
				else
				{
					if ( s.Mid(i,5) == "&amp;" )
					{
						res += '&';
						i += 4;
						continue;
					}
					else
					{
						six = s.Mid(i,6);
						if ( six == "&apos;" )
						{
							res += '\'';
							i += 5;
							continue;
						}
						else if ( six == "&quot;" )
						{
							res += '"';
							i += 5;
							continue;
						}
					}
				}
			}
		}
		
		res += s.Data()[i];
	}

	// too slow, probably
	/* res = res.Replace("&apos;", "\'");
	res = res.Replace("&quot;", "\"");
	res = res.Replace("&lt;",   "<");
	res = res.Replace("&gt;",   ">");
	res = res.Replace("&amp;",  "&"); */

	return res;
}

/** */
bool CXml::StartNewChild( const char * name )
{
	if ( pNode )
	{
		pNode = xmlNewChild( pNode, 0, (const xmlChar*) name, 0 );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewBoolChild( const char * name, bool b )
{
	if ( pNode )
	{
		if ( b )
		{
			xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)XML_TRUE );
		}
		else
		{
			xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)XML_FALSE );
		}
		
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const int n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const unsigned int n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const long n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const unsigned long n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const long long n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewNumericChild( const char * name, const ulonglong n )
{
	if ( pNode )
	{
		xmlNewChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)CString::number(n).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewStringChild( const char * name, const CString & s )
{
	if ( pNode )
	{
		/* xmlNewTextChild actually can do the < to &lt; for us */
		xmlNewTextChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)ToUTF8(s).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewStringChild( const char * name, const char * s )
{
	if ( pNode )
	{
		/* xmlNewTextChild actually can do the < to &lt; for us */
		xmlNewTextChild( pNode, 0, (const xmlChar*) name, (const xmlChar*)ToUTF8(s).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewStringProp( const CString & prop, const CString & value )
{
	if ( pNode )
	{
		xmlNewProp( pNode, (const xmlChar*)prop.Data(), (const xmlChar*)ToUTF8(value).Data() );
		return true;
	}
	
	return false;
}

/** */
bool CXml::NewStringProp( const char * prop, const char * value )
{
	if ( pNode )
	{
		xmlNewProp( pNode, (const xmlChar*)prop, (const xmlChar*)ToUTF8(value).Data() );
		return true;
	}
	
	return false;
}

/** */
void CXml::InitParser()
{
	printf("Checking libxml2 version... ");
	LIBXML_TEST_VERSION;
	printf("compiled for '%s' using '%s'\n",LIBXML_VERSION_STRING,xmlParserVersion);
	
	if ( xmlParserVersion != CString("20510") )
	{
		xmlInitParser();
	}
}

/** */
void CXml::CleanupParser()
{
#if LIBXML_VERSION > 20510
	if ( xmlParserVersion != CString("20510") )
	{
		xmlCleanupParser();
	}
#endif
}

/** */
const char * CXml::Libxml2CompiledVersion()
{
	return LIBXML_VERSION_STRING;
}

/** */
const char * CXml::Libxml2RunningVersion()
{
	return xmlParserVersion;
}
