From c2e571f085d0cbc37047b52e656770a66ae364a3 Mon Sep 17 00:00:00 2001 From: Matthew Baranowski Date: Mon, 1 Jul 2013 16:03:45 -0400 Subject: [PATCH] Changed the convention so that all NSValueTransformers used by MTLXMLAdapter accept a NSArray of DDXMLNode objects. Also updated NSValueTransformer+MTLXMLTransformerAdditions to follow this convention in addition to several helper functions for parsing integers, NSURLs and NSDate objects. The default case if no NSValueTransformer has been specified, is to get the text of the first xml node returned by the specified xpath, just as before. This allows us to handle arrays of elements as selected by an xpath expressions without special case code. However now all code assuming a single xmlnode has to explicitly get the first element. This breaks backwards compatibility, so bumping up to version 0.2.0 --- Mantle/MTLXMLAdapter.m | 2 +- ...ueTransformer+MTLXMLTransformerAdditions.h | 4 + ...ueTransformer+MTLXMLTransformerAdditions.m | 114 ++++++++++++++---- MantleTests/MTLTestModelXML.h | 7 ++ MantleTests/MTLTestModelXML.m | 84 +++++-------- MantleTests/MTLXMLAdapterSpec.m | 42 ++++++- MantleXMLAdapter.podspec | 4 +- 7 files changed, 175 insertions(+), 82 deletions(-) diff --git a/Mantle/MTLXMLAdapter.m b/Mantle/MTLXMLAdapter.m index 36bd6c5a..aa9ca4c6 100644 --- a/Mantle/MTLXMLAdapter.m +++ b/Mantle/MTLXMLAdapter.m @@ -96,7 +96,7 @@ - (id)initWithXMLNode:(DDXMLNode*)xmlNode modelClass:(Class)modelClass error:(NS DDXMLNode* node = nodes[0]; NSValueTransformer *transformer = [self XMLTransformerForKey:propertyKey]; if (transformer != nil) { - value = [transformer transformedValue:node] ?: NSNull.null; + value = [transformer transformedValue:nodes] ?: NSNull.null; } else { value = [node stringValue]; } diff --git a/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.h b/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.h index 10728a2a..b97a37ed 100644 --- a/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.h +++ b/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.h @@ -10,6 +10,10 @@ @interface NSValueTransformer (MTLXMLTransformerAdditions) ++ (NSValueTransformer *)mtl_XMLTransformerForInteger; ++ (NSValueTransformer *)mtl_XMLTransformerForURL; ++ (NSValueTransformer *)mtl_XMLTransformerForDateWithFormat:(NSString*)dateFormat; + + (NSValueTransformer *)mtl_XMLTransformerWithModelClass:(Class)modelClass; + (NSValueTransformer *)mtl_XMLArrayTransformerWithModelClass:(Class)modelClass; + (NSValueTransformer *)mtl_XMLNonUniformObjectArrayTransformerWithModelClass:(Class)modelClass; diff --git a/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.m b/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.m index 930667b8..a1b2087b 100644 --- a/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.m +++ b/Mantle/NSValueTransformer+MTLXMLTransformerAdditions.m @@ -11,19 +11,91 @@ #import "MTLValueTransformer.h" #import "MTLXMLAdapter.h" + @implementation NSValueTransformer (MTLXMLTransformerAdditions) ++ (NSDateFormatter *)dateFormatter +{ + static NSDateFormatter* _dateFormatter; + if (!_dateFormatter) + { + _dateFormatter = [NSDateFormatter new]; + [_dateFormatter setDateStyle:NSDateFormatterFullStyle]; + [_dateFormatter setTimeStyle:NSDateFormatterFullStyle]; + } + + return _dateFormatter; +} + ++ (NSValueTransformer *)mtl_XMLTransformerForDateWithFormat:(NSString*)dateFormat { + return [MTLValueTransformer + reversibleTransformerWithForwardBlock:^id(NSArray *nodes) { + if (nodes == nil || nodes.count == 0) return nil; + + DDXMLNode* node = nodes[0]; + NSDateFormatter* formatter = [NSValueTransformer dateFormatter]; + [formatter setDateFormat:dateFormat]; + + NSString* locale = @"en_US_POSIX"; + if ([node kind] == DDXMLElementKind) { + DDXMLElement* element = (DDXMLElement*)node; + DDXMLNode* node = [element attributeForName:@"locale"]; + locale = [node stringValue]; + } + + [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:locale]]; + + id myDate; + NSError* error; + if (![formatter getObjectValue:&myDate + forString:[node stringValue] + range:nil + error:&error]) { + return nil; + } + + return myDate; + } + reverseBlock:^(NSDate *date) { + NSDateFormatter* formatter = [NSValueTransformer dateFormatter]; + [formatter setDateFormat:dateFormat]; + return [formatter stringFromDate:date]; + }]; +} + + ++ (NSValueTransformer *)mtl_XMLTransformerForInteger { + return [MTLValueTransformer + reversibleTransformerWithForwardBlock:^(NSArray *nodes) { + return @([nodes[0] stringValue].integerValue); + } + reverseBlock:^(NSNumber* num) { + return [ num stringValue]; + }]; +} + ++ (NSValueTransformer *)mtl_XMLTransformerForURL { + return [MTLValueTransformer + reversibleTransformerWithForwardBlock:^ id (NSArray *nodes) { + if (nodes == nil || nodes.count == 0) return nil; + DDXMLNode* node = nodes[0]; + return [NSURL URLWithString:node.stringValue]; + } + reverseBlock:^ id (NSURL *URL) { + if (![URL isKindOfClass:NSURL.class]) return nil; + return URL.absoluteString; + }]; +} + + (NSValueTransformer *)mtl_XMLTransformerWithModelClass:(Class)modelClass { NSParameterAssert([modelClass isSubclassOfClass:MTLModel.class]); NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLXMLSerializing)]); return [MTLValueTransformer - reversibleTransformerWithForwardBlock:^ id (DDXMLNode *node) { - if (node == nil) return nil; - - NSAssert([node isKindOfClass:DDXMLNode.class], @"Expected a DDXMLNode, got: %@", node); - - return [MTLXMLAdapter modelOfClass:modelClass fromXMLNode:node error:NULL]; + reversibleTransformerWithForwardBlock:^ id (NSArray *nodes) { + if (nodes == nil || nodes.count == 0) return nil; + NSAssert([nodes[0] isKindOfClass:DDXMLNode.class], @"Expected a DDXMLNode, got: %@", nodes[0]); + return [MTLXMLAdapter modelOfClass:modelClass fromXMLNode:nodes[0] error:NULL]; } reverseBlock:^ id (MTLModel *model) { if (model == nil) return nil; @@ -31,7 +103,7 @@ + (NSValueTransformer *)mtl_XMLTransformerWithModelClass:(Class)modelClass { NSAssert([model isKindOfClass:MTLModel.class], @"Expected a MTLModel object, got %@", model); NSAssert([model conformsToProtocol:@protocol(MTLXMLSerializing)], @"Expected a model object conforming to , got %@", model); - return [MTLXMLAdapter XMLElementFromModel:model]; + return @[ [MTLXMLAdapter XMLElementFromModel:model] ]; }]; } @@ -39,14 +111,15 @@ + (NSValueTransformer *)mtl_XMLArrayTransformerWithModelClass:(Class)modelClass NSValueTransformer *xmlTransformer = [self mtl_XMLTransformerWithModelClass:modelClass]; return [MTLValueTransformer - reversibleTransformerWithForwardBlock:^ id (DDXMLNode *xmlNode) { - if (xmlNode == nil) return nil; - - NSMutableArray *models = [NSMutableArray arrayWithCapacity:xmlNode.childCount]; - for (DDXMLNode *child in [xmlNode children]) { - id model = [xmlTransformer transformedValue:child]; - if (model == nil) continue; - [models addObject:model]; + reversibleTransformerWithForwardBlock:^ id (NSArray *nodes) { + if (nodes == nil) return nil; + NSMutableArray *models = [NSMutableArray arrayWithCapacity:nodes.count]; + for (DDXMLNode *child in nodes) { + if ([child isKindOfClass:[DDXMLElement class]]) { + id model = [xmlTransformer transformedValue:@[child] ]; + if (model == nil) continue; + [models addObject:model]; + } } return models; @@ -72,16 +145,15 @@ + (NSValueTransformer *)mtl_XMLArrayTransformerWithModelClass:(Class)modelClass + (NSValueTransformer *)mtl_XMLNonUniformObjectArrayTransformerWithModelClass:(Class)modelClass { return [MTLValueTransformer - reversibleTransformerWithForwardBlock:^ id (DDXMLNode *xmlNode) { - if (xmlNode == nil) return nil; + reversibleTransformerWithForwardBlock:^ id (NSArray *nodes) { + if (nodes == nil || nodes.count == 0) return nil; - NSMutableArray *models = [NSMutableArray arrayWithCapacity:xmlNode.childCount]; - for (DDXMLNode *child in [xmlNode children]) { + NSMutableArray *models = [NSMutableArray arrayWithCapacity:nodes.count]; + for (DDXMLNode *child in nodes) { Class classForNode = [modelClass classForParsingXML:child]; NSValueTransformer *xmlTransformer = [self mtl_XMLTransformerWithModelClass:classForNode]; - - id model = [xmlTransformer transformedValue:child]; + id model = [xmlTransformer transformedValue:@[child]]; if (model == nil) continue; [models addObject:model]; } diff --git a/MantleTests/MTLTestModelXML.h b/MantleTests/MTLTestModelXML.h index c9934c56..89cf3cf7 100644 --- a/MantleTests/MTLTestModelXML.h +++ b/MantleTests/MTLTestModelXML.h @@ -9,6 +9,10 @@ #import #import "MTLXMLAdapter.h" +@interface MTLTestElementXML : MTLModel +@property (nonatomic, copy) NSString *value; +@end + @interface MTLTestModelXML : MTLModel @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *userName; @@ -16,6 +20,9 @@ @property (nonatomic, assign) NSUInteger count; @property (nonatomic, copy) NSDate* date; +@property (nonatomic, copy) NSArray* arrayOfStrings1; +@property (nonatomic, copy) NSArray* arrayOfStrings2; + - (DDXMLElement *)serializeToXMLElement; @end diff --git a/MantleTests/MTLTestModelXML.m b/MantleTests/MTLTestModelXML.m index d7ee64ed..cd2f1ae3 100644 --- a/MantleTests/MTLTestModelXML.m +++ b/MantleTests/MTLTestModelXML.m @@ -8,9 +8,18 @@ #import "MTLTestModelXML.h" #import "DDXMLElementAdditions.h" - +#import "NSValueTransformer+MTLXMLTransformerAdditions.h" #import "DDXML.h" +@implementation MTLTestElementXML ++ (NSString*)XPathPrefix { + return @"self::element/"; +} ++ (NSDictionary *)XMLKeyPathsByPropertyKey { + return @{ @"value": @"text()" }; +} +@end + @implementation MTLTestModelXML + (NSString*)XPathPrefix { @@ -19,67 +28,26 @@ + (NSString*)XPathPrefix { + (NSDictionary *)XMLKeyPathsByPropertyKey { return @{ @"userName": @"userId", @"date": @"nested/date", - @"password": @"userId/@password" + @"password": @"userId/@password", + @"arrayOfStrings1": @"arrayOfStrings1/element", + @"arrayOfStrings2": @"element" }; } + (NSValueTransformer *)countXMLTransformer { - return [MTLValueTransformer - reversibleTransformerWithForwardBlock:^(DDXMLNode *node) { - return @([node stringValue].integerValue); - } - reverseBlock:^(NSNumber* num) { - return [DDXMLNode elementWithName:@"count" - stringValue:[num stringValue]]; - }]; + return [MTLValueTransformer mtl_XMLTransformerForInteger]; } -+ (NSDateFormatter *)dateFormatter -{ - static NSDateFormatter* _dateFormatter; - if (!_dateFormatter) - { - _dateFormatter = [NSDateFormatter new]; - [_dateFormatter setDateStyle:NSDateFormatterFullStyle]; - [_dateFormatter setTimeStyle:NSDateFormatterFullStyle]; - } - - return _dateFormatter; ++ (NSValueTransformer *)dateXMLTransformer { + return [NSValueTransformer mtl_XMLTransformerForDateWithFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; } -+ (NSValueTransformer *)dateXMLTransformer { - return [MTLValueTransformer - reversibleTransformerWithForwardBlock:^id(DDXMLNode *node) { - - NSDateFormatter* formatter = [MTLTestModelXML dateFormatter]; - [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; - - NSString* locale = @"en_US_POSIX"; - if ([node kind] == DDXMLElementKind) { - DDXMLElement* element = (DDXMLElement*)node; - DDXMLNode* node = [element attributeForName:@"locale"]; - locale = [node stringValue]; - } - - [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:locale]]; - - - id myDate; - NSError* error; - if (![formatter getObjectValue:&myDate - forString:[node stringValue] - range:nil - error:&error]) { - return nil; - } - - return myDate; - } - reverseBlock:^(NSDate *date) { - NSDateFormatter* formatter = [MTLTestModelXML dateFormatter]; - [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; - return [DDXMLNode elementWithName:@"date" stringValue:[formatter stringFromDate:date] ]; - }]; ++(NSValueTransformer*)arrayOfStrings1XMLTransformer { + return [NSValueTransformer mtl_XMLArrayTransformerWithModelClass:[MTLTestElementXML class]]; +} + ++(NSValueTransformer*)arrayOfStrings2XMLTransformer { + return [NSValueTransformer mtl_XMLArrayTransformerWithModelClass:[MTLTestElementXML class]]; } - (DDXMLElement *)serializeToXMLElement @@ -91,13 +59,15 @@ - (DDXMLElement *)serializeToXMLElement [userIdNode addAttributeWithName:@"password" stringValue:self.password]; [root addChild:userIdNode]; - NSValueTransformer* countTransformer = [MTLTestModelXML countXMLTransformer]; - [root addChild:[countTransformer reverseTransformedValue:[NSNumber numberWithInteger:self.count]]]; + DDXMLElement* countElement = [DDXMLNode elementWithName:@"count" + stringValue:[[MTLTestModelXML countXMLTransformer] reverseTransformedValue:@(self.count)]]; + [root addChild:countElement]; DDXMLElement* nestedNode = [[DDXMLElement alloc] initWithName:@"nested"]; NSValueTransformer *dateTransformer = [MTLTestModelXML dateXMLTransformer]; - DDXMLElement* dateNode = [dateTransformer reverseTransformedValue:self.date]; + DDXMLElement* dateNode = [[DDXMLElement alloc] initWithName:@"date" + stringValue:[dateTransformer reverseTransformedValue:self.date]]; [dateNode addAttributeWithName:@"locale" stringValue:@"en_US_POSIX"]; [nestedNode addChild:dateNode]; diff --git a/MantleTests/MTLXMLAdapterSpec.m b/MantleTests/MTLXMLAdapterSpec.m index 3d8ece14..0f11fdd6 100644 --- a/MantleTests/MTLXMLAdapterSpec.m +++ b/MantleTests/MTLXMLAdapterSpec.m @@ -26,7 +26,7 @@ // create an xml string NSString* xmlString = [element prettyXMLString]; - NSLog(@"xml:\n%@", xmlString); + //NSLog(@"xml:\n%@", xmlString); NSError* error = nil; DDXMLDocument* doc = [[DDXMLDocument alloc] initWithXMLString:xmlString options:0 error:&error]; @@ -93,4 +93,44 @@ expect(model.date).to.equal(myDate); }); +it(@"should initialize XML arrays", ^{ + + NSString* xmlString = @"\ + \ + A\ + B\ + C\ + D\ + \ + D\ + E\ + F\ + G\ + "; + + NSError *error = nil; + DDXMLDocument* doc = [[DDXMLDocument alloc] initWithXMLString:xmlString options:0 error:&error]; + expect(doc).notTo.beNil(); + expect(error).to.beNil(); + + MTLXMLAdapter* adapter = [[MTLXMLAdapter alloc] initWithXMLNode:doc + modelClass:MTLTestModelXML.class + error:&error]; + expect(adapter).notTo.beNil(); + expect(error).to.beNil(); + + MTLTestModelXML *model = (id)adapter.model; + expect(model.arrayOfStrings1.count).to.equal(4); + expect([model.arrayOfStrings1[0] value]).to.equal(@"A"); + expect([model.arrayOfStrings1[1] value]).to.equal(@"B"); + expect([model.arrayOfStrings1[2] value]).to.equal(@"C"); + expect([model.arrayOfStrings1[3] value]).to.equal(@"D"); + + expect(model.arrayOfStrings2.count).to.equal(4); + expect([model.arrayOfStrings2[0] value]).to.equal(@"D"); + expect([model.arrayOfStrings2[1] value]).to.equal(@"E"); + expect([model.arrayOfStrings2[2] value]).to.equal(@"F"); + expect([model.arrayOfStrings2[3] value]).to.equal(@"G"); +}); + SpecEnd \ No newline at end of file diff --git a/MantleXMLAdapter.podspec b/MantleXMLAdapter.podspec index 12ede0c8..914f15c2 100644 --- a/MantleXMLAdapter.podspec +++ b/MantleXMLAdapter.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = "MantleXMLAdapter" s.platform = :ios, "5.0" - s.version = "0.1.2" + s.version = "0.2.0" s.summary = "MantleXMLAdapter adds support to Mantle to create MTLModel objects from xml documents and (optionally) from models into xml documents." s.homepage = "https://github.com/mbaranowski/MantleXMLAdapter" s.license = "MIT" s.authors = { "Matthew Baranowski" => "matt.baranowski@willowtreeapps.com" } - s.source = { :git => "https://github.com/mbaranowski/MantleXMLAdapter.git", :tag => '0.1.2' } + s.source = { :git => "https://github.com/mbaranowski/MantleXMLAdapter.git", :tag => '0.2.0' } s.source_files = 'Mantle/MTLXMLAdapter.{h,m}', 'Mantle/NSValueTransformer+MTLXMLTransformerAdditions.{h,m}' s.dependency 'KissXML' s.dependency 'Mantle'