123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952 |
- //
- // 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
|