/*
SRXMLElement.m

Author: Makoto Kinoshita

Copyright 2004 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import <libxml/xpath.h>
#import <libxml/xpathInternals.h>
#import "SRXMLNode.h"
#import "SRXMLNodePrivate.h"
#import "SRXMLElement.h"
#import "SRXMLDocument.h"
#import "SRXMLDocumentPrivate.h"

@interface NSString (CharacterReference)
- (NSString*)stringByReplacingCharacterReferences;
@end

#pragma mark -

@interface SRXMLNodePrivate : NSObject
{
@public
	SRXMLDocument*	_document;
	xmlNodePtr		_node;
	NSMutableArray*	_children;
	
	SRXMLNodeKind	_kind;
	unsigned int	_options;
	
	NSString*		_name;
	NSString*		_URI;
	NSString*		_string;
}
@end

@implementation SRXMLNodePrivate

- (id)init
{
	self = [super init];
	if (!self) {
		return nil;
	}
	
	// Initialize instance variables
	_document = nil;
	_node = NULL;
	_children = nil;
	_kind = 0;
	_options = 0;
	_name = nil;
	_URI = nil;
	_string = nil;
	
	return self;
}

- (void)dealloc
{
#if 0
	if (_node) {
		xmlFreeNode(_node);
	}
#endif
	[_children release];
	[_name release];
	[_URI release];
	[_string release];
	
	[super dealloc];
}

@end

#pragma mark -

@implementation SRXMLNode

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

+ (id)document
{
	SRXMLDocument*	document;
	document = [[SRXMLDocument alloc] init];
	[document autorelease];
	
	return document;
}

- (id)initWithKind:(SRXMLNodeKind)kind
{
	return [self initWithKind:kind options:0];
}

- (id)initWithKind:(SRXMLNodeKind)kind options:(unsigned int)options
{
	self = [super init];
	if (!self) {
		return nil;
	}
	
	// Initialize instance variables
	_nodePrivate = [[SRXMLNodePrivate alloc] init];
	_nodePrivate->_kind = kind;
	_nodePrivate->_options = options;
	
	return self;
}

- (void)dealloc
{
	[_nodePrivate release];
	_nodePrivate = nil;
    
	[super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Node attributes --
//--------------------------------------------------------------//

- (SRXMLNodeKind)kind
{
	return _nodePrivate->_kind;
}

- (NSString*)name
{
	return [_nodePrivate->_name copyWithZone:[self zone]];
}

- (void)setName:(NSString*)name
{
	[_nodePrivate->_name release];
	_nodePrivate->_name = [[name copyWithZone:[self zone]] retain];
}

- (NSString*)stringValue
{
	return [_nodePrivate->_string copyWithZone:[self zone]];
}

- (void)setStringValue:(NSString*)string
{
	[_nodePrivate->_string release];
	_nodePrivate->_string = [[string copyWithZone:[self zone]] retain];
}

- (NSString*)URI
{
	return [_nodePrivate->_URI copyWithZone:[self zone]];
}

- (void)_setURI:(NSString*)URI
{
	[_nodePrivate->_URI release];
	_nodePrivate->_URI = [[URI copyWithZone:[self zone]] retain];
}

//--------------------------------------------------------------//
#pragma mark -- Tree navigation --
//--------------------------------------------------------------//

- (void)_createChildren
{
	if (!_nodePrivate->_children) {
		_nodePrivate->_children = [[NSMutableArray array] retain];
	}
	[_nodePrivate->_children removeAllObjects];
	
	xmlNodePtr	child;
	child = _nodePrivate->_node->children;
	while (child) {
		// Create child node
		SRXMLNode*	node;
		node = [SRXMLNode _nodeWithNode:child];
		
		[_nodePrivate->_children addObject:node];
		
		child = child->next;
	}
}

- (SRXMLNode*)childAtIndex:(unsigned int)index
{
	if (!_nodePrivate->_children) {
		[self _createChildren];
	}
	
	if (index > [_nodePrivate->_children count]) {
		return nil;
	}
	
	return [_nodePrivate->_children objectAtIndex:index];
}

- (unsigned int)childCount
{
	if (!_nodePrivate->_children) {
		[self _createChildren];
	}
	
	return [_nodePrivate->_children count];
}

- (NSArray*)children
{
	if (!_nodePrivate->_children) {
		[self _createChildren];
	}
	
	return _nodePrivate->_children;
}

- (SRXMLDocument*)rootDocument
{
	return _nodePrivate->_document;
}

//--------------------------------------------------------------//
#pragma mark -- Node output --
//--------------------------------------------------------------//

- (NSString*)XMLString
{
	return [self XMLStringWithOptions:0];
}

- (NSString*)XMLStringWithOptions:(unsigned int)options
{
	// Dump node
	xmlBufferPtr	buffer;
	int				result;
	buffer = xmlBufferCreate();
	result = xmlNodeDump(buffer, [[self rootDocument] _doc], _nodePrivate->_node, 0, 0);
	
	// Create string
	NSData*		data;
	NSString*	string;
	data = [NSData dataWithBytes:(const char*)xmlBufferContent(buffer) length:xmlBufferLength(buffer)];
	string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
	[string autorelease];
	
	xmlBufferFree(buffer);
	
	return string;
}

//--------------------------------------------------------------//
#pragma mark -- Query executions --
//--------------------------------------------------------------//

- (NSArray*)nodesForXPath:(NSString*)xpath error:(NSError**)error
{
#if 1
    // For root document
    if ([xpath isEqualToString:@"/"]) {
        return [NSArray arrayWithObject:[self rootDocument]];
    }
    
    // Decide first node
    SRXMLNode*  node = self;
    if ([xpath hasPrefix:@"/"]) {
        node = [self rootDocument];
        xpath = [xpath substringFromIndex:1];
    }
    
    // Separete componenets
    NSArray*    components;
    components = [xpath componentsSeparatedByString:@"/"];
    
    NSMutableArray* nodes;
    nodes = [NSMutableArray array];
    
    int i;
    for (i = 0; i < [components count]; i++) {
        // Get componenet
        NSString*   component;
        component = [components objectAtIndex:i];
        
        // Get child info
        int     childCount, j, foundChild = 1; // Path index starts with 1
        BOOL    isFoundChild = NO;
        childCount = [node childCount];
        
        // For text()
        if ([component isEqualToString:@"text()"]) {
            // Aggregate text nodes
            for (j = 0; j < childCount; j++) {
                SRXMLNode*  child;
                child = [node childAtIndex:j];
                if ([child kind] == SRXMLTextKind) {
                    [nodes addObject:child];
                }
            }
            return nodes;
        }
        
        // For comment()
        if ([component isEqualToString:@"comment()"]) {
            // Aggregate comment nodes
            for (j = 0; j < childCount; j++) {
                SRXMLNode*  child;
                child = [node childAtIndex:j];
                if ([child kind] == SRXMLCommentKind) {
                    [nodes addObject:child];
                }
            }
            return nodes;
        }
        NSString*   qName;
        int         index = -1;
        qName = component;
        
        NSRange range0, range1;
        range0 = [qName rangeOfString:@"["];
        range1 = [qName rangeOfString:@"]"];
        if (range0.location != NSNotFound && range1.location != NSNotFound) {
            qName = [component substringToIndex:range0.location];
            
            NSString*   indexString;
            indexString = [component substringWithRange:
                    NSMakeRange(range0.location + 1, range1.location - (range0.location + 1))];
            index = [indexString intValue];
            if (index < 0) {
                index = 0;
            }
        }
        
        // Find in children
        for (j = 0; j < childCount; j++) {
            SRXMLNode*  child;
            NSString*   name;
            child = [node childAtIndex:j];
            name = [child name];
            if ([name isEqualToString:qName]) {
                isFoundChild = YES;
                
                if (i == [components count] - 1) {
                    if (index < 0) {
                        [nodes addObject:child];
                    }
                    else if (index == foundChild) {
                        [nodes addObject:child];
                        break;
                    }
                }
                else {
                    if (index < 0 || index == foundChild) {
                        node = child;
                        break;
                    }
                }
                foundChild++;
            }
        }
        
        if (!isFoundChild) {
            return nil;
        }
    }
    
    return nodes;
#else
	// Get document
	SRXMLDocument*	document;
	document = [self rootDocument];
	
	// Create context
	xmlXPathContextPtr	context;
	context = xmlXPathNewContext([document _doc]);
	if (!context) {
		return nil;
	}
	
	// Set namespaces
	context->namespaces = xmlGetNsList([document _doc], [[document rootElement] _node]);
	context->nsNr = 0;
	if (context->namespaces != NULL) {
        xmlNsPtr    ns;
        while ((ns = context->namespaces[context->nsNr]) != NULL) {
			context->nsNr++;
		}
	}
	
	// Evaluation
	xmlXPathObjectPtr	xpathObject;
	xmlNodeSetPtr		nodeSet;
	xpathObject = xmlXPathEval((const xmlChar*)[xpath cString], context);
	if (!xpathObject) {
		return nil;
	}
	nodeSet = xpathObject->nodesetval;
	if (!nodeSet) {
		return nil;
	}
	
	// Create array
	NSMutableArray*	nodes;
	nodes = [NSMutableArray array];
	
	int	i;
	for (i = 0; i < nodeSet->nodeNr; i++) {
		xmlNodePtr	node;
		node = nodeSet->nodeTab[i];
		
		SRXMLNode*	nodeObj;
		nodeObj = [SRXMLNode _nodeWithNode:node];
		
		[nodes addObject:nodeObj];
	}
	
	xmlXPathFreeObject(xpathObject);
	xmlXPathFreeContext(context);
	
	return nodes;
#endif
}

- (NSString*)XPath
{
	xmlChar*	xpath;
	xpath = xmlGetNodePath(_nodePrivate->_node);
	if (!xpath) {
		return nil;
	}
	
	NSString*	string;
	string = [NSString stringWithUTF8String:(const char*)xpath];
	free(xpath);
	
	return string;
}

//--------------------------------------------------------------//
#pragma mark -- Namespace handling --
//--------------------------------------------------------------//

- (NSString*)prefix
{
    xmlNs*  ns;
    ns = _nodePrivate->_node->ns;
    if (!ns) {
        return nil;
    }
    
    const xmlChar*  prefix;
    prefix = ns->prefix;
    if (!prefix) {
        return nil;
    }
    
    NSString*   string;
    string = [NSString stringWithUTF8String:(const char*)prefix];
    return string;
}

//--------------------------------------------------------------//
#pragma mark -- Extension --
//--------------------------------------------------------------//

- (SRXMLNode*)singleNodeForXPath:(NSString*)XPath
{
    NSArray*    nodes;
    nodes = [self nodesForXPath:XPath error:NULL];
    if (!nodes || [nodes count] != 1) {
        return nil;
    }
    return [nodes objectAtIndex:0];
}

- (NSString*)stringValueForXPath:(NSString*)XPath
{
    SRXMLNode*  node;
    node = [self singleNodeForXPath:XPath];
    if (!node) {
        return nil;
    }
    
    //return [node stringValue];
    return [[node stringValue] stringByReplacingCharacterReferences];
}

@end

#pragma mark -

@implementation SRXMLNode (private)

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

+ (id)_nodeWithNode:(xmlNodePtr)node
{
	// Decide class
	Class	nodeClass;
	nodeClass = [SRXMLNode class];
	
	if (node->type == XML_ELEMENT_NODE) {
		nodeClass = [SRXMLElement class];
	}
	else if (node->type == XML_DOCUMENT_NODE) {
		nodeClass = [SRXMLDocument class];
	}
	
	// Create node
	id	nodeObject;
	nodeObject = [[nodeClass alloc] _initWithNode:node];
	[nodeObject autorelease];
	
	return nodeObject;
}

- (id)_initWithNode:(xmlNodePtr)node
{
	SRXMLNodeKind	kind;
	kind = SRXMLInvalidKind;
	
	switch (node->type) {
	case XML_ELEMENT_NODE: { kind = SRXMLElementKind; break; }
	case XML_ATTRIBUTE_NODE: { kind = SRXMLAttributeKind; break; }
	case XML_TEXT_NODE: { kind = SRXMLTextKind; break; }
	case XML_CDATA_SECTION_NODE: { kind = SRXMLTextKind; break; }
	case XML_PI_NODE: { kind = SRXMLProcessingInstructionKind; break; }
	case XML_COMMENT_NODE: { kind = SRXMLCommentKind; break; }
	case XML_DOCUMENT_NODE: { kind = SRXMLDocumentKind; break; }
	case XML_DTD_NODE: { kind = SRXMLDTDKind; break; }
	case XML_ELEMENT_DECL: { kind = SRXMLElementDeclarationKind; break; }
	case XML_ATTRIBUTE_DECL: { kind = SRXMLAttributeDeclarationKind; break; }
	case XML_ENTITY_DECL: { kind = SRXMLEntityDeclarationKind; break; }
	case XML_NAMESPACE_DECL: { kind = SRXMLNamespaceKind; break; }
	case XML_NOTATION_NODE: { kind = SRXMLNotationDeclarationKind; break; }
	default: break;
	}
	
	self = [self initWithKind:kind];
	if (!self) {
		return nil;
	}
	
	// Initialize instance varialbes
	[self _setNode:node];
	
	if (node->name) {
        if (node->ns && node->ns->prefix) {
            NSString*   name;
            name = [NSString stringWithFormat:@"%@:%@", 
                    [NSString stringWithUTF8String:(const char*)node->ns->prefix], 
                    [NSString stringWithUTF8String:(const char*)node->name]];
            [self setName:name];
        }
        else {
            [self setName:[NSString stringWithUTF8String:(const char*)node->name]];
        }
	}
	
	xmlChar*	string;
	string = xmlNodeGetContent(node);
	if (string) {
		[self setStringValue:[NSString stringWithUTF8String:(const char*)string]];
		free(string);
	}
	
	return self;
}

//--------------------------------------------------------------//
#pragma mark -- Node attributes --
//--------------------------------------------------------------//

- (xmlNodePtr)_node
{
	return _nodePrivate->_node;
}

- (void)_setNode:(xmlNodePtr)node
{
	_nodePrivate->_node = node;
}

- (void)_setRootDocument:(SRXMLDocument*)document
{
	_nodePrivate->_document = document;
}

@end
