/*****
 NAME
 	cxmlfactory.m - source code for CXMLFactory class
 VERSION
 	$Id$
 CHANGELOG
 	$Log$
 */

#include <coconut/cxmlfactory.h>
#include <coconut/csystem.h>
#include <coconut/cindent.h>
#include <coconut/ctext.h>
#include <coconut/cmessage.h>
#include <coconut/fxml.h>
#include <coconut/tobject.h>

#include <libxml/xpath.h>
#include <libxml/xmlerror.h>

#include <stdarg.h>

static void errorHandler(void * ctx, const char * data, ...) ;

/* the variable to keep the errorHandler */
static signal_t	error_sig ;

@implementation CXMLFactory

+ doExpandEntity: (boolean) flag
{
	xmlSubstituteEntitiesDefault(flag ? 1 : 0) ;
	return nil ;
}

+ doValidCheck: (boolean) flag
{
	xmlDoValidityCheckingDefaultValue = flag ? 1 : 0 ;
	return nil ;
}

+ setErrorHandler: (id <PStream>) stream
{
	SET_SIGNAL(error_sig, stream, @selector(putPtr:),NULL,NULL);
	xmlSetGenericErrorFunc(&error_sig, &errorHandler) ;
	return nil ;
}

+ (const utf8_char *) nodeType2Str: (int) type
{
	static const utf8_char unknown_str[] = "?unknown?" ;
	static const utf8_char element_str[] = "element" ;
	static const utf8_char attribute_str[] = "attribute" ;
	static const utf8_char text_str[] = "text" ;
	static const utf8_char cdata_section_str[] = "cdata_section" ;
	static const utf8_char entity_ref_str[] = "entity_ref" ;
	static const utf8_char entity_str[] = "entity" ;
	static const utf8_char pi_str[] = "pi" ;
	static const utf8_char comment_str[] = "comment" ;
	static const utf8_char document_str[] = "document" ;
	static const utf8_char document_type_str[] = "document_type" ;
	static const utf8_char document_frag_str[] = "document_frag" ;
	static const utf8_char notation_str[] = "notation" ;
	static const utf8_char html_document_str[] = "html_document" ;
	static const utf8_char dtd_str[] = "dtd" ;
	static const utf8_char element_decl_str[] = "element_decl" ;
	static const utf8_char attribute_decl_str[] = "attribute_decl" ;
	static const utf8_char entity_decl_str[] = "entity_decl" ;
	static const utf8_char namespace_decl_str[] = "namespace_decl" ;
	static const utf8_char xinclude_start_str[] = "xinclude_start" ;
	static const utf8_char xinclude_end_str[] = "xinclude_end" ;
#if defined(LIBXML_DOCB_ENABLED)
	static const utf8_char docb_document_str[] = "docb_document" ;
#endif

	const char * result = unknown_str ;

#define	CASE(ENUM, STR) \
	  case ENUM: result = STR ; break ;
	switch(type){
	  CASE(XML_ELEMENT_NODE, element_str) ;
	  CASE(XML_ATTRIBUTE_NODE, attribute_str) ;
	  CASE(XML_TEXT_NODE, text_str) ;
	  CASE(XML_CDATA_SECTION_NODE, cdata_section_str) ;
	  CASE(XML_ENTITY_REF_NODE, entity_ref_str) ;
	  CASE(XML_ENTITY_NODE, entity_str) ;
	  CASE(XML_PI_NODE, pi_str) ;
    	  CASE(XML_COMMENT_NODE, comment_str) ;
    	  CASE(XML_DOCUMENT_NODE, document_str) ;
    	  CASE(XML_DOCUMENT_TYPE_NODE, document_type_str) ;
    	  CASE(XML_DOCUMENT_FRAG_NODE, document_frag_str) ;
     	  CASE(XML_NOTATION_NODE, notation_str) ;
	  CASE(XML_HTML_DOCUMENT_NODE, html_document_str) ;
	  CASE(XML_DTD_NODE, dtd_str) ;
	  CASE(XML_ELEMENT_DECL, element_decl_str) ;
    	  CASE(XML_ATTRIBUTE_DECL, attribute_decl_str) ;
	  CASE(XML_ENTITY_DECL, entity_decl_str) ;
    	  CASE(XML_NAMESPACE_DECL, namespace_decl_str) ;
    	  CASE(XML_XINCLUDE_START, xinclude_start_str) ;
    	  CASE(XML_XINCLUDE_END, xinclude_end_str) ;
#if defined(LIBXML_DOCB_ENABLED)
    	  CASE(XML_DOCB_DOCUMENT_NODE, docb_document_str) ;
#endif
	}
#undef	CASE
	return result ;
}

+ (u_int) nodeDepth: (id <PXMLNode>) node
{
	int		depth = 0 ;
	id <PXMLNode>	parent ;
	if(node){
		while((parent = [node parent]) != nil){
			depth++ ;
			node = parent ;
		}
		return depth ; /* -1 for hidden root node */
	} else
		return 0 ;
}

+ pretty: (id <PXMLNode>) node indent: (const utf8_char *) spaces
{
	id <PIndent>	indent ;

	indent = [[CIndent alloc] initIndent: spaces] ;
	  [CXMLFactory p_pretty: node indent: indent] ;
	[indent release] ;
	return nil ;
}

/* This method inserts the stubs for an element node. The following stubs will
   be inserted.
	(1) heading spaces for the node.  this is NOT inserted. (This stub
	    will be inserted by the parent of "node".
	(2a) stubs for children. inserted by the top of loop
	(2b) indent for children. inserted when the allocation of child
	(3a) indent for next children. inserted when the allocation of
	     previois child
	(3b) see 2b
	(4a) see 3a

   ---(1)--- <node> CR
   ---(2a)--- ---(2b)---  first child CR
   ---(3a)--- ---(3b)---  last child CR
   ---(4a)--- </node>
 */
