-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathOXmlNonKVCTests.m
executable file
·211 lines (175 loc) · 11.2 KB
/
OXmlNonKVCTests.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
OXmlNonKVCTests.m
SAXy OX - Object-to-XML mapping library
Demonstrates KVC-compliant and non-KVC compliant mappings.
The KVC-compliant example is trivial in it's simplicity.
The non-KVC compliant example is not trivial, but demonstrates SAXy's most advanced features, including:
1) proxies
2) custom getter and setter blocks
3) virtual properties
4) mapper locking
5) manual configuration of 'result' property
Created by Richard Easterling on 2/16/13.
*/
#import <SenTestingKit/SenTestingKit.h>
#import "OXmlMapper.h"
#import "OXmlElementMapper.h"
#import "OXmlContext.h"
#import <CoreLocation/CLLocation.h>
#import "OXmlReader.h"
#import "OXmlWriter.h"
#import "OXUtil.h"
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - test class
////////////////////////////////////////////////////////////////////////////////////////
@interface OXLocation : NSObject
@property(nonatomic)CLLocationDegrees latitude;
@property(nonatomic)CLLocationDegrees longitude;
@property(nonatomic)CLLocationDistance altitude;
@property(nonatomic)CLLocationAccuracy horizontalAccuracy;
@property(nonatomic)CLLocationAccuracy verticalAccuracy;
@property(nonatomic)CLLocationDirection course;
@property(nonatomic)CLLocationSpeed speed;
@property(nonatomic)NSDate *timestamp;
- (CLLocation *)toCLLocation;
@end
@implementation OXLocation
- (CLLocation *)toCLLocation
{
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(self.latitude, self.longitude)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
@end
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - tests
////////////////////////////////////////////////////////////////////////////////////////
@interface OXmlNonKVCTests : SenTestCase @end
@implementation OXmlNonKVCTests
/**
Many KVC compliant mappings can be trivial, especialy if element names match property names, and attributes
and mixed namespaces are avoided.
The mapping for KVC-compliant OXLocation occurs automaticly, each property being mapped to an element of the
same name. By default, scalar properties with zero values are treated as nil avoiding output like: <course>0</course>
Unspecified elements/properties are mapped in alphabetical order: altitude, latitude, longitude
*/
- (void)testKVC_ComplientReaderWriter
{
//Declare a mapper with a root and a 'location' element that maps to a single OXLocation instance.
OXmlMapper *mapper = [[OXmlMapper mapper] elements:@[ [OXmlElementMapper rootXPath:@"/location" type:[OXLocation class]] ]];
OXmlReader *reader = [OXmlReader readerWithMapper:mapper]; //Create a reader
reader.context.logReaderStack = NO; //log mapping
NSString *xml1 = @"<location><altitude>3988.2</altitude><latitude>39.112233</latitude><longitude>-114.112233</longitude></location>";
OXLocation *loc = [reader readXmlText:xml1]; //read xml
STAssertEquals(3988.2, loc.altitude, @"loc.altitude==3988.2");
STAssertEquals(39.112233, loc.latitude, @"loc.latitude==39.112233");
STAssertEquals(-114.112233, loc.longitude, @"loc.longitude==-114.112233");
OXmlWriter *writer = [OXmlWriter writerWithMapper:mapper]; //Create a writer
writer.xmlHeader = nil; //don't emit xml header:
NSString *xml2 = [writer writeXml:loc prettyPrint:NO];
STAssertEqualObjects(xml1, xml2, @"input xml == output xml");
}
/**
CLLocation is a non-KVC compliant class and mapping it requires using SAXy's most advanced features.
Marshalling - CLLocation can be used directly for writing, but several challenges must be overcome:
1) latitude and longitude are not real properties
solution: create virtual, read-only properties using custom getter blocks
2) timestamp allways returns a date instance even if instantiated with a nil
solution: identify nil dates using 'timeIntervalSinceReferenceDate' in a custom getter block
3) CLLocation has problematic hidden properties
solution: declare desired properties, then lock mapping, preventing self-reflection scan
Unmarshalling - CLLocation's properties can't be set individualy, but must be set all at once in the constructor.
A SAXy work-around is to use a KVC-compliant equivalent class as a proxy. The proxy in this case will be OXLocation
configured by using the 'proxyClass' builder method. SAXy will load the OXLocation mapper, push a new OXLocation
intance on the stack and populate its properties. The only manual step required is to override the setter method,
swapping the proxy for the desired object before the assignment is made. In this case, the setter is the root
element's 'result' setter.
*/
- (void)testNonKVC_ComplientReaderWriter
{
OXmlMapper *mapper = [[OXmlMapper mapper] elements:@[
//Map the root element, overriding its 'result' setter. We swap the OXLocation
//instance with a CLLocation instance via the 'toCLLocation' method:
[[OXmlElementMapper root]
xpathMapper:[[[OXmlXPathMapper xpath: @"location"
type: [CLLocation class]
property: @"result"]
proxyClass: [OXLocation class]]
setter: ^(NSString *path, id value, id target, OXContext *ctx) {
//take the proxy (OXLocation) and convert it to the expected type (CLLocation):
CLLocation *loc = [value isMemberOfClass:[OXLocation class]] ? [value toCLLocation] : value;
[target setValue:loc forKey:path]; //basiclly: ctx.result = loc;
}]
],
//map CLLocation for writing/marshalling. Proxies are not used (or needed) for writing.
[[[[[[OXmlElementMapper elementClass:[CLLocation class]]
xpathMapper:[[[[OXmlXPathMapper xpath:@"latitude" scalar:@encode(CLLocationDegrees) property:@"latitude"]
setter:^(NSString *key, id value, id target, OXContext *ctx) {
NSAssert(NO, @"NOOP - CLLocation.coordinate.latitude is readonly, setter should never be called");
}]
getter:^(NSString *key, id target, OXContext *ctx) {
CLLocation *loc = target;
return loc ? [NSString stringWithFormat:@"%f", loc.coordinate.latitude] : nil;
}]
isVirtualProperty] //this sets a flag that prevents property validation against CLLocation
]
xpathMapper:[[[[OXmlXPathMapper xpath:@"longitude" scalar:@encode(CLLocationDegrees) property:@"longitude"]
setter:^(NSString *key, id value, id target, OXContext *ctx) {
NSAssert(NO, @"NOOP - CLLocation.coordinate.longitude is readonly, setter should never be called");
}]
getter:^(NSString *key, id target, OXContext *ctx) {
CLLocation *loc = target;
return loc ? [NSString stringWithFormat:@"%f", loc.coordinate.longitude] : nil;
}]
isVirtualProperty]
]
xpathMapper:[[OXmlXPathMapper xpath:@"timestamp" type:[NSDate class] property:@"timestamp"]
getter:^(NSString *key, id target, OXContext *ctx) {
//fix date handling - return nil if time interval is zero
CLLocation *loc = target;
NSDate *date = [loc timestamp];
NSTimeInterval interval = date ? [date timeIntervalSinceReferenceDate] : 0.0;
NSString *result = (interval == 0.0) ? nil : ctx.currentMapper.fromTransform(date, ctx);
return result;
}]
]
tags:@[@"altitude",@"horizontalAccuracy",@"verticalAccuracy",@"course",@"speed"]]
lockMapping] //prevents self-reflective discovery of additional/hidden properties
]];
OXmlReader *reader = [OXmlReader readerWithMapper:mapper];
reader.context.logReaderStack = NO;
NSString *xml1 = @"<location><latitude>39.112233</latitude><longitude>-114.112233</longitude><altitude>3988.2</altitude></location>";
CLLocation *loc = [reader readXmlText:xml1];
STAssertEquals(3988.2, loc.altitude, @"altitude");
STAssertEquals(39.112233, loc.coordinate.latitude, @"loc.coordinate.latitude==39.112233");
STAssertEquals(-114.112233, loc.coordinate.longitude, @"loc.coordinate.longitude==-114.112233");
OXmlWriter *writer = [OXmlWriter writerWithMapper:mapper];
writer.xmlHeader = nil;
NSString *xml2 = [writer writeXml:loc prettyPrint:NO];
//Notice that element ordering is determined by mapping declaration order: latitude, longitude, altitude
STAssertEqualObjects(xml1, xml2, @"input xml == output xml");
//uncomment to see hidden properties in CLLocation:
//[OXUtil propertyInspectionForClass:[CLLocation class] withBlock:^(NSString *propertyName, Class propertyClass, const char *attributes) {
// NSLog(@"%@:%@ [%s]", propertyName, NSStringFromClass(propertyClass), attributes);
//}];
}
@end
//
// Copyright (c) 2013 Outsource Cafe, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//