Permalink
Browse files

Added array/element transformer for XML documents.

  • Loading branch information...
1 parent 3d8d6db commit bec50725ff71631ca23a5dc7491bdee3bac66855 @sleroux sleroux committed May 30, 2014
@@ -139,7 +139,6 @@
E93E47A41936304E006B0D66 /* MTLXMLAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = E93E479719362EF9006B0D66 /* MTLXMLAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; };
E93E47A6193630D3006B0D66 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E93E47A5193630D3006B0D66 /* libz.dylib */; };
E93E47A8193630DB006B0D66 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E93E47A7193630DB006B0D66 /* libxml2.dylib */; };
- E93E47AA193632F7006B0D66 /* libRaptureXML.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E93E47A9193632F7006B0D66 /* libRaptureXML.a */; };
E93E47AB19363303006B0D66 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E93E47A7193630DB006B0D66 /* libxml2.dylib */; };
E93E47B0193637AB006B0D66 /* MTLTestXMLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E93E47AD1936373B006B0D66 /* MTLTestXMLModel.m */; };
E93E47B1193637AC006B0D66 /* MTLTestXMLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E93E47AD1936373B006B0D66 /* MTLTestXMLModel.m */; };
@@ -304,7 +303,6 @@
buildActionMask = 2147483647;
files = (
E93E47AB19363303006B0D66 /* libxml2.dylib in Frameworks */,
- E93E47AA193632F7006B0D66 /* libRaptureXML.a in Frameworks */,
D021D947170F8A8100C37E36 /* CoreData.framework in Frameworks */,
D042FC8815F72BC7004E8054 /* SenTestingKit.framework in Frameworks */,
D042FC8B15F72BC7004E8054 /* Foundation.framework in Frameworks */,
View
@@ -1,6 +1,6 @@
#import <Foundation/Foundation.h>
-@class MTLModel;
+@class MTLModel, MTLXMLElement;
@protocol MTLXMLSerializable <NSObject>
@@ -29,9 +29,17 @@ extern const NSInteger MTLXMLAdapterErrorInvalidRXMLElement;
@property (strong, nonatomic, readonly) MTLModel *model;
+ (id)modelOfClass:(Class)modelClass
- fromXML:(NSData *)xml
+ fromData:(NSData *)xml
error:(NSError **)error;
-- (id)initWithXMLData:(NSData *)xml modelClass:(Class)modelClass error:(NSError **)error;
++ (id)modelOfClass:(Class)modelClass
+ fromXMLElement:(MTLXMLElement *)xml
+ error:(NSError **)error;
+
+- (id)initWithModel:(MTLModel<MTLXMLSerializable> *)model;
+
+- (id)initWithXMLData:(NSData *)data modelClass:(Class)modelClass error:(NSError **)error;
+
+- (id)initWithXMLElement:(MTLXMLElement *)xmlDoc modelClass:(Class)modelClass error:(NSError **)error;
@end
View
@@ -14,48 +14,54 @@
@interface MTLXMLAdapter ()
@property (strong, nonatomic, readonly) Class modelClass;
-@property (copy, nonatomic, readonly) NSDictionary *XMLXPathsByPropertyKey;
+@property (copy, nonatomic, readonly) NSDictionary *XMLXPathsByPropertyKey;
@end
@implementation MTLXMLAdapter
+ (id)modelOfClass:(Class)modelClass
- fromXML:(NSData *)xml
+ fromData:(NSData *)xml
error:(NSError **)error
{
MTLXMLAdapter *adapter = [[self alloc] initWithXMLData:xml modelClass:modelClass error:error];
return adapter.model;
}
-- (id)initWithXMLData:(NSData *)xml modelClass:(Class)modelClass error:(NSError **)error
++ (id)modelOfClass:(Class)modelClass
+ fromXMLElement:(MTLXMLElement *)xml
+ error:(NSError **)error
+{
+ MTLXMLAdapter *adapter = [[self alloc] initWithXMLElement:xml modelClass:modelClass error:error];
+ return adapter.model;
+}
+
+- (id)initWithModel:(MTLModel<MTLXMLSerializable> *)model
+{
+ NSParameterAssert(model != nil);
+
+ self = [super init];
+ if (self == nil) return nil;
+
+ _model = model;
+ _modelClass = model.class;
+ _XMLXPathsByPropertyKey = [[model.class XMLXPathsByPropertyKey] copy];
+
+ return self;
+}
+
+- (id)initWithXMLData:(NSData *)data modelClass:(Class)modelClass error:(NSError **)error
+{
+ return [self initWithXMLElement:[[MTLXMLElement alloc] initWithData:data] modelClass:modelClass error:error];
+}
+
+- (id)initWithXMLElement:(MTLXMLElement *)xmlDoc modelClass:(Class)modelClass error:(NSError **)error
{
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass isSubclassOfClass:MTLModel.class]);
-
- if (xml == nil || ![xml isKindOfClass:NSData.class]) {
- if (error != NULL) {
- NSString *errorReason = [NSString stringWithFormat:NSLocalizedString(@"%@ could not be created because an "
- "invalid NSData instance was provided: %@",
- @""),
- NSStringFromClass(modelClass), xml.class];
-
- NSDictionary *userInfo = @{
- NSLocalizedDescriptionKey: NSLocalizedString(@"Missing NSData", @""),
- NSLocalizedFailureReasonErrorKey: errorReason
- };
-
- *error = [NSError errorWithDomain:MTLXMLAdapterErrorDomain code:MTLXMLAdapterErrorInvalidData
- userInfo:userInfo];
- }
-
- return nil;
- }
if(!(self = [super init])) return nil;
- MTLXMLElement *xmlDoc = [[MTLXMLElement alloc] initWithData:xml];
-
NSMutableDictionary *dictionaryValue = [NSMutableDictionary dictionary];
_modelClass = modelClass;
@@ -83,11 +89,11 @@ - (id)initWithXMLData:(NSData *)xml modelClass:(Class)modelClass error:(NSError
if (XMLXPath == nil) continue;
NSArray *xpathResults = [xmlDoc xpathQuery:XMLXPath error:error];
- if (*error) return nil;
+ if (error != NULL && *error) return nil;
id value;
if (xpathResults.count > 0) {
- value = [xpathResults[0] text];
+ value = [xpathResults[0] value];
} else {
value = nil;
}
View
@@ -12,7 +12,7 @@
- (id)initWithData:(NSData *)data;
-- (NSString *)text;
+- (NSString *)value;
- (NSArray *)xpathQuery:(NSString *)xpath error:(NSError **)error;
View
@@ -41,19 +41,39 @@ - (id)initWithXMLDoc:(xmlDocPtr)doc node:(xmlNodePtr)node
return self;
}
-- (NSString *)text
+- (NSString *)value
{
- xmlChar *key = xmlNodeGetContent(node_);
- NSString *text = (key ? [NSString stringWithUTF8String:(const char *)key] : @"");
- xmlFree(key);
+ NSString *valueStr;
- return text;
+ switch (node_->type) {
+ case XML_ELEMENT_NODE: {
+ xmlChar *key = xmlNodeGetContent(node_);
+ valueStr = (key ? [NSString stringWithUTF8String:(const char *)key] : @"");
+ xmlFree(key);
+ break;
+ }
+
+ case XML_ATTRIBUTE_NODE: {
+ if (node_->children->content != NULL) {
+ valueStr = [NSString stringWithUTF8String:(const char *)node_->children->content];
+ } else {
+ valueStr = nil;
+ }
+
+ break;
+ }
+
+ default:
+ valueStr = nil;
+ }
+
+ return valueStr;
}
- (NSArray *)xpathQuery:(NSString *)xpath error:(NSError **)error
{
NSParameterAssert(xpath);
-
+
xmlXPathContextPtr context = xmlXPathNewContext(doc_);
if (context == NULL) {
@@ -74,11 +94,27 @@ - (NSArray *)xpathQuery:(NSString *)xpath error:(NSError **)error
NSMutableArray *resultNodes = [NSMutableArray array];
for (NSInteger i = 0; i < nodes->nodeNr; i++) {
- MTLXMLElement *element = [[MTLXMLElement alloc] initWithXMLDoc:doc_ node:nodes->nodeTab[i]];
-
- if (element != NULL) {
- [resultNodes addObject:element];
+ xmlNodePtr node = nodes->nodeTab[i];
+ id result;
+
+ switch (node->type) {
+ case XML_ATTRIBUTE_NODE: {
+ result = [[MTLXMLElement alloc] initWithXMLDoc:doc_ node:node];
+ break;
+ }
+
+ case XML_ELEMENT_NODE: {
+ NSString *nodeAsString = [self nodePtrToString:node];
+ result = [[MTLXMLElement alloc] initWithData:[nodeAsString dataUsingEncoding:NSUTF8StringEncoding]];
+ break;
+ }
+
+ default:
+ result = [[MTLXMLElement alloc] init];
+ break;
}
+
+ [resultNodes addObject:result];
}
xmlXPathFreeObject(object);
@@ -87,5 +123,14 @@ - (NSArray *)xpathQuery:(NSString *)xpath error:(NSError **)error
return resultNodes;
}
+- (NSString *)nodePtrToString:(xmlNodePtr)nodePtr
+{
+ xmlBufferPtr buffer = xmlBufferCreate();
+ xmlNodeDump(buffer, doc_, nodePtr, 0, 1);
+ NSString *objcString = [NSString stringWithUTF8String:(const char *)buffer->content];
+ xmlBufferFree(buffer);
+ return objcString;
+}
+
@end
@@ -8,8 +8,10 @@
#import "NSValueTransformer+MTLPredefinedTransformerAdditions.h"
#import "MTLJSONAdapter.h"
+#import "MTLXMLAdapter.h"
#import "MTLModel.h"
#import "MTLValueTransformer.h"
+#import "MTLXMLElement.h"
NSString * const MTLURLValueTransformerName = @"MTLURLValueTransformerName";
NSString * const MTLBooleanValueTransformerName = @"MTLBooleanValueTransformerName";
@@ -118,12 +120,26 @@ + (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)modelClass
+ (NSValueTransformer *)mtl_XMLDocumentTransformerWithModelClass:(Class)modelClass
{
- return nil;
+ return [MTLValueTransformer transformerWithBlock:^ id (id element) {
+ NSAssert([element isKindOfClass:MTLXMLElement.class], @"XML Document transformer requires a MTLXMLElement has input");
+ return [MTLXMLAdapter modelOfClass:modelClass fromXMLElement:element error:NULL];
+ }];
}
+ (NSValueTransformer *)mtl_XMLArrayTransformerWithModelClass:(Class)modelClass
{
- return nil;
+ return [MTLValueTransformer transformerWithBlock:^id (id elements) {
+ NSAssert([elements isKindOfClass:NSArray.class], @"Array XML transformer expects array as input");
+
+ NSMutableArray *models = [NSMutableArray array];
+ for (id element in elements) {
+ NSAssert([element isKindOfClass:MTLXMLElement.class], @"XML array must only use MTLXMLElements");
+ id model = [MTLXMLAdapter modelOfClass:modelClass fromXMLElement:element error:NULL];
+ [models addObject:model];
+ }
+
+ return models;
+ }];
}
+ (NSValueTransformer *)mtl_valueMappingTransformerWithDictionary:(NSDictionary *)dictionary {
@@ -8,6 +8,7 @@
#import "MTLTestModel.h"
#import "MTLTestXMLModel.h"
+#import "MTLXMLElement.h"
enum : NSInteger {
MTLPredefinedTransformerAdditionsSpecEnumNegative = -1,
@@ -128,34 +129,49 @@
describe(@"XML Transformers", ^{
describe(@"MTLXMLElement transformer", ^{
__block NSValueTransformer *transformer;
+ __block MTLTestXMLModel *model;
+ __block MTLXMLElement *rootElement;
+
+ const NSString *modelXML = @"<model><username>first</username></model>";
beforeEach(^{
+ rootElement = [[MTLXMLElement alloc] initWithData:[modelXML dataUsingEncoding:NSUTF8StringEncoding]];
+ model = [[MTLTestXMLModel alloc] init];
+ model.name = @"first";
transformer = [NSValueTransformer mtl_XMLDocumentTransformerWithModelClass:MTLTestXMLModel.class];
expect(transformer).notTo.beNil();
});
it(@"should transform a MTLXMLElement into a model", ^{
- });
-
- it(@"should transform a model into an MTLXMLElement", ^{
-
+ expect([transformer transformedValue:rootElement]).to.equal(model);
});
});
describe(@"Array of MTLXMLElement representation", ^{
__block NSValueTransformer *transformer;
+ __block NSArray *elements;
+ __block NSArray *models;
+ const NSString *modelsXML = @"<models><model><username>first</username></model><model><username>second</username></model></models>";
+
beforeEach(^{
+ MTLXMLElement *element = [[MTLXMLElement alloc] initWithData:[modelsXML dataUsingEncoding:NSUTF8StringEncoding]];
+ elements = [element xpathQuery:@"/models/model" error:NULL];
+
+ MTLTestXMLModel *firstModel = [[MTLTestXMLModel alloc] init];
+ firstModel.name = @"first";
+
+ MTLTestXMLModel *secondModel = [[MTLTestXMLModel alloc] init];
+ secondModel.name = @"second";
+
+ models = @[firstModel, secondModel];
+
transformer = [NSValueTransformer mtl_XMLArrayTransformerWithModelClass:MTLTestXMLModel.class];
expect(transformer).notTo.beNil();
});
it(@"should transform an array of MTLXMLElements into an array of models", ^{
-
- });
-
- it(@"should transform an array of models into an array of MTLXMLElement", ^{
-
+ expect([transformer transformedValue:elements]).to.equal(models);
});
});
});
@@ -38,7 +38,7 @@
NSData *xmlData = [values dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
- MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromXML:xmlData error:&error];
+ MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromData:xmlData error:&error];
expect(model).notTo.beNil();
expect(error).to.beNil();
@@ -52,7 +52,7 @@
NSData *xmlData = [values dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
- MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromXML:xmlData error:&error];
+ MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromData:xmlData error:&error];
expect(model).notTo.beNil();
expect(error).to.beNil();
@@ -65,7 +65,7 @@
NSData *xmlData = [values dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
- MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromXML:xmlData error:&error];
+ MTLTestXMLModel *model = [MTLXMLAdapter modelOfClass:MTLTestXMLModel.class fromData:xmlData error:&error];
expect(model).notTo.beNil();
expect(error).to.beNil();
@@ -77,7 +77,7 @@
NSData *xmlData = [values dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
- MTLIllegalXMLMappingModel *model = [MTLXMLAdapter modelOfClass:MTLIllegalXMLMappingModel.class fromXML:xmlData error:&error];
+ MTLIllegalXMLMappingModel *model = [MTLXMLAdapter modelOfClass:MTLIllegalXMLMappingModel.class fromData:xmlData error:&error];
expect(model).beNil();
expect(error).notTo.beNil();
expect(error.domain).to.equal(MTLXMLAdapterErrorDomain);

0 comments on commit bec5072

Please sign in to comment.