/*****
 NAME
	cxmlnode.m - source code for CXMLNode class
 VERSION
	$Id$
 CHANGELOG
	$Log$
 */

#include <coconut/cxmlnode.h>
#include <coconut/cxmlalloc.h>
#include <coconut/cxmlfactory.h>
#include <coconut/cconststr.h>
#include <coconut/cmemory.h>
#include <coconut/csystem.h>
#include <coconut/cerror.h>
#include <coconut/ptext.h>
#include <coconut/fxml.h>
#include <coconut/dconststr.h>
#include <ctype.h>

@implementation CXMLNode 

- init
{
	return [self initXMLNode: NULL] ;
}

- initXMLNode: (xmlNodePtr) node
{
	current_node = node ; 
	attr_ptr = NULL ;
	node_selection = selection_undetermined ;
	return [super init] ;
}

/* do not dispose element by "release", use xmlDestroyNode function
- (void) dealloc
{
}
 */

- (boolean) isElementNode
{
	return current_node->type == XML_ELEMENT_NODE ;
}

- (boolean) isTextNode
{
	return current_node->type == XML_TEXT_NODE ;
}

- (boolean) isEmptyTextNode
{	
	xmlChar *	content ;
	xmlChar		c ;
	boolean		isempty = TRUE ;
	if(current_node->type != XML_TEXT_NODE)
		return FALSE ;
	content = xmlNodeGetContent(current_node) ;
	if(content){
		for( ; (c = *content) != '\0' ; content++)
			if(!isspace(c)){
				isempty = FALSE ;
				break ;
			}
	}
	return isempty ;
}

- (boolean) hasChildren
{
	return current_node->children != NULL ;
}

- (boolean) hasAttrName: (const utf8_char *) name
{
	return xmlHasProp(current_node, name) != NULL ;
}

- (const utf8_char *) tagName
{
	return current_node->name ;
}

- (int) type
{
	return current_node->type ;
}

- (id <PConstStr>) content
{
	id <PConstStr>	str ;
	xmlChar * content ;
	content = xmlNodeGetContent(current_node) ;
	if(content){
		str = [CConstStr newConstStr: content] ;
		extXmlFree(content) ;
	} else
		str = nil ;
	return str ;
}

- (selection_t) selection
{
	return node_selection ;
}

- (u_int) lineno
{
	/* the "+1" means increase the line number for the
	   fist line <?xml version=".."> */
	 return xmlGetLineNo(current_node) + 1 ;
}

- (id <PXMLNode>) rootNode
{
	xmlNodePtr	root ;
	if(current_node){
		root = xmlDocGetRootElement(current_node->doc) ;
		return xmlNodePtr2Object(root) ;
	}
	return NULL ;
}

- setTagName: (const utf8_char *) name
{
	xmlNodeSetName(current_node, name) ;
	return nil ;
}

- setContent: (const utf8_char *) content
{
	xmlNodeSetContent(current_node, content) ;
	return nil ;
}

- setContent: (const utf8_char *) content length: (u_int) len
{
	xmlNodeSetContentLen(current_node, content, len) ;
	return nil ;
}

- addContent: (const utf8_char *) content
{
	xmlNodeAddContent(current_node, content) ;
	return nil ;
}

- addContent: (const utf8_char *) content length: (u_int) len
{
	xmlNodeAddContentLen(current_node, content, len) ;
	return nil ;
}

- removeContentHeadSpaces
{
	xmlChar *	content ;
	xmlChar *	head ;
	xmlChar 	c ;

	if(current_node->type != XML_TEXT_NODE)
		return nil ;
	content = xmlNodeGetContent(current_node) ;
	if(content == NULL)
		return nil ;
	for(head = content ; (c = *head) != '\0' ; head++)
		if(!isspace(c))
			break ;
	xmlNodeSetContentLen(current_node, head, strlen(head)) ;
	extXmlFree(content) ;
	return nil ;
}

