forked from mattgemmell/MGTwitterEngine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MGTwitterYAJLParser.m
371 lines (290 loc) · 9.6 KB
/
MGTwitterYAJLParser.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
//
// MGTwitterYAJLParser.m
// MGTwitterEngine
//
// Created by Matt Gemmell on 18/02/2008.
// Copyright 2008 Instinctive Code.
#import "MGTwitterYAJLParser.h"
@implementation MGTwitterYAJLParser
#pragma mark Callbacks
static NSString *currentKey;
int MGTwitterYAJLParser_processNull(void *ctx)
{
id self = ctx;
if (currentKey)
{
[self addValue:[NSNull null] forKey:currentKey];
}
return 1;
}
int MGTwitterYAJLParser_processBoolean(void * ctx, int boolVal)
{
id self = ctx;
if (currentKey)
{
[self addValue:[NSNumber numberWithBool:(BOOL)boolVal] forKey:currentKey];
[self clearCurrentKey];
}
return 1;
}
int MGTwitterYAJLParser_processNumber(void *ctx, const char *numberVal, unsigned int numberLen)
{
id self = ctx;
if (currentKey)
{
NSString *stringValue = [[NSString alloc] initWithBytesNoCopy:(void *)numberVal length:numberLen encoding:NSUTF8StringEncoding freeWhenDone:NO];
// if there's a decimal, assume it's a double
if([stringValue rangeOfString:@"."].location != NSNotFound){
NSNumber *doubleValue = [NSNumber numberWithDouble:[stringValue doubleValue]];
[self addValue:doubleValue forKey:currentKey];
}else{
NSNumber *longLongValue = [NSNumber numberWithLongLong:[stringValue longLongValue]];
[self addValue:longLongValue forKey:currentKey];
}
[stringValue release];
[self clearCurrentKey];
}
return 1;
}
int MGTwitterYAJLParser_processString(void *ctx, const unsigned char * stringVal, unsigned int stringLen)
{
id self = ctx;
if (currentKey)
{
NSMutableString *value = [[[NSMutableString alloc] initWithBytes:stringVal length:stringLen encoding:NSUTF8StringEncoding] autorelease];
[value replaceOccurrencesOfString:@">" withString:@">" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])];
[value replaceOccurrencesOfString:@"<" withString:@"<" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])];
[value replaceOccurrencesOfString:@"&" withString:@"&" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])];
[value replaceOccurrencesOfString:@""" withString:@"\"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])];
if ([currentKey isEqualToString:@"created_at"])
{
// we have a priori knowledge that the value for created_at is a date, not a string
struct tm theTime;
if ([value hasSuffix:@"+0000"])
{
// format for Search API: "Fri, 06 Feb 2009 07:28:06 +0000"
strptime([value UTF8String], "%a, %d %b %Y %H:%M:%S +0000", &theTime);
}
else
{
// format for REST API: "Thu Jan 15 02:04:38 +0000 2009"
strptime([value UTF8String], "%a %b %d %H:%M:%S +0000 %Y", &theTime);
}
time_t epochTime = timegm(&theTime);
// save the date as a long with the number of seconds since the epoch in 1970
[self addValue:[NSNumber numberWithLong:epochTime] forKey:currentKey];
// this value can be converted to a date with [NSDate dateWithTimeIntervalSince1970:epochTime]
}
else
{
[self addValue:value forKey:currentKey];
}
[self clearCurrentKey];
}
return 1;
}
int MGTwitterYAJLParser_processMapKey(void *ctx, const unsigned char * stringVal, unsigned int stringLen)
{
id self = (id)ctx;
if (currentKey)
{
[self clearCurrentKey];
}
currentKey = [[NSString alloc] initWithBytes:stringVal length:stringLen encoding:NSUTF8StringEncoding];
return 1;
}
int MGTwitterYAJLParser_processStartMap(void *ctx)
{
id self = ctx;
[self startDictionaryWithKey:currentKey];
return 1;
}
int MGTwitterYAJLParser_processEndMap(void *ctx)
{
id self = ctx;
[self endDictionary];
return 1;
}
int MGTwitterYAJLParser_processStartArray(void *ctx)
{
id self = ctx;
[self startArrayWithKey:currentKey];
return 1;
}
int MGTwitterYAJLParser_processEndArray(void *ctx)
{
id self = ctx;
[self endArray];
return 1;
}
static yajl_callbacks sMGTwitterYAJLParserCallbacks = {
MGTwitterYAJLParser_processNull,
MGTwitterYAJLParser_processBoolean,
NULL,
NULL,
MGTwitterYAJLParser_processNumber,
MGTwitterYAJLParser_processString,
MGTwitterYAJLParser_processStartMap,
MGTwitterYAJLParser_processMapKey,
MGTwitterYAJLParser_processEndMap,
MGTwitterYAJLParser_processStartArray,
MGTwitterYAJLParser_processEndArray
};
#pragma mark Creation and Destruction
+ (id)parserWithJSON:(NSData *)theJSON delegate:(NSObject *)theDelegate
connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType
responseType:(MGTwitterResponseType)respType URL:(NSURL *)URL
deliveryOptions:(MGTwitterEngineDeliveryOptions)deliveryOptions
{
id parser = [[self alloc] initWithJSON:theJSON
delegate:theDelegate
connectionIdentifier:identifier
requestType:reqType
responseType:respType
URL:URL
deliveryOptions:deliveryOptions];
return [parser autorelease];
}
- (id)initWithJSON:(NSData *)theJSON delegate:(NSObject *)theDelegate
connectionIdentifier:(NSString *)theIdentifier requestType:(MGTwitterRequestType)reqType
responseType:(MGTwitterResponseType)respType URL:(NSURL *)theURL
deliveryOptions:(MGTwitterEngineDeliveryOptions)theDeliveryOptions
{
if (self = [super init])
{
json = [theJSON retain];
identifier = [theIdentifier retain];
requestType = reqType;
responseType = respType;
URL = [theURL retain];
deliveryOptions = theDeliveryOptions;
delegate = theDelegate;
if (deliveryOptions & MGTwitterEngineDeliveryAllResultsOption)
{
parsedObjects = [[NSMutableArray alloc] initWithCapacity:0];
}
else
{
parsedObjects = nil; // rely on nil target to discard addObject
}
if ([json length] <= 5)
{
// NOTE: this is a hack for API methods that return short JSON responses that can't be parsed by YAJL. These include:
// friendships/exists: returns "true" or "false"
// help/test: returns "ok"
// An empty response of "[]" is a special case.
NSString *result = [[[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding] autorelease];
if (! [result isEqualToString:@"[]"])
{
NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] initWithCapacity:1] autorelease];
if ([result isEqualToString:@"\"ok\""])
{
[dictionary setObject:[NSNumber numberWithBool:YES] forKey:@"ok"];
}
else
{
[dictionary setObject:[NSNumber numberWithBool:[result isEqualToString:@"true"]] forKey:@"friends"];
}
[dictionary setObject:[NSNumber numberWithInt:requestType] forKey:TWITTER_SOURCE_REQUEST_TYPE];
[self _parsedObject:dictionary];
[parsedObjects addObject:dictionary];
}
}
else
{
// setup the yajl parser
yajl_parser_config cfg = {
0, // allowComments: if nonzero, javascript style comments will be allowed in the input (both /* */ and //)
0 // checkUTF8: if nonzero, invalid UTF8 strings will cause a parse error
};
_handle = yajl_alloc(&sMGTwitterYAJLParserCallbacks, &cfg, NULL, self);
if (! _handle)
{
return nil;
}
yajl_status status = yajl_parse(_handle, [json bytes], [json length]);
if (status != yajl_status_insufficient_data && status != yajl_status_ok)
{
unsigned char *errorMessage = yajl_get_error(_handle, 0, [json bytes], [json length]);
NSLog(@"MGTwitterYAJLParser: error = %s", errorMessage);
[self _parsingErrorOccurred:[NSError errorWithDomain:@"YAJL" code:status userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithUTF8String:(char *)errorMessage] forKey:@"errorMessage"]]];
yajl_free_error(_handle, errorMessage);
}
// free the yajl parser
yajl_free(_handle);
}
// notify the delegate that parsing completed
[self _parsingDidEnd];
}
return self;
}
- (void)dealloc
{
[parsedObjects release];
[json release];
[identifier release];
[URL release];
delegate = nil;
[super dealloc];
}
- (void)parse
{
// empty implementation -- override in subclasses
}
#pragma mark Subclass utilities
- (void)addValue:(id)value forKey:(NSString *)key
{
// default implementation -- override in subclasses
NSLog(@"%@ = %@ (%@)", key, value, NSStringFromClass([value class]));
}
- (void)startDictionaryWithKey:(NSString *)key
{
// default implementation -- override in subclasses
NSLog(@"dictionary start = %@", key);
}
- (void)endDictionary
{
// default implementation -- override in subclasses
NSLog(@"dictionary end");
}
- (void)startArrayWithKey:(NSString *)key
{
// default implementation -- override in subclasses
NSLog(@"array start = %@", key);
arrayDepth++;
}
- (void)endArray
{
// default implementation -- override in subclasses
NSLog(@"array end");
arrayDepth--;
[self clearCurrentKey];
}
- (void)clearCurrentKey{
if(arrayDepth == 0){
[currentKey release];
currentKey = nil;
}
}
#pragma mark Delegate callbacks
- (BOOL) _isValidDelegateForSelector:(SEL)selector
{
return ((delegate != nil) && [delegate respondsToSelector:selector]);
}
- (void)_parsingDidEnd
{
if ([self _isValidDelegateForSelector:@selector(parsingSucceededForRequest:ofResponseType:withParsedObjects:)])
[delegate parsingSucceededForRequest:identifier ofResponseType:responseType withParsedObjects:parsedObjects];
}
- (void)_parsingErrorOccurred:(NSError *)parseError
{
if ([self _isValidDelegateForSelector:@selector(parsingFailedForRequest:ofResponseType:withError:)])
[delegate parsingFailedForRequest:identifier ofResponseType:responseType withError:parseError];
}
- (void)_parsedObject:(NSDictionary *)dictionary
{
if (deliveryOptions & MGTwitterEngineDeliveryIndividualResultsOption)
if ([self _isValidDelegateForSelector:@selector(parsedObject:forRequest:ofResponseType:)])
[delegate parsedObject:(NSDictionary *)dictionary forRequest:identifier ofResponseType:responseType];
}
@end