/////////////////////////////////////////////////////////////////////////////
// Name:        tinyxml.cpp
// Purpose:     TinyXML parser (borrowed from wxWorkshop)
// Author:      Aleksandras Gluchovas
// Created:     2000/03/05
// RCS-ID:      $Id: tinyxml.cpp,v 1.7 2001/04/28 04:22:00 georgetasker Exp $
// Copyright:   (c) 2000 Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////
 
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/wfstream.h"
#include "wx/log.h"
#include "wx/xml/xmlio.h"

#include "wx/wspp/tinyxml.h"
#include "wx/log.h"

/*** global helpers ***/

static void ConcatString( wxString& toStr, const char* what, size_t len )
{
	char* str = (char*)what;
	char tmp = what[len];
	str[len] = '\0'; // terminate temporarely

	toStr += wxString( what, len );

	str[len] = tmp;
}

static inline bool get_next_word( char*& cur, char* end )
{
    for(;;)
    {
        while( cur < end && 
              ( *cur == ' ' || *cur == '\n' || *cur == '\r' || *cur == '\t' ) ) ++cur;

        // skip comment-lines

        if ( *cur == '/' && *(cur+1) == '/' )
        {
            while ( cur < end && *cur != '\n' ) ++cur;

            continue;
        }
        else break;

    } 
        
    return ( cur < end );
}

static inline bool skip_xml_word( char*& cur, char* end )
{
	// handle quoted strings and "blobs" enclosed within "[[ ]]" double-brackets

	if ( *cur == '"' )
	{
		++cur;

		while( cur < end && ( *cur != '\"' || 
			                  ( *(cur-1) == '\\' && *cur == '\"' )
						    )
		     ) 
			
			++cur;

		if ( cur >= end ) return FALSE;

		++cur;

		return TRUE;
	}
	else

	if ( *cur == '=' ||
		 *cur == '<' ||
		 *cur == '>' ||
		 *cur == '/' )
	{
		 ++cur;

		 return ( cur < end );
	}

    while( cur < end && *cur != '\n' && *cur != '\r' &&
           *cur != ' ' && *cur !='\t' &&

		   *cur != '=' &&
		   *cur != '<' &&
		   *cur != '>' &&
		   *cur != '/' 
		 )

		   ++cur;

    return ( cur < end );
}

static wxString _gTmpStForWord = wxString( 'x', 1024 );

static wxString& get_xml_word( char* cur, char* end )
{
	char* start = cur;

	skip_xml_word( cur, end );

	// strip quotes and double brackets ("[[ ]]")

	if ( *start == '"' )
	{
		++start; --cur;
	}
	else
	if ( *start == '[' && *(start+1) == '[' )
	{
		start += 2; cur -= 2;
	}

	// for performance use global wxString-object (cut out ref-count stuff)

	_gTmpStForWord = "";

	ConcatString( _gTmpStForWord, start, size_t( cur - start ) );

	return _gTmpStForWord;
}

void tiny_xml_translate_value( wxString& value, bool afterLoading )
{
	char* cur = (char*)(const char*)value.c_str();
	char* substPos = cur;

	wxString* subst = NULL;

	while( *cur )
	{
		if ( afterLoading ) // before loading from parsed xml to records
		{
			/* very basic translation of few encoded special characters:
			 * '&amp;' to ';', '&gt;' to '>'and '&lt;' to '<' - just the ones
			 * needed prevent from messing up basic xml-syntax
			 */

			if ( *cur == '&' )
			{
				char* start = cur; // at '&'
				while( *cur && *cur != ';' )
					++cur;

				char schar = '\0';

				if ( start[1] == 'a' &&
					 start[2] == 'm' &&
					 start[3] == 'p' )
				
					schar = '&';
				else
				if ( start[1] == 'g' &&
					 start[2] == 't' )
				
					schar = '>';
				else
				if ( start[1] == 'l' &&
					 start[2] == 't' )
				
					schar = '<';
				
				if ( *cur ) 
					++cur; // skip ';'

				if ( schar != '\0' )
				{
					if ( !subst ) 
						subst = new wxString();

					*start = '\0';
					subst->Append( substPos );
					subst->Append( schar ); 
					*start = '&';

					substPos = cur;
				}
			}
			else
				// Line-Feed characters translated to '\n' escape-squences
				// to allow editing mutiline resource properties in single-line
				// textcontrols (of property sheets)

			if ( *cur == '\n' )
			{
				if ( !subst ) 
					subst = new wxString();

				*cur = '\0';
				subst->Append( substPos );
				subst->Append( "\\n" );
				*cur = '\n';
				++cur;
				substPos = cur;
			}
			else
				++cur;
		}
		else
			// otherwise before formatting xml output from record fields
		{
			if ( *cur == '&' ||
				 *cur == '>' ||
				 *cur == '<' )
			{
				char ch = *cur;

				if ( !subst ) 
					subst = new wxString();

				*cur = '\0';
				subst->Append( substPos );

				switch( ch )
				{
					case '&' : subst->Append( "&amp;" );break;
					case '>' : subst->Append( "&gt;" );break;
					case '<' : subst->Append( "&lt;" );break;
					default : break;
				}

				*cur = ch;
				++cur;
				substPos = cur;
			}
			else
			if ( *cur == '\\' && *(cur+1) == 'n' )
			{
				if ( !subst ) 
					subst = new wxString();

				*cur = '\0';
				subst->Append( substPos );
				subst->Append( '\n' );
				*cur = '\\';
				cur += 2; // skip "\\n"
				substPos = cur;
			}
			else
				++cur;

		} // end of 'else'

	} // end of while(..)

	if ( subst ) // if anything found for translation
	{
		if ( substPos != cur )
			subst->Append( substPos );

		value = *subst;
		delete subst;
	}
}