+ p_pretty: (id <PXMLNode>) node indent: (id <PIndent>) indent 
{
	id <PXMLNode>		child ;
	id <PXMLNode>		prev ;
	id <PXMLNode>		next ;
	id <PXMLNode>		stub ;
	id <PConstStr>		contorg ;
	id <PText>		conttext ;
	id <PString>		newcont ;

	if(node == nil)
		return nil ;
	if(![node isElementNode])
		return nil ;
	if((child = [node child]) == nil)
		return nil ;

	while(TRUE){
		next = [child next] ;
		if([child isEmptyTextNode]){
			prev = [child prev] ;
			[child unlink] ;
			destroyXMLNodeObject(child) ;
			child = prev ; /* child will be nil */
		} else if([child isTextNode]){

			/* WARNING: text node are merged when the new node
			     was added. so, the modification of the text
			     must be done before adding newline and spaces */

			/* adjust the content of text (2b) */
			contorg = [child content] ;
			conttext = [CText newTextFromStr: contorg withNewline:
			  TRUE] ;
			[indent incLevel] ;
			  [conttext pretty: [indent unitPtr] indent: indent] ;
			[indent decLevel] ;
			newcont = [conttext concatenate] ;
			[child setContent: [newcont ptr]] ;
			[contorg release] ;
			[conttext release] ;
			[newcont release] ;

			/* add next spaces (newline has already added) (3a) */
			stub = newXMLTextObject([indent ptr]) ;
			[child addNextSibling: stub] ;
		} else if(![child hasChildren]){
			/* add previous spaces (2b) */
			stub = newXMLTextObject([indent unitPtr]) ;
			[child addPrevSibling: stub] ;
			/* add next newline and spaces (3a) */
			stub = newXMLTextObject("\n") ;
			[stub addContent: [indent ptr]] ;
			[child addNextSibling: stub] ;
		} else {
			/* add previous newline and spaces (2b) */
			stub = newXMLTextObject([indent unitPtr]) ;
			[child addPrevSibling: stub] ;
			[indent incLevel] ;
			  /* add child nodes */
			  [CXMLFactory p_pretty: child indent: indent] ;
			[indent decLevel] ;
			/* add next newline and spaces (3a) */
			stub = newXMLTextObject("\n") ;
			[stub addContent: [indent ptr]] ;
			[child addNextSibling: stub] ;
		}
		if(next != nil)
			child = next ;
		else
			break ;
	}
	/* add newline code after the open tag (above (2a)) */
	if((child = [node child]) != nil){
		stub = newXMLTextObject("\n") ;
		[stub addContent: [indent ptr]] ;
		[child addPrevSibling: stub] ;
	}
	return nil ;
}

+ (id <PXMLNode>) searchNodeByName: (const utf8_char *) name
    from: (id <PXMLNode>) node depth: (int) dep follower: (boolean) follower
{
	id <PXMLNode>	result ;
	if(node == nil || dep == 0)
		return nil ;
	if([node compareTagName: name] == 0)
		return node ;
	result = [CXMLFactory searchNodeByName: name from: [node child]
	  depth: dep > 0 ? dep - 1 : dep follower: TRUE] ;
	if(result)
		return result ;
	return follower ? [CXMLFactory searchNodeByName: name from: [node next]
	  depth: dep follower: TRUE] : nil ;
}

+ removeFirstEmptyTextChildren: (id <PXMLNode>) parent
{
	id <PXMLNode>	child ;
	id <PXMLNode>	next ;

	for(child = [parent child] ; child ; child = next){
		next = [child next] ;
		if([child isEmptyTextNode]){
			destroyXMLNodeObject(child) ;
		} else if([child isTextNode]){
			[child removeContentHeadSpaces] ;
		} else
			break ;
	}
	return nil ;
}

+ removeLastEmptyTextChildren: (id <PXMLNode>) parent
{
	id <PXMLNode>	child ;
	id <PXMLNode>	prev ;

	child = [parent child] ; child = [child lastSibling] ;
	for( ; child ; child = prev){
		prev = [child prev] ;
		if([child isEmptyTextNode]){
			destroyXMLNodeObject(child) ;
		} else if([child isTextNode]){
			[child removeContentTailSpaces] ;
		} else
			break ;
	}
	return nil ;
}

+ removeEmptyTextChildren: (id <PXMLNode>) node
{
	id <PXMLNode>	child ;
	id <PXMLNode>	next ;
	if(node){
		child = [node child] ; next = [node next] ;
		if([node isEmptyTextNode]){
			destroyXMLNodeObject(node) ;
		} else if([node isTextNode]){
			[node removeContentSideSpaces] ;
		}
		[CXMLFactory removeEmptyTextChildren: child] ;
		[CXMLFactory removeEmptyTextChildren: next] ;
	}
	return nil ;
}

+ errorByNode: (id <PXMLNode>) node file: (const char *) name 
    format: (const char *) form, ...
{
	va_list		args ;

	va_start(args, form) ;
	[CMessage vfmessage: name lineno: [node lineno]
	  type: error_message code: illegal_format_err format: form
	  valist: args] ;
	va_end(args) ;
	return nil ;
}

@end

#define	BUFSIZE		512
static void errorHandler(void * ctx, const char * data, ...)
{
	static char		buf[BUFSIZE] ;
	int			len ;
	va_list			args ;
	signal_t *		msg ;

	msg = (signal_t *) ctx ;
	va_start(args, data) ;
	  len = vsnprintf(buf, BUFSIZE-1, data, args) ;
	va_end(args) ;
	[msg->obj perform: msg->message with: (void *) buf with: NULL] ;
}

