5 // Created by Jonathan Wight on 12/07/2005.
6 // Copyright 2005 Toxic Software. All rights reserved.
9 #import "CJSONScanner.h"
11 #import "NSCharacterSet_Extensions.h"
12 #import "CDataScanner_Extensions.h"
14 inline static int HexToInt(char inCharacter)
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']);
23 @interface CJSONScanner ()
24 @property (readwrite, retain) NSCharacterSet *notQuoteCharacters;
25 @property (readwrite, retain) NSCharacterSet *whitespaceCharacterSet;
30 @implementation CJSONScanner
32 @synthesize scanComments;
33 @synthesize notQuoteCharacters;
34 @synthesize whitespaceCharacterSet;
38 if ((self = [super init]) != nil)
40 self.notQuoteCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"\\\""] invertedSet];
41 self.whitespaceCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
48 self.notQuoteCharacters = NULL;
49 self.whitespaceCharacterSet = NULL;
56 - (void)setData:(NSData *)inData
58 NSData *theData = inData;
59 if (theData && theData.length >= 4)
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)
66 if (theChars[2] != 0 && theChars[3] == 0)
67 theEncoding = NSUTF16LittleEndianStringEncoding;
68 else if (theChars[2] == 0 && theChars[3] == 0)
69 theEncoding = NSUTF32LittleEndianStringEncoding;
71 else if (theChars[0] == 0 && theChars[2] == 0 && theChars[3] != 0)
74 theEncoding = NSUTF32BigEndianStringEncoding;
75 else if (theChars[1] != 0)
76 theEncoding = NSUTF16BigEndianStringEncoding;
79 if (theEncoding != NSUTF8StringEncoding)
81 NSString *theString = [[[NSString alloc] initWithData:theData encoding:theEncoding] autorelease];
82 theData = [theString dataUsingEncoding:NSUTF8StringEncoding];
85 [super setData:theData];
90 - (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
92 [self skipJSONWhitespace];
96 const unichar C = [self currentCharacter];
100 if ([self scanUTF8String:"true" intoString:NULL])
102 theObject = [NSNumber numberWithBool:YES];
106 if ([self scanUTF8String:"false" intoString:NULL])
108 theObject = [NSNumber numberWithBool:NO];
112 if ([self scanUTF8String:"null" intoString:NULL])
114 theObject = [NSNull null];
119 [self scanJSONStringConstant:&theObject error:outError];
132 [self scanJSONNumberConstant:&theObject error:outError];
135 [self scanJSONDictionary:&theObject error:outError];
138 [self scanJSONArray:&theObject error:outError];
145 if (outObject != NULL)
146 *outObject = theObject;
150 - (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError
152 unsigned theScanLocation = [self scanLocation];
154 //[self skipJSONWhitespace];
156 if ([self scanCharacter:'{'] == NO)
160 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-1 userInfo:NULL];
165 NSMutableDictionary *theDictionary = [NSMutableDictionary dictionary];
167 while ([self scanCharacter:'}'] == NO)
169 NSString *theKey = NULL;
170 if ([self scanJSONStringConstant:&theKey error:outError] == NO)
172 [self setScanLocation:theScanLocation];
175 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-2 userInfo:NULL];
180 [self skipJSONWhitespace];
182 if ([self scanCharacter:':'] == NO)
184 [self setScanLocation:theScanLocation];
187 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-3 userInfo:NULL];
193 if ([self scanJSONObject:&theValue error:outError] == NO)
195 [self setScanLocation:theScanLocation];
198 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-4 userInfo:NULL];
203 [theDictionary setValue:theValue forKey:theKey];
205 [self skipJSONWhitespace];
206 if ([self scanCharacter:','] == NO)
208 if ([self currentCharacter] != '}')
210 [self setScanLocation:theScanLocation];
213 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-5 userInfo:NULL];
221 [self skipJSONWhitespace];
222 if ([self currentCharacter] == '}')
227 if ([self scanCharacter:'}'] == NO)
229 [self setScanLocation:theScanLocation];
232 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-6 userInfo:NULL];
237 if (outDictionary != NULL)
238 *outDictionary = theDictionary;
242 - (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError
244 unsigned theScanLocation = [self scanLocation];
246 //[self skipJSONWhitespace];
247 if ([self scanCharacter:'['] == NO)
251 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-7 userInfo:NULL];
256 NSMutableArray *theArray = [NSMutableArray array];
258 [self skipJSONWhitespace];
259 while ([self currentCharacter] != ']')
261 NSString *theValue = NULL;
262 if ([self scanJSONObject:&theValue error:outError] == NO)
264 [self setScanLocation:theScanLocation];
267 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-8 userInfo:NULL];
272 [theArray addObject:theValue];
274 [self skipJSONWhitespace];
275 if ([self scanCharacter:','] == NO)
277 [self skipJSONWhitespace];
278 if ([self currentCharacter] != ']')
280 [self setScanLocation:theScanLocation];
283 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-9 userInfo:NULL];
290 [self skipJSONWhitespace];
293 [self skipJSONWhitespace];
295 if ([self scanCharacter:']'] == NO)
297 [self setScanLocation:theScanLocation];
300 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-10 userInfo:NULL];
305 if (outArray != NULL)
306 *outArray = theArray;
310 - (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError
312 unsigned theScanLocation = [self scanLocation];
314 [self skipJSONWhitespace]; // TODO - i want to remove this method. But breaks unit tests.
316 NSMutableString *theString = [NSMutableString string];
318 if ([self scanCharacter:'"'] == NO)
320 [self setScanLocation:theScanLocation];
323 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-11 userInfo:NULL];
328 while ([self scanCharacter:'"'] == NO)
330 NSString *theStringChunk = NULL;
331 if ([self scanCharactersFromSet:notQuoteCharacters intoString:&theStringChunk])
333 [theString appendString:theStringChunk];
336 if ([self scanCharacter:'\\'] == YES)
338 unichar theCharacter = [self scanCharacter];
339 switch (theCharacter)
365 for (theShift = 12; theShift >= 0; theShift -= 4)
367 const int theDigit = HexToInt([self scanCharacter]);
370 [self setScanLocation:theScanLocation];
373 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-12 userInfo:NULL];
377 theCharacter |= (theDigit << theShift);
383 [self setScanLocation:theScanLocation];
386 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-13 userInfo:NULL];
392 CFStringAppendCharacters((CFMutableStringRef)theString, &theCharacter, 1);
393 // [theString appendFormat:@"%C", theCharacter];
397 if (outStringConstant != NULL)
398 *outStringConstant = theString;
403 - (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError
405 //[self skipJSONWhitespace];
407 NSNumber *theNumber = NULL;
408 if ([self scanNumber:&theNumber] == YES)
410 if (outNumberConstant != NULL)
411 *outNumberConstant = theNumber;
418 *outError = [NSError errorWithDomain:@"CJSONScannerErrorDomain" code:-14 userInfo:NULL];
424 - (void)skipJSONWhitespace
426 [self scanCharactersFromSet:whitespaceCharacterSet intoString:NULL];
429 [self scanCStyleComment:NULL];
430 [self scanCPlusPlusStyleComment:NULL];
431 [self scanCharactersFromSet:whitespaceCharacterSet intoString:NULL];