- removeContentTailSpaces
{
	xmlChar *	content ;
	xmlChar *	tail ;
	xmlChar 	c ;
	u_int		len ;

	if(current_node->type != XML_TEXT_NODE)
		return nil ;
	content = xmlNodeGetContent(current_node) ;
	len = strlen(content) ;
	if(len > 0){
		for(tail = &content[len-1] ; tail > content ; tail--){
			c = *tail ;
			if(!isspace(c))
				break ;
		}
		xmlNodeSetContentLen(current_node, content, tail - content) ;
		extXmlFree(content) ;
	}
	return nil ;
}

- removeContentSideSpaces
{
	xmlChar *	content ;
	xmlChar *	head ;
	xmlChar *	tail ;
	xmlChar 	c ;
	u_int		len ;

	if(current_node->type != XML_TEXT_NODE)
		return nil ;
	content = xmlNodeGetContent(current_node) ;
	if(content == NULL)
		return nil ;
	for(head = content ; (c = *head) != '\0' ; head++)
		if(!isspace(c))
			break ;
	len = strlen(head) ;
	if(len>0){
		for(tail = &head[len-1] ; tail > head ; tail--){
			c = *tail ;
			if(!isspace(c))
				break ;
		}
		xmlNodeSetContentLen(current_node, head, tail - head) ;
	} else
		xmlNodeSetContentLen(current_node, "", 0) ;
	extXmlFree(content) ;
	return nil ;
}

- setSelection: (selection_t) sel 
{
	node_selection = sel ;
	return nil ;
}

- setUndetermined
{
	node_selection = selection_undetermined ;
	return nil ;
}

- setRejected
{
	node_selection = selection_rejected ;
	return nil ;
}

- setAccepted
{
	node_selection = selection_accepted ;
	return nil ;
}

- chooseTagNameByStr: (const utf8_char *) name
{
	if(IS_REJECTED(node_selection))
		return nil ;
	node_selection = selection_rejected ;
	if(current_node->type == XML_ELEMENT_NODE){
		if([self compareTagName: name] == 0){
			node_selection = selection_accepted ;
		}
	}
	return nil ;
}

- chooseTagNameByRegExp: (id <PRegExp>) regexp
{
	id <PError>		err ;
	off_t			start ;
	off_t			stop ;

	if(IS_REJECTED(node_selection))
		return nil ;
	node_selection = selection_rejected ;
	if(current_node->type != XML_ELEMENT_NODE){
		return nil ;
	}
	if((err = [regexp match: [self tagName] start:&start stop:&stop])==nil){
		node_selection = selection_accepted ;
	} else if([err code] == not_matched_err){
		/* reset the error flag */
		err = nil ;
	} else {
		g_error("cxmlfactory:cTNE illegal selector") ;
	}
	return err ;
}

- chooseAttrNameByStr: (const utf8_char *) name 
{
	const utf8_char *	attrname ;

	if(IS_REJECTED(node_selection))
		return nil ;
	node_selection = selection_rejected ;
	if(current_node->type != XML_ELEMENT_NODE)
		return nil ;
	[self setFirstAttr] ;
	for( ; (attrname = [self attrName]) != NULL  ; [self moveNextAttr]){
		if(xmlStrcmp(attrname, name) == 0){
			node_selection = selection_accepted ;
			break ;
		}
	}
	return nil ;
}

- chooseAttrNameByRegExp: (id <PRegExp>) regexp
{
	id <PError>		err = nil ;
	const utf8_char *	attrname ;
	off_t			matchstart ;
	off_t			matchstop ;

	if(IS_REJECTED(node_selection))
		return nil ;
	node_selection = selection_rejected ;
	if(current_node->type != XML_ELEMENT_NODE)
		return nil ;
	[self setFirstAttr] ;
	for( ; (attrname = [self attrName]) != NULL  ; [self moveNextAttr]){
		err= [regexp match: attrname start:&matchstart stop:&matchstop];
		if(err == nil){
			node_selection = selection_accepted ;
			break ;
		} else if([err code] != not_matched_err){
			err = nil ;
		} else {
			g_error("cxmlfactory:cANBRE illegal selecter") ;
		}
	}
	return err ;
}

