//
//  SVGKParser.m
//  SVGKit
//
//  Copyright Matt Rajca 2010-2011. All rights reserved.
//

#import "SVGKParser.h"
#import <libxml/parser.h>

#import "SVGKParserSVG.h"

@class SVGKParserGradient;
#import "SVGKParserGradient.h"
@class SVGKParserPatternsAndGradients;
#import "SVGKParserPatternsAndGradients.h"
@class SVGKParserStyles;
#import "SVGKParserStyles.h"
@class SVGKParserDefsAndUse;
#import "SVGKParserDefsAndUse.h"
@class SVGKParserDOM;
#import "SVGKParserDOM.h"

#import "SVGDocument_Mutable.h" // so we can modify the SVGDocuments we're parsing

#import "Node.h"

#import "SVGKSourceString.h"
#import "SVGKSourceURL.h"
#import "CSSStyleSheet.h"
#import "StyleSheetList+Mutable.h"
#import "NSData+NSInputStream.h"

@interface SVGKParser()
@property(nonatomic,retain, readwrite) SVGKSource* source;
@property(nonatomic,retain, readwrite) NSMutableArray* externalStylesheets;
@property(nonatomic,retain, readwrite) SVGKParseResult* currentParseRun;
@property(nonatomic,retain) NSString* defaultXMLNamespaceForThisParseRun;
@property(nonatomic) BOOL hasCancelBeenRequested;
@end

@implementation SVGKParser

@synthesize source;
@synthesize externalStylesheets;
@synthesize currentParseRun;
@synthesize defaultXMLNamespaceForThisParseRun;

@synthesize parserExtensions;
@synthesize parserKnownNamespaces;

static xmlSAXHandler SAXHandler;

static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void	endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void	charactersFoundSAX(void * ctx, const xmlChar * ch, int len);
static void errorEncounteredSAX(void * ctx, const char * msg, ...);

static NSString *NSStringFromLibxmlString (const xmlChar *string);
static NSMutableDictionary *NSDictionaryFromLibxmlNamespaces (const xmlChar **namespaces, int namespaces_ct);
static NSMutableDictionary *NSDictionaryFromLibxmlAttributes (const xmlChar **attrs, int attr_ct);

#define kThreadLocalCurrentlyActiveParser ( @"kThreadLocalCurrentlyActiveParser" )

/** This is a workaround to the major, catastophic bugs in libxml that you cannot
 attach a "context" object to libxml parser - and without that, you can't actually
 parse, because you have no reference to the context of your original "parse" call. ARGH!
 */
SVGKParser* getCurrentlyParsingParser()
{
	/** Currently implemented NON THREAD SAFE using a static varailbe that only
	 allows one parse in memory at a time:
	 */
	return [[NSThread currentThread].threadDictionary objectForKey:kThreadLocalCurrentlyActiveParser];
}

+(void)cancelParser:(SVGKParser *)parserToCancel
{
	parserToCancel.hasCancelBeenRequested = TRUE;
}

+(SVGKParser *) newParserWithDefaultSVGKParserExtensions:(SVGKSource *)source
{
	SVGKParser *parser = [[SVGKParser alloc] initWithSource:source];
	[parser addDefaultSVGParserExtensions];
	
	return parser;
}

+ (SVGKParseResult*) parseSourceUsingDefaultSVGKParser:(SVGKSource*) source;
{
	SVGKParser* parser = [self newParserWithDefaultSVGKParserExtensions:source];
	SVGKParseResult* result = [parser parseSynchronously];
	[parser release];
	
	return result;
}


#define READ_CHUNK_SZ 1024*10

- (id)initWithSource:(SVGKSource *) s {
	self = [super init];
	if (self) {
		self.parserExtensions = [NSMutableArray array];
		
		self.source = s;
        self.externalStylesheets = nil;
		
		_storedChars = [NSMutableString new];
		_stackOfParserExtensions = [NSMutableArray new];
	}
	return self;
}

- (void)dealloc {
	self.currentParseRun = nil;
	self.source = nil;
    self.externalStylesheets = nil;
	[_storedChars release];
	[_stackOfParserExtensions release];
   // [_parentOfCurrentNode release];
	self.parserExtensions = nil;
    self.parserKnownNamespaces = nil;
    self.defaultXMLNamespaceForThisParseRun = nil;
	[super dealloc];
}

-(void) addDefaultSVGParserExtensions
{
	SVGKParserSVG *subParserSVG = [[[SVGKParserSVG alloc] init] autorelease];
	SVGKParserGradient* subParserGradients = [[[SVGKParserGradient alloc] init] autorelease];
	SVGKParserPatternsAndGradients *subParserPatternsAndGradients = [[[SVGKParserPatternsAndGradients alloc] init] autorelease];
	SVGKParserStyles* subParserStyles = [[[SVGKParserStyles alloc] init] autorelease];
	SVGKParserDefsAndUse *subParserDefsAndUse = [[[SVGKParserDefsAndUse alloc] init] autorelease];
	SVGKParserDOM *subParserXMLDOM = [[[SVGKParserDOM alloc] init] autorelease];
	
	[self addParserExtension:subParserSVG];
	[self addParserExtension:subParserGradients];
	[self addParserExtension:subParserPatternsAndGradients]; // FIXME: this is a "not implemente yet" parser; now that we have gradients, it should be deleted / renamed!
	[self addParserExtension:subParserStyles];
	[self addParserExtension:subParserDefsAndUse];
	[self addParserExtension:subParserXMLDOM];
}

- (void) addParserExtension:(NSObject<SVGKParserExtension>*) extension
{
	// TODO: Should check for conflicts between this parser-extension and our existing parser-extensions, and issue warnings for any we find
	
	if( self.parserExtensions == nil )
	{
		self.parserExtensions = [NSMutableArray array];
	}
	
	if( [self.parserExtensions containsObject:extension])
	{
		DDLogVerbose(@"[%@] WARNING: attempted to add a ParserExtension that was already added = %@", [self class], extension);
		return;
	}
	
	[self.parserExtensions addObject:extension];
	
	if( self.parserKnownNamespaces == nil )
	{
		self.parserKnownNamespaces = [NSMutableDictionary dictionary];
	}
	for( NSString* parserNamespace in extension.supportedNamespaces )
	{
		NSMutableArray* extensionsForNamespace = [self.parserKnownNamespaces objectForKey:parserNamespace];
		if( extensionsForNamespace == nil )
		{
			extensionsForNamespace = [NSMutableArray array];
			[self.parserKnownNamespaces setObject:extensionsForNamespace forKey:parserNamespace];
		}
		
		[extensionsForNamespace addObject:extension];
	}
}

//static FILE *desc;
//static size_t
//readPacket(char *mem, int size) {
//    size_t res;
//	
//    res = fread(mem, 1, size, desc);
//    return(res);
//}

