Skip to content
Browse files

first commit of pubnub demo project

  • Loading branch information...
0 parents commit 20b4877e1a32002e97fd9fb2fff8a1c69f5acdb5 @phrygianlabs phrygianlabs committed Mar 23, 2011
17 .gitignore
@@ -0,0 +1,17 @@
+# xcode noise
+build/*
+*.pbxuser
+*.mode1v3
+
+*.pyc
+*~.nib/
+*.pbxuser
+*.perspective
+*.perspectivev3
+
+# old skool
+.svn
+
+# osx noise
+.DS_Store
+profile
67 CEPubnub/CEPubnub.h
@@ -0,0 +1,67 @@
+//
+// CEPubnub.h
+// PubNubLib
+//
+// Created by Chad Etzel on 3/21/11.
+// Copyright 2011 Phrygian Labs, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "JSON.h"
+#import "CEPubnubRequest.h"
+
+@interface CEPubnub : NSObject {
+ NSString* publish_key;
+ NSString* subscribe_key;
+ NSString* secret_key;
+ NSString* scheme;
+ NSString* host;
+ NSMutableDictionary* subscriptions;
+ //NSAutoreleasePool* pool;
+ SBJsonParser* parser;
+ SBJsonWriter* writer;
+}
+
+@property (nonatomic, copy) NSString *publish_key;
+@property (nonatomic, copy) NSString *subscribe_key;
+@property (nonatomic, copy) NSString *secret_key;
+@property (nonatomic, copy) NSString *scheme;
+@property (nonatomic, copy) NSString *host;
+@property (nonatomic, retain) NSMutableDictionary *subscriptions;
+@property (nonatomic, retain) SBJsonParser *parser;
+@property (nonatomic, retain) SBJsonWriter *writer;
+
+-(CEPubnub*)
+ publishKey: (NSString*) pub_key
+ subscribeKey: (NSString*) sub_key
+ secretKey: (NSString*) sec_key
+ sslOn: (BOOL) ssl_on
+ origin: (NSString*) origin;
+
+-(void)
+ publish: (NSString*) channel
+ message: (id) message
+ delegate: (id) delegate;
+
+-(void)
+ subscribe: (NSString*) channel
+ delegate: (id) delegate;
+
+-(void) subscribe: (NSDictionary*) args;
+-(BOOL) subscribed: (NSString*) channel;
+
+-(void)
+ history: (NSString*) channel
+ limit: (int) limit
+ delegate: (id) delegate;
+
+-(void) unsubscribe: (NSString*) channel;
+-(void) time: (id) delegate;
+@end
+
+@interface CEPubnubSubscribeDelegate: CEPubnubResponse @end
+@interface CEPubnubHistoryDelegate: CEPubnubResponse @end
+@interface CEPubnubPublishDelegate: CEPubnubResponse @end
+@interface CEPubnubTimeDelegate: CEPubnubResponse @end
+
388 CEPubnub/CEPubnub.m
@@ -0,0 +1,388 @@
+//
+// CEPubnub.m
+// PubNubLib
+//
+// Created by Chad Etzel on 3/21/11.
+// Copyright 2011 Phrygian Labs, Inc. All rights reserved.
+//
+
+#import "CEPubnub.h"
+
+#import <CommonCrypto/CommonDigest.h>
+
+@implementation CEPubnub
+
+@synthesize publish_key, subscribe_key, secret_key, scheme, host, subscriptions, parser, writer;
+
+-(CEPubnub *)
+publishKey: (NSString*) pub_key
+subscribeKey: (NSString*) sub_key
+secretKey: (NSString*) sec_key
+sslOn: (BOOL) ssl_on
+origin: (NSString*) origin
+{
+ //pool = [[NSAutoreleasePool alloc] init];
+ self = [super init];
+ publish_key = pub_key;
+ subscribe_key = sub_key;
+ secret_key = sec_key;
+ scheme = ssl_on ? @"https" : @"http";
+ host = origin;
+ subscriptions = [[NSMutableDictionary alloc] init];
+ parser = [SBJsonParser new];
+ writer = [SBJsonWriter new];
+
+ return self;
+}
+
++(NSString*) urlencode: (NSString*) string {
+
+ return [string
+ stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding
+ ];
+
+
+ //return [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)(string), NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8) autorelease];
+}
++(NSString*) md5: (NSString*) input {
+ const char* str = [input UTF8String];
+ unsigned char result[CC_MD5_DIGEST_LENGTH];
+ CC_MD5(str, strlen(str), result);
+
+ NSMutableString *ret = [NSMutableString stringWithCapacity:
+ CC_MD5_DIGEST_LENGTH * 2
+ ];
+ for (int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
+ [ret appendFormat:@"%02x",result[i]];
+ }
+ return ret;
+}
+
+// * /publish/pub-key/sub-key/signature/channel/callback/"msg"
+-(void)
+publish: (NSString*) channel
+message: (id) message
+delegate: (id) delegate
+{
+
+
+ NSString* message_string;
+ if ([message isKindOfClass:[NSString class]]) {
+ message_string = [NSString stringWithFormat:@"\"%@\"", message];
+ } else {
+ message_string = [writer stringWithObject: message];
+ }
+
+ NSString* signature = [CEPubnub md5: [NSString
+ stringWithFormat: @"%@/%@/%@/%@/%@",
+ publish_key,
+ subscribe_key,
+ secret_key,
+ channel,
+ message_string
+ ]];
+
+ if (1500 < [message_string length]) {
+ NSLog(@"Message Too Long (Over: 1500)");
+ return;
+ }
+
+ [[[CEPubnubRequest alloc] autorelease]
+ scheme: scheme
+ host: host
+ path: [NSString
+ stringWithFormat: @"/publish/%@/%@/%@/%@/0/%@",
+ publish_key,
+ subscribe_key,
+ signature,
+ [CEPubnub urlencode: channel],
+ [CEPubnub urlencode: message_string]
+ ]
+ callback: [[[CEPubnubPublishDelegate alloc] autorelease]
+ finished: delegate
+ pubnub: self
+ channel: channel
+ ]
+ ];
+
+}
+
+-(BOOL) subscribed: (NSString*) channel {
+ if ([subscriptions objectForKey: channel]) return YES;
+ return NO;
+}
+// * /subscribe/sub-key/channel/callback/timetoken
+-(void) subscribe: (NSDictionary*) args {
+ NSString* channel = [args objectForKey:@"channel"];
+
+ CEPubnubResponse *callback = [CEPubnubSubscribeDelegate alloc];
+ [callback
+ finished: [args objectForKey:@"delegate"]
+ pubnub: self
+ channel: channel
+ ];
+
+
+ CEPubnubRequest *req = [CEPubnubRequest alloc];
+
+ [req
+ scheme: scheme
+ host: host
+ path: [NSString
+ stringWithFormat: @"/subscribe/%@/%@/0/%@",
+ subscribe_key,
+ [CEPubnub urlencode: channel],
+ [args objectForKey:@"timetoken"]
+ ]
+ callback: callback /*[[CEPubnubSubscribeDelegate alloc]
+ finished: [args objectForKey:@"delegate"]
+ pubnub: self
+ channel: channel
+ ]*/
+ ];
+
+ [subscriptions setObject:req forKey:channel];
+ [req release];
+ [callback release];
+ //NSLog(@"req retainCount: %d", [req retainCount]);
+ //NSLog(@"callback retainCount: %d", [callback retainCount]);
+
+
+}
+
+-(void)
+subscribe: (NSString*) channel
+delegate: (id) delegate
+{
+
+
+ if ([self subscribed: channel]) {
+ NSLog( @"Already Subscribed: %@", channel );
+ return;
+ }
+
+
+
+ [self
+ performSelector: @selector(subscribe:)
+ withObject: [NSDictionary
+ dictionaryWithObjectsAndKeys:
+ channel, @"channel",
+ delegate, @"delegate",
+ @"0", @"timetoken",
+ nil]
+ ];
+}
+
+
+-(void) unsubscribe: (NSString*) channel {
+ CEPubnubRequest *req = [subscriptions objectForKey:channel];
+
+ if (req.connection) {
+ [req.connection cancel];
+ }
+
+ [subscriptions removeObjectForKey:channel];
+
+}
+
+// * /history/sub-key/channel/callback/limit
+-(void)
+history: (NSString*) channel
+limit: (int) limit
+delegate: (id) delegate
+{
+ if (limit > 100) limit = 100;
+
+ [[[CEPubnubRequest alloc] autorelease]
+ scheme: scheme
+ host: host
+ path: [NSString
+ stringWithFormat: @"/history/%@/%@/0/%i",
+ subscribe_key,
+ [CEPubnub urlencode: channel],
+ limit
+ ]
+ callback: [[[CEPubnubHistoryDelegate alloc] autorelease]
+ finished: delegate
+ pubnub: self
+ channel: channel
+ ]
+ ];
+}
+
+-(void) time: (id) delegate {
+ [[[CEPubnubRequest alloc] autorelease]
+ scheme: scheme
+ host: host
+ path: @"/time/0"
+ callback: [[[CEPubnubTimeDelegate alloc] autorelease]
+ finished: delegate
+ pubnub: self
+ ]
+ ];
+}
+
+-(void) dealloc {
+ //SLog(@"DEALLOCING PUBNUB!!!!");
+
+ [subscriptions release];
+ [parser release];
+ [writer release];
+
+ [super dealloc];
+}
+
+@end
+
+@implementation CEPubnubSubscribeDelegate
+-(void) callback: (id) response {
+
+
+
+ NSArray* response_data = [parser objectWithString: response];
+ if (![pubnub subscribed: channel]) return;
+
+
+
+ [pubnub
+ performSelector: @selector(subscribe:)
+ withObject: [NSDictionary
+ dictionaryWithObjectsAndKeys:
+ channel, @"channel",
+ delegate, @"delegate",
+ [response_data objectAtIndex:1], @"timetoken",
+ nil]
+ ];
+
+ id message;
+ NSEnumerator* messages = [[response_data objectAtIndex:0]
+ objectEnumerator
+ ];
+
+
+ while ((message = [messages nextObject])) {
+
+
+ //[delegate callback: message];
+ if (delegate) {
+
+
+ if ([message isKindOfClass:[NSDictionary class]]) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidReceiveDictionary:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidReceiveDictionary:message onChannel:channel];
+ }
+ } else if ([message isKindOfClass:[NSArray class]]) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidReceiveArray:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidReceiveArray:message onChannel:channel];
+ }
+ } else if ([message isKindOfClass:[NSString class]]) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidReceiveString:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidReceiveString:message onChannel:channel];
+ }
+ }
+
+
+ } else {
+
+ }
+ }
+
+}
+
+-(void) fail: (id) response {
+ if (![pubnub subscribed: channel]) return;
+
+ [pubnub
+ performSelector: @selector(subscribe:)
+ withObject: [NSDictionary
+ dictionaryWithObjectsAndKeys:
+ channel, @"channel",
+ delegate, @"delegate",
+ @"1", @"timetoken",
+ nil]
+ afterDelay: 1.0
+ ];
+
+ if (delegate) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidFailWithResponse:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidFailWithResponse:response onChannel:channel];
+ }
+ }
+}
+@end
+
+
+@implementation CEPubnubHistoryDelegate
+-(void) callback: (id) response {
+
+
+
+ NSArray* response_data = [parser objectWithString: response];
+
+ if (delegate) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidReceiveHistoryArray:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidReceiveHistoryArray:response_data onChannel:channel];
+ }
+ }
+
+}
+
+-(void) fail: (id) response {
+ if (![pubnub subscribed: channel]) return;
+
+ [pubnub
+ performSelector: @selector(subscribe:)
+ withObject: [NSDictionary
+ dictionaryWithObjectsAndKeys:
+ channel, @"channel",
+ delegate, @"delegate",
+ @"1", @"timetoken",
+ nil]
+ afterDelay: 1.0
+ ];
+
+ if (delegate) {
+ if ([delegate respondsToSelector:@selector(pubnub:subscriptionDidFailWithResponse:onChannel:)]) {
+ [delegate pubnub:pubnub subscriptionDidFailWithResponse:response onChannel:channel];
+ }
+ }
+}
+@end
+
+@implementation CEPubnubPublishDelegate
+
+-(void) callback: (id) response {
+
+ if (delegate) {
+
+ if ([delegate respondsToSelector:@selector(pubnub:publishDidSucceedWithResponse:onChannel:)]) {
+ [delegate pubnub:pubnub publishDidSucceedWithResponse:response onChannel:channel];
+ }
+ }
+}
+
+-(void) fail: (id) response {
+
+ if (delegate) {
+
+ if ([delegate respondsToSelector:@selector(pubnub:publishDidFailWithResponse:onChannel:)]) {
+ [delegate pubnub:pubnub publishDidFailWithResponse:response onChannel:channel];
+ }
+ }
+}
+
+@end
+
+
+@implementation CEPubnubTimeDelegate
+-(void) callback: (id) response {
+ //[delegate callback: [[parser objectWithString: response] objectAtIndex:0]];
+
+ if (delegate) {
+ if ([delegate respondsToSelector:@selector(pubnub:didReceiveTime:)]) {
+ [delegate pubnub:pubnub didReceiveTime:(NSString *)[[parser objectWithString: response] objectAtIndex:0]];
+ }
+ }
+}
+@end
30 CEPubnub/CEPubnubDelegate.h
@@ -0,0 +1,30 @@
+//
+// CEPubnubDelegate.h
+// PubNubLib
+//
+// Created by Chad Etzel on 3/21/11.
+// Copyright 2011 Phrygian Labs, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class CEPubnub;
+
+@protocol CEPubnubDelegate <NSObject>
+
+@optional
+- (void)pubnub:(CEPubnub *)pubnub subscriptionDidReceiveDictionary:(NSDictionary *)response onChannel:(NSString *)channel;
+- (void)pubnub:(CEPubnub *)pubnub subscriptionDidReceiveArray:(NSArray *)response onChannel:(NSString *)channel;
+- (void)pubnub:(CEPubnub *)pubnub subscriptionDidReceiveString:(NSString *)response onChannel:(NSString *)channel;
+- (void)pubnub:(CEPubnub *)pubnub subscriptionDidFailWithResponse:(NSString *)response onChannel:(NSString *)channel;
+
+- (void)pubnub:(CEPubnub *)pubnub subscriptionDidReceiveHistoryArray:(NSArray *)response onChannel:(NSString *)channel;
+
+- (void)pubnub:(CEPubnub *)pubnub publishDidSucceedWithResponse:(NSString *)response onChannel:(NSString *)channel;
+- (void)pubnub:(CEPubnub *)pubnub publishDidFailWithResponse:(NSString *)response onChannel:(NSString *)channel;
+
+- (void)pubnub:(CEPubnub *)pubnub didReceiveTime:(NSString *)timestamp;
+
+
+
+@end
74 CEPubnub/CEPubnubRequest.h
@@ -0,0 +1,74 @@
+//
+// CEPubnubRequest.h
+// PubNubLib
+//
+// Created by Chad Etzel on 3/21/11.
+// Copyright 2011 Phrygian Labs, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+#import "JSON.h"
+
+#import "CEPubnubDelegate.h"
+
+@class CEPubnub;
+
+@interface CEPubnubResponse: NSObject {
+ id <CEPubnubDelegate> delegate;
+ SBJsonParser* parser;
+ CEPubnub* pubnub;
+ NSString* channel;
+
+}
+
+@property (nonatomic, retain) SBJsonParser *parser;
+@property (nonatomic, retain) CEPubnub *pubnub;
+@property (nonatomic, copy) NSString *channel;
+
+
+-(CEPubnubResponse *)
+finished: (id) callback
+pubnub: (CEPubnub*) pubnub_o;
+
+-(CEPubnubResponse *)
+finished: (id) callback
+pubnub: (CEPubnub*) pubnub_o
+channel: (NSString*) channel_o;
+
+-(CEPubnubResponse *)
+pubnub: (CEPubnub*) pubnub_o
+channel: (NSString*) channel_o;
+
+-(void) callback: (id) response;
+-(void) fail: (id) response;
+@end
+
+
+
+
+
+//CEPubnubRequest
+
+@interface CEPubnubRequest: NSObject {
+ id delegate;
+
+ NSString *response;
+ NSURLConnection *connection;
+ //NSAutoreleasePool *pool;
+
+@private
+ NSMutableData *receivedData;
+}
+
+@property (nonatomic, copy) NSString *response;
+@property (nonatomic, retain) NSURLConnection *connection;
+
+-(void)
+scheme: (NSString*) scheme
+host: (NSString*) host
+path: (NSString*) path
+callback: (CEPubnubResponse *) callback;
+@end
+
195 CEPubnub/CEPubnubRequest.m
@@ -0,0 +1,195 @@
+//
+// CEPubnubRequest.m
+// PubNubLib
+//
+// Created by Chad Etzel on 3/21/11.
+// Copyright 2011 Phrygian Labs, Inc. All rights reserved.
+//
+
+#import "CEPubnubRequest.h"
+
+
+
+
+@implementation CEPubnubRequest
+
+@synthesize response, connection;
+
+-(void)
+scheme: (NSString*) scheme
+host: (NSString*) host
+path: (NSString*) path
+callback: (CEPubnubResponse*) callback;
+{
+ //pool = [[NSAutoreleasePool alloc] init];
+ response = nil;
+ receivedData = [[NSMutableData alloc] initWithLength:0];
+
+ delegate = callback;
+ [delegate retain];
+ //NSLog(@"callback retainCount: %d", [delegate retainCount]);
+
+ NSURL *url = [NSURL
+ URLWithString: [NSString
+ stringWithFormat: @"%@://%@%@",
+ scheme,
+ host,
+ path
+ ]
+ ];
+
+
+ NSMutableURLRequest *request = [NSMutableURLRequest
+ requestWithURL: url
+ cachePolicy: NSURLRequestReloadIgnoringCacheData
+ timeoutInterval: 200
+ ];
+ [request setValue:@"close" forHTTPHeaderField:@"Connection"];
+ [request setValue:@"Accept-Encoding" forHTTPHeaderField:@"gzip"];
+ connection = [[NSURLConnection alloc]
+ initWithRequest: request
+ delegate: self
+ ];
+
+
+}
+
+-(void)
+connection: (NSURLConnection*) aConnection
+didReceiveData: (NSData*) data
+{
+ [receivedData appendData:data];
+
+}
+
+-(void)
+connectionDidFinishLoading: (NSURLConnection *) aConnection
+{
+ response = [[NSString alloc]
+ initWithData: receivedData
+ encoding: NSUTF8StringEncoding
+ ];
+
+ [delegate callback: response];
+ [delegate release];
+ delegate = nil;
+
+}
+
+-(void)
+connection: (NSURLConnection*) connection
+didFailWithError: (NSError *) error
+{
+
+ [delegate fail: response];
+ [delegate release];
+ delegate = nil;
+
+}
+
+- (void) dealloc
+{
+ //NSLog(@"CALLING DEALLOC of CEPubnubRequest!!");
+ if (delegate) {
+ [delegate release];
+ }
+ if (response) {
+ [response release];
+ }
+ [receivedData release];
+ [connection release];
+ [super dealloc];
+}
+
+@end
+
+@implementation CEPubnubResponse
+
+@synthesize parser, pubnub, channel;
+
+-(CEPubnubResponse *)
+finished: (id) callback
+pubnub: (CEPubnub*) pubnub_o
+{
+ self = [super init];
+ delegate = callback;
+ parser = [SBJsonParser new];
+ [self setPubnub:pubnub_o];
+ channel = nil;
+ return self;
+}
+
+
+-(CEPubnubResponse *)
+finished: (id) callback
+pubnub: (CEPubnub*) pubnub_o
+channel: (NSString*) channel_o
+{
+ self = [super init];
+ delegate = callback;
+ parser = [SBJsonParser new];
+ //pubnub = pubnub_o;
+ [self setPubnub:pubnub_o];
+ channel = channel_o;
+ return self;
+}
+
+
+-(CEPubnubResponse *)
+pubnub: (CEPubnub*) pubnub_o
+channel: (NSString*) channel_o
+{
+ self = [super init];
+ delegate = nil;
+ parser = [SBJsonParser new];
+ //pubnub = pubnub_o;
+ [self setPubnub:pubnub_o];
+ channel = channel_o;
+ return self;
+}
+
+
+-(void) callback: (id) response {
+ //[delegate callback: [parser objectWithString: response]];
+ logit(@"x");
+ //override in subclass
+ /*
+ if (delegate) {
+ logit(@"calling delegate!");
+ [delegate pubub:pubnub subscriptionDidReceiveObject:response onChannel:channel];
+ }
+ */
+}
+
+
+-(void) fail: (id) response {
+ //[delegate fail: response];
+ logit(@"x");
+ // override in subclass
+ /*
+ if (delegate) {
+ logit(@"calling delegate fail");
+ [delegate pubnub:pubnub subscriptionDidFailWithResponse:response onChannel:channel];
+ }
+ */
+}
+
+
+-(void) dealloc
+{
+ //NSLog(@"CALLING DEALLOC!!");
+ if (parser) {
+ [parser release];
+ }
+ if (pubnub) {
+ [pubnub release];
+ pubnub = nil;
+ }
+ //[channel release];
+
+ [super dealloc];
+}
+
+@end
+
+
69 JSON/JSON.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (C) 2009-2010 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ @mainpage A strict JSON parser and generator for Objective-C
+
+ JSON (JavaScript Object Notation) is a lightweight data-interchange
+ format. This framework provides two apis for parsing and generating
+ JSON. One standard object-based and a higher level api consisting of
+ categories added to existing Objective-C classes.
+
+ This framework does its best to be as strict as possible, both in what it accepts and what it generates. For example, it does not support trailing commas in arrays or objects. Nor does it support embedded comments, or anything else not in the JSON specification. This is considered a feature.
+
+ @section Links
+
+ @li <a href="http://stig.github.com/json-framework">Project home page</a>.
+ @li Online version of the <a href="http://stig.github.com/json-framework/api">API documentation</a>.
+
+*/
+
+
+// This setting of 1 is best if you copy the source into your project.
+// The build transforms the 1 to a 0 when building the framework and static lib.
+
+#if 1
+
+#import "SBJsonParser.h"
+#import "SBJsonWriter.h"
+#import "SBJsonStreamWriter.h"
+#import "SBJsonStreamParser.h"
+#import "SBJsonStreamParserAdapter.h"
+#import "NSObject+JSON.h"
+
+#else
+
+#import <JSON/SBJsonParser.h>
+#import <JSON/SBJsonWriter.h>
+#import <JSON/SBJsonStreamParser.h>
+#import <JSON/SBJsonStreamParserAdapter.h>
+#import <JSON/SBJsonStreamWriter.h>
+#import <JSON/NSObject+JSON.h>
+
+#endif
61 JSON/NSObject+JSON.h
@@ -0,0 +1,61 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#pragma mark JSON Writing
+
+/// Adds JSON generation to NSArray
+@interface NSArray (NSArray_SBJsonWriting)
+
+/// Returns a string containing the receiver encoded in JSON.
+- (NSString *)JSONRepresentation;
+
+@end
+
+
+/// Adds JSON generation to NSArray
+@interface NSDictionary (NSDictionary_SBJsonWriting)
+
+/// Returns a string containing the receiver encoded in JSON.
+- (NSString *)JSONRepresentation;
+
+@end
+
+#pragma mark JSON Parsing
+
+/// Adds JSON parsing methods to NSString
+@interface NSString (NSString_SBJsonParsing)
+
+/// Returns the NSDictionary or NSArray represented by the receiver's JSON representation, or nil on error
+- (id)JSONValue;
+
+@end
+
+
60 JSON/NSObject+JSON.m
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "NSObject+JSON.h"
+#import "SBJsonWriter.h"
+#import "SBJsonParser.h"
+
+@implementation NSObject (NSObject_SBJsonWriting)
+
+- (NSString *)JSONRepresentation {
+ SBJsonWriter *jsonWriter = [SBJsonWriter new];
+ NSString *json = [jsonWriter stringWithObject:self];
+ if (!json)
+ NSLog(@"-JSONRepresentation failed. Error is: %@", jsonWriter.error);
+ [jsonWriter release];
+ return json;
+}
+
+@end
+
+
+
+@implementation NSString (NSString_SBJsonParsing)
+
+- (id)JSONValue {
+ SBJsonParser *jsonParser = [SBJsonParser new];
+ id repr = [jsonParser objectWithString:self];
+ if (!repr)
+ NSLog(@"-JSONValue failed. Error is: %@", jsonParser.error);
+ [jsonParser release];
+ return repr;
+}
+
+@end
113 JSON/SBJsonParser.h
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ @brief The JSON parser class.
+
+ JSON is mapped to Objective-C types in the following way:
+
+ @li Null -> NSNull
+ @li String -> NSMutableString
+ @li Array -> NSMutableArray
+ @li Object -> NSMutableDictionary
+ @li Boolean -> NSNumber (initialised with -initWithBool:)
+ @li Number -> (NSNumber | NSDecimalNumber)
+
+ Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber
+ instances. These are initialised with the -initWithBool: method, and
+ round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be
+ represented as 'true' and 'false' again.)
+
+ As an optimisation short JSON integers turn into NSNumber instances, while complex ones turn into NSDecimalNumber instances.
+ We can thus avoid any loss of precision as JSON allows ridiculously large numbers.
+
+ */
+
+@interface SBJsonParser : NSObject {
+ id value;
+ NSString *error;
+ NSUInteger depth, maxDepth;
+
+}
+
+/**
+ @brief The maximum recursing depth.
+
+ Defaults to 512. If the input is nested deeper than this the input will be deemed to be
+ malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
+ turn off this security feature by setting the maxDepth value to 0.
+ */
+@property NSUInteger maxDepth;
+
+/**
+ @brief Return an error trace, or nil if there was no errors.
+
+ Note that this method returns the trace of the last method that failed.
+ You need to check the return value of the call you're making to figure out
+ if the call actually failed, before you know call this method.
+ */
+@property(copy) NSString *error;
+
+/**
+ @brief Return the object represented by the given NSData object.
+
+ The data *must* be UTF8 encoded.
+ @param data the data to parse.
+
+ */
+- (id)objectWithData:(NSData*)data;
+
+/**
+ @brief Return the object represented by the given string
+
+ Returns the object represented by the passed-in string or nil on error. The returned object can be
+ a string, number, boolean, null, array or dictionary.
+
+ @param repr the json string to parse
+ */
+- (id)objectWithString:(NSString *)repr;
+
+/**
+ @brief Return the object represented by the given string
+
+ Returns the object represented by the passed-in string or nil on error. The returned object can be
+ a string, number, boolean, null, array or dictionary.
+
+ @param jsonText the json string to parse
+ @param error pointer to an NSError object to populate on error
+ */
+
+- (id)objectWithString:(NSString*)jsonText
+ error:(NSError**)error;
+
+@end
+
+
120 JSON/SBJsonParser.m
@@ -0,0 +1,120 @@
+/*
+ Copyright (C) 2009,2010 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SBJsonParser.h"
+#import "SBJsonStreamParser.h"
+#import "SBJsonStreamParserAdapter.h"
+
+@interface SBJsonParser () <SBJsonStreamParserAdapterDelegate>
+@end
+
+
+@implementation SBJsonParser
+
+@synthesize maxDepth;
+@synthesize error;
+
+#pragma mark SBJsonStreamParserAdapterDelegate
+
+- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray *)array {
+ value = [array retain];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary *)dict {
+ value = [dict retain];
+}
+
+- (id)init {
+ self = [super init];
+ if (self)
+ self.maxDepth = 512;
+ return self;
+}
+
+- (void)dealloc {
+ [error release];
+ [super dealloc];
+}
+
+#pragma mark Methods
+
+- (id)objectWithData:(NSData *)data {
+
+ if (!data) {
+ self.error = @"Input was 'nil'";
+ return nil;
+ }
+
+ SBJsonStreamParserAdapter *adapter = [SBJsonStreamParserAdapter new];
+ adapter.delegate = self;
+
+ SBJsonStreamParser *parser = [SBJsonStreamParser new];
+ parser.maxDepth = self.maxDepth;
+ parser.delegate = adapter;
+
+ id retval = nil;
+ switch ([parser parse:data]) {
+ case SBJsonStreamParserComplete:
+ retval = [value autorelease];
+ break;
+
+ case SBJsonStreamParserWaitingForData:
+ self.error = @"Didn't find full object before EOF";
+ break;
+
+ case SBJsonStreamParserError:
+ self.error = parser.error;
+ break;
+ }
+
+
+ [adapter release];
+ [parser release];
+
+ return retval;
+}
+
+- (id)objectWithString:(NSString *)repr {
+ return [self objectWithData:[repr dataUsingEncoding:NSUTF8StringEncoding]];
+}
+
+- (id)objectWithString:(NSString*)repr error:(NSError**)error_ {
+ id tmp = [self objectWithString:repr];
+ if (tmp)
+ return tmp;
+
+ if (error_) {
+ NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil];
+ *error_ = [NSError errorWithDomain:@"org.brautaset.json.parser.ErrorDomain" code:0 userInfo:ui];
+ }
+
+ return nil;
+}
+
+@end
136 JSON/SBJsonStreamParser.h
@@ -0,0 +1,136 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class SBJsonTokeniser;
+@class SBJsonStreamParser;
+@class SBJsonStreamParserState;
+
+typedef enum {
+ SBJsonStreamParserComplete,
+ SBJsonStreamParserWaitingForData,
+ SBJsonStreamParserError,
+} SBJsonStreamParserStatus;
+
+
+/**
+ @brief Delegate for interacting directly with the stream parser
+
+ You will most likely find it much more convenient to implement the
+ SBJsonStreamParserAdapterDelegate protocol instead.
+ */
+@protocol SBJsonStreamParserDelegate<NSObject>
+
+@optional
+/// Called when object start is found
+- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser;
+
+/// Called when object key is found
+- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key;
+
+/// Called when object end is found
+- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser;
+
+/// Called when array start is found
+- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser;
+
+/// Called when array end is found
+- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser;
+
+/// Called when a boolean value is found
+- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x;
+
+/// Called when a null value is found
+- (void)parserFoundNull:(SBJsonStreamParser*)parser;
+
+/// Called when a number is found
+- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num;
+
+/// Called when a string is found
+- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string;
+
+@end
+
+
+/**
+ @brief JSON Stream-parser class
+
+ */
+@interface SBJsonStreamParser : NSObject {
+ BOOL multi;
+ id<SBJsonStreamParserDelegate> delegate;
+ SBJsonTokeniser *tokeniser;
+ SBJsonStreamParserState **states;
+ NSUInteger depth, maxDepth;
+ NSString *error;
+}
+
+/**
+ @brief Expect multiple documents separated by whitespace
+
+ If you set this property to true the parser will never return SBJsonStreamParserComplete.
+ Once an object is completed it will expect another object to follow, separated only by whitespace.
+
+ @see The TwitterStream example project.
+ */
+@property BOOL multi;
+
+/// Set this to the object you want to receive messages
+@property (assign) id<SBJsonStreamParserDelegate> delegate;
+
+/// The current depth in the json document (each [ and { each count 1)
+@property (readonly) NSUInteger depth;
+
+/// The max depth to allow the parser to reach
+@property NSUInteger maxDepth;
+
+/// @internal
+@property (readonly) SBJsonStreamParserState **states;
+
+/// Holds the error after SBJsonStreamParserError was returned
+@property (copy) NSString *error;
+
+/**
+ @brief Parse some JSON
+
+ The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one.
+
+ @return
+ @li SBJsonStreamParserComplete if a full document was found
+ @li SBJsonStreamParserWaitingForData if a partial document was found and more data is required to complete it
+ @li SBJsonStreamParserError if an error occured. (See the error property for details in this case.)
+
+ */
+- (SBJsonStreamParserStatus)parse:(NSData*)data;
+
+@end
317 JSON/SBJsonStreamParser.m
@@ -0,0 +1,317 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SBJsonStreamParser.h"
+#import "SBJsonTokeniser.h"
+#import "SBJsonStreamParserState.h"
+
+
+@implementation SBJsonStreamParser
+
+@synthesize multi;
+@synthesize error;
+@synthesize delegate;
+@dynamic maxDepth;
+@synthesize states;
+@synthesize depth;
+
+#pragma mark Housekeeping
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ tokeniser = [SBJsonTokeniser new];
+ maxDepth = 512;
+ states = calloc(maxDepth, sizeof(SBJsonStreamParserState*));
+ NSAssert(states, @"States not initialised");
+ states[0] = [SBJsonStreamParserStateStart sharedInstance];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ self.error = nil;
+ free(states);
+ [tokeniser release];
+ [super dealloc];
+}
+
+#pragma mark Methods
+
+- (NSString*)tokenName:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_array_start:
+ return @"start of array";
+ break;
+
+ case sbjson_token_array_end:
+ return @"end of array";
+ break;
+
+ case sbjson_token_double:
+ case sbjson_token_integer:
+ return @"number";
+ break;
+
+ case sbjson_token_string:
+ case sbjson_token_string_encoded:
+ return @"string";
+ break;
+
+ case sbjson_token_true:
+ case sbjson_token_false:
+ return @"boolean";
+ break;
+
+ case sbjson_token_null:
+ return @"null";
+ break;
+
+ case sbjson_token_key_value_separator:
+ return @"key-value separator";
+ break;
+
+ case sbjson_token_separator:
+ return @"value separator";
+ break;
+
+ case sbjson_token_object_start:
+ return @"start of object";
+ break;
+
+ case sbjson_token_object_end:
+ return @"end of object";
+ break;
+
+ case sbjson_token_eof:
+ case sbjson_token_error:
+ break;
+ }
+ NSAssert(NO, @"Should not get here");
+ return @"<aaiiie!>";
+}
+
+
+- (void)handleObjectStart {
+ if (depth >= maxDepth) {
+ self.error = [NSString stringWithFormat:@"Parser exceeded max depth of %lu", maxDepth];
+ states[depth] = kSBJsonStreamParserStateError;
+
+ } else {
+ if (delegate && [delegate respondsToSelector:@selector(parserFoundObjectStart:)]) {
+ [delegate parserFoundObjectStart:self];
+ }
+ states[++depth] = kSBJsonStreamParserStateObjectStart;
+ }
+
+}
+- (void)handleArrayStart {
+ if (depth >= maxDepth) {
+ self.error = [NSString stringWithFormat:@"Parser exceeded max depth of %lu", maxDepth];
+ states[depth] = kSBJsonStreamParserStateError;
+ } else {
+
+ if (delegate && [delegate respondsToSelector:@selector(parserFoundArrayStart:)]) {
+ [delegate parserFoundArrayStart:self];
+ }
+ states[++depth] = kSBJsonStreamParserStateArrayStart;
+ }
+
+}
+
+- (void)handleNumber:(sbjson_token_t)tok {
+ const char *buf;
+ NSUInteger len;
+
+ if ([tokeniser getToken:&buf length:&len]) {
+ NSNumber *number;
+ if (tok == sbjson_token_integer && len < 12) {
+ char *e = NULL;
+ long l = strtol(buf, &e, 0);
+ NSAssert((e-buf) == len, @"unexpected length");
+ number = [NSNumber numberWithLong:l];
+
+ } else if (tok == sbjson_token_double && len < 7) {
+ char *e = NULL;
+ double d = strtod(buf, &e);
+ NSAssert((e-buf) == len, @"unexpected length");
+ number = [NSNumber numberWithDouble:d];
+
+ } else {
+ NSData *data = [NSData dataWithBytes:buf length:len];
+ NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
+ number = [[[NSDecimalNumber alloc] initWithString:string] autorelease];
+ }
+ NSParameterAssert(number);
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundNumber:)]) {
+ [delegate parser:self foundNumber:number];
+ }
+
+ }
+}
+
+- (void)handleString:(sbjson_token_t)tok {
+ const char *buf;
+ NSUInteger len;
+
+ NSString *string;
+ if (tok == sbjson_token_string) {
+ [tokeniser getToken:&buf length:&len];
+ string = [[[NSString alloc] initWithBytes:buf+1 length:len-2 encoding:NSUTF8StringEncoding] autorelease];
+ } else {
+ string = [tokeniser getDecodedStringToken];
+ }
+ NSParameterAssert(string);
+ if ([states[depth] needKey]) {
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundObjectKey:)]) {
+ [delegate parser:self foundObjectKey:string];
+ }
+ }
+ else {
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundString:)]) {
+ [delegate parser:self foundString:string];
+ }
+ }
+}
+
+- (SBJsonStreamParserStatus)parse:(NSData *)data_ {
+ [tokeniser appendData:data_];
+
+
+ for (;;) {
+ if ([states[depth] parserShouldStop:self])
+ return [states[depth] parserShouldReturn:self];
+
+ sbjson_token_t tok = [tokeniser next];
+
+ switch (tok) {
+ case sbjson_token_eof:
+ return SBJsonStreamParserWaitingForData;
+ break;
+
+ case sbjson_token_error:
+ states[depth] = kSBJsonStreamParserStateError;
+ self.error = tokeniser.error;
+ return SBJsonStreamParserError;
+ break;
+
+ default:
+
+ if (![states[depth] parser:self shouldAcceptToken:tok]) {
+ NSString *tokenName = [self tokenName:tok];
+ NSString *stateName = [states[depth] name];
+ NSLog(@"STATE: %@", states[depth]);
+ self.error = [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName];
+ states[depth] = kSBJsonStreamParserStateError;
+ return SBJsonStreamParserError;
+ }
+
+ switch (tok) {
+ case sbjson_token_object_start:
+ [self handleObjectStart];
+ break;
+
+ case sbjson_token_object_end:
+ [states[--depth] parser:self shouldTransitionTo:tok];
+ if (delegate && [delegate respondsToSelector:@selector(parserFoundObjectEnd:)]) {
+ [delegate parserFoundObjectEnd:self];
+ }
+ break;
+
+ case sbjson_token_array_start:
+ [self handleArrayStart];
+ break;
+
+ case sbjson_token_array_end:
+ [states[--depth] parser:self shouldTransitionTo:tok];
+ if (delegate && [delegate respondsToSelector:@selector(parserFoundArrayEnd:)]) {
+ [delegate parserFoundArrayEnd:self];
+ }
+ break;
+
+ case sbjson_token_separator:
+ case sbjson_token_key_value_separator:
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_true:
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundBoolean:)]) {
+ [delegate parser:self foundBoolean:YES];
+ }
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_false:
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundBoolean:)]) {
+ [delegate parser:self foundBoolean:NO];
+ }
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_null:
+ if (delegate && [delegate respondsToSelector:@selector(parserFoundNull:)]) {
+ [delegate parserFoundNull:self];
+ }
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_integer:
+ case sbjson_token_double:
+ [self handleNumber:tok];
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_string:
+ case sbjson_token_string_encoded:
+ [self handleString:tok];
+ [states[depth] parser:self shouldTransitionTo:tok];
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+ return SBJsonStreamParserComplete;
+}
+
+#pragma mark Private methods
+
+- (void)setMaxDepth:(NSUInteger)x {
+ NSAssert(x, @"maxDepth must be greater than 0");
+ maxDepth = x;
+ states = realloc(states, x);
+ NSAssert(states, @"Failed to reallocate more memory for states");
+}
+
+@end
88 JSON/SBJsonStreamParserAdapter.h
@@ -0,0 +1,88 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SBJsonStreamParser.h"
+
+typedef enum {
+ SBJsonStreamParserAdapterNone,
+ SBJsonStreamParserAdapterArray,
+ SBJsonStreamParserAdapterObject,
+} SBJsonStreamParserAdapterType;
+
+/**
+ @brief Delegate for getting objects & arrays from the stream parser adapter
+
+ You will most likely find it much more convenient to implement this
+ than the raw SBJsonStreamParserDelegate protocol.
+
+ @see The TwitterStream example project.
+ */
+@protocol SBJsonStreamParserAdapterDelegate<NSObject>
+@optional
+/// Called when a JSON array is found
+- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray*)array;
+
+/// Called when a JSON object is found
+- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary*)dict;
+
+@end
+
+
+@interface SBJsonStreamParserAdapter : NSObject <SBJsonStreamParserDelegate> {
+ id<SBJsonStreamParserAdapterDelegate> delegate;
+ NSUInteger skip, depth;
+ __weak NSMutableArray *array;
+ __weak NSMutableDictionary *dict;
+ NSMutableArray *keyStack;
+ NSMutableArray *stack;
+
+ SBJsonStreamParserAdapterType currentType;
+}
+
+/**
+ @brief How many levels to skip
+
+ This is useful for parsing HUGE JSON documents, particularly if it consists of an
+ outer array and multiple objects.
+
+ If you set this to N it will skip the outer N levels and call the -parser:foundArray:
+ or -parser:foundObject: methods for each of the inner objects, as appropriate.
+
+ @see The StreamParserIntegrationTest.m file for examples
+*/
+@property NSUInteger skip;
+
+/// Set this to the object you want to receive messages
+@property (assign) id<SBJsonStreamParserAdapterDelegate> delegate;
+
+@end
175 JSON/SBJsonStreamParserAdapter.m
@@ -0,0 +1,175 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SBJsonStreamParserAdapter.h"
+
+@interface SBJsonStreamParserAdapter ()
+
+- (void)pop;
+- (void)parser:(SBJsonStreamParser*)parser found:(id)obj;
+
+@end
+
+
+
+@implementation SBJsonStreamParserAdapter
+
+@synthesize delegate;
+@synthesize skip;
+
+#pragma mark Housekeeping
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ keyStack = [[NSMutableArray alloc] initWithCapacity:32];
+ stack = [[NSMutableArray alloc] initWithCapacity:32];
+
+ currentType = SBJsonStreamParserAdapterNone;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [keyStack release];
+ [stack release];
+ [super dealloc];
+}
+
+#pragma mark Private methods
+
+- (void)pop {
+ [stack removeLastObject];
+ array = nil;
+ dict = nil;
+ currentType = SBJsonStreamParserAdapterNone;
+
+ id value = [stack lastObject];
+
+ if ([value isKindOfClass:[NSArray class]]) {
+ array = value;
+ currentType = SBJsonStreamParserAdapterArray;
+ } else if ([value isKindOfClass:[NSDictionary class]]) {
+ dict = value;
+ currentType = SBJsonStreamParserAdapterObject;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser found:(id)obj {
+ NSParameterAssert(obj);
+
+ switch (currentType) {
+ case SBJsonStreamParserAdapterArray:
+ [array addObject:obj];
+ break;
+
+ case SBJsonStreamParserAdapterObject:
+ NSParameterAssert(keyStack.count);
+ [dict setObject:obj forKey:[keyStack lastObject]];
+ [keyStack removeLastObject];
+ break;
+
+ case SBJsonStreamParserAdapterNone:
+ if ([obj isKindOfClass:[NSArray class]]) {
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundArray:)]) {
+ [delegate parser:parser foundArray:obj];
+ }
+ } else {
+ if (delegate && [delegate respondsToSelector:@selector(parser:foundObject:)]) {
+ [delegate parser:parser foundObject:obj];
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+#pragma mark Delegate methods
+
+- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser {
+ if (++depth > skip) {
+ dict = [[NSMutableDictionary new] autorelease];
+ [stack addObject:dict];
+ currentType = SBJsonStreamParserAdapterObject;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key_ {
+ [keyStack addObject:key_];
+}
+
+- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser {
+ if (depth-- > skip) {
+ id value = [dict retain];
+ [self pop];
+ [self parser:parser found:value];
+ [value release];
+ }
+}
+
+- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser {
+ if (++depth > skip) {
+ array = [[NSMutableArray new] autorelease];
+ [stack addObject:array];
+ currentType = SBJsonStreamParserAdapterArray;
+ }
+}
+
+- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser {
+ if (depth-- > skip) {
+ id value = [array retain];
+ [self pop];
+ [self parser:parser found:value];
+ [value release];
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x {
+ [self parser:parser found:[NSNumber numberWithBool:x]];
+}
+
+- (void)parserFoundNull:(SBJsonStreamParser*)parser {
+ [self parser:parser found:[NSNull null]];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num {
+ [self parser:parser found:num];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string {
+ [self parser:parser found:string];
+}
+
+@end
89 JSON/SBJsonStreamParserState.h
@@ -0,0 +1,89 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "SBJsonTokeniser.h"
+#import "SBJsonStreamParser.h"
+
+@interface SBJsonStreamParserState : NSObject
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token;
+- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser;
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser;
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok;
+- (BOOL)needKey;
+
+- (NSString*)name;
+
+@end
+
+@interface SBJsonStreamParserStateStart : SBJsonStreamParserState
++ (id)sharedInstance;
+@end
+
+@interface SBJsonStreamParserStateComplete : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateError : SBJsonStreamParserState
+@end
+
+
+@interface SBJsonStreamParserStateObjectStart : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectGotKey : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectSeparator : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectGotValue : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectNeedKey : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayStart : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayGotValue : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayNeedValue : SBJsonStreamParserState
+@end
+
+extern SBJsonStreamParserStateStart *kSBJsonStreamParserStateStart;
+extern SBJsonStreamParserStateError *kSBJsonStreamParserStateError;
+extern SBJsonStreamParserStateObjectStart *kSBJsonStreamParserStateObjectStart;
+extern SBJsonStreamParserStateArrayStart *kSBJsonStreamParserStateArrayStart;
+
370 JSON/SBJsonStreamParserState.m
@@ -0,0 +1,370 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "SBJsonStreamParserState.h"
+#import "SBJsonStreamParser.h"
+
+SBJsonStreamParserStateStart *kSBJsonStreamParserStateStart;
+SBJsonStreamParserStateError *kSBJsonStreamParserStateError;
+static SBJsonStreamParserStateComplete *kSBJsonStreamParserStateComplete;
+
+SBJsonStreamParserStateObjectStart *kSBJsonStreamParserStateObjectStart;
+static SBJsonStreamParserStateObjectGotKey *kSBJsonStreamParserStateObjectGotKey;
+static SBJsonStreamParserStateObjectSeparator *kSBJsonStreamParserStateObjectSeparator;
+static SBJsonStreamParserStateObjectGotValue *kSBJsonStreamParserStateObjectGotValue;
+static SBJsonStreamParserStateObjectNeedKey *kSBJsonStreamParserStateObjectNeedKey;
+
+SBJsonStreamParserStateArrayStart *kSBJsonStreamParserStateArrayStart;
+static SBJsonStreamParserState *kSBJsonStreamParserStateArrayGotValue;
+static SBJsonStreamParserState *kSBJsonStreamParserStateArrayNeedValue;
+
+@implementation SBJsonStreamParserState
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return NO;
+}
+
+- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser {
+ return NO;
+}
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserWaitingForData;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {}
+
+- (BOOL)needKey {
+ return NO;
+}
+
+- (NSString*)name {
+ return @"<aaiie!>";
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateStart
+
++ (id)sharedInstance {
+ if (!kSBJsonStreamParserStateStart) {
+ kSBJsonStreamParserStateStart = [[SBJsonStreamParserStateStart alloc] init];
+ kSBJsonStreamParserStateError = [[SBJsonStreamParserStateError alloc] init];
+ kSBJsonStreamParserStateComplete = [[SBJsonStreamParserStateComplete alloc] init];
+ kSBJsonStreamParserStateObjectStart = [[SBJsonStreamParserStateObjectStart alloc] init];
+ kSBJsonStreamParserStateObjectGotKey = [[SBJsonStreamParserStateObjectGotKey alloc] init];
+ kSBJsonStreamParserStateObjectSeparator = [[SBJsonStreamParserStateObjectSeparator alloc] init];
+ kSBJsonStreamParserStateObjectGotValue = [[SBJsonStreamParserStateObjectGotValue alloc] init];
+ kSBJsonStreamParserStateObjectNeedKey = [[SBJsonStreamParserStateObjectNeedKey alloc] init];
+ kSBJsonStreamParserStateArrayStart = [[SBJsonStreamParserStateArrayStart alloc] init];
+ kSBJsonStreamParserStateArrayGotValue = [[SBJsonStreamParserStateArrayGotValue alloc] init];
+ kSBJsonStreamParserStateArrayNeedValue = [[SBJsonStreamParserStateArrayNeedValue alloc] init];
+ }
+ return kSBJsonStreamParserStateStart;
+}
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_array_start || token == sbjson_token_object_start;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+
+ SBJsonStreamParserState *state = nil;
+ switch (tok) {
+ case sbjson_token_array_start:
+ state = kSBJsonStreamParserStateArrayStart;
+ break;
+
+ case sbjson_token_object_start:
+ state = kSBJsonStreamParserStateObjectStart;
+ break;
+
+ case sbjson_token_array_end:
+ case sbjson_token_object_end:
+ if (parser.multi)
+ state = parser.states[parser.depth];
+ else
+ state = kSBJsonStreamParserStateComplete;
+ break;
+
+ case sbjson_token_eof:
+ return;
+
+ default:
+ state = kSBJsonStreamParserStateError;
+ break;
+ }
+
+
+ parser.states[parser.depth] = state;
+}
+
+- (NSString*)name { return @"before outer-most array or object"; }
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateComplete
+
+- (NSString*)name { return @"after outer-most array or object"; }
+
+- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser {
+ return YES;
+}
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserComplete;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateError
+
+- (NSString*)name { return @"in error"; }
+
+- (BOOL)parserShouldStop:(SBJsonStreamParser*)parser {
+ return YES;
+}
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserError;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectStart
+
+- (NSString*)name { return @"at beginning of object"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_string:
+ case sbjson_token_string_encoded:
+ return YES;
+ break;
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotKey;
+}
+
+- (BOOL)needKey {
+ return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectGotKey
+
+- (NSString*)name { return @"after object key"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_key_value_separator;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateObjectSeparator;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectSeparator
+
+- (NSString*)name { return @"as object value"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_start:
+ case sbjson_token_array_start:
+ case sbjson_token_true:
+ case sbjson_token_false:
+ case sbjson_token_null:
+ case sbjson_token_integer:
+ case sbjson_token_double:
+ case sbjson_token_string:
+ case sbjson_token_string_encoded:
+ return YES;
+ break;
+
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotValue;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectGotValue
+
+- (NSString*)name { return @"after object value"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_separator:
+ return YES;
+ break;
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateObjectNeedKey;
+}
+
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectNeedKey
+
+- (NSString*)name { return @"in place of object key"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_string:
+ case sbjson_token_string_encoded:
+ return YES;
+ break;
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateObjectGotKey;
+}
+
+- (BOOL)needKey {
+ return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayStart
+
+- (NSString*)name { return @"at array start"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_key_value_separator:
+ case sbjson_token_separator:
+ return NO;
+ break;
+
+ default:
+ return YES;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateArrayGotValue;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayGotValue
+
+- (NSString*)name { return @"after array value"; }
+
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_array_end || token == sbjson_token_separator;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ if (tok == sbjson_token_separator)
+ parser.states[parser.depth] = kSBJsonStreamParserStateArrayNeedValue;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayNeedValue
+
+- (NSString*)name { return @"as array value"; }
+
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_array_end:
+ case sbjson_token_key_value_separator:
+ case sbjson_token_object_end:
+ case sbjson_token_separator:
+ return NO;
+ break;
+
+ default:
+ return YES;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.states[parser.depth] = kSBJsonStreamParserStateArrayGotValue;
+}
+
+@end
+
163 JSON/SBJsonStreamWriter.h
@@ -0,0 +1,163 @@
+/*
+ Copyright (c) 2010, Stig Brautaset.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ Neither the name of the the author nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+/// Enable JSON writing for non-native objects
+@interface NSObject (SBProxyForJson)
+
+/**
+ @brief Allows generation of JSON for otherwise unsupported classes.
+
+ If you have a custom class that you want to create a JSON representation for you can implement this method in your class. It should return a representation of your object defined in terms of objects that can be translated into JSON. For example, a Person object might implement it like this:
+
+ @code
+ - (id)proxyForJson {
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ name, @"name",
+ phone, @"phone",
+ email, @"email",
+ nil];
+ }
+ @endcode
+
+ */
+- (id)proxyForJson;
+
+@end
+
+@class SBJsonStreamWriterState;
+
+/**
+ @brief The Stream Writer class.
+
+ Accepts a stream of messages and writes JSON of these to an internal data object. At any time you can retrieve the amount of data up to now, for example if you want to start sending data to a file before you're finished generating the JSON.
+
+ A range of high-, mid- and low-level methods. You can mix and match calls to these. For example, you may want to call -writeArrayOpen to start an array and then repeatedly call -writeObject: with an object.
+
+ In JSON the keys of an object must be strings. NSDictionary keys need not be, but attempting to convert an NSDictionary with non-string keys into JSON will result in an error.
+
+ NSNumber instances created with the +initWithBool: method are converted into the JSON boolean "true" and "false" values, and vice versa. Any other NSNumber instances are converted to a JSON number the way you would expect.
+
+ */
+
+@interface SBJsonStreamWriter : NSObject {
+ NSString *error;
+ SBJsonStreamWriterState **states;
+ NSMutableData *data;
+ NSUInteger depth, maxDepth;
+ BOOL sortKeys, humanReadable;
+}
+
+/**
+ @brief The data written to the stream so far.
+
+ This is a mutable object. This means that you can write a chunk of its
+ contents to an NSOutputStream, then chop as many bytes as you wrote off
+ the beginning of the buffer.
+ */
+@property(readonly) NSMutableData *data;
+
+@property(readonly) NSObject **states;
+@property(readonly) NSUInteger depth;