/**************************************************************

frameset.c (C-Munipack project)
Frame set context
Copyright (C) 2003 David Motl, dmotl@volny.cz

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 <assert.h>
#include <expat.h>

#include "xmldom.h"
#include "comfun.h"

#define BUFFSIZE 4096

typedef struct _ParseContext
{
	int result;
	CmpackDocument *doc;
	CmpackNode *parent;
	char *cdata;
} ParseContext;

/*************************   HELPER FUNCTIONS   ***************/

static void node_clear(void *data)
{
	CmpackNode *ptr, *next;

	CmpackNode *node = (CmpackNode*)data;
	cmpack_free(node->nodeName);
	cmpack_free(node->nodeValue);

	ptr = node->firstChild; 
	while (ptr) {
		next = ptr->nextSibling;
		if (ptr->destroy)
			ptr->destroy(ptr);
		else
			node_clear(ptr);
		cmpack_free(ptr);
		ptr = next;
	}
}

static void element_clear(void *data)
{
	int i;
	CmpackElement *elem = (CmpackElement*)data;

	for (i=0; i<elem->attr.count; i++) {
		cmpack_free(elem->attr.list[i].name);
		cmpack_free(elem->attr.list[i].value);
	}
	cmpack_free(elem->attr.list);
	node_clear(&elem->node);
}

static CmpackElement *element_new(const char *name, const char **atlist)
{
	int i, j, count;

	CmpackElement *elem = (CmpackElement*)cmpack_calloc(1, sizeof(CmpackElement));
	elem->node.destroy  = element_clear;
	elem->node.nodeType = ELEMENT_NODE;
	elem->node.nodeName = cmpack_strdup(name);
	if (atlist && atlist[0]) {
		count = 0;
  		for (i=0; atlist[i]; i+=2)
			count++;
		elem->attr.list = (CmpackAttribute*)cmpack_malloc(count*sizeof(CmpackAttribute));
  		for (i=j=0; atlist[i]; i+=2) {
			elem->attr.list[j].name = cmpack_strdup(atlist[i]);
			elem->attr.list[j].value = cmpack_strdup(atlist[i+1]);
			j++;
		}
		elem->attr.count = j;
	}
	return elem;
}

static void node_add(CmpackNode *parent, CmpackNode *child)
{
	if (parent && child) {
		child->parentNode = parent;
		if (!parent->lastChild) {
			/* Parent's first child */
			parent->firstChild = child;
			parent->lastChild = child;
		} else {
			/* Parent's next child */
			parent->lastChild->nextSibling = child;
			parent->lastChild = child;
		}
	}
}

static CmpackDocument *document_new(void)
{
	return (CmpackDocument*)cmpack_calloc(1, sizeof(CmpackDocument));
}

static CmpackComment *comment_new(const char *data)
{
	CmpackComment *doc = (CmpackComment*)cmpack_calloc(1, sizeof(CmpackComment));
	doc->node.nodeType = COMMENT_NODE;
	doc->node.nodeValue = cmpack_strdup(data);
	return doc;
}

/* Strip leading and trailing white spaces */
static CmpackCData *cdata_new(char *str)
{
	CmpackCData *cd;

	if (str) {
		/* Remove leading spaces */
		while (*str>0 && *str<=32) 
			str++;
		if (*str==0)
			return NULL;
		/* Create character data node */
		cd = (CmpackCData*)cmpack_calloc(1, sizeof(CmpackCData));
		cd->node.nodeType = TEXT_NODE;
		cd->node.nodeValue = cmpack_strdup(str);
		/* Remove trailing spaces */
		if (cd->node.nodeValue[0]!=0) {
			char *ptr = cd->node.nodeValue + strlen(cd->node.nodeValue)-1;
			while (ptr>=str && *ptr>=0 && *ptr<=32)
				ptr--;
			*(ptr+1) = '\0';
		}
		return cd;
	}
	return NULL;
}

static char *merge_strings(char *str1, const char *str2)
{
	char *out;

	if (!str1) {
		if (str2) 
			return cmpack_strdup(str2);
		else
			return NULL;
	} else {
		if (str2) {
			size_t len1 = strlen(str1), len2 = strlen(str2);
			out = (char*)cmpack_malloc((len1+len2+1)*sizeof(char));
			memcpy(out, str1, len1);
			memcpy(out+len1, str2, len2);
			out[len1+len2] = '\0';
			cmpack_free(str1);
			return out;
		} else
			return str1;
	}
}

static void process_cdata(ParseContext *f)
{
	if (f->cdata && f->cdata[0]!='\0' && f->parent) {
		if (f->parent->lastChild && f->parent->lastChild->nodeType == TEXT_NODE) {
			/* Append to last text node */
			f->parent->lastChild->nodeValue = merge_strings(f->parent->lastChild->nodeValue, f->cdata);
		} else {
			/* Create a new text node */
			node_add(f->parent, (CmpackNode*)cdata_new(f->cdata));
		}
		cmpack_free(f->cdata);
		f->cdata = NULL;
	}
}

