Skip to content

Commit

Permalink
Changed the convention so that all NSValueTransformers used by MTLXML…
Browse files Browse the repository at this point in the history
…Adapter 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
  • Loading branch information
mbaranowski committed Jul 1, 2013
1 parent 2a35c70 commit c2e571f
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 82 deletions.
2 changes: 1 addition & 1 deletion Mantle/MTLXMLAdapter.m
Expand Up @@ -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];
}
Expand Down
4 changes: 4 additions & 0 deletions Mantle/NSValueTransformer+MTLXMLTransformerAdditions.h
Expand Up @@ -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;
Expand Down
114 changes: 93 additions & 21 deletions Mantle/NSValueTransformer+MTLXMLTransformerAdditions.m
Expand Up @@ -11,42 +11,115 @@
#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<MTLXMLSerializing> *model) {
if (model == nil) return nil;

NSAssert([model isKindOfClass:MTLModel.class], @"Expected a MTLModel object, got %@", model);
NSAssert([model conformsToProtocol:@protocol(MTLXMLSerializing)], @"Expected a model object conforming to <MTLXMLSerializing>, got %@", model);

return [MTLXMLAdapter XMLElementFromModel:model];
return @[ [MTLXMLAdapter XMLElementFromModel:model] ];
}];
}

+ (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;
Expand All @@ -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];
}
Expand Down
7 changes: 7 additions & 0 deletions MantleTests/MTLTestModelXML.h
Expand Up @@ -9,13 +9,20 @@
#import <Mantle/Mantle.h>
#import "MTLXMLAdapter.h"

@interface MTLTestElementXML : MTLModel <MTLXMLSerializing>
@property (nonatomic, copy) NSString *value;
@end

@interface MTLTestModelXML : MTLModel <MTLXMLSerializing>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, assign) NSUInteger count;
@property (nonatomic, copy) NSDate* date;

@property (nonatomic, copy) NSArray* arrayOfStrings1;
@property (nonatomic, copy) NSArray* arrayOfStrings2;

- (DDXMLElement *)serializeToXMLElement;

@end
84 changes: 27 additions & 57 deletions MantleTests/MTLTestModelXML.m
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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];

Expand Down
42 changes: 41 additions & 1 deletion MantleTests/MTLXMLAdapterSpec.m
Expand Up @@ -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];
Expand Down Expand Up @@ -93,4 +93,44 @@
expect(model.date).to.equal(myDate);
});

it(@"should initialize XML arrays", ^{

NSString* xmlString = @"<TestModel>\
<arrayOfStrings1>\
<element>A</element>\
<element>B</element>\
<element>C</element>\
<element>D</element>\
</arrayOfStrings1>\
<element>D</element>\
<element>E</element>\
<element>F</element>\
<element>G</element>\
</TestModel>";

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
4 changes: 2 additions & 2 deletions 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'
Expand Down

0 comments on commit c2e571f

Please sign in to comment.