/*****
 NAME
	chtmltree.m - source code for CHTMLTree class
 VERSION
	$Id$
 CHANGELOG
	$Log$
 */

#include <coconut/chtmltree.h>
#include <coconut/cxmlnode.h>
#include <coconut/cxmlfactory.h>
#include <coconut/cconststr.h>
#include <coconut/cfile.h>
#include <coconut/csystem.h>
#include <coconut/cerror.h>
#include <coconut/dconststr.h>
#include <coconut/fxml.h>

static xmlChar	default_uri[] = "http://www.w3.org/TR/REC-html40/loose.dtd" ;
static xmlChar	default_dtd[] = "-//W3C//DTD HTML 4.0 Transitional//EN" ;
static xmlCharEncoding	default_char_encoding = 0 ;

@implementation CHTMLTree

- init 
{
	/* replace memory allocation functions */
	xmlMemSetup(&extXmlFree, &extXmlMalloc, &extXmlRealloc, &extXmlStrdup) ;

	this_doc = NULL ; current_node = nil ;
	input_name = [[CConstStr alloc] init] ;
	output_name = [[CConstStr alloc] init] ;
	return input_name && output_name ? [super init] : nil ;
}

- (void) dealloc
{
	if(this_doc)
		xmlFreeDoc(this_doc) ;
	[input_name release] ;
	[output_name release] ;
	[super dealloc] ;
}

- newDocument: (const char *) filename
{
	[self clear] ;
	this_doc = htmlNewDoc(default_uri, default_dtd) ;
	[CSystem checkPtr: this_doc] ;
	if(filename == NULL)
		filename = HYPHEN_STR ;
	[output_name setPtr: filename] ;
	return nil ;
}

- (id <PConstStr>) inputFileName
{
	return input_name ;
}

- (id <PConstStr>) outputFileName
{
	return output_name ;
}

- (u_int) loadFile: (const char *) fname validate: (boolean) dovalid
{
	id <PFile>	stream ;
	id <PError>	err ;
	u_int		line ;

	/* open the file to input */
	stream = [[CFile alloc] init] ;
	if((err = [stream open: read_open name: fname]) != nil){
		[stream release] ;
		return 1 ;
	}
	/* load the content */
	line = [self loadStream: stream name: fname validate: dovalid] ;
	/* close the input stream */
	err = [stream close] ;
	g_assert(err == nil) ;
	[stream release] ;
	return line ;
}

- (u_int) loadStream: (id <PStream>) stream name: (const char *) fname
    validate: (boolean) dovalid
{
	id <PConstStr>		linestr ;
	htmlParserCtxtPtr	ctxt ;
	htmlDocPtr		doc ;
	htmlNodePtr		node ;
	int			res ;
	int			lineno ;

	/* for activation of xmlGetLineNo */
	xmlLineNumbersDefault(1) ;
	[CXMLFactory doValidCheck: dovalid] ;

	/* initialize document */
	[self clear] ; lineno = 0 ;

	/* copy the input file name */
	[input_name setPtr: fname ? fname : STDIN_STR] ;

	if((linestr = [stream getLine]) != nil){
		lineno++ ;
		ctxt = htmlCreatePushParserCtxt(NULL, (void *) self,
		  [linestr ptr], [linestr length], [input_name ptr], 
		  default_char_encoding) ;
		[linestr release] ;
		if(ctxt == NULL){
			return lineno ; ;
		}
		while((linestr = [stream getLine]) != nil){
			lineno++ ;
			res = htmlParseChunk(ctxt, [linestr ptr],
			  [linestr length], 0) ;
			if(res != 0){
				htmlFreeParserCtxt(ctxt) ;
				return lineno ;
			}
			[linestr release] ;
		}
		/* last "1" means end of the input */
		htmlParseChunk(ctxt, EMPTY_STR, 0, 1) ;
		doc = ctxt->myDoc ;
		res = ctxt->wellFormed ;
		htmlFreeParserCtxt(ctxt) ;

		if(res){
			this_doc = doc ;
			node = xmlDocGetRootElement(this_doc) ;
			current_node = node ? xmlNodePtr2Object(node) : nil ;
			return 0 ;
		} else {
			xmlFreeDoc(doc) ;
			return lineno ;
		} 
	} else {
		return 1 ;
	}
}

