
#include "XmlRpcUtil.h"

#ifndef MAKEDEPEND
# include <ctype.h>
# include <iostream>
# include <stdarg.h>
# include <stdio.h>
# include <string.h>
#endif

#include "XmlRpc.h"

using namespace XmlRpc;

//#define USE_WINDOWS_DEBUG // To make the error and log messages go to VC++ debug output
#ifdef USE_WINDOWS_DEBUG
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

// Version id
const char XmlRpc::XMLRPC_VERSION[] = "XMLRPC++ 0.7";

// Default log verbosity: 0 for no messages through 5 (writes everything)
int XmlRpcLogHandler::_verbosity = 0;

// Default log handler
static class DefaultLogHandler : public XmlRpcLogHandler
{
	public:

		void log(int level, const char* msg)
		{
		#ifdef USE_WINDOWS_DEBUG
			if (level <= _verbosity) { OutputDebugString(msg); OutputDebugString("\n"); }
		#else
			if (level <= _verbosity) std::cout << msg << std::endl;
		#endif
		}

} defaultLogHandler;

// Message log singleton
XmlRpcLogHandler* XmlRpcLogHandler::_logHandler = &defaultLogHandler;

// Default error handler
static class DefaultErrorHandler : public XmlRpcErrorHandler
{
	public:

		void error(const char* msg)
		{
		#ifdef USE_WINDOWS_DEBUG
			OutputDebugString(msg); OutputDebugString("\n");
		#else
			std::cerr << msg << std::endl;
		#endif
		}
} defaultErrorHandler;

// Error handler singleton
XmlRpcErrorHandler* XmlRpcErrorHandler::_errorHandler = &defaultErrorHandler;

// Easy API for log verbosity
int XmlRpc::getVerbosity() { return XmlRpcLogHandler::getVerbosity(); }
void XmlRpc::setVerbosity(int level) { XmlRpcLogHandler::setVerbosity(level); }

void XmlRpcUtil::log(int level, const char* fmt, ...)
{
	if (level <= XmlRpcLogHandler::getVerbosity())
	{
		va_list va;
		char buf[1024];
		va_start( va, fmt);
		vsnprintf(buf,sizeof(buf)-1,fmt,va);
		buf[sizeof(buf)-1] = 0;
		XmlRpcLogHandler::getLogHandler()->log(level, buf);
	}
}


void XmlRpcUtil::error(const char* fmt, ...)
{
	va_list va;
	va_start(va, fmt);
	char buf[1024];
	vsnprintf(buf,sizeof(buf)-1,fmt,va);
	buf[sizeof(buf)-1] = 0;
	XmlRpcErrorHandler::getErrorHandler()->error(buf);
}


// Returns contents between <tag> and </tag>, updates offset to char after </tag>
std::string
XmlRpcUtil::parseTag(const char* tag, std::string const& xml, int* offset)
{
	if (*offset >= int(xml.length())) return std::string();
	size_t istart = xml.find(tag, *offset);
	if (istart == std::string::npos) return std::string();
	istart += strlen(tag);
	std::string etag = "</";
	etag += tag + 1;
	size_t iend = xml.find(etag, istart);
	if (iend == std::string::npos) return std::string();

	*offset = int(iend + etag.length());
	return xml.substr(istart, iend-istart);
}


// Returns true if the tag is found and updates offset to the char after the tag
bool
XmlRpcUtil::findTag(const char* tag, std::string const& xml, int* offset)
{
	if (*offset >= int(xml.length())) return false;
	size_t istart = xml.find(tag, *offset);
	if (istart == std::string::npos)
		return false;

	*offset = int(istart + strlen(tag));
	return true;
}


// Returns true if the tag is found at the specified offset (modulo any whitespace)
// and updates offset to the char after the tag
bool
XmlRpcUtil::nextTagIs(const char* tag, std::string const& xml, int* offset)
{
	if (*offset >= int(xml.length())) return false;
	const char* cp = xml.c_str() + *offset;
	int nc = 0;
	while (*cp && isspace(*cp))
	{
		++cp;
		++nc;
	}

	int len = int(strlen(tag));
	if  (*cp && (strncmp(cp, tag, len) == 0))
	{
		*offset += nc + len;
		return true;
	}
	return false;
}


// Returns the next tag and updates offset to the char after the tag, or empty string
// if the next non-whitespace character is not '<'
std::string
XmlRpcUtil::getNextTag(std::string const& xml, int* offset)
{
	if (*offset >= int(xml.length())) return std::string();

	size_t pos = *offset;
	const char* cp = xml.c_str() + pos;
	while (*cp && isspace(*cp))
	{
		++cp;
		++pos;
	}

	if (*cp != '<') return std::string();

	std::string s;
	do
	{
		s += *cp;
		++pos;
	} while (*cp++ != '>' && *cp != 0);

	*offset = int(pos);
	return s;
}


// xml encodings (xml-encoded entities are preceded with '&')
static const char  AMP = '&';
static const char  rawEntity[] = { '<',   '>',   '&',    '\'',    '\"',    0 };
static const char* xmlEntity[] = { "lt;", "gt;", "amp;", "apos;", "quot;", 0 };
static const int   xmlEntLen[] = { 3,     3,     4,      5,       5 };

// Replace xml-encoded entities with the raw text equivalents.

std::string
XmlRpcUtil::xmlDecode(const std::string& encoded)
{
	std::string::size_type iAmp = encoded.find(AMP);
	if (iAmp == std::string::npos)
		return encoded;

	std::string decoded(encoded, 0, iAmp);
	std::string::size_type iSize = encoded.size();
	decoded.reserve(iSize);

	const char* ens = encoded.c_str();
	while (iAmp != iSize)
	{
		if (encoded[iAmp] == AMP && iAmp+1 < iSize)
		{
			int iEntity;
			for (iEntity=0; xmlEntity[iEntity] != 0; ++iEntity)
				//if (encoded.compare(iAmp+1, xmlEntLen[iEntity], xmlEntity[iEntity]) == 0)
				if (strncmp(ens+iAmp+1, xmlEntity[iEntity], xmlEntLen[iEntity]) == 0)
			{
				decoded += rawEntity[iEntity];
				iAmp += xmlEntLen[iEntity]+1;
				break;
			}
								 // unrecognized sequence
			if (xmlEntity[iEntity] == 0)
				decoded += encoded[iAmp++];

		}
		else
		{
			decoded += encoded[iAmp++];
		}
	}

	return decoded;
}


// Replace raw text with xml-encoded entities.

std::string
XmlRpcUtil::xmlEncode(const std::string& raw)
{
	std::string::size_type iRep = raw.find_first_of(rawEntity);
	if (iRep == std::string::npos)
		return raw;

	std::string encoded(raw, 0, iRep);
	std::string::size_type iSize = raw.size();

	while (iRep != iSize)
	{
		int iEntity;
		for (iEntity=0; rawEntity[iEntity] != 0; ++iEntity)
			if (raw[iRep] == rawEntity[iEntity])
		{
			encoded += AMP;
			encoded += xmlEntity[iEntity];
			break;
		}
		if (rawEntity[iEntity] == 0)
			encoded += raw[iRep];
		++iRep;
	}
	return encoded;
}