#define RETURN_ERROR return FALSE;

static bool LoadFromXML( wxXmlNode* pNode, char*& cur, char* end )
{
	for(;;) // skip all leading comment and version tags
	{
		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		if ( *cur != '<' )
			RETURN_ERROR;


		++cur; // skip '<';

		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		if ( !( *cur == '!' && *(cur+1) == '-' ) && 
			 !( *cur == '?' )
			) 
			
			break;

		while( cur < end && *cur != '>' ) 
			++cur;

		++cur; // skip '>'
	}

	pNode->SetName( get_xml_word( cur, end ) );
	pNode->SetType( wxXML_ELEMENT_NODE );

	skip_xml_word( cur, end );

	for(;;) // retrieve atomic properties of this record
	{
		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		if ( *cur == '>' )
			break;

		if ( *cur == '/' )
		{
			while( cur < end && *cur != '>' )
				++cur;

			++cur; // skip '>' 

			// this is the end

			return TRUE;
		}

		wxString propName = get_xml_word( cur, end );

		skip_xml_word( cur, end );

		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		if ( *cur != '=' )
			RETURN_ERROR; 
		
		++cur; // skip '=';

		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		wxString propValue = get_xml_word( cur, end );

		skip_xml_word( cur, end );

		tiny_xml_translate_value( propValue, TRUE );

		pNode->AddProperty( propName, propValue ); 
	}

	++cur; // skip '>'

	for(;;) // obtain all nested records
	{
		if ( !get_next_word( cur, end ) )
			RETURN_ERROR;

		if ( *cur == '<' && *(cur+1) == '!' && *(cur+2) == '[' )
		{
			// handle <![CDATA[...]]>

			while( cur < end && *cur != '[' )
				++cur;

			++cur;

			while( cur < end && *cur != '[' )
				++cur;

			++cur;

			char* start = cur;

			while( cur < end && *cur != ']' )
				++cur;

			wxString val;
			ConcatString( val, start, size_t( cur - start ) );

			wxXmlNode* pContent = new wxXmlNode( NULL, wxXML_CDATA_SECTION_NODE, 
												 "", val, NULL, NULL );

			pNode->AddChild( pContent );

			while( cur < end && *cur != '>' )
				++cur;

			++cur;
			
			continue;
		}
		else
		if ( *cur != '<' )
		{
			char* start = cur;

			while( cur < end && *cur != '<' )
				++cur;

			wxString val;
			ConcatString( val, start, size_t( cur - start ) );
			tiny_xml_translate_value( val, TRUE );

			wxXmlNode* pContent = new wxXmlNode( NULL, wxXML_TEXT_NODE, 
												 "", val, NULL, NULL );
			
			pNode->AddChild( pContent ); // FIXME:: find out why this is needed
			pNode->SetContent( val ); 

			continue;
		}
		else
		if ( *cur == '<' && *(cur+1) == '/' )

			break;

		wxXmlNode* pChild = new wxXmlNode();

		LoadFromXML( pChild, cur, end );

		pNode->AddChild( pChild );
	}

	// FIXME:: curde... should ceck if the closing tag matches the opening one

	while( cur < end && *cur != '>' )
		++cur; // skip </tag>

	++cur; // skip '>'

	return TRUE;
}

static bool ContainsReservedXMLChars( const wxString& s )
{
	const char* cur = (const char*)s;

	while( *cur )
	{
		if ( *cur == '>' || *cur == '/' || *cur == '<' || *cur == '=' )

			return TRUE;

		++cur;
	}

	return FALSE;
}

