/*
** iksemel (XML Parser for Jabber)
** Copyright (C) 2000-2001 Gurer Ozen <palpa@jabber.org>
**
** This code is free software; you can redistribute it and/or
** modify it under the terms of GNU Lesser General Public License.
**
** xml parser
*/

#include "common.h"
#include "iksemel.h"

enum contexts
{
	C_CDATA = 0,
	C_TAG,
	C_ATTRIBUTE,
	C_VALUE,
	C_WHITESPACE,
	C_ENTITY,
	C_COMMENT,
	C_MARKUP,
	C_PI
};

static int parse_xml(iksparser *prs, char *buf, int len);


/* following function sucks -madcat */
int iks_parse(iksparser *prs, char *data, int len, int finish)
{
	char *buf;
	int diff;

	if(!data || len == 0) return(IKS_RET_OK);

	if(len == -1) len = strlen(data);

	if(prs->buffer)
	{
		buf = malloc(prs->buflen + len);
		if(!buf) return(IKS_RET_NOMEM);
		memcpy(buf, prs->buffer, prs->buflen);
		memcpy(buf + prs->buflen, data, len);

		diff = (int)buf - (int)prs->buffer;
		if(prs->tagname) prs->tagname += diff;
		if(prs->atts)
		{
			int i = 0;
			while(i < (prs->attmax * 2))
			{
				if(prs->atts[i]) prs->atts[i] += diff;
				i++;
			}
		}
		free(prs->buffer);
		prs->buffer = buf;
		buf += prs->buflen;
		prs->buflen += len;
	}
	else
	{
		buf = malloc(len);
		if(!buf) return(IKS_RET_NOMEM);
		memcpy(buf, data, len);
		prs->buffer = buf;
		prs->buflen = len;
	}

	parse_xml(prs, buf, len);

	return IKS_RET_OK;
}


static int parse_xml(iksparser *prs, char *buf, int len)
{
	int pos = 0, old = 0;

	while(pos < len)
	{
		switch(prs->context)
		{
		case C_CDATA:
			if(buf[pos] == '&')
			{
				prs->context = C_ENTITY;
				buf[pos] = '\0';
				if(prs->charData) (prs->charData)(prs->udata, &buf[old], pos - old);
				prs->entpos = 0;
			}
			if(buf[pos] == '<')
			{
				if(old < pos)
				{
					buf[pos] = '\0';
					if(prs->charData) (prs->charData)(prs->udata, &buf[old], pos - old);
					old = pos + 1;
				}
				prs->minpos = pos;
				prs->oldcontext = C_CDATA;
				prs->context = C_TAG;
			}
			break;

		case C_TAG:
			if(!prs->tagname)
			{
				prs->tagname = buf + pos;
				if(buf[pos] == '/')
				{
					prs->tagflag = 2;
					prs->tagname++;
					break;
				}
			}
			if(buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\r' || buf[pos] == '\n')
			{
				buf[pos] = '\0';
				prs->oldcontext = C_ATTRIBUTE;
				prs->context = C_WHITESPACE;
				break;
			}
			if(buf[pos] == '/')
			{
				buf[pos] = '\0';
				prs->tagflag = 1;
			}
			if(buf[pos] == '>')
			{
				buf[pos] = '\0';
				if(prs->tagStart && prs->tagflag != 2) (prs->tagStart)(prs->udata, prs->tagname, NULL);
				if(prs->tagEnd && prs->tagflag) (prs->tagEnd)(prs->udata, prs->tagname);
				prs->tagname = NULL;
				prs->tagflag = 0;
				prs->context = C_CDATA;
				old = pos + 1;
				prs->minpos = -1;
			}
			break;

		case C_ATTRIBUTE:
			if(!prs->atts)
			{
				prs->attmax = 6;
				prs->atts = malloc(sizeof(char *) * 2 * 6);
				if(!prs->atts) return(0);
				memset(prs->atts, 0, sizeof(char *) * 2 * 6);
				prs->attcur = 0;
			}
			else
			{
				if(prs->attcur >= (prs->attmax * 2))
				{
					void *tmp;
					prs->attmax += 4;
					tmp = malloc(sizeof(char *) * 2 * prs->attmax);
					if(!tmp) return(0);
					memset(tmp, 0, sizeof(char *) * 2 * prs->attmax);
					memcpy(tmp, prs->atts, sizeof(char *) * (prs->attcur - 1));
					free(prs->atts);
					prs->atts = tmp;
				}
			}

			if(!prs->atts[prs->attcur])
			{
				prs->atts[prs->attcur] = buf + pos;
			}

			if(buf[pos] == '=')
			{
				buf[pos] = '\0';
				prs->context = C_VALUE;
				break;
			}

			if(buf[pos] == '/') prs->tagflag = 1;
			if(buf[pos] == '>')
			{
				buf[pos] = '\0';
				if(prs->atts) prs->atts[prs->attcur] = NULL;
				if(prs->tagStart) (prs->tagStart)(prs->udata, prs->tagname, prs->atts);
				free(prs->atts);
				prs->atts = NULL;
				prs->attcur = 0;
				if(prs->tagEnd && prs->tagflag) (prs->tagEnd)(prs->udata, prs->tagname);
				prs->tagname = NULL;
				prs->tagflag = 0;
				prs->context = C_CDATA;
				old = pos + 1;
			}
			break;

		case C_VALUE:
			if(buf[pos] == '\'' || buf[pos] == '"')
			{
				buf[pos] = '\0';
				if(prs->valflag)
				{
					prs->valflag = 0;
					prs->oldcontext = C_ATTRIBUTE;
					prs->context = C_WHITESPACE;
					prs->attcur += 2;
					break;
				}
				prs->valflag = 1;
				prs->atts[prs->attcur + 1] = buf + pos + 1;
			}
			break;

		case C_WHITESPACE:
			if(buf[pos] != ' ' && buf[pos] != '\t' && buf[pos] != '\r' && buf[pos] != '\n')
			{
				prs->context = prs->oldcontext;
				pos--;
			}
			break;

		case C_ENTITY:
			if(buf[pos] != ';')
			{
				prs->entity[prs->entpos++] = buf[pos];
			}
			else
			{
				char t = '?';
				prs->entity[prs->entpos] = '\0';
				if(strcmp(prs->entity, "amp") == 0)
					t = '&';
				else if(strcmp(prs->entity, "quot") == 0)
					t = '"';
				else if(strcmp(prs->entity, "apos") == 0)
					t = '\'';
				else if(strcmp(prs->entity, "lt") == 0)
					t = '<';
				else if(strcmp(prs->entity, "gt") == 0)
					t = '>';
				buf[pos] = t;
				old = pos;
				prs->context = C_CDATA;
			}
			break;

		case C_COMMENT:

		case C_MARKUP:

		case C_PI:
			break;

		}
		pos++;
	}

	if(prs->charData && prs->context == C_CDATA && old < pos)
	{
		(prs->charData)(prs->udata, &buf[old], pos - old);
	}

	return 1;
}
