From 7183db6054c572a921287089e15abc3f50301b23 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Wed, 3 Feb 2010 16:28:17 -0500 Subject: [PATCH] Allow collision keys in the XML parser in order to support RSS feeds. RSS feeds tend to allow their feed items to be placed in the channel object as flat items, instead of within an array. The original XML parser implementation assumed that keys were always unique, but this is not the case with RSS feeds. Options were added to make it possible to toggle this functionality. --- src/TTURLXMLResponse.m | 8 +++++-- src/TTXMLParser.m | 42 ++++++++++++++++++++++++++++++---- src/Three20/TTURLXMLResponse.h | 13 +++++++++-- src/Three20/TTXMLParser.h | 14 +++++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/TTURLXMLResponse.m b/src/TTURLXMLResponse.m index 92758f2006..e2d11325ef 100644 --- a/src/TTURLXMLResponse.m +++ b/src/TTURLXMLResponse.m @@ -26,7 +26,8 @@ /////////////////////////////////////////////////////////////////////////////////////////////////// @implementation TTURLXMLResponse -@synthesize rootObject = _rootObject; +@synthesize rootObject = _rootObject; +@synthesize isRssFeed = _isRssFeed; /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -52,9 +53,12 @@ - (NSError*)request:(TTURLRequest*)request processResponse:(NSHTTPURLResponse*)r TTDASSERT(nil == _rootObject); if ([data isKindOfClass:[NSData class]]) { - NSLog(@"Data: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); + //NSLog(@"Data: %@", [[[NSString alloc] + // initWithData: data + // encoding: NSUTF8StringEncoding] autorelease]); TTXMLParser* parser = [[TTXMLParser alloc] initWithData:data]; parser.delegate = self; + parser.treatDuplicateKeysAsArrayItems = self.isRssFeed; [parser parse]; _rootObject = [parser.rootObject retain]; TT_RELEASE_SAFELY(parser); diff --git a/src/TTXMLParser.m b/src/TTXMLParser.m index 0928507067..22ba7cd647 100644 --- a/src/TTXMLParser.m +++ b/src/TTXMLParser.m @@ -36,7 +36,8 @@ /////////////////////////////////////////////////////////////////////////////////////////////////// @implementation TTXMLParser -@synthesize rootObject = _rootObject; +@synthesize rootObject = _rootObject; +@synthesize treatDuplicateKeysAsArrayItems = _treatDuplicateKeysAsArrayItems; /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -82,8 +83,8 @@ - (id)allocObjectForElementName: (NSString*) elementName /////////////////////////////////////////////////////////////////////////////////////////////////// - (void)addChild:(id)childObject toObject:(id)object { - // Is this an internal common "array" type? + // Is this an internal common "array" type? if ([object isKindOfClass:[NSDictionary class]] && [[object objectForKey:kPrivateKey_EntityType] isEqualToString:kCommonType_Array]) { @@ -92,10 +93,37 @@ - (void)addChild:(id)childObject toObject:(id)object { [[object objectForKey:kPrivateKey_Array] addObject:childObject]; } + // Is it an unknown dictionary type? } else if ([object isKindOfClass:[NSDictionary class]] && [[object objectForKey:kPrivateKey_EntityType] isEqualToString:kCommonXMLType_Unknown]) { - // It's an unknown dictionary type, let's just add this object then. - [object setObject:childObject forKey:[childObject objectForKey:kPrivateKey_EntityName]]; + + if (self.treatDuplicateKeysAsArrayItems) { + NSString* entityName = [childObject objectForKey:kPrivateKey_EntityName]; + id entityObject = [object objectForKey:entityName]; + if (nil == entityObject) { + // No collision, add it! + [object setObject:childObject forKey:entityName]; + + } else { + // Collision, check if it's already an array. + if (TTIsArrayWithItems(entityObject)) { + [entityObject addObject:childObject]; + } else { + NSMutableArray* array = [[NSMutableArray alloc] init]; + [array addObject:entityObject]; + [array addObject:childObject]; + [object setObject:array forKey:entityName]; + TT_RELEASE_SAFELY(array); + } + } + + } else { + // Avoid overwriting existing keys. + // If this is asserting, you probably need treatDuplicateKeysAsArrayItems set to YES. + TTDASSERT(nil == [object objectForKey:[childObject objectForKey:kPrivateKey_EntityName]]); + + [object setObject:childObject forKey:[childObject objectForKey:kPrivateKey_EntityName]]; + } } } @@ -215,6 +243,12 @@ - (void) parser: (NSXMLParser*) parser } +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { + TTDERROR(@"Error parsing the XML: %@", [parseError localizedDescription]); +} + + @end diff --git a/src/Three20/TTURLXMLResponse.h b/src/Three20/TTURLXMLResponse.h index d494403e6e..3c7d73004c 100644 --- a/src/Three20/TTURLXMLResponse.h +++ b/src/Three20/TTURLXMLResponse.h @@ -23,9 +23,18 @@ * parse HTML pages that are likely to have invalid markup. */ @interface TTURLXMLResponse : NSObject { - id _rootObject; + id _rootObject; + BOOL _isRssFeed; } -@property (nonatomic, retain, readonly) id rootObject; +@property (nonatomic, retain, readonly) id rootObject; + +/** + * Is this XML response an RSS feed? This distinction is necessary in order to allow duplicate + * keys in the XML objects. + * + * @default NO + */ +@property (nonatomic, assign) BOOL isRssFeed; @end diff --git a/src/Three20/TTXMLParser.h b/src/Three20/TTXMLParser.h index 72be32d26d..a77135e59e 100644 --- a/src/Three20/TTXMLParser.h +++ b/src/Three20/TTXMLParser.h @@ -27,10 +27,22 @@ extern NSString* kCommonXMLType_Unknown; @private id _rootObject; + BOOL _treatDuplicateKeysAsArrayItems; + NSMutableArray* _objectStack; } -@property (nonatomic, readonly) id rootObject; +@property (nonatomic, readonly) id rootObject; + +/** + * When a duplicate key is encountered, the key's value is turned into an array and both + * the original item and the new duplicate item are added to the array. Any subsequent duplicates + * are also added to this array. + * This is useful for RSS feeds, where feed items are presented inline (and not as array items). + * + * @default NO + */ +@property (nonatomic, assign) BOOL treatDuplicateKeysAsArrayItems; @end