/
FromXMLElementDelegate.m
183 lines (152 loc) · 6.98 KB
/
FromXMLElementDelegate.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//
// FromXMLElementDelegate.m
//
//
// Created by Ryan Daigle on 7/31/08.
// Copyright 2008 yFactorial, LLC. All rights reserved.
//
#import "FromXMLElementDelegate.h"
#import "XMLSerializableSupport.h"
#import "CoreSupport.h"
@implementation FromXMLElementDelegate
@synthesize targetClass, parsedObject, currentPropertyName, contentOfCurrentProperty, unclosedProperties, currentPropertyType;
+ (FromXMLElementDelegate *)delegateForClass:(Class)targetClass {
FromXMLElementDelegate *delegate = [[[self alloc] init] autorelease];
[delegate setTargetClass:targetClass];
return delegate;
}
- (id)init {
super;
self.unclosedProperties = [NSMutableArray array];
return self;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([@"nil-classes" isEqualToString:elementName]) {
//empty result set, do nothing
}
//Start of an array type
else if ([@"array" isEqualToString:[attributeDict objectForKey:@"type"]]) {
self.parsedObject = [NSMutableArray array];
[self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]];
self.currentPropertyName = elementName;
}
//Start of the root object
else if (parsedObject == nil && [elementName isEqualToString:[self.targetClass xmlElementName]]) {
self.parsedObject = [[[self.targetClass alloc] init] autorelease];
[self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]];
self.currentPropertyName = elementName;
}
else {
//if we are inside another element and it is not the current parent object,
// then create an object for that parent element
if(self.currentPropertyName != nil && (![self.currentPropertyName isEqualToString:[[self.parsedObject class] xmlElementName]])) {
Class elementClass = NSClassFromString([currentPropertyName toClassName]);
if (elementClass != nil) {
//classname matches, instantiate a new instance of the class and set it as the current parent object
self.parsedObject = [[[elementClass alloc] init] autorelease];
[self.unclosedProperties addObject:[NSArray arrayWithObjects:self.currentPropertyName, self.parsedObject, nil]];
}
}
// If we recognize an element that corresponds to a known property of the current parent object, or if the
// current parent is an array then start collecting content for this child element
if (([self.parsedObject isKindOfClass:[NSArray class]]) ||
([[[self.parsedObject class] propertyNames] containsObject:[[self convertElementName:elementName] camelize]])) {
self.currentPropertyName = [self convertElementName:elementName];
self.contentOfCurrentProperty = [NSMutableString string];
self.currentPropertyType = [attributeDict objectForKey:@"type"];
} else {
// The element isn't one that we care about, so set the property that holds the
// character content of the current element to nil. That way, in the parser:foundCharacters:
// callback, the string that the parser reports will be ignored.
self.currentPropertyName = nil;
self.contentOfCurrentProperty = nil;
}
}
}
// Characters (i.e. an element value - retarded, I know) are given to us in chunks,
// so we need to append them onto the current property value.
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// If the current property is nil, then we know we're currently at
// an element that we don't know about or don't care about
if (self.contentOfCurrentProperty) {
// Append string value on since we're given them in chunks
[self.contentOfCurrentProperty appendString:string];
}
}
//Basic type conversion based on the ObjectiveResource "type" attribute
- (id) convertProperty:(NSString *)propertyValue toType:(NSString *)type {
if ([type isEqualToString:@"datetime" ]) {
return [NSDate fromXMLDateTimeString:propertyValue];
}
else if ([type isEqualToString:@"date"]) {
return [NSDate fromXMLDateString:propertyValue];
}
// uncomment this if you what to support NSNumber and NSDecimalNumber
// if you do your classId must be a NSNumber since rails will pass it as such
//else if ([type isEqualToString:@"decimal"]) {
// return [NSDecimalNumber decimalNumberWithString:propertyValue];
//}
//else if ([type isEqualToString:@"integer"]) {
// return [NSNumber numberWithInt:[propertyValue intValue]];
//}
else {
return [NSString fromXmlString:propertyValue];
}
}
// Converts the Id element to modelNameId
- (NSString *) convertElementName:(NSString *)anElementName {
if([anElementName isEqualToString:@"id"]) {
return [NSString stringWithFormat:@"%@Id",[[[self.parsedObject class]xmlElementName] camelize]];
// return [NSString stringWithFormat:@"%@_%@" , [NSStringFromClass([self.parsedObject class])
// stringByReplacingCharactersInRange:NSMakeRange(0, 1)
// withString:[[NSStringFromClass([self.parsedObject class])
// substringWithRange:NSMakeRange(0,1)]
// lowercaseString]], anElementName];
}
else {
return anElementName;
}
}
// We're done receiving the value of a particular element, so take the value we've collected and
// set it on the current object
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if(self.contentOfCurrentProperty != nil && self.currentPropertyName != nil) {
[self.parsedObject
setValue:[self convertProperty:self.contentOfCurrentProperty toType:self.currentPropertyType]
forKey:[self.currentPropertyName camelize]];
}
else if ([self.currentPropertyName isEqualToString:[self convertElementName:elementName]]) {
//element is closed, pop it from the stack
[self.unclosedProperties removeLastObject];
//check for a parent object on the stack
if ([self.unclosedProperties count] > 0) {
//handle arrays as a special case
if ([[[self.unclosedProperties lastObject] objectAtIndex:1] isKindOfClass:[NSArray class]]) {
[[[self.unclosedProperties lastObject] objectAtIndex:1] addObject:self.parsedObject];
}
else {
[[[self.unclosedProperties lastObject] objectAtIndex:1] setValue:self.parsedObject forKey:[self convertElementName:[elementName camelize]]];
}
self.parsedObject = [[self.unclosedProperties lastObject] objectAtIndex:1];
}
}
self.contentOfCurrentProperty = nil;
//set the parent object, if one exists, as the current element
if ([self.unclosedProperties count] > 0) {
self.currentPropertyName = [[self.unclosedProperties lastObject] objectAtIndex:0];
}
}
#pragma mark cleanup
- (void)dealloc {
[targetClass release];
[currentPropertyName release];
[parsedObject release];
[contentOfCurrentProperty release];
[unclosedProperties release];
[currentPropertyType release];
[super dealloc];
}
@end