changed git call from https to git readonly
[atutor.git] / mods / atsocial_iphone_app / TouchJSON / JSON / CJSONScanner.m
1 //
2 //  CJSONScanner.m
3 //  TouchJSON
4 //
5 //  Created by Jonathan Wight on 12/07/2005.
6 //  Copyright 2005 Toxic Software. All rights reserved.
7 //
8
9 #import "CJSONScanner.h"
10
11 #import "NSCharacterSet_Extensions.h"
12 #import "CDataScanner_Extensions.h"
13
14 inline static int HexToInt(char inCharacter)
15 {
16 int theValues[] = { 0x0 /* 48 '0' */, 0x1 /* 49 '1' */, 0x2 /* 50 '2' */, 0x3 /* 51 '3' */, 0x4 /* 52 '4' */, 0x5 /* 53 '5' */, 0x6 /* 54 '6' */, 0x7 /* 55 '7' */, 0x8 /* 56 '8' */, 0x9 /* 57 '9' */, -1 /* 58 ':' */, -1 /* 59 ';' */, -1 /* 60 '<' */, -1 /* 61 '=' */, -1 /* 62 '>' */, -1 /* 63 '?' */, -1 /* 64 '@' */, 0xa /* 65 'A' */, 0xb /* 66 'B' */, 0xc /* 67 'C' */, 0xd /* 68 'D' */, 0xe /* 69 'E' */, 0xf /* 70 'F' */, -1 /* 71 'G' */, -1 /* 72 'H' */, -1 /* 73 'I' */, -1 /* 74 'J' */, -1 /* 75 'K' */, -1 /* 76 'L' */, -1 /* 77 'M' */, -1 /* 78 'N' */, -1 /* 79 'O' */, -1 /* 80 'P' */, -1 /* 81 'Q' */, -1 /* 82 'R' */, -1 /* 83 'S' */, -1 /* 84 'T' */, -1 /* 85 'U' */, -1 /* 86 'V' */, -1 /* 87 'W' */, -1 /* 88 'X' */, -1 /* 89 'Y' */, -1 /* 90 'Z' */, -1 /* 91 '[' */, -1 /* 92 '\' */, -1 /* 93 ']' */, -1 /* 94 '^' */, -1 /* 95 '_' */, -1 /* 96 '`' */, 0xa /* 97 'a' */, 0xb /* 98 'b' */, 0xc /* 99 'c' */, 0xd /* 100 'd' */, 0xe /* 101 'e' */, 0xf /* 102 'f' */, };
17 if (inCharacter >= '0' && inCharacter <= 'f')
18         return(theValues[inCharacter - '0']);
19 else
20         return(-1);
21 }
22
23 @interface CJSONScanner ()
24 @property (readwrite, retain) NSCharacterSet *notQuoteCharacters;
25 @property (readwrite, retain) NSCharacterSet *whitespaceCharacterSet;
26 @end
27
28 #pragma mark -
29
30 @implementation CJSONScanner
31
32 @synthesize scanComments;
33 @synthesize notQuoteCharacters;
34 @synthesize whitespaceCharacterSet;
35
36 - (id)init
37 {
38 if ((self = [super init]) != nil)
39         {
40         self.notQuoteCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"\\\""] invertedSet];
41         self.whitespaceCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
42         }
43 return(self);
44 }
45
46 - (void)dealloc
47 {
48 self.notQuoteCharacters = NULL;
49 self.whitespaceCharacterSet = NULL;
50 //
51 [super dealloc];
52 }
53
54 #pragma mark -
55
56 - (void)setData:(NSData *)inData
57 {
58 NSData *theData = inData;
59 if (theData && theData.length >= 4)
60         {
61         // This code is lame, but it works. Because the first character of any JSON string will always be a control character we can work out the Unicode encoding by the bit pattern. See section 3 of http://www.ietf.org/rfc/rfc4627.txt
62         const char *theChars = theData.bytes;
63         NSStringEncoding theEncoding = NSUTF8StringEncoding;
64         if (theChars[0] != 0 && theChars[1] == 0)
65                 {
66                 if (theChars[2] != 0 && theChars[3] == 0)
67                         theEncoding = NSUTF16LittleEndianStringEncoding;
68                 else if (theChars[2] == 0 && theChars[3] == 0)
69                         theEncoding = NSUTF32LittleEndianStringEncoding;
70                 }
71         else if (theChars[0] == 0 && theChars[2] == 0 && theChars[3] != 0)
72                 {
73                 if (theChars[1] == 0)
74                         theEncoding = NSUTF32BigEndianStringEncoding;
75                 else if (theChars[1] != 0)
76                         theEncoding = NSUTF16BigEndianStringEncoding;
77                 }
78                 
79         if (theEncoding != NSUTF8StringEncoding)
80                 {
81                 NSString *theString = [[[NSString alloc] initWithData:theData encoding:theEncoding] autorelease];
82                 theData = [theString dataUsingEncoding:NSUTF8StringEncoding];
83                 }
84         }
85 [super setData:theData];
86 }
87
88 #pragma mark -
89
90 - (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
91 {
92 [self skipJSONWhitespace];
93
94 id theObject = NULL;
95
96 const unichar C = [self currentCharacter];
97 switch (C)
98         {
99         case 't':
100                 if ([self scanUTF8String:"true" intoString:NULL])
101                         {
102                         theObject = [NSNumber numberWithBool:YES];
103                         }
104                 break;
105         case 'f':
106                 if ([self scanUTF8String:"false" intoString:NULL])
107                         {
108                         theObject = [NSNumber numberWithBool:NO];
109                         }
110                 break;
111         case 'n':
112                 if ([self scanUTF8String:"null" intoString:NULL])
113                         {
114                         theObject = [NSNull null];
115                         }
116                 break;
117         case '\"':
118         case '\'':
119                 [self scanJSONStringConstant:&theObject error:outError];
120                 break;
121         case '0':
122         case '1':
123         case '2':
124         case '3':
125         case '4':
126         case '5':
127         case '6':
128         case '7':
129         case '8':
130         case '9':
131         case '-':
132                 [self scanJSONNumberConstant:&theObject error:outError];
133                 break;
134         case '{':
135                 [self scanJSONDictionary:&theObject error:outError];
136                 break;
137         case '[':
138                 [self scanJSONArray:&theObject error:outError];
139                 break;
140         default:
141                 
142                 break;
143         }
144
145 if (outObject != NULL)
146         *outObject = theObject;
147 return(YES);
148 }
149
150 - (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError
151 {
152 unsigned theScanLocation = [self scanLocation];
153
154 //[self skipJSONWhitespace];
155
156 if ([self scanCharacter:'{'] == NO)
157         {
158         if (outError)
159                 {
160                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-1 userInfo:NULL];
161                 }
162         return(NO);
163         }
164
165 NSMutableDictionary *theDictionary = [NSMutableDictionary dictionary];
166
167 while ([self scanCharacter:'}'] == NO)
168         {
169         NSString *theKey = NULL;
170         if ([self scanJSONStringConstant:&theKey error:outError] == NO)
171                 {
172                 [self setScanLocation:theScanLocation];
173                 if (outError)
174                         {
175                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-2 userInfo:NULL];
176                         }
177                 return(NO);
178                 }
179
180         [self skipJSONWhitespace];
181
182         if ([self scanCharacter:':'] == NO)
183                 {
184                 [self setScanLocation:theScanLocation];
185                 if (outError)
186                         {
187                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-3 userInfo:NULL];
188                         }
189                 return(NO);
190                 }
191
192         id theValue = NULL;
193         if ([self scanJSONObject:&theValue error:outError] == NO)
194                 {
195                 [self setScanLocation:theScanLocation];
196                 if (outError)
197                         {
198                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-4 userInfo:NULL];
199                         }
200                 return(NO);
201                 }
202
203         [theDictionary setValue:theValue forKey:theKey];
204
205         [self skipJSONWhitespace];
206         if ([self scanCharacter:','] == NO)
207                 {
208                 if ([self currentCharacter] != '}')
209                         {
210                         [self setScanLocation:theScanLocation];
211                         if (outError)
212                                 {
213                                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-5 userInfo:NULL];
214                                 }
215                         return(NO);
216                         }
217                 break;
218                 }
219         else
220                 {
221                 [self skipJSONWhitespace];
222                 if ([self currentCharacter] == '}')
223                         break;
224                 }
225         }
226
227 if ([self scanCharacter:'}'] == NO)
228         {
229         [self setScanLocation:theScanLocation];
230         if (outError)
231                 {
232                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-6 userInfo:NULL];
233                 }
234         return(NO);
235         }
236         
237 if (outDictionary != NULL)
238         *outDictionary = theDictionary;
239 return(YES);
240 }
241
242 - (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError
243 {
244 unsigned theScanLocation = [self scanLocation];
245
246 //[self skipJSONWhitespace];
247 if ([self scanCharacter:'['] == NO)
248         {
249         if (outError)
250                 {
251                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-7 userInfo:NULL];
252                 }
253         return(NO);
254         }
255
256 NSMutableArray *theArray = [NSMutableArray array];
257
258 [self skipJSONWhitespace];
259 while ([self currentCharacter] != ']')
260         {
261         NSString *theValue = NULL;
262         if ([self scanJSONObject:&theValue error:outError] == NO)
263                 {
264                 [self setScanLocation:theScanLocation];
265                 if (outError)
266                         {
267                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-8 userInfo:NULL];
268                         }
269                 return(NO);
270                 }
271
272         [theArray addObject:theValue];
273         
274         [self skipJSONWhitespace];
275         if ([self scanCharacter:','] == NO)
276                 {
277                 [self skipJSONWhitespace];
278                 if ([self currentCharacter] != ']')
279                         {
280                         [self setScanLocation:theScanLocation];
281                         if (outError)
282                                 {
283                                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-9 userInfo:NULL];
284                                 }
285                         return(NO);
286                         }
287                 
288                 break;
289                 }
290         [self skipJSONWhitespace];
291         }
292
293 [self skipJSONWhitespace];
294
295 if ([self scanCharacter:']'] == NO)
296         {
297         [self setScanLocation:theScanLocation];
298         if (outError)
299                 {
300                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-10 userInfo:NULL];
301                 }
302         return(NO);
303         }
304
305 if (outArray != NULL)
306         *outArray = theArray;
307 return(YES);
308 }
309
310 - (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError
311 {
312 unsigned theScanLocation = [self scanLocation];
313
314 [self skipJSONWhitespace]; //  TODO - i want to remove this method. But breaks unit tests.
315
316 NSMutableString *theString = [NSMutableString string];
317
318 if ([self scanCharacter:'"'] == NO)
319         {
320         [self setScanLocation:theScanLocation];
321         if (outError)
322                 {
323                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-11 userInfo:NULL];
324                 }
325         return(NO);
326         }
327
328 while ([self scanCharacter:'"'] == NO)
329         {
330         NSString *theStringChunk = NULL;
331         if ([self scanCharactersFromSet:notQuoteCharacters intoString:&theStringChunk])
332                 {
333                 [theString appendString:theStringChunk];
334                 }
335         
336         if ([self scanCharacter:'\\'] == YES)
337                 {
338                 unichar theCharacter = [self scanCharacter];
339                 switch (theCharacter)
340                         {
341                         case '"':
342                         case '\\':
343                         case '/':
344                                 break;
345                         case 'b':
346                                 theCharacter = '\b';
347                                 break;
348                         case 'f':
349                                 theCharacter = '\f';
350                                 break;
351                         case 'n':
352                                 theCharacter = '\n';
353                                 break;
354                         case 'r':
355                                 theCharacter = '\r';
356                                 break;
357                         case 't':
358                                 theCharacter = '\t';
359                                 break;
360                         case 'u':
361                                 {
362                                 theCharacter = 0;
363
364                                 int theShift;
365                                 for (theShift = 12; theShift >= 0; theShift -= 4)
366                                         {
367                                         const int theDigit = HexToInt([self scanCharacter]);
368                                         if (theDigit == -1)
369                                                 {
370                                                 [self setScanLocation:theScanLocation];
371                                                 if (outError)
372                                                         {
373                                                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-12 userInfo:NULL];
374                                                         }
375                                                 return(NO);
376                                                 }
377                                         theCharacter |= (theDigit << theShift);
378                                         }
379                                 }
380                                 break;
381                         default:
382                                 {
383                                 [self setScanLocation:theScanLocation];
384                                 if (outError)
385                                         {
386                                         *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-13 userInfo:NULL];
387                                         }
388                                 return(NO);
389                                 }
390                                 break;
391                         }
392                 CFStringAppendCharacters((CFMutableStringRef)theString, &theCharacter, 1);
393 //              [theString appendFormat:@"%C", theCharacter];
394                 }
395         }
396         
397 if (outStringConstant != NULL)
398         *outStringConstant = theString;
399
400 return(YES);
401 }
402
403 - (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError
404 {
405 //[self skipJSONWhitespace];
406
407 NSNumber *theNumber = NULL;
408 if ([self scanNumber:&theNumber] == YES)
409         {
410         if (outNumberConstant != NULL)
411                 *outNumberConstant = theNumber;
412         return(YES);
413         }
414 else
415         {
416         if (outError)
417                 {
418                 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-14 userInfo:NULL];
419                 }
420         return(NO);
421         }
422 }
423
424 - (void)skipJSONWhitespace
425 {
426 [self scanCharactersFromSet:whitespaceCharacterSet intoString:NULL];
427 if (scanComments)
428         {
429         [self scanCStyleComment:NULL];
430         [self scanCPlusPlusStyleComment:NULL];
431         [self scanCharactersFromSet:whitespaceCharacterSet intoString:NULL];
432         }
433 }
434
435 @end