static wxString GetXMLTextContent( wxXmlNode* pNode, const wxString& lmargin )
{
	wxString text = "";

	text = "\n" + lmargin + "<" + pNode->GetName();

	size_t nProps = 0;

	size_t i = 0;

	wxXmlProperty* pProp = pNode->GetProperties();

	while( pProp )
	{
		if ( text.Length() >= 60 )
			text += "\n" + lmargin + "\t";
		else
			text += " ";

		wxString value = pProp->GetValue();
		tiny_xml_translate_value( value, FALSE );
		text += pProp->GetName() + "=\"" + value + "\"";

		++nProps;

		pProp = pProp->GetNext();
	}

	text += ">";

	nProps = 0;

	wxXmlNode* pChild = pNode->GetChildren();

	wxString content;

	while( pChild )
	{
		if ( pChild->GetType() == wxXML_ELEMENT_NODE )
		{
			wxString margin = lmargin + "\t"; // increase margin

			text += GetXMLTextContent( pChild, margin );

			++nProps;
		}
		else
		if ( pChild->GetType() == wxXML_TEXT_NODE ||
			 pChild->GetType() == wxXML_CDATA_SECTION_NODE )
		
			 content = pChild->GetContent();

		pChild = pChild->GetNext();
	}

	if ( content.Length() )
	{
		if ( ContainsReservedXMLChars( content ) )
		{
			text += "\n" + lmargin + "\t" + "<![CDATA[" + content + "]]>";
			nProps = 1;
		}
		else
			text += content;
	}

	if ( nProps )

		text += "\n" + lmargin;

	text += "</" + pNode->GetName() + ">";

	return text;
}

/***** Implementation for class wxXmlIOHandlerTinyxml *****/

//IMPLEMENT_CLASS( wxXmlIOHandlerTinyxml, wxXmlIOHandler )

wxXmlIOHandlerTinyxml::wxXmlIOHandlerTinyxml()
{}

wxXmlIOType wxXmlIOHandlerTinyxml::GetType() 
{ 
	return wxXML_IO_AUTO; 
}

bool wxXmlIOHandlerTinyxml::CanLoad(wxInputStream& stream)
{
	return TRUE;
}

bool wxXmlIOHandlerTinyxml::CanSave()
{
	return TRUE;
}

bool wxXmlIOHandlerTinyxml::Load(wxInputStream& stream, wxXmlDocument& doc)
{
	// TBD:: checking the content format (to regard wxXML_IO_AUTO)

	int bufLen = 1024*10;
	char* buf = new char[bufLen + 1];
	wxXmlNode* pRoot = new wxXmlNode();

	wxString toString; int len = 0;

	while( stream.LastError() != wxStream_EOF )
	{
		if ( bufLen == len )
		{
			char* old = buf;
			bufLen = len * 2;
			buf = new char[ bufLen +  1 ];

			memcpy( buf, old, len );
			delete [] old;
		}

		stream.Read( buf + len, bufLen - len );

		len += stream.LastRead();

		if ( stream.LastError() == wxStream_READ_ERR )

			goto err; // makes perfect sense here
	}

	buf[len] = '\0';

	{
	
		char* at = buf;
		if ( !LoadFromXML( pRoot, at, buf + len ) )
		
			goto err;

		if ( pRoot->GetName() == "resource" )
		{
	        doc.SetVersion("1.0");
		    doc.SetEncoding("");
			doc.SetRoot( pRoot );
		}
		else
			goto err;
	}

	delete [] buf;
	return TRUE;

err:
	delete pRoot;
	delete [] buf;
	return FALSE;
}

bool wxXmlIOHandlerTinyxml::Save(wxOutputStream& stream, const wxXmlDocument& doc)
{
	wxString content = GetXMLTextContent( doc.GetRoot(), "" );

	stream.Write( content, content.Length() );

	return TRUE;
}

void wxXmlIOHandlerTinyxml::InitTinyXml()
{
	// Nothing here. Calling this forces a reference
	// to wspp.lib so as to compile initialization wxTinyXmlModule's 
	// class info in and further registration as a module
}

#include "wx/module.h"

class wxTinyXmlModule: public wxModule
{
    DECLARE_DYNAMIC_CLASS(wxTinyXmlModule)
    public:
        wxTinyXmlModule() {}
        
		bool OnInit() { 
		  wxXmlDocument::AddHandler( new wxXmlIOHandlerTinyxml() ); 
		  wxLogMessage( "wxTinyXmlModule registered OK" );
		  return TRUE; 
		}

        void OnExit() {};
};

IMPLEMENT_DYNAMIC_CLASS(wxTinyXmlModule, wxModule)