- setAttr: (const utf8_char *) name value: (const utf8_char *) value
{
	xmlSetProp(current_node, name, value) ;
	return nil ;
}

- unsetAttr: (const utf8_char *) name
{
	xmlUnsetProp(current_node, name) ;
	return nil ;
}

- changeAttr: (const utf8_char *) name value: (const utf8_char *) value
{
	xmlChar *	orgval ;

	if(current_node->type != XML_ELEMENT_NODE)
		return nil ;
	if((orgval = xmlGetProp(current_node, name)) != NULL){
		xmlSetProp(current_node, name, value) ;
	}
	return nil ;
}

- removeAttrByRegExp: (id <PRegExp>) nameexp value: (id <PRegExp>) valueexp
{
	const utf8_char *	attrname ;
	id <PConstStr> 		attrvalue ;
	id <PError>		err ;
	off_t			start, stop ;

	if(![self isElementNode])
		return nil ;
	[self setFirstAttr] ;
	for( ; (attrname = [self attrName])!=NULL ; [self moveNextAttr]){
		err = [nameexp match: attrname start:&start stop:&stop] ;
		if(err == nil){
			attrvalue = [self getAttrValue] ;
			err = [valueexp match: [attrvalue ptr] 
			  start:&start stop:&stop] ;
			if(err == nil){
				[self unsetAttr: attrname] ;
			}
			[attrvalue release] ;
		}
	}
	return nil ;
}

- (id <PConstStr>) getAttrValueByName: (const utf8_char *) attrname
{
	id <PConstStr>	str ;
	xmlChar *	attrvalue ;
	attrvalue = xmlGetProp(current_node, attrname) ;
	if(attrvalue){
		str = [CConstStr newConstStr: attrvalue] ;
		extXmlFree(attrvalue) ;
	} else
		str = nil ;
	return str ;
}

- (id <PConstStr>) getChildText
{
	id <PConstStr>	str ;
	xmlChar *	childtext ;
	xmlNodePtr	child ;

	str = nil ;
	if((child = current_node->children) != NULL){
		childtext = xmlNodeListGetString(current_node->doc, child,1) ;
		if(childtext){
			str = [CConstStr newConstStr: childtext] ;
			extXmlFree(childtext) ;
		}
	}
	return str ;
}

- (boolean) setFirstAttr
{
	attr_ptr = current_node->properties ;
	return attr_ptr != NULL ;
}

  /* free the result string by freeAttrValue method */
- (const utf8_char *) attrName
{
	return attr_ptr ? attr_ptr->name : NULL ;
}

- (int) compareAttrName: (const utf8_char *) name
{
	const utf8_char * thisname ;
	thisname = attr_ptr ? attr_ptr->name : (const utf8_char *) EMPTY_STR ;
	return xmlStrcmp(thisname, name) ;
}

- (id <PConstStr>) getAttrValue
{
	id <PConstStr>	str ;
	xmlChar *	attrvalue ;
	xmlNodePtr	child ;

	str = nil ;
	if((child = attr_ptr->children) != NULL){
		attrvalue = xmlNodeListGetString(attr_ptr->doc, child,1) ;
		if(attrvalue){
			str = [CConstStr newConstStr: attrvalue] ;
			extXmlFree(attrvalue) ;
		}
	} 
	return str ;
}

- (boolean) moveNextAttr
{
	if(attr_ptr){
		attr_ptr = attr_ptr->next ;
		return TRUE ;
	} else
		return FALSE ;
}

- (id <PXMLNode>) next
{
	xmlNodePtr	next = current_node->next ;
	return next ? xmlNodePtr2Object(next) : nil ;
}

- (id <PXMLNode>) prev
{
	xmlNodePtr	prev = current_node->prev ;
	return prev ? xmlNodePtr2Object(prev) : nil ;
}

- (id <PXMLNode>) child
{
	xmlNodePtr	child = current_node->children ;
	return child ? xmlNodePtr2Object(child) : nil ;
}

- (id <PXMLNode>) lastChild
{
	xmlNodePtr	child = xmlGetLastChild(current_node) ;
	return child ? xmlNodePtr2Object(child) : nil ;
}

- (id <PXMLNode>) parent
{
	xmlNodePtr	parent = current_node->parent ;
	return parent ? xmlNodePtr2Object(parent) : nil ;
}

- (id <PXMLNode>) firstSibling
{
	xmlNodePtr	node, prev ;

	node = current_node ; prev = node->prev ;
	while(prev){
		node = prev ; prev = node->prev ;
	}
	return xmlNodePtr2Object(node) ;
}

- (id <PXMLNode>) lastSibling
{	
	xmlNodePtr	node, next ;

	node = current_node ; next = node->next ;
	while(next){
		node = next ; next = node->next ;
	}
	return xmlNodePtr2Object(node) ;
}

- addNextSibling: (id <PXMLNode>) node
{
	xmlAddNextSibling(current_node, [node getNodePtr]) ;
	return nil ;
}

- addPrevSibling: (id <PXMLNode>) node
{
	xmlAddPrevSibling(current_node, [node getNodePtr]) ;
	return nil ;
}

- appendChild: (id <PXMLNode>) node
{
	xmlAddChild(current_node, [node getNodePtr]) ;
	return nil ;
}

- appendChildList: (id <PXMLNode>) node
{
	xmlAddChildList(current_node, [node getNodePtr]) ;
	return nil ;
}

- prependChild: (id <PXMLNode>) node
{
	xmlNodePtr	prev = current_node->prev ;
	xmlAddChild(prev ? prev : current_node, [node getNodePtr]) ;
	return nil ;
}

- prependChildList: (id <PXMLNode>) node
{
	xmlNodePtr	prev = current_node->prev ;
	xmlAddChildList(prev ? prev : current_node, [node getNodePtr]) ;
	return nil ;
}

- (int) compareTagName: (const utf8_char *) name
{
	int	delta ;
	if((delta = current_node->type - XML_ELEMENT_NODE) == 0){
		return xmlStrcmp(current_node->name, name) ;
	} else
		return delta ;
}

- (id <PXMLNode>) duplicate
{
	xmlNodePtr	newnode ;
	/* "1" means the recursive copy */
	newnode = xmlCopyNode(current_node, 1) ;
	[CSystem checkPtr: newnode] ;
	return xmlNodePtr2Object(newnode) ;
}

- (id <PXMLNode>) duplicateWithSiblings
{
	xmlNodePtr	newnode ;
	newnode = xmlCopyNodeList(current_node) ;
	[CSystem checkPtr: newnode] ;
	return xmlNodePtr2Object(newnode) ;
}

- unlink
{
	[self foreachObject: @selector(setUndetermined) with: nil with: nil] ;
	xmlUnlinkNode(current_node) ;
	return nil ;
}

- foreach: (id) obj message:(SEL) message with: p1
{
	id		result ;
	CXMLNode *	child ;

	result = [obj perform: message with: self with: p1] ;
	if(result)
		return result ;
	if((child = [self child]) != nil)
		result = [child p_foreach: obj message: message with: p1] ;
	return result ;
}

- p_foreach: (id) obj message:(SEL) message with: p1
{
	id		result ;
	CXMLNode *	child ;
	CXMLNode *	next ;

	result = [obj perform: message with: self with: p1] ;
	if(result)
		return result ;
	if((child = [self child]) != nil){
		result = [child p_foreach: obj message: message with: p1] ;
		if(result)
			return result ;
	}
	if((next = [self next]) != nil)
		result = [next p_foreach: obj message: message with: p1] ;
	return result ;
}

