diff --git a/Code/ObjectMapping/RKParserRegistry.h b/Code/ObjectMapping/RKParserRegistry.h index d0ef74096f..36c1e812aa 100644 --- a/Code/ObjectMapping/RKParserRegistry.h +++ b/Code/ObjectMapping/RKParserRegistry.h @@ -22,39 +22,76 @@ #import "RKParser.h" /** - The Parser Registry provides for the registration of RKParser classes - for a particular MIME Type. This enables - */ + RKParserRegistry provides for the registration of RKParser classes + that handle parsing/serializing for content by MIME Type. Registration + is configured via exact string matches (i.e. application/json) or via regular + expression. +*/ @interface RKParserRegistry : NSObject { NSMutableDictionary *_MIMETypeToParserClasses; + NSMutableArray *_MIMETypeToParserClassesRegularExpressions; } /** Return the global shared singleton registry for MIME Type to Parsers + + @return The global shared RKParserRegistry instance. */ + (RKParserRegistry *)sharedRegistry; /** Sets the global shared registry singleton to a new instance of RKParserRegistry + + @param registry A new parser registry object to configure as the shared instance. */ + (void)setSharedRegistry:(RKParserRegistry *)registry; /** - Instantiate and return a Parser for the given MIME Type + Returns an instance of the RKParser conformant class registered to handle content + with the given MIME Type. + + MIME Types are searched in the order in which they are registered and exact + string matches are favored over regular expressions. + + @param MIMEType The MIME Type of the content to be parsed/serialized. + @return An instance of the RKParser conformant class registered to handle the given MIME Type. */ - (id)parserForMIMEType:(NSString *)MIMEType; /** - Return the class registered for handling parser/encoder operations - for a given MIME Type + Returns an instance of the RKParser conformant class registered to handle content + with the given MIME Type. + + MIME Types are searched in the order in which they are registered and exact + string matches are favored over regular expressions. + + @param MIMEType The MIME Type of the content to be parsed/serialized. + @return The RKParser conformant class registered to handle the given MIME Type. */ - (Class)parserClassForMIMEType:(NSString *)MIMEType; /** - Registers an RKParser conformant class as the handler for the specified MIME Type + Registers an RKParser conformant class as the handler for MIME Types exactly matching the + specified MIME Type string. + + @param parserClass The RKParser conformant class to instantiate when parsing/serializing MIME Types matching MIMETypeExpression. + @param MIMEType A MIME Type string for which instances of parserClass should be used for parsing/serialization. */ - (void)setParserClass:(Class)parserClass forMIMEType:(NSString *)MIMEType; +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + +/** + Registers an RKParser conformant class as the handler for MIME Types matching the + specified regular expression. + + @param parserClass The RKParser conformant class to instantiate when parsing/serializing MIME Types matching MIMETypeExpression. + @param MIMETypeRegex A regular expression that matches MIME Types that should be handled by instances of parserClass. + */ +- (void)setParserClass:(Class)parserClass forMIMETypeRegularExpression:(NSRegularExpression *)MIMETypeRegex; + +#endif + /** Automatically configure the registry via run-time reflection of the RKParser classes available that ship with RestKit. This happens automatically when the shared registry diff --git a/Code/ObjectMapping/RKParserRegistry.m b/Code/ObjectMapping/RKParserRegistry.m index 80ce3c12af..0f76d69374 100644 --- a/Code/ObjectMapping/RKParserRegistry.m +++ b/Code/ObjectMapping/RKParserRegistry.m @@ -20,11 +20,11 @@ #import "RKParserRegistry.h" -RKParserRegistry* gSharedRegistry; +RKParserRegistry *gSharedRegistry; @implementation RKParserRegistry -+ (RKParserRegistry*)sharedRegistry { ++ (RKParserRegistry *)sharedRegistry { if (gSharedRegistry == nil) { gSharedRegistry = [RKParserRegistry new]; [gSharedRegistry autoconfigure]; @@ -33,7 +33,7 @@ + (RKParserRegistry*)sharedRegistry { return gSharedRegistry; } -+ (void)setSharedRegistry:(RKParserRegistry*)registry { ++ (void)setSharedRegistry:(RKParserRegistry *)registry { [registry retain]; [gSharedRegistry release]; gSharedRegistry = registry; @@ -43,6 +43,7 @@ - (id)init { self = [super init]; if (self) { _MIMETypeToParserClasses = [[NSMutableDictionary alloc] init]; + _MIMETypeToParserClassesRegularExpressions = [[NSMutableArray alloc] init]; } return self; @@ -50,18 +51,42 @@ - (id)init { - (void)dealloc { [_MIMETypeToParserClasses release]; + [_MIMETypeToParserClassesRegularExpressions release]; [super dealloc]; } -- (Class)parserClassForMIMEType:(NSString*)MIMEType { - return [_MIMETypeToParserClasses objectForKey:MIMEType]; +- (Class)parserClassForMIMEType:(NSString *)MIMEType { + id parserClass = [_MIMETypeToParserClasses objectForKey:MIMEType]; +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + if (!parserClass) + { + for (NSArray *regexAndClass in _MIMETypeToParserClassesRegularExpressions) { + NSRegularExpression *regex = [regexAndClass objectAtIndex:0]; + NSUInteger numberOfMatches = [regex numberOfMatchesInString:MIMEType options:0 range:NSMakeRange(0, [MIMEType length])]; + if (numberOfMatches) { + parserClass = [regexAndClass objectAtIndex:1]; + break; + } + } + } +#endif + return parserClass; } -- (void)setParserClass:(Class)parserClass forMIMEType:(NSString*)MIMEType { +- (void)setParserClass:(Class)parserClass forMIMEType:(NSString *)MIMEType { [_MIMETypeToParserClasses setObject:parserClass forKey:MIMEType]; } -- (id)parserForMIMEType:(NSString*)MIMEType { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + +- (void)setParserClass:(Class)parserClass forMIMETypeRegularExpression:(NSRegularExpression *)MIMETypeRegex { + NSArray *expressionAndClass = [NSArray arrayWithObjects:MIMETypeRegex, parserClass, nil]; + [_MIMETypeToParserClassesRegularExpressions addObject:expressionAndClass]; +} + +#endif + +- (id)parserForMIMEType:(NSString *)MIMEType { Class parserClass = [self parserClassForMIMEType:MIMEType]; if (parserClass) { return [[[parserClass alloc] init] autorelease]; @@ -74,8 +99,8 @@ - (void)autoconfigure { Class parserClass = nil; // JSON - NSSet* JSONParserClassNames = [NSSet setWithObjects:@"RKJSONParserJSONKit", @"RKJSONParserYAJL", @"RKJSONParserSBJSON", @"RKJSONParserNXJSON", nil]; - for (NSString* parserClassName in JSONParserClassNames) { + NSSet *JSONParserClassNames = [NSSet setWithObjects:@"RKJSONParserJSONKit", @"RKJSONParserYAJL", @"RKJSONParserSBJSON", @"RKJSONParserNXJSON", nil]; + for (NSString *parserClassName in JSONParserClassNames) { parserClass = NSClassFromString(parserClassName); if (parserClass) { [self setParserClass:parserClass forMIMEType:RKMIMETypeJSON]; diff --git a/Tests/Logic/ObjectMapping/RKParserRegistryTest.m b/Tests/Logic/ObjectMapping/RKParserRegistryTest.m index 83b6f85de6..b79674bd61 100644 --- a/Tests/Logic/ObjectMapping/RKParserRegistryTest.m +++ b/Tests/Logic/ObjectMapping/RKParserRegistryTest.m @@ -53,4 +53,36 @@ - (void)testShouldAutoconfigureBasedOnReflection { assertThat(parser, is(instanceOf([RKXMLParserXMLReader class]))); } +- (void)testRetrievalOfExactStringMatchForMIMEType { + RKParserRegistry* registry = [[RKParserRegistry new] autorelease]; + [registry setParserClass:[RKJSONParserJSONKit class] forMIMEType:RKMIMETypeJSON]; + id parser = [registry parserForMIMEType:RKMIMETypeJSON]; + assertThat(parser, is(instanceOf([RKJSONParserJSONKit class]))); +} + +- (void)testRetrievalOfRegularExpressionMatchForMIMEType { + RKParserRegistry *registry = [[RKParserRegistry new] autorelease]; + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"application/xml\\+\\w+" options:0 error:&error]; + [registry setParserClass:[RKJSONParserJSONKit class] forMIMETypeRegularExpression:regex]; + id parser = [registry parserForMIMEType:@"application/xml+whatever"]; + assertThat(parser, is(instanceOf([RKJSONParserJSONKit class]))); +} + +- (void)testRetrievalOfExactStringMatchIsFavoredOverRegularExpression { + RKParserRegistry *registry = [[RKParserRegistry new] autorelease]; + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"application/xml\\+\\w+" options:0 error:&error]; + [registry setParserClass:[RKJSONParserJSONKit class] forMIMETypeRegularExpression:regex]; + [registry setParserClass:[RKXMLParserXMLReader class] forMIMEType:@"application/xml+whatever"]; + + // Exact match + id exactParser = [registry parserForMIMEType:@"application/xml+whatever"]; + assertThat(exactParser, is(instanceOf([RKXMLParserXMLReader class]))); + + // Fallback to regex + id regexParser = [registry parserForMIMEType:@"application/xml+different"]; + assertThat(regexParser, is(instanceOf([RKJSONParserJSONKit class]))); +} + @end