- (SVGKParseResult*) parseSynchronously
{
	if( self.currentParseRun != nil )
	{
		DDLogError(@"FATAL: attempting to run the parser twice in one thread; limxml is single-threaded only, so we are too (until someone wraps libxml to be multi-threaded)");
	}
	
	self.currentParseRun = [[SVGKParseResult new] autorelease];
	_parentOfCurrentNode = nil;
	[_stackOfParserExtensions removeAllObjects];
	[[NSThread currentThread].threadDictionary setObject:self forKey:kThreadLocalCurrentlyActiveParser];
	
	/*
	// 1. while (source has chunks of BYTES)
	// 2.   read a chunk from source, send to libxml
	// 3.   if libxml failed chunk, break
	// 4. return result
	*/
	
	NSInputStream* stream = source.stream;
	if( stream == nil )
	{
		[currentParseRun addSourceError:[NSError errorWithDomain:@"SVGKit" code:2354 userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Source failed to create a valid NSInputStream; check your log files for why the SVGKSource failed (source = %@)",source]}]];
																								
	}
	else
	{
		[stream open];
	NSStreamStatus status = [stream streamStatus];
	if (status != NSStreamStatusOpen)
	{
		if (status == NSStreamStatusError)
		{
			[currentParseRun addSourceError:[stream streamError]];
		}
		else
		{
			[currentParseRun addSourceError:[NSError errorWithDomain:@"SVGKit" code:2573 userInfo:@{NSLocalizedDescriptionKey: @"The stream wouldn't open; this can happen when Apple libraries incorrectly open slowly over the internet. Any other case is probably a threading bug inside SVGKit"}]];
		}
		[stream close];
		return  currentParseRun;
	}
	char buff[READ_CHUNK_SZ];
	
	xmlParserCtxtPtr ctx;
	ctx = xmlCreatePushParserCtxt(&SAXHandler, NULL, NULL, 0, NULL); // NEVER pass anything except NULL in second arg - libxml has a massive bug internally
	
	/* 
	 DDLogVerbose(@"[%@] WARNING: Substituting entities directly into document, c.f. http://www.xmlsoft.org/entities.html for why!", [self class]);
	 xmlSubstituteEntitiesDefault(1);
	xmlCtxtUseOptions( ctx,
					  XML_PARSE_DTDATTR  // default DTD attributes
					  | XML_PARSE_NOENT    // substitute entities
					  | XML_PARSE_DTDVALID // validate with the DTD
					  );
	*/
	
	if( ctx ) // if libxml init succeeds...
	{
		// 1. while (source has chunks of BYTES)
		// 2. Check asynch cancellation flag
		// 3.   read a chunk from source, send to libxml
		uint64_t totalBytesRead = 0;
		NSInteger bytesRead = [stream read:(uint8_t*)&buff maxLength:READ_CHUNK_SZ];
		while( bytesRead > 0 )
		{
			totalBytesRead += bytesRead;
			
			if( self.hasCancelBeenRequested )
			{
				DDLogInfo( @"SVGKParser: 'cancel parse' discovered; bailing on this XML parse" );
				break;
			}
			else
			{
				if( source.approximateLengthInBytesOr0 > 0 )
				{
					currentParseRun.parseProgressFractionApproximate = totalBytesRead / (double) source.approximateLengthInBytesOr0;
				}
				else
					currentParseRun.parseProgressFractionApproximate = 0;
			}
			
			NSInteger libXmlParserParseError;
			@try
			{
			libXmlParserParseError = xmlParseChunk(ctx, buff, (int)bytesRead, 0);
			}
			@catch( NSException* e )
			{
				DDLogError( @"Exception while trying to parse SVG file, will store in parse results. Exception = %@", e);
				[currentParseRun addParseErrorFatal:[NSError errorWithDomain:@"SVGK Parsing" code:@"32523432" userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Exception = %@", e]}]];
			}
			
			if( [currentParseRun.errorsFatal count] > 0 )
			{
				// 3.   if libxml failed chunk, break
				if( libXmlParserParseError > 0 )
				{
				DDLogVerbose(@"[%@] libXml reported internal parser error with magic libxml code = %li (look this up on http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors)", [self class], (long)libXmlParserParseError );
				currentParseRun.libXMLFailed = YES;
				}
				else
				{
					DDLogWarn(@"[%@] SVG parser generated one or more FATAL errors (not the XML parser), errors follow:", [self class] );
					for( NSError* error in currentParseRun.errorsFatal )
					{
						DDLogWarn(@"[%@] ... FATAL ERRRO in SVG parse: %@", [self class], error );
					}
				}
				
				break;
			}
			
			bytesRead = [stream read:(uint8_t*)&buff maxLength:READ_CHUNK_SZ];
		}
	}
	
	[stream close]; // close the handle NO MATTER WHAT
    
	if (!currentParseRun.libXMLFailed
	&& currentParseRun.errorsFatal.count < 1 )
		xmlParseChunk(ctx, NULL, 0, 1); // EOF
	
	xmlFreeParserCtxt(ctx);
	}
	
	[[NSThread currentThread].threadDictionary removeObjectForKey:kThreadLocalCurrentlyActiveParser];
	
	// 4. return result
	return currentParseRun;
}

/** ADAM: use this for a higher-performance, *non-blocking* parse
 (when someone upgrades this class and the interface to support non-blocking parse)
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection 
	didReceiveData:(NSData *)data 
{
	// Process the downloaded chunk of data.
	xmlParseChunk(_xmlParserContext, (const char *)[data bytes], [data length], 0);//....Getting Exception at this line.
}
 */


- (SVGKSource *)loadCSSFrom:(NSString *)href
{
    SVGKSource *cssSource = nil;
    if( [href hasPrefix:@"http"] )
    {
        NSURL *url = [NSURL URLWithString:href];
        cssSource = [SVGKSourceURL sourceFromURL:url];
    }
    else
    {
        cssSource = [self.source sourceFromRelativePath:href];
    }
    
    if( cssSource == nil )
    {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *path = [documentsDirectory stringByAppendingPathComponent:href];
        NSString *cssText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
        
        if( cssText == nil )
        {
            DDLogWarn(@"[%@] Unable to find external CSS file '%@'", [self class], href );
        }
        else
        {
            cssSource = [SVGKSourceString sourceFromContentsOfString:cssText];
        }
    }
    
    return cssSource;
}

- (NSString *)stringFromSource:(SVGKSource *) src
{
    [src.stream open]; // if we do this, we CANNOT parse from this source again in future
    NSData *data = [NSData dataWithContentsOfStream:src.stream initialCapacity:4096 error:nil];
    return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
}

- (void)handleProcessingInstruction:(NSString *)target withData:(NSString *) data
{
    if( [@"xml-stylesheet" isEqualToString:target] && ( [data rangeOfString:@"type=\"text/css\""].location != NSNotFound || [data rangeOfString:@"type="].location == NSNotFound ) )
    {
        NSRange startHref = [data rangeOfString:@"href=\""];
        if( startHref.location != NSNotFound )
        {
            NSUInteger startIndex = startHref.location + startHref.length;
            NSRange endHref = [data rangeOfString:@"\"" options:0 range:NSMakeRange(startIndex, data.length - startIndex)];
            if( startHref.location != NSNotFound )
            {
                NSString *href = [data substringWithRange:NSMakeRange(startIndex, endHref.location - startIndex)];
                SVGKSource* cssSource = [self loadCSSFrom:href];
                
                if( cssSource != nil )
                {
                    NSString *cssText = [self stringFromSource:cssSource];
                    CSSStyleSheet* parsedStylesheet = [[[CSSStyleSheet alloc] initWithString:cssText] autorelease];
                    
                    if( currentParseRun.parsedDocument.rootElement == nil )
                    {
                        if( self.externalStylesheets == nil )
                            self.externalStylesheets = [[[NSMutableArray alloc] init] autorelease];
                        [self.externalStylesheets addObject:parsedStylesheet];
                    }
                    else
                    {
                        [currentParseRun.parsedDocument.rootElement.styleSheets.internalArray addObject:parsedStylesheet];
                    }
                }
            }
        }
    }
}

- (void)addExternalStylesheetsToRootElement {
    
    if( self.externalStylesheets != nil )
    {
        [currentParseRun.parsedDocument.rootElement.styleSheets.internalArray addObjectsFromArray:self.externalStylesheets];
        self.externalStylesheets = nil;
    }
}

static void processingInstructionSAX (void * ctx,
                                         const xmlChar * target,
                                         const xmlChar * data)
{
	SVGKParser* self = getCurrentlyParsingParser();
	
    NSString *stringTarget = NSStringFromLibxmlString(target);
	NSString *stringData = NSStringFromLibxmlString(data);
    
    [self handleProcessingInstruction:stringTarget withData:stringData];
}

- (void)handleStartElement:(NSString *)name namePrefix:(NSString*)prefix namespaceURI:(NSString*) XMLNSURI attributeObjects:(NSMutableDictionary *) attributeObjects
{
	BOOL parsingRootTag = FALSE;
	
	if( _parentOfCurrentNode == nil )
		parsingRootTag = TRUE;
	
	if( ! parsingRootTag && _storedChars.length > 0 )
	{
		/** Send any partially-parsed text data into the old node that is now the parent node,
		 then change the "storing chars" flag to fit the new node */
		
		Text *tNode = [[[Text alloc] initWithValue:_storedChars] autorelease];
		
		[_parentOfCurrentNode appendChild:tNode];
		
		[_storedChars setString:@""];
	}
	
	/**
	 Search for a Parser Extension to handle this XML tag ...
	 
	 (most tags are handled by the default SVGParserSVG - but if you have other XML embedded in your SVG, you'll
	 have custom parser extentions too)
	 */
	NSObject<SVGKParserExtension>* defaultParserForThisNamespace = nil;
	NSObject<SVGKParserExtension>* defaultParserForEverything = nil;
	for( NSObject<SVGKParserExtension>* subParser in self.parserExtensions )
	{
		// TODO: rather than checking for the default parser on every node, we should stick them in a Dictionar at the start and re-use them when needed
		/**
		 First: check if this parser is a "default" / fallback parser. If so, skip it, and only use it
		 AT THE VERY END after checking all other parsers
		 */
		BOOL shouldBreakBecauseParserIsADefault = FALSE;
		
		if( [[subParser supportedNamespaces] count] == 0 )
		{
			defaultParserForEverything = subParser;
			shouldBreakBecauseParserIsADefault = TRUE;
		}
		
		if( [[subParser supportedNamespaces] containsObject:XMLNSURI]
		   && [[subParser supportedTags] count] == 0 )
		{
			defaultParserForThisNamespace = subParser;
			shouldBreakBecauseParserIsADefault = TRUE;
		}
		
		if( shouldBreakBecauseParserIsADefault )
			continue;
			
		/**
		 Now we know it's a specific parser, check if it handles this particular node
		 */
		if( [[subParser supportedNamespaces] containsObject:XMLNSURI]
		   && [[subParser supportedTags] containsObject:name] )
		{
			[_stackOfParserExtensions addObject:subParser];
			
			/** Parser Extenstion creates a node for us */
			Node* subParserResult = [subParser handleStartElement:name document:source namePrefix:prefix namespaceURI:XMLNSURI attributes:attributeObjects parseResult:self.currentParseRun parentNode:_parentOfCurrentNode];
			
#if DEBUG_XML_PARSER
			DDLogVerbose(@"[%@] tag: <%@:%@> id=%@ -- handled by subParser: %@", [self class], prefix, name, ([((Attr*)[attributeObjects objectForKey:@"id"]) value] != nil?[((Attr*)[attributeObjects objectForKey:@"id"]) value]:@"(none)"), subParser );
#endif
			
			/** Add the new (partially parsed) node to the parent node in tree
			 
			 (need this for some of the parsing, later on, where we need to be able to read up
			 the tree to make decisions about the data - this is REQUIRED by the SVG Spec)
			 */
			[_parentOfCurrentNode appendChild:subParserResult]; // this is a DOM method: should NOT have side-effects
			_parentOfCurrentNode = subParserResult;
			
			if( parsingRootTag )
			{
				currentParseRun.parsedDocument.rootElement = (SVGSVGElement*) subParserResult;
                [self addExternalStylesheetsToRootElement];
			}
			
			return;
		}
	}
	
	/**
	 IF we had a specific matching parser, we would have returned already.
	 
	 Since we haven't, it means we have to try the default parsers instead
	 */
	NSObject<SVGKParserExtension>* eventualParser = defaultParserForThisNamespace != nil ? defaultParserForThisNamespace : defaultParserForEverything;
	NSAssert( eventualParser != nil, @"Found a tag (prefix:%@ name:%@) that was rejected by all the parsers available. Perhaps you forgot to include a default parser (usually: SVGKParserDOM, which will handle any / all XML tags)", prefix, name );
	
	DDLogVerbose(@"[%@] WARN: found a tag with no namespace parser: (</%@>), using default parser(%@)", [self class], name, eventualParser );
	
	
	[_stackOfParserExtensions addObject:eventualParser];
	
	/** Parser Extenstion creates a node for us */
	Node* subParserResult = [eventualParser handleStartElement:name document:source namePrefix:prefix namespaceURI:XMLNSURI attributes:attributeObjects parseResult:self.currentParseRun parentNode:_parentOfCurrentNode];
	
#if DEBUG_XML_PARSER
	DDLogVerbose(@"[%@] tag: <%@:%@> id=%@ -- handled by subParser: %@", [self class], prefix, name, ([((Attr*)[attributeObjects objectForKey:@"id"]) value] != nil?[((Attr*)[attributeObjects objectForKey:@"id"]) value]:@"(none)"), eventualParser );
#endif
	
	/** Add the new (partially parsed) node to the parent node in tree
	 
	 (need this for some of the parsing, later on, where we need to be able to read up
	 the tree to make decisions about the data - this is REQUIRED by the SVG Spec)
	 */
	[_parentOfCurrentNode appendChild:subParserResult]; // this is a DOM method: should NOT have side-effects
	_parentOfCurrentNode = subParserResult;
	
		
	if( parsingRootTag )
	{
		currentParseRun.parsedDocument.rootElement = (SVGSVGElement*) subParserResult;
        [self addExternalStylesheetsToRootElement];
	}
	
	return;
}


static void startElementSAX (void *ctx, const xmlChar *localname, const xmlChar *prefix,
							 const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces,
							 int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
	
	SVGKParser *self = getCurrentlyParsingParser();
	
	NSString *stringLocalName = NSStringFromLibxmlString(localname);
	NSString *stringPrefix = NSStringFromLibxmlString(prefix);
	NSMutableDictionary *namespacesByPrefix = NSDictionaryFromLibxmlNamespaces(namespaces, nb_namespaces); // TODO: need to do something with this; this is the ONLY point at which we discover the "xmlns:" definitions in the SVG doc! See below for a temp fix
	NSMutableDictionary *attributeObjects = NSDictionaryFromLibxmlAttributes(attributes, nb_attributes);
	NSString *stringURI = NSStringFromLibxmlString(URI);
	
	/** Set a default Namespace for rest of this document if one is included in the attributes */
	if( self.defaultXMLNamespaceForThisParseRun == nil )
	{
		NSString* newDefaultNamespace = [namespacesByPrefix valueForKey:@""];
		if( newDefaultNamespace != nil )
		{
			self.defaultXMLNamespaceForThisParseRun = newDefaultNamespace;
		}
	}
	
	if( stringURI == nil
	&& self.defaultXMLNamespaceForThisParseRun != nil )
	{
		/** Apply the default XML NS to this tag as if it had been typed in.
		 
		 e.g. if somewhere in this doc the author put:
		 
		 <svg xmlns="blah">
		 
		 ...then any time we find a tag that HAS NO EXPLICIT NAMESPACE, we act as if it had that one.
		 */
		
		stringURI = self.defaultXMLNamespaceForThisParseRun;
	}
	
	for( Attr* newAttribute in attributeObjects.allValues )
	{
		if( newAttribute.namespaceURI == nil )
			newAttribute.namespaceURI = self.defaultXMLNamespaceForThisParseRun;
	}
	
	/**
	 This appears to be a major bug in libxml: "xmlns:blah="blah"" is treated as a namespace declaration - but libxml
	 FAILS to report it as an attribute (according to the XML spec, it appears to be "both" of those things?)
	 
	 ...but I could be wrong. The XML definition of Namespaces is badly written and missing several key bits of info
	 (I have inferred the "both" status from the definition of XML's Node class, which raises an error on setting
	 Node.prefix "if the node is an attribute, and it's in the xmlns namespace ... and ... and" -- which implies to me
	 that attributes can be xmlns="blah" definitions)
	 
	 ... UPDATE: I have found confirming evidence in the "http://www.w3.org/2000/xmlns/" namespace itself. Visit that URL! It has docs...
	 
	 
	 NB: this bug / issue was irrelevant until we started trying to export SVG documents from memory back to XML strings,
	 at which point: we need this info! Or else we end up substantially changing the incoming SVG :(.
	 
	 So:
	 
	 Add any namespace declarations to the attributes dictionary:
	 */
	for( NSString* prefix in namespacesByPrefix )
	{
		NSString* namespace = [namespacesByPrefix objectForKey:prefix];
		
		/** NB this happens *AFTER* setting default namespaces for all attributes - the xmlns: attributes are required by the XML
		 spec to all live in a special magical namespace AND to all use the same prefix of "xmlns" - no other is allowed!
		 */
		Attr* newAttributeFromNamespaceDeclaration = [[[Attr alloc] initWithNamespace:@"http://www.w3.org/2000/xmlns/" qualifiedName:[NSString stringWithFormat:@"xmlns:%@", prefix] value:namespace] autorelease];
		
		[attributeObjects setObject:newAttributeFromNamespaceDeclaration forKey:newAttributeFromNamespaceDeclaration.nodeName];
	}
	
	/**
	 TODO: temporary workaround to PRETEND that all namespaces are always defined;
	 this is INCORRECT: namespaces should be UNdefined once you close the parent tag that defined them (I think?)
	 */
	for( NSString* prefix in namespacesByPrefix )
	{
		NSString* uri = [namespacesByPrefix objectForKey:prefix];
		
		if( [prefix isEqualToString:@""] ) // special string we put in earlier to indicate zero-length / "default" prefix
			[self.currentParseRun.namespacesEncountered setObject:uri forKey:[NSNull null]];
		else
			[self.currentParseRun.namespacesEncountered setObject:uri forKey:prefix];
	}
	
#if DEBUG_XML_PARSER
#if DEBUG_VERBOSE_LOG_EVERY_TAG
	DDLogWarn(@"[%@] DEBUG_VERBOSE: <%@%@> (namespace URL:%@), attributes: %i", [self class], [NSString stringWithFormat:@"%@:",stringPrefix], name, stringURI, nb_attributes );
#endif
#endif
	
#if DEBUG_VERBOSE_LOG_EVERY_TAG
	if( prefix2 == nil )
	{
		/* The XML library allows this, although it's very unhelpful when writing application code */
		
		/* Let's find out what namespaces DO exist... */
		
		/*
		 
		 TODO / DEVELOPER WARNING: the library says nb_namespaces is the number of elements in the array,
		 but it keeps returning nil pointer (not always, but often). WTF? Not sure what we're doing wrong
		 here, but commenting it out for now...
		 
		if( nb_namespaces > 0 )
		{
			for( int i=0; i<nb_namespaces; i++ )
			{
				DDLogWarn(@"[%@] DEBUG: found namespace [%i] : %@", [self class], i, namespaces[i] );
			}
		}
		else
			DDLogWarn(@"[%@] DEBUG: there are ZERO namespaces!", [self class] );
		 */
	}
#endif
	
	if( stringURI == nil && stringPrefix == nil )
	{
		DDLogWarn(@"[%@] WARNING: Your input SVG contains tags that have no namespace, and your document doesn't define a default namespace. This is always incorrect - it means some of your SVG data will be ignored, and usually means you have a typo in there somewhere. Tag with no namespace: <%@>", [self class], stringLocalName );
	}
		  
	[self handleStartElement:stringLocalName namePrefix:stringPrefix namespaceURI:stringURI attributeObjects:attributeObjects];
}

- (void)handleEndElement:(NSString *)name {
	//DELETE DEBUG DDLogVerbose(@"ending element, name = %@", name);
	
	
	NSObject* lastobject = [_stackOfParserExtensions lastObject];
	
	[_stackOfParserExtensions removeLastObject];
	
	NSObject<SVGKParserExtension>* parser = (NSObject<SVGKParserExtension>*)lastobject;
//	NSObject<SVGKParserExtension>* parentParser = [_stackOfParserExtensions lastObject];
	
#if DEBUG_XML_PARSER
#if DEBUG_VERBOSE_LOG_EVERY_TAG
	DDLogVerbose(@"[%@] DEBUG-PARSER: ended tag (</%@>), handled by parser (%@) with parent parsed by %@", [self class], name, parser, parentParser );
#endif
#endif
	
	/**
	 At this point, the "parent of current node" is still set to the node we're
	 closing - because we haven't finished closing it yet
	 */
	if( _storedChars.length > 0 )
	{
		/** Send any parsed text data into the node-we're-closing */
		
		Text *tNode = [[[Text alloc] initWithValue:_storedChars] autorelease];
		
		[_parentOfCurrentNode appendChild:tNode];
		
		[_storedChars setString:@""];
	}
	
	[parser handleEndElement:_parentOfCurrentNode document:source parseResult:self.currentParseRun];
	
	/** Update the _parentOfCurrentNode to point to the parent of the node we just closed...
	 */
	_parentOfCurrentNode = _parentOfCurrentNode.parentNode;
}

static void	endElementSAX (void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
	SVGKParser* self = getCurrentlyParsingParser();
	
	[self handleEndElement:NSStringFromLibxmlString(localname)];
}

- (void)handleFoundCharacters:(const xmlChar *)chars length:(int)len {
	[_storedChars appendString:[[[NSString alloc] initWithBytes:chars length:len encoding:NSUTF8StringEncoding] autorelease]];
}

static void cDataFoundSAX(void *ctx, const xmlChar *value, int len)
{
    SVGKParser* self = getCurrentlyParsingParser();
	
	[self handleFoundCharacters:value length:len];
}

static void	charactersFoundSAX (void *ctx, const xmlChar *chars, int len) {
	SVGKParser* self = getCurrentlyParsingParser();
	
	[self handleFoundCharacters:chars length:len];
}

static void errorEncounteredSAX (void *ctx, const char *msg, ...) {
	DDLogWarn(@"Error encountered during parse: %s", msg);
	SVGKParser* self = getCurrentlyParsingParser();
	SVGKParseResult* parseResult = self.currentParseRun;
	[parseResult addSAXError:[NSError errorWithDomain:@"SVG-SAX" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
																				  (NSString*) msg, NSLocalizedDescriptionKey,
																				nil]]];
}

static void	unparsedEntityDeclaration(void * ctx,
									 const xmlChar * name,
									 const xmlChar * publicId,
									 const xmlChar * systemId,
									 const xmlChar * notationName)
{
	DDLogWarn(@"Error: unparsed entity Decl");
}

static void structuredError		(void * userData, 
									 xmlErrorPtr error)
{
	/**
	 XML_ERR_WARNING = 1 : A simple warning
	 XML_ERR_ERROR = 2 : A recoverable error
	 XML_ERR_FATAL = 3 : A fatal error
	 */
	xmlErrorLevel errorLevel = error->level;
	
	NSMutableDictionary* details = [NSMutableDictionary dictionaryWithObjectsAndKeys:
									[NSString stringWithCString:error->message encoding:NSUTF8StringEncoding], NSLocalizedDescriptionKey,
									[NSNumber numberWithInt:error->line], @"lineNumber",
									[NSNumber numberWithInt:error->int2], @"columnNumber",
									nil];
	
	if( error->str1 )
		[details setValue:[NSString stringWithCString:error->str1 encoding:NSUTF8StringEncoding] forKey:@"bonusInfo1"];
	if( error->str2 )
		[details setValue:[NSString stringWithCString:error->str2 encoding:NSUTF8StringEncoding] forKey:@"bonusInfo2"];
	if( error->str3 )
		[details setValue:[NSString stringWithCString:error->str3 encoding:NSUTF8StringEncoding] forKey:@"bonusInfo3"];
	
	NSError* objcError = [NSError errorWithDomain:[[NSNumber numberWithInt:error->domain] stringValue] code:error->code userInfo:details];
	
	SVGKParser* self = getCurrentlyParsingParser();
	SVGKParseResult* parseResult = self.currentParseRun;
	switch( errorLevel )
	{
		case XML_ERR_WARNING:
		{
			[parseResult addParseWarning:objcError];
		}break;
			
		case XML_ERR_ERROR:
		{
			[parseResult addParseErrorRecoverable:objcError];
		}break;
			
		case XML_ERR_FATAL:
		{
			[parseResult addParseErrorFatal:objcError];
		}
        default:
            break;
	}
	
}

static xmlSAXHandler SAXHandler = {
    NULL,                       /* internalSubset */
    NULL,                       /* isStandalone   */
    NULL,                       /* hasInternalSubset */
    NULL,                       /* hasExternalSubset */
    NULL,                       /* resolveEntity */
    NULL,                       /* getEntity */
    NULL,                       /* entityDecl */
    NULL,                       /* notationDecl */
    NULL,                       /* attributeDecl */
    NULL,                       /* elementDecl */
    unparsedEntityDeclaration,                       /* unparsedEntityDecl */
    NULL,                       /* setDocumentLocator */
    NULL,                       /* startDocument */
    NULL,                       /* endDocument */
    NULL,                       /* startElement*/
    NULL,                       /* endElement */
    NULL,                       /* reference */
    charactersFoundSAX,         /* characters */
    NULL,                       /* ignorableWhitespace */
    processingInstructionSAX,   /* processingInstruction */
    NULL,                       /* comment */
    NULL,                       /* warning */
    errorEncounteredSAX,        /* error */
    NULL,                       /* fatalError //: unused error() get all the errors */
    NULL,                       /* getParameterEntity */
    cDataFoundSAX,              /* cdataBlock */
    NULL,                       /* externalSubset */
    XML_SAX2_MAGIC,
    NULL,
    startElementSAX,            /* startElementNs */
    endElementSAX,              /* endElementNs */
    structuredError,                       /* serror */
};

#pragma mark -
#pragma mark Utility

static NSString *NSStringFromLibxmlString (const xmlChar *string) {
	if( string == NULL ) // Yes, Apple requires we do this check!
		return nil;
	else
		return [NSString stringWithUTF8String:(const char *) string];
}

static NSMutableDictionary *NSDictionaryFromLibxmlNamespaces (const xmlChar **namespaces, int namespaces_ct)
{
	NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
	
	for (int i = 0; i < namespaces_ct * 2; i += 2)
	{
		NSString* prefix = NSStringFromLibxmlString(namespaces[i]);
		NSString* uri = NSStringFromLibxmlString(namespaces[i+1]);
		
		if( prefix == nil )
			prefix = @""; // Special case: Apple dictionaries can't handle null keys
		
		[dict setObject:uri
				 forKey:prefix];
	}
	
	return [dict autorelease];
}


static NSMutableDictionary *NSDictionaryFromLibxmlAttributes (const xmlChar **attrs, int attr_ct) {
	NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
	
	for (int i = 0; i < attr_ct * 5; i += 5) {
		const char *begin = (const char *) attrs[i + 3];
		const char *end = (const char *) attrs[i + 4];
		size_t len = end - begin;
		
		NSString* value = [[[NSString alloc] initWithBytes:begin length:len encoding:NSUTF8StringEncoding] autorelease];
		
		NSString* localName = NSStringFromLibxmlString(attrs[i]);
		NSString* prefix = NSStringFromLibxmlString(attrs[i+1]);
		NSString* uri = NSStringFromLibxmlString(attrs[i+2]);
		
		NSString* qname = (prefix == nil) ? localName : [NSString stringWithFormat:@"%@:%@", prefix, localName];
		
		Attr* newAttribute = [[[Attr alloc] initWithNamespace:uri qualifiedName:qname value:value] autorelease];
		
		[dict setObject:newAttribute
				 forKey:qname];
	}
	
	return [dict autorelease];
}

#define MAX_ACCUM 256
#define MAX_NAME 256

+(NSDictionary *) NSDictionaryFromCSSAttributes: (Attr*) styleAttribute {
	
	if( styleAttribute == nil )
	{
		DDLogWarn(@"[%@] WARNING: asked to convert an empty CSS string into a CSS dictionary; returning empty dictionary", [self class] );
		return [NSDictionary dictionary];
	}
	
	NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
	
	const char *cstr = [styleAttribute.value UTF8String];
	size_t len = strlen(cstr);
	
	char name[MAX_NAME];
	bzero(name, MAX_NAME);
	
	char accum[MAX_ACCUM];
	bzero(accum, MAX_ACCUM);
	
	size_t accumIdx = 0;
	
	for (size_t n = 0; n <= len; n++) {
		char c = cstr[n];
		
		if (c == '\n' || c == '\t' || c == ' ') {
			continue;
		}
		
		if (c == ':') {
			strcpy(name, accum);
			name[accumIdx] = '\0';
			
			bzero(accum, MAX_ACCUM);
			accumIdx = 0;
			
			continue;
		}
		else if (c == ';' || c == '\0') {
			accum[accumIdx] = '\0';
			
			Attr* newAttribute = [[[Attr alloc] initWithNamespace:styleAttribute.namespaceURI qualifiedName:[NSString stringWithUTF8String:name] value:[NSString stringWithUTF8String:accum]] autorelease];
			
			[dict setObject:newAttribute
					 forKey:newAttribute.localName];
			
			bzero(name, MAX_NAME);
			
			bzero(accum, MAX_ACCUM);
			accumIdx = 0;
			
			continue;
		}
		
		accum[accumIdx++] = c;
	}
	
	return [dict autorelease];
}

@end