- foreachCondition: (condition_func_t) func obj: (id) obj message:(SEL) message 
    with: p1
{
	id		result = nil ;
	CXMLNode *	child ;

	if((*func)(node_selection)){
		result = [obj perform: message with: self with: p1] ;
		if(result)
			return result ;
	}
	if((child = [self child]) != nil){
		result = [child p_foreachCondition: func obj: obj message: 
		  message with: p1] ;
	}
	return result ;
}

- p_foreachCondition: (condition_func_t) func obj: (id) obj message:(SEL) 
    message with: p1
{
	id		result = nil ;
	CXMLNode *	child ;
	CXMLNode *	next ;

	if((*func)(node_selection)){
		result = [obj perform: message with: self with: p1] ;
		if(result)
			return result ;
	}
	if((child = [self child]) != nil){
		result = [child p_foreachCondition: func obj: obj message: 
		  message with: p1] ;
		if(result)
			return result ;
	}
	if((next = [self next]) != nil){
		result = [next p_foreachCondition: func obj: obj message: 
		  message with: p1] ;
	}
	return result ;
}

- foreachObject: (SEL) message with: p1 with: p2
{
	id 		result ;
	CXMLNode *	child ;

	result = [self perform: message with: p1 with: p2] ;
	if(result != nil)
		return result ;
	if((child = [self child]) != nil)
		result = [child p_foreachObject: message with: p1 with: p2] ;
	return result ;
}

- p_foreachObject: (SEL) message with: p1 with: p2
{	
	id 		result ;
	CXMLNode *	child ;
	CXMLNode *	next ;

	result = [self perform: message with: p1 with: p2] ;
	if(result != nil)
		return result ;
	if((child = [self child]) != nil){
		result = [child p_foreachObject: message with: p1 with: p2] ;
		if(result != nil)
			return result ;
	}
	if((next = [self next]) != nil){
		result = [next p_foreachObject: message with: p1 with: p2] ;
	}
	return result ;
}

- foreachConditionObject: (condition_func_t) func message:(SEL) message 
    with: p1 with: p2
{
	id		result = nil ;
	CXMLNode *	child ;

	if((*func)(node_selection)){
		result = [self perform: message with: p1 with: p2] ;
		if(result)
			return result ;
	}
	if((child = [self child]) != nil){
		result = [child p_foreachConditionObject: func message:
		  message with: p1 with: p2] ;
		if(result)
			return result ;
	}
	return result ;
}

- p_foreachConditionObject: (condition_func_t) func message:(SEL) message 
    with: p1 with: p2
{	
	id 		result = nil ;
	CXMLNode *	child ;
	CXMLNode *	next ;

	if((*func)(node_selection)){
		result = [self perform: message with: p1 with: p2] ;
		if(result != nil)
			return result ;
	}
	if((child = [self child]) != nil){
		result = [child p_foreachConditionObject: func message: message 
		  with: p1 with: p2] ;
		if(result != nil)
			return result ;
	}
	if((next = [self next]) != nil){
		result = [next p_foreachConditionObject: func message: message 
		  with: p1 with: p2] ;
	}
	return result ;
}

- (const utf8_char *) searchNameSpacePrefixByHref: (const utf8_char *) href
{
	xmlNsPtr	ns ;
	ns = xmlSearchNsByHref(current_node->doc, current_node, href) ;
	return ns ? ns->prefix : NULL ;
}

- (id <PXMLNode>) searchChildByTagName: (const utf8_char *) name
    level: (int) level ;
{
	CXMLNode *	child ;

	if((child = [self child]) != nil && level > 0)
		return [child p_searchChildByTagName: name level: level] ;
	else
		return nil ;
}