/**********************   XML PARSER HANDLERS   *****************/

/* Obligatory callback from XML parser */
static int XMLCALL XMLUnknownEncodingHandler(void *data, const XML_Char *encoding, XML_Encoding *info)
{
 	int i;
 	
 	for (i=0; i<256; i++) 
 		info->map[i] = i;
 	info->data = NULL;
 	info->convert = NULL;
 	info->release = NULL;
 	return XML_STATUS_OK;
}

/* Start element callback. This is called whenever the XML parser finds
 * a start of the element. The 'aname' parameter is name of the element,
 * 'atlist' is the list of attributes (key+val) pairs.
 */
static void XMLCALL XMLStartElementHandler(void *data, const char *name, const char **atlist)
{
  	ParseContext *f = (ParseContext*)data;

	CmpackElement *elem = element_new(name, atlist);
	if (!f->doc->root) 
		f->doc->root = (CmpackNode*)elem;
	if (f->parent)
		process_cdata(f);
	node_add(f->parent, (CmpackNode*)elem);
	f->parent = (CmpackNode*)elem;
}

/* End element handler. XML parser calls this function whenever the end of
 * an element is found.
 */
static void XMLCALL XMLEndElementHandler(void *adata, const char *aname)
{
  	ParseContext *f = (ParseContext*) adata;

	if (f->parent) {
		process_cdata(f);
		f->parent = f->parent->parentNode;
	}
}

/* Character data handler */
static void XMLCALL XMLCharacterDataHandler(void *adata, const char *data, int len)
{
	int dstlen;
  	ParseContext *f = (ParseContext*) adata;

	if (f->parent && len>0) {
		if (f->cdata) {
			dstlen = (int)strlen(f->cdata);
			f->cdata = (char*)cmpack_realloc(f->cdata, (dstlen+len+1)*sizeof(char));
			memcpy(f->cdata + dstlen, data, len);
			f->cdata[dstlen+len] = '\0';
		} else {
			f->cdata = (char*)cmpack_malloc((len+1)*sizeof(char));
			memcpy(f->cdata, data, len);
			f->cdata[len] = '\0';
		}
	}
}

/* Comment handler */
static void XMLCALL XMLCommentHandler(void *adata, const char *data)
{
  	ParseContext *f = (ParseContext*) adata;

	if (f->parent) {
		process_cdata(f);
		node_add(f->parent, (CmpackNode*)comment_new(data));
	}
}

/**********************   PUBLIC FUNCTIONS   *****************/

/* Load document from a file */
CmpackDocument *xml_doc_from_file(FILE *from)
{
	size_t len;
	int done, res = 0;
	char buf[BUFFSIZE];
	ParseContext p;
	XML_Parser parser;

	memset(&p, 0, sizeof(ParseContext));
	p.doc = document_new();

	parser = XML_ParserCreate(NULL);
	XML_SetUserData(parser, &p);
	XML_SetElementHandler(parser, XMLStartElementHandler, XMLEndElementHandler);
	XML_SetUnknownEncodingHandler(parser, XMLUnknownEncodingHandler, NULL);
	XML_SetCharacterDataHandler(parser, XMLCharacterDataHandler);
	XML_SetCommentHandler(parser, XMLCommentHandler);
	do {
		len = fread(buf, 1, BUFFSIZE, from);
		if (ferror(from)) {
			res = CMPACK_ERR_READ_ERROR;
			break;
		}
		done = feof(from);
		if (XML_Parse(parser, buf, (int)len, done)==XML_STATUS_ERROR) {
			res = p.result;
			break;
		}
	} while (!done);
	XML_ParserFree(parser);
	if (res!=0) {
		xml_doc_free(p.doc);
		return NULL;
	}
	return p.doc;
}

/* Destroy the XML document */
void xml_doc_free(CmpackDocument *doc)
{
	if (doc) {
		if (doc->root) {
			node_clear(doc->root);
			cmpack_free(doc->root);
		}
		cmpack_free(doc);
	}
}

/* Get document root */
CmpackElement *xml_doc_get_root(CmpackDocument *doc)
{
	return (CmpackElement*)doc->root;
}

/* Get first child element by name */
CmpackElement *xml_first_element(CmpackElement *elem, const char *name)
{
	CmpackNode *ptr;
	
	for (ptr=elem->node.firstChild; ptr!=NULL; ptr=ptr->nextSibling) {
		if (ptr->nodeType == ELEMENT_NODE && strcmp(ptr->nodeName, name)==0)
			return (CmpackElement*)ptr;
	}
	return NULL;
}

/* Get next child element of the same name */
CmpackElement *xml_next_element(CmpackElement *elem)
{
	CmpackNode *ptr;

	for (ptr=elem->node.nextSibling; ptr!=NULL; ptr=ptr->nextSibling) {
		if (ptr->nodeType == ELEMENT_NODE && strcmp(ptr->nodeName, elem->node.nodeName)==0)
			return (CmpackElement*)ptr;
	}
	return NULL;
}