- (id <PError>) saveFile 
{
	id <PFile>	stream ;
	id <PError>	err ;

	if([output_name length]==0 || [output_name comparePtr: HYPHEN_STR]==0)
		return [self saveStream: nil] ;
	/* open output stream */
	stream = [[CFile alloc] init] ;
	if((err = [stream open: write_open name: [output_name ptr]]) != nil){
		[stream release] ;
		return err ;
	}
	/* save the content */
	if((err = [self saveStream: stream]) == nil){
		err = [stream close] ;
	}
	[stream release] ;
	return err ;
}

- (id <PError>) saveFileIfExist
{
	if([output_name length] == 0)
		return nil ;
	else
		return [self saveFile] ;
}

- (id <PError>) saveFileAs: (const char *) name
{
	if(name){
		[output_name setPtr: name] ;
		return [self saveFile] ;
	} else
		return [self saveStream: nil] ;
}

- (id <PError>) saveStream: (id <PStream>) file
{
	id <PError>	result ;
	io_status_t	stat ;
	size_t		ressize ;
	xmlChar *	txtptr ;
	int		txtsize ;

	if(this_doc == NULL || current_node == nil)
		return [CError not_exist] ;
	htmlDocDumpMemory(this_doc, &txtptr, &txtsize) ;
	[CSystem checkPtr: txtptr] ;
	if(file){
		stat = [file putPtr: txtptr length: sizeof(xmlChar)*txtsize];
		if(stat == io_status_normal){
			stat = [file flush] ;
		}
		result = stat==io_status_normal ? nil : [CError can_not_write] ;
	} else {
		ressize = fwrite(txtptr, sizeof(xmlChar), txtsize, stdout) ;
		result = ressize == txtsize ? nil : [CError can_not_write] ;
	}
	xmlFree(txtptr) ;
	return result ;
}

- setCompressMode: (int) mode
{
	g_message("sCM is not supported") ;
	return nil ;
}

- (int) compressMode
{
	g_message("cM is not supported") ;
	return FALSE ;
}

- clear
{
	if(this_doc){
		xmlFreeDoc(this_doc) ;
	}
	this_doc = NULL ;
	current_node = nil ;
	[input_name setPtr: NULL] ;
	[output_name setPtr: NULL] ;
	return nil ;
}

- (id <PXMLNode>) currentNode
{
	return current_node ;
}

- (id <PXMLNode>) rootNode
{
	htmlNodePtr	node ;
	node = xmlDocGetRootElement(this_doc) ;
	return node ? xmlNodePtr2Object(node) : nil ;
}

- (u_int) count
{
	u_int	num = 0 ;
	[[self rootNode] foreach: self message:@selector(foreach_count:with:) 
	  with: PTR2ID(&num)] ;
	return num ;
}

- (id <PXMLNode>) addRootNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	htmlNodePtr 	node ;
	
	g_assert(current_node == nil) ;
	node = xmlNewDocNode(this_doc, NULL, name, content) ;
	[CSystem checkPtr: node] ;
	this_doc->children = node ;
	current_node = xmlNodePtr2Object(node) ;
	return current_node ;
}

- (id <PXMLNode>) addNextNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	id <PXMLNode>	obj ;
	if(current_node == nil){
		return [self addRootNode: name content: content] ;
	} else {
		obj = newXMLNodeObject(NULL, name) ;
		[obj setContent: content] ;
		[current_node addNextSibling: obj] ;
		current_node = obj ;
		return current_node ;
	}
}

- (id <PXMLNode>) addPrevNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	id <PXMLNode>	obj ;

	if(current_node == nil){
		return [self addRootNode: name content: content] ;
	} else {
		obj = newXMLNodeObject(NULL, name) ;
		[obj setContent: content] ;
		[current_node addPrevSibling: obj] ;
		current_node = obj ;
		return current_node ;
	}
}

- (id <PXMLNode>) appendNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	id <PXMLNode>	obj, last ;

	if(current_node == nil){
		return [self addRootNode: name content: content] ;
	} else {
		obj = newXMLNodeObject(NULL, name) ;
		[obj setContent: content] ;
		last = [current_node lastSibling] ;
		[last addNextSibling: obj] ;
		return obj ;
	}
}

