Skip to content

Instantly share code, notes, and snippets.

@paul-delange
Created October 2, 2013 12:11
Show Gist options
  • Save paul-delange/6792749 to your computer and use it in GitHub Desktop.
Save paul-delange/6792749 to your computer and use it in GitHub Desktop.
#import <Foundation/Foundation.h>
@class SVGParser;
FOUNDATION_EXPORT NSString * const kSVGParserErrorDomain;
@protocol SVGParserDelegate <NSObject>
@optional
- (void) svgParser: (SVGParser*) parser scannedAttributes: (NSDictionary*) attributes inElement: (NSString*) elementName;
- (void) svgParser: (SVGParser*) parser didFailWithError: (NSError*) error;
@end
@interface SVGParser : NSOperation
+ (instancetype) parserForFile: (NSString*) filePath; //Handles absolute file paths or filenames inside the main bundle
@property (weak, nonatomic) id<SVGParserDelegate> delegate; //Must be set before this operation starts
@end
#import "SVGParser.h"
#import <libxml/parser.h>
NSString * const kSVGFileExtension = @"svg";
NSString * const kSVGCompressedFileExtension = @"svgz";
NSString * const kSVGParserErrorDomain = @"SVGParserError";
static xmlSAXHandler svgSAXHandlerStruct;
static void errorEncounteredSAX(void * ctx, const char * msg, ...);
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);
@interface SVGParser () {
NSString* _fileName;
}
- (instancetype) initWithSVGNamed: (NSString*) svgFileName;
@property (readonly, nonatomic) BOOL isAbsoluteFilePath;
@end
@implementation SVGParser
+ (instancetype) parserForFile: (NSString*) svgFileName {
return [[self alloc] initWithSVGNamed: svgFileName];
}
- (instancetype) initWithSVGNamed:(NSString *)svgFileName {
NSString* extension = [svgFileName pathExtension];
NSAssert([extension isEqualToString: kSVGFileExtension] ||
[extension isEqualToString: kSVGCompressedFileExtension],
@"'%@' is not a valid SVG file",
svgFileName);
self = [super init];
if( self ) {
//fragile...
if( [svgFileName characterAtIndex: 0] == '/' ) {
_isAbsoluteFilePath = YES;
}
_fileName = svgFileName;
}
return self;
}
#pragma mark - NSOperation
- (void) main {
NSParameterAssert(_delegate);
NSString* filePath = _fileName;
if( !self.isAbsoluteFilePath ) {
NSBundle* bundle = [NSBundle mainBundle];
filePath = [bundle pathForResource: _fileName ofType: nil];
}
NSAssert([[NSFileManager defaultManager] fileExistsAtPath: filePath], @"Could not find file at path %@", filePath);
// http://xmlsoft.org/html/libxml-parser.html#xmlSAXUserParseFile
int result = xmlSAXUserParseFile(&svgSAXHandlerStruct, (__bridge void*)self, [filePath UTF8String]);
NSAssert(result == 0, @"Failed to parse svg file (%d) at path: %@", result, filePath);
if( result != 0 ) {
if( [self.delegate respondsToSelector: @selector(svgParser:didFailWithError:)] ) {
xmlErrorPtr xmlError = xmlGetLastError();
NSString* errorMsg = [[NSString alloc] initWithCString: xmlError->message encoding: NSUTF8StringEncoding];
NSError* error = [NSError errorWithDomain: kSVGParserErrorDomain code: xmlError->code userInfo: @{NSLocalizedDescriptionKey: errorMsg}];
[self.delegate svgParser: self didFailWithError: error];
}
}
xmlCleanupParser();
}
@end
#pragma mark - libxml SAX Parser
// http://xmlsoft.org/html/libxml-tree.html#xmlSAXHandler
static xmlSAXHandler svgSAXHandlerStruct = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
NULL, /* getEntity */
NULL, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
NULL, /* startElement*/
NULL, /* endElement */
NULL, /* reference */
NULL, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
NULL, /* warning */
errorEncounteredSAX, /* error */
NULL, /* fatalError //: unused error() get all the errors */
NULL, /* getParameterEntity */
NULL, /* cdataBlock */
NULL, /* externalSubset */
XML_SAX2_MAGIC, //
NULL,
startElementSAX, /* startElementNs */
NULL, /* endElementNs */
NULL, /* serror */
};
static NSMutableDictionary *NSDictionaryCreateFromLibxmlAttributes (const xmlChar **attrs, int attr_ct) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSUInteger limit = attr_ct * 5;
for (int i = 0; i < limit/*attr_ct * 5*/; i += 5) {
const char *begin = (const char *) attrs[i + 3];
const char *end = (const char *) attrs[i + 4];
int vlen = strlen(begin) - strlen(end);
char val[vlen + 1];
strncpy(val, begin, vlen);
val[vlen] = '\0';
[dict setObject:[NSString stringWithUTF8String:val]
forKey:[NSString stringWithUTF8String:(const char*)attrs[i]]];
}
return dict;
}
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) {
SVGParser* parser = (__bridge SVGParser*)ctx;
if( [parser.delegate respondsToSelector: @selector(svgParser:scannedAttributes:inElement:)] ) {
NSDictionary* attrs =NSDictionaryCreateFromLibxmlAttributes(attributes, nb_attributes);
NSString* element = [[NSString alloc] initWithCString: (const char*)localname encoding: NSUTF8StringEncoding];
[parser.delegate svgParser: parser scannedAttributes: attrs inElement: element];
}
}
static void errorEncounteredSAX(void *ctx, const char *msg, ...) {
SVGParser* parser = (__bridge SVGParser*)ctx;
if( [parser.delegate respondsToSelector: @selector(svgParser:didFailWithError:)] ) {
NSString* errorMsg = [[NSString alloc] initWithCString: msg encoding: NSUTF8StringEncoding];
NSError* error = [NSError errorWithDomain: kSVGParserErrorDomain code: -1 userInfo: @{NSLocalizedDescriptionKey : errorMsg}];
[parser.delegate svgParser: parser didFailWithError: error];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment