filesbox/app/filesbox_ios/FilesBox/Pods/SVGKit/Source/Parsers/SVGKParser.m
2023-09-21 10:53:23 +08:00

953 lines
34 KiB
Objective-C

//
// 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"
#import "SVGKDefine_Private.h"
@interface SVGKParser()
@property(nonatomic,strong, readwrite) SVGKSource* source;
@property(nonatomic,strong, readwrite) NSMutableArray* externalStylesheets;
@property(nonatomic,strong, readwrite) SVGKParseResult* currentParseRun;
@property(nonatomic,strong) 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];
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) addDefaultSVGParserExtensions
{
SVGKParserSVG *subParserSVG = [[SVGKParserSVG alloc] init];
SVGKParserGradient* subParserGradients = [[SVGKParserGradient alloc] init];
SVGKParserPatternsAndGradients *subParserPatternsAndGradients = [[SVGKParserPatternsAndGradients alloc] init];
SVGKParserStyles* subParserStyles = [[SVGKParserStyles alloc] init];
SVGKParserDefsAndUse *subParserDefsAndUse = [[SVGKParserDefsAndUse alloc] init];
SVGKParserDOM *subParserXMLDOM = [[SVGKParserDOM alloc] init];
[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])
{
SVGKitLogVerbose(@"[%@] 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 )
{
SVGKitLogError(@"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];
_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
/*
SVGKitLogVerbose(@"[%@] 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 )
{
SVGKitLogInfo( @"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 )
{
SVGKitLogError( @"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 )
{
SVGKitLogVerbose(@"[%@] 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
{
SVGKitLogWarn(@"[%@] SVG parser generated one or more FATAL errors (not the XML parser), errors follow:", [self class] );
for( NSError* error in currentParseRun.errorsFatal )
{
SVGKitLogWarn(@"[%@] ... 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 )
{
SVGKitLogWarn(@"[%@] 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];
}
- (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];
if( currentParseRun.parsedDocument.rootElement == nil )
{
if( self.externalStylesheets == nil )
self.externalStylesheets = [[NSMutableArray alloc] init];
[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];
[_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
SVGKitLogVerbose(@"[%@] 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 );
SVGKitLogVerbose(@"[%@] 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
SVGKitLogVerbose(@"[%@] 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];
[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
SVGKitLogWarn(@"[%@] 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++ )
{
SVGKitLogWarn(@"[%@] DEBUG: found namespace [%i] : %@", [self class], i, namespaces[i] );
}
}
else
SVGKitLogWarn(@"[%@] DEBUG: there are ZERO namespaces!", [self class] );
*/
}
#endif
if( stringURI == nil && stringPrefix == nil )
{
SVGKitLogWarn(@"[%@] 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 SVGKitLogVerbose(@"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
SVGKitLogVerbose(@"[%@] 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];
[_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]];
}
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, ...) {
SVGKitLogWarn(@"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 stringWithCString:msg encoding:NSUTF8StringEncoding], NSLocalizedDescriptionKey,
nil]]];
}
static void unparsedEntityDeclaration(void * ctx,
const xmlChar * name,
const xmlChar * publicId,
const xmlChar * systemId,
const xmlChar * notationName)
{
SVGKitLogWarn(@"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;
}
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];
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];
[dict setObject:newAttribute
forKey:qname];
}
return dict;
}
#define MAX_ACCUM 256
#define MAX_NAME 256
+(NSDictionary *) NSDictionaryFromCSSAttributes: (Attr*) styleAttribute {
if( styleAttribute == nil )
{
SVGKitLogWarn(@"[%@] 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]];
[dict setObject:newAttribute
forKey:newAttribute.localName];
bzero(name, MAX_NAME);
bzero(accum, MAX_ACCUM);
accumIdx = 0;
continue;
}
accum[accumIdx++] = c;
}
return dict;
}
@end