const char *xml_comment(CmpackNode *node)
{
	CmpackNode *ptr;
	
	for (ptr=node->firstChild; ptr!=NULL; ptr=ptr->nextSibling) {
		if (ptr->nodeType == COMMENT_NODE && ptr->nodeValue!=NULL)
			return ptr->nodeValue;
	}
	return NULL;
}

/* Get number of child nodes of given name */
int xml_get_n_children(CmpackElement *elem, const char *name)
{
	int count = 0;
	CmpackNode *ptr;

	for (ptr=elem->node.firstChild; ptr!=NULL; ptr=ptr->nextSibling) {
		if (ptr->nodeType == ELEMENT_NODE && strcmp(ptr->nodeName, name)==0)
			count++;
	}
	return count;
}

/* Returns nonzero if the element has the attribute */
int xml_has_attribute(CmpackElement *elem, const char *attr)
{
	int i;

	for (i=0; i<elem->attr.count; i++) {
		if (strcmp(elem->attr.list[i].name, attr)==0)
			return 1;
	}
	return 0;
}

const char *xml_attr_s(CmpackElement *elem, const char *attr, const char *defval)
{
	int i;

	for (i=0; i<elem->attr.count; i++) {
		if (strcmp(elem->attr.list[i].name, attr)==0)
			return elem->attr.list[i].value;
	}
	return defval;
}

int xml_attr_i(CmpackElement *elem, const char *attr, int defval)
{
	char *endptr;

	const char *str = xml_attr_s(elem, attr, NULL);
	if (str) {
		int value = strtol(str, &endptr, 10);
		if (endptr!=str)
			return value;
	}
	return defval;
}

double xml_attr_d(CmpackElement *elem, const char *attr, double defval)
{
	char *endptr;

	const char *str = xml_attr_s(elem, attr, NULL);
	if (str) {
		double value = strtod(str, &endptr);
		if (endptr!=str)
			return value;
	}
	return defval;
}

int xml_value_s(CmpackElement *elem, char *buf, int bufsize)
{
	CmpackNode *child = elem->node.firstChild;
	while (child) {
		if (child->nodeType == TEXT_NODE) {
			strcpy_truncate(buf, bufsize, child->nodeValue);
			return 1;
		}
		child = child->nextSibling;
	}
	return 0;
}

const char *xml_value(CmpackElement *elem, const char *defval)
{
	CmpackNode *child = elem->node.firstChild;
	while (child) {
		if (child->nodeType == TEXT_NODE)
			return child->nodeValue;
		child = child->nextSibling;
	}
	return NULL;
}

int xml_value_i(CmpackElement *elem, int defval)
{
	char *endptr;

	const char *str = xml_value(elem, NULL);
	if (str) {
		int value = strtol(str, &endptr, 10);
		if (endptr!=str)
			return value;
	}
	return defval;
}

double xml_value_d(CmpackElement *elem, double defval)
{
	char *endptr;

	const char *str = xml_value(elem, NULL);
	if (str) {
		double value = strtod(str, &endptr);
		if (endptr!=str)
			return value;
	}
	return defval;
}

int xml_value_tm(CmpackElement *elem, struct tm *t)
{
	const char *str, *ptr;
	
	memset(t, 0, sizeof(struct tm));
	str = xml_value(elem, NULL);
	if (str && sscanf(str, " %4d-%2d-%2d", &t->tm_year, &t->tm_mon, &t->tm_mday)==3) {
		t->tm_year -= 1900;
		t->tm_mon--;
		ptr = strchr(str, ' ');
		if (ptr && sscanf(ptr, " %2d:%2d:%2d", &t->tm_hour, &t->tm_min, &t->tm_sec)==3) 
			return 1;
	}
	return 0;
}

/* Get child node's value */
const char *xml_child_value(CmpackElement *elem, const char *name, const char *defval)
{
	CmpackElement *child = xml_first_element(elem, name);
	if (child)
		return xml_value(child, defval);
	return defval;
}

int xml_child_value_s(CmpackElement *elem, const char *name, char *buf, int bufsize)
{
	CmpackElement *child = xml_first_element(elem, name);
	if (child)
		return xml_value_s(child, buf, bufsize);
	return 0;
}

int xml_child_value_i(CmpackElement *elem, const char *name, int defval)
{
	CmpackElement *child = xml_first_element(elem, name);
	if (child)
		return xml_value_i(child, defval);
	return defval;
}

double xml_child_value_d(CmpackElement *elem, const char *name, double defval)
{
	CmpackElement *child = xml_first_element(elem, name);
	if (child)
		return xml_value_d(child, defval);
	return defval;
}

int xml_child_value_tm(CmpackElement *elem, const char *name, struct tm *t)
{
	CmpackElement *child = xml_first_element(elem, name);
	if (child)
		return xml_value_tm(child, t);
	return 0;
}