- (id <PXMLNode>) p_searchChildByTagName: (const utf8_char *) name
    level: (int) level
{
	CXMLNode *	child ;
	CXMLNode *	result ;

	/* check the depth */
	if(level == 0)
		return nil ;

	/* compare thid name */
	if(current_node->type == XML_ELEMENT_NODE &&
	    xmlStrcmp(current_node->name, name) == 0){
	    	return self ;
	}
	/* search child */
	if((child = [self child]) != nil){
		result = [child p_searchChildByTagName: name level: level-1] ;
		if(result != nil)
			return result ;
	}
	/* search next */
	return [(CXMLNode *) [self next] p_searchChildByTagName: name 
	  level: level] ;
}

/* do not use thid method except cxmlnode.m */
- (xmlNodePtr) getNodePtr
{
	return current_node ;
}

/* do not use thid method except fxml.m */
- resetNodePtr: (void *) p
{
	current_node = (xmlNodePtr) p ;
	return nil ;
}

#define	STREAM	((id <PIndentStream>) stream)
- print: (id) stream
{
	id <PConstStr>		attrvalue ;
	id <PConstStr>		content ;
	const utf8_char *	attrname ;
	const utf8_char *	str ;
	utf8_char		c ;

	[STREAM putPtr: "-----\n"] ;
	[STREAM putFormat: "type: %s\n", 
	  [CXMLFactory nodeType2Str: [self type]]] ;
	[STREAM putFormat: "node name: %s\n", [self tagName]] ;
	[STREAM putFormat: "line no: %u\n", [self lineno]] ;
	[STREAM putFormat: "choice: %d\n", (int) node_selection] ;

	if(current_node->type == XML_TEXT_NODE && 
	  (content = [self content]) != nil){
		[STREAM putPtr: "content: \""] ;
		str = [content ptr] ;
		for( ; (c = *str) != '\0' ; str++){
			if(c == '\n')
				[STREAM putPtr: "\\n"] ;
			else
				[STREAM putChar: c] ;
		}
		[STREAM putPtr: "\"\n"] ;
		[content release] ;
	} else
		[STREAM putPtr: "content: nil\n"] ;

	if(current_node->type == XML_ELEMENT_NODE && [self setFirstAttr]){
		[STREAM putPtr: "attributes:\n"] ;
		for( ; (attrname = [self attrName]) != NULL ; 
		  [self moveNextAttr]){
			attrvalue = [self getAttrValue] ;
			  [STREAM putFormat: "  attr name: %s, value: %s\n", 
			    attrname, [attrvalue ptr]] ;
			[attrvalue release] ;
		}
	}
	return nil ;
}
#undef	STREAM

- printTagName: (id <PStream>) stream withDepth: (id) withdepth
{
	if(current_node->type != XML_ELEMENT_NODE)
		return nil ;
	if(withdepth)
		[stream putFormat: "%u ", [CXMLFactory nodeDepth: self]] ;
	[stream putPtr: current_node->name] ;
	[stream putChar: '\n'] ;
	return nil ;
}

- printMatchedAttr: (id <PStream>) stream param: (param_pb2_t *) paramset
{
	id <PRegExp>		regexp = (id <PRegExp>) paramset->ptr_value ;
	id <PError>		err ;
	boolean			doname = paramset->bool_value1 ;
	boolean			dovalue = paramset->bool_value2 ;
	const utf8_char *	attrname ;
	id <PConstStr>		content ;
	off_t			start, stop ;

	if(current_node->type != XML_ELEMENT_NODE)
		return nil ;

	[self setFirstAttr] ;
	for( ; (attrname = [self attrName]) != NULL ; [self moveNextAttr]){
		/* check print attr name */
		if(regexp){
			err= [regexp match: attrname start: &start stop: &stop];
			if(err != nil)
				continue ;
		}

		if(doname){
			[stream putFormat: "%s",attrname] ;
			if(dovalue){
				[stream putPtr: " "] ;
			}
		}
		if(dovalue){
			content = [self getAttrValue] ;
			  [stream putFormat: "%s", content ? (char *) 
			    [content ptr] : ""] ;
			[content release] ;
		}
		[stream putChar: '\n'] ;
	}
	return nil ;
}

@end

