/* $Id$ */
/* Copyright (c) 2010-2021 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System libParser */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <System.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "System/Parser/XML.h"

#ifdef DEBUG
# define DEBUG_CALLBACK() fprintf(stderr, "DEBUG: %s()\n", __func__)
#else
# define DEBUG_CALLBACK()
#endif


/* XML */
/* private */
/* types */
typedef enum _XMLContext
{
	XML_CONTEXT_DATA,
	XML_CONTEXT_TAG,
	XML_CONTEXT_TAG_ATTRIBUTES,
	XML_CONTEXT_TAG_ATTRIBUTES_VALUE
} XMLContext;
#define XML_CONTEXT_TAG_FIRST	XML_CONTEXT_TAG
#define XML_CONTEXT_TAG_LAST	XML_CONTEXT_TAG_ATTRIBUTES_VALUE

struct _XML
{
	XMLPrefs prefs;
	XMLDocument * document;

	/* parsing */
	Parser * parser;
	XMLContext context;
	char * inject;
};

typedef enum _XMLCode
{
	XML_CODE_DATA,
	XML_CODE_ENTITY,
	XML_CODE_TAG_ATTRIBUTE,
	XML_CODE_TAG_ATTRIBUTE_VALUE,
	XML_CODE_TAG_CLOSE,
	XML_CODE_TAG_ENTER,
	XML_CODE_TAG_LEAVE,
	XML_CODE_TAG_NAME,
	XML_CODE_TAG_SPECIAL
} XMLCode;


/* prototypes */
static int _xml_inject(XML * xml, char const * string);

/* attribute */
static XMLAttribute * _xml_attribute_new(char const * name, char const * value);
static void _xml_attribute_delete(XMLAttribute * attribute);
static int _xml_attribute_set_value(XMLAttribute * attribute,
		char const * value);