- (id <PXMLNode>) prependNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	id <PXMLNode>	obj, first ;

	if(current_node == nil){
		return [self addRootNode: name content: content] ;
	} else {
		obj = newXMLNodeObject(NULL, name) ;
		[obj setContent: content] ;
		first = [obj firstSibling] ;
		[first addPrevSibling: obj] ;
		return obj ;
	}
}

- (id <PXMLNode>) appendChildNode: (const utf8_char *) name 
    content: (const utf8_char *) content
{
	id <PXMLNode>	obj ;

	if(current_node == nil){
		return [self addRootNode: name content: content] ;
	} else {
		obj = newXMLNodeObject(NULL, name) ;
		[obj setContent: content] ;
		[current_node appendChild: obj] ;
		return obj ;
	}
}

- (id <PError>) removeNode ;
{
	id <PXMLNode>	oldnode ;

	if((oldnode = current_node) != nil){
		if(! [self moveNext]){
			if(! [self movePrev]){
				[self moveToParent] ;
			}
		}
		destroyXMLNodeObject(oldnode) ;
		return nil ;
	} else
		return [CError not_exist] ;
}

- moveToHead
{
	if(current_node){
		current_node = [current_node firstSibling] ;
	}
	return nil ;
}

- moveToTail
{
	if(current_node){
		current_node = [current_node lastSibling] ;
	}
	return nil ;
}

- moveToRoot
{
	htmlNodePtr	node ;
	node = xmlDocGetRootElement(this_doc) ;
	current_node = node ? xmlNodePtr2Object(node) : nil ;
	return nil ;
}

- (id <PXMLNode>) moveNext
{
	id <PXMLNode>	next ;

	if(current_node){
		next = [current_node next] ;
		if(next){
			current_node = next ;
			return next ; 
		}
	}
	return nil ;
}

- (id <PXMLNode>) movePrev
{
	id <PXMLNode>	prev ;

	if(current_node){
		prev = [current_node prev] ;
		if(prev){
			current_node = prev ;
			return prev ;
		}
	}
	return nil ;
}

- (id <PXMLNode>) moveToChild
{
	id <PXMLNode> child ;
	if(current_node){
		if((child = [current_node child]) != nil){
			current_node = child ;
			return child ;
		}
	}
	return nil ;
}

- (id <PXMLNode>) moveToParent
{
	id <PXMLNode> parent ;
	if(current_node){
		if((parent = [current_node parent]) != nil){
			current_node = parent ;
			return parent ;
		}
	}
	return nil ;
}

- (id <PXMLNode> ) skipToChild
{
	id <PXMLNode> child ;
	if(current_node){
		if((child = [current_node child]) != nil){
			while(child){
				if([child isElementNode]){
					current_node = child ;
					return child ;
				}
				child = [child next] ;
			}
		}
	}
	return nil ;
}

- (id <PXMLNode>) skipNext
{
	id <PXMLNode>	node ;

	if(current_node){
		node = [current_node next] ;
		while(node){
			if([node isElementNode]){
				current_node = node ;
				return node ;
			}
			node = [node next] ;
		}
	}
	return nil ;
}

- (id <PXMLNode>) skipPrev
{
	id <PXMLNode>	node ;

	if(current_node){
		node = [current_node prev] ;
		while(node){
			if([node isElementNode]){
				current_node = node ;
				return node ;
			}
			node = [node prev] ;
		}
	}
	return nil ;
}

- addNewline
{
	static const xmlChar	newline[] = "\n" ;
	id <PXMLNode>		obj ;

	if(current_node != nil){
		obj = newXMLTextObject(newline) ;
		[current_node addNextSibling: obj] ;
		current_node = obj ;
	}
	return nil ;
}

- appendNewline
{
	static const xmlChar	newline[] = "\n" ;
	id <PXMLNode>		obj, last ;

	if(current_node != nil){
		obj = newXMLTextObject(newline) ;
		last = [current_node lastSibling] ;
		[last addNextSibling: obj] ;
	}
	return nil ;
}

- foreach_count: p1 with: p2
{
	id <PXMLNode>	node = (id <PXMLNode>) p1 ;
	if([node isElementNode]){
		++*((u_int *) p2) ;
	}
	return nil ;
}

- (htmlDocPtr) peekDocPtr
{
	return this_doc ;
}

@end