/* callbacks */
static int _xml_callback_data(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_entity(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_attribute(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_attribute_value(Parser * parser, Token * token,
		int c, void * data);
static int _xml_callback_tag_close(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_enter(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_leave(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_name(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_special(Parser * parser, Token * token, int c,
		void * data);
static int _xml_callback_tag_whitespace(Parser * parser, Token * token, int c,
		void * data);

/* document */
static XMLDocument * _xml_document_new(XMLNode * node);
static void _xml_document_delete(XMLDocument * document);

/* filters */
static int _xml_filter_comment(int * c, void * data);
static int _xml_filter_inject(int * c, void * data);
static int _xml_filter_whitespace(int * c, void * data);

/* node */
static XMLNode * _xml_node_new(XMLNodeType type, XMLNodeTag * parent);
static XMLNode * _xml_node_new_data(XMLNodeTag * parent, char const * buffer,
		size_t size);
static XMLNode * _xml_node_new_entity(XMLNodeTag * parent, char const * name);
static XMLNode * _xml_node_new_tag(XMLNodeTag * parent, char const * name);
static void _xml_node_delete(XMLNode * node);

static char const * _xml_node_tag_get_name(XMLNodeTag * node);
static int _xml_node_tag_add_attribute(XMLNodeTag * node,
		XMLAttribute * attribute);
static int _xml_node_tag_add_child(XMLNodeTag * node, XMLNode * child);


/* public */
/* functions */
/* xml_new */
static XML * _new_do(XMLPrefs * prefs, char const * pathname,
		char const * string, size_t length);

XML * xml_new(XMLPrefs * prefs, char const * pathname)
{
	return _new_do(prefs, pathname, NULL, 0);
}

static XML * _new_do(XMLPrefs * prefs, char const * pathname,
		char const * string, size_t length)
{
	XML * xml;

	if((xml = object_new(sizeof(*xml))) == NULL)
		return NULL;
	if(prefs != NULL)
		memcpy(&xml->prefs, prefs, sizeof(xml->prefs));
	else
		memset(&xml->prefs, 0, sizeof(xml->prefs));
	xml->document = NULL;
	if(pathname != NULL)
		xml->parser = parser_new(pathname);
	else
		xml->parser = parser_new_string(string, length);
	xml->context = XML_CONTEXT_DATA;
	xml->inject = NULL;
	if(xml->parser == NULL)
	{
		xml_delete(xml);
		return NULL;
	}
	parser_add_filter(xml->parser, _xml_filter_inject, xml);
	if((xml->prefs.filters & XML_FILTER_WHITESPACE)
			== XML_FILTER_WHITESPACE)
		parser_add_filter(xml->parser, _xml_filter_whitespace, xml);
	/* FIXME filter out comments only optionally */
	parser_add_filter(xml->parser, _xml_filter_comment, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_whitespace, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_special, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_name, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_attribute, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_attribute_value,
			xml);
	parser_add_callback(xml->parser, _xml_callback_tag_close, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_enter, xml);
	parser_add_callback(xml->parser, _xml_callback_tag_leave, xml);
	parser_add_callback(xml->parser, _xml_callback_entity, xml);
	parser_add_callback(xml->parser, _xml_callback_data, xml);
	return xml;
}


/* xml_new_string */
XML * xml_new_string(XMLPrefs * prefs, char const * string, size_t length)
{
	return _new_do(prefs, NULL, string, length);
}


/* xml_delete */
void xml_delete(XML * xml)
{
	if(xml->document != NULL)
		_xml_document_delete(xml->document);
	if(xml->parser != NULL)
		parser_delete(xml->parser);
	object_delete(xml);
}


/* accessors */
/* xml_get_document */
static int _document_data(Token * token, XMLNodeTag * current);
static int _document_entity(Token * token, XMLNodeTag * current);
static int _document_tag_attribute(Token * token, XMLNodeTag * node,
		XMLAttribute ** attribute);
static int _document_tag_attribute_value(Token * token,
		XMLAttribute * attribute);
static int _document_tag_close(Token * token, XMLNodeTag ** current);
static int _document_tag_open(XML * xml, Token * token, XMLNodeTag ** current);

XMLDocument * xml_get_document(XML * xml)
{
	Token * token = NULL;
	XMLCode code;
	XMLNodeTag * current = NULL;
	XMLAttribute * attribute = NULL;
	enum TagState { TS_UNKNOWN = 0, TS_OPEN, TS_CLOSE, TS_SHORT }
	closed = TS_UNKNOWN;

	if(xml->document != NULL)
		return xml->document;
	if((xml->document = _xml_document_new(NULL)) == NULL)
		return NULL;
	for(; parser_get_token(xml->parser, &token) == 0 && token != NULL;
			token_delete(token))
	{
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() code=%u string \"%s\" closed=%d\n",
				__func__, token_get_code(token),
				token_get_string(token), closed);
#endif
		switch((code = token_get_code(token)))
		{
			case XML_CODE_DATA:
				_document_data(token, current);
				break;
			case XML_CODE_ENTITY:
				_document_entity(token, current);
				break;
			case XML_CODE_TAG_ATTRIBUTE:
				_document_tag_attribute(token, current,
						&attribute);
				break;
			case XML_CODE_TAG_ATTRIBUTE_VALUE:
				_document_tag_attribute_value(token, attribute);
				break;
			case XML_CODE_TAG_CLOSE:
				closed = (closed == TS_OPEN) ? TS_SHORT
					: TS_CLOSE;
				break;
			case XML_CODE_TAG_ENTER:
				break;
			case XML_CODE_TAG_LEAVE:
				if(closed == TS_SHORT)
					current = current->parent;
				closed = TS_UNKNOWN;
				break;
			case XML_CODE_TAG_NAME:
				if(closed == TS_CLOSE)
				{
					_document_tag_close(token, &current);
					break;
				}
				closed = TS_OPEN;
				_document_tag_open(xml, token, &current);
				break;
			case XML_CODE_TAG_SPECIAL:
				break;
		}
	}
	return xml->document;
}

static int _document_data(Token * token, XMLNodeTag * current)
{
	XMLNode * node;
	String const * string;
	size_t size = 0;

	if(current == NULL)
		return -1;
	if((string = token_get_string(token)) != NULL)
		size = string_get_length(string);
	node = _xml_node_new_data(current, string, size);
	return _xml_node_tag_add_child(current, node);
}

static int _document_entity(Token * token, XMLNodeTag * current)
{
	XMLNode * node;
	String const * string;

	if(current == NULL)
		return -1;
	if((string = token_get_string(token)) == NULL)
		return -1;
	if((node = _xml_node_new_entity(current, string)) == NULL)
		return -1;
	return _xml_node_tag_add_child(current, node);
}

static int _document_tag_attribute(Token * token, XMLNodeTag * current,
		XMLAttribute ** attribute)
{
	if(current == NULL)
		return -1;
	if((*attribute = _xml_attribute_new(token_get_string(token), NULL))
			== NULL)
		return -1;
	return _xml_node_tag_add_attribute(current, *attribute);
}

static int _document_tag_attribute_value(Token * token,
		XMLAttribute * attribute)
{
	if(attribute == NULL)
		return -1;
	return _xml_attribute_set_value(attribute, token_get_string(token));
}

static int _document_tag_close(Token * token, XMLNodeTag ** current)
{
	char const * parent;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__,
			token_get_string(token));
#endif
	if(*current == NULL)
		return -1; /* XXX the document is malformed */
	parent = _xml_node_tag_get_name(*current);
	if(strcmp(parent, token_get_string(token)) != 0)
		return -1; /* XXX the document is malformed */
	*current = (*current)->parent;
	return 0;
}

static int _document_tag_open(XML * xml, Token * token, XMLNodeTag ** current)
{
	XMLNode * node;

	if((node = _xml_node_new_tag(*current, token_get_string(token)))
			== NULL)
		return -1;
	if(*current == NULL)
		xml->document->root = node;
	else
		_xml_node_tag_add_child(*current, node);
	*current = &node->tag;
	return 0;
}


/* xml_get_filename */
char const * xml_get_filename(XML * xml)
{
	return parser_get_filename(xml->parser);
}


/* node */
/* xml_node_get_attribute_value_by_name */
char const * xml_node_get_attribute_value_by_name(XMLNode * node,
		char const * name)
{
	size_t i;

	if(node->type != XML_NODE_TYPE_TAG)
		return NULL;
	for(i = 0; i < node->tag.attributes_cnt; i++)
		if(strcmp(node->tag.attributes[i]->name, name) == 0)
			return node->tag.attributes[i]->value;
	return NULL;
}


/* private */
/* functions */
/* xml_inject */
static int _xml_inject(XML * xml, char const * string)
{
	if(string == NULL || string[0] == '\0')
		return 0; /* don't bother */
	if(xml->inject == NULL)
	{
		if((xml->inject = string_new(string)) == NULL)
			return -1;
	}
	else if(string_append(&xml->inject, string) != 0)
		return -1;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p, \"%s\") => \"%s\"\n", __func__,
			(void *)xml, string, xml->inject);
#endif
	return 0;
}


/* attribute */
/* xml_attribute_new */
static XMLAttribute * _xml_attribute_new(char const * name, char const * value)
{
	XMLAttribute * attribute;

	if(name == NULL)
	{
		error_set_code(1, "%s", strerror(EINVAL));
		return NULL;
	}
	if(value == NULL)
		value = name;
	if((attribute = object_new(sizeof(*attribute))) == NULL)
		return NULL;
	attribute->name = string_new(name);
	attribute->value = string_new(value);
	if(attribute->name == NULL || attribute->value == NULL)
	{
		_xml_attribute_delete(attribute);
		return NULL;
	}
	return attribute;
}


/* xml_attribute_delete */
static void _xml_attribute_delete(XMLAttribute * attribute)
{
	string_delete(attribute->name);
	string_delete(attribute->value);
	object_delete(attribute);
}


/* xml_attribute_set_value */
static int _xml_attribute_set_value(XMLAttribute * attribute,
		char const * value)
{
	char * v;

	if((v = string_new(value)) == NULL)
		return -1;
	string_delete(attribute->value);
	attribute->value = v;
	return 0;
}


/* callbacks */
/* xml_callback_data */
static int _xml_callback_data(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	char * string = NULL;
	size_t len = 0;
	char * p;

	if(xml->context != XML_CONTEXT_DATA)
		return -1;
	while(c != EOF && c != '<')
	{
		if((p = realloc(string, len + 2)) == NULL)
			return -1; /* XXX report error */
		string = p;
		string[len++] = c;
		c = parser_scan_filter(parser);
		if(c == '&')
			break;
	}
	if(len == 0)
		return -1;
	DEBUG_CALLBACK();
	token_set_code(token, XML_CODE_DATA);
	string[len] = '\0';
	token_set_string(token, string);
	free(string);
	return 0;
}


/* xml_callback_entity */
static int _xml_callback_entity(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	char * string = NULL;
	size_t len = 0;
	char * p;

	if(xml->context != XML_CONTEXT_DATA || c != '&')
		return -1;
	for(c = parser_scan_filter(parser); c != EOF && c != '<'; len++)
	{
		if((p = realloc(string, len + 2)) == NULL)
			return -1; /* XXX report error */
		string = p;
		string[len] = c;
		c = parser_scan_filter(parser);
		if(string[len] == ';')
			break;
	}
	if(len == 0)
		return -1;
	DEBUG_CALLBACK();
	token_set_code(token, XML_CODE_ENTITY);
	string[len] = '\0';
	token_set_string(token, string);
	free(string);
	return 0;
}


/* xml_callback_tag_attribute */
static int _xml_callback_tag_attribute(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	int q = '\0';
	char * string = NULL;
	size_t len = 0;
	char * p;

	if(xml->context != XML_CONTEXT_TAG_ATTRIBUTES
			|| (!isalnum(c) && c != '"'))
		return -1;
	if(c == '"')
	{
		q = c;
		c = parser_scan_filter(parser);
	}
	while(c != EOF && (isalnum(c) || c == ':' || c == '-'
				|| (q != '\0' && c != q)))
	{
		if((p = realloc(string, len + 2)) == NULL)
			return -1; /* XXX report error */
		string = p;
		string[len++] = c;
		c = parser_scan_filter(parser);
	}
	if(len == 0)
		return -1;
	if(q != '\0')
		parser_scan_filter(parser);
	DEBUG_CALLBACK();
	token_set_code(token, XML_CODE_TAG_ATTRIBUTE);
	string[len] = '\0';
	token_set_string(token, string);
	free(string);
	if(c == '=')
		xml->context = XML_CONTEXT_TAG_ATTRIBUTES_VALUE;
	return 0;
}


/* xml_callback_tag_attribute_value */
static int _xml_callback_tag_attribute_value(Parser * parser, Token * token,
		int c, void * data)
{
	XML * xml = data;
	int q = '\0';
	char * string = NULL;
	size_t len = 0;
	char * p;

	if(xml->context != XML_CONTEXT_TAG_ATTRIBUTES_VALUE)
		return -1;
	if(c != '=')
		return -1;
	DEBUG_CALLBACK();
	if((c = parser_scan_filter(parser)) == '\'' || c == '"')
	{
		q = c;
		c = parser_scan_filter(parser);
	}
	while(c != EOF && ((q == '\0' && isalnum(c)) || (q != '\0' && c != q)))
	{
		if((p = realloc(string, len + 2)) == NULL)
			return -1; /* XXX report error */
		string = p;
		string[len++] = c;
		c = parser_scan_filter(parser);
	}
	if(q != '\0')
		parser_scan_filter(parser);
	token_set_code(token, XML_CODE_TAG_ATTRIBUTE_VALUE);
	if(len == 0)
		token_set_string(token, "");
	else
	{
		string[len] = '\0';
		token_set_string(token, string);
		free(string);
	}
	xml->context = XML_CONTEXT_TAG_ATTRIBUTES;
	return 0;
}


/* xml_callback_tag_close */
static int _xml_callback_tag_close(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;

	if(c != '/')
		return -1;
	if(xml->context < XML_CONTEXT_TAG_FIRST
			|| xml->context > XML_CONTEXT_TAG_LAST)
		return -1;
	DEBUG_CALLBACK();
	parser_scan_filter(parser);
	token_set_code(token, XML_CODE_TAG_CLOSE);
	token_set_string(token, "/");
	return 0;
}


/* xml_callback_tag_enter */
static int _xml_callback_tag_enter(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;

	if(xml->context != XML_CONTEXT_DATA || c != '<')
		return -1;
	DEBUG_CALLBACK();
	parser_scan_filter(parser);
	xml->context = XML_CONTEXT_TAG;
	token_set_code(token, XML_CODE_TAG_ENTER);
	token_set_string(token, "<");
	return 0;
}


/* xml_callback_tag_leave */
static int _xml_callback_tag_leave(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;

	if(c != '>')
		return -1;
	if(xml->context < XML_CONTEXT_TAG_FIRST
			|| xml->context > XML_CONTEXT_TAG_LAST)
		return -1;
	DEBUG_CALLBACK();
	parser_scan_filter(parser);
	xml->context = XML_CONTEXT_DATA;
	token_set_code(token, XML_CODE_TAG_LEAVE);
	token_set_string(token, ">");
	return 0;
}


/* xml_callback_tag_name */
static int _xml_callback_tag_name(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	char * string = NULL;
	size_t len = 0;
	char * p;

	if(xml->context != XML_CONTEXT_TAG || !isalnum(c))
		return -1;
	DEBUG_CALLBACK();
	do
	{
		if((p = realloc(string, len + 2)) == NULL)
			return -1; /* XXX report error */
		string = p;
		string[len++] = c;
	}
	while((c = parser_scan_filter(parser)) != EOF && c != '<' && c != '!'
			&& c != '?' && c != '/' && c != '=' && c != '>'
			&& !isspace(c));
	token_set_code(token, XML_CODE_TAG_NAME);
	string[len] = '\0';
	token_set_string(token, string);
	free(string);
	xml->context = XML_CONTEXT_TAG_ATTRIBUTES;
	return 0;
}


/* xml_callback_tag_special */
/* FIXME decompose this function in at least two different ones */
static int _xml_callback_tag_special(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	char buf[2] = { '\0', '\0' };

	if((xml->context != XML_CONTEXT_TAG && xml->context
				!= XML_CONTEXT_TAG_ATTRIBUTES)
			|| (c != '?' && c != '!'))
		return -1;
	DEBUG_CALLBACK();
	buf[0] = c;
	parser_scan_filter(parser);
	token_set_code(token, XML_CODE_TAG_SPECIAL);
	token_set_string(token, buf);
	return 0;
}


/* xml_callback_tag_whitespace */
static int _xml_callback_tag_whitespace(Parser * parser, Token * token, int c,
		void * data)
{
	XML * xml = data;
	(void) token;

	if(!isspace(c))
		return -1;
	if(xml->context < XML_CONTEXT_TAG_FIRST
			|| xml->context > XML_CONTEXT_TAG_LAST)
		return -1;
	DEBUG_CALLBACK();
	while(isspace(c))
		c = parser_scan_filter(parser);
	if(xml->context == XML_CONTEXT_TAG_ATTRIBUTES_VALUE)
		xml->context = XML_CONTEXT_TAG_ATTRIBUTES;
	return -1;
}


/* document */
/* xml_document_new */
static XMLDocument * _xml_document_new(XMLNode * node)
{
	XMLDocument * document;

	if(node != NULL && node->type != XML_NODE_TYPE_TAG)
	{
		error_set_code(1, "%s", strerror(EINVAL));
		return NULL;
	}
	if((document = object_new(sizeof(*document))) == NULL)
		return NULL;
	document->root = node;
	return document;
}


/* xml_document_delete */
static void _xml_document_delete(XMLDocument * document)
{
	if(document->root != NULL)
		_xml_node_delete(document->root);
	object_delete(document);
}


/* filters */
/* xml_filter_comment */
static int _xml_filter_comment(int * c, void * data)
{
	XML * xml = data;
	char start[5] = "<!--";
	size_t i;

	if(*c != '<')
		return 0;
	for(i = 1; i < sizeof(start) - 1; i++)
	{
		if((*c = parser_scan(xml->parser)) == start[i])
			continue;
		start[i] = *c;
		start[i + 1] = '\0';
		if(_xml_inject(xml, &start[1]) != 0)
			return -1;
		*c = '<';
		return -1;
	}
	for(*c = parser_scan(xml->parser), i = 0; *c != EOF;
			*c = parser_scan(xml->parser))
		if(*c == '-')
			i++;
		else if(i >= 2 && *c == '>')
			break;
		else
			i = 0;
	*c = parser_scan(xml->parser);
	return 0;
}


/* xml_filter_inject */
static int _xml_filter_inject(int * c, void * data)
{
	XML * xml = data;
	size_t len;

	if(xml->inject == NULL)
		return 0;
	if((len = strlen(xml->inject)) > 0)
	{
		*c = xml->inject[0];
		memmove(xml->inject, &xml->inject[1], len--);
	}
	if(len > 0)
		return -1;
	free(xml->inject);
	xml->inject = NULL;
	return 0;
}


/* xml_filter_whitespace */
static int _xml_filter_whitespace(int * c, void * data)
{
	XML * xml = data;
	char buf[2] = { '\0', '\0' };

	if(!isspace(*c))
		return 0;
	for(*c = parser_scan(xml->parser); isspace(*c);
			*c = parser_scan(xml->parser));
	if(*c == EOF)
		return 0;
	buf[0] = *c;
	if(_xml_inject(xml, buf) != 0)
		return -1;
	*c = ' ';
	return -1;
}


/* node */
/* xml_node_new */
static XMLNode * _xml_node_new(XMLNodeType type, XMLNodeTag * parent)
{
	XMLNode * node;

	if((node = object_new(sizeof(*node))) == NULL)
		return NULL;
	node->type = type;
	node->parent.parent = parent;
	return node;
}


/* xml_node_new_data */
static XMLNode * _xml_node_new_data(XMLNodeTag * parent, char const * buffer,
		size_t size)
{
	XMLNode * node;

	if((node = _xml_node_new(XML_NODE_TYPE_DATA, parent)) == NULL)
		return NULL;
	/* XXX use buffer_new() */
	if((node->data.buffer = object_new(size + 1)) == NULL)
	{
		_xml_node_delete(node);
		return NULL;
	}
	memcpy(node->data.buffer, buffer, size);
	node->data.buffer[size] = '\0'; /* terminating NULL character */
	node->data.size = size;
	return node;
}


/* xml_node_new_entity */
static XMLNode * _xml_node_new_entity(XMLNodeTag * parent, char const * name)
{
	XMLNode * node;

	if((node = _xml_node_new(XML_NODE_TYPE_ENTITY, parent)) == NULL)
		return NULL;
	if((node->entity.name = string_new(name)) == NULL)
	{
		_xml_node_delete(node);
		return NULL;
	}
	return node;
}


/* xml_node_new_tag */
static XMLNode * _xml_node_new_tag(XMLNodeTag * parent, char const * name)
{
	XMLNode * node;

	if((node = _xml_node_new(XML_NODE_TYPE_TAG, parent)) == NULL)
		return NULL;
	node->tag.name = string_new(name);
	node->tag.attributes = NULL;
	node->tag.attributes_cnt = 0;
	node->tag.childs = NULL;
	node->tag.childs_cnt = 0;
	if(node->tag.name == NULL)
	{
		_xml_node_delete(node);
		return NULL;
	}
	return node;
}


/* xml_node_delete */
static void _xml_node_delete(XMLNode * node)
{
	size_t i;

	switch(node->type)
	{
		case XML_NODE_TYPE_DATA:
			object_delete(node->data.buffer);
			break;
		case XML_NODE_TYPE_TAG:
			for(i = 0; i < node->tag.attributes_cnt; i++)
				_xml_attribute_delete(node->tag.attributes[i]);
			free(node->tag.attributes);
			for(i = 0; i < node->tag.childs_cnt; i++)
				_xml_node_delete(node->tag.childs[i]);
			free(node->tag.childs);
			free(node->tag.name);
			break;
		case XML_NODE_TYPE_ENTITY:
			string_delete(node->entity.name);
			break;
	}
	object_delete(node);
}


/* xml_node_tag_get_name */
static char const * _xml_node_tag_get_name(XMLNodeTag * node)
{
	return node->name;
}


/* xml_node_tag_add_attribute */
static int _xml_node_tag_add_attribute(XMLNodeTag * node,
		XMLAttribute * attribute)
{
	XMLAttribute ** p;

	if((p = realloc(node->attributes, sizeof(*p) * (node->attributes_cnt
						+ 1))) == NULL)
		return error_set_code(1, "%s", strerror(errno));
	node->attributes = p;
	node->attributes[node->attributes_cnt++] = attribute;
	return 0;
}


/* xml_node_tag_add_child */
static int _xml_node_tag_add_child(XMLNodeTag * node, XMLNode * child)
{
	XMLNode ** p;

	if((p = realloc(node->childs, sizeof(*p) * (node->childs_cnt + 1)))
			== NULL)
		return error_set_code(1, "%s", strerror(errno));
	node->childs = p;
	node->childs[node->childs_cnt++] = child;
	return 0;
}
