diff --git a/LICENSE b/LICENSE index 378fb99..99b3f43 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -This source tree contains a mixture of original material and packages of -other people's work. These packages carry their own licenses, and the -conditions in this file do not apply to these packages. +This project contains a mixture of original material and source code +packages of other people's work. These packages carry their own licenses, +and the conditions in this file do not apply to these packages. Copyright (c) 2010 Mike Clark, The Pragmatic Studio diff --git a/code/2-json/.gitignore b/code/2-json/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/2-json/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/2-json/Classes/Goal.h b/code/2-json/Classes/Goal.h new file mode 100644 index 0000000..c3471c5 --- /dev/null +++ b/code/2-json/Classes/Goal.h @@ -0,0 +1,20 @@ +@interface Goal : NSObject { + NSString *name; + NSString *amount; + NSString *goalId; + NSDate *createdAt; + NSDate *updatedAt; +} + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, retain) NSDate *createdAt; +@property (nonatomic, retain) NSDate *updatedAt; + +- (id)initWithDictionary:(NSDictionary *)dictionary; + ++ (NSArray *)findAllRemote; + +@end + diff --git a/code/2-json/Classes/Goal.m b/code/2-json/Classes/Goal.m new file mode 100644 index 0000000..5d84e9d --- /dev/null +++ b/code/2-json/Classes/Goal.m @@ -0,0 +1,58 @@ +#import "Goal.h" +#import "SBJSON.h" + +@implementation Goal + +@synthesize name; +@synthesize amount; +@synthesize goalId; +@synthesize createdAt; +@synthesize updatedAt; + +- (void)dealloc { + [name release]; + [amount release]; + [goalId release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +- (id)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.name = [dictionary valueForKey:@"name"]; + self.amount = [NSString stringWithFormat:@"%@", + [dictionary valueForKey:@"amount"]]; + self.goalId = [dictionary valueForKey:@"id"]; + self.createdAt = [dictionary valueForKey:@"created_at"]; + self.updatedAt = [dictionary valueForKey:@"updated_at"]; + } + return self; +} + ++ (NSArray *)findAllRemote { + NSURL *url = [NSURL URLWithString:@"http://localhost:3000/goals.json"]; + + NSError *error = nil; + + NSString *jsonString = + [NSString stringWithContentsOfURL:url + encoding:NSUTF8StringEncoding + error:&error]; + + NSMutableArray *goals = [NSMutableArray array]; + if (jsonString) { + SBJSON *json = [[SBJSON alloc] init]; + NSArray *results = [json objectWithString:jsonString error:&error]; + [json release]; + + for (NSDictionary *dictionary in results) { + Goal *goal = [[Goal alloc] initWithDictionary:dictionary]; + [goals addObject:goal]; + [goal release]; + } + } + return goals; +} + +@end diff --git a/code/2-json/Classes/GoalsViewController.h b/code/2-json/Classes/GoalsViewController.h new file mode 100644 index 0000000..9b37bcc --- /dev/null +++ b/code/2-json/Classes/GoalsViewController.h @@ -0,0 +1,9 @@ +#import + +@interface GoalsViewController : UITableViewController { + NSMutableArray *goals; +} + +@property (nonatomic, retain) NSArray *goals; + +@end diff --git a/code/2-json/Classes/GoalsViewController.m b/code/2-json/Classes/GoalsViewController.m new file mode 100644 index 0000000..225dd4d --- /dev/null +++ b/code/2-json/Classes/GoalsViewController.m @@ -0,0 +1,88 @@ +#import "GoalsViewController.h" + +#import "Goal.h" + +@implementation GoalsViewController + +@synthesize goals; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + self.goals = [Goal findAllRemote]; + [self.tableView reloadData]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh + target:self + action:@selector(refresh)]; + self.navigationItem.rightBarButtonItem = refreshButton; + [refreshButton release]; + + [self refresh]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CellIdentifier] autorelease]; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = goal.amount; + + return cell; +} + +- (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView beginUpdates]; + if (editingStyle == UITableViewCellEditingStyleDelete) { + [goals removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationFade]; + } + [tableView endUpdates]; +} + +@end + diff --git a/code/2-json/Classes/SaveUpAppDelegate.h b/code/2-json/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..a62ade0 --- /dev/null +++ b/code/2-json/Classes/SaveUpAppDelegate.h @@ -0,0 +1,12 @@ +#import + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; + +@end + diff --git a/code/2-json/Classes/SaveUpAppDelegate.m b/code/2-json/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..7bffccf --- /dev/null +++ b/code/2-json/Classes/SaveUpAppDelegate.m @@ -0,0 +1,33 @@ +#import "SaveUpAppDelegate.h" + +#import "GoalsViewController.h" + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Save data if appropriate +} + +@end + diff --git a/code/2-json/GoalsViewController.xib b/code/2-json/GoalsViewController.xib new file mode 100644 index 0000000..f2ba22e --- /dev/null +++ b/code/2-json/GoalsViewController.xib @@ -0,0 +1,382 @@ + + + + 784 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {320, 247} + + 3 + MQA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + + YES + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{144, 609}, {320, 247}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 5 + + + + YES + + GoalsViewController + UITableViewController + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/2-json/MainWindow.xib b/code/2-json/MainWindow.xib new file mode 100644 index 0000000..f0b4143 --- /dev/null +++ b/code/2-json/MainWindow.xib @@ -0,0 +1,535 @@ + + + + 800 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + + + YES + + + IBCocoaTouchFramework + + + GoalsViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{673, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{186, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + GoalsViewController + UITableViewController + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UITableViewController + + IBUserSource + + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/2-json/README.md b/code/2-json/README.md new file mode 100644 index 0000000..1025639 --- /dev/null +++ b/code/2-json/README.md @@ -0,0 +1,14 @@ +Save Up iPhone App +================== + +This version populates the table view using a synchronous request and the +json-framework library to fetch remote goals from a scaffold-generated Rails app. + +Quickstart +---------- + + $ rails saveup + $ cd saveup + $ rails g scaffold goal name:string amount:decimal + $ rake db:migrate + $ rails s diff --git a/code/2-json/SaveUp-Info.plist b/code/2-json/SaveUp-Info.plist new file mode 100644 index 0000000..660b9ed --- /dev/null +++ b/code/2-json/SaveUp-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/2-json/SaveUp.xcodeproj/project.pbxproj b/code/2-json/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..7341527 --- /dev/null +++ b/code/2-json/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,326 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 273459F911936B5B005C8C5F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 273459F811936B5B005C8C5F /* icon.png */; }; + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */; }; + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */; }; + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CC11937585006153B1 /* SBJSON.m */; }; + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CE11937585006153B1 /* SBJsonBase.m */; }; + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D011937585006153B1 /* SBJsonParser.m */; }; + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D211937585006153B1 /* SBJsonWriter.m */; }; + 27AAD7E01193766E006153B1 /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7DF1193766E006153B1 /* Goal.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* GoalsViewController.m */; }; + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* GoalsViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 273459F811936B5B005C8C5F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 27AAD7C611937585006153B1 /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 27AAD7CB11937585006153B1 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 27AAD7CC11937585006153B1 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 27AAD7CD11937585006153B1 /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 27AAD7CE11937585006153B1 /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 27AAD7CF11937585006153B1 /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 27AAD7D011937585006153B1 /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 27AAD7D111937585006153B1 /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 27AAD7D211937585006153B1 /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 27AAD7DE1193766E006153B1 /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 27AAD7DF1193766E006153B1 /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 28F335F01007B36200424DE2 /* GoalsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsViewController.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SaveUp-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */, + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + 27AAD7DE1193766E006153B1 /* Goal.h */, + 27AAD7DF1193766E006153B1 /* Goal.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 27AAD7C011937571006153B1 /* Vendor */ = { + isa = PBXGroup; + children = ( + 27AAD7C511937585006153B1 /* json-framework */, + ); + name = Vendor; + sourceTree = ""; + }; + 27AAD7C511937585006153B1 /* json-framework */ = { + isa = PBXGroup; + children = ( + 27AAD7C611937585006153B1 /* JSON.h */, + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */, + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */, + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */, + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */, + 27AAD7CB11937585006153B1 /* SBJSON.h */, + 27AAD7CC11937585006153B1 /* SBJSON.m */, + 27AAD7CD11937585006153B1 /* SBJsonBase.h */, + 27AAD7CE11937585006153B1 /* SBJsonBase.m */, + 27AAD7CF11937585006153B1 /* SBJsonParser.h */, + 27AAD7D011937585006153B1 /* SBJsonParser.m */, + 27AAD7D111937585006153B1 /* SBJsonWriter.h */, + 27AAD7D211937585006153B1 /* SBJsonWriter.m */, + ); + path = "json-framework"; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 27AAD7C011937571006153B1 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 273459F811936B5B005C8C5F /* icon.png */, + 28F335F01007B36200424DE2 /* GoalsViewController.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = SaveUp; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */, + 273459F911936B5B005C8C5F /* icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */, + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */, + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */, + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */, + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */, + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */, + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */, + 27AAD7E01193766E006153B1 /* Goal.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/2-json/SaveUp_Prefix.pch b/code/2-json/SaveUp_Prefix.pch new file mode 100644 index 0000000..a154808 --- /dev/null +++ b/code/2-json/SaveUp_Prefix.pch @@ -0,0 +1,14 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/code/2-json/icon.png b/code/2-json/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/2-json/icon.png differ diff --git a/code/2-json/json-framework/JSON.h b/code/2-json/json-framework/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/code/2-json/json-framework/JSON.h @@ -0,0 +1,50 @@ +/* + 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. + */ + +/** + @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. + + Learn more on the http://code.google.com/p/json-framework project site. + + 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. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/code/2-json/json-framework/NSObject+SBJSON.h b/code/2-json/json-framework/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/code/2-json/json-framework/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + 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 + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/2-json/json-framework/NSObject+SBJSON.m b/code/2-json/json-framework/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/code/2-json/json-framework/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + 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+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/code/2-json/json-framework/NSString+SBJSON.h b/code/2-json/json-framework/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/code/2-json/json-framework/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + 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 + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/code/2-json/json-framework/NSString+SBJSON.m b/code/2-json/json-framework/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/code/2-json/json-framework/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-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 "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/code/2-json/json-framework/SBJSON.h b/code/2-json/json-framework/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/code/2-json/json-framework/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-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 +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/code/2-json/json-framework/SBJSON.m b/code/2-json/json-framework/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/code/2-json/json-framework/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-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 "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + 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 value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/code/2-json/json-framework/SBJsonBase.h b/code/2-json/json-framework/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/code/2-json/json-framework/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + 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 + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + 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,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/code/2-json/json-framework/SBJsonBase.m b/code/2-json/json-framework/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/code/2-json/json-framework/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + 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 "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/code/2-json/json-framework/SBJsonParser.h b/code/2-json/json-framework/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/code/2-json/json-framework/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @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; + +@end + + +/** + @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 -> 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.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/code/2-json/json-framework/SBJsonParser.m b/code/2-json/json-framework/SBJsonParser.m new file mode 100644 index 0000000..eda051a --- /dev/null +++ b/code/2-json/json-framework/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + 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 "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/code/2-json/json-framework/SBJsonWriter.h b/code/2-json/json-framework/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/code/2-json/json-framework/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + 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 throw an exception. + + 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 SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @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)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/code/2-json/json-framework/SBJsonWriter.m b/code/2-json/json-framework/SBJsonWriter.m new file mode 100644 index 0000000..0f32904 --- /dev/null +++ b/code/2-json/json-framework/SBJsonWriter.m @@ -0,0 +1,237 @@ +/* + 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 "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/code/2-json/main.m b/code/2-json/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/2-json/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/3-scaffold/.gitignore b/code/3-scaffold/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/3-scaffold/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/3-scaffold/Classes/AppHelpers.h b/code/3-scaffold/Classes/AppHelpers.h new file mode 100644 index 0000000..2137364 --- /dev/null +++ b/code/3-scaffold/Classes/AppHelpers.h @@ -0,0 +1,8 @@ +#define TABLE_BACKGROUND_COLOR [UIColor colorWithRed:0.951 green:0.951 blue:0.951 alpha:1.000] + +void showAlert(NSString *message); + +NSString *formatDate(NSDate *date); +NSDate *parseDateTime(NSString *dateTimeString); + +NSString* numberToCurrency(NSString *number); \ No newline at end of file diff --git a/code/3-scaffold/Classes/AppHelpers.m b/code/3-scaffold/Classes/AppHelpers.m new file mode 100644 index 0000000..0765641 --- /dev/null +++ b/code/3-scaffold/Classes/AppHelpers.m @@ -0,0 +1,48 @@ +#import "AppHelpers.h" + +void showAlert(NSString *message) { + UIAlertView *alert = + [[UIAlertView alloc] initWithTitle:@"Whoops" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + +NSString* formatDate(NSDate *date) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + [formatter setTimeStyle:NSDateFormatterMediumStyle]; + NSString *result = [formatter stringFromDate:date]; + [formatter release]; + return result; +} + +NSDate* parseDateTime(NSString *dateTimeString) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + NSDate *result = [formatter dateFromString:dateTimeString]; + [formatter release]; + return result; +} + +NSString * numberToCurrency(NSString *number) { + if (number == nil) { + return @"$0.00"; + } + + NSDecimalNumber *decimalNumber = + [NSDecimalNumber decimalNumberWithString:number]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:decimalNumber]; + + [formatter release]; + return result; +} \ No newline at end of file diff --git a/code/3-scaffold/Classes/Goal.h b/code/3-scaffold/Classes/Goal.h new file mode 100644 index 0000000..4183150 --- /dev/null +++ b/code/3-scaffold/Classes/Goal.h @@ -0,0 +1,19 @@ +@interface Goal : NSObject { + NSString *name; + NSString *amount; + NSString *goalId; + NSDate *createdAt; + NSDate *updatedAt; +} + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, retain) NSDate *createdAt; +@property (nonatomic, retain) NSDate *updatedAt; + +- (id)initWithDictionary:(NSDictionary *)dictionary; + ++ (NSArray *)findAllRemote; + +@end diff --git a/code/3-scaffold/Classes/Goal.m b/code/3-scaffold/Classes/Goal.m new file mode 100644 index 0000000..b3f2cb3 --- /dev/null +++ b/code/3-scaffold/Classes/Goal.m @@ -0,0 +1,71 @@ +#import "Goal.h" + +#import "SBJSON.h" +#import "Resource.h" + +@implementation Goal + +static NSString *siteURL = @"http://localhost:3000"; + +@synthesize name; +@synthesize amount; +@synthesize goalId; +@synthesize createdAt; +@synthesize updatedAt; + +- (void)dealloc { + [name release]; + [amount release]; + [goalId release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +- (id)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.name = [dictionary valueForKey:@"name"]; + self.amount = [NSString stringWithFormat:@"%@", [dictionary valueForKey:@"amount"]]; + self.goalId = [dictionary valueForKey:@"id"]; + self.createdAt = parseDateTime([dictionary valueForKey:@"created_at"]); + self.updatedAt = parseDateTime([dictionary valueForKey:@"updated_at"]); + } + return self; +} + ++ (NSArray *)findAllRemote { + NSString *url = + [NSString stringWithFormat:@"%@/goals.json", siteURL]; + + NSError *error = nil; + + NSString *jsonString = [Resource get:url]; + + NSMutableArray *goals = [NSMutableArray array]; + if (jsonString) { + SBJSON *json = [[SBJSON alloc] init]; + NSArray *results = [json objectWithString:jsonString error:&error]; + [json release]; + + for (NSDictionary *dictionary in results) { + Goal *goal = [[Goal alloc] initWithDictionary:dictionary]; + [goals addObject:goal]; + [goal release]; + } + } + return goals; +} + +- (NSString *)params { + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + [attributes setValue:self.name forKey:@"name"]; + [attributes setValue:self.amount forKey:@"amount"]; + + NSMutableDictionary *params = + [NSMutableDictionary dictionaryWithObject:attributes forKey:@"goal"]; + + SBJSON *json = [[[SBJSON alloc] init] autorelease]; + return [json stringWithObject:params error:nil]; +} + +@end diff --git a/code/3-scaffold/Classes/GoalAddViewController.h b/code/3-scaffold/Classes/GoalAddViewController.h new file mode 100644 index 0000000..778845f --- /dev/null +++ b/code/3-scaffold/Classes/GoalAddViewController.h @@ -0,0 +1,26 @@ +#import + +@class Goal; + +@protocol GoalChangeDelegate +- (void)didChangeGoal:(Goal *)goal; +@end + +@interface GoalAddViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)cancel; +- (IBAction)save; + +@end diff --git a/code/3-scaffold/Classes/GoalAddViewController.m b/code/3-scaffold/Classes/GoalAddViewController.m new file mode 100644 index 0000000..4fcb970 --- /dev/null +++ b/code/3-scaffold/Classes/GoalAddViewController.m @@ -0,0 +1,176 @@ +#import "GoalAddViewController.h" + +#import "Goal.h" + +@interface GoalAddViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newCancelButton; +- (UIBarButtonItem *)newSaveButton; +- (UITextField *)newTextField; +@end + +@implementation GoalAddViewController + +@synthesize goal; +@synthesize nameField; +@synthesize amountField; +@synthesize delegate; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [nameField release]; + [amountField release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newTextField]; + nameField.keyboardType = UIKeyboardTypeASCIICapable; + [nameField becomeFirstResponder]; + + amountField = [self newTextField]; + amountField.keyboardType = UIKeyboardTypeNumberPad; + + if (goal.goalId) { + nameField.text = goal.name; + amountField.text = goal.amount; + } else { + nameField.placeholder = @"Name"; + amountField.placeholder = @"Amount"; + } + + UIBarButtonItem *cancelButton = [self newCancelButton]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [self newSaveButton]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (goal.goalId) { + self.title = @"Edit Goal"; + } else { + self.title = @"Add Goal"; + } +} + +#pragma mark - +#pragma mark Actions + +-(IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +-(IBAction)save { + goal.name = nameField.text; + goal.amount = amountField.text; + // TODO: create or update remote goal + [self.delegate didChangeGoal:goal]; + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newCancelButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Cancel" + style:UIBarButtonSystemItemCancel + target:self + action:@selector(cancel)]; +} + +- (UIBarButtonItem *)newSaveButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Save" + style:UIBarButtonSystemItemSave + target:self + action:@selector(save)]; +} + +- (UITextField *)newTextField { + UITextField *textField = + [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 285, 25)]; + textField.font = [UIFont systemFontOfSize:16]; + textField.delegate = self; + textField.returnKeyType = UIReturnKeyDone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + [textField addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return textField; +} + +@end diff --git a/code/3-scaffold/Classes/GoalDetailViewController.h b/code/3-scaffold/Classes/GoalDetailViewController.h new file mode 100644 index 0000000..06c2fd1 --- /dev/null +++ b/code/3-scaffold/Classes/GoalDetailViewController.h @@ -0,0 +1,19 @@ +#import + +#import "GoalAddViewController.h" + +@class Goal; + +@interface GoalDetailViewController : UITableViewController { + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)edit; + +@end diff --git a/code/3-scaffold/Classes/GoalDetailViewController.m b/code/3-scaffold/Classes/GoalDetailViewController.m new file mode 100644 index 0000000..0ddfd25 --- /dev/null +++ b/code/3-scaffold/Classes/GoalDetailViewController.m @@ -0,0 +1,138 @@ +#import "GoalDetailViewController.h" + +#import "Goal.h" + +@interface GoalDetailViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newEditButton; +@end + +@implementation GoalDetailViewController + +@synthesize goal; +@synthesize delegate; + +enum ExpenseTableSections { + kNameSection = 0, + kAmountSection, + kDateSection +}; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + UIBarButtonItem *editButton = [self newEditButton]; + self.navigationItem.rightBarButtonItem = editButton; + [editButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + self.title = goal.name; + [self.tableView reloadData]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)edit { + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self.delegate]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 3; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 1; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForHeaderInSection:(NSInteger)section { + NSString *title = nil; + switch (section) { + case kNameSection: + title = @"Name"; + break; + case kAmountSection: + title = @"Amount"; + break; + case kDateSection: + title = @"Date"; + break; + } + return title; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CellIdentifier] autorelease]; + } + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case kNameSection: + cell.textLabel.text = goal.name; + break; + case kAmountSection: + cell.textLabel.text = numberToCurrency(goal.amount); + break; + case kDateSection: + cell.textLabel.text = formatDate(goal.createdAt); + break; + } +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newEditButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemEdit + target:self + action:@selector(edit)]; +} + +@end diff --git a/code/3-scaffold/Classes/GoalsViewController.h b/code/3-scaffold/Classes/GoalsViewController.h new file mode 100644 index 0000000..5d015ad --- /dev/null +++ b/code/3-scaffold/Classes/GoalsViewController.h @@ -0,0 +1,11 @@ +#import "GoalAddViewController.h" + +@interface GoalsViewController : UITableViewController { + NSMutableArray *goals; +} + +@property (nonatomic, retain) NSArray *goals; + +- (IBAction)add; + +@end diff --git a/code/3-scaffold/Classes/GoalsViewController.m b/code/3-scaffold/Classes/GoalsViewController.m new file mode 100644 index 0000000..586b2db --- /dev/null +++ b/code/3-scaffold/Classes/GoalsViewController.m @@ -0,0 +1,135 @@ +#import "GoalsViewController.h" + +#import "Goal.h" +#import "GoalDetailViewController.h" + +@interface GoalsViewController () +- (UIBarButtonItem *)newAddButton; +@end + +@implementation GoalsViewController + +@synthesize goals; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + self.goals = [Goal findAllRemote]; + [self.tableView reloadData]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [self newAddButton]; + self.navigationItem.rightBarButtonItem = addButton; + [addButton release]; + + [self refresh]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)add { + Goal *goal = [[Goal alloc] init]; + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self]; + [goal release]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CellIdentifier] autorelease]; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = numberToCurrency(goal.amount); + + return cell; +} + +- (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView beginUpdates]; + if (editingStyle == UITableViewCellEditingStyleDelete) { + Goal *goal = [goals objectAtIndex:indexPath.row]; + // TODO: destroy remote goal + [goals removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationFade]; + } + [tableView endUpdates]; +} + +#pragma mark - +#pragma mark Table view delegate + +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + GoalDetailViewController *controller = + [[GoalDetailViewController alloc] initWithGoal:goal andDelegate:self]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Goal lifecycle callbacks + +- (void)didChangeGoal:(Goal *)goal { + [self refresh]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newAddButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(add)]; +} + +@end + diff --git a/code/3-scaffold/Classes/Resource.h b/code/3-scaffold/Classes/Resource.h new file mode 100644 index 0000000..194a5ec --- /dev/null +++ b/code/3-scaffold/Classes/Resource.h @@ -0,0 +1,11 @@ +#import + +@interface Resource : NSObject { +} + ++ (NSString *)get:(NSString *)url; ++ (NSString *)post:(NSString *)body to:(NSString *)url; ++ (NSString *)put:(NSString *)body to:(NSString *)url; ++ (NSString *)delete:(NSString *)url; + +@end diff --git a/code/3-scaffold/Classes/Resource.m b/code/3-scaffold/Classes/Resource.m new file mode 100644 index 0000000..bf3845d --- /dev/null +++ b/code/3-scaffold/Classes/Resource.m @@ -0,0 +1,71 @@ +// +// This class uses synchronous networking, which generally isn't +// a good idea because it blocks the UI. This class is used in this +// version of the application merely as a quick and dirty example, +// and also as a reminder what not to do! See subsequent application +// versions for asynchronous networking approaches. +// + +#import "Resource.h" + +@interface Resource () ++ (NSString *)sendBy:(NSString *)method to:(NSString *)url withBody:(NSString *)body; ++ (NSString *)sendRequest:(NSMutableURLRequest *)request; +@end + +@implementation Resource + ++ (NSString *)get:(NSString *)url { + return [self sendBy:@"GET" to:url withBody:nil]; +} + ++ (NSString *)post:(NSString *)body to:(NSString *)url { + return [self sendBy:@"POST" to:url withBody:body]; +} + ++ (NSString *)put:(NSString *)body to:(NSString *)url { + return [self sendBy:@"PUT" to:url withBody:body]; +} + ++ (NSString *)delete:(NSString *)url { + return [self sendBy:@"DELETE" to:url withBody:nil]; +} + +#pragma mark - +#pragma mark Private + ++ (NSString *)sendBy:(NSString *)method to:(NSString *)url withBody:(NSString *)body { + NSMutableURLRequest *request = + [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; + [request setHTTPMethod:method]; + if (body) { + [request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + } + return [self sendRequest:request]; +} + ++ (NSString *)sendRequest:(NSMutableURLRequest *)request { + NSHTTPURLResponse *response; + NSError *error; + + NSData *responseData = + [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + + NSString *responseString = + [[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding]; + [responseString autorelease]; + + NSLog(@"%@: %@", [request HTTPMethod], [request URL]); + NSLog(@"Response Code: %d", [response statusCode]); + NSLog(@"Content-Type: %@", [[response allHeaderFields] + objectForKey:@"Content-Type"]); + NSLog(@"Response: %@\n\n", responseString); + + return responseString; +} + +@end diff --git a/code/3-scaffold/Classes/SaveUpAppDelegate.h b/code/3-scaffold/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..a62ade0 --- /dev/null +++ b/code/3-scaffold/Classes/SaveUpAppDelegate.h @@ -0,0 +1,12 @@ +#import + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; + +@end + diff --git a/code/3-scaffold/Classes/SaveUpAppDelegate.m b/code/3-scaffold/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..7bffccf --- /dev/null +++ b/code/3-scaffold/Classes/SaveUpAppDelegate.m @@ -0,0 +1,33 @@ +#import "SaveUpAppDelegate.h" + +#import "GoalsViewController.h" + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Save data if appropriate +} + +@end + diff --git a/code/3-scaffold/GoalsViewController.xib b/code/3-scaffold/GoalsViewController.xib new file mode 100644 index 0000000..f2ba22e --- /dev/null +++ b/code/3-scaffold/GoalsViewController.xib @@ -0,0 +1,382 @@ + + + + 784 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {320, 247} + + 3 + MQA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + + YES + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{144, 609}, {320, 247}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 5 + + + + YES + + GoalsViewController + UITableViewController + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/3-scaffold/MainWindow.xib b/code/3-scaffold/MainWindow.xib new file mode 100644 index 0000000..bd15e7f --- /dev/null +++ b/code/3-scaffold/MainWindow.xib @@ -0,0 +1,556 @@ + + + + 800 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + 1 + + + YES + + + + IBCocoaTouchFramework + + + GoalsViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{673, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{186, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + GoalsViewController + UITableViewController + + add + id + + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UITableViewController + + IBUserSource + + + + + NSObject + + IBProjectSource + json-framework/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + json-framework/SBJsonWriter.h + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/3-scaffold/README.md b/code/3-scaffold/README.md new file mode 100644 index 0000000..70a8a4e --- /dev/null +++ b/code/3-scaffold/README.md @@ -0,0 +1,5 @@ +Save Up iPhone App +================== + +This version is the starting point for using a custom Resource class +to CRUD remote goals (synchronous). \ No newline at end of file diff --git a/code/3-scaffold/SaveUp-Info.plist b/code/3-scaffold/SaveUp-Info.plist new file mode 100644 index 0000000..660b9ed --- /dev/null +++ b/code/3-scaffold/SaveUp-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/3-scaffold/SaveUp.xcodeproj/project.pbxproj b/code/3-scaffold/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..46e14f9 --- /dev/null +++ b/code/3-scaffold/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,350 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 273459F911936B5B005C8C5F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 273459F811936B5B005C8C5F /* icon.png */; }; + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */; }; + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */; }; + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CC11937585006153B1 /* SBJSON.m */; }; + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CE11937585006153B1 /* SBJsonBase.m */; }; + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D011937585006153B1 /* SBJsonParser.m */; }; + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D211937585006153B1 /* SBJsonWriter.m */; }; + 27AAD7E01193766E006153B1 /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7DF1193766E006153B1 /* Goal.m */; }; + 27AC9D9C11A30E1800F242C8 /* Resource.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AC9D9B11A30E1800F242C8 /* Resource.m */; }; + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */; }; + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */; }; + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* GoalsViewController.m */; }; + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* GoalsViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 273459F811936B5B005C8C5F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 27AAD7C611937585006153B1 /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 27AAD7CB11937585006153B1 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 27AAD7CC11937585006153B1 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 27AAD7CD11937585006153B1 /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 27AAD7CE11937585006153B1 /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 27AAD7CF11937585006153B1 /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 27AAD7D011937585006153B1 /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 27AAD7D111937585006153B1 /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 27AAD7D211937585006153B1 /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 27AAD7DE1193766E006153B1 /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 27AAD7DF1193766E006153B1 /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 27AC9D9A11A30E1800F242C8 /* Resource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Resource.h; sourceTree = ""; }; + 27AC9D9B11A30E1800F242C8 /* Resource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Resource.m; sourceTree = ""; }; + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHelpers.h; sourceTree = ""; }; + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHelpers.m; sourceTree = ""; }; + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalAddViewController.h; sourceTree = ""; }; + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalAddViewController.m; sourceTree = ""; }; + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalDetailViewController.h; sourceTree = ""; }; + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalDetailViewController.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 28F335F01007B36200424DE2 /* GoalsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsViewController.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SaveUp-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 27AC9D9A11A30E1800F242C8 /* Resource.h */, + 27AC9D9B11A30E1800F242C8 /* Resource.m */, + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */, + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */, + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */, + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */, + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */, + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */, + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */, + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + 27AAD7DE1193766E006153B1 /* Goal.h */, + 27AAD7DF1193766E006153B1 /* Goal.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 27AAD7C011937571006153B1 /* Vendor */ = { + isa = PBXGroup; + children = ( + 27AAD7C511937585006153B1 /* json-framework */, + ); + name = Vendor; + sourceTree = ""; + }; + 27AAD7C511937585006153B1 /* json-framework */ = { + isa = PBXGroup; + children = ( + 27AAD7C611937585006153B1 /* JSON.h */, + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */, + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */, + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */, + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */, + 27AAD7CB11937585006153B1 /* SBJSON.h */, + 27AAD7CC11937585006153B1 /* SBJSON.m */, + 27AAD7CD11937585006153B1 /* SBJsonBase.h */, + 27AAD7CE11937585006153B1 /* SBJsonBase.m */, + 27AAD7CF11937585006153B1 /* SBJsonParser.h */, + 27AAD7D011937585006153B1 /* SBJsonParser.m */, + 27AAD7D111937585006153B1 /* SBJsonWriter.h */, + 27AAD7D211937585006153B1 /* SBJsonWriter.m */, + ); + path = "json-framework"; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 27AAD7C011937571006153B1 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 273459F811936B5B005C8C5F /* icon.png */, + 28F335F01007B36200424DE2 /* GoalsViewController.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = SaveUp; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */, + 273459F911936B5B005C8C5F /* icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */, + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */, + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */, + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */, + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */, + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */, + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */, + 27AAD7E01193766E006153B1 /* Goal.m in Sources */, + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */, + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */, + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */, + 27AC9D9C11A30E1800F242C8 /* Resource.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/3-scaffold/SaveUp_Prefix.pch b/code/3-scaffold/SaveUp_Prefix.pch new file mode 100644 index 0000000..cde934a --- /dev/null +++ b/code/3-scaffold/SaveUp_Prefix.pch @@ -0,0 +1,15 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + + +#ifdef __OBJC__ + #import + #import + #import "AppHelpers.h" +#endif diff --git a/code/3-scaffold/icon.png b/code/3-scaffold/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/3-scaffold/icon.png differ diff --git a/code/3-scaffold/json-framework/JSON.h b/code/3-scaffold/json-framework/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/code/3-scaffold/json-framework/JSON.h @@ -0,0 +1,50 @@ +/* + 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. + */ + +/** + @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. + + Learn more on the http://code.google.com/p/json-framework project site. + + 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. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/code/3-scaffold/json-framework/NSObject+SBJSON.h b/code/3-scaffold/json-framework/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/code/3-scaffold/json-framework/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + 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 + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/3-scaffold/json-framework/NSObject+SBJSON.m b/code/3-scaffold/json-framework/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/code/3-scaffold/json-framework/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + 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+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/code/3-scaffold/json-framework/NSString+SBJSON.h b/code/3-scaffold/json-framework/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/code/3-scaffold/json-framework/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + 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 + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/code/3-scaffold/json-framework/NSString+SBJSON.m b/code/3-scaffold/json-framework/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/code/3-scaffold/json-framework/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-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 "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/code/3-scaffold/json-framework/SBJSON.h b/code/3-scaffold/json-framework/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/code/3-scaffold/json-framework/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-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 +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/code/3-scaffold/json-framework/SBJSON.m b/code/3-scaffold/json-framework/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/code/3-scaffold/json-framework/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-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 "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + 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 value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/code/3-scaffold/json-framework/SBJsonBase.h b/code/3-scaffold/json-framework/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + 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 + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + 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,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/code/3-scaffold/json-framework/SBJsonBase.m b/code/3-scaffold/json-framework/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + 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 "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/code/3-scaffold/json-framework/SBJsonParser.h b/code/3-scaffold/json-framework/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @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; + +@end + + +/** + @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 -> 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.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/code/3-scaffold/json-framework/SBJsonParser.m b/code/3-scaffold/json-framework/SBJsonParser.m new file mode 100644 index 0000000..eda051a --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + 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 "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/code/3-scaffold/json-framework/SBJsonWriter.h b/code/3-scaffold/json-framework/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + 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 throw an exception. + + 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 SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @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)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/code/3-scaffold/json-framework/SBJsonWriter.m b/code/3-scaffold/json-framework/SBJsonWriter.m new file mode 100644 index 0000000..0f32904 --- /dev/null +++ b/code/3-scaffold/json-framework/SBJsonWriter.m @@ -0,0 +1,237 @@ +/* + 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 "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/code/3-scaffold/main.m b/code/3-scaffold/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/3-scaffold/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/4-crud/.gitignore b/code/4-crud/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/4-crud/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/4-crud/Classes/AppHelpers.h b/code/4-crud/Classes/AppHelpers.h new file mode 100644 index 0000000..2137364 --- /dev/null +++ b/code/4-crud/Classes/AppHelpers.h @@ -0,0 +1,8 @@ +#define TABLE_BACKGROUND_COLOR [UIColor colorWithRed:0.951 green:0.951 blue:0.951 alpha:1.000] + +void showAlert(NSString *message); + +NSString *formatDate(NSDate *date); +NSDate *parseDateTime(NSString *dateTimeString); + +NSString* numberToCurrency(NSString *number); \ No newline at end of file diff --git a/code/4-crud/Classes/AppHelpers.m b/code/4-crud/Classes/AppHelpers.m new file mode 100644 index 0000000..0765641 --- /dev/null +++ b/code/4-crud/Classes/AppHelpers.m @@ -0,0 +1,48 @@ +#import "AppHelpers.h" + +void showAlert(NSString *message) { + UIAlertView *alert = + [[UIAlertView alloc] initWithTitle:@"Whoops" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + +NSString* formatDate(NSDate *date) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + [formatter setTimeStyle:NSDateFormatterMediumStyle]; + NSString *result = [formatter stringFromDate:date]; + [formatter release]; + return result; +} + +NSDate* parseDateTime(NSString *dateTimeString) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + NSDate *result = [formatter dateFromString:dateTimeString]; + [formatter release]; + return result; +} + +NSString * numberToCurrency(NSString *number) { + if (number == nil) { + return @"$0.00"; + } + + NSDecimalNumber *decimalNumber = + [NSDecimalNumber decimalNumberWithString:number]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:decimalNumber]; + + [formatter release]; + return result; +} \ No newline at end of file diff --git a/code/4-crud/Classes/Goal.h b/code/4-crud/Classes/Goal.h new file mode 100644 index 0000000..8c0f0b4 --- /dev/null +++ b/code/4-crud/Classes/Goal.h @@ -0,0 +1,23 @@ +@interface Goal : NSObject { + NSString *name; + NSString *amount; + NSString *goalId; + NSDate *createdAt; + NSDate *updatedAt; +} + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, retain) NSDate *createdAt; +@property (nonatomic, retain) NSDate *updatedAt; + +- (id)initWithDictionary:(NSDictionary *)dictionary; + ++ (NSArray *)findAllRemote; +- (void)createRemote; +- (void)updateRemote; +- (void)saveRemote; +- (void)destroyRemote; + +@end diff --git a/code/4-crud/Classes/Goal.m b/code/4-crud/Classes/Goal.m new file mode 100644 index 0000000..d2161e8 --- /dev/null +++ b/code/4-crud/Classes/Goal.m @@ -0,0 +1,99 @@ +#import "Goal.h" + +#import "SBJSON.h" +#import "Resource.h" + +@implementation Goal + +static NSString *siteURL = @"http://localhost:3000"; + +@synthesize name; +@synthesize amount; +@synthesize goalId; +@synthesize createdAt; +@synthesize updatedAt; + +- (void)dealloc { + [name release]; + [amount release]; + [goalId release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +- (id)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.name = [dictionary valueForKey:@"name"]; + self.amount = [NSString stringWithFormat:@"%@", [dictionary valueForKey:@"amount"]]; + self.goalId = [dictionary valueForKey:@"id"]; + self.createdAt = parseDateTime([dictionary valueForKey:@"created_at"]); + self.updatedAt = parseDateTime([dictionary valueForKey:@"updated_at"]); + } + return self; +} + ++ (NSArray *)findAllRemote { + NSString *url = + [NSString stringWithFormat:@"%@/goals.json", siteURL]; + + NSError *error = nil; + + NSString *jsonString = [Resource get:url]; + + NSMutableArray *goals = [NSMutableArray array]; + + if (jsonString) { + SBJSON *json = [[SBJSON alloc] init]; + NSArray *results = [json objectWithString:jsonString error:&error]; + [json release]; + + for (NSDictionary *dictionary in results) { + Goal *goal = [[Goal alloc] initWithDictionary:dictionary]; + [goals addObject:goal]; + [goal release]; + } + } + + return goals; +} + +- (NSString *)params { + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + [attributes setValue:self.name forKey:@"name"]; + [attributes setValue:self.amount forKey:@"amount"]; + + NSMutableDictionary *params = + [NSMutableDictionary dictionaryWithObject:attributes forKey:@"goal"]; + + SBJSON *json = [[[SBJSON alloc] init] autorelease]; + return [json stringWithObject:params error:nil]; +} + +- (void)createRemote { + NSString *url = + [NSString stringWithFormat:@"%@/goals.json", siteURL]; + [Resource post:[self params] to:url]; +} + +- (void)updateRemote { + NSString *url = + [NSString stringWithFormat:@"%@/goals/%@.json", siteURL, self.goalId]; + [Resource put:[self params] to:url]; +} + +- (void)saveRemote { + if (self.goalId == nil) { + [self createRemote]; + } else { + [self updateRemote]; + } +} + +- (void)destroyRemote { + NSString *url = + [NSString stringWithFormat:@"%@/goals/%@.json", siteURL, self.goalId]; + [Resource delete:url]; +} + +@end diff --git a/code/4-crud/Classes/GoalAddViewController.h b/code/4-crud/Classes/GoalAddViewController.h new file mode 100644 index 0000000..778845f --- /dev/null +++ b/code/4-crud/Classes/GoalAddViewController.h @@ -0,0 +1,26 @@ +#import + +@class Goal; + +@protocol GoalChangeDelegate +- (void)didChangeGoal:(Goal *)goal; +@end + +@interface GoalAddViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)cancel; +- (IBAction)save; + +@end diff --git a/code/4-crud/Classes/GoalAddViewController.m b/code/4-crud/Classes/GoalAddViewController.m new file mode 100644 index 0000000..6f1ec81 --- /dev/null +++ b/code/4-crud/Classes/GoalAddViewController.m @@ -0,0 +1,176 @@ +#import "GoalAddViewController.h" + +#import "Goal.h" + +@interface GoalAddViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newCancelButton; +- (UIBarButtonItem *)newSaveButton; +- (UITextField *)newTextField; +@end + +@implementation GoalAddViewController + +@synthesize goal; +@synthesize nameField; +@synthesize amountField; +@synthesize delegate; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [nameField release]; + [amountField release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newTextField]; + nameField.keyboardType = UIKeyboardTypeASCIICapable; + [nameField becomeFirstResponder]; + + amountField = [self newTextField]; + amountField.keyboardType = UIKeyboardTypeNumberPad; + + if (goal.goalId) { + nameField.text = goal.name; + amountField.text = goal.amount; + } else { + nameField.placeholder = @"Name"; + amountField.placeholder = @"Amount"; + } + + UIBarButtonItem *cancelButton = [self newCancelButton]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [self newSaveButton]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (goal.goalId) { + self.title = @"Edit Goal"; + } else { + self.title = @"Add Goal"; + } +} + +#pragma mark - +#pragma mark Actions + +-(IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +-(IBAction)save { + goal.name = nameField.text; + goal.amount = amountField.text; + [goal saveRemote]; + [self.delegate didChangeGoal:goal]; + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newCancelButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Cancel" + style:UIBarButtonSystemItemCancel + target:self + action:@selector(cancel)]; +} + +- (UIBarButtonItem *)newSaveButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Save" + style:UIBarButtonSystemItemSave + target:self + action:@selector(save)]; +} + +- (UITextField *)newTextField { + UITextField *textField = + [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 285, 25)]; + textField.font = [UIFont systemFontOfSize:16]; + textField.delegate = self; + textField.returnKeyType = UIReturnKeyDone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + [textField addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return textField; +} + +@end diff --git a/code/4-crud/Classes/GoalDetailViewController.h b/code/4-crud/Classes/GoalDetailViewController.h new file mode 100644 index 0000000..06c2fd1 --- /dev/null +++ b/code/4-crud/Classes/GoalDetailViewController.h @@ -0,0 +1,19 @@ +#import + +#import "GoalAddViewController.h" + +@class Goal; + +@interface GoalDetailViewController : UITableViewController { + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)edit; + +@end diff --git a/code/4-crud/Classes/GoalDetailViewController.m b/code/4-crud/Classes/GoalDetailViewController.m new file mode 100644 index 0000000..0ddfd25 --- /dev/null +++ b/code/4-crud/Classes/GoalDetailViewController.m @@ -0,0 +1,138 @@ +#import "GoalDetailViewController.h" + +#import "Goal.h" + +@interface GoalDetailViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newEditButton; +@end + +@implementation GoalDetailViewController + +@synthesize goal; +@synthesize delegate; + +enum ExpenseTableSections { + kNameSection = 0, + kAmountSection, + kDateSection +}; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + UIBarButtonItem *editButton = [self newEditButton]; + self.navigationItem.rightBarButtonItem = editButton; + [editButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + self.title = goal.name; + [self.tableView reloadData]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)edit { + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self.delegate]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 3; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 1; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForHeaderInSection:(NSInteger)section { + NSString *title = nil; + switch (section) { + case kNameSection: + title = @"Name"; + break; + case kAmountSection: + title = @"Amount"; + break; + case kDateSection: + title = @"Date"; + break; + } + return title; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CellIdentifier] autorelease]; + } + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case kNameSection: + cell.textLabel.text = goal.name; + break; + case kAmountSection: + cell.textLabel.text = numberToCurrency(goal.amount); + break; + case kDateSection: + cell.textLabel.text = formatDate(goal.createdAt); + break; + } +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newEditButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemEdit + target:self + action:@selector(edit)]; +} + +@end diff --git a/code/4-crud/Classes/GoalsViewController.h b/code/4-crud/Classes/GoalsViewController.h new file mode 100644 index 0000000..5d015ad --- /dev/null +++ b/code/4-crud/Classes/GoalsViewController.h @@ -0,0 +1,11 @@ +#import "GoalAddViewController.h" + +@interface GoalsViewController : UITableViewController { + NSMutableArray *goals; +} + +@property (nonatomic, retain) NSArray *goals; + +- (IBAction)add; + +@end diff --git a/code/4-crud/Classes/GoalsViewController.m b/code/4-crud/Classes/GoalsViewController.m new file mode 100644 index 0000000..c1f8fa9 --- /dev/null +++ b/code/4-crud/Classes/GoalsViewController.m @@ -0,0 +1,135 @@ +#import "GoalsViewController.h" + +#import "Goal.h" +#import "GoalDetailViewController.h" + +@interface GoalsViewController () +- (UIBarButtonItem *)newAddButton; +@end + +@implementation GoalsViewController + +@synthesize goals; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + self.goals = [Goal findAllRemote]; + [self.tableView reloadData]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [self newAddButton]; + self.navigationItem.rightBarButtonItem = addButton; + [addButton release]; + + [self refresh]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)add { + Goal *goal = [[Goal alloc] init]; + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self]; + [goal release]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CellIdentifier] autorelease]; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = numberToCurrency(goal.amount); + + return cell; +} + +- (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView beginUpdates]; + if (editingStyle == UITableViewCellEditingStyleDelete) { + Goal *goal = [goals objectAtIndex:indexPath.row]; + [goal destroyRemote]; + [goals removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationFade]; + } + [tableView endUpdates]; +} + +#pragma mark - +#pragma mark Table view delegate + +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + GoalDetailViewController *controller = + [[GoalDetailViewController alloc] initWithGoal:goal andDelegate:self]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Goal lifecycle callbacks + +- (void)didChangeGoal:(Goal *)goal { + [self refresh]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newAddButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(add)]; +} + +@end + diff --git a/code/4-crud/Classes/Resource.h b/code/4-crud/Classes/Resource.h new file mode 100644 index 0000000..194a5ec --- /dev/null +++ b/code/4-crud/Classes/Resource.h @@ -0,0 +1,11 @@ +#import + +@interface Resource : NSObject { +} + ++ (NSString *)get:(NSString *)url; ++ (NSString *)post:(NSString *)body to:(NSString *)url; ++ (NSString *)put:(NSString *)body to:(NSString *)url; ++ (NSString *)delete:(NSString *)url; + +@end diff --git a/code/4-crud/Classes/Resource.m b/code/4-crud/Classes/Resource.m new file mode 100644 index 0000000..bf3845d --- /dev/null +++ b/code/4-crud/Classes/Resource.m @@ -0,0 +1,71 @@ +// +// This class uses synchronous networking, which generally isn't +// a good idea because it blocks the UI. This class is used in this +// version of the application merely as a quick and dirty example, +// and also as a reminder what not to do! See subsequent application +// versions for asynchronous networking approaches. +// + +#import "Resource.h" + +@interface Resource () ++ (NSString *)sendBy:(NSString *)method to:(NSString *)url withBody:(NSString *)body; ++ (NSString *)sendRequest:(NSMutableURLRequest *)request; +@end + +@implementation Resource + ++ (NSString *)get:(NSString *)url { + return [self sendBy:@"GET" to:url withBody:nil]; +} + ++ (NSString *)post:(NSString *)body to:(NSString *)url { + return [self sendBy:@"POST" to:url withBody:body]; +} + ++ (NSString *)put:(NSString *)body to:(NSString *)url { + return [self sendBy:@"PUT" to:url withBody:body]; +} + ++ (NSString *)delete:(NSString *)url { + return [self sendBy:@"DELETE" to:url withBody:nil]; +} + +#pragma mark - +#pragma mark Private + ++ (NSString *)sendBy:(NSString *)method to:(NSString *)url withBody:(NSString *)body { + NSMutableURLRequest *request = + [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; + [request setHTTPMethod:method]; + if (body) { + [request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + } + return [self sendRequest:request]; +} + ++ (NSString *)sendRequest:(NSMutableURLRequest *)request { + NSHTTPURLResponse *response; + NSError *error; + + NSData *responseData = + [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + + NSString *responseString = + [[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding]; + [responseString autorelease]; + + NSLog(@"%@: %@", [request HTTPMethod], [request URL]); + NSLog(@"Response Code: %d", [response statusCode]); + NSLog(@"Content-Type: %@", [[response allHeaderFields] + objectForKey:@"Content-Type"]); + NSLog(@"Response: %@\n\n", responseString); + + return responseString; +} + +@end diff --git a/code/4-crud/Classes/SaveUpAppDelegate.h b/code/4-crud/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..a62ade0 --- /dev/null +++ b/code/4-crud/Classes/SaveUpAppDelegate.h @@ -0,0 +1,12 @@ +#import + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; + +@end + diff --git a/code/4-crud/Classes/SaveUpAppDelegate.m b/code/4-crud/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..7bffccf --- /dev/null +++ b/code/4-crud/Classes/SaveUpAppDelegate.m @@ -0,0 +1,33 @@ +#import "SaveUpAppDelegate.h" + +#import "GoalsViewController.h" + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Save data if appropriate +} + +@end + diff --git a/code/4-crud/GoalsViewController.xib b/code/4-crud/GoalsViewController.xib new file mode 100644 index 0000000..f2ba22e --- /dev/null +++ b/code/4-crud/GoalsViewController.xib @@ -0,0 +1,382 @@ + + + + 784 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {320, 247} + + 3 + MQA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + + YES + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{144, 609}, {320, 247}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 5 + + + + YES + + GoalsViewController + UITableViewController + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/4-crud/MainWindow.xib b/code/4-crud/MainWindow.xib new file mode 100644 index 0000000..bd15e7f --- /dev/null +++ b/code/4-crud/MainWindow.xib @@ -0,0 +1,556 @@ + + + + 800 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + 1 + + + YES + + + + IBCocoaTouchFramework + + + GoalsViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{673, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{186, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + GoalsViewController + UITableViewController + + add + id + + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UITableViewController + + IBUserSource + + + + + NSObject + + IBProjectSource + json-framework/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + json-framework/SBJsonWriter.h + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/4-crud/README.md b/code/4-crud/README.md new file mode 100644 index 0000000..a73275e --- /dev/null +++ b/code/4-crud/README.md @@ -0,0 +1,5 @@ +Save Up iPhone App +================== + +This version is the finished app for using a custom Resource class +to CRUD remote goals (synchronous). \ No newline at end of file diff --git a/code/4-crud/SaveUp-Info.plist b/code/4-crud/SaveUp-Info.plist new file mode 100644 index 0000000..660b9ed --- /dev/null +++ b/code/4-crud/SaveUp-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/4-crud/SaveUp.xcodeproj/project.pbxproj b/code/4-crud/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..d94cedd --- /dev/null +++ b/code/4-crud/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,350 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 273459F911936B5B005C8C5F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 273459F811936B5B005C8C5F /* icon.png */; }; + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */; }; + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */; }; + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CC11937585006153B1 /* SBJSON.m */; }; + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7CE11937585006153B1 /* SBJsonBase.m */; }; + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D011937585006153B1 /* SBJsonParser.m */; }; + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7D211937585006153B1 /* SBJsonWriter.m */; }; + 27AAD7E01193766E006153B1 /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7DF1193766E006153B1 /* Goal.m */; }; + 27DD8ADA1194744800FAC6C4 /* Resource.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AD91194744800FAC6C4 /* Resource.m */; }; + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */; }; + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */; }; + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* GoalsViewController.m */; }; + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* GoalsViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 273459F811936B5B005C8C5F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 27AAD7C611937585006153B1 /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 27AAD7CB11937585006153B1 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 27AAD7CC11937585006153B1 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 27AAD7CD11937585006153B1 /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 27AAD7CE11937585006153B1 /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 27AAD7CF11937585006153B1 /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 27AAD7D011937585006153B1 /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 27AAD7D111937585006153B1 /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 27AAD7D211937585006153B1 /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 27AAD7DE1193766E006153B1 /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 27AAD7DF1193766E006153B1 /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 27DD8AD81194744700FAC6C4 /* Resource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Resource.h; sourceTree = ""; }; + 27DD8AD91194744800FAC6C4 /* Resource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Resource.m; sourceTree = ""; }; + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHelpers.h; sourceTree = ""; }; + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHelpers.m; sourceTree = ""; }; + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalAddViewController.h; sourceTree = ""; }; + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalAddViewController.m; sourceTree = ""; }; + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalDetailViewController.h; sourceTree = ""; }; + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalDetailViewController.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 28F335F01007B36200424DE2 /* GoalsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsViewController.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SaveUp-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */, + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */, + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */, + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */, + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */, + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */, + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */, + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + 27AAD7DE1193766E006153B1 /* Goal.h */, + 27AAD7DF1193766E006153B1 /* Goal.m */, + 27DD8AD81194744700FAC6C4 /* Resource.h */, + 27DD8AD91194744800FAC6C4 /* Resource.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 27AAD7C011937571006153B1 /* Vendor */ = { + isa = PBXGroup; + children = ( + 27AAD7C511937585006153B1 /* json-framework */, + ); + name = Vendor; + sourceTree = ""; + }; + 27AAD7C511937585006153B1 /* json-framework */ = { + isa = PBXGroup; + children = ( + 27AAD7C611937585006153B1 /* JSON.h */, + 27AAD7C711937585006153B1 /* NSObject+SBJSON.h */, + 27AAD7C811937585006153B1 /* NSObject+SBJSON.m */, + 27AAD7C911937585006153B1 /* NSString+SBJSON.h */, + 27AAD7CA11937585006153B1 /* NSString+SBJSON.m */, + 27AAD7CB11937585006153B1 /* SBJSON.h */, + 27AAD7CC11937585006153B1 /* SBJSON.m */, + 27AAD7CD11937585006153B1 /* SBJsonBase.h */, + 27AAD7CE11937585006153B1 /* SBJsonBase.m */, + 27AAD7CF11937585006153B1 /* SBJsonParser.h */, + 27AAD7D011937585006153B1 /* SBJsonParser.m */, + 27AAD7D111937585006153B1 /* SBJsonWriter.h */, + 27AAD7D211937585006153B1 /* SBJsonWriter.m */, + ); + path = "json-framework"; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 27AAD7C011937571006153B1 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 273459F811936B5B005C8C5F /* icon.png */, + 28F335F01007B36200424DE2 /* GoalsViewController.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = SaveUp; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */, + 273459F911936B5B005C8C5F /* icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */, + 27AAD7D311937585006153B1 /* NSObject+SBJSON.m in Sources */, + 27AAD7D411937585006153B1 /* NSString+SBJSON.m in Sources */, + 27AAD7D511937585006153B1 /* SBJSON.m in Sources */, + 27AAD7D611937585006153B1 /* SBJsonBase.m in Sources */, + 27AAD7D711937585006153B1 /* SBJsonParser.m in Sources */, + 27AAD7D811937585006153B1 /* SBJsonWriter.m in Sources */, + 27AAD7E01193766E006153B1 /* Goal.m in Sources */, + 27DD8ADA1194744800FAC6C4 /* Resource.m in Sources */, + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */, + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */, + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/4-crud/SaveUp_Prefix.pch b/code/4-crud/SaveUp_Prefix.pch new file mode 100644 index 0000000..cde934a --- /dev/null +++ b/code/4-crud/SaveUp_Prefix.pch @@ -0,0 +1,15 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + + +#ifdef __OBJC__ + #import + #import + #import "AppHelpers.h" +#endif diff --git a/code/4-crud/icon.png b/code/4-crud/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/4-crud/icon.png differ diff --git a/code/4-crud/json-framework/JSON.h b/code/4-crud/json-framework/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/code/4-crud/json-framework/JSON.h @@ -0,0 +1,50 @@ +/* + 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. + */ + +/** + @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. + + Learn more on the http://code.google.com/p/json-framework project site. + + 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. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/code/4-crud/json-framework/NSObject+SBJSON.h b/code/4-crud/json-framework/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/code/4-crud/json-framework/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + 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 + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/4-crud/json-framework/NSObject+SBJSON.m b/code/4-crud/json-framework/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/code/4-crud/json-framework/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + 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+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/code/4-crud/json-framework/NSString+SBJSON.h b/code/4-crud/json-framework/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/code/4-crud/json-framework/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + 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 + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/code/4-crud/json-framework/NSString+SBJSON.m b/code/4-crud/json-framework/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/code/4-crud/json-framework/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-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 "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/code/4-crud/json-framework/SBJSON.h b/code/4-crud/json-framework/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/code/4-crud/json-framework/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-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 +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/code/4-crud/json-framework/SBJSON.m b/code/4-crud/json-framework/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/code/4-crud/json-framework/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-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 "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + 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 value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/code/4-crud/json-framework/SBJsonBase.h b/code/4-crud/json-framework/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/code/4-crud/json-framework/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + 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 + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + 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,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/code/4-crud/json-framework/SBJsonBase.m b/code/4-crud/json-framework/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/code/4-crud/json-framework/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + 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 "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/code/4-crud/json-framework/SBJsonParser.h b/code/4-crud/json-framework/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/code/4-crud/json-framework/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @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; + +@end + + +/** + @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 -> 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.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/code/4-crud/json-framework/SBJsonParser.m b/code/4-crud/json-framework/SBJsonParser.m new file mode 100644 index 0000000..eda051a --- /dev/null +++ b/code/4-crud/json-framework/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + 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 "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/code/4-crud/json-framework/SBJsonWriter.h b/code/4-crud/json-framework/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/code/4-crud/json-framework/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + 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 throw an exception. + + 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 SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @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)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/code/4-crud/json-framework/SBJsonWriter.m b/code/4-crud/json-framework/SBJsonWriter.m new file mode 100644 index 0000000..0f32904 --- /dev/null +++ b/code/4-crud/json-framework/SBJsonWriter.m @@ -0,0 +1,237 @@ +/* + 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 "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/code/4-crud/main.m b/code/4-crud/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/4-crud/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/5-httpriot/.gitignore b/code/5-httpriot/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/5-httpriot/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/5-httpriot/Classes/AppHelpers.h b/code/5-httpriot/Classes/AppHelpers.h new file mode 100644 index 0000000..2137364 --- /dev/null +++ b/code/5-httpriot/Classes/AppHelpers.h @@ -0,0 +1,8 @@ +#define TABLE_BACKGROUND_COLOR [UIColor colorWithRed:0.951 green:0.951 blue:0.951 alpha:1.000] + +void showAlert(NSString *message); + +NSString *formatDate(NSDate *date); +NSDate *parseDateTime(NSString *dateTimeString); + +NSString* numberToCurrency(NSString *number); \ No newline at end of file diff --git a/code/5-httpriot/Classes/AppHelpers.m b/code/5-httpriot/Classes/AppHelpers.m new file mode 100644 index 0000000..0765641 --- /dev/null +++ b/code/5-httpriot/Classes/AppHelpers.m @@ -0,0 +1,48 @@ +#import "AppHelpers.h" + +void showAlert(NSString *message) { + UIAlertView *alert = + [[UIAlertView alloc] initWithTitle:@"Whoops" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + +NSString* formatDate(NSDate *date) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + [formatter setTimeStyle:NSDateFormatterMediumStyle]; + NSString *result = [formatter stringFromDate:date]; + [formatter release]; + return result; +} + +NSDate* parseDateTime(NSString *dateTimeString) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + NSDate *result = [formatter dateFromString:dateTimeString]; + [formatter release]; + return result; +} + +NSString * numberToCurrency(NSString *number) { + if (number == nil) { + return @"$0.00"; + } + + NSDecimalNumber *decimalNumber = + [NSDecimalNumber decimalNumberWithString:number]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:decimalNumber]; + + [formatter release]; + return result; +} \ No newline at end of file diff --git a/code/5-httpriot/Classes/Goal.h b/code/5-httpriot/Classes/Goal.h new file mode 100644 index 0000000..2db45df --- /dev/null +++ b/code/5-httpriot/Classes/Goal.h @@ -0,0 +1,21 @@ +@interface Goal : NSObject { + NSString *name; + NSString *amount; + NSString *goalId; + NSDate *createdAt; + NSDate *updatedAt; +} + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, retain) NSDate *createdAt; +@property (nonatomic, retain) NSDate *updatedAt; + ++ (NSArray *)makeGoalsFromJSONArray:(NSArray *)results; + +- (id)initWithDictionary:(NSDictionary *)dictionary; + +- (NSString *)JSONRepresentation; + +@end diff --git a/code/5-httpriot/Classes/Goal.m b/code/5-httpriot/Classes/Goal.m new file mode 100644 index 0000000..b80c129 --- /dev/null +++ b/code/5-httpriot/Classes/Goal.m @@ -0,0 +1,56 @@ +#import "Goal.h" + +#import "SBJSON.h" + +@implementation Goal + +@synthesize name; +@synthesize amount; +@synthesize goalId; +@synthesize createdAt; +@synthesize updatedAt; + +- (void)dealloc { + [name release]; + [amount release]; + [goalId release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +- (id)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.name = [dictionary valueForKey:@"name"]; + self.amount = [NSString stringWithFormat:@"%@", [dictionary valueForKey:@"amount"]]; + self.goalId = [dictionary valueForKey:@"id"]; + self.createdAt = parseDateTime([dictionary valueForKey:@"created_at"]); + self.updatedAt = parseDateTime([dictionary valueForKey:@"updated_at"]); + } + return self; +} + ++ (NSArray *)makeGoalsFromJSONArray:(NSArray *)results { + NSMutableArray *goals = [NSMutableArray array]; + + for (NSDictionary *dictionary in results) { + Goal *goal = [[Goal alloc] initWithDictionary:dictionary]; + [goals addObject:goal]; + [goal release]; + } + + return goals; +} + +- (NSString *)JSONRepresentation { + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + [attributes setValue:self.name forKey:@"name"]; + [attributes setValue:self.amount forKey:@"amount"]; + + NSMutableDictionary *params = + [NSMutableDictionary dictionaryWithObject:attributes forKey:@"goal"]; + + return [params JSONRepresentation]; +} + +@end diff --git a/code/5-httpriot/Classes/GoalAddViewController.h b/code/5-httpriot/Classes/GoalAddViewController.h new file mode 100644 index 0000000..3988b4b --- /dev/null +++ b/code/5-httpriot/Classes/GoalAddViewController.h @@ -0,0 +1,26 @@ +#import "HTTPRiot.h" + +@class Goal; + +@protocol GoalChangeDelegate +- (void)didChangeGoal:(Goal *)goal; +@end + +@interface GoalAddViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)cancel; +- (IBAction)save; + +@end diff --git a/code/5-httpriot/Classes/GoalAddViewController.m b/code/5-httpriot/Classes/GoalAddViewController.m new file mode 100644 index 0000000..ba511f1 --- /dev/null +++ b/code/5-httpriot/Classes/GoalAddViewController.m @@ -0,0 +1,227 @@ +#import "GoalAddViewController.h" + +#import "Goal.h" + +@interface GoalAddViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newCancelButton; +- (UIBarButtonItem *)newSaveButton; +- (UITextField *)newTextField; +@end + +@implementation GoalAddViewController + +@synthesize goal; +@synthesize nameField; +@synthesize amountField; +@synthesize delegate; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [nameField release]; + [amountField release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newTextField]; + nameField.keyboardType = UIKeyboardTypeASCIICapable; + [nameField becomeFirstResponder]; + + amountField = [self newTextField]; + amountField.keyboardType = UIKeyboardTypeNumberPad; + + if (goal.goalId) { + nameField.text = goal.name; + amountField.text = goal.amount; + } else { + nameField.placeholder = @"Name"; + amountField.placeholder = @"Amount"; + } + + UIBarButtonItem *cancelButton = [self newCancelButton]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [self newSaveButton]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; + + [HRRestModel setDelegate:self]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (goal.goalId) { + self.title = @"Edit Goal"; + } else { + self.title = @"Add Goal"; + } +} + +#pragma mark - +#pragma mark Actions + +-(IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +-(IBAction)save { + goal.name = nameField.text; + goal.amount = amountField.text; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + + NSDictionary *options = + [NSDictionary dictionaryWithObject:[goal JSONRepresentation] forKey:@"body"]; + + if (goal.goalId == nil) { + [HRRestModel postPath:@"goals" withOptions:options object:nil]; + } else { + [HRRestModel putPath:[NSString stringWithFormat:@"goals/%@", goal.goalId] + withOptions:options object:nil]; + } +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark HRRestConnection delegate methods + +- (void)restConnection:(NSURLConnection *)connection + didReturnResource:(id)resource + object:(id)object { + + Goal *remoteGoal = [[Goal alloc] initWithDictionary:resource]; + [self.delegate didChangeGoal:remoteGoal]; + [remoteGoal release]; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + [self.navigationController popViewControllerAnimated:YES]; +} + +- (void)restConnection:(NSURLConnection *)connection + didReceiveResponse:(NSHTTPURLResponse *)response + object:(id)object { + //NSLog(@"Response status code: %i", [response statusCode]); +} + +- (void)restConnection:(NSURLConnection *)connection + didFailWithError:(NSError *)error + object:(id)object { + showAlert([error localizedDescription]); +} + +- (void)restConnection:(NSURLConnection *)connection + didReceiveError:(NSError *)error + response:(NSHTTPURLResponse *)response + object:(id)object { + showAlert([error localizedDescription]); +} + ++ (void)restConnection:(NSURLConnection *)connection + didReceiveParseError:(NSError *)error + responseBody:(NSString *)string { + showAlert([error localizedDescription]); +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newCancelButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Cancel" + style:UIBarButtonSystemItemCancel + target:self + action:@selector(cancel)]; +} + +- (UIBarButtonItem *)newSaveButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Save" + style:UIBarButtonSystemItemSave + target:self + action:@selector(save)]; +} + +- (UITextField *)newTextField { + UITextField *textField = + [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 285, 25)]; + textField.font = [UIFont systemFontOfSize:16]; + textField.delegate = self; + textField.returnKeyType = UIReturnKeyDone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + [textField addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return textField; +} + +@end diff --git a/code/5-httpriot/Classes/GoalDetailViewController.h b/code/5-httpriot/Classes/GoalDetailViewController.h new file mode 100644 index 0000000..06c2fd1 --- /dev/null +++ b/code/5-httpriot/Classes/GoalDetailViewController.h @@ -0,0 +1,19 @@ +#import + +#import "GoalAddViewController.h" + +@class Goal; + +@interface GoalDetailViewController : UITableViewController { + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)edit; + +@end diff --git a/code/5-httpriot/Classes/GoalDetailViewController.m b/code/5-httpriot/Classes/GoalDetailViewController.m new file mode 100644 index 0000000..0ddfd25 --- /dev/null +++ b/code/5-httpriot/Classes/GoalDetailViewController.m @@ -0,0 +1,138 @@ +#import "GoalDetailViewController.h" + +#import "Goal.h" + +@interface GoalDetailViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newEditButton; +@end + +@implementation GoalDetailViewController + +@synthesize goal; +@synthesize delegate; + +enum ExpenseTableSections { + kNameSection = 0, + kAmountSection, + kDateSection +}; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + UIBarButtonItem *editButton = [self newEditButton]; + self.navigationItem.rightBarButtonItem = editButton; + [editButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + self.title = goal.name; + [self.tableView reloadData]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)edit { + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self.delegate]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 3; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 1; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForHeaderInSection:(NSInteger)section { + NSString *title = nil; + switch (section) { + case kNameSection: + title = @"Name"; + break; + case kAmountSection: + title = @"Amount"; + break; + case kDateSection: + title = @"Date"; + break; + } + return title; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CellIdentifier] autorelease]; + } + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case kNameSection: + cell.textLabel.text = goal.name; + break; + case kAmountSection: + cell.textLabel.text = numberToCurrency(goal.amount); + break; + case kDateSection: + cell.textLabel.text = formatDate(goal.createdAt); + break; + } +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newEditButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemEdit + target:self + action:@selector(edit)]; +} + +@end diff --git a/code/5-httpriot/Classes/GoalsViewController.h b/code/5-httpriot/Classes/GoalsViewController.h new file mode 100644 index 0000000..dbe935f --- /dev/null +++ b/code/5-httpriot/Classes/GoalsViewController.h @@ -0,0 +1,15 @@ +#import "GoalAddViewController.h" + +#import "HTTPRiot.h" + +@interface GoalsViewController : UITableViewController { + NSMutableArray *goals; + NSIndexPath *indexPathOfItemToDelete; + BOOL isDeleting; +} + +@property (nonatomic, retain) NSMutableArray *goals; + +- (IBAction)add; + +@end diff --git a/code/5-httpriot/Classes/GoalsViewController.m b/code/5-httpriot/Classes/GoalsViewController.m new file mode 100644 index 0000000..dfe3c19 --- /dev/null +++ b/code/5-httpriot/Classes/GoalsViewController.m @@ -0,0 +1,188 @@ +#import "GoalsViewController.h" + +#import "Goal.h" +#import "GoalDetailViewController.h" + +@interface GoalsViewController () +- (UIBarButtonItem *)newAddButton; +@end + +@implementation GoalsViewController + +@synthesize goals; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + indexPathOfItemToDelete = nil; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [HRRestModel setDelegate:self]; + [HRRestModel getPath:@"/goals" withOptions:nil object:nil]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [self newAddButton]; + self.navigationItem.rightBarButtonItem = addButton; + [addButton release]; + + isDeleting = NO; + + [self refresh]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)add { + Goal *goal = [[Goal alloc] init]; + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self]; + [goal release]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CellIdentifier] autorelease]; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = numberToCurrency(goal.amount); + + return cell; +} + +- (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + if (editingStyle == UITableViewCellEditingStyleDelete) { + isDeleting = YES; + indexPathOfItemToDelete = indexPath; + Goal *goal = [goals objectAtIndex:indexPath.row]; + [HRRestModel deletePath:[NSString stringWithFormat:@"goals/%@", goal.goalId] + withOptions:nil object:nil]; + } +} + +#pragma mark - +#pragma mark Table view delegate + +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + GoalDetailViewController *controller = + [[GoalDetailViewController alloc] initWithGoal:goal andDelegate:self]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Goal lifecycle callbacks + +- (void)didChangeGoal:(Goal *)goal { + [self refresh]; +} + +#pragma mark - +#pragma mark HRRestConnection delegate methods + +- (void)restConnection:(NSURLConnection *)connection + didReturnResource:(id)resource + object:(id)object { + if (isDeleting) { + [goals removeObjectAtIndex:indexPathOfItemToDelete.row]; + [self.tableView beginUpdates]; + [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPathOfItemToDelete, nil] + withRowAnimation:UITableViewRowAnimationFade]; + [self.tableView endUpdates]; + isDeleting = NO; + } else { + NSMutableArray *remoteGoals = [[NSMutableArray alloc] init]; + for (NSDictionary *dictionary in resource) { + Goal *remoteGoal = [[Goal alloc] initWithDictionary:dictionary]; + [remoteGoals addObject:remoteGoal]; + [remoteGoal release]; + } + self.goals = remoteGoals; + [remoteGoals release]; + + [self.tableView reloadData]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + } +} + +- (void)restConnection:(NSURLConnection *)connection + didReceiveResponse:(NSHTTPURLResponse *)response + object:(id)object { + //NSLog(@"Response status code: %i", [response statusCode]); +} + +- (void)restConnection:(NSURLConnection *)connection + didFailWithError:(NSError *)error + object:(id)object { + showAlert([error localizedDescription]); +} + +- (void)restConnection:(NSURLConnection *)connection + didReceiveError:(NSError *)error + response:(NSHTTPURLResponse *)response + object:(id)object { + showAlert([error localizedDescription]); +} + ++ (void)restConnection:(NSURLConnection *)connection + didReceiveParseError:(NSError *)error + responseBody:(NSString *)string { + showAlert([error localizedDescription]); +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newAddButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(add)]; +} + +@end + diff --git a/code/5-httpriot/Classes/SaveUpAppDelegate.h b/code/5-httpriot/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..a62ade0 --- /dev/null +++ b/code/5-httpriot/Classes/SaveUpAppDelegate.h @@ -0,0 +1,12 @@ +#import + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; + +@end + diff --git a/code/5-httpriot/Classes/SaveUpAppDelegate.m b/code/5-httpriot/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..5191bbd --- /dev/null +++ b/code/5-httpriot/Classes/SaveUpAppDelegate.m @@ -0,0 +1,35 @@ +#import "SaveUpAppDelegate.h" + +#import "GoalsViewController.h" +#import "HTTPRiot.h" + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [HRRestModel setBaseURL:[NSURL URLWithString:@"http://localhost:3000"]]; + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Save data if appropriate +} + +@end + diff --git a/code/5-httpriot/GoalsViewController.xib b/code/5-httpriot/GoalsViewController.xib new file mode 100644 index 0000000..f2ba22e --- /dev/null +++ b/code/5-httpriot/GoalsViewController.xib @@ -0,0 +1,382 @@ + + + + 784 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {320, 247} + + 3 + MQA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + + YES + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{144, 609}, {320, 247}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 5 + + + + YES + + GoalsViewController + UITableViewController + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.h b/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.h new file mode 100644 index 0000000..2602734 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.h @@ -0,0 +1,6 @@ +#import + +@interface NSDictionary (ParamUtils) +- (NSString*) toQueryString; +@end + diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.m b/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.m new file mode 100644 index 0000000..82a7075 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSDictionary+ParamUtils.m @@ -0,0 +1,30 @@ +// +// NSDictionary+Misc.m +// Legislate +// +// Created by Justin Palmer on 7/24/08. +// Copyright 2008 Active Reload, LLC. All rights reserved. +// + +#import "NSDictionary+ParamUtils.h" +#import "NSString+EscapingUtils.h" + +@implementation NSDictionary (ParamUtils) + + + +- (NSString *)toQueryString { + NSMutableArray *pairs = [[[NSMutableArray alloc] init] autorelease]; + for (id key in [self allKeys]) { + id value = [self objectForKey:key]; + if ([value isKindOfClass:[NSArray class]]) { + for (id val in value) { + [pairs addObject:[NSString stringWithFormat:@"%@=%@",key, [val stringByPreparingForURL]]]; + } + } else { + [pairs addObject:[NSString stringWithFormat:@"%@=%@",key, [value stringByPreparingForURL]]]; + } + } + return [pairs componentsJoinedByString:@"&"]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.h b/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.h new file mode 100644 index 0000000..203f2bf --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.h @@ -0,0 +1,15 @@ +// +// NSObject+InvocationUtils.h +// HTTPRiot +// +// Created by Justin Palmer on 6/25/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import + + +@interface NSObject (InvocationUtils) +- (void)performSelectorOnMainThread:(SEL)selector withObjects:(id)obj1, ...; +- (void)performSelectorOnMainThread:(SEL)selector withObjectArray:(NSArray *)objects; +@end diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.m b/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.m new file mode 100644 index 0000000..eb116ac --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSObject+InvocationUtils.m @@ -0,0 +1,46 @@ +// +// NSObject+InvocationUtils.m +// HTTPRiot +// +// Created by Justin Palmer on 6/25/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "NSObject+InvocationUtils.h" + + +@implementation NSObject (InvocationUtils) +- (void)performSelectorOnMainThread:(SEL)selector withObjects:(id)obj1, ... { + id argitem; va_list args; + NSMutableArray *objects = [[NSMutableArray alloc] init]; + if(obj1 != nil) { + [objects addObject:obj1]; + va_start(args, obj1); + + while (argitem = va_arg(args, id)) { + [objects addObject:argitem]; + } + + va_end(args); + } + + [self performSelectorOnMainThread:selector withObjectArray:objects]; + [objects release]; +} + +- (void)performSelectorOnMainThread:(SEL)selector withObjectArray:(NSArray *)objects { + NSMethodSignature *signature = [self methodSignatureForSelector:selector]; + if(signature) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setTarget:self]; + [invocation setSelector:selector]; + + for(size_t i = 0; i < objects.count; ++i) { + id obj = [objects objectAtIndex:i]; + [invocation setArgument:&obj atIndex:(i + 2)]; + } + + [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES]; + } +} +@end diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.h b/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.h new file mode 100644 index 0000000..10f2e94 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.h @@ -0,0 +1,5 @@ +#import + +@interface NSString (EscapingUtils) +- (NSString *) stringByPreparingForURL; +@end diff --git a/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.m b/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.m new file mode 100644 index 0000000..7f39e94 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Extensions/NSString+EscapingUtils.m @@ -0,0 +1,13 @@ +#import "NSString+EscapingUtils.h" + +@implementation NSString (EscapingUtils) +- (NSString *)stringByPreparingForURL { + NSString *escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)self, + NULL, + (CFStringRef)@":/?=,!$&'()*+;[]@#", + CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); + + return [escapedString autorelease]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.h b/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.h new file mode 100644 index 0000000..ffa854e --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.h @@ -0,0 +1,15 @@ +// +// HRFormatJSON.h +// HTTPRiot +// +// Created by Justin Palmer on 2/8/09. +// Copyright 2009 Alternateidea. All rights reserved. +// +#import +#import "HRFormatterProtocol.h" + +@interface HRFormatJSON : NSObject { + +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.m b/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.m new file mode 100644 index 0000000..a9251cd --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Formatters/HRFormatJSON.m @@ -0,0 +1,46 @@ +// +// HRFormatJSON.m +// HTTPRiot +// +// Created by Justin Palmer on 2/8/09. +// Copyright 2009 Alternateidea. All rights reserved. +// + +#import "HRFormatJSON.h" +#import "JSON.h" + +@implementation HRFormatJSON ++ (NSString *)extension { + return @"json"; +} + ++ (NSString *)mimeType { + return @"application/json"; +} + ++ (id)decode:(NSData *)data error:(NSError **)error { + NSString *rawString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + // If we failed to decode the data using UTF8 attempt to use ASCII encoding. + if(rawString == nil && ([data length] > 0)) { + rawString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + } + + NSError *parseError = nil; + SBJSON *parser = [[SBJSON alloc] init]; + id results = [parser objectWithString:rawString error:&parseError]; + [parser release]; + [rawString release]; + + if(parseError && !results) { + if(error != nil) + *error = parseError; + return nil; + } + + return results; +} + ++ (NSString *)encode:(id)data error:(NSError **)error { + return [data JSONRepresentation]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.h b/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.h new file mode 100644 index 0000000..f5c2847 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.h @@ -0,0 +1,14 @@ +// +// HRFormatXML.h +// HTTPRiot +// +// Created by Justin Palmer on 2/8/09. +// Copyright 2009 Alternateidea. All rights reserved. +// +#import +#import "HRFormatterProtocol.h" +@interface HRFormatXML : NSObject { + +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.m b/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.m new file mode 100644 index 0000000..e8daabf --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Formatters/HRFormatXML.m @@ -0,0 +1,40 @@ +// +// HTTPRiotFormatXML.m +// HTTPRiot +// +// Created by Justin Palmer on 2/8/09. +// Copyright 2009 Alternateidea. All rights reserved. +// + +#import "HRFormatXML.h" +#import "AIXMLSerialization.h" + +@implementation HRFormatXML ++ (NSString *)extension { + return @"xml"; +} + ++ (NSString *)mimeType { + return @"application/xml"; +} + ++ (id)decode:(NSData *)data error:(NSError **)error { + NSError *parseError = nil; + NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:data options:NSXMLDocumentTidyXML error:&parseError] autorelease]; + + if(parseError != nil) { + if(error != nil) + *error = parseError; + + return nil; + } + + return [doc toDictionary]; +} + ++ (NSString *)encode:(id)data error:(NSError **)error { + NSAssert(true, @"XML Encoding is not supported. Currently accepting patches"); + return nil; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Formatters/HRFormatterProtocol.h b/code/5-httpriot/HTTPRiot/Formatters/HRFormatterProtocol.h new file mode 100644 index 0000000..a6314de --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Formatters/HRFormatterProtocol.h @@ -0,0 +1,47 @@ +/** + * @file HRFormatterProtocol.h Protocol for the formatters. + */ + +// +// HRFormatterProtocol.h +// HTTPRiot +// +// Created by Justin Palmer on 2/8/09. +// Copyright 2009 Alternateidea. All rights reserved. +// +#import + +/** + * @protocol HRFormatterProtocol + * + * Formatters used in formatting response data + * Formatters should be able to encode and decode a specific data type. + */ +@protocol HRFormatterProtocol + +/** + * The file extension. Example: json, xml, plist, n3, etc. + */ ++ (NSString *)extension; + +/** + * The mime-type represented by this formatter + */ ++ (NSString *)mimeType; + +/** + * Takes the format and turns it into the appropriate Obj-C data type. + * + * @param data Raw data to be decoded. + * @param error Returns any errors that happened while decoding. + */ ++ (id)decode:(NSData *)data error:(NSError **)error; + +/** + * Takes an Obj-C data type and turns it into the proper format. + * + * @param object The Obj-C object to be encoded by the formatter. + * @param error Returns any errors that happened while encoding. + */ ++ (NSString *)encode:(id)object error:(NSError **)error; +@end diff --git a/code/5-httpriot/HTTPRiot/HRGlobal.h b/code/5-httpriot/HTTPRiot/HRGlobal.h new file mode 100644 index 0000000..71f14ed --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRGlobal.h @@ -0,0 +1,67 @@ +/** + * @file HRGlobal.h Shared types and constants. + */ +#import + +/// Key for delgate +extern NSString *kHRClassAttributesDelegateKey; +/// Key for base url +extern NSString *kHRClassAttributesBaseURLKey; +/// Key for headers +extern NSString *kHRClassAttributesHeadersKey; +/// Key for basic auth +extern NSString *kHRClassAttributesBasicAuthKey; +/// Key for username +extern NSString *kHRClassAttributesUsernameKey; +/// Key for password +extern NSString *kHRClassAttributesPasswordKey; +/// Key for format +extern NSString *kHRClassAttributesFormatKey; +/// Key for default params +extern NSString *kHRClassAttributesDefaultParamsKey; +/// Key for params +extern NSString *kHRClassAttributesParamsKey; +/// Key for body +extern NSString *kHRClassAttributesBodyKey; + + +/** + * Supported REST methods. + * @see HRRequestOperation + */ +typedef enum { + /// Unknown [NOT USED] + HRRequestMethodUnknown = -1, + /// GET + HRRequestMethodGet, + /// POST + HRRequestMethodPost, + /// PUT + HRRequestMethodPut, + /// DELETE + HRRequestMethodDelete +} HRRequestMethod; + +/** + Supported formats. + @see HRRestModel#setFormat + */ +typedef enum { + /// Unknown [NOT USED] + HRDataFormatUnknown = -1, + /// JSON Format + HRDataFormatJSON, + /// XML Format + HRDataFormatXML +} HRDataFormat; + +/// HTTPRiot's error domain +#define HTTPRiotErrorDomain @"com.labratrevenge.HTTPRiot.ErrorDomain" + +#ifdef DEBUG +/// Logging Helper +#define HRLOG NSLog +#else +/// Logging Helper +#define HRLOG +#endif \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/HRGlobal.m b/code/5-httpriot/HTTPRiot/HRGlobal.m new file mode 100644 index 0000000..9cb03ee --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRGlobal.m @@ -0,0 +1,20 @@ +// +// HRGlobal.m +// HTTPRiot +// +// Created by Justin Palmer on 8/11/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "HRGlobal.h" + +NSString *kHRClassAttributesDelegateKey = @"delegate"; +NSString *kHRClassAttributesBaseURLKey = @"baseURL"; +NSString *kHRClassAttributesHeadersKey = @"headers"; +NSString *kHRClassAttributesBasicAuthKey = @"basicAuth"; +NSString *kHRClassAttributesUsernameKey = @"username"; +NSString *kHRClassAttributesPasswordKey = @"password"; +NSString *kHRClassAttributesFormatKey = @"format"; +NSString *kHRClassAttributesDefaultParamsKey = @"defaultParams"; +NSString *kHRClassAttributesParamsKey = @"params"; +NSString *kHRClassAttributesBodyKey = @"body"; \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/HROperationQueue.h b/code/5-httpriot/HTTPRiot/HROperationQueue.h new file mode 100644 index 0000000..0a6154e --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HROperationQueue.h @@ -0,0 +1,23 @@ +// +// HROperationQueue.h +// HTTPRiot +// +// Created by Justin Palmer on 7/2/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import + + +/** + * Gives you access to the shared operation queue used to manage all connections. + */ +@interface HROperationQueue : NSOperationQueue { + +} + +/** + * Shared operation queue. + */ ++ (HROperationQueue *)sharedOperationQueue; +@end diff --git a/code/5-httpriot/HTTPRiot/HROperationQueue.m b/code/5-httpriot/HTTPRiot/HROperationQueue.m new file mode 100644 index 0000000..7a09b5b --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HROperationQueue.m @@ -0,0 +1,26 @@ +// +// HROperationQueue.m +// HTTPRiot +// +// Created by Justin Palmer on 7/2/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "HROperationQueue.h" +#import "HRGlobal.h" + +static HROperationQueue *sharedOperationQueue = nil; + + +@implementation HROperationQueue ++ (HROperationQueue *)sharedOperationQueue { + @synchronized(self) { + if (sharedOperationQueue == nil) { + sharedOperationQueue = [[HROperationQueue alloc] init]; + sharedOperationQueue.maxConcurrentOperationCount = 3; + } + } + + return sharedOperationQueue; +} +@end diff --git a/code/5-httpriot/HTTPRiot/HRRequestOperation.h b/code/5-httpriot/HTTPRiot/HRRequestOperation.h new file mode 100644 index 0000000..b059b5f --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRRequestOperation.h @@ -0,0 +1,98 @@ +// +// HRRequestOperation.h +// HTTPRiot +// +// Created by Justin Palmer on 1/30/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// +#import +#import "HRGlobal.h" +#import "HRResponseDelegate.h" + +/** + * The object which all requests are routed through. You shouldn't need to use + * this class directly, but instead use HRRestModel which wraps the method + * of this class neatly. + */ +@interface HRRequestOperation : NSOperation { + /// HRResponse Delegate + NSObject *_delegate; + + /// Connection object + NSURLConnection *_connection; + + /// Data received from response + NSMutableData *_responseData; + + /// The path or URL to use in REST methods + NSString *_path; + + /// Contains all options used by the request. + NSDictionary *_options; + + /// How long before the request will timeout + NSTimeInterval _timeout; + + /// The request method to use + HRRequestMethod _requestMethod; + + /// The HRFormatter object + id _formatter; + + /// The object passed to all delegate methods + id _object; + + /// Determines whether the operation is finished + BOOL _isFinished; + + /// Determines whether the operation is executing + BOOL _isExecuting; + + /// Determines whether the connection is cancelled + BOOL _isCancelled; +} + +/// The HRResponseDelegate +/** + * The HRResponseDelegate responsible for handling the success and failure of + * a request. + */ +@property (nonatomic, readonly, assign) NSObject *delegate; + +/// The lenght of time in seconds before the request times out. +/** + * Sets the length of time in seconds before a request will timeout. + * This defaults to 30.0. + */ +@property (nonatomic, assign) NSTimeInterval timeout; + +/// The REST method to use when performing a request +/** + * This defaults to HRRequestMethodGet. Valid options are ::HRRequestMethod. + */ +@property (nonatomic, assign) HRRequestMethod requestMethod; + +/// The relative path or url string used in a request +/** + If you provide a relative path here, you must set the baseURL option. + If given a full url this will overide the baseURL option. + */ +@property (nonatomic, copy) NSString *path; + +/// An NSDictionary containing all the options for a request. +/** + This needs documented + */ +@property (nonatomic, retain) NSDictionary *options; + +/// The formatter used to decode the response body. +/** + Currently, only JSON is supported. + */ +@property (nonatomic, readonly, retain) id formatter; + +/** + * Returns an HRRequestOperation + */ ++ (HRRequestOperation *)requestWithMethod:(HRRequestMethod)method path:(NSString*)urlPath options:(NSDictionary*)requestOptions object:(id)obj; +@end diff --git a/code/5-httpriot/HTTPRiot/HRRequestOperation.m b/code/5-httpriot/HTTPRiot/HRRequestOperation.m new file mode 100644 index 0000000..eb71c92 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRRequestOperation.m @@ -0,0 +1,356 @@ +// +// HRRequestOperation.m +// HTTPRiot +// +// Created by Justin Palmer on 1/30/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "HRRequestOperation.h" +#import "HRFormatJSON.h" +#import "HRFormatXML.h" +#import "NSObject+InvocationUtils.h" +#import "NSString+EscapingUtils.h" +#import "NSDictionary+ParamUtils.h" +#import "HRBase64.h" +#import "HROperationQueue.h" + +@interface HRRequestOperation (PrivateMethods) +- (NSMutableURLRequest *)http; +- (NSArray *)formattedResults:(NSData *)data; +- (void)setDefaultHeadersForRequest:(NSMutableURLRequest *)request; +- (void)setAuthHeadersForRequest:(NSMutableURLRequest *)request; +- (NSMutableURLRequest *)configuredRequest; +- (id)formatterFromFormat; +- (NSURL *)composedURL; ++ (id)handleResponse:(NSHTTPURLResponse *)response error:(NSError **)error; ++ (NSString *)buildQueryStringFromParams:(NSDictionary *)params; +- (void)finish; +@end + +@implementation HRRequestOperation +@synthesize timeout = _timeout; +@synthesize requestMethod = _requestMethod; +@synthesize path = _path; +@synthesize options = _options; +@synthesize formatter = _formatter; +@synthesize delegate = _delegate; + +- (void)dealloc { + [_path release]; + [_options release]; + [_formatter release]; + [_object release]; + [super dealloc]; +} + +- (id)initWithMethod:(HRRequestMethod)method path:(NSString*)urlPath options:(NSDictionary*)opts object:(id)obj { + + if(self = [super init]) { + _isExecuting = NO; + _isFinished = NO; + _isCancelled = NO; + _requestMethod = method; + _path = [urlPath copy]; + _options = [opts retain]; + _object = [obj retain]; + _timeout = 30.0; + _delegate = [[opts valueForKey:kHRClassAttributesDelegateKey] nonretainedObjectValue]; + _formatter = [[self formatterFromFormat] retain]; + } + + return self; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Concurrent NSOperation Methods +- (void)start { + // Snow Leopard Fix. See http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/ + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; + return; + } + + [self willChangeValueForKey:@"isExecuting"]; + _isExecuting = YES; + [self didChangeValueForKey:@"isExecuting"]; + + NSURLRequest *request = [self configuredRequest]; + HRLOG(@"FETCHING:%@ \nHEADERS:%@", [[request URL] absoluteString], [request allHTTPHeaderFields]); + _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; + + if(_connection) { + _responseData = [[NSMutableData alloc] init]; + } else { + [self finish]; + } +} + +- (void)finish { + HRLOG(@"Operation Finished. Releasing..."); + [_connection release]; + _connection = nil; + + [_responseData release]; + _responseData = nil; + + [self willChangeValueForKey:@"isExecuting"]; + [self willChangeValueForKey:@"isFinished"]; + + _isExecuting = NO; + _isFinished = YES; + + [self didChangeValueForKey:@"isExecuting"]; + [self didChangeValueForKey:@"isFinished"]; +} + +- (void)cancel { + HRLOG(@"SHOULD CANCEL"); + [self willChangeValueForKey:@"isCancelled"]; + + [_connection cancel]; + _isCancelled = YES; + + [self didChangeValueForKey:@"isCancelled"]; + + [self finish]; +} + +- (BOOL)isExecuting { + return _isExecuting; +} + +- (BOOL)isFinished { + return _isFinished; +} + +- (BOOL)isCancelled { + return _isCancelled; +} + +- (BOOL)isConcurrent { + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - NSURLConnection delegates +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { + HRLOG(@"Server responded with:%i, %@", [response statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]); + + if ([_delegate respondsToSelector:@selector(restConnection:didReceiveResponse:object:)]) { + [_delegate performSelectorOnMainThread:@selector(restConnection:didReceiveResponse:object:) withObjects:connection, response, _object, nil]; + } + + NSError *error = nil; + [[self class] handleResponse:(NSHTTPURLResponse *)response error:&error]; + + if(error) { + if([_delegate respondsToSelector:@selector(restConnection:didReceiveError:response:object:)]) { + [_delegate performSelectorOnMainThread:@selector(restConnection:didReceiveError:response:object:) withObjects:connection, error, response, _object, nil]; + [connection cancel]; + [self finish]; + } + } + + [_responseData setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [_responseData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + HRLOG(@"Connection failed: %@", [error localizedDescription]); + if([_delegate respondsToSelector:@selector(restConnection:didFailWithError:object:)]) { + [_delegate performSelectorOnMainThread:@selector(restConnection:didFailWithError:object:) withObjects:connection, error, _object, nil]; + } + + [self finish]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + id results = [NSNull null]; + NSError *parseError = nil; + if([_responseData length] > 0) { + results = [[self formatter] decode:_responseData error:&parseError]; + + if(parseError) { + NSString *rawString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding]; + if([_delegate respondsToSelector:@selector(restConnection:didReceiveParseError:responseBody:object:)]) { + [_delegate performSelectorOnMainThread:@selector(restConnection:didReceiveParseError:responseBody:object:) withObjects:connection, parseError, rawString, _object, nil]; + } + + [rawString release]; + [self finish]; + + return; + } + } + + if([_delegate respondsToSelector:@selector(restConnection:didReturnResource:object:)]) { + [_delegate performSelectorOnMainThread:@selector(restConnection:didReturnResource:object:) withObjects:connection, results, _object, nil]; + } + + [self finish]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Configuration + +- (void)setDefaultHeadersForRequest:(NSMutableURLRequest *)request { + NSDictionary *headers = [[self options] valueForKey:kHRClassAttributesHeadersKey]; + [request setValue:[[self formatter] mimeType] forHTTPHeaderField:@"Content-Type"]; + [request addValue:[[self formatter] mimeType] forHTTPHeaderField:@"Accept"]; + if(headers) { + for(NSString *header in headers) { + NSString *value = [headers valueForKey:header]; + if([header isEqualToString:@"Accept"]) { + [request addValue:value forHTTPHeaderField:header]; + } else { + [request setValue:value forHTTPHeaderField:header]; + } + } + } +} + +- (void)setAuthHeadersForRequest:(NSMutableURLRequest *)request { + NSDictionary *authDict = [_options valueForKey:kHRClassAttributesBasicAuthKey]; + NSString *username = [authDict valueForKey:kHRClassAttributesUsernameKey]; + NSString *password = [authDict valueForKey:kHRClassAttributesPasswordKey]; + + if(username || password) { + NSString *userPass = [NSString stringWithFormat:@"%@:%@", username, password]; + NSData *upData = [userPass dataUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedUserPass = [HRBase64 encode:upData]; + NSString *basicHeader = [NSString stringWithFormat:@"Basic %@", encodedUserPass]; + [request setValue:basicHeader forHTTPHeaderField:@"Authorization"]; + } +} + +- (NSMutableURLRequest *)configuredRequest { + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + [request setTimeoutInterval:_timeout]; + [request setHTTPShouldHandleCookies:YES]; + [self setDefaultHeadersForRequest:request]; + [self setAuthHeadersForRequest:request]; + + NSURL *composedURL = [self composedURL]; + NSDictionary *params = [[self options] valueForKey:kHRClassAttributesParamsKey]; + id body = [[self options] valueForKey:kHRClassAttributesBodyKey]; + NSString *queryString = [[self class] buildQueryStringFromParams:params]; + + if(_requestMethod == HRRequestMethodGet || _requestMethod == HRRequestMethodDelete) { + NSString *urlString = [[composedURL absoluteString] stringByAppendingString:queryString]; + NSURL *url = [NSURL URLWithString:urlString]; + [request setURL:url]; + + if(_requestMethod == HRRequestMethodGet) { + [request setHTTPMethod:@"GET"]; + } else { + [request setHTTPMethod:@"DELETE"]; + } + + } else if(_requestMethod == HRRequestMethodPost || _requestMethod == HRRequestMethodPut) { + + NSData *bodyData = nil; + if([body isKindOfClass:[NSDictionary class]]) { + bodyData = [[body toQueryString] dataUsingEncoding:NSUTF8StringEncoding]; + } else if([body isKindOfClass:[NSString class]]) { + bodyData = [body dataUsingEncoding:NSUTF8StringEncoding]; + } else if([body isKindOfClass:[NSData class]]) { + bodyData = body; + } else { + [NSException exceptionWithName:@"InvalidBodyData" + reason:@"The body must be an NSDictionary, NSString, or NSData" + userInfo:nil]; + } + + [request setHTTPBody:bodyData]; + [request setURL:composedURL]; + + if(_requestMethod == HRRequestMethodPost) + [request setHTTPMethod:@"POST"]; + else + [request setHTTPMethod:@"PUT"]; + + } + + return request; +} + +- (NSURL *)composedURL { + NSURL *tmpURI = [NSURL URLWithString:_path]; + NSURL *baseURL = [_options objectForKey:kHRClassAttributesBaseURLKey]; + + if([tmpURI host] == nil && [baseURL host] == nil) + [NSException raise:@"UnspecifiedHost" format:@"host wasn't provided in baseURL or path"]; + + if([tmpURI host]) + return tmpURI; + + return [NSURL URLWithString:[[baseURL absoluteString] stringByAppendingPathComponent:_path]]; +} + +- (id)formatterFromFormat { + NSNumber *format = [[self options] objectForKey:kHRClassAttributesFormatKey]; + id theFormatter = nil; + switch([format intValue]) { + case HRDataFormatJSON: + theFormatter = [HRFormatJSON class]; + break; + case HRDataFormatXML: + theFormatter = [HRFormatXML class]; + break; + default: + theFormatter = [HRFormatJSON class]; + break; + } + + NSString *errorMessage = [NSString stringWithFormat:@"Invalid Formatter %@", NSStringFromClass(theFormatter)]; + NSAssert([theFormatter conformsToProtocol:@protocol(HRFormatterProtocol)], errorMessage); + + return theFormatter; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Class Methods ++ (HRRequestOperation *)requestWithMethod:(HRRequestMethod)method path:(NSString*)urlPath options:(NSDictionary*)requestOptions object:(id)obj { + id operation = [[self alloc] initWithMethod:method path:urlPath options:requestOptions object:obj]; + [[HROperationQueue sharedOperationQueue] addOperation:operation]; + return [operation autorelease]; +} + ++ (id)handleResponse:(NSHTTPURLResponse *)response error:(NSError **)error { + NSInteger code = [response statusCode]; + NSUInteger ucode = [[NSNumber numberWithInt:code] unsignedIntValue]; + NSRange okRange = NSMakeRange(200, 201); + + if(NSLocationInRange(ucode, okRange)) { + return response; + } + + if(error != nil) { + NSDictionary *headers = [response allHeaderFields]; + NSString *errorReason = [NSString stringWithFormat:@"%d Error: ", code]; + NSString *errorDescription = [NSHTTPURLResponse localizedStringForStatusCode:code]; + NSDictionary *userInfo = [[[NSDictionary dictionaryWithObjectsAndKeys: + errorReason, NSLocalizedFailureReasonErrorKey, + errorDescription, NSLocalizedDescriptionKey, + headers, kHRClassAttributesHeadersKey, + [[response URL] absoluteString], @"url", nil] retain] autorelease]; + *error = [NSError errorWithDomain:HTTPRiotErrorDomain code:code userInfo:userInfo]; + } + + return nil; +} + ++ (NSString *)buildQueryStringFromParams:(NSDictionary *)theParams { + if(theParams) { + if([theParams count] > 0) + return [NSString stringWithFormat:@"?%@", [theParams toQueryString]]; + } + + return @""; +} +@end diff --git a/code/5-httpriot/HTTPRiot/HRResponseDelegate.h b/code/5-httpriot/HTTPRiot/HRResponseDelegate.h new file mode 100644 index 0000000..a858ccc --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRResponseDelegate.h @@ -0,0 +1,66 @@ +/** + * @file HRResponseDelegate.h Protocol for the response delegate methods. + */ + +// +// HRResponseDelegate.h +// HTTPRiot +// +// Created by Justin Palmer on 6/24/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +/** + * @protocol HRResponseDelegate + * + * Implementing the HRResponseDelegate protocol allows you to handle requests. + */ +@protocol HRResponseDelegate +@optional +/** + * Called when the resource was succeffully fetched and encoded + * + * @param connection The connection object for the current request + * @param resource The converted objc representation of the response data returned by the server. + * @param object Any custom object you passed in while making the request. + */ +- (void)restConnection:(NSURLConnection *)connection didReturnResource:(id)resource object:(id)object; + +/** + * Called when the connection fails in situations where the server is not available, etc. + * + * @param connection The connection object for the current request + * @param error The error returned by the connection. + * @param object Any custom object you passed in while making the request. + */ +- (void)restConnection:(NSURLConnection *)connection didFailWithError:(NSError *)error object:(id)object; + +/** + * Called when the connection receieves any type of response + * + * @param connection The connection object for the current request + * @param response The response object returned by the server. + * @param object Any custom object you passed in while making the request. + */ +- (void)restConnection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response object:(id)object; + +/** + * Called when the connection receieves a statusCode that isn't a success code. + * + * @param connection The connection object for the current request + * @param error The error returned by the connection. + * @param response The response object returned by the server. + * @param object Any custom object you passed in while making the request. + */ +- (void)restConnection:(NSURLConnection *)connection didReceiveError:(NSError *)error response:(NSHTTPURLResponse *)response object:(id)object; + +/** + * Called when the HRFormatter recieved an error parsing the response data. + * + * @param connection The connection object for the current request + * @param error The parser error returned by the formatter. + * @param body A string representation of the response body returned by the server. + * @param object Any custom object you passed in while making the request. + */ +- (void)restConnection:(NSURLConnection *)connection didReceiveParseError:(NSError *)error responseBody:(NSString *)body object:(id)object; +@end diff --git a/code/5-httpriot/HTTPRiot/HRRestModel.h b/code/5-httpriot/HTTPRiot/HRRestModel.h new file mode 100644 index 0000000..d208eff --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRRestModel.h @@ -0,0 +1,205 @@ +// +// HRRestModel.h +// HTTPRiot +// +// Created by Justin Palmer on 1/28/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// +#import +#import "HRGlobal.h" + +/** + * This class allows you to easily interact with RESTful resources. Responses are automatically + * converted to the proper Objective-C type. You can use this class directly or subclass it. + * + * Using this class directly means that all requests share the same configuration + * (including delegate). This works fine for simple situations but when you start dealing + * with different resource types it's best to subclass HRRestModel, giving each class its + * own set of configuation options. + * + * + * In the code below all requests originating from Person will have an api_key + * default parameter, the same base url, and the same delegate. See HRResponseDelegate for the + * the delegate methods available to you. + * + * @code + * @implementation Person + * + (void)initialize { + * [self setDelegate:self]; + * NSDictionary *params = [NSDictionary dictionaryWithObject:@"1234567" forKey:@"api_key"]; + * [self setBaseURL:[NSURL URLWithString:@"http://localhost:1234/api"]]; + * [self setDefaultParameters:params]; + * } + * + * + (void)restConnection:(NSURLConnection *)connection didReturnResource:(id)resource object:(id)object { + * for(id person in resource) { + * // do something with a person dictionary + * } + * } + * @end + * + * // Would send a request to http://localhost:1234/api/people/1?api_key=1234567 + * [Person getPath:@"/people/1" withOptions:nil object:nil]; + * @endcode + * + *

A note on default properties and subclassing

+ * Each subclass has its own set of unique properties and these properties are not + * inherited by any additional subclasses. + */ +@interface HRRestModel : NSObject { + +} + +/** + * @name Setting default request options + * Set the default options that can be used in every request made from the model + * that sets them. + * @{ + */ + +/** + * Returns the HRResponseDelegate + */ ++ (NSObject *)delegate; + +/** +* Set the HRResponseDelegate +* @param del The HRResponseDelegate responsible for handling callbacks +*/ ++ (void)setDelegate:(NSObject *)del; + +/** + * The base url to use in every request + */ ++ (NSURL *)baseURL; + +/** + * Set the base URL to be used in every request. + * + * Instead of providing a URL for every request you can set the base + * url here. You can also provide a path and port if you wish. This + * url is prepended to the path argument of the request methods. + * + * @param url The base uri used in all request + */ ++ (void)setBaseURL:(NSURL *)url; + +/** + * Default headers sent with every request + */ ++ (NSDictionary *)headers; + +/** + * Set the default headers sent with every request. + * @param hdrs An NSDictionary of headers. For example you can + * set this up. + * + * @code + * NSDictionary *hdrs = [NSDictionary dictionaryWithObject:@"application/json" forKey:@"Accept"]; + * [self setHeaders:hdrs]; + * @endcode + */ ++ (void)setHeaders:(NSDictionary *)hdrs; + +/** + * Returns a dictionary containing the username and password used for basic auth. + */ ++ (NSDictionary *)basicAuth; + +/** + * Set the username and password used in requests that require basic authentication. + * + * The username and password privded here will be Base64 encoded and sent as an + * Authorization header. + * @param username user name used to authenticate + * @param password Password used to authenticate + */ ++ (void)setBasicAuthWithUsername:(NSString *)username password:(NSString *)password; + +/** + * Default params sent with every request. + */ ++ (NSDictionary *)defaultParams; + +/** + * Set the defaul params sent with every request. + * If you need to send something with every request this is the perfect way to do it. + * For GET request, these parameters will be appended to the query string. For + * POST request these parameters are sent with the body. + */ ++ (void)setDefaultParams:(NSDictionary *)params; + +/** + * The format used to decode and encode request and responses. + * Supported formats are JSON and XML. + */ ++ (HRDataFormat)format; + +/** + * Set the format used to decode and encode request and responses. + */ ++ (void)setFormat:(HRDataFormat)format; +//@} + +/** + * @name Sending Requests + * These methods allow you to send GET, POST, PUT and DELETE requetsts. + * + *

Request Options

+ * All requests can take numerous types of options passed as the second argument. + * @li @b headers NSDictionary - The headers to send with the request + * @li @b params NSDictionary - The query or body parameters sent with the request. + * @li @b body NSData, NSString or NSDictionary - This option is used only during POST and PUT + * requests. This option is eventually transformed into an NSData object before it is sent. + * If you supply the body as an NSDictionary it's turned to a query string &foo=bar&baz=boo and + * then it's encoded as an NSData object. If you supply an NSString, it's encoded as an NSData + * object and sent. + * @{ + */ + +/** + * Send a GET request + * @param path The path to get. If you haven't setup the baseURL option you'll need to provide a + * full url. + * @param options The options for this request. + * @param object An object to be passed to the delegate methods + * + */ ++ (NSOperation *)getPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)object; + +/** + * Send a POST request + * @param path The path to POST to. If you haven't setup the baseURL option you'll need to provide a + * full url. + * @param options The options for this request. + * @param object An object to be passed to the delegate methods + * + * Note: If you'd like to post raw data like JSON or XML you'll need to set the body option. + * + */ ++ (NSOperation *)postPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)object; + +/** + * Send a PUT request + * @param path The path to PUT to. If you haven't setup the baseURL option you'll need to provide a + * full url. + * @param options The options for this request. + * @param object An object to be passed to the delegate methods + * + * @remarks Note: All data found in the body option will be PUT. Setting the body + * option will cause the params option to be ignored. + * + */ ++ (NSOperation *)putPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)object; + +/** + * Send a DELETE request + * @param path The path to DELETE. If you haven't setup the baseURL option you'll need to provide a + * full url. + * @param options The options for this request. + * @param object An object to be passed to the delegate methods + * + */ ++ (NSOperation *)deletePath:(NSString *)path withOptions:(NSDictionary *)options object:(id)object; +//@} +@end diff --git a/code/5-httpriot/HTTPRiot/HRRestModel.m b/code/5-httpriot/HTTPRiot/HRRestModel.m new file mode 100644 index 0000000..6249638 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HRRestModel.m @@ -0,0 +1,141 @@ +// +// HRRestModel.m +// HTTPRiot +// +// Created by Justin Palmer on 1/28/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "HRRestModel.h" +#import "HRRequestOperation.h" +#import "HRGlobal.h" + +@interface HRRestModel (PrivateMethods) ++ (void)setAttributeValue:(id)attr forKey:(NSString *)key; ++ (NSMutableDictionary *)classAttributes; ++ (NSMutableDictionary *)mergedOptions:(NSDictionary *)options; ++ (NSOperation *)requestWithMethod:(HRRequestMethod)method path:(NSString *)path options:(NSDictionary *)options object:(id)obj; +@end + +@implementation HRRestModel +static NSMutableDictionary *attributes; ++ (void)initialize { + if(!attributes) + attributes = [[NSMutableDictionary alloc] init]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Class Attributes + +// Given that we want to allow classes to define default attributes we need to create +// a classname-based dictionary store that maps a subclass name to a dictionary +// containing its attributes. ++ (NSMutableDictionary *)classAttributes { + NSString *className = NSStringFromClass([self class]); + + NSMutableDictionary *newDict; + NSMutableDictionary *dict = [attributes objectForKey:className]; + + if(dict) { + return dict; + } else { + newDict = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:HRDataFormatJSON] forKey:@"format"]; + [attributes setObject:newDict forKey:className]; + } + + return newDict; +} + ++ (NSObject *)delegate { + return [[self classAttributes] objectForKey:kHRClassAttributesDelegateKey]; +} + ++ (void)setDelegate:(NSObject *)del { + [self setAttributeValue:[NSValue valueWithNonretainedObject:del] forKey:kHRClassAttributesDelegateKey]; +} + ++ (NSURL *)baseURL { + return [[self classAttributes] objectForKey:kHRClassAttributesBaseURLKey]; +} + ++ (void)setBaseURL:(NSURL *)uri { + [self setAttributeValue:uri forKey:kHRClassAttributesBaseURLKey]; +} + ++ (NSDictionary *)headers { + return [[self classAttributes] objectForKey:kHRClassAttributesHeadersKey]; +} + ++ (void)setHeaders:(NSDictionary *)hdrs { + [self setAttributeValue:hdrs forKey:kHRClassAttributesHeadersKey]; +} + ++ (NSDictionary *)basicAuth { + return [[self classAttributes] objectForKey:kHRClassAttributesBasicAuthKey]; +} + ++ (void)setBasicAuthWithUsername:(NSString *)username password:(NSString *)password { + NSDictionary *authDict = [NSDictionary dictionaryWithObjectsAndKeys:username, kHRClassAttributesUsernameKey, password, kHRClassAttributesPasswordKey, nil]; + [self setAttributeValue:authDict forKey:kHRClassAttributesBasicAuthKey]; +} + ++ (HRDataFormat)format { + return [[[self classAttributes] objectForKey:kHRClassAttributesFormatKey] intValue]; +} + ++ (void)setFormat:(HRDataFormat)format { + [[self classAttributes] setValue:[NSNumber numberWithInt:format] forKey:kHRClassAttributesFormatKey]; +} + ++ (NSDictionary *)defaultParams { + return [[self classAttributes] objectForKey:kHRClassAttributesDefaultParamsKey]; +} + ++ (void)setDefaultParams:(NSDictionary *)params { + [self setAttributeValue:params forKey:kHRClassAttributesDefaultParamsKey]; +} + ++ (void)setAttributeValue:(id)attr forKey:(NSString *)key { + [[self classAttributes] setObject:attr forKey:key]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - REST Methods + ++ (NSOperation *)getPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)obj { + return [self requestWithMethod:HRRequestMethodGet path:path options:options object:obj]; +} + ++ (NSOperation *)postPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)obj { + return [self requestWithMethod:HRRequestMethodPost path:path options:options object:obj]; +} + ++ (NSOperation *)putPath:(NSString *)path withOptions:(NSDictionary *)options object:(id)obj { + return [self requestWithMethod:HRRequestMethodPut path:path options:options object:obj]; +} + ++ (NSOperation *)deletePath:(NSString *)path withOptions:(NSDictionary *)options object:(id)obj { + return [self requestWithMethod:HRRequestMethodDelete path:path options:options object:obj]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Private + ++ (NSOperation *)requestWithMethod:(HRRequestMethod)method path:(NSString *)path options:(NSDictionary *)options object:(id)obj { + NSMutableDictionary *opts = [self mergedOptions:options]; + return [HRRequestOperation requestWithMethod:method path:path options:opts object:obj]; +} + ++ (NSMutableDictionary *)mergedOptions:(NSDictionary *)options { + NSMutableDictionary *defaultParams = [NSMutableDictionary dictionaryWithDictionary:[self defaultParams]]; + [defaultParams addEntriesFromDictionary:[options valueForKey:kHRClassAttributesParamsKey]]; + + options = [NSMutableDictionary dictionaryWithDictionary:options]; + [(NSMutableDictionary *)options setObject:defaultParams forKey:kHRClassAttributesParamsKey]; + NSMutableDictionary *opts = [NSMutableDictionary dictionaryWithDictionary:[self classAttributes]]; + [opts addEntriesFromDictionary:options]; + [opts removeObjectForKey:kHRClassAttributesDefaultParamsKey]; + + return opts; +} +@end diff --git a/code/5-httpriot/HTTPRiot/HTTPRiot.h b/code/5-httpriot/HTTPRiot/HTTPRiot.h new file mode 100644 index 0000000..94493c9 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HTTPRiot.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009 Justin Palmer , 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 HTTPRiot - A simple HTTP REST Library + +@image html httpriot.png + +@li HTTPRiot homepage +@li Source Code +@li Twitter + +HTTPRiot is a simple REST library designed to make interacting with REST services +much easier. It supports GET, POST, PUSH and DELETE requests and HTTP Basic Authentication. +HTTPRiot was inspired by John Nunemaker's excellent +httparty Ruby library. + +
+ Related Guides +
    +
  • @subpage iphone-setup
  • +
  • @subpage cocoa-setup
  • +
+
+ +

Some Examples

+ +

Send a GET request

+@code +[HRRestModel getPath:@"/person.json" withOptions:nil object:nil]; +@endcode + +

Send a POST request with JSON body data

+@code +NSDictionary *opts = [NSDictionary dictionaroyWithObject:[person JSONRepresentation] forKey:@"body"]; +[HRRestModel postPath:@"/person" withOptions:opts object:nil]; +@endcode + +

Send a PUT request

+@code +NSDictionary *opts = [NSDictionary dictionaroyWithObject:[updatedPerson JSONRepresentation] forKey:@"body"]; +[HRRestModel putPath:@"/person" withOptions:opts object:nil]; +@endcode + +

Send a DELETE request

+@code +[HRRestModel deletePath:@"/person/1" withOptions:nil object:nil]; +@endcode + +

Subclassing HRRestModel

+Although you can use HTTPRiot straight out of the box by itself, this approach has some pitfals. +Every request will share the same configuation options. By subclassing HRRestModel you can have +per-class configuation options meaning that all requests generating from your subclass share a +local set of configuation options and will not affect other requests not originating from your subclass. + +@include Tweet.m + +@page iphone-setup Using the HTTPRiot Framework in your iPhone Applications + +HTTPRiot comes with a simple SDK package that makes it very easy to get up and running quickly +on the iphone. You'll need to put this SDK package somewhere where it won't get deleted and you +can share it with all your iPhone projects. + +

NOTE: Make sure you select "All Configurations" in the Build tab before changing any settings.

+ +-# Move the httpriot-* directory to ~/Library/SDKs. You might need to create this directory. + It's not mandatory that it lives in this location, but it's a good idea that you put it somewhere + where it can be shared. +-# Create a new project or open an existing project in XCode. Select your application's target and + press ⌘i to bring up the properties window. Set the Additional SDKs + property to ~/Library/SDKs/httpriot-0.4.0/\$(PLATFORM_NAME)\$(IPHONEOS_DEPLOYMENT_TARGET).sdk + @image html additional-sdks.png +-# Set the Other Linker Flags to -lhttpriot -lxml2 -ObjC -all_load + @image html other-linker-flags.png +-# Set Header Search Paths to /usr/include/libxml2/** +-# Use \#include in one of your application's files. + That's it! Now you're ready to use HTTPRiot! + +@page cocoa-setup Using the HTTPRiot Framework in your Desktop Applications + +-# Right click Other Frameworks in XCode and select Add → Existing Frameworks. Select + the HTTPRiot.framework and press Add. @image html httpriot-framework.png +-# Include the framework \#include in your project. That's it! + +

Embedding HTTPRiot.framework in your application

+If you want to distribute HTTPRiot.framework with your application you'll need to do another step. + +-# Right click your target name and select "Add > New Build Phase > New Copy Files Build Phase". + Set Frameworks as the destination path in the popup. @image html copy-files.png +-# Drag the HTTPRiot.framework file to this new phase. +*/ + +#import + +#import "AIXMLSerialization.h" +#import "JSON.h" + +#import "HROperationQueue.h" +#import "HRRequestOperation.h" +#import "HRRestModel.h" +#import "HRResponseDelegate.h" \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/HTTPRiot_Prefix.pch b/code/5-httpriot/HTTPRiot/HTTPRiot_Prefix.pch new file mode 100644 index 0000000..2e9237d --- /dev/null +++ b/code/5-httpriot/HTTPRiot/HTTPRiot_Prefix.pch @@ -0,0 +1 @@ +#import "HRGlobal.h" \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/LICENSE b/code/5-httpriot/HTTPRiot/LICENSE new file mode 100644 index 0000000..48b3969 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2009 Justin Palmer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/code/5-httpriot/HTTPRiot/Utilities/HRBase64.h b/code/5-httpriot/HTTPRiot/Utilities/HRBase64.h new file mode 100644 index 0000000..7ccda80 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Utilities/HRBase64.h @@ -0,0 +1,18 @@ +// +// HRBase64.h +// HTTPRiot +// +// Created by Justin Palmer on 7/2/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// +// This was taken from Cyrus' Public Domain implementation on the bottom of +// http://www.cocoadev.com/index.pl?BaseSixtyFour. +// +#import + + +@interface HRBase64 : NSObject { + +} ++ (NSString*) encode:(NSData*)rawBytes; +@end diff --git a/code/5-httpriot/HTTPRiot/Utilities/HRBase64.m b/code/5-httpriot/HTTPRiot/Utilities/HRBase64.m new file mode 100644 index 0000000..878995a --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Utilities/HRBase64.m @@ -0,0 +1,124 @@ +// +// HRBase64.m +// HTTPRiot +// +// Created by Justin Palmer on 7/2/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "HRBase64.h" + +@interface NSData (MBBase64) ++ (id)dataWithBase64EncodedString:(NSString *)string; // Padding '=' characters are optional. Whitespace is ignored. +- (NSString *)base64Encoding; +@end + +static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + +@implementation NSData (MBBase64) + ++ (id)dataWithBase64EncodedString:(NSString *)string { + if (string == nil) + [NSException raise:NSInvalidArgumentException format:nil]; + if ([string length] == 0) + return [NSData data]; + + static char *decodingTable = NULL; + if (decodingTable == NULL) + { + decodingTable = malloc(256); + if (decodingTable == NULL) + return nil; + memset(decodingTable, CHAR_MAX, 256); + NSUInteger i; + for (i = 0; i < 64; i++) + decodingTable[(short)encodingTable[i]] = i; + } + + const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding]; + if (characters == NULL) // Not an ASCII string! + return nil; + char *bytes = malloc((([string length] + 3) / 4) * 3); + if (bytes == NULL) + return nil; + NSUInteger length = 0; + + NSUInteger i = 0; + while (YES) + { + char buffer[4]; + short bufferLength; + for (bufferLength = 0; bufferLength < 4; i++) + { + if (characters[i] == '\0') + break; + if (isspace(characters[i]) || characters[i] == '=') + continue; + buffer[bufferLength] = decodingTable[(short)characters[i]]; + if (buffer[bufferLength++] == CHAR_MAX) // Illegal character! + { + free(bytes); + return nil; + } + } + + if (bufferLength == 0) + break; + if (bufferLength == 1) // At least two characters are needed to produce one byte! + { + free(bytes); + return nil; + } + + // Decode the characters in the buffer to bytes. + bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4); + if (bufferLength > 2) + bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2); + if (bufferLength > 3) + bytes[length++] = (buffer[2] << 6) | buffer[3]; + } + + realloc(bytes, length); + return [NSData dataWithBytesNoCopy:bytes length:length]; +} + +- (NSString *)base64Encoding { + if ([self length] == 0) + return @""; + + char *characters = malloc((([self length] + 2) / 3) * 4); + if (characters == NULL) + return nil; + NSUInteger length = 0; + + NSUInteger i = 0; + while (i < [self length]) + { + char buffer[3] = {0,0,0}; + short bufferLength = 0; + while (bufferLength < 3 && i < [self length]) + buffer[bufferLength++] = ((char *)[self bytes])[i++]; + + // Encode the bytes in the buffer to four characters, including padding "=" characters if necessary. + characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2]; + characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)]; + if (bufferLength > 1) + characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)]; + else characters[length++] = '='; + if (bufferLength > 2) + characters[length++] = encodingTable[buffer[2] & 0x3F]; + else characters[length++] = '='; + } + + NSString *str = [[[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; + return [str stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; +} + +@end + +@implementation HRBase64 ++ (NSString*) encode:(NSData *) rawBytes { + return [rawBytes base64Encoding]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.h b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.h new file mode 100644 index 0000000..13ba35b --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.h @@ -0,0 +1,14 @@ +// +// NSXMLDocument+Serialize.h +// AIXMLSerialize +// +// Created by Justin Palmer on 2/24/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// +#import +#import "AIXMLSerialization.h" + + +@interface NSXMLDocument (Serialize) +- (NSMutableDictionary *)toDictionary; +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.m b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.m new file mode 100644 index 0000000..2d349fd --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLDocumentSerialize.m @@ -0,0 +1,21 @@ +// +// NSXMLDocument+Serialize.m +// AIXMLSerialize +// +// Created by Justin Palmer on 2/24/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "AIXMLDocumentSerialize.h" +#import "AIXMLElementSerialize.h" + +@implementation NSXMLDocument (Serialize) +/** + * Convert NSXMLDocument to an NSDictionary + * @see NSXMLElement#toDictionary + */ +- (NSMutableDictionary *)toDictionary +{ + return [[self rootElement] toDictionary]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.h b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.h new file mode 100644 index 0000000..f4bfc24 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.h @@ -0,0 +1,191 @@ +// +// NSXMLElement+Serialize.h +// AIXMLSerialize +// +// Created by Justin Palmer on 2/24/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// +#import +#import "AIXMLSerialization.h" + +@interface NSXMLElement (Serialize) +/** + * Return this elements attributes as an NSDictionary + */ +- (NSDictionary *)attributesAsDictionary; + +/** + * Transform this NSXMLElement and all of its children to an NSDictionary. + * + * Given the XML below: + * @code + * + * + * + * + * + * Providing for consideration of motions to suspend the rules, and for other purposes. + * + * + * + * + * + * + * The House Committee on Rules reported an original measure, H. Rept. 111-14, by Mr. Perlmutter. + * The resolutions authorizes the Speaker to entertain motions that the House suspend the rules at any time through the legislative day of February 13, 2009. The resolution also provides that the Speaker or her designee shall consult with the Minority Leader or his designee on the designation of any matter for consideration under suspension of the rules pursuant to the resolution. The resolution also provides that H. Res. 10 is amended to change the hour of daily meeting of the House to 9:00 a.m. for Fridays and Saturdays. + * Placed on the House Calendar, Calendar No. 9. + * Considered as privileged matter. + * DEBATE - The House proceeded with one hour of debate on H. Res. 157. + * The previous question was ordered without objection. + * POSTPONED PROCEEDINGS - At the conclusion of debate on H.Res. 157, the Chair put the question on the agreeing to the resolution, and by voice vote, announced that the ayes had prevailed. Ms. Foxx demanded the yeas and nays and the Chair postponed further proceedings on the resolution until later in the legislative day. + * Considered as unfinished business. + * On agreeing to the resolution Agreed to by the Yeas and Nays: 248 - 174 (Roll no. 63). + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @endcode + * + * Generates an NSDictionary + * @code + * bill = { + * actions = { + * action = ( + * { + * date = 1234392180; + * datetime = "2009-02-11T18:43:00-05:00"; + * text = "The House Committee on Rules reported an original measure, H. Rept. 111-14, by Mr. Perlmutter."; + * }, + * { + * date = 1234392180; + * datetime = "2009-02-11T18:43:00-05:00"; + * text = "The resolutions authorizes the Speaker to entertain motions that the House suspend the rules at any time through the legislative day of February 13, 2009. The resolution also provides that the Speaker or her designee shall consult with the Minority Leader or his designee on the designation of any matter for consideration under suspension of the rules pursuant to the resolution. The resolution also provides that H. Res. 10 is amended to change the hour of daily meeting of the House to 9:00 a.m. for Fridays and Saturdays."; + * }, + * { + * date = 1234449000; + * datetime = "2009-02-12T10:30:00-05:00"; + * reference = { + * label = consideration; + * ref = "CR H1254-1260"; + * }; + * text = "Considered as privileged matter."; + * }, + * { + * date = 1234449000; + * datetime = "2009-02-12T10:30:00-05:00"; + * text = "DEBATE - The House proceeded with one hour of debate on H. Res. 157."; + * }, + * { + * date = 1234452420; + * datetime = "2009-02-12T11:27:00-05:00"; + * reference = { + * label = consideration; + * ref = "CR H1260"; + * }; + * text = "The previous question was ordered without objection."; + * }, + * { + * date = 1234452420; + * datetime = "2009-02-12T11:27:00-05:00"; + * text = "POSTPONED PROCEEDINGS - At the conclusion of debate on H.Res. 157, the Chair put the question on the agreeing to the resolution, and by voice vote, announced that the ayes had prevailed. Ms. Foxx demanded the yeas and nays and the Chair postponed further proceedings on the resolution until later in the legislative day."; + * }, + * { + * date = 1234458060; + * datetime = "2009-02-12T13:01:00-05:00"; + * reference = { + * label = consideration; + * ref = "CR H1261"; + * }; + * text = "Considered as unfinished business."; + * } + * ); + * calendar = { + * date = 1234392240; + * datetime = "2009-02-11T18:44:00-05:00"; + * text = "Placed on the House Calendar, Calendar No. 9."; + * }; + * vote = { + * date = 1234459620; + * datetime = "2009-02-12T13:27:00-05:00"; + * how = roll; + * reference = { + * label = text; + * ref = "CR H1255"; + * }; + * result = pass; + * roll = 63; + * text = "On agreeing to the resolution Agreed to by the Yeas and Nays: 248 - 174 (Roll no. 63)."; + * type = vote; + * where = h; + * }; + * }; + * amendments = { + * }; + * committees = { + * committee = { + * activity = "Origin, Reporting"; + * name = "House Rules"; + * subcommittee = ""; + * }; + * }; + * cosponsors = { + * }; + * introduced = { + * date = 1234328400; + * datetime = "2009-02-11"; + * }; + * number = 157; + * relatedbills = { + * bill = { + * number = 10; + * relation = unknown; + * session = 111; + * type = hr; + * }; + * }; + * session = 111; + * sponsor = { + * id = 412192; + * }; + * status = { + * vote = { + * date = 1234459620; + * datetime = "2009-02-12T13:27:00-05:00"; + * how = roll; + * result = pass; + * roll = 63; + * where = h; + * }; + * }; + * subjects = { + * }; + * summary = { + * }; + * titles = { + * title = { + * as = introduced; + * content = "Providing for consideration of motions to suspend the rules, and for other purposes."; + * type = official; + * }; + * }; + * type = hr; + * updated = "2009-02-14T16:25:06-05:00"; + * }; + * } + * @endcode + */ +- (NSMutableDictionary *)toDictionary; +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.m b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.m new file mode 100644 index 0000000..931c783 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLElementSerialize.m @@ -0,0 +1,147 @@ +// +// NSXMLElement+Serialize.m +// AIXMLSerialize +// +// Created by Justin Palmer on 2/24/09. +// Copyright 2009 LabratRevenge LLC.. All rights reserved. +// + +#import "AIXMLElementSerialize.h" + +@implementation NSXMLElement (Serialize) + +// Should this be configurable? Ruby's XmlSimple handles nodes with +// string values and attributes by assigning the string value to a +// 'content' key, although that seems like a pretty generic key which +// could cause collisions if an element has a 'content' attribute. +static NSString *contentItem; ++ (void)initialize +{ + if(!contentItem) + contentItem = @"content"; +} + +- (NSDictionary *)attributesAsDictionary +{ + NSArray *attributes = [self attributes]; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[attributes count]]; + + uint i; + for(i = 0; i < [attributes count]; i++) + { + NSXMLNode *node = [attributes objectAtIndex:i]; + [result setObject:[node stringValue] forKey:[node name]]; + } + return result; +} + +- (NSMutableDictionary *)toDictionary +{ + id out, rawObj, nodeObj; + NSXMLNode *node; + NSArray *nodes = [self children]; + NSString *elName = [self name], *key; + NSDictionary *attrs = [self attributesAsDictionary]; + NSString *type = [attrs valueForKey:@"type"]; + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + NSMutableArray *objs; + + for(node in nodes) + { + // It's an element, lets create the proper groups for these elements + // consolidating any duplicate elements at this level. + if([node kind] == NSXMLElementKind) + { + NSString *childName = [node name]; + NSMutableArray *group = [groups objectForKey:childName]; + if(!group) + { + group = [NSMutableArray array]; + [groups setObject:group forKey:childName]; + } + + [group addObject:node]; + } + + // We're on a text node so the parent node will be this nodes name. + // Once we get done parsing this text node we can go ahead and return + // its dictionary rep because there is no need for further processing. + else if([node kind] == NSXMLTextKind) + { + NSXMLElement *containerObj = (NSXMLElement *)[node parent]; + NSDictionary *nodeAttrs = [containerObj attributesAsDictionary]; + NSString *contents = [node stringValue]; + + + // If this node has attributes and content text we need to + // create a dictionary for it and use the static contentItem + // value as a place to store the stringValue. + if([nodeAttrs count] > 0 && contents) + { + nodeObj = [NSMutableDictionary dictionaryWithObject:contents forKey:contentItem]; + [nodeObj addEntriesFromDictionary:nodeAttrs]; + } + // Else this node only has a string value or is empty so we set + // it's value to a string. + else + { + nodeObj = contents; + } + + return [NSMutableDictionary dictionaryWithObject:nodeObj forKey:[containerObj name]]; + } + } + + // Array + // We have an element who says it's children should be treated as an array. + // Instead of creating {:child_name => {:other, :attrs}} children, we create + // an array of anonymous dictionaries. [{:other, :attrs}, {:other, :attrs}] + if([type isEqualToString:@"array"]) + { + out = [NSMutableArray array]; + for(key in groups) + { + NSMutableDictionary *dictRep; + objs = [groups objectForKey:key]; + for(rawObj in objs) + { + dictRep = [rawObj toDictionary]; + [out addObject:[dictRep valueForKey:key]]; + } + } + } + + // Dictionary + else + { + out = [NSMutableDictionary dictionary]; + for(key in groups) + { + NSMutableDictionary *dictRep; + objs = [groups objectForKey:key]; + if([objs count] == 1) + { + dictRep = [[objs objectAtIndex:0] toDictionary]; + [out addEntriesFromDictionary:dictRep]; + } + else + { + NSMutableArray *dictCollection = [NSMutableArray array]; + for(rawObj in objs) + { + dictRep = [rawObj toDictionary]; + id finalItems = [dictRep valueForKey:key]; + [dictCollection addObject:finalItems]; + } + + [out setObject:dictCollection forKey:key]; + } + } + + if([attrs count] > 0) + [out addEntriesFromDictionary:attrs]; + } + + return [NSMutableDictionary dictionaryWithObject:out forKey:elName]; +} +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLSerialization.h b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLSerialization.h new file mode 100644 index 0000000..b3a2fe5 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/AIXMLSerialization/AIXMLSerialization.h @@ -0,0 +1,62 @@ +#import + +#if TARGET_OS_IPHONE + +#import "DDXML.h" + +#ifndef NSXMLNode +#define NSXMLNode DDXMLNode +#endif +#ifndef NSXMLElement +#define NSXMLElement DDXMLElement +#endif +#ifndef NSXMLDocument +#define NSXMLDocument DDXMLDocument +#endif + +#ifndef NSXMLNodeKind +#define NSXMLInvalidKind DDXMLInvalidKind +#define NSXMLDocumentKind DDXMLDocumentKind +#define NSXMLElementKind DDXMLElementKind +#define NSXMLAttributeKind DDXMLAttributeKind +#define NSXMLNamespaceKind DDXMLNamespaceKind +#define NSXMLProcessingInstructionKind DDXMLProcessingInstructionKind +#define NSXMLCommentKind DDXMLCommentKind +#define NSXMLTextKind DDXMLTextKind +#define NSXMLDTDKind DDXMLDTDKind +#define NSXMLEntityDeclarationKind DDXMLEntityDeclarationKind +#define NSXMLAttributeDeclarationKind DDXMLAttributeDeclarationKind +#define NSXMLElementDeclarationKind DDXMLElementDeclarationKind +#define NSXMLNotationDeclarationKind DDXMLNotationDeclarationKind + +#define NSXMLNodeKind DDXMLNodeKind; +#endif + +#ifndef NSXMLNodeOptionsNone +#define NSXMLNodeOptionsNone DDXMLNodeOptionsNone +#define NSXMLNodeExpandEmptyElement DDXMLNodeExpandEmptyElement +#define NSXMLNodeCompactEmptyElement DDXMLNodeCompactEmptyElement +#define NSXMLNodePrettyPrint DDXMLNodePrettyPrint +#endif + +#ifndef NSXMLDocumentXMLKind +#define NSXMLDocumentXMLKind DDXMLDocumentXMLKind +#define NSXMLDocumentXHTMLKind DDXMLDocumentXHTMLKind +#define NSXMLDocumentHTMLKind DDXMLDocumentHTMLKind +#define NSXMLDocumentTextKind DDXMLDocumentTextKind + +#define NSXMLDocumentContentKind DDXMLDocumentContentKind +#endif + +#ifndef NSXMLDocumentTidyHTML +#define NSXMLDocumentTidyHTML 1 << 9 +#define NSXMLDocumentTidyXML 1 << 10 +#define NSXMLDocumentValidate 1 << 13 +#define NSXMLDocumentXInclude 1 << 16 +#endif + +#endif + + +#import "AIXMLDocumentSerialize.h" +#import "AIXMLElementSerialize.h" \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/CREDITS b/code/5-httpriot/HTTPRiot/Vendor/JSON/CREDITS new file mode 100644 index 0000000..8f9c0cf --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/CREDITS @@ -0,0 +1,19 @@ +Blake Seely + A lot of the inspiration to early versions of this framework + came from Blake's BSJSONAdditions project. + +Marc Lehmann + Part of the inspiration for my JSON work has been from Marc's + JSON::XS Perl module. I also adopted the way he organised the + tests. Having a separate fixture for testing error conditions + makes a lot of sense. + +Jens Alfke - http://mooseyard.com/Jens + Jens emailed me out of the blue one day with a couple of patches + that gave a speedup of 11x for generation and 5x for parsing of + the long (12k) JSON string I've been using for testing. + +Greg Bolsinga + Provided patches for dropping the dependency on AppKit and thus + truly making this a Foundation framework, and for building a + static library suitable for use with the iPhone. diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/JSON.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/JSON.h @@ -0,0 +1,50 @@ +/* + 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. + */ + +/** + @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. + + Learn more on the http://code.google.com/p/json-framework project site. + + 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. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + 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 + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + 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+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + 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 + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-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 "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-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 +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-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 "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + 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 value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + 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 + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + 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,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + 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 "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @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; + +@end + + +/** + @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 -> 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.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.m new file mode 100644 index 0000000..0fdfb36 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + 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 "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + ++ (void)initialize +{ + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.h b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + 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 +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + 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 throw an exception. + + 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 SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @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)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.m b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.m new file mode 100644 index 0000000..03718b9 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/JSON/SBJsonWriter.m @@ -0,0 +1,228 @@ +/* + 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 "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + static NSMutableCharacterSet *kEscapeChars; + if( ! kEscapeChars ) { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; + } + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXML.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXML.h new file mode 100644 index 0000000..2cf21a5 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXML.h @@ -0,0 +1,3 @@ +#import "DDXMLNode.h" +#import "DDXMLElement.h" +#import "DDXMLDocument.h" diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.h new file mode 100644 index 0000000..68f1b2e --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.h @@ -0,0 +1,67 @@ +#import +#import "DDXMLElement.h" +#import "DDXMLNode.h" + + +enum { + DDXMLDocumentXMLKind = 0, + DDXMLDocumentXHTMLKind, + DDXMLDocumentHTMLKind, + DDXMLDocumentTextKind +}; +typedef NSUInteger DDXMLDocumentContentKind; + +@interface DDXMLDocument : DDXMLNode +{ +} + +- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error; +//- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error; +- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error; +//- (id)initWithRootElement:(DDXMLElement *)element; + +//+ (Class)replacementClassForClass:(Class)cls; + +//- (void)setCharacterEncoding:(NSString *)encoding; //primitive +//- (NSString *)characterEncoding; //primitive + +//- (void)setVersion:(NSString *)version; +//- (NSString *)version; + +//- (void)setStandalone:(BOOL)standalone; +//- (BOOL)isStandalone; + +//- (void)setDocumentContentKind:(DDXMLDocumentContentKind)kind; +//- (DDXMLDocumentContentKind)documentContentKind; + +//- (void)setMIMEType:(NSString *)MIMEType; +//- (NSString *)MIMEType; + +//- (void)setDTD:(DDXMLDTD *)documentTypeDeclaration; +//- (DDXMLDTD *)DTD; + +//- (void)setRootElement:(DDXMLNode *)root; +- (DDXMLElement *)rootElement; + +//- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index; + +//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index; + +//- (void)removeChildAtIndex:(NSUInteger)index; + +//- (void)setChildren:(NSArray *)children; + +//- (void)addChild:(DDXMLNode *)child; + +//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node; + +- (NSData *)XMLData; +- (NSData *)XMLDataWithOptions:(NSUInteger)options; + +//- (id)objectByApplyingXSLT:(NSData *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error; +//- (id)objectByApplyingXSLTString:(NSString *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error; +//- (id)objectByApplyingXSLTAtURL:(NSURL *)xsltURL arguments:(NSDictionary *)argument error:(NSError **)error; + +//- (BOOL)validateAndReturnError:(NSError **)error; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.m b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.m new file mode 100644 index 0000000..b678b5b --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLDocument.m @@ -0,0 +1,111 @@ +#import "DDXMLDocument.h" +#import "NSStringAdditions.h" +#import "DDXMLPrivate.h" + + +@implementation DDXMLDocument + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * If the wrapper object already exists, it is retained/autoreleased and returned. + * Otherwise a new wrapper object is alloc/init/autoreleased and returned. +**/ ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr +{ + // If a wrapper object already exists, the _private variable is pointing to it. + + xmlDocPtr doc = (xmlDocPtr)kindPtr; + if(doc->_private == NULL) + return [[[DDXMLDocument alloc] initWithCheckedPrimitive:kindPtr] autorelease]; + else + return [[((DDXMLDocument *)(doc->_private)) retain] autorelease]; +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * The given node is checked, meaning a wrapper object for it does not already exist. +**/ +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr +{ + self = [super initWithCheckedPrimitive:kindPtr]; + return self; +} + +/** + * Initializes and returns a DDXMLDocument object created from an NSData object. + * + * Returns an initialized DDXMLDocument object, or nil if initialization fails + * because of parsing errors or other reasons. +**/ +- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error +{ + return [self initWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:mask error:error]; +} + +/** + * Initializes and returns a DDXMLDocument object created from an NSData object. + * + * Returns an initialized DDXMLDocument object, or nil if initialization fails + * because of parsing errors or other reasons. +**/ +- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error +{ + if(data == nil || [data length] == 0) + { + if(error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:0 userInfo:nil]; + + [self release]; + return nil; + } + + // Even though xmlKeepBlanksDefault(0) is called in DDXMLNode's initialize method, + // it has been documented that this call seems to get reset on the iPhone: + // http://code.google.com/p/kissxml/issues/detail?id=8 + // + // Therefore, we call it again here just to be safe. + xmlKeepBlanksDefault(0); + + xmlDocPtr doc = xmlParseMemory([data bytes], [data length]); + if(doc == NULL) + { + if(error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil]; + + [self release]; + return nil; + } + + return [self initWithCheckedPrimitive:(xmlKindPtr)doc]; +} + +/** + * Returns the root element of the receiver. +**/ +- (DDXMLElement *)rootElement +{ + xmlDocPtr doc = (xmlDocPtr)genericPtr; + + // doc->children is a list containing possibly comments, DTDs, etc... + + xmlNodePtr rootNode = xmlDocGetRootElement(doc); + + if(rootNode != NULL) + return [DDXMLElement nodeWithPrimitive:(xmlKindPtr)rootNode]; + else + return nil; +} + +- (NSData *)XMLData +{ + return [[self XMLString] dataUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSData *)XMLDataWithOptions:(NSUInteger)options +{ + return [[self XMLStringWithOptions:options] dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.h new file mode 100644 index 0000000..8b50789 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.h @@ -0,0 +1,49 @@ +#import +#import "DDXMLNode.h" + + +@interface DDXMLElement : DDXMLNode +{ +} + +- (id)initWithName:(NSString *)name; +- (id)initWithName:(NSString *)name URI:(NSString *)URI; +- (id)initWithName:(NSString *)name stringValue:(NSString *)string; +- (id)initWithXMLString:(NSString *)string error:(NSError **)error; + +#pragma mark --- Elements by name --- + +- (NSArray *)elementsForName:(NSString *)name; +- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI; + +#pragma mark --- Attributes --- + +- (void)addAttribute:(DDXMLNode *)attribute; +- (void)removeAttributeForName:(NSString *)name; +- (void)setAttributes:(NSArray *)attributes; +//- (void)setAttributesAsDictionary:(NSDictionary *)attributes; +- (NSArray *)attributes; +- (DDXMLNode *)attributeForName:(NSString *)name; +//- (DDXMLNode *)attributeForLocalName:(NSString *)localName URI:(NSString *)URI; + +#pragma mark --- Namespaces --- + +- (void)addNamespace:(DDXMLNode *)aNamespace; +- (void)removeNamespaceForPrefix:(NSString *)name; +- (void)setNamespaces:(NSArray *)namespaces; +- (NSArray *)namespaces; +- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix; +- (DDXMLNode *)resolveNamespaceForName:(NSString *)name; +- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI; + +#pragma mark --- Children --- + +- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index; +//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index; +- (void)removeChildAtIndex:(NSUInteger)index; +- (void)setChildren:(NSArray *)children; +- (void)addChild:(DDXMLNode *)child; +//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node; +//- (void)normalizeAdjacentTextNodesPreservingCDATA:(BOOL)preserve; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.m b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.m new file mode 100644 index 0000000..591f576 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElement.m @@ -0,0 +1,610 @@ +#import "DDXMLElement.h" +#import "NSStringAdditions.h" +#import "DDXMLPrivate.h" + + +@implementation DDXMLElement + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * If the wrapper object already exists, it is retained/autoreleased and returned. + * Otherwise a new wrapper object is alloc/init/autoreleased and returned. +**/ ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr +{ + // If a wrapper object already exists, the _private variable is pointing to it. + + xmlNodePtr node = (xmlNodePtr)kindPtr; + if(node->_private == NULL) + return [[[DDXMLElement alloc] initWithCheckedPrimitive:kindPtr] autorelease]; + else + return [[((DDXMLElement *)(node->_private)) retain] autorelease]; +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * The given node is checked, meaning a wrapper object for it does not already exist. +**/ +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr +{ + self = [super initWithCheckedPrimitive:kindPtr]; + return self; +} + +- (id)initWithName:(NSString *)name +{ + // Note: Make every guarantee that genericPtr is not null + + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); + if(node == NULL) + { + [self release]; + return nil; + } + + return [self initWithCheckedPrimitive:(xmlKindPtr)node]; +} + +- (id)initWithName:(NSString *)name URI:(NSString *)URI +{ + // Note: Make every guarantee that genericPtr is not null + + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); + if(node == NULL) + { + [self release]; + return nil; + } + + DDXMLElement *result = [self initWithCheckedPrimitive:(xmlKindPtr)node]; + [result setURI:URI]; + + return result; +} + +- (id)initWithName:(NSString *)name stringValue:(NSString *)string +{ + // Note: Make every guarantee that genericPtr is not null + + xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]); + if(node == NULL) + { + [self release]; + return nil; + } + + DDXMLElement *result = [self initWithCheckedPrimitive:(xmlKindPtr)node]; + [result setStringValue:string]; + + return result; +} + +- (id)initWithXMLString:(NSString *)string error:(NSError **)error +{ + DDXMLDocument *doc = [[DDXMLDocument alloc] initWithXMLString:string options:0 error:error]; + if(doc == nil) + { + [self release]; + return nil; + } + + DDXMLElement *result = [doc rootElement]; + [result detach]; + [doc release]; + + [self release]; + return [result retain]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Elements by name +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the child element nodes (as DDXMLElement objects) of the receiver that have a specified name. + * + * If name is a qualified name, then this method invokes elementsForLocalName:URI: with the URI parameter set to + * the URI associated with the prefix. Otherwise comparison is based on string equality of the qualified or + * non-qualified name. +**/ +- (NSArray *)elementsForName:(NSString *)name +{ + if(name == nil) return [NSArray array]; + + // We need to check to see if name has a prefix. + // If it does have a prefix, we need to figure out what the corresponding URI is for that prefix, + // and then search for any elements that have the same name (including prefix) OR have the same URI. + // Otherwise we loop through the children as usual and do a string compare on the name + + NSString *prefix = [[self class] prefixForName:name]; + if([prefix length] > 0) + { + xmlNodePtr node = (xmlNodePtr)genericPtr; + xmlNsPtr ns = xmlSearchNs(node->doc, node, [prefix xmlChar]); + if(ns != NULL) + { + NSString *uri = [NSString stringWithUTF8String:((const char *)ns->href)]; + return [self elementsWithName:name uri:uri]; + } + + // Note: We used xmlSearchNs instead of resolveNamespaceForName: - avoid creating wrapper objects when possible + } + + return [self elementsWithName:name uri:nil]; +} + +- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI +{ + if(localName == nil) return [NSArray array]; + + // We need to figure out what the prefix is for this URI. + // Then we search for elements that are named prefix:localName OR (named localName AND have the given URI). + + NSString *prefix = [self resolvePrefixForNamespaceURI:URI]; + if(prefix != nil) + { + NSString *name = [NSString stringWithFormat:@"%@:%@", prefix, localName]; + + return [self elementsWithName:name uri:URI]; + } + else + { + return [self elementsWithName:localName uri:URI]; + } +} + +/** + * Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated. + * The name parameter is required, URI is optional. +**/ +- (NSArray *)elementsWithName:(NSString *)name uri:(NSString *)uri +{ + // Supplied: name, !uri : match: name + // Supplied: p:name, uri : match: p:name || (name && uri) + // Supplied: name, uri : match: name && uri + + NSMutableArray *result = [NSMutableArray array]; + + xmlNodePtr node = (xmlNodePtr)genericPtr; + + BOOL hasPrefix = [[[self class] prefixForName:name] length] > 0; + NSString *localName = [[self class] localNameForName:name]; + + xmlNodePtr child = node->children; + while(child != NULL) + { + if(child->type == XML_ELEMENT_NODE) + { + BOOL match = NO; + if(uri == nil) + { + match = xmlStrEqual(child->name, [name xmlChar]); + } + else + { + BOOL nameMatch = xmlStrEqual(child->name, [name xmlChar]); + BOOL localNameMatch = xmlStrEqual(child->name, [localName xmlChar]); + + BOOL uriMatch = NO; + if(child->ns != NULL) + { + uriMatch = xmlStrEqual(child->ns->href, [uri xmlChar]); + } + + if(hasPrefix) + match = nameMatch || (localNameMatch && uriMatch); + else + match = nameMatch && uriMatch; + } + + if(match) + { + [result addObject:[DDXMLElement nodeWithPrimitive:(xmlKindPtr)child]]; + } + } + + child = child->next; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Attributes +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)addAttribute:(DDXMLNode *)attribute +{ + // NSXML version uses this same assertion + DDCheck([attribute hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first"); + DDCheck([attribute isXmlAttrPtr], @"Not an attribute"); + + // xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur) + // Add a new node to @parent, at the end of the child (or property) list merging + // adjacent TEXT nodes (in which case @cur is freed). If the new node is ATTRIBUTE, it is added + // into properties instead of children. If there is an attribute with equal name, it is first destroyed. + + [self removeAttributeForName:[attribute name]]; + + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)attribute->genericPtr); +} + +- (void)removeAttribute:(xmlAttrPtr)attr +{ + [[self class] removeAttribute:attr fromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeAllAttributes +{ + [[self class] removeAllAttributesFromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeAttributeForName:(NSString *)name +{ + // If we use xmlUnsetProp, then the attribute will be automatically freed. + // We don't want this unless no other wrapper objects have a reference to the property. + + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; + while(attr != NULL) + { + if(xmlStrEqual(attr->name, [name xmlChar])) + { + [self removeAttribute:attr]; + return; + } + attr = attr->next; + } +} + +- (NSArray *)attributes +{ + NSMutableArray *result = [NSMutableArray array]; + + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; + while(attr != NULL) + { + [result addObject:[DDXMLNode nodeWithPrimitive:(xmlKindPtr)attr]]; + + attr = attr->next; + } + + return result; +} + +- (DDXMLNode *)attributeForName:(NSString *)name +{ + xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties; + while(attr != NULL) + { + if(xmlStrEqual([name xmlChar], attr->name)) + { + return [DDXMLNode nodeWithPrimitive:(xmlKindPtr)attr]; + } + attr = attr->next; + } + return nil; +} + +/** + * Sets the list of attributes for the element. + * Any previously set attributes are removed. +**/ +- (void)setAttributes:(NSArray *)attributes +{ + [self removeAllAttributes]; + + NSUInteger i; + for(i = 0; i < [attributes count]; i++) + { + DDXMLNode *attribute = [attributes objectAtIndex:i]; + [self addAttribute:attribute]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Namespaces +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)addNamespace:(DDXMLNode *)namespace +{ + // NSXML version uses this same assertion + DDCheck([namespace hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first"); + DDCheck([namespace isXmlNsPtr], @"Not a namespace"); + + // Beware: [namespace prefix] does NOT return what you might expect. Use [namespace name] instead. + + [self removeNamespaceForPrefix:[namespace name]]; + + xmlNsPtr currentNs = ((xmlNodePtr)genericPtr)->nsDef; + if(currentNs == NULL) + { + ((xmlNodePtr)genericPtr)->nsDef = (xmlNsPtr)namespace->genericPtr; + } + else + { + while(currentNs != NULL) + { + if(currentNs->next == NULL) + { + currentNs->next = (xmlNsPtr)namespace->genericPtr; + break; // Yes this break is needed + } + currentNs = currentNs->next; + } + } + + // The xmlNs structure doesn't contain a reference to the parent, so we manage our own reference + namespace->nsParentPtr = (xmlNodePtr)genericPtr; + + // Did we just add a default namespace + if([[namespace name] isEqualToString:@""]) + { + ((xmlNodePtr)genericPtr)->ns = (xmlNsPtr)namespace->genericPtr; + + // Note: The removeNamespaceForPrefix method above properly handled removing any previous default namespace + } +} + +- (void)removeNamespace:(xmlNsPtr)ns +{ + [[self class] removeNamespace:ns fromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeAllNamespaces +{ + [[self class] removeAllNamespacesFromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeNamespaceForPrefix:(NSString *)name +{ + // If name is nil or the empty string, the user is wishing to remove the default namespace + const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL; + + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; + while(ns != NULL) + { + if(xmlStrEqual(ns->prefix, xmlName)) + { + [self removeNamespace:ns]; + break; + } + ns = ns->next; + } + + // Note: The removeNamespace method properly handles the situation where the namespace is the default namespace +} + +- (NSArray *)namespaces +{ + NSMutableArray *result = [NSMutableArray array]; + + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; + while(ns != NULL) + { + [result addObject:[DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr]]; + + ns = ns->next; + } + + return result; +} + +- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix +{ + // If the prefix is nil or the empty string, the user is requesting the default namespace + + if([prefix length] == 0) + { + // Requesting the default namespace + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->ns; + if(ns != NULL) + { + return [DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr]; + } + } + else + { + xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef; + while(ns != NULL) + { + if(xmlStrEqual(ns->prefix, [prefix xmlChar])) + { + return [DDXMLNode nodeWithPrimitive:ns nsParent:(xmlNodePtr)genericPtr]; + } + ns = ns->next; + } + } + + return nil; +} + +- (void)setNamespaces:(NSArray *)namespaces +{ + [self removeAllNamespaces]; + + NSUInteger i; + for(i = 0; i < [namespaces count]; i++) + { + DDXMLNode *namespace = [namespaces objectAtIndex:i]; + [self addNamespace:namespace]; + } +} + +/** + * Recursively searches the given node for the given namespace +**/ ++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr +{ + if(nodePtr == NULL) return nil; + + xmlNsPtr ns = nodePtr->nsDef; + while(ns != NULL) + { + if(xmlStrEqual(ns->prefix, [prefix xmlChar])) + { + return [DDXMLNode nodeWithPrimitive:ns nsParent:nodePtr]; + } + ns = ns->next; + } + + return [self resolveNamespaceForPrefix:prefix atNode:nodePtr->parent]; +} + +/** + * Returns the namespace node with the prefix matching the given qualified name. + * Eg: You pass it "a:dog", it returns the namespace (defined in this node or parent nodes) that has the "a" prefix. +**/ +- (DDXMLNode *)resolveNamespaceForName:(NSString *)name +{ + // If the user passes nil or an empty string for name, they're looking for the default namespace. + if([name length] == 0) + { + return [[self class] resolveNamespaceForPrefix:nil atNode:(xmlNodePtr)genericPtr]; + } + + NSString *prefix = [[self class] prefixForName:name]; + + if([prefix length] > 0) + { + // Unfortunately we can't use xmlSearchNs because it returns an xmlNsPtr. + // This gives us mostly what we want, except we also need to know the nsParent. + // So we do the recursive search ourselves. + + return [[self class] resolveNamespaceForPrefix:prefix atNode:(xmlNodePtr)genericPtr]; + } + + return nil; +} + +/** + * Recursively searches the given node for a namespace with the given URI, and a set prefix. +**/ ++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr +{ + if(nodePtr == NULL) return nil; + + xmlNsPtr ns = nodePtr->nsDef; + while(ns != NULL) + { + if(xmlStrEqual(ns->href, [uri xmlChar])) + { + if(ns->prefix != NULL) + { + return [NSString stringWithUTF8String:((const char *)ns->prefix)]; + } + } + ns = ns->next; + } + + return [self resolvePrefixForURI:uri atNode:nodePtr->parent]; +} + +/** + * Returns the prefix associated with the specified URI. + * Returns a string that is the matching prefix or nil if it finds no matching prefix. +**/ +- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI +{ + // We can't use xmlSearchNsByHref because it will return xmlNsPtr's with NULL prefixes. + // We're looking for a definitive prefix for the given URI. + + return [[self class] resolvePrefixForURI:namespaceURI atNode:(xmlNodePtr)genericPtr]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Children +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)removeChild:(xmlNodePtr)child +{ + [[self class] removeChild:child fromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeAllChildren +{ + [[self class] removeAllChildrenFromNode:(xmlNodePtr)genericPtr]; +} + +- (void)removeChildAtIndex:(NSUInteger)index +{ + NSUInteger i = 0; + + xmlNodePtr child = ((xmlNodePtr)genericPtr)->children; + while(child != NULL) + { + // Ignore all but element, comment, text, or processing instruction nodes + if([[self class] isXmlNodePtr:(xmlKindPtr)child]) + { + if(i == index) + { + [self removeChild:child]; + return; + } + + i++; + } + child = child->next; + } +} + +- (void)addChild:(DDXMLNode *)child +{ + // NSXML version uses these same assertions + DDCheck([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first"); + DDCheck([child isXmlNodePtr], @"Elements can only have text, elements, processing instructions, and comments as children"); + + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr); +} + +- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index +{ + // NSXML version uses these same assertions + DDCheck([child hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first"); + DDCheck([child isXmlNodePtr], @"Elements can only have text, elements, processing instructions, and comments as children"); + + NSUInteger i = 0; + + xmlNodePtr childNodePtr = ((xmlNodePtr)genericPtr)->children; + while(childNodePtr != NULL) + { + // Ignore all but element, comment, text, or processing instruction nodes + if([[self class] isXmlNodePtr:(xmlKindPtr)childNodePtr]) + { + if(i == index) + { + xmlAddPrevSibling(childNodePtr, (xmlNodePtr)child->genericPtr); + return; + } + + i++; + } + childNodePtr = childNodePtr->next; + } + + if(i == index) + { + xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr); + return; + } + + // NSXML version uses this same assertion + DDCheck(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)++i); +} + +- (void)setChildren:(NSArray *)children +{ + [self removeAllChildren]; + + NSUInteger i; + for(i = 0; i < [children count]; i++) + { + DDXMLNode *child = [children objectAtIndex:i]; + [self addChild:child]; + } +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.h new file mode 100644 index 0000000..d7972de --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.h @@ -0,0 +1,21 @@ +#import +#import "DDXML.h" + +// These methods are not part of the standard NSXML API. +// But any developer working extensively with XML will likely appreciate them. + +@interface DDXMLElement (DDAdditions) + ++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns; + +- (DDXMLElement *)elementForName:(NSString *)name; +- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns; + +- (NSString *)xmlns; +- (void)setXmlns:(NSString *)ns; + +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string; + +- (NSDictionary *)attributesAsDictionary; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.m b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.m new file mode 100644 index 0000000..640d786 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLElementAdditions.m @@ -0,0 +1,115 @@ +#import "DDXMLElementAdditions.h" + +@implementation DDXMLElement (DDAdditions) + +/** + * Quick method to create an element +**/ ++ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns +{ + DDXMLElement *element = [DDXMLElement elementWithName:name]; + [element setXmlns:ns]; + return element; +} + +/** + * This method returns the first child element for the given name. + * If no child element exists for the given name, returns nil. +**/ +- (DDXMLElement *)elementForName:(NSString *)name +{ + NSArray *elements = [self elementsForName:name]; + if([elements count] > 0) + { + return [elements objectAtIndex:0]; + } + else + { + // Note: If you port this code to work with Apple's NSXML, beware of the following: + // + // There is a bug in the NSXMLElement elementsForName: method. + // Consider the following XML fragment: + // + // + // + // + // + // Calling [query elementsForName:@"x"] results in an empty array! + // + // However, it will work properly if you use the following: + // [query elementsForLocalName:@"x" URI:@"some:other:namespace"] + // + // The trouble with this is that we may not always know the xmlns in advance, + // so in this particular case there is no way to access the element without looping through the children. + // + // This bug was submitted to apple on June 1st, 2007 and was classified as "serious". + // + // --!!-- This bug does NOT exist in DDXML --!!-- + + return nil; + } +} + +/** + * This method returns the first child element for the given name and given xmlns. + * If no child elements exist for the given name and given xmlns, returns nil. +**/ +- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns +{ + NSArray *elements = [self elementsForLocalName:name URI:xmlns]; + if([elements count] > 0) + { + return [elements objectAtIndex:0]; + } + else + { + return nil; + } +} + +/** + * Returns the common xmlns "attribute", which is only accessible via the namespace methods. + * The xmlns value is often used in jabber elements. +**/ +- (NSString *)xmlns +{ + return [[self namespaceForPrefix:@""] stringValue]; +} + +- (void)setXmlns:(NSString *)ns +{ + // If you use setURI: then the xmlns won't be displayed in the XMLString. + // Adding the namespace this way works properly. + // + // This applies to both Apple's NSXML and DDXML. + + [self addNamespace:[DDXMLNode namespaceWithName:@"" stringValue:ns]]; +} + +/** + * Shortcut to avoid having to manually create a DDXMLNode everytime. +**/ +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string +{ + [self addAttribute:[DDXMLNode attributeWithName:name stringValue:string]]; +} + +/** + * Returns all the attributes as a dictionary. +**/ +- (NSDictionary *)attributesAsDictionary +{ + NSArray *attributes = [self attributes]; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[attributes count]]; + + uint i; + for(i = 0; i < [attributes count]; i++) + { + DDXMLNode *node = [attributes objectAtIndex:i]; + + [result setObject:[node stringValue] forKey:[node name]]; + } + return result; +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.h new file mode 100644 index 0000000..cb1db24 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.h @@ -0,0 +1,170 @@ +#import +#import + +@class DDXMLDocument; + + +enum { + DDXMLInvalidKind = 0, + DDXMLDocumentKind = XML_DOCUMENT_NODE, + DDXMLElementKind = XML_ELEMENT_NODE, + DDXMLAttributeKind = XML_ATTRIBUTE_NODE, + DDXMLNamespaceKind = XML_NAMESPACE_DECL, + DDXMLProcessingInstructionKind = XML_PI_NODE, + DDXMLCommentKind = XML_COMMENT_NODE, + DDXMLTextKind = XML_TEXT_NODE, + DDXMLDTDKind = XML_DTD_NODE, + DDXMLEntityDeclarationKind = XML_ENTITY_DECL, + DDXMLAttributeDeclarationKind = XML_ATTRIBUTE_DECL, + DDXMLElementDeclarationKind = XML_ELEMENT_DECL, + DDXMLNotationDeclarationKind = XML_NOTATION_NODE +}; +typedef NSUInteger DDXMLNodeKind; + +enum { + DDXMLNodeOptionsNone = 0, + DDXMLNodeExpandEmptyElement = 1 << 1, + DDXMLNodeCompactEmptyElement = 1 << 2, + DDXMLNodePrettyPrint = 1 << 17, +}; + +/** + * DDXMLNode can represent several underlying types, such as xmlNodePtr, xmlDocPtr, xmlAttrPtr, xmlNsPtr, etc. + * All of these are pointers to structures, and all of those structures start with a pointer, and a type. + * The xmlKind struct is used as a generic structure, and a stepping stone. + * We use it to check the type of a structure, and then perform the appropriate cast. + * + * For example: + * if(genericPtr->type == XML_ATTRIBUTE_NODE) + * { + * xmlAttrPtr attr = (xmlAttrPtr)genericPtr; + * // Do something with attr + * } +**/ +struct _xmlKind { + void * ignore; + xmlElementType type; +}; +typedef struct _xmlKind *xmlKindPtr; + +/** + * Most xml types all start with this standard structure. In fact, all do except the xmlNsPtr. + * We will occasionally take advantage of this to simplify code when the code wouldn't vary from type to type. + * Obviously, you cannnot cast a xmlNsPtr to a xmlStdPtr. +**/ +struct _xmlStd { + void * _private; + xmlElementType type; + const xmlChar *name; + struct _xmlNode *children; + struct _xmlNode *last; + struct _xmlNode *parent; + struct _xmlStd *next; + struct _xmlStd *prev; + struct _xmlDoc *doc; +}; +typedef struct _xmlStd *xmlStdPtr; + +@interface DDXMLNode : NSObject +{ + // Every DDXML object is simply a wrapper around an underlying libxml node + xmlKindPtr genericPtr; + + // The xmlNsPtr type doesn't store a reference to it's parent + // This is here to fix that problem, and make this class more compatible with the NSXML classes + xmlNodePtr nsParentPtr; +} + +//- (id)initWithKind:(DDXMLNodeKind)kind; + +//- (id)initWithKind:(DDXMLNodeKind)kind options:(NSUInteger)options; + +//+ (id)document; + +//+ (id)documentWithRootElement:(DDXMLElement *)element; + ++ (id)elementWithName:(NSString *)name; + ++ (id)elementWithName:(NSString *)name URI:(NSString *)URI; + ++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string; + ++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes; + ++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue; + ++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue; + ++ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue; + ++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue; + ++ (id)commentWithStringValue:(NSString *)stringValue; + ++ (id)textWithStringValue:(NSString *)stringValue; + +//+ (id)DTDNodeWithXMLString:(NSString *)string; + +#pragma mark --- Properties --- + +- (DDXMLNodeKind)kind; + +- (void)setName:(NSString *)name; +- (NSString *)name; + +//- (void)setObjectValue:(id)value; +//- (id)objectValue; + +- (void)setStringValue:(NSString *)string; +//- (void)setStringValue:(NSString *)string resolvingEntities:(BOOL)resolve; +- (NSString *)stringValue; + +#pragma mark --- Tree Navigation --- + +- (NSUInteger)index; + +- (NSUInteger)level; + +- (DDXMLDocument *)rootDocument; + +- (DDXMLNode *)parent; +- (NSUInteger)childCount; +- (NSArray *)children; +- (DDXMLNode *)childAtIndex:(NSUInteger)index; + +- (DDXMLNode *)previousSibling; +- (DDXMLNode *)nextSibling; + +- (DDXMLNode *)previousNode; +- (DDXMLNode *)nextNode; + +- (void)detach; + +- (NSString *)XPath; + +#pragma mark --- QNames --- + +- (NSString *)localName; +- (NSString *)prefix; + +- (void)setURI:(NSString *)URI; +- (NSString *)URI; + ++ (NSString *)localNameForName:(NSString *)name; ++ (NSString *)prefixForName:(NSString *)name; +//+ (DDXMLNode *)predefinedNamespaceForPrefix:(NSString *)name; + +#pragma mark --- Output --- + +- (NSString *)description; +- (NSString *)XMLString; +- (NSString *)XMLStringWithOptions:(NSUInteger)options; +//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments; + +#pragma mark --- XPath/XQuery --- + +- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error; +//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error; +//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.m b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.m new file mode 100644 index 0000000..384b209 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLNode.m @@ -0,0 +1,1808 @@ +#import "DDXMLNode.h" +#import "DDXMLElement.h" +#import "DDXMLDocument.h" +#import "NSStringAdditions.h" +#import "DDXMLPrivate.h" + +#import +#import + + +@implementation DDXMLNode + +static void MyErrorHandler(void * userData, xmlErrorPtr error); + ++ (void)initialize +{ + static BOOL initialized = NO; + if(!initialized) + { + // Redirect error output to our own function (don't clog up the console) + initGenericErrorDefaultFunc(NULL); + xmlSetStructuredErrorFunc(NULL, MyErrorHandler); + + // Tell libxml not to keep ignorable whitespace (such as node indentation, formatting, etc). + // NSXML ignores such whitespace. + // This also has the added benefit of taking up less RAM when parsing formatted XML documents. + xmlKeepBlanksDefault(0); + + initialized = YES; + } +} + ++ (id)elementWithName:(NSString *)name +{ + return [[[DDXMLElement alloc] initWithName:name] autorelease]; +} + ++ (id)elementWithName:(NSString *)name stringValue:(NSString *)string +{ + return [[[DDXMLElement alloc] initWithName:name stringValue:string] autorelease]; +} + ++ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes +{ + DDXMLElement *result = [[[DDXMLElement alloc] initWithName:name] autorelease]; + [result setChildren:children]; + [result setAttributes:attributes]; + + return result; +} + ++ (id)elementWithName:(NSString *)name URI:(NSString *)URI +{ + return [[[DDXMLElement alloc] initWithName:name URI:URI] autorelease]; +} + ++ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue +{ + xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]); + + if(attr == NULL) return nil; + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)attr] autorelease]; +} + ++ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue +{ + xmlAttrPtr attr = xmlNewProp(NULL, [name xmlChar], [stringValue xmlChar]); + + if(attr == NULL) return nil; + + DDXMLNode *result = [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)attr] autorelease]; + [result setURI:URI]; + + return result; +} + ++ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue +{ + // If the user passes a nil or empty string name, they are trying to create a default namespace + const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL; + + xmlNsPtr ns = xmlNewNs(NULL, [stringValue xmlChar], xmlName); + + if(ns == NULL) return nil; + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)ns] autorelease]; +} + ++ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue +{ + xmlNodePtr procInst = xmlNewPI([name xmlChar], [stringValue xmlChar]); + + if(procInst == NULL) return nil; + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)procInst] autorelease]; +} + ++ (id)commentWithStringValue:(NSString *)stringValue +{ + xmlNodePtr comment = xmlNewComment([stringValue xmlChar]); + + if(comment == NULL) return nil; + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)comment] autorelease]; +} + ++ (id)textWithStringValue:(NSString *)stringValue +{ + xmlNodePtr text = xmlNewText([stringValue xmlChar]); + + if(text == NULL) return nil; + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)text] autorelease]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Init, Dealloc +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr +{ + if(kindPtr->type == XML_DOCUMENT_NODE) + { + return [DDXMLDocument nodeWithPrimitive:kindPtr]; + } + else if(kindPtr->type == XML_ELEMENT_NODE) + { + return [DDXMLElement nodeWithPrimitive:kindPtr]; + } + else + { + return [DDXMLNode nodeWithPrimitive:kindPtr]; + } +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * If the wrapper object already exists, it is retained/autoreleased and returned. + * Otherwise a new wrapper object is alloc/init/autoreleased and returned. +**/ ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr +{ + // If a wrapper object already exists, the _private variable is pointing to it. + // Warning: The _private variable is in a different location in the xmlNsPtr + + if([self isXmlNsPtr:kindPtr]) + { + xmlNsPtr ns = (xmlNsPtr)kindPtr; + if(ns->_private != NULL) + { + return [[((DDXMLNode *)(ns->_private)) retain] autorelease]; + } + } + else + { + xmlStdPtr node = (xmlStdPtr)kindPtr; + if(node->_private != NULL) + { + return [[((DDXMLNode *)(node->_private)) retain] autorelease]; + } + } + + return [[[DDXMLNode alloc] initWithCheckedPrimitive:kindPtr] autorelease]; +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * The given node is checked, meaning a wrapper object for it does not already exist. +**/ +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr +{ + if((self = [super init])) + { + genericPtr = kindPtr; + nsParentPtr = NULL; + [self nodeRetain]; + } + return self; +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * If the wrapper object already exists, it is retained/autoreleased and returned. + * Otherwise a new wrapper object is alloc/init/autoreleased and returned. +**/ ++ (id)nodeWithPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent +{ + // If a wrapper object already exists, the _private variable is pointing to it. + + if(ns->_private == NULL) + return [[[DDXMLNode alloc] initWithCheckedPrimitive:ns nsParent:parent] autorelease]; + else + return [[((DDXMLNode *)(ns->_private)) retain] autorelease]; +} + +/** + * Returns a DDXML wrapper object for the given primitive node. + * The given node MUST be non-NULL and of the proper type. + * + * The given node is checked, meaning a wrapper object for it does not already exist. +**/ +- (id)initWithCheckedPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent +{ + if((self = [super init])) + { + genericPtr = (xmlKindPtr)ns; + nsParentPtr = parent; + [self nodeRetain]; + } + return self; +} + +- (void)dealloc +{ + // Check if genericPtr is NULL + // This may be the case if, eg, DDXMLElement calls [self release] from it's init method + if(genericPtr != NULL) + { + [self nodeRelease]; + } + [super dealloc]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Copying +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (id)copyWithZone:(NSZone *)zone +{ + if([self isXmlDocPtr]) + { + xmlDocPtr copyDocPtr = xmlCopyDoc((xmlDocPtr)genericPtr, 1); + + if(copyDocPtr == NULL) return nil; + + return [[DDXMLDocument alloc] initWithCheckedPrimitive:(xmlKindPtr)copyDocPtr]; + } + + if([self isXmlNodePtr]) + { + xmlNodePtr copyNodePtr = xmlCopyNode((xmlNodePtr)genericPtr, 1); + + if(copyNodePtr == NULL) return nil; + + if([self isKindOfClass:[DDXMLElement class]]) + return [[DDXMLElement alloc] initWithCheckedPrimitive:(xmlKindPtr)copyNodePtr]; + else + return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyNodePtr]; + } + + if([self isXmlAttrPtr]) + { + xmlAttrPtr copyAttrPtr = xmlCopyProp(NULL, (xmlAttrPtr)genericPtr); + + if(copyAttrPtr == NULL) return nil; + + return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyAttrPtr]; + } + + if([self isXmlNsPtr]) + { + xmlNsPtr copyNsPtr = xmlCopyNamespace((xmlNsPtr)genericPtr); + + if(copyNsPtr == NULL) return nil; + + return [[DDXMLNode alloc] initWithCheckedPrimitive:copyNsPtr nsParent:NULL]; + } + + if([self isXmlDtdPtr]) + { + xmlDtdPtr copyDtdPtr = xmlCopyDtd((xmlDtdPtr)genericPtr); + + if(copyDtdPtr == NULL) return nil; + + return [[DDXMLNode alloc] initWithCheckedPrimitive:(xmlKindPtr)copyDtdPtr]; + } + + return nil; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Properties +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (DDXMLNodeKind)kind +{ + if(genericPtr != NULL) + return genericPtr->type; + else + return DDXMLInvalidKind; +} + +- (void)setName:(NSString *)name +{ + if([self isXmlNsPtr]) + { + xmlNsPtr ns = (xmlNsPtr)genericPtr; + + xmlFree((xmlChar *)ns->prefix); + ns->prefix = xmlStrdup([name xmlChar]); + } + else + { + // The xmlNodeSetName function works for both nodes and attributes + xmlNodeSetName((xmlNodePtr)genericPtr, [name xmlChar]); + } +} + +- (NSString *)name +{ + if([self isXmlNsPtr]) + { + xmlNsPtr ns = (xmlNsPtr)genericPtr; + if(ns->prefix != NULL) + return [NSString stringWithUTF8String:((const char*)ns->prefix)]; + else + return @""; + } + else + { + const char *name = (const char *)((xmlStdPtr)genericPtr)->name; + + if(name == NULL) + return nil; + else + return [NSString stringWithUTF8String:name]; + } +} + +- (void)setStringValue:(NSString *)string +{ + if([self isXmlNsPtr]) + { + xmlNsPtr ns = (xmlNsPtr)genericPtr; + + xmlFree((xmlChar *)ns->href); + ns->href = xmlEncodeSpecialChars(NULL, [string xmlChar]); + } + else if([self isXmlAttrPtr]) + { + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; + + if(attr->children != NULL) + { + xmlChar *escapedString = xmlEncodeSpecialChars(attr->doc, [string xmlChar]); + xmlNodeSetContent((xmlNodePtr)attr, escapedString); + xmlFree(escapedString); + } + else + { + xmlNodePtr text = xmlNewText([string xmlChar]); + attr->children = text; + } + } + else if([self isXmlNodePtr]) + { + xmlStdPtr node = (xmlStdPtr)genericPtr; + + // Setting the content of a node erases any existing child nodes. + // Therefore, we need to remove them properly first. + [[self class] removeAllChildrenFromNode:(xmlNodePtr)node]; + + xmlChar *escapedString = xmlEncodeSpecialChars(node->doc, [string xmlChar]); + xmlNodeSetContent((xmlNodePtr)node, escapedString); + xmlFree(escapedString); + } +} + +/** + * Returns the content of the receiver as a string value. + * + * If the receiver is a node object of element kind, the content is that of any text-node children. + * This method recursively visits elements nodes and concatenates their text nodes in document order with + * no intervening spaces. +**/ +- (NSString *)stringValue +{ + if([self isXmlNsPtr]) + { + return [NSString stringWithUTF8String:((const char *)((xmlNsPtr)genericPtr)->href)]; + } + else if([self isXmlAttrPtr]) + { + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; + + if(attr->children != NULL) + { + return [NSString stringWithUTF8String:(const char *)attr->children->content]; + } + + return nil; + } + else if([self isXmlNodePtr]) + { + xmlChar *content = xmlNodeGetContent((xmlNodePtr)genericPtr); + + NSString *result = [NSString stringWithUTF8String:(const char *)content]; + + xmlFree(content); + return result; + } + + return nil; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Tree Navigation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the index of the receiver identifying its position relative to its sibling nodes. + * The first child node of a parent has an index of zero. +**/ +- (NSUInteger)index +{ + if([self isXmlNsPtr]) + { + // The xmlNsPtr has no prev pointer, so we have to search from the parent + if(nsParentPtr == NULL) return 0; + + xmlNsPtr currentNs = nsParentPtr->nsDef; + + NSUInteger result = 0; + while(currentNs != NULL) + { + if(currentNs == (xmlNsPtr)genericPtr) + { + return result; + } + result++; + currentNs = currentNs->next; + } + return 0; + } + else + { + xmlStdPtr node = ((xmlStdPtr)genericPtr)->prev; + + NSUInteger result = 0; + while(node != NULL) + { + result++; + node = node->prev; + } + + return result; + } +} + +/** + * Returns the nesting level of the receiver within the tree hierarchy. + * The root element of a document has a nesting level of one. +**/ +- (NSUInteger)level +{ + xmlNodePtr currentNode; + if([self isXmlNsPtr]) + currentNode = nsParentPtr; + else + currentNode = ((xmlStdPtr)genericPtr)->parent; + + NSUInteger result = 0; + while(currentNode != NULL) + { + result++; + currentNode = currentNode->parent; + } + + return result; +} + +/** + * Returns the DDXMLDocument object containing the root element and representing the XML document as a whole. + * If the receiver is a standalone node (that is, a node at the head of a detached branch of the tree), this + * method returns nil. +**/ +- (DDXMLDocument *)rootDocument +{ + xmlStdPtr node; + if([self isXmlNsPtr]) + node = (xmlStdPtr)nsParentPtr; + else + node = (xmlStdPtr)genericPtr; + + if(node == NULL || node->doc == NULL) + return nil; + else + return [DDXMLDocument nodeWithPrimitive:(xmlKindPtr)node->doc]; +} + +/** + * Returns the parent node of the receiver. + * + * Document nodes and standalone nodes (that is, the root of a detached branch of a tree) have no parent, and + * sending this message to them returns nil. A one-to-one relationship does not always exists between a parent and + * its children; although a namespace or attribute node cannot be a child, it still has a parent element. +**/ +- (DDXMLNode *)parent +{ + if([self isXmlNsPtr]) + { + if(nsParentPtr == NULL) return nil; + + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)nsParentPtr]; + } + + xmlStdPtr node = (xmlStdPtr)genericPtr; + + if(node->parent == NULL) return nil; + + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent]; +} + +/** + * Returns the number of child nodes the receiver has. + * For performance reasons, use this method instead of getting the count from the array returned by children. +**/ +- (NSUInteger)childCount +{ + if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) return 0; + + NSUInteger result = 0; + + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; + while(child != NULL) + { + result++; + child = child->next; + } + + return result; +} + +/** + * Returns an immutable array containing the child nodes of the receiver (as DDXMLNode objects). +**/ +- (NSArray *)children +{ + if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) return nil; + + NSMutableArray *result = [NSMutableArray array]; + + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; + while(child != NULL) + { + [result addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child]]; + + child = child->next; + } + + return [[result copy] autorelease]; +} + +/** + * Returns the child node of the receiver at the specified location. + * Returns a DDXMLNode object or nil if the receiver has no children. + * + * If the receive has children and index is out of bounds, an exception is raised. + * + * The receiver should be a DDXMLNode object representing a document, element, or document type declaration. + * The returned node object can represent an element, comment, text, or processing instruction. +**/ +- (DDXMLNode *)childAtIndex:(NSUInteger)index +{ + if(![self isXmlDocPtr] && ![self isXmlNodePtr] && ![self isXmlDtdPtr]) return nil; + + NSUInteger i = 0; + + xmlNodePtr child = ((xmlStdPtr)genericPtr)->children; + + if(child == NULL) + { + // NSXML doesn't raise an exception if there are no children + return nil; + } + + while(child != NULL) + { + if(i == index) + { + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)child]; + } + + i++; + child = child->next; + } + + // NSXML version uses this same assertion + DDCheck(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)i); + + return nil; +} + +/** + * Returns the previous DDXMLNode object that is a sibling node to the receiver. + * + * This object will have an index value that is one less than the receiver’s. + * If there are no more previous siblings (that is, other child nodes of the receiver’s parent) the method returns nil. +**/ +- (DDXMLNode *)previousSibling +{ + if([self isXmlNsPtr]) return nil; + + xmlStdPtr node = (xmlStdPtr)genericPtr; + + if(node->prev == NULL) return nil; + + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->prev]; +} + +/** + * Returns the next DDXMLNode object that is a sibling node to the receiver. + * + * This object will have an index value that is one more than the receiver’s. + * If there are no more subsequent siblings (that is, other child nodes of the receiver’s parent) the + * method returns nil. +**/ +- (DDXMLNode *)nextSibling +{ + if([self isXmlNsPtr]) return nil; + + xmlStdPtr node = (xmlStdPtr)genericPtr; + + if(node->next == NULL) return nil; + + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->next]; +} + +/** + * Returns the previous DDXMLNode object in document order. + * + * You use this method to “walk” backward through the tree structure representing an XML document or document section. + * (Use nextNode to traverse the tree in the opposite direction.) Document order is the natural order that XML + * constructs appear in markup text. If you send this message to the first node in the tree (that is, the root element), + * nil is returned. DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order. +**/ +- (DDXMLNode *)previousNode +{ + if([self isXmlNsPtr] || [self isXmlAttrPtr]) return nil; + + // If the node has a previous sibling, + // then we need the last child of the last child of the last child etc + + // Note: Try to accomplish this task without creating dozens of intermediate wrapper objects + + xmlStdPtr node = (xmlStdPtr)genericPtr; + xmlStdPtr previousSibling = node->prev; + + if(previousSibling != NULL) + { + if(previousSibling->last != NULL) + { + xmlNodePtr lastChild = previousSibling->last; + while(lastChild->last != NULL) + { + lastChild = lastChild->last; + } + + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)lastChild]; + } + else + { + // The previous sibling has no children, so the previous node is simply the previous sibling + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)previousSibling]; + } + } + + // If there are no previous siblings, then the previous node is simply the parent + + // Note: rootNode.parent == docNode + + if(node->parent == NULL || node->parent->type == XML_DOCUMENT_NODE) + return nil; + else + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node->parent]; +} + +/** + * Returns the next DDXMLNode object in document order. + * + * You use this method to “walk” forward through the tree structure representing an XML document or document section. + * (Use previousNode to traverse the tree in the opposite direction.) Document order is the natural order that XML + * constructs appear in markup text. If you send this message to the last node in the tree, nil is returned. + * DDXMLNode bypasses namespace and attribute nodes when it traverses a tree in document order. +**/ +- (DDXMLNode *)nextNode +{ + if([self isXmlNsPtr] || [self isXmlAttrPtr]) return nil; + + // If the node has children, then next node is the first child + DDXMLNode *firstChild = [self childAtIndex:0]; + if(firstChild) + return firstChild; + + // If the node has a next sibling, then next node is the same as next sibling + + DDXMLNode *nextSibling = [self nextSibling]; + if(nextSibling) + return nextSibling; + + // There are no children, and no more siblings, so we need to get the next sibling of the parent. + // If that is nil, we need to get the next sibling of the grandparent, etc. + + // Note: Try to accomplish this task without creating dozens of intermediate wrapper objects + + xmlNodePtr parent = ((xmlStdPtr)genericPtr)->parent; + while(parent != NULL) + { + xmlNodePtr parentNextSibling = parent->next; + if(parentNextSibling != NULL) + return [DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)parentNextSibling]; + else + parent = parent->parent; + } + + return nil; +} + +/** + * Detaches the receiver from its parent node. + * + * This method is applicable to DDXMLNode objects representing elements, text, comments, processing instructions, + * attributes, and namespaces. Once the node object is detached, you can add it as a child node of another parent. +**/ +- (void)detach +{ + if([self isXmlNsPtr]) + { + if(nsParentPtr != NULL) + { + [[self class] removeNamespace:(xmlNsPtr)genericPtr fromNode:nsParentPtr]; + } + return; + } + + xmlStdPtr node = (xmlStdPtr)genericPtr; + + if(node->parent == NULL) return; + + if([self isXmlAttrPtr]) + { + [[self class] detachAttribute:(xmlAttrPtr)node fromNode:node->parent]; + } + else if([self isXmlNodePtr]) + { + [[self class] detachChild:(xmlNodePtr)node fromNode:node->parent]; + } +} + +- (NSString *)XPath +{ + NSMutableString *result = [NSMutableString stringWithCapacity:25]; + + // Examples: + // /rootElement[1]/subElement[4]/thisNode[2] + // topElement/thisNode[2] + + xmlStdPtr node = NULL; + + if([self isXmlNsPtr]) + { + node = (xmlStdPtr)nsParentPtr; + + if(node == NULL) + [result appendFormat:@"namespace::%@", [self name]]; + else + [result appendFormat:@"/namespace::%@", [self name]]; + } + else if([self isXmlAttrPtr]) + { + node = (xmlStdPtr)(((xmlAttrPtr)genericPtr)->parent); + + if(node == NULL) + [result appendFormat:@"@%@", [self name]]; + else + [result appendFormat:@"/@%@", [self name]]; + } + else + { + node = (xmlStdPtr)genericPtr; + } + + // Note: rootNode.parent == docNode + + while((node != NULL) && (node->type != XML_DOCUMENT_NODE)) + { + if((node->parent == NULL) && (node->doc == NULL)) + { + // We're at the top of the heirarchy, and there is no xml document. + // Thus we don't use a leading '/', and we don't need an index. + + [result insertString:[NSString stringWithFormat:@"%s", node->name] atIndex:0]; + } + else + { + // Find out what index this node is. + // If it's the first node with this name, the index is 1. + // If there are previous siblings with the same name, the index is greater than 1. + + int index = 1; + xmlStdPtr prevNode = node->prev; + while(prevNode != NULL) + { + if(xmlStrEqual(node->name, prevNode->name)) + { + index++; + } + prevNode = prevNode->prev; + } + + [result insertString:[NSString stringWithFormat:@"/%s[%i]", node->name, index] atIndex:0]; + } + + node = (xmlStdPtr)node->parent; + } + + return [[result copy] autorelease]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark QNames +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the local name of the receiver. + * + * The local name is the part of a node name that follows a namespace-qualifying colon or the full name if + * there is no colon. For example, “chapter” is the local name in the qualified name “acme:chapter”. +**/ +- (NSString *)localName +{ + if([self isXmlNsPtr]) + { + // Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string + xmlNsPtr ns = (xmlNsPtr)genericPtr; + if(ns->prefix != NULL) + return [NSString stringWithUTF8String:((const char *)ns->prefix)]; + else + return @""; + } + + return [[self class] localNameForName:[self name]]; +} + +/** + * Returns the prefix of the receiver’s name. + * + * The prefix is the part of a namespace-qualified name that precedes the colon. + * For example, “acme” is the local name in the qualified name “acme:chapter”. + * This method returns an empty string if the receiver’s name is not qualified by a namespace. +**/ +- (NSString *)prefix +{ + if([self isXmlNsPtr]) + { + // Strangely enough, the localName of a namespace is the prefix, and the prefix is an empty string + return @""; + } + + return [[self class] prefixForName:[self name]]; +} + +/** + * Sets the URI identifying the source of this document. + * Pass nil to remove the current URI. +**/ +- (void)setURI:(NSString *)URI +{ + if([self isXmlNodePtr]) + { + xmlNodePtr node = (xmlNodePtr)genericPtr; + if(node->ns != NULL) + { + [[self class] removeNamespace:node->ns fromNode:node]; + } + + if(URI) + { + // Create a new xmlNsPtr, add it to the nsDef list, and make ns point to it + xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL); + ns->next = node->nsDef; + node->nsDef = ns; + node->ns = ns; + } + } + else if([self isXmlAttrPtr]) + { + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; + if(attr->ns != NULL) + { + // An attribute can only have a single namespace attached to it. + // In addition, this namespace can only be accessed via the URI method. + // There is no way, within the API, to get a DDXMLNode wrapper for the attribute's namespace. + xmlFreeNs(attr->ns); + attr->ns = NULL; + } + + if(URI) + { + // Create a new xmlNsPtr, and make ns point to it + xmlNsPtr ns = xmlNewNs(NULL, [URI xmlChar], NULL); + attr->ns = ns; + } + } +} + +/** + * Returns the URI associated with the receiver. + * + * A node’s URI is derived from its namespace or a document’s URI; for documents, the URI comes either from the + * parsed XML or is explicitly set. You cannot change the URI for a particular node other for than a namespace + * or document node. +**/ +- (NSString *)URI +{ + if([self isXmlAttrPtr]) + { + xmlAttrPtr attr = (xmlAttrPtr)genericPtr; + if(attr->ns != NULL) + { + return [NSString stringWithUTF8String:((const char *)attr->ns->href)]; + } + } + else if([self isXmlNodePtr]) + { + xmlNodePtr node = (xmlNodePtr)genericPtr; + if(node->ns != NULL) + { + return [NSString stringWithUTF8String:((const char *)node->ns->href)]; + } + } + + return nil; +} + +/** + * Returns the local name from the specified qualified name. + * + * Examples: + * "a:node" -> "node" + * "a:a:node" -> "a:node" + * "node" -> "node" + * nil - > nil +**/ ++ (NSString *)localNameForName:(NSString *)name +{ + if(name) + { + NSRange range = [name rangeOfString:@":"]; + + if(range.length != 0) + return [name substringFromIndex:(range.location + range.length)]; + else + return name; + } + return nil; +} + +/** + * Extracts the prefix from the given name. + * If name is nil, or has no prefix, an empty string is returned. + * + * Examples: + * "a:deusty.com" -> "a" + * "a:a:deusty.com" -> "a" + * "node" -> "" + * nil -> "" +**/ ++ (NSString *)prefixForName:(NSString *)name +{ + if(name) + { + NSRange range = [name rangeOfString:@":"]; + + if(range.length != 0) + { + return [name substringToIndex:range.location]; + } + } + return @""; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Output +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSString *)description +{ + return [self XMLStringWithOptions:0]; +} + +- (NSString *)XMLString +{ + // Todo: Test XMLString for namespace node + return [self XMLStringWithOptions:0]; +} + +- (NSString *)XMLStringWithOptions:(NSUInteger)options +{ + // xmlSaveNoEmptyTags: + // Global setting, asking the serializer to not output empty tags + // as but . those two forms are undistinguishable + // once parsed. + // Disabled by default + + if(options & DDXMLNodeCompactEmptyElement) + xmlSaveNoEmptyTags = 0; + else + xmlSaveNoEmptyTags = 1; + + int format = 0; + if(options & DDXMLNodePrettyPrint) + { + format = 1; + xmlIndentTreeOutput = 1; + } + + xmlBufferPtr bufferPtr = xmlBufferCreate(); + if([self isXmlNsPtr]) + xmlNodeDump(bufferPtr, NULL, (xmlNodePtr)genericPtr, 0, format); + else + xmlNodeDump(bufferPtr, ((xmlStdPtr)genericPtr)->doc, (xmlNodePtr)genericPtr, 0, format); + + if([self kind] == DDXMLTextKind) + { + NSString *result = [NSString stringWithUTF8String:(const char *)bufferPtr->content]; + + xmlBufferFree(bufferPtr); + + return result; + } + else + { + NSMutableString *resTmp = [NSMutableString stringWithUTF8String:(const char *)bufferPtr->content]; + NSString *result = [resTmp stringByTrimming]; + + xmlBufferFree(bufferPtr); + + return result; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark XPath/XQuery +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +-(NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error +{ + xmlXPathContextPtr xpathCtx; + xmlXPathObjectPtr xpathObj; + + BOOL isTempDoc = NO; + xmlDocPtr doc; + + if([DDXMLNode isXmlDocPtr:genericPtr]) + { + doc = (xmlDocPtr)genericPtr; + } + else if([DDXMLNode isXmlNodePtr:genericPtr]) + { + doc = ((xmlNodePtr)genericPtr)->doc; + + if(doc == NULL) + { + isTempDoc = YES; + + doc = xmlNewDoc(NULL); + xmlDocSetRootElement(doc, (xmlNodePtr)genericPtr); + } + } + else + { + return nil; + } + + xpathCtx = xmlXPathNewContext(doc); + xpathCtx->node = (xmlNodePtr)genericPtr; + + xmlNodePtr rootNode = (doc)->children; + if(rootNode != NULL) + { + xmlNsPtr ns = rootNode->nsDef; + while(ns != NULL) + { + xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href); + + ns = ns->next; + } + } + + xpathObj = xmlXPathEvalExpression([xpath xmlChar], xpathCtx); + + NSArray *result; + + if(xpathObj == NULL) + { + if(error) *error = [[self class] lastError]; + result = nil; + } + else + { + if(error) *error = nil; + + int count = xmlXPathNodeSetGetLength(xpathObj->nodesetval); + + if(count == 0) + { + result = [NSArray array]; + } + else + { + NSMutableArray *mResult = [NSMutableArray arrayWithCapacity:count]; + + int i; + for (i = 0; i < count; i++) + { + xmlNodePtr node = xpathObj->nodesetval->nodeTab[i]; + + [mResult addObject:[DDXMLNode nodeWithUnknownPrimitive:(xmlKindPtr)node]]; + } + + result = mResult; + } + } + + if(xpathObj) xmlXPathFreeObject(xpathObj); + if(xpathCtx) xmlXPathFreeContext(xpathCtx); + + if(isTempDoc) + { + xmlUnlinkNode((xmlNodePtr)genericPtr); + xmlFreeDoc(doc); + + // xmlUnlinkNode doesn't remove the doc ptr + [[self class] recursiveStripDocPointersFromNode:(xmlNodePtr)genericPtr]; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns whether or not the given node is of type xmlAttrPtr. +**/ ++ (BOOL)isXmlAttrPtr:(xmlKindPtr)kindPtr +{ + return kindPtr->type == XML_ATTRIBUTE_NODE; +} + +/** + * Returns whether or not the genericPtr is of type xmlAttrPtr. +**/ +- (BOOL)isXmlAttrPtr +{ + return [[self class] isXmlAttrPtr:genericPtr]; +} + +/** + * Returns whether or not the given node is of type xmlNodePtr. +**/ ++ (BOOL)isXmlNodePtr:(xmlKindPtr)kindPtr +{ + xmlElementType type = kindPtr->type; + switch(type) + { + case XML_ELEMENT_NODE : + case XML_PI_NODE : + case XML_COMMENT_NODE : + case XML_TEXT_NODE : + case XML_CDATA_SECTION_NODE : return YES; + default : return NO; + } +} + +/** + * Returns whether or not the genericPtr is of type xmlNodePtr. +**/ +- (BOOL)isXmlNodePtr +{ + return [[self class] isXmlNodePtr:genericPtr]; +} + +/** + * Returns whether or not the given node is of type xmlDocPtr. +**/ ++ (BOOL)isXmlDocPtr:(xmlKindPtr)kindPtr +{ + return kindPtr->type == XML_DOCUMENT_NODE; +} + +/** + * Returns whether or not the genericPtr is of type xmlDocPtr. +**/ +- (BOOL)isXmlDocPtr +{ + return [[self class] isXmlDocPtr:genericPtr]; +} + +/** + * Returns whether or not the given node is of type xmlDtdPtr. +**/ ++ (BOOL)isXmlDtdPtr:(xmlKindPtr)kindPtr +{ + return kindPtr->type == XML_DTD_NODE; +} + +/** + * Returns whether or not the genericPtr is of type xmlDtdPtr. +**/ +- (BOOL)isXmlDtdPtr +{ + return [[self class] isXmlDtdPtr:genericPtr]; +} + +/** + * Returns whether or not the given node is of type xmlNsPtr. +**/ ++ (BOOL)isXmlNsPtr:(xmlKindPtr)kindPtr +{ + return kindPtr->type == XML_NAMESPACE_DECL; +} + +/** + * Returns whether or not the genericPtr is of type xmlNsPtr. +**/ +- (BOOL)isXmlNsPtr +{ + return [[self class] isXmlNsPtr:genericPtr]; +} + +/** + * Returns whether or not the node has a parent. + * Use this method instead of parent when you only need to ensure parent is nil. + * This prevents the unnecessary creation of a parent node wrapper. +**/ +- (BOOL)hasParent +{ + if([self isXmlNsPtr]) + { + return (nsParentPtr != NULL); + } + + xmlStdPtr node = (xmlStdPtr)genericPtr; + + return (node->parent != NULL); +} + +/** + * - - - - - - - - - - R E A D M E - - - - - - - - - - + * + * The memory management of these wrapper classes is straight-forward, but requires explanation. + * To understand the problem, consider the following situation: + * + * + * + * + * + * + * + * Imagine the user has retained two DDXMLElements - one for root, and one for level2. + * Then they release the root element, but they want to hold onto the level2 element. + * We need to release root, and level1, but keep level2 intact until the user is done with it. + * Note that this is also how the NSXML classes work. + * The user will no longer be able to traverse up the tree from level2, but will be able to access all the normal + * information in level2, as well as any children, if there was any. + * + * So the first question is, how do we know if a libxml node is being referenced by a cocoa wrapper? + * In order to accomplish this, we take advantage of the node's _private variable. + * If the private variable is NULL, then the node isn't being directly referenced by any cocoa wrapper objects. + * If the private variable is NON-NULL, then the private variable points to the cocoa wrapper object. + * When a cocoa wrapper object is created, it points the private variable to itself (via nodeRetain), + * and when it's dealloced it sets the private variable back to NULL (via nodeRelease). + * + * With this simple technique, then given any libxml node, we can easily determine if it's still needed, + * or if we can free it: + * Is there a cocoa wrapper objects still directly referring to the node? + * If so, we can't free the node. + * Otherwise, does the node still have a parent? + * If so, then the node is still part of a heirarchy, and we can't free the node. + * + * To fully understand the parent restriction, consider the following scenario: + * Imagine the user extracts the level1 DDXMLElement from the root. + * The user reads the data, and the level1 DDXMLElement is autoreleased. The root is still retained. + * When the level1 DDXMLElement is dealloced, nodeRelease will be called, and the private variable will be set to NULL. + * Can we free the level1 node at this point? + * Of course not, because it's still within the root heirarchy, and the user is still using the root element. + * + * The following should be spelled out: + * If you call libxml's xmlFreeNode(), this method will free all linked attributes and children. + * So you can't blindly call this method, because you can't free nodes that are still being referenced. +**/ + + ++ (void)stripDocPointersFromAttr:(xmlAttrPtr)attr +{ + xmlNodePtr child = attr->children; + while(child != NULL) + { + child->doc = NULL; + child = child->next; + } + + attr->doc = NULL; +} + ++ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node +{ + xmlAttrPtr attr = node->properties; + while(attr != NULL) + { + [self stripDocPointersFromAttr:attr]; + attr = attr->next; + } + + xmlNodePtr child = node->children; + while(child != NULL) + { + [self recursiveStripDocPointersFromNode:child]; + child = child->next; + } + + node->doc = NULL; +} + +/** + * This method will recursively free the given node, as long as the node is no longer being referenced. + * If the node is still being referenced, then it's parent, prev, next and doc pointers are destroyed. +**/ ++ (void)nodeFree:(xmlNodePtr)node +{ + NSAssert1([self isXmlNodePtr:(xmlKindPtr)node], @"Wrong kind of node passed to nodeFree: %i", node->type); + + if(node->_private == NULL) + { + [self removeAllAttributesFromNode:node]; + [self removeAllNamespacesFromNode:node]; + [self removeAllChildrenFromNode:node]; + + xmlFreeNode(node); + } + else + { + node->parent = NULL; + node->prev = NULL; + node->next = NULL; + if(node->doc != NULL) [self recursiveStripDocPointersFromNode:node]; + } +} + +/** + * Detaches the given attribute from the given node. + * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list. + * Then the attribute's parent, prev, next and doc pointers are destroyed. +**/ ++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node +{ + // Update the surrounding prev/next pointers + if(attr->prev == NULL) + { + if(attr->next == NULL) + { + node->properties = NULL; + } + else + { + node->properties = attr->next; + attr->next->prev = NULL; + } + } + else + { + if(attr->next == NULL) + { + attr->prev->next = NULL; + } + else + { + attr->prev->next = attr->next; + attr->next->prev = attr->prev; + } + } + + // Nullify pointers + attr->parent = NULL; + attr->prev = NULL; + attr->next = NULL; + if(attr->doc != NULL) [self stripDocPointersFromAttr:attr]; +} + +/** + * Removes the given attribute from the given node. + * The attribute's surrounding prev/next pointers are properly updated to remove the attribute from the attr list. + * Then the attribute is freed if it's no longer being referenced. + * Otherwise, it's parent, prev, next and doc pointers are destroyed. +**/ ++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node +{ + [self detachAttribute:attr fromNode:node]; + + // Free the attr if it's no longer in use + if(attr->_private == NULL) + { + xmlFreeProp(attr); + } +} + +/** + * Removes all attributes from the given node. + * All attributes are either freed, or their parent, prev, next and doc pointers are properly destroyed. + * Upon return, the given node's properties pointer is NULL. +**/ ++ (void)removeAllAttributesFromNode:(xmlNodePtr)node +{ + xmlAttrPtr attr = node->properties; + + while(attr != NULL) + { + xmlAttrPtr nextAttr = attr->next; + + // Free the attr if it's no longer in use + if(attr->_private == NULL) + { + xmlFreeProp(attr); + } + else + { + attr->parent = NULL; + attr->prev = NULL; + attr->next = NULL; + if(attr->doc != NULL) [self stripDocPointersFromAttr:attr]; + } + + attr = nextAttr; + } + + node->properties = NULL; +} + +/** + * Detaches the given namespace from the given node. + * The namespace's surrounding next pointers are properly updated to remove the namespace from the node's nsDef list. + * Then the namespace's parent and next pointers are destroyed. +**/ ++ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node +{ + // Namespace nodes have no previous pointer, so we have to search for the node + xmlNsPtr previousNs = NULL; + xmlNsPtr currentNs = node->nsDef; + while(currentNs != NULL) + { + if(currentNs == ns) + { + if(previousNs == NULL) + node->nsDef = currentNs->next; + else + previousNs->next = currentNs->next; + + break; + } + + previousNs = currentNs; + currentNs = currentNs->next; + } + + // Nullify pointers + ns->next = NULL; + + if(node->ns == ns) + { + node->ns = NULL; + } + + // We also have to nullify the nsParentPtr, which is in the cocoa wrapper object (if one exists) + if(ns->_private != NULL) + { + DDXMLNode *node = (DDXMLNode *)ns->_private; + node->nsParentPtr = NULL; + } +} + +/** + * Removes the given namespace from the given node. + * The namespace's surrounding next pointers are properly updated to remove the namespace from the nsDef list. + * Then the namespace is freed if it's no longer being referenced. + * Otherwise, it's nsParent and next pointers are destroyed. +**/ ++ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node +{ + [self detachNamespace:ns fromNode:node]; + + // Free the ns if it's no longer in use + if(ns->_private == NULL) + { + xmlFreeNs(ns); + } +} + +/** + * Removes all namespaces from the given node. + * All namespaces are either freed, or their nsParent and next pointers are properly destroyed. + * Upon return, the given node's nsDef pointer is NULL. +**/ ++ (void)removeAllNamespacesFromNode:(xmlNodePtr)node +{ + xmlNsPtr ns = node->nsDef; + + while(ns != NULL) + { + xmlNsPtr nextNs = ns->next; + + // We manage the nsParent pointer, which is in the cocoa wrapper object, so we have to nullify it ourself + if(ns->_private != NULL) + { + DDXMLNode *node = (DDXMLNode *)ns->_private; + node->nsParentPtr = NULL; + } + + // Free the ns if it's no longer in use + if(ns->_private == NULL) + { + xmlFreeNs(ns); + } + else + { + ns->next = NULL; + } + + ns = nextNs; + } + + node->nsDef = NULL; + node->ns = NULL; +} + +/** + * Detaches the given child from the given node. + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. + * Then, if flag is YES, the child's parent, prev, next and doc pointers are destroyed. +**/ ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node andNullifyPointers:(BOOL)flag +{ + // Update the surrounding prev/next pointers + if(child->prev == NULL) + { + if(child->next == NULL) + { + node->children = NULL; + node->last = NULL; + } + else + { + node->children = child->next; + child->next->prev = NULL; + } + } + else + { + if(child->next == NULL) + { + node->last = child->prev; + child->prev->next = NULL; + } + else + { + child->prev->next = child->next; + child->next->prev = child->prev; + } + } + + if(flag) + { + // Nullify pointers + child->parent = NULL; + child->prev = NULL; + child->next = NULL; + if(child->doc != NULL) [self recursiveStripDocPointersFromNode:child]; + } +} + +/** + * Detaches the given child from the given node. + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. + * Then the child's parent, prev, next and doc pointers are destroyed. +**/ ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node +{ + [self detachChild:child fromNode:node andNullifyPointers:YES]; +} + +/** + * Removes the given child from the given node. + * The child's surrounding prev/next pointers are properly updated to remove the child from the node's children list. + * Then the child is recursively freed if it's no longer being referenced. + * Otherwise, it's parent, prev, next and doc pointers are destroyed. + * + * During the recursive free, subnodes still being referenced are properly handled. +**/ ++ (void)removeChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node +{ + // We perform a wee bit of optimization here. + // Imagine that we're removing the root element of a big tree, and none of the elements are retained. + // If we simply call detachChild:fromNode:, this will traverse the entire tree, nullifying doc pointers. + // Then, when we call nodeFree:, it will again traverse the entire tree, freeing all the nodes. + // To avoid this double traversal, we skip the nullification step in the detach method, and let nodeFree do it. + [self detachChild:child fromNode:node andNullifyPointers:NO]; + + // Free the child recursively if it's no longer in use + [self nodeFree:child]; +} + +/** + * Removes all children from the given node. + * All children are either recursively freed, or their parent, prev, next and doc pointers are properly destroyed. + * Upon return, the given node's children pointer is NULL. + * + * During the recursive free, subnodes still being referenced are properly handled. +**/ ++ (void)removeAllChildrenFromNode:(xmlNodePtr)node +{ + xmlNodePtr child = node->children; + + while(child != NULL) + { + xmlNodePtr nextChild = child->next; + + // Free the child recursively if it's no longer in use + [self nodeFree:child]; + + child = nextChild; + } + + node->children = NULL; + node->last = NULL; +} + +/** + * Removes the root element from the given document. +**/ ++ (void)removeAllChildrenFromDoc:(xmlDocPtr)doc +{ + xmlNodePtr child = doc->children; + + while(child != NULL) + { + xmlNodePtr nextChild = child->next; + + if(child->type == XML_ELEMENT_NODE) + { + // Remove child from list of children + if(child->prev != NULL) + { + child->prev->next = child->next; + } + if(child->next != NULL) + { + child->next->prev = child->prev; + } + if(doc->children == child) + { + doc->children = child->next; + } + if(doc->last == child) + { + doc->last = child->prev; + } + + // Free the child recursively if it's no longer in use + [self nodeFree:child]; + } + else + { + // Leave comments and DTD's embedded in the doc's child list. + // They will get freed in xmlFreeDoc. + } + + child = nextChild; + } +} + +/** + * Adds self to the node's retain list. + * This way we know the node is still being referenced, and it won't be improperly freed. +**/ +- (void)nodeRetain +{ + // Warning: The _private variable is in a different location in the xmlNsPtr + + if([self isXmlNsPtr]) + ((xmlNsPtr)genericPtr)->_private = self; + else + ((xmlStdPtr)genericPtr)->_private = self; +} + +/** + * Removes self from the node's retain list. + * If the node is no longer being referenced, and it's not still embedded within a heirarchy above, then + * the node is properly freed. This includes element nodes, which are recursively freed, detaching any subnodes + * that are still being referenced. +**/ +- (void)nodeRelease +{ + // Check to see if the node can be released. + // Did you read the giant readme comment section above? + + // Warning: The _private variable is in a different location in the xmlNsPtr + + if([self isXmlNsPtr]) + { + xmlNsPtr ns = (xmlNsPtr)genericPtr; + ns->_private = NULL; + + if(nsParentPtr == NULL) + { + xmlFreeNs(ns); + } + else + { + // The node still has a parent, so it's still in use + } + } + else + { + xmlStdPtr node = (xmlStdPtr)genericPtr; + node->_private = NULL; + + if(node->parent == NULL) + { + if([self isXmlAttrPtr]) + { + xmlFreeProp((xmlAttrPtr)genericPtr); + } + else if([self isXmlDtdPtr]) + { + xmlFreeDtd((xmlDtdPtr)genericPtr); + } + else if([self isXmlDocPtr]) + { + [[self class] removeAllChildrenFromDoc:(xmlDocPtr)genericPtr]; + xmlFreeDoc((xmlDocPtr)genericPtr); + } + else + { + [[self class] nodeFree:(xmlNodePtr)genericPtr]; + } + } + else + { + // The node still has a parent, so it's still in use + } + } +} + +/** + * Returns the last error encountered by libxml. + * Errors are caught in the MyErrorHandler method within DDXMLDocument. +**/ ++ (NSError *)lastError +{ + NSValue *lastErrorValue = [[[NSThread currentThread] threadDictionary] objectForKey:DDLastErrorKey]; + if(lastErrorValue) + { + xmlError lastError; + [lastErrorValue getValue:&lastError]; + + int errCode = lastError.code; + NSString *errMsg = [[NSString stringWithFormat:@"%s", lastError.message] stringByTrimming]; + + NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + + return [NSError errorWithDomain:@"DDXMLErrorDomain" code:errCode userInfo:info]; + } + else + { + return nil; + } +} + +static void MyErrorHandler(void * userData, xmlErrorPtr error) +{ + // This method is called by libxml when an error occurs. + // We register for this error in the initialize method below. + + // Extract error message and store in the current thread's dictionary. + // This ensure's thread safey, and easy access for all other DDXML classes. + + if(error == NULL) + { + [[[NSThread currentThread] threadDictionary] removeObjectForKey:DDLastErrorKey]; + } + else + { + NSValue *errorValue = [NSValue valueWithBytes:error objCType:@encode(xmlError)]; + + [[[NSThread currentThread] threadDictionary] setObject:errorValue forKey:DDLastErrorKey]; + } +} + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLPrivate.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLPrivate.h new file mode 100644 index 0000000..cd3f6b6 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/DDXMLPrivate.h @@ -0,0 +1,79 @@ +#import "DDXMLNode.h" +#import "DDXMLElement.h" +#import "DDXMLDocument.h" + +// We can't rely solely on NSAssert, because many developers disable them for release builds. +// Our API contract requires us to keep these assertions intact. +#define DDCheck(condition, desc, ...) { if(!(condition)) { [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd object:self file:[NSString stringWithUTF8String:__FILE__] lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; } } + +#define DDLastErrorKey @"DDXML:LastError" + + +@interface DDXMLNode (PrivateAPI) + ++ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr; + ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr; +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr; + ++ (id)nodeWithPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent; +- (id)initWithCheckedPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent; + ++ (BOOL)isXmlAttrPtr:(xmlKindPtr)kindPtr; +- (BOOL)isXmlAttrPtr; + ++ (BOOL)isXmlNodePtr:(xmlKindPtr)kindPtr; +- (BOOL)isXmlNodePtr; + ++ (BOOL)isXmlDocPtr:(xmlKindPtr)kindPtr; +- (BOOL)isXmlDocPtr; + ++ (BOOL)isXmlDtdPtr:(xmlKindPtr)kindPtr; +- (BOOL)isXmlDtdPtr; + ++ (BOOL)isXmlNsPtr:(xmlKindPtr)kindPtr; +- (BOOL)isXmlNsPtr; + +- (BOOL)hasParent; + ++ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node; + ++ (void)detachAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node; ++ (void)removeAttribute:(xmlAttrPtr)attr fromNode:(xmlNodePtr)node; ++ (void)removeAllAttributesFromNode:(xmlNodePtr)node; + ++ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node; ++ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node; ++ (void)removeAllNamespacesFromNode:(xmlNodePtr)node; + ++ (void)detachChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node; ++ (void)removeChild:(xmlNodePtr)child fromNode:(xmlNodePtr)node; ++ (void)removeAllChildrenFromNode:(xmlNodePtr)node; + ++ (void)removeAllChildrenFromDoc:(xmlDocPtr)doc; + +- (void)nodeRetain; +- (void)nodeRelease; + ++ (NSError *)lastError; + +@end + +@interface DDXMLElement (PrivateAPI) + ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr; +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr; + +- (NSArray *)elementsWithName:(NSString *)name uri:(NSString *)URI; + ++ (DDXMLNode *)resolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr; ++ (NSString *)resolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr; + +@end + +@interface DDXMLDocument (PrivateAPI) + ++ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr; +- (id)initWithCheckedPrimitive:(xmlKindPtr)kindPtr; + +@end \ No newline at end of file diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.h b/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.h new file mode 100644 index 0000000..a944c23 --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.h @@ -0,0 +1,14 @@ +#import +#import + + +@interface NSString (NSStringAdditions) + +/** + * xmlChar - A basic replacement for char, a byte in a UTF-8 encoded string. +**/ +- (const xmlChar *)xmlChar; + +- (NSString *)stringByTrimming; + +@end diff --git a/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.m b/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.m new file mode 100644 index 0000000..b73ed2b --- /dev/null +++ b/code/5-httpriot/HTTPRiot/Vendor/KissXML/NSStringAdditions.m @@ -0,0 +1,29 @@ +#import "NSStringAdditions.h" + + +@implementation NSString (NSStringAdditions) + +- (const xmlChar *)xmlChar +{ + return (const xmlChar *)[self UTF8String]; +} + +#ifdef GNUSTEP +- (NSString *)stringByTrimming +{ + return [self stringByTrimmingSpaces]; +} +#else +- (NSString *)stringByTrimming +{ + NSMutableString *mStr = [self mutableCopy]; + CFStringTrimWhitespace((CFMutableStringRef)mStr); + + NSString *result = [mStr copy]; + + [mStr release]; + return [result autorelease]; +} +#endif + +@end diff --git a/code/5-httpriot/MainWindow.xib b/code/5-httpriot/MainWindow.xib new file mode 100644 index 0000000..bd15e7f --- /dev/null +++ b/code/5-httpriot/MainWindow.xib @@ -0,0 +1,556 @@ + + + + 800 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + 1 + + + YES + + + + IBCocoaTouchFramework + + + GoalsViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{673, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{186, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + GoalsViewController + UITableViewController + + add + id + + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UITableViewController + + IBUserSource + + + + + NSObject + + IBProjectSource + json-framework/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + json-framework/SBJsonWriter.h + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/5-httpriot/README.md b/code/5-httpriot/README.md new file mode 100644 index 0000000..e8c5fd9 --- /dev/null +++ b/code/5-httpriot/README.md @@ -0,0 +1,4 @@ +Save Up iPhone App +================== + +This version uses the HTTPRiot library to CRUD remote goals (asynchronous). \ No newline at end of file diff --git a/code/5-httpriot/SaveUp-Info.plist b/code/5-httpriot/SaveUp-Info.plist new file mode 100644 index 0000000..660b9ed --- /dev/null +++ b/code/5-httpriot/SaveUp-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/5-httpriot/SaveUp.xcodeproj/project.pbxproj b/code/5-httpriot/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..2857f35 --- /dev/null +++ b/code/5-httpriot/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,550 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 2729868011A49DDD004873E9 /* NSDictionary+ParamUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729864411A49DDD004873E9 /* NSDictionary+ParamUtils.m */; }; + 2729868111A49DDD004873E9 /* NSObject+InvocationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729864611A49DDD004873E9 /* NSObject+InvocationUtils.m */; }; + 2729868211A49DDD004873E9 /* NSString+EscapingUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729864811A49DDD004873E9 /* NSString+EscapingUtils.m */; }; + 2729868311A49DDD004873E9 /* HRFormatJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729864B11A49DDD004873E9 /* HRFormatJSON.m */; }; + 2729868411A49DDD004873E9 /* HRFormatXML.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729864E11A49DDD004873E9 /* HRFormatXML.m */; }; + 2729868511A49DDD004873E9 /* HRGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729865011A49DDD004873E9 /* HRGlobal.m */; }; + 2729868611A49DDD004873E9 /* HROperationQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729865211A49DDD004873E9 /* HROperationQueue.m */; }; + 2729868711A49DDD004873E9 /* HRRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729865411A49DDD004873E9 /* HRRequestOperation.m */; }; + 2729868811A49DDD004873E9 /* HRRestModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729865711A49DDD004873E9 /* HRRestModel.m */; }; + 2729868911A49DDD004873E9 /* HRBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729865C11A49DDD004873E9 /* HRBase64.m */; }; + 2729868A11A49DDD004873E9 /* AIXMLDocumentSerialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866011A49DDD004873E9 /* AIXMLDocumentSerialize.m */; }; + 2729868B11A49DDD004873E9 /* AIXMLElementSerialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866211A49DDD004873E9 /* AIXMLElementSerialize.m */; }; + 2729868C11A49DDD004873E9 /* CREDITS in Resources */ = {isa = PBXBuildFile; fileRef = 2729866511A49DDD004873E9 /* CREDITS */; }; + 2729868D11A49DDD004873E9 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866811A49DDD004873E9 /* NSObject+SBJSON.m */; }; + 2729868E11A49DDD004873E9 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866A11A49DDD004873E9 /* NSString+SBJSON.m */; }; + 2729868F11A49DDD004873E9 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866C11A49DDD004873E9 /* SBJSON.m */; }; + 2729869011A49DDD004873E9 /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729866E11A49DDD004873E9 /* SBJsonBase.m */; }; + 2729869111A49DDD004873E9 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867011A49DDD004873E9 /* SBJsonParser.m */; }; + 2729869211A49DDD004873E9 /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867211A49DDD004873E9 /* SBJsonWriter.m */; }; + 2729869311A49DDD004873E9 /* DDXMLDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867611A49DDD004873E9 /* DDXMLDocument.m */; }; + 2729869411A49DDD004873E9 /* DDXMLElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867811A49DDD004873E9 /* DDXMLElement.m */; }; + 2729869511A49DDD004873E9 /* DDXMLElementAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867A11A49DDD004873E9 /* DDXMLElementAdditions.m */; }; + 2729869611A49DDD004873E9 /* DDXMLNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867C11A49DDD004873E9 /* DDXMLNode.m */; }; + 2729869711A49DDD004873E9 /* NSStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2729867F11A49DDD004873E9 /* NSStringAdditions.m */; }; + 273459F911936B5B005C8C5F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 273459F811936B5B005C8C5F /* icon.png */; }; + 27AAD7E01193766E006153B1 /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7DF1193766E006153B1 /* Goal.m */; }; + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */; }; + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */; }; + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* GoalsViewController.m */; }; + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* GoalsViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 2729864311A49DDD004873E9 /* NSDictionary+ParamUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+ParamUtils.h"; sourceTree = ""; }; + 2729864411A49DDD004873E9 /* NSDictionary+ParamUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+ParamUtils.m"; sourceTree = ""; }; + 2729864511A49DDD004873E9 /* NSObject+InvocationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+InvocationUtils.h"; sourceTree = ""; }; + 2729864611A49DDD004873E9 /* NSObject+InvocationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+InvocationUtils.m"; sourceTree = ""; }; + 2729864711A49DDD004873E9 /* NSString+EscapingUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+EscapingUtils.h"; sourceTree = ""; }; + 2729864811A49DDD004873E9 /* NSString+EscapingUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+EscapingUtils.m"; sourceTree = ""; }; + 2729864A11A49DDD004873E9 /* HRFormatJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRFormatJSON.h; sourceTree = ""; }; + 2729864B11A49DDD004873E9 /* HRFormatJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRFormatJSON.m; sourceTree = ""; }; + 2729864C11A49DDD004873E9 /* HRFormatterProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRFormatterProtocol.h; sourceTree = ""; }; + 2729864D11A49DDD004873E9 /* HRFormatXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRFormatXML.h; sourceTree = ""; }; + 2729864E11A49DDD004873E9 /* HRFormatXML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRFormatXML.m; sourceTree = ""; }; + 2729864F11A49DDD004873E9 /* HRGlobal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRGlobal.h; sourceTree = ""; }; + 2729865011A49DDD004873E9 /* HRGlobal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRGlobal.m; sourceTree = ""; }; + 2729865111A49DDD004873E9 /* HROperationQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HROperationQueue.h; sourceTree = ""; }; + 2729865211A49DDD004873E9 /* HROperationQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HROperationQueue.m; sourceTree = ""; }; + 2729865311A49DDD004873E9 /* HRRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRRequestOperation.h; sourceTree = ""; }; + 2729865411A49DDD004873E9 /* HRRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRRequestOperation.m; sourceTree = ""; }; + 2729865511A49DDD004873E9 /* HRResponseDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRResponseDelegate.h; sourceTree = ""; }; + 2729865611A49DDD004873E9 /* HRRestModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRRestModel.h; sourceTree = ""; }; + 2729865711A49DDD004873E9 /* HRRestModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRRestModel.m; sourceTree = ""; }; + 2729865811A49DDD004873E9 /* HTTPRiot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRiot.h; sourceTree = ""; }; + 2729865911A49DDD004873E9 /* HTTPRiot_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRiot_Prefix.pch; sourceTree = ""; }; + 2729865B11A49DDD004873E9 /* HRBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HRBase64.h; sourceTree = ""; }; + 2729865C11A49DDD004873E9 /* HRBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HRBase64.m; sourceTree = ""; }; + 2729865F11A49DDD004873E9 /* AIXMLDocumentSerialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIXMLDocumentSerialize.h; sourceTree = ""; }; + 2729866011A49DDD004873E9 /* AIXMLDocumentSerialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIXMLDocumentSerialize.m; sourceTree = ""; }; + 2729866111A49DDD004873E9 /* AIXMLElementSerialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIXMLElementSerialize.h; sourceTree = ""; }; + 2729866211A49DDD004873E9 /* AIXMLElementSerialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIXMLElementSerialize.m; sourceTree = ""; }; + 2729866311A49DDD004873E9 /* AIXMLSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIXMLSerialization.h; sourceTree = ""; }; + 2729866511A49DDD004873E9 /* CREDITS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CREDITS; sourceTree = ""; }; + 2729866611A49DDD004873E9 /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 2729866711A49DDD004873E9 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 2729866811A49DDD004873E9 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 2729866911A49DDD004873E9 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 2729866A11A49DDD004873E9 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 2729866B11A49DDD004873E9 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 2729866C11A49DDD004873E9 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 2729866D11A49DDD004873E9 /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 2729866E11A49DDD004873E9 /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 2729866F11A49DDD004873E9 /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 2729867011A49DDD004873E9 /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 2729867111A49DDD004873E9 /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 2729867211A49DDD004873E9 /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 2729867411A49DDD004873E9 /* DDXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXML.h; sourceTree = ""; }; + 2729867511A49DDD004873E9 /* DDXMLDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXMLDocument.h; sourceTree = ""; }; + 2729867611A49DDD004873E9 /* DDXMLDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDXMLDocument.m; sourceTree = ""; }; + 2729867711A49DDD004873E9 /* DDXMLElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXMLElement.h; sourceTree = ""; }; + 2729867811A49DDD004873E9 /* DDXMLElement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDXMLElement.m; sourceTree = ""; }; + 2729867911A49DDD004873E9 /* DDXMLElementAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXMLElementAdditions.h; sourceTree = ""; }; + 2729867A11A49DDD004873E9 /* DDXMLElementAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDXMLElementAdditions.m; sourceTree = ""; }; + 2729867B11A49DDD004873E9 /* DDXMLNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXMLNode.h; sourceTree = ""; }; + 2729867C11A49DDD004873E9 /* DDXMLNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDXMLNode.m; sourceTree = ""; }; + 2729867D11A49DDD004873E9 /* DDXMLPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDXMLPrivate.h; sourceTree = ""; }; + 2729867E11A49DDD004873E9 /* NSStringAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSStringAdditions.h; sourceTree = ""; }; + 2729867F11A49DDD004873E9 /* NSStringAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSStringAdditions.m; sourceTree = ""; }; + 273459F811936B5B005C8C5F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 27AAD7DE1193766E006153B1 /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 27AAD7DF1193766E006153B1 /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHelpers.h; sourceTree = ""; }; + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHelpers.m; sourceTree = ""; }; + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalAddViewController.h; sourceTree = ""; }; + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalAddViewController.m; sourceTree = ""; }; + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalDetailViewController.h; sourceTree = ""; }; + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalDetailViewController.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 28F335F01007B36200424DE2 /* GoalsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsViewController.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SaveUp-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */, + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */, + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */, + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */, + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */, + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */, + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */, + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + 27AAD7DE1193766E006153B1 /* Goal.h */, + 27AAD7DF1193766E006153B1 /* Goal.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 2729864011A49DD5004873E9 /* Vendor */ = { + isa = PBXGroup; + children = ( + 2729864111A49DDD004873E9 /* HTTPRiot */, + ); + name = Vendor; + sourceTree = ""; + }; + 2729864111A49DDD004873E9 /* HTTPRiot */ = { + isa = PBXGroup; + children = ( + 2729864211A49DDD004873E9 /* Extensions */, + 2729864911A49DDD004873E9 /* Formatters */, + 2729864F11A49DDD004873E9 /* HRGlobal.h */, + 2729865011A49DDD004873E9 /* HRGlobal.m */, + 2729865111A49DDD004873E9 /* HROperationQueue.h */, + 2729865211A49DDD004873E9 /* HROperationQueue.m */, + 2729865311A49DDD004873E9 /* HRRequestOperation.h */, + 2729865411A49DDD004873E9 /* HRRequestOperation.m */, + 2729865511A49DDD004873E9 /* HRResponseDelegate.h */, + 2729865611A49DDD004873E9 /* HRRestModel.h */, + 2729865711A49DDD004873E9 /* HRRestModel.m */, + 2729865811A49DDD004873E9 /* HTTPRiot.h */, + 2729865911A49DDD004873E9 /* HTTPRiot_Prefix.pch */, + 2729865A11A49DDD004873E9 /* Utilities */, + 2729865D11A49DDD004873E9 /* Vendor */, + ); + path = HTTPRiot; + sourceTree = ""; + }; + 2729864211A49DDD004873E9 /* Extensions */ = { + isa = PBXGroup; + children = ( + 2729864311A49DDD004873E9 /* NSDictionary+ParamUtils.h */, + 2729864411A49DDD004873E9 /* NSDictionary+ParamUtils.m */, + 2729864511A49DDD004873E9 /* NSObject+InvocationUtils.h */, + 2729864611A49DDD004873E9 /* NSObject+InvocationUtils.m */, + 2729864711A49DDD004873E9 /* NSString+EscapingUtils.h */, + 2729864811A49DDD004873E9 /* NSString+EscapingUtils.m */, + ); + path = Extensions; + sourceTree = ""; + }; + 2729864911A49DDD004873E9 /* Formatters */ = { + isa = PBXGroup; + children = ( + 2729864A11A49DDD004873E9 /* HRFormatJSON.h */, + 2729864B11A49DDD004873E9 /* HRFormatJSON.m */, + 2729864C11A49DDD004873E9 /* HRFormatterProtocol.h */, + 2729864D11A49DDD004873E9 /* HRFormatXML.h */, + 2729864E11A49DDD004873E9 /* HRFormatXML.m */, + ); + path = Formatters; + sourceTree = ""; + }; + 2729865A11A49DDD004873E9 /* Utilities */ = { + isa = PBXGroup; + children = ( + 2729865B11A49DDD004873E9 /* HRBase64.h */, + 2729865C11A49DDD004873E9 /* HRBase64.m */, + ); + path = Utilities; + sourceTree = ""; + }; + 2729865D11A49DDD004873E9 /* Vendor */ = { + isa = PBXGroup; + children = ( + 2729865E11A49DDD004873E9 /* AIXMLSerialization */, + 2729866411A49DDD004873E9 /* JSON */, + 2729867311A49DDD004873E9 /* KissXML */, + ); + path = Vendor; + sourceTree = ""; + }; + 2729865E11A49DDD004873E9 /* AIXMLSerialization */ = { + isa = PBXGroup; + children = ( + 2729865F11A49DDD004873E9 /* AIXMLDocumentSerialize.h */, + 2729866011A49DDD004873E9 /* AIXMLDocumentSerialize.m */, + 2729866111A49DDD004873E9 /* AIXMLElementSerialize.h */, + 2729866211A49DDD004873E9 /* AIXMLElementSerialize.m */, + 2729866311A49DDD004873E9 /* AIXMLSerialization.h */, + ); + path = AIXMLSerialization; + sourceTree = ""; + }; + 2729866411A49DDD004873E9 /* JSON */ = { + isa = PBXGroup; + children = ( + 2729866511A49DDD004873E9 /* CREDITS */, + 2729866611A49DDD004873E9 /* JSON.h */, + 2729866711A49DDD004873E9 /* NSObject+SBJSON.h */, + 2729866811A49DDD004873E9 /* NSObject+SBJSON.m */, + 2729866911A49DDD004873E9 /* NSString+SBJSON.h */, + 2729866A11A49DDD004873E9 /* NSString+SBJSON.m */, + 2729866B11A49DDD004873E9 /* SBJSON.h */, + 2729866C11A49DDD004873E9 /* SBJSON.m */, + 2729866D11A49DDD004873E9 /* SBJsonBase.h */, + 2729866E11A49DDD004873E9 /* SBJsonBase.m */, + 2729866F11A49DDD004873E9 /* SBJsonParser.h */, + 2729867011A49DDD004873E9 /* SBJsonParser.m */, + 2729867111A49DDD004873E9 /* SBJsonWriter.h */, + 2729867211A49DDD004873E9 /* SBJsonWriter.m */, + ); + path = JSON; + sourceTree = ""; + }; + 2729867311A49DDD004873E9 /* KissXML */ = { + isa = PBXGroup; + children = ( + 2729867411A49DDD004873E9 /* DDXML.h */, + 2729867511A49DDD004873E9 /* DDXMLDocument.h */, + 2729867611A49DDD004873E9 /* DDXMLDocument.m */, + 2729867711A49DDD004873E9 /* DDXMLElement.h */, + 2729867811A49DDD004873E9 /* DDXMLElement.m */, + 2729867911A49DDD004873E9 /* DDXMLElementAdditions.h */, + 2729867A11A49DDD004873E9 /* DDXMLElementAdditions.m */, + 2729867B11A49DDD004873E9 /* DDXMLNode.h */, + 2729867C11A49DDD004873E9 /* DDXMLNode.m */, + 2729867D11A49DDD004873E9 /* DDXMLPrivate.h */, + 2729867E11A49DDD004873E9 /* NSStringAdditions.h */, + 2729867F11A49DDD004873E9 /* NSStringAdditions.m */, + ); + path = KissXML; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 2729864011A49DD5004873E9 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 273459F811936B5B005C8C5F /* icon.png */, + 28F335F01007B36200424DE2 /* GoalsViewController.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = SaveUp; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */, + 273459F911936B5B005C8C5F /* icon.png in Resources */, + 2729868C11A49DDD004873E9 /* CREDITS in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */, + 27AAD7E01193766E006153B1 /* Goal.m in Sources */, + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */, + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */, + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */, + 2729868011A49DDD004873E9 /* NSDictionary+ParamUtils.m in Sources */, + 2729868111A49DDD004873E9 /* NSObject+InvocationUtils.m in Sources */, + 2729868211A49DDD004873E9 /* NSString+EscapingUtils.m in Sources */, + 2729868311A49DDD004873E9 /* HRFormatJSON.m in Sources */, + 2729868411A49DDD004873E9 /* HRFormatXML.m in Sources */, + 2729868511A49DDD004873E9 /* HRGlobal.m in Sources */, + 2729868611A49DDD004873E9 /* HROperationQueue.m in Sources */, + 2729868711A49DDD004873E9 /* HRRequestOperation.m in Sources */, + 2729868811A49DDD004873E9 /* HRRestModel.m in Sources */, + 2729868911A49DDD004873E9 /* HRBase64.m in Sources */, + 2729868A11A49DDD004873E9 /* AIXMLDocumentSerialize.m in Sources */, + 2729868B11A49DDD004873E9 /* AIXMLElementSerialize.m in Sources */, + 2729868D11A49DDD004873E9 /* NSObject+SBJSON.m in Sources */, + 2729868E11A49DDD004873E9 /* NSString+SBJSON.m in Sources */, + 2729868F11A49DDD004873E9 /* SBJSON.m in Sources */, + 2729869011A49DDD004873E9 /* SBJsonBase.m in Sources */, + 2729869111A49DDD004873E9 /* SBJsonParser.m in Sources */, + 2729869211A49DDD004873E9 /* SBJsonWriter.m in Sources */, + 2729869311A49DDD004873E9 /* DDXMLDocument.m in Sources */, + 2729869411A49DDD004873E9 /* DDXMLElement.m in Sources */, + 2729869511A49DDD004873E9 /* DDXMLElementAdditions.m in Sources */, + 2729869611A49DDD004873E9 /* DDXMLNode.m in Sources */, + 2729869711A49DDD004873E9 /* NSStringAdditions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = ""; + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + HEADER_SEARCH_PATHS = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.2.sdk/usr/include/libxml2; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + OTHER_LDFLAGS = ( + "-lxml2", + "-ObjC", + "-all_load", + ); + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = ""; + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + HEADER_SEARCH_PATHS = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.2.sdk/usr/include/libxml2; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + OTHER_LDFLAGS = ( + "-lxml2", + "-ObjC", + "-all_load", + ); + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = "~/Library/SDKs/httpriot-0.6.10/$(PLATFORM_NAME)$(IPHONEOS_DEPLOYMENT_TARGET).sdk"; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.2.sdk/usr/include/libxml2; + OTHER_LDFLAGS = ( + "-lhttpriot", + "-lxml2", + "-ObjC", + "-all_load", + ); + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ADDITIONAL_SDKS = "~/Library/SDKs/httpriot-0.6.10/$(PLATFORM_NAME)$(IPHONEOS_DEPLOYMENT_TARGET).sdk"; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.2.sdk/usr/include/libxml2; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + OTHER_LDFLAGS = ( + "-lhttpriot", + "-lxml2", + "-ObjC", + "-all_load", + ); + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/5-httpriot/SaveUp_Prefix.pch b/code/5-httpriot/SaveUp_Prefix.pch new file mode 100644 index 0000000..cde934a --- /dev/null +++ b/code/5-httpriot/SaveUp_Prefix.pch @@ -0,0 +1,15 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + + +#ifdef __OBJC__ + #import + #import + #import "AppHelpers.h" +#endif diff --git a/code/5-httpriot/icon.png b/code/5-httpriot/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/5-httpriot/icon.png differ diff --git a/code/5-httpriot/main.m b/code/5-httpriot/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/5-httpriot/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/6-objectiveresource/.gitignore b/code/6-objectiveresource/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/6-objectiveresource/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/6-objectiveresource/Classes/AppHelpers.h b/code/6-objectiveresource/Classes/AppHelpers.h new file mode 100644 index 0000000..2137364 --- /dev/null +++ b/code/6-objectiveresource/Classes/AppHelpers.h @@ -0,0 +1,8 @@ +#define TABLE_BACKGROUND_COLOR [UIColor colorWithRed:0.951 green:0.951 blue:0.951 alpha:1.000] + +void showAlert(NSString *message); + +NSString *formatDate(NSDate *date); +NSDate *parseDateTime(NSString *dateTimeString); + +NSString* numberToCurrency(NSString *number); \ No newline at end of file diff --git a/code/6-objectiveresource/Classes/AppHelpers.m b/code/6-objectiveresource/Classes/AppHelpers.m new file mode 100644 index 0000000..0765641 --- /dev/null +++ b/code/6-objectiveresource/Classes/AppHelpers.m @@ -0,0 +1,48 @@ +#import "AppHelpers.h" + +void showAlert(NSString *message) { + UIAlertView *alert = + [[UIAlertView alloc] initWithTitle:@"Whoops" + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + +NSString* formatDate(NSDate *date) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + [formatter setTimeStyle:NSDateFormatterMediumStyle]; + NSString *result = [formatter stringFromDate:date]; + [formatter release]; + return result; +} + +NSDate* parseDateTime(NSString *dateTimeString) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + NSDate *result = [formatter dateFromString:dateTimeString]; + [formatter release]; + return result; +} + +NSString * numberToCurrency(NSString *number) { + if (number == nil) { + return @"$0.00"; + } + + NSDecimalNumber *decimalNumber = + [NSDecimalNumber decimalNumberWithString:number]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:decimalNumber]; + + [formatter release]; + return result; +} \ No newline at end of file diff --git a/code/6-objectiveresource/Classes/Goal.h b/code/6-objectiveresource/Classes/Goal.h new file mode 100644 index 0000000..ed57f35 --- /dev/null +++ b/code/6-objectiveresource/Classes/Goal.h @@ -0,0 +1,17 @@ +#import "ObjectiveResource.h" + +@interface Goal : NSObject { + NSString *name; + NSString *amount; + NSString *goalId; + NSDate *createdAt; + NSDate *updatedAt; +} + +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, retain) NSDate *createdAt; +@property (nonatomic, retain) NSDate *updatedAt; + +@end \ No newline at end of file diff --git a/code/6-objectiveresource/Classes/Goal.m b/code/6-objectiveresource/Classes/Goal.m new file mode 100644 index 0000000..845d0f3 --- /dev/null +++ b/code/6-objectiveresource/Classes/Goal.m @@ -0,0 +1,20 @@ +#import "Goal.h" + +@implementation Goal + +@synthesize name; +@synthesize amount; +@synthesize goalId; +@synthesize createdAt; +@synthesize updatedAt; + +- (void)dealloc { + [name release]; + [amount release]; + [goalId release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +@end diff --git a/code/6-objectiveresource/Classes/GoalAddViewController.h b/code/6-objectiveresource/Classes/GoalAddViewController.h new file mode 100644 index 0000000..778845f --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalAddViewController.h @@ -0,0 +1,26 @@ +#import + +@class Goal; + +@protocol GoalChangeDelegate +- (void)didChangeGoal:(Goal *)goal; +@end + +@interface GoalAddViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)cancel; +- (IBAction)save; + +@end diff --git a/code/6-objectiveresource/Classes/GoalAddViewController.m b/code/6-objectiveresource/Classes/GoalAddViewController.m new file mode 100644 index 0000000..98f6836 --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalAddViewController.m @@ -0,0 +1,181 @@ +#import "GoalAddViewController.h" + +#import "Goal.h" + +@interface GoalAddViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newCancelButton; +- (UIBarButtonItem *)newSaveButton; +- (UITextField *)newTextField; +@end + +@implementation GoalAddViewController + +@synthesize goal; +@synthesize nameField; +@synthesize amountField; +@synthesize delegate; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [nameField release]; + [amountField release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newTextField]; + nameField.keyboardType = UIKeyboardTypeASCIICapable; + [nameField becomeFirstResponder]; + + amountField = [self newTextField]; + amountField.keyboardType = UIKeyboardTypeNumberPad; + + if (goal.goalId) { + nameField.text = goal.name; + amountField.text = goal.amount; + } else { + nameField.placeholder = @"Name"; + amountField.placeholder = @"Amount"; + } + + UIBarButtonItem *cancelButton = [self newCancelButton]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [self newSaveButton]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (goal.goalId) { + self.title = @"Edit Goal"; + } else { + self.title = @"Add Goal"; + } +} + +#pragma mark - +#pragma mark Actions + +-(IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +-(IBAction)save { + goal.name = nameField.text; + goal.amount = amountField.text; + + // If the model is new, then createRemote will be called. + // Otherwise, updateRemote will be called. + [goal saveRemote]; + + [self.delegate didChangeGoal:goal]; + + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newCancelButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Cancel" + style:UIBarButtonSystemItemCancel + target:self + action:@selector(cancel)]; +} + +- (UIBarButtonItem *)newSaveButton { + return [[UIBarButtonItem alloc] + initWithTitle:@"Save" + style:UIBarButtonSystemItemSave + target:self + action:@selector(save)]; +} + +- (UITextField *)newTextField { + UITextField *textField = + [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 285, 25)]; + textField.font = [UIFont systemFontOfSize:16]; + textField.delegate = self; + textField.returnKeyType = UIReturnKeyDone; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + [textField addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return textField; +} + +@end diff --git a/code/6-objectiveresource/Classes/GoalDetailViewController.h b/code/6-objectiveresource/Classes/GoalDetailViewController.h new file mode 100644 index 0000000..06c2fd1 --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalDetailViewController.h @@ -0,0 +1,19 @@ +#import + +#import "GoalAddViewController.h" + +@class Goal; + +@interface GoalDetailViewController : UITableViewController { + Goal *goal; + id delegate; +} + +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, assign) id delegate; + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate; + +- (IBAction)edit; + +@end diff --git a/code/6-objectiveresource/Classes/GoalDetailViewController.m b/code/6-objectiveresource/Classes/GoalDetailViewController.m new file mode 100644 index 0000000..0ddfd25 --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalDetailViewController.m @@ -0,0 +1,138 @@ +#import "GoalDetailViewController.h" + +#import "Goal.h" + +@interface GoalDetailViewController () +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath; +- (UIBarButtonItem *)newEditButton; +@end + +@implementation GoalDetailViewController + +@synthesize goal; +@synthesize delegate; + +enum ExpenseTableSections { + kNameSection = 0, + kAmountSection, + kDateSection +}; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goal release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal andDelegate:(id)aDelegate { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + self.delegate = aDelegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + UIBarButtonItem *editButton = [self newEditButton]; + self.navigationItem.rightBarButtonItem = editButton; + [editButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + self.title = goal.name; + [self.tableView reloadData]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)edit { + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self.delegate]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 3; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 1; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForHeaderInSection:(NSInteger)section { + NSString *title = nil; + switch (section) { + case kNameSection: + title = @"Name"; + break; + case kAmountSection: + title = @"Amount"; + break; + case kDateSection: + title = @"Date"; + break; + } + return title; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:CellIdentifier] autorelease]; + } + + [self prepareCell:cell forIndexPath:indexPath]; + + return cell; +} + +- (void)prepareCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case kNameSection: + cell.textLabel.text = goal.name; + break; + case kAmountSection: + cell.textLabel.text = numberToCurrency(goal.amount); + break; + case kDateSection: + cell.textLabel.text = formatDate(goal.createdAt); + break; + } +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newEditButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemEdit + target:self + action:@selector(edit)]; +} + +@end diff --git a/code/6-objectiveresource/Classes/GoalsViewController.h b/code/6-objectiveresource/Classes/GoalsViewController.h new file mode 100644 index 0000000..5d015ad --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalsViewController.h @@ -0,0 +1,11 @@ +#import "GoalAddViewController.h" + +@interface GoalsViewController : UITableViewController { + NSMutableArray *goals; +} + +@property (nonatomic, retain) NSArray *goals; + +- (IBAction)add; + +@end diff --git a/code/6-objectiveresource/Classes/GoalsViewController.m b/code/6-objectiveresource/Classes/GoalsViewController.m new file mode 100644 index 0000000..1619198 --- /dev/null +++ b/code/6-objectiveresource/Classes/GoalsViewController.m @@ -0,0 +1,135 @@ +#import "GoalsViewController.h" + +#import "Goal.h" +#import "GoalDetailViewController.h" + +@interface GoalsViewController () +- (UIBarButtonItem *)newAddButton; +@end + +@implementation GoalsViewController + +@synthesize goals; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + self.goals = [Goal findAllRemote]; + [self.tableView reloadData]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [self newAddButton]; + self.navigationItem.rightBarButtonItem = addButton; + [addButton release]; + + [self refresh]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)add { + Goal *goal = [[Goal alloc] init]; + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal andDelegate:self]; + [goal release]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CellIdentifier = @"Cell"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CellIdentifier] autorelease]; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = numberToCurrency(goal.amount); + + return cell; +} + +- (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView beginUpdates]; + if (editingStyle == UITableViewCellEditingStyleDelete) { + Goal *goal = [goals objectAtIndex:indexPath.row]; + [goal destroyRemote]; + [goals removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationFade]; + } + [tableView endUpdates]; +} + +#pragma mark - +#pragma mark Table view delegate + +- (void)tableView:(UITableView *)tableView +didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + GoalDetailViewController *controller = + [[GoalDetailViewController alloc] initWithGoal:goal andDelegate:self]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +#pragma mark - +#pragma mark Goal lifecycle callbacks + +- (void)didChangeGoal:(Goal *)goal { + [self refresh]; +} + +#pragma mark - +#pragma mark Private methods + +- (UIBarButtonItem *)newAddButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(add)]; +} + +@end + diff --git a/code/6-objectiveresource/Classes/SaveUpAppDelegate.h b/code/6-objectiveresource/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..a62ade0 --- /dev/null +++ b/code/6-objectiveresource/Classes/SaveUpAppDelegate.h @@ -0,0 +1,12 @@ +#import + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; + +@end + diff --git a/code/6-objectiveresource/Classes/SaveUpAppDelegate.m b/code/6-objectiveresource/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..a038113 --- /dev/null +++ b/code/6-objectiveresource/Classes/SaveUpAppDelegate.m @@ -0,0 +1,36 @@ +#import "SaveUpAppDelegate.h" + +#import "GoalsViewController.h" +#import "ObjectiveResourceConfig.h" + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [ObjectiveResourceConfig setSite:@"http://localhost:3000/"]; + [ObjectiveResourceConfig setResponseType:XmlResponse]; + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Save data if appropriate +} + +@end + diff --git a/code/6-objectiveresource/GoalsViewController.xib b/code/6-objectiveresource/GoalsViewController.xib new file mode 100644 index 0000000..f2ba22e --- /dev/null +++ b/code/6-objectiveresource/GoalsViewController.xib @@ -0,0 +1,382 @@ + + + + 784 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {320, 247} + + 3 + MQA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + + YES + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{144, 609}, {320, 247}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 5 + + + + YES + + GoalsViewController + UITableViewController + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/6-objectiveresource/MainWindow.xib b/code/6-objectiveresource/MainWindow.xib new file mode 100644 index 0000000..bd15e7f --- /dev/null +++ b/code/6-objectiveresource/MainWindow.xib @@ -0,0 +1,556 @@ + + + + 800 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + YES + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + 1 + + + YES + + + + IBCocoaTouchFramework + + + GoalsViewController + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{673, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{186, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 15 + + + + YES + + GoalsViewController + UITableViewController + + add + id + + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UITableViewController + + IBUserSource + + + + + NSObject + + IBProjectSource + json-framework/NSObject+SBJSON.h + + + + NSObject + + IBProjectSource + json-framework/SBJsonWriter.h + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableViewController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITableViewController.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + SaveUp.xcodeproj + 3 + 87 + + diff --git a/code/6-objectiveresource/README.md b/code/6-objectiveresource/README.md new file mode 100644 index 0000000..7c96e63 --- /dev/null +++ b/code/6-objectiveresource/README.md @@ -0,0 +1,4 @@ +Save Up iPhone App +================== + +This version uses the ObjectiveResource library to CRUD remote goals (synchronous). diff --git a/code/6-objectiveresource/SaveUp-Info.plist b/code/6-objectiveresource/SaveUp-Info.plist new file mode 100644 index 0000000..660b9ed --- /dev/null +++ b/code/6-objectiveresource/SaveUp-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/6-objectiveresource/SaveUp.xcodeproj/project.pbxproj b/code/6-objectiveresource/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..e3f4907 --- /dev/null +++ b/code/6-objectiveresource/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,612 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 273459F911936B5B005C8C5F /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 273459F811936B5B005C8C5F /* icon.png */; }; + 279D9BB011A5B1D600EFD522 /* Connection.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B5D11A5B1D600EFD522 /* Connection.m */; }; + 279D9BB111A5B1D600EFD522 /* ConnectionDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B5F11A5B1D600EFD522 /* ConnectionDelegate.m */; }; + 279D9BB211A5B1D600EFD522 /* ConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B6111A5B1D600EFD522 /* ConnectionManager.m */; }; + 279D9BB311A5B1D600EFD522 /* NSHTTPURLResponse+Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B6311A5B1D600EFD522 /* NSHTTPURLResponse+Error.m */; }; + 279D9BB411A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B6511A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.m */; }; + 279D9BB511A5B1D600EFD522 /* NSObject+ObjectiveResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B6711A5B1D600EFD522 /* NSObject+ObjectiveResource.m */; }; + 279D9BB611A5B1D600EFD522 /* NSString+InflectionSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B6F11A5B1D600EFD522 /* NSString+InflectionSupport.m */; }; + 279D9BB711A5B1D600EFD522 /* NSData+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7111A5B1D600EFD522 /* NSData+Additions.m */; }; + 279D9BB811A5B1D600EFD522 /* NSObject+PropertySupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7311A5B1D600EFD522 /* NSObject+PropertySupport.m */; }; + 279D9BB911A5B1D600EFD522 /* NSString+GSub.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7511A5B1D600EFD522 /* NSString+GSub.m */; }; + 279D9BBA11A5B1D600EFD522 /* ObjectiveResourceDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7711A5B1D600EFD522 /* ObjectiveResourceDateFormatter.m */; }; + 279D9BBB11A5B1D600EFD522 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7C11A5B1D600EFD522 /* NSObject+SBJSON.m */; }; + 279D9BBC11A5B1D600EFD522 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B7E11A5B1D600EFD522 /* NSString+SBJSON.m */; }; + 279D9BBD11A5B1D600EFD522 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8011A5B1D600EFD522 /* SBJSON.m */; }; + 279D9BBE11A5B1D600EFD522 /* NSArray+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8611A5B1D600EFD522 /* NSArray+JSONSerializableSupport.m */; }; + 279D9BBF11A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8811A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.m */; }; + 279D9BC011A5B1D600EFD522 /* NSObject+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8A11A5B1D600EFD522 /* NSObject+JSONSerializableSupport.m */; }; + 279D9BC111A5B1D600EFD522 /* NSDate+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8C11A5B1D600EFD522 /* NSDate+Serialize.m */; }; + 279D9BC211A5B1D600EFD522 /* NSDictionary+KeyTranslation.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B8E11A5B1D600EFD522 /* NSDictionary+KeyTranslation.m */; }; + 279D9BC311A5B1D600EFD522 /* NSObject+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9011A5B1D600EFD522 /* NSObject+Serialize.m */; }; + 279D9BC411A5B1D600EFD522 /* NSString+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9211A5B1D600EFD522 /* NSString+Serialize.m */; }; + 279D9BC511A5B1D600EFD522 /* FromXMLElementDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9611A5B1D600EFD522 /* FromXMLElementDelegate.m */; }; + 279D9BC611A5B1D600EFD522 /* NSArray+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9811A5B1D600EFD522 /* NSArray+XMLSerializableSupport.m */; }; + 279D9BC711A5B1D600EFD522 /* NSDate+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9A11A5B1D600EFD522 /* NSDate+XMLSerializableSupport.m */; }; + 279D9BC811A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9C11A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.m */; }; + 279D9BC911A5B1D600EFD522 /* NSNull+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9B9E11A5B1D600EFD522 /* NSNull+XMLSerializableSupport.m */; }; + 279D9BCA11A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BA011A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.m */; }; + 279D9BCB11A5B1D600EFD522 /* NSObject+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BA211A5B1D600EFD522 /* NSObject+XMLSerializableSupport.m */; }; + 279D9BCC11A5B1D600EFD522 /* NSString+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BA411A5B1D600EFD522 /* NSString+XMLSerializableSupport.m */; }; + 279D9BCD11A5B1D600EFD522 /* ObjectiveResourceConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BA911A5B1D600EFD522 /* ObjectiveResourceConfig.m */; }; + 279D9BCE11A5B1D600EFD522 /* Response.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BAB11A5B1D600EFD522 /* Response.m */; }; + 279D9BCF11A5B1D600EFD522 /* NSError+Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 279D9BAD11A5B1D600EFD522 /* NSError+Error.m */; }; + 279D9BD011A5B1D600EFD522 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 279D9BAE11A5B1D600EFD522 /* LICENSE */; }; + 279D9BD111A5B1D600EFD522 /* README.textile in Resources */ = {isa = PBXBuildFile; fileRef = 279D9BAF11A5B1D600EFD522 /* README.textile */; }; + 27AAD7E01193766E006153B1 /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AAD7DF1193766E006153B1 /* Goal.m */; }; + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */; }; + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */; }; + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* GoalsViewController.m */; }; + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28F335F01007B36200424DE2 /* GoalsViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 273459F811936B5B005C8C5F /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 279D9B5C11A5B1D600EFD522 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = ""; }; + 279D9B5D11A5B1D600EFD522 /* Connection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Connection.m; sourceTree = ""; }; + 279D9B5E11A5B1D600EFD522 /* ConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionDelegate.h; sourceTree = ""; }; + 279D9B5F11A5B1D600EFD522 /* ConnectionDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConnectionDelegate.m; sourceTree = ""; }; + 279D9B6011A5B1D600EFD522 /* ConnectionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionManager.h; sourceTree = ""; }; + 279D9B6111A5B1D600EFD522 /* ConnectionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConnectionManager.m; sourceTree = ""; }; + 279D9B6211A5B1D600EFD522 /* NSHTTPURLResponse+Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSHTTPURLResponse+Error.h"; sourceTree = ""; }; + 279D9B6311A5B1D600EFD522 /* NSHTTPURLResponse+Error.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPURLResponse+Error.m"; sourceTree = ""; }; + 279D9B6411A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableURLRequest+ResponseType.h"; sourceTree = ""; }; + 279D9B6511A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableURLRequest+ResponseType.m"; sourceTree = ""; }; + 279D9B6611A5B1D600EFD522 /* NSObject+ObjectiveResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+ObjectiveResource.h"; sourceTree = ""; }; + 279D9B6711A5B1D600EFD522 /* NSObject+ObjectiveResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+ObjectiveResource.m"; sourceTree = ""; }; + 279D9B6C11A5B1D600EFD522 /* CoreSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreSupport.h; sourceTree = ""; }; + 279D9B6E11A5B1D600EFD522 /* NSString+InflectionSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+InflectionSupport.h"; sourceTree = ""; }; + 279D9B6F11A5B1D600EFD522 /* NSString+InflectionSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+InflectionSupport.m"; sourceTree = ""; }; + 279D9B7011A5B1D600EFD522 /* NSData+Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Additions.h"; sourceTree = ""; }; + 279D9B7111A5B1D600EFD522 /* NSData+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Additions.m"; sourceTree = ""; }; + 279D9B7211A5B1D600EFD522 /* NSObject+PropertySupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PropertySupport.h"; sourceTree = ""; }; + 279D9B7311A5B1D600EFD522 /* NSObject+PropertySupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PropertySupport.m"; sourceTree = ""; }; + 279D9B7411A5B1D600EFD522 /* NSString+GSub.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+GSub.h"; sourceTree = ""; }; + 279D9B7511A5B1D600EFD522 /* NSString+GSub.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+GSub.m"; sourceTree = ""; }; + 279D9B7611A5B1D600EFD522 /* ObjectiveResourceDateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResourceDateFormatter.h; sourceTree = ""; }; + 279D9B7711A5B1D600EFD522 /* ObjectiveResourceDateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectiveResourceDateFormatter.m; sourceTree = ""; }; + 279D9B7811A5B1D600EFD522 /* ObjectiveSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveSupport.h; sourceTree = ""; }; + 279D9B7A11A5B1D600EFD522 /* JSONFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONFramework.h; sourceTree = ""; }; + 279D9B7B11A5B1D600EFD522 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 279D9B7C11A5B1D600EFD522 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 279D9B7D11A5B1D600EFD522 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 279D9B7E11A5B1D600EFD522 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 279D9B7F11A5B1D600EFD522 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 279D9B8011A5B1D600EFD522 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 279D9B8311A5B1D600EFD522 /* JSONSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONSerializable.h; sourceTree = ""; }; + 279D9B8411A5B1D600EFD522 /* JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONSerializableSupport.h; sourceTree = ""; }; + 279D9B8511A5B1D600EFD522 /* NSArray+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+JSONSerializableSupport.h"; sourceTree = ""; }; + 279D9B8611A5B1D600EFD522 /* NSArray+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+JSONSerializableSupport.m"; sourceTree = ""; }; + 279D9B8711A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+JSONSerializableSupport.h"; sourceTree = ""; }; + 279D9B8811A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+JSONSerializableSupport.m"; sourceTree = ""; }; + 279D9B8911A5B1D600EFD522 /* NSObject+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+JSONSerializableSupport.h"; sourceTree = ""; }; + 279D9B8A11A5B1D600EFD522 /* NSObject+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+JSONSerializableSupport.m"; sourceTree = ""; }; + 279D9B8B11A5B1D600EFD522 /* NSDate+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Serialize.h"; sourceTree = ""; }; + 279D9B8C11A5B1D600EFD522 /* NSDate+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Serialize.m"; sourceTree = ""; }; + 279D9B8D11A5B1D600EFD522 /* NSDictionary+KeyTranslation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+KeyTranslation.h"; sourceTree = ""; }; + 279D9B8E11A5B1D600EFD522 /* NSDictionary+KeyTranslation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+KeyTranslation.m"; sourceTree = ""; }; + 279D9B8F11A5B1D600EFD522 /* NSObject+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Serialize.h"; sourceTree = ""; }; + 279D9B9011A5B1D600EFD522 /* NSObject+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Serialize.m"; sourceTree = ""; }; + 279D9B9111A5B1D600EFD522 /* NSString+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Serialize.h"; sourceTree = ""; }; + 279D9B9211A5B1D600EFD522 /* NSString+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Serialize.m"; sourceTree = ""; }; + 279D9B9311A5B1D600EFD522 /* Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Serialize.h; sourceTree = ""; }; + 279D9B9511A5B1D600EFD522 /* FromXMLElementDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FromXMLElementDelegate.h; sourceTree = ""; }; + 279D9B9611A5B1D600EFD522 /* FromXMLElementDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FromXMLElementDelegate.m; sourceTree = ""; }; + 279D9B9711A5B1D600EFD522 /* NSArray+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9B9811A5B1D600EFD522 /* NSArray+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9B9911A5B1D600EFD522 /* NSDate+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9B9A11A5B1D600EFD522 /* NSDate+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9B9B11A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9B9C11A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9B9D11A5B1D600EFD522 /* NSNull+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNull+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9B9E11A5B1D600EFD522 /* NSNull+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNull+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9B9F11A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNumber+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9BA011A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNumber+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9BA111A5B1D600EFD522 /* NSObject+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9BA211A5B1D600EFD522 /* NSObject+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9BA311A5B1D600EFD522 /* NSString+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XMLSerializableSupport.h"; sourceTree = ""; }; + 279D9BA411A5B1D600EFD522 /* NSString+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XMLSerializableSupport.m"; sourceTree = ""; }; + 279D9BA511A5B1D600EFD522 /* XMLSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLSerializable.h; sourceTree = ""; }; + 279D9BA611A5B1D600EFD522 /* XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLSerializableSupport.h; sourceTree = ""; }; + 279D9BA711A5B1D600EFD522 /* ObjectiveResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResource.h; sourceTree = ""; }; + 279D9BA811A5B1D600EFD522 /* ObjectiveResourceConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResourceConfig.h; sourceTree = ""; }; + 279D9BA911A5B1D600EFD522 /* ObjectiveResourceConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectiveResourceConfig.m; sourceTree = ""; }; + 279D9BAA11A5B1D600EFD522 /* Response.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Response.h; sourceTree = ""; }; + 279D9BAB11A5B1D600EFD522 /* Response.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Response.m; sourceTree = ""; }; + 279D9BAC11A5B1D600EFD522 /* NSError+Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+Error.h"; sourceTree = ""; }; + 279D9BAD11A5B1D600EFD522 /* NSError+Error.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+Error.m"; sourceTree = ""; }; + 279D9BAE11A5B1D600EFD522 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 279D9BAF11A5B1D600EFD522 /* README.textile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.textile; sourceTree = ""; }; + 27AAD7DE1193766E006153B1 /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 27AAD7DF1193766E006153B1 /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHelpers.h; sourceTree = ""; }; + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHelpers.m; sourceTree = ""; }; + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalAddViewController.h; sourceTree = ""; }; + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalAddViewController.m; sourceTree = ""; }; + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalDetailViewController.h; sourceTree = ""; }; + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalDetailViewController.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 28F335F01007B36200424DE2 /* GoalsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsViewController.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SaveUp-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 27DD8ADF119474AD00FAC6C4 /* AppHelpers.h */, + 27DD8AE0119474AD00FAC6C4 /* AppHelpers.m */, + 28C286DF0D94DF7D0034E888 /* GoalsViewController.h */, + 28C286E00D94DF7D0034E888 /* GoalsViewController.m */, + 27DD8AE61194753700FAC6C4 /* GoalAddViewController.h */, + 27DD8AE71194753700FAC6C4 /* GoalAddViewController.m */, + 27DD8AE81194753700FAC6C4 /* GoalDetailViewController.h */, + 27DD8AE91194753700FAC6C4 /* GoalDetailViewController.m */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + 27AAD7DE1193766E006153B1 /* Goal.h */, + 27AAD7DF1193766E006153B1 /* Goal.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 279D9B5911A5B1D600EFD522 /* objectiveresource */ = { + isa = PBXGroup; + children = ( + 279D9B5A11A5B1D600EFD522 /* Classes */, + 279D9BAE11A5B1D600EFD522 /* LICENSE */, + 279D9BAF11A5B1D600EFD522 /* README.textile */, + ); + path = objectiveresource; + sourceTree = ""; + }; + 279D9B5A11A5B1D600EFD522 /* Classes */ = { + isa = PBXGroup; + children = ( + 279D9B5B11A5B1D600EFD522 /* lib */, + 279D9BAC11A5B1D600EFD522 /* NSError+Error.h */, + 279D9BAD11A5B1D600EFD522 /* NSError+Error.m */, + ); + path = Classes; + sourceTree = ""; + }; + 279D9B5B11A5B1D600EFD522 /* lib */ = { + isa = PBXGroup; + children = ( + 279D9B5C11A5B1D600EFD522 /* Connection.h */, + 279D9B5D11A5B1D600EFD522 /* Connection.m */, + 279D9B5E11A5B1D600EFD522 /* ConnectionDelegate.h */, + 279D9B5F11A5B1D600EFD522 /* ConnectionDelegate.m */, + 279D9B6011A5B1D600EFD522 /* ConnectionManager.h */, + 279D9B6111A5B1D600EFD522 /* ConnectionManager.m */, + 279D9B6211A5B1D600EFD522 /* NSHTTPURLResponse+Error.h */, + 279D9B6311A5B1D600EFD522 /* NSHTTPURLResponse+Error.m */, + 279D9B6411A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.h */, + 279D9B6511A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.m */, + 279D9B6611A5B1D600EFD522 /* NSObject+ObjectiveResource.h */, + 279D9B6711A5B1D600EFD522 /* NSObject+ObjectiveResource.m */, + 279D9B6811A5B1D600EFD522 /* objective_support */, + 279D9BA711A5B1D600EFD522 /* ObjectiveResource.h */, + 279D9BA811A5B1D600EFD522 /* ObjectiveResourceConfig.h */, + 279D9BA911A5B1D600EFD522 /* ObjectiveResourceConfig.m */, + 279D9BAA11A5B1D600EFD522 /* Response.h */, + 279D9BAB11A5B1D600EFD522 /* Response.m */, + ); + path = lib; + sourceTree = ""; + }; + 279D9B6811A5B1D600EFD522 /* objective_support */ = { + isa = PBXGroup; + children = ( + 279D9B6911A5B1D600EFD522 /* Classes */, + ); + path = objective_support; + sourceTree = ""; + }; + 279D9B6911A5B1D600EFD522 /* Classes */ = { + isa = PBXGroup; + children = ( + 279D9B6A11A5B1D600EFD522 /* lib */, + ); + path = Classes; + sourceTree = ""; + }; + 279D9B6A11A5B1D600EFD522 /* lib */ = { + isa = PBXGroup; + children = ( + 279D9B6B11A5B1D600EFD522 /* Core */, + 279D9B7911A5B1D600EFD522 /* json-framework */, + 279D9B8111A5B1D600EFD522 /* Serialization */, + ); + path = lib; + sourceTree = ""; + }; + 279D9B6B11A5B1D600EFD522 /* Core */ = { + isa = PBXGroup; + children = ( + 279D9B6C11A5B1D600EFD522 /* CoreSupport.h */, + 279D9B6D11A5B1D600EFD522 /* Inflections */, + 279D9B7011A5B1D600EFD522 /* NSData+Additions.h */, + 279D9B7111A5B1D600EFD522 /* NSData+Additions.m */, + 279D9B7211A5B1D600EFD522 /* NSObject+PropertySupport.h */, + 279D9B7311A5B1D600EFD522 /* NSObject+PropertySupport.m */, + 279D9B7411A5B1D600EFD522 /* NSString+GSub.h */, + 279D9B7511A5B1D600EFD522 /* NSString+GSub.m */, + 279D9B7611A5B1D600EFD522 /* ObjectiveResourceDateFormatter.h */, + 279D9B7711A5B1D600EFD522 /* ObjectiveResourceDateFormatter.m */, + 279D9B7811A5B1D600EFD522 /* ObjectiveSupport.h */, + ); + path = Core; + sourceTree = ""; + }; + 279D9B6D11A5B1D600EFD522 /* Inflections */ = { + isa = PBXGroup; + children = ( + 279D9B6E11A5B1D600EFD522 /* NSString+InflectionSupport.h */, + 279D9B6F11A5B1D600EFD522 /* NSString+InflectionSupport.m */, + ); + path = Inflections; + sourceTree = ""; + }; + 279D9B7911A5B1D600EFD522 /* json-framework */ = { + isa = PBXGroup; + children = ( + 279D9B7A11A5B1D600EFD522 /* JSONFramework.h */, + 279D9B7B11A5B1D600EFD522 /* NSObject+SBJSON.h */, + 279D9B7C11A5B1D600EFD522 /* NSObject+SBJSON.m */, + 279D9B7D11A5B1D600EFD522 /* NSString+SBJSON.h */, + 279D9B7E11A5B1D600EFD522 /* NSString+SBJSON.m */, + 279D9B7F11A5B1D600EFD522 /* SBJSON.h */, + 279D9B8011A5B1D600EFD522 /* SBJSON.m */, + ); + path = "json-framework"; + sourceTree = ""; + }; + 279D9B8111A5B1D600EFD522 /* Serialization */ = { + isa = PBXGroup; + children = ( + 279D9B8211A5B1D600EFD522 /* JSON */, + 279D9B8B11A5B1D600EFD522 /* NSDate+Serialize.h */, + 279D9B8C11A5B1D600EFD522 /* NSDate+Serialize.m */, + 279D9B8D11A5B1D600EFD522 /* NSDictionary+KeyTranslation.h */, + 279D9B8E11A5B1D600EFD522 /* NSDictionary+KeyTranslation.m */, + 279D9B8F11A5B1D600EFD522 /* NSObject+Serialize.h */, + 279D9B9011A5B1D600EFD522 /* NSObject+Serialize.m */, + 279D9B9111A5B1D600EFD522 /* NSString+Serialize.h */, + 279D9B9211A5B1D600EFD522 /* NSString+Serialize.m */, + 279D9B9311A5B1D600EFD522 /* Serialize.h */, + 279D9B9411A5B1D600EFD522 /* XML */, + ); + path = Serialization; + sourceTree = ""; + }; + 279D9B8211A5B1D600EFD522 /* JSON */ = { + isa = PBXGroup; + children = ( + 279D9B8311A5B1D600EFD522 /* JSONSerializable.h */, + 279D9B8411A5B1D600EFD522 /* JSONSerializableSupport.h */, + 279D9B8511A5B1D600EFD522 /* NSArray+JSONSerializableSupport.h */, + 279D9B8611A5B1D600EFD522 /* NSArray+JSONSerializableSupport.m */, + 279D9B8711A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.h */, + 279D9B8811A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.m */, + 279D9B8911A5B1D600EFD522 /* NSObject+JSONSerializableSupport.h */, + 279D9B8A11A5B1D600EFD522 /* NSObject+JSONSerializableSupport.m */, + ); + path = JSON; + sourceTree = ""; + }; + 279D9B9411A5B1D600EFD522 /* XML */ = { + isa = PBXGroup; + children = ( + 279D9B9511A5B1D600EFD522 /* FromXMLElementDelegate.h */, + 279D9B9611A5B1D600EFD522 /* FromXMLElementDelegate.m */, + 279D9B9711A5B1D600EFD522 /* NSArray+XMLSerializableSupport.h */, + 279D9B9811A5B1D600EFD522 /* NSArray+XMLSerializableSupport.m */, + 279D9B9911A5B1D600EFD522 /* NSDate+XMLSerializableSupport.h */, + 279D9B9A11A5B1D600EFD522 /* NSDate+XMLSerializableSupport.m */, + 279D9B9B11A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.h */, + 279D9B9C11A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.m */, + 279D9B9D11A5B1D600EFD522 /* NSNull+XMLSerializableSupport.h */, + 279D9B9E11A5B1D600EFD522 /* NSNull+XMLSerializableSupport.m */, + 279D9B9F11A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.h */, + 279D9BA011A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.m */, + 279D9BA111A5B1D600EFD522 /* NSObject+XMLSerializableSupport.h */, + 279D9BA211A5B1D600EFD522 /* NSObject+XMLSerializableSupport.m */, + 279D9BA311A5B1D600EFD522 /* NSString+XMLSerializableSupport.h */, + 279D9BA411A5B1D600EFD522 /* NSString+XMLSerializableSupport.m */, + 279D9BA511A5B1D600EFD522 /* XMLSerializable.h */, + 279D9BA611A5B1D600EFD522 /* XMLSerializableSupport.h */, + ); + path = XML; + sourceTree = ""; + }; + 27AAD7C011937571006153B1 /* Vendor */ = { + isa = PBXGroup; + children = ( + 279D9B5911A5B1D600EFD522 /* objectiveresource */, + ); + name = Vendor; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 27AAD7C011937571006153B1 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 273459F811936B5B005C8C5F /* icon.png */, + 28F335F01007B36200424DE2 /* GoalsViewController.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* SaveUp-Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = SaveUp; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 28F335F11007B36200424DE2 /* GoalsViewController.xib in Resources */, + 273459F911936B5B005C8C5F /* icon.png in Resources */, + 279D9BD011A5B1D600EFD522 /* LICENSE in Resources */, + 279D9BD111A5B1D600EFD522 /* README.textile in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 28C286E10D94DF7D0034E888 /* GoalsViewController.m in Sources */, + 27AAD7E01193766E006153B1 /* Goal.m in Sources */, + 27DD8AE1119474AD00FAC6C4 /* AppHelpers.m in Sources */, + 27DD8AEA1194753700FAC6C4 /* GoalAddViewController.m in Sources */, + 27DD8AEB1194753700FAC6C4 /* GoalDetailViewController.m in Sources */, + 279D9BB011A5B1D600EFD522 /* Connection.m in Sources */, + 279D9BB111A5B1D600EFD522 /* ConnectionDelegate.m in Sources */, + 279D9BB211A5B1D600EFD522 /* ConnectionManager.m in Sources */, + 279D9BB311A5B1D600EFD522 /* NSHTTPURLResponse+Error.m in Sources */, + 279D9BB411A5B1D600EFD522 /* NSMutableURLRequest+ResponseType.m in Sources */, + 279D9BB511A5B1D600EFD522 /* NSObject+ObjectiveResource.m in Sources */, + 279D9BB611A5B1D600EFD522 /* NSString+InflectionSupport.m in Sources */, + 279D9BB711A5B1D600EFD522 /* NSData+Additions.m in Sources */, + 279D9BB811A5B1D600EFD522 /* NSObject+PropertySupport.m in Sources */, + 279D9BB911A5B1D600EFD522 /* NSString+GSub.m in Sources */, + 279D9BBA11A5B1D600EFD522 /* ObjectiveResourceDateFormatter.m in Sources */, + 279D9BBB11A5B1D600EFD522 /* NSObject+SBJSON.m in Sources */, + 279D9BBC11A5B1D600EFD522 /* NSString+SBJSON.m in Sources */, + 279D9BBD11A5B1D600EFD522 /* SBJSON.m in Sources */, + 279D9BBE11A5B1D600EFD522 /* NSArray+JSONSerializableSupport.m in Sources */, + 279D9BBF11A5B1D600EFD522 /* NSDictionary+JSONSerializableSupport.m in Sources */, + 279D9BC011A5B1D600EFD522 /* NSObject+JSONSerializableSupport.m in Sources */, + 279D9BC111A5B1D600EFD522 /* NSDate+Serialize.m in Sources */, + 279D9BC211A5B1D600EFD522 /* NSDictionary+KeyTranslation.m in Sources */, + 279D9BC311A5B1D600EFD522 /* NSObject+Serialize.m in Sources */, + 279D9BC411A5B1D600EFD522 /* NSString+Serialize.m in Sources */, + 279D9BC511A5B1D600EFD522 /* FromXMLElementDelegate.m in Sources */, + 279D9BC611A5B1D600EFD522 /* NSArray+XMLSerializableSupport.m in Sources */, + 279D9BC711A5B1D600EFD522 /* NSDate+XMLSerializableSupport.m in Sources */, + 279D9BC811A5B1D600EFD522 /* NSDictionary+XMLSerializableSupport.m in Sources */, + 279D9BC911A5B1D600EFD522 /* NSNull+XMLSerializableSupport.m in Sources */, + 279D9BCA11A5B1D600EFD522 /* NSNumber+XMLSerializableSupport.m in Sources */, + 279D9BCB11A5B1D600EFD522 /* NSObject+XMLSerializableSupport.m in Sources */, + 279D9BCC11A5B1D600EFD522 /* NSString+XMLSerializableSupport.m in Sources */, + 279D9BCD11A5B1D600EFD522 /* ObjectiveResourceConfig.m in Sources */, + 279D9BCE11A5B1D600EFD522 /* Response.m in Sources */, + 279D9BCF11A5B1D600EFD522 /* NSError+Error.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = "SaveUp-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + PREBINDING = NO; + SDKROOT = iphoneos3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/6-objectiveresource/SaveUp_Prefix.pch b/code/6-objectiveresource/SaveUp_Prefix.pch new file mode 100644 index 0000000..cde934a --- /dev/null +++ b/code/6-objectiveresource/SaveUp_Prefix.pch @@ -0,0 +1,15 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// +#import + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iPhone SDK 3.0 and later." +#endif + + +#ifdef __OBJC__ + #import + #import + #import "AppHelpers.h" +#endif diff --git a/code/6-objectiveresource/icon.png b/code/6-objectiveresource/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/6-objectiveresource/icon.png differ diff --git a/code/6-objectiveresource/main.m b/code/6-objectiveresource/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/6-objectiveresource/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.h b/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.h new file mode 100644 index 0000000..2b3ce8e --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.h @@ -0,0 +1,13 @@ +// +// NSError+Error.h +// objective_resource +// +// Created by Adam Alexander on 3/10/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +@interface NSError(Error) + +-(NSArray *)errors; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.m b/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.m new file mode 100644 index 0000000..b42584d --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/NSError+Error.m @@ -0,0 +1,17 @@ +// +// NSError+Error.m +// objective_resource +// +// Created by Adam Alexander on 3/10/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import "NSError+Error.h" + +@implementation NSError(Error) + +-(NSArray *)errors { + return [self.userInfo valueForKey:NSLocalizedRecoveryOptionsErrorKey]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.h b/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.h new file mode 100644 index 0000000..043e46d --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.h @@ -0,0 +1,23 @@ +// +// Connection.h +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@class Response; + +@interface Connection : NSObject ++ (void) setTimeout:(float)timeout; ++ (float) timeout; ++ (Response *)post:(NSString *)body to:(NSString *)url; ++ (Response *)post:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)get:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)get:(NSString *)url; ++ (Response *)put:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)delete:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; + ++ (void) cancelAllActiveConnections; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.m b/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.m new file mode 100644 index 0000000..7098ee9 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/Connection.m @@ -0,0 +1,145 @@ +// +// Connection.m +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "Connection.h" +#import "Response.h" +#import "NSData+Additions.h" +#import "NSMutableURLRequest+ResponseType.h" +#import "ConnectionDelegate.h" + +//#define debugLog(...) NSLog(__VA_ARGS__) +#ifndef debugLog(...) + #define debugLog(...) +#endif + +@implementation Connection + +static float timeoutInterval = 5.0; + +static NSMutableArray *activeDelegates; + ++ (NSMutableArray *)activeDelegates { + if (nil == activeDelegates) { + activeDelegates = [NSMutableArray array]; + [activeDelegates retain]; + } + return activeDelegates; +} + ++ (void)setTimeout:(float)timeOut { + timeoutInterval = timeOut; +} ++ (float)timeout { + return timeoutInterval; +} + ++ (void)logRequest:(NSURLRequest *)request to:(NSString *)url { + debugLog(@"%@ -> %@", [request HTTPMethod], url); + if([request HTTPBody]) { + debugLog([[[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding] autorelease]); + } +} + ++ (Response *)sendRequest:(NSMutableURLRequest *)request withUser:(NSString *)user andPassword:(NSString *)password { + + //lots of servers fail to implement http basic authentication correctly, so we pass the credentials even if they are not asked for + //TODO make this configurable? + NSURL *url = [request URL]; + if(user && password) { + NSString *authString = [[[NSString stringWithFormat:@"%@:%@",user, password] dataUsingEncoding:NSUTF8StringEncoding] base64Encoding]; + [request addValue:[NSString stringWithFormat:@"Basic %@", authString] forHTTPHeaderField:@"Authorization"]; + NSString *escapedUser = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)user, NULL, (CFStringRef)@"@.:", kCFStringEncodingUTF8); + NSString *escapedPassword = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)password, NULL, (CFStringRef)@"@.:", kCFStringEncodingUTF8); + NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@://%@:%@@%@",[url scheme],escapedUser,escapedPassword,[url host],nil]; + if([url port]) { + [urlString appendFormat:@":%@",[url port],nil]; + } + [urlString appendString:[url path]]; + if([url query]){ + [urlString appendFormat:@"?%@",[url query],nil]; + } + [request setURL:[NSURL URLWithString:urlString]]; + [escapedUser release]; + [escapedPassword release]; + } + + + [self logRequest:request to:[url absoluteString]]; + + ConnectionDelegate *connectionDelegate = [[[ConnectionDelegate alloc] init] autorelease]; + + [[self activeDelegates] addObject:connectionDelegate]; + NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:connectionDelegate startImmediately:NO] autorelease]; + connectionDelegate.connection = connection; + + + //use a custom runloop + static NSString *runLoopMode = @"com.yfactorial.objectiveresource.connectionLoop"; + [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode]; + [connection start]; + while (![connectionDelegate isDone]) { + [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.3]]; + } + Response *resp = [Response responseFrom:(NSHTTPURLResponse *)connectionDelegate.response + withBody:connectionDelegate.data + andError:connectionDelegate.error]; + [resp log]; + + [activeDelegates removeObject:connectionDelegate]; + + //if there are no more active delegates release the array + if (0 == [activeDelegates count]) { + NSMutableArray *tempDelegates = activeDelegates; + activeDelegates = nil; + [tempDelegates release]; + } + + return resp; +} + ++ (Response *)post:(NSString *)body to:(NSString *)url { + return [self post:body to:url withUser:@"X" andPassword:@"X"]; +} + ++ (Response *)sendBy:(NSString *)method withBody:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:method]; + [request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (Response *)post:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + return [self sendBy:@"POST" withBody:body to:url withUser:user andPassword:password]; +} + ++ (Response *)put:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + return [self sendBy:@"PUT" withBody:body to:url withUser:user andPassword:password]; +} + ++ (Response *)get:(NSString *)url { + return [self get:url withUser:@"X" andPassword:@"X"]; +} + ++ (Response *)get:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:@"GET"]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (Response *)delete:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:@"DELETE"]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (void) cancelAllActiveConnections { + for (ConnectionDelegate *delegate in activeDelegates) { + [delegate performSelectorOnMainThread:@selector(cancel) withObject:nil waitUntilDone:NO]; + } +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.h b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.h new file mode 100644 index 0000000..6d6e789 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.h @@ -0,0 +1,30 @@ +// +// ConnectionDelegate.h +// iphone-harvest +// +// Created by vickeryj on 1/14/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import + + +@interface ConnectionDelegate : NSObject { + + NSMutableData *data; + NSURLResponse *response; + BOOL done; + NSError *error; + NSURLConnection *connection; + +} + +- (BOOL) isDone; +- (void) cancel; + +@property(nonatomic, retain) NSURLResponse *response; +@property(nonatomic, retain) NSMutableData *data; +@property(nonatomic, retain) NSError *error; +@property(nonatomic, retain) NSURLConnection *connection; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.m b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.m new file mode 100644 index 0000000..9233b8a --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionDelegate.m @@ -0,0 +1,89 @@ +// +// ConnectionDelegate.m +// iphone-harvest +// +// Created by vickeryj on 1/14/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ConnectionDelegate.h" + + +@implementation ConnectionDelegate + +@synthesize response, data, error, connection; + +- (id) init +{ + self = [super init]; + if (self != nil) { + self.data = [NSMutableData data]; + done = NO; + } + return self; +} + +- (BOOL) isDone { + return done; +} + +- (void) cancel { + [connection cancel]; + self.response = nil; + self.data = nil; + self.error = nil; + done = YES; +} + +#pragma mark NSURLConnectionDelegate methods +- (NSURLRequest *)connection:(NSURLConnection *)aConnection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { + return request; +} + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + if ([challenge previousFailureCount] > 0) { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } + else { + [[challenge sender] useCredential:[challenge proposedCredential] forAuthenticationChallenge:challenge]; + } +} +- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + done = YES; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse { + self.response = aResponse; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)someData { + [data appendData:someData]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { + done = YES; +} + +- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)aError { + self.error = aError; + done = YES; +} + +//don't cache resources for now +- (NSCachedURLResponse *)connection:(NSURLConnection *)aConnection willCacheResponse:(NSCachedURLResponse *)cachedResponse { + return nil; +} + + +#pragma mark cleanup +- (void) dealloc +{ + [data release]; + [response release]; + [error release]; + [connection release]; + [super dealloc]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.h b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.h new file mode 100644 index 0000000..a970cd3 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.h @@ -0,0 +1,19 @@ +// +// ConnectionManager.h +// +// Created by Adam Alexander on 2/28/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import + + +@interface ConnectionManager : NSObject { + NSOperationQueue *operationQueue; +} +@property (nonatomic, retain) NSOperationQueue *operationQueue; ++ (ConnectionManager *)sharedInstance; +- (void)cancelAllJobs; +- (void)runJob:(SEL)selector onTarget:(id)target; +- (void)runJob:(SEL)selector onTarget:(id)target withArgument:(id)argument; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.m b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.m new file mode 100644 index 0000000..e8035ce --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ConnectionManager.m @@ -0,0 +1,87 @@ +// +// ConnectionManager.m +// +// Created by Adam Alexander on 2/28/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import "ConnectionManager.h" + + +@implementation ConnectionManager +@synthesize operationQueue; + +- (void)cancelAllJobs { + [operationQueue cancelAllOperations]; +} + +- (void) runJob:(SEL)selector onTarget:(id)target { + [self runJob:selector onTarget:target withArgument:nil]; +} + +- (void) runJob:(SEL)selector onTarget:(id)target withArgument:(id)argument { + NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:target selector:selector object:argument]; + [operationQueue addOperation:operation]; + [operation release]; +} + +- (id)init { + if ( self = [super init] ) { + self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; + [operationQueue setMaxConcurrentOperationCount:1]; + return self; + } else { + return nil; + } +} + +#pragma mark Standard Singleton Plumbing + +static ConnectionManager *sharedConnectionManager = nil; + ++ (ConnectionManager *)sharedInstance +{ + @synchronized(self) { + if (sharedConnectionManager == nil) { + [[self alloc] init]; // assignment not done here + } + } + return sharedConnectionManager; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + if (sharedConnectionManager == nil) { + sharedConnectionManager = [super allocWithZone:zone]; + return sharedConnectionManager; // assignment and return on first allocation + } + } + return nil; //on subsequent allocation attempts return nil +} + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)retain +{ + return self; +} + +- (unsigned)retainCount +{ + return UINT_MAX; //denotes an object that cannot be released +} + +- (void)release +{ + //do nothing +} + +- (id)autorelease +{ + return self; +} +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h b/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h new file mode 100644 index 0000000..7b8b48b --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h @@ -0,0 +1,15 @@ +// +// NSHTTPURLResponse+Error.h +// iphone-harvest +// +// Created by James Burka on 12/23/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +@interface NSHTTPURLResponse(Error) + +-(NSError *) errorWithBody:(NSData *)data; +-(BOOL) isSuccess; ++ (NSError *)buildResponseError:(int)statusCode withBody:(NSData *)data; ++ (NSArray *)errorArrayForBody:(NSData *)data; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m b/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m new file mode 100644 index 0000000..39468cb --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m @@ -0,0 +1,52 @@ +// +// NSHTTPURLResponse+Error.m +// iphone-harvest +// +// Created by James Burka on 12/23/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import "NSHTTPURLResponse+Error.h" +#import "ObjectiveResourceConfig.h" + +@implementation NSHTTPURLResponse(Error) + ++ (NSError *)buildResponseError:(int)statusCode withBody:(NSData *)data{ + NSMutableDictionary *description = [NSMutableDictionary dictionary]; + [description setObject:[NSString stringWithFormat:@"%i Error",statusCode] forKey:NSLocalizedFailureReasonErrorKey]; + [description setObject:[[self class] localizedStringForStatusCode:statusCode] forKey:NSLocalizedDescriptionKey]; + NSArray *errorArray = [[self class] errorArrayForBody:data]; + if (nil != errorArray) { + [description setObject:errorArray forKey:NSLocalizedRecoveryOptionsErrorKey]; + } + return [NSError errorWithDomain:@"com.yfactorial.objectiveresource" code:statusCode userInfo:description]; +} + ++ (NSArray *)errorArrayForBody:(NSData *)data { + if (@selector(fromJSONData:) == [ObjectiveResourceConfig getParseDataMethod]) { + NSMutableArray *returnStrings = [NSMutableArray array]; + NSArray *errorArrays = [[self class] performSelector:[ObjectiveResourceConfig getParseDataMethod] withObject:data]; + for (NSArray *error in errorArrays) { + [returnStrings addObject:[error componentsJoinedByString:@" "]]; + } + return returnStrings; + } + else { + return nil; + } +} + +-(NSError *) errorWithBody:(NSData *)data { + if([self isSuccess]) { + return nil; + } + else { + return [[self class] buildResponseError:[self statusCode] withBody:data]; + } +} + +-(BOOL) isSuccess { + return [self statusCode] >= 200 && [self statusCode] < 400; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h b/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h new file mode 100644 index 0000000..60879c6 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h @@ -0,0 +1,13 @@ +// +// NSMutableURLRequest+ResponseType.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSMutableURLRequest(ResponseType) + ++(NSMutableURLRequest *) requestWithUrl:(NSURL *)url andMethod:(NSString*)method; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m b/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m new file mode 100644 index 0000000..89c5516 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m @@ -0,0 +1,32 @@ +// +// NSMutableURLRequest+ResponseType.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSMutableURLRequest+ResponseType.h" +#import "ObjectiveResource.h" +#import "Connection.h" + +@implementation NSMutableURLRequest(ResponseType) + ++(NSMutableURLRequest *) requestWithUrl:(NSURL *)url andMethod:(NSString*)method { + NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:[Connection timeout]]; + [request setHTTPMethod:method]; + switch ([ObjectiveResourceConfig getResponseType]) { + case JSONResponse: + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; + break; + default: + [request setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; + [request addValue:@"application/xml" forHTTPHeaderField:@"Accept"]; + break; + } + return request; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h b/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h new file mode 100644 index 0000000..dc68586 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h @@ -0,0 +1,84 @@ +// +// NSObject+ObjectiveResource.h +// objectivesync +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import + +@interface NSObject (ObjectiveResource) + + +// Response Formats +typedef enum { + XmlResponse = 0, + JSONResponse, +} ORSResponseFormat; + +// Resource configuration ++ (NSString *)getRemoteSite; ++ (void)setRemoteSite:(NSString*)siteURL; ++ (NSString *)getRemoteUser; ++ (void)setRemoteUser:(NSString *)user; ++ (NSString *)getRemotePassword; ++ (void)setRemotePassword:(NSString *)password; ++ (SEL)getRemoteParseDataMethod; ++ (void)setRemoteParseDataMethod:(SEL)parseMethod; ++ (SEL) getRemoteSerializeMethod; ++ (void) setRemoteSerializeMethod:(SEL)serializeMethod; ++ (NSString *)getRemoteProtocolExtension; ++ (void)setRemoteProtocolExtension:(NSString *)protocolExtension; ++ (void)setRemoteResponseType:(ORSResponseFormat) format; ++ (ORSResponseFormat)getRemoteResponseType; + +// Prefix additions ++ (NSString *)getLocalClassesPrefix; ++ (void)setLocalClassesPrefix:(NSString *)prefix; + +// Finders ++ (NSArray *)findAllRemote; ++ (NSArray *)findAllRemoteWithResponse:(NSError **)aError; ++ (id)findRemote:(NSString *)elementId; ++ (id)findRemote:(NSString *)elementId withResponse:(NSError **)aError; + +// URL construction accessors ++ (NSString *)getRemoteElementName; ++ (NSString *)getRemoteCollectionName; ++ (NSString *)getRemoteElementPath:(NSString *)elementId; ++ (NSString *)getRemoteCollectionPath; ++ (NSString *)getRemoteCollectionPathWithParameters:(NSDictionary *)parameters; ++ (NSString *)populateRemotePath:(NSString *)path withParameters:(NSDictionary *)parameters; + +// Instance-specific methods +- (id)getRemoteId; +- (void)setRemoteId:(id)orsId; +- (NSString *)getRemoteClassIdName; +- (BOOL)createRemote; +- (BOOL)createRemoteWithResponse:(NSError **)aError; +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters; +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters andResponse:(NSError **)aError; +- (BOOL)destroyRemote; +- (BOOL)destroyRemoteWithResponse:(NSError **)aError; +- (BOOL)updateRemote; +- (BOOL)updateRemoteWithResponse:(NSError **)aError; +- (BOOL)saveRemote; +- (BOOL)saveRemoteWithResponse:(NSError **)aError; + + +- (BOOL)createRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; +- (BOOL)updateRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; +- (BOOL)destroyRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; + +// Instance helpers for getting at commonly used class-level values +- (NSString *)getRemoteCollectionPath; +- (NSString *)convertToRemoteExpectedType; + +//Equality test for remote enabled objects based on class name and remote id +- (BOOL)isEqualToRemote:(id)anObject; +- (NSUInteger)hashForRemote; + +- (NSArray *)excludedPropertyNames; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m b/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m new file mode 100644 index 0000000..39af45e --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m @@ -0,0 +1,362 @@ +// +// NSObject+ObjectiveResource.m +// objectivesync +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "NSObject+ObjectiveResource.h" +#import "Connection.h" +#import "Response.h" +#import "CoreSupport.h" +#import "XMLSerializableSupport.h" +#import "JSONSerializableSupport.h" + +static NSString *_activeResourceSite = nil; +static NSString *_activeResourceUser = nil; +static NSString *_activeResourcePassword = nil; +static SEL _activeResourceParseDataMethod = nil; +static SEL _activeResourceSerializeMethod = nil; +static NSString *_activeResourceProtocolExtension = @".xml"; +static ORSResponseFormat _format; +static NSString *_activeResourcePrefix = nil; + + +@implementation NSObject (ObjectiveResource) + +#pragma mark configuration methods ++ (NSString *)getRemoteSite { + return _activeResourceSite; +} + ++ (void)setRemoteSite:(NSString *)siteURL { + if (_activeResourceSite != siteURL) { + [_activeResourceSite autorelease]; + _activeResourceSite = [siteURL copy]; + } +} + ++ (NSString *)getRemoteUser { + return _activeResourceUser; +} + ++ (void)setRemoteUser:(NSString *)user { + if (_activeResourceUser != user) { + [_activeResourceUser autorelease]; + _activeResourceUser = [user copy]; + } +} + ++ (NSString *)getRemotePassword { + return _activeResourcePassword; +} + ++ (void)setRemotePassword:(NSString *)password { + if (_activeResourcePassword != password) { + [_activeResourcePassword autorelease]; + _activeResourcePassword = [password copy]; + } +} + ++ (void)setRemoteResponseType:(ORSResponseFormat) format { + _format = format; + switch (format) { + case JSONResponse: + [[self class] setRemoteProtocolExtension:@".json"]; + [[self class] setRemoteParseDataMethod:@selector(fromJSONData:)]; + [[self class] setRemoteSerializeMethod:@selector(toJSONExcluding:)]; + break; + default: + [[self class] setRemoteProtocolExtension:@".xml"]; + [[self class] setRemoteParseDataMethod:@selector(fromXMLData:)]; + [[self class] setRemoteSerializeMethod:@selector(toXMLElementExcluding:)]; + break; + } +} + ++ (ORSResponseFormat)getRemoteResponseType { + return _format; +} + ++ (SEL)getRemoteParseDataMethod { + return (nil == _activeResourceParseDataMethod) ? @selector(fromXMLData:) : _activeResourceParseDataMethod; +} + ++ (void)setRemoteParseDataMethod:(SEL)parseMethod { + _activeResourceParseDataMethod = parseMethod; +} + ++ (SEL) getRemoteSerializeMethod { + return (nil == _activeResourceSerializeMethod) ? @selector(toXMLElementExcluding:) : _activeResourceSerializeMethod; +} + ++ (void) setRemoteSerializeMethod:(SEL)serializeMethod { + _activeResourceSerializeMethod = serializeMethod; +} + ++ (NSString *)getRemoteProtocolExtension { + return _activeResourceProtocolExtension; +} + ++ (void)setRemoteProtocolExtension:(NSString *)protocolExtension { + if (_activeResourceProtocolExtension != protocolExtension) { + [_activeResourceProtocolExtension autorelease]; + _activeResourceProtocolExtension = [protocolExtension copy]; + } +} + +// Prefix additions ++ (NSString *)getLocalClassesPrefix { + return _activeResourcePrefix; +} + ++ (void)setLocalClassesPrefix:(NSString *)prefix { + if (prefix != _activeResourcePrefix) { + [_activeResourcePrefix autorelease]; + _activeResourcePrefix = [prefix copy]; + } +} + +// Find all items ++ (NSArray *)findAllRemoteWithResponse:(NSError **)aError { + Response *res = [Connection get:[self getRemoteCollectionPath] withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + return nil; + } + else { + return [self performSelector:[self getRemoteParseDataMethod] withObject:res.body]; + } +} + ++ (NSArray *)findAllRemote { + NSError *aError; + return [self findAllRemoteWithResponse:&aError]; +} + ++ (id)findRemote:(NSString *)elementId withResponse:(NSError **)aError { + Response *res = [Connection get:[self getRemoteElementPath:elementId] withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + return [self performSelector:[self getRemoteParseDataMethod] withObject:res.body]; +} + ++ (id)findRemote:(NSString *)elementId { + NSError *aError; + return [self findRemote:elementId withResponse:&aError]; +} + ++ (NSString *)getRemoteElementName { + NSString * remoteElementName = NSStringFromClass([self class]); + if (_activeResourcePrefix != nil) { + remoteElementName = [remoteElementName substringFromIndex:[_activeResourcePrefix length]]; + } + return [[remoteElementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) + withString:[[remoteElementName substringWithRange:NSMakeRange(0, 1)] + lowercaseString]] + underscore]; +} + ++ (NSString *)getRemoteCollectionName { + return [[self getRemoteElementName] stringByAppendingString:@"s"]; +} + ++ (NSString *)getRemoteElementPath:(NSString *)elementId { + return [NSString stringWithFormat:@"%@%@/%@%@", [self getRemoteSite], [self getRemoteCollectionName], elementId, [self getRemoteProtocolExtension]]; +} + ++ (NSString *)getRemoteCollectionPath { + return [[[self getRemoteSite] stringByAppendingString:[self getRemoteCollectionName]] stringByAppendingString:[self getRemoteProtocolExtension]]; +} + ++ (NSString *)getRemoteCollectionPathWithParameters:(NSDictionary *)parameters { + return [self populateRemotePath:[self getRemoteCollectionPath] withParameters:parameters]; +} + ++ (NSString *)populateRemotePath:(NSString *)path withParameters:(NSDictionary *)parameters { + + // Translate each key to have a preceeding ":" for proper URL param notiation + NSMutableDictionary *parameterized = [NSMutableDictionary dictionaryWithCapacity:[parameters count]]; + for (NSString *key in parameters) { + [parameterized setObject:[parameters objectForKey:key] forKey:[NSString stringWithFormat:@":%@", key]]; + } + return [path gsub:parameterized]; +} + +- (NSString *)getRemoteCollectionPath { + return [[self class] getRemoteCollectionPath]; +} + +// Converts the object to the data format expected by the server +- (NSString *)convertToRemoteExpectedType { + return [self performSelector:[[self class] getRemoteSerializeMethod] withObject:[self excludedPropertyNames]]; +} + + +#pragma mark default equals methods for id and class based equality +- (BOOL)isEqualToRemote:(id)anObject { + return [NSStringFromClass([self class]) isEqualToString:NSStringFromClass([anObject class])] && + [anObject respondsToSelector:@selector(getRemoteId)] && [[anObject getRemoteId]isEqualToString:[self getRemoteId]]; +} +- (NSUInteger)hashForRemote { + return [[self getRemoteId] intValue] + [NSStringFromClass([self class]) hash]; +} + +#pragma mark Instance-specific methods +- (id)getRemoteId { + id result = nil; + SEL idMethodSelector = NSSelectorFromString([self getRemoteClassIdName]); + if ([self respondsToSelector:idMethodSelector]) { + result = [self performSelector:idMethodSelector]; + if ([result respondsToSelector:@selector(stringValue)]) { + result = [result stringValue]; + } + } + return result; +} +- (void)setRemoteId:(id)orsId { + SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@Id:",[self className]]); + if ([self respondsToSelector:setter]) { + [self performSelector:setter withObject:orsId]; + } +} + + +- (NSString *)getRemoteClassIdName { + NSString * remoteElementName = NSStringFromClass([self class]); + if (_activeResourcePrefix != nil) { + remoteElementName = [remoteElementName substringFromIndex:[_activeResourcePrefix length]]; + } + return [NSString stringWithFormat:@"%@Id", + [remoteElementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) + withString:[[remoteElementName substringWithRange:NSMakeRange(0,1)] lowercaseString]]]; +} + +- (BOOL)createRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection post:[self convertToRemoteExpectedType] to:path withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + if ([res isSuccess]) { + NSDictionary *newProperties = [[[self class] performSelector:[[self class] getRemoteParseDataMethod] withObject:res.body] properties]; + [self setProperties:newProperties]; + return YES; + } + else { + return NO; + } +} + +-(BOOL)updateRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection put:[self convertToRemoteExpectedType] to:path + withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + if ([res isSuccess]) { + if([(NSString *)[res.headers objectForKey:@"Content-Length"] intValue] > 1) { + NSDictionary *newProperties = [[[self class] performSelector:[[self class] getRemoteParseDataMethod] withObject:res.body] properties]; + [self setProperties:newProperties]; + } + return YES; + } + else { + return NO; + } + +} + +- (BOOL)destroyRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection delete:path withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + return [res isSuccess]; +} + +- (BOOL)createRemoteWithResponse:(NSError **)aError { + return [self createRemoteAtPath:[self getRemoteCollectionPath] withResponse:aError]; +} + +- (BOOL)createRemote { + NSError *error; + return [self createRemoteWithResponse:&error]; +} + +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters andResponse:(NSError **)aError { + return [self createRemoteAtPath:[[self class] getRemoteCollectionPathWithParameters:parameters] withResponse:aError]; +} + +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters { + NSError *error; + return [self createRemoteWithParameters:parameters andResponse:&error]; +} + + +- (BOOL)destroyRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil != myId) { + return [self destroyRemoteAtPath:[[self class] getRemoteElementPath:myId] withResponse:aError]; + } + else { + // this should return a error + return NO; + } +} + +- (BOOL)destroyRemote { + NSError *error; + return [self destroyRemoteWithResponse:&error]; +} + +- (BOOL)updateRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil != myId) { + return [self updateRemoteAtPath:[[self class] getRemoteElementPath:myId] withResponse:aError]; + } + else { + // this should return an error + return NO; + } +} + +- (BOOL)updateRemote { + NSError *error; + return [self updateRemoteWithResponse:&error]; +} + +- (BOOL)saveRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil == myId) { + return [self createRemoteWithResponse:aError]; + } + else { + return [self updateRemoteWithResponse:aError]; + } +} + +- (BOOL)saveRemote { + NSError *error; + return [self saveRemoteWithResponse:&error]; +} + +/* + Override this in your model class to extend or replace the excluded properties + eg. + - (NSArray *)excludedPropertyNames + { + NSArray *exclusions = [NSArray arrayWithObjects:@"extraPropertyToExclude", nil]; + return [[super excludedPropertyNames] arrayByAddingObjectsFromArray:exclusions]; + } +*/ + +- (NSArray *)excludedPropertyNames +{ + // exclude id , created_at , updated_at + return [NSArray arrayWithObjects:[self getRemoteClassIdName],@"createdAt",@"updatedAt",nil]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResource.h b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResource.h new file mode 100644 index 0000000..b0bb8bb --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResource.h @@ -0,0 +1,11 @@ +// +// ObjectiveResource.h +// +// +// Created by Ryan Daigle on 7/24/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+ObjectiveResource.h" +#import "ObjectiveResourceConfig.h" +#import "NSError+Error.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.h b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.h new file mode 100644 index 0000000..1a2cca1 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.h @@ -0,0 +1,30 @@ +// +// ObjectiveResourceConfig.h +// objective_resource +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ObjectiveResource.h" + +@interface ObjectiveResourceConfig : NSObject + ++ (NSString *)getSite; ++ (void)setSite:(NSString*)siteURL; ++ (NSString *)getUser; ++ (void)setUser:(NSString *)user; ++ (NSString *)getPassword; ++ (void)setPassword:(NSString *)password; ++ (SEL)getParseDataMethod; ++ (void)setParseDataMethod:(SEL)parseMethod; ++ (SEL) getSerializeMethod; ++ (void) setSerializeMethod:(SEL)serializeMethod; ++ (NSString *)protocolExtension; ++ (void)setProtocolExtension:(NSString *)protocolExtension; ++ (void)setResponseType:(ORSResponseFormat) format; ++ (ORSResponseFormat)getResponseType; ++ (NSString *)getLocalClassesPrefix; ++ (void)setLocalClassesPrefix:(NSString *)prefix; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.m b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.m new file mode 100644 index 0000000..bbf2dd9 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/ObjectiveResourceConfig.m @@ -0,0 +1,56 @@ +// +// ObjectiveResourceConfig.m +// objective_resource +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ObjectiveResourceConfig.h" + +@implementation ObjectiveResourceConfig + ++ (NSString *)getSite { + return [self getRemoteSite]; +} ++ (void)setSite:(NSString*)siteURL { + [self setRemoteSite:siteURL]; +} ++ (NSString *)getUser { + return [self getRemoteUser]; +} ++ (void)setUser:(NSString *)user { + [self setRemoteUser:user]; +} ++ (NSString *)getPassword { + return [self getRemotePassword]; +} ++ (void)setPassword:(NSString *)password { + [self setRemotePassword:password]; +} ++ (SEL)getParseDataMethod { + return [self getRemoteParseDataMethod]; +} ++ (void)setParseDataMethod:(SEL)parseMethod { + [self setRemoteParseDataMethod:parseMethod]; +} ++ (SEL) getSerializeMethod { + return [self getRemoteSerializeMethod]; +} ++ (void) setSerializeMethod:(SEL)serializeMethod { + [self setRemoteSerializeMethod:serializeMethod]; +} ++ (NSString *)protocolExtension { + return [self getRemoteProtocolExtension]; +} ++ (void)setProtocolExtension:(NSString *)protocolExtension { + [self setRemoteProtocolExtension:protocolExtension]; +} ++ (void)setResponseType:(ORSResponseFormat) format { + [self setRemoteResponseType:format]; +} ++ (ORSResponseFormat)getResponseType { + return [self getRemoteResponseType]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/Response.h b/code/6-objectiveresource/objectiveresource/Classes/lib/Response.h new file mode 100644 index 0000000..9d196a0 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/Response.h @@ -0,0 +1,34 @@ +// +// Response.h +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// +#ifdef __OBJC__ +//setup debug only logging +#ifdef DEBUG +#define debugLog(...) NSLog(__VA_ARGS__) +#else +#define debugLog(...) +#endif +#endif + +@interface Response : NSObject { + NSData *body; + NSDictionary *headers; + NSInteger statusCode; + NSError *error; +} + +@property (nonatomic, retain) NSData *body; +@property (nonatomic, retain) NSDictionary *headers; +@property (assign, nonatomic) NSInteger statusCode; +@property (nonatomic, retain) NSError *error; + ++ (id)responseFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError; +- (id)initFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError; +- (BOOL)isSuccess; +- (BOOL)isError; +- (void)log; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/Response.m b/code/6-objectiveresource/objectiveresource/Classes/lib/Response.m new file mode 100644 index 0000000..feaa2bf --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/Response.m @@ -0,0 +1,73 @@ +// +// Response.m +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "Response.h" +#import "NSHTTPURLResponse+Error.h" + +@implementation Response + +@synthesize body, headers, statusCode, error; + ++ (id)responseFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError { + return [[[self alloc] initFrom:response withBody:data andError:aError] autorelease]; +} + +- (void)normalizeError:(NSError *)aError withBody:(NSData *)data{ + switch ([aError code]) { + case NSURLErrorUserCancelledAuthentication: + self.statusCode = 401; + self.error = [NSHTTPURLResponse buildResponseError:401 withBody:data]; + break; + default: + self.error = aError; + break; + } +} + +- (id)initFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError { + [self init]; + self.body = data; + if(response) { + self.statusCode = [response statusCode]; + self.headers = [response allHeaderFields]; + self.error = [response errorWithBody:data]; + } + else { + [self normalizeError:aError withBody:data]; + } + return self; +} + +- (BOOL)isSuccess { + return statusCode >= 200 && statusCode < 400; +} + +- (BOOL)isError { + return ![self isSuccess]; +} + +- (void)log { + if ([self isSuccess]) { + debugLog(@"<= %@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); + } + else { + NSLog(@"<= %@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); + } +} + +#pragma mark cleanup + +- (void) dealloc +{ + [body release]; + [headers release]; + [super dealloc]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h new file mode 100644 index 0000000..243a6bc --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h @@ -0,0 +1,11 @@ +// +// CoreSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+GSub.h" +#import "NSString+InflectionSupport.h" +#import "NSObject+PropertySupport.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h new file mode 100644 index 0000000..d2b9bb3 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h @@ -0,0 +1,42 @@ +// +// NSString+InflectionSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSString (InflectionSupport) + +/** + * Return the dashed form af this camelCase string: + * + * [@"camelCase" dasherize] //> @"camel-case" + */ +- (NSString *)dasherize; + +/** + * Return the underscored form af this camelCase string: + * + * [@"camelCase" underscore] //> @"camel_case" + */ +- (NSString *)underscore; + +/** + * Return the camelCase form af this dashed/underscored string: + * + * [@"camel-case_string" camelize] //> @"camelCaseString" + */ +- (NSString *)camelize; + +/** + * Return a copy of the string suitable for displaying in a title. Each word is downcased, with the first letter upcased. + */ +- (NSString *)titleize; + +/** + * Return a copy of the string with the first letter capitalized. + */ +- (NSString *)toClassName; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m new file mode 100644 index 0000000..620c5c3 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m @@ -0,0 +1,94 @@ +// +// NSString+InflectionSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+InflectionSupport.h" + +@implementation NSString (InflectionSupport) + +- (NSCharacterSet *)capitals { + return [NSCharacterSet uppercaseLetterCharacterSet]; +} + +- (NSString *)deCamelizeWith:(NSString *)delimiter { + + unichar *buffer = calloc([self length], sizeof(unichar)); + [self getCharacters:buffer ]; + NSMutableString *underscored = [NSMutableString string]; + + NSString *currChar; + for (int i = 0; i < [self length]; i++) { + currChar = [NSString stringWithCharacters:buffer+i length:1]; + if([[self capitals] characterIsMember:buffer[i]]) { + [underscored appendFormat:@"%@%@", delimiter, [currChar lowercaseString]]; + } else { + [underscored appendString:currChar]; + } + } + + free(buffer); + return underscored; +} + + +- (NSString *)dasherize { + return [self deCamelizeWith:@"-"]; +} + +- (NSString *)underscore { + return [self deCamelizeWith:@"_"]; +} + +- (NSCharacterSet *)camelcaseDelimiters { + return [NSCharacterSet characterSetWithCharactersInString:@"-_"]; +} + +- (NSString *)camelize { + + unichar *buffer = calloc([self length], sizeof(unichar)); + [self getCharacters:buffer ]; + NSMutableString *underscored = [NSMutableString string]; + + BOOL capitalizeNext = NO; + NSCharacterSet *delimiters = [self camelcaseDelimiters]; + for (int i = 0; i < [self length]; i++) { + NSString *currChar = [NSString stringWithCharacters:buffer+i length:1]; + if([delimiters characterIsMember:buffer[i]]) { + capitalizeNext = YES; + } else { + if(capitalizeNext) { + [underscored appendString:[currChar uppercaseString]]; + capitalizeNext = NO; + } else { + [underscored appendString:currChar]; + } + } + } + + free(buffer); + return underscored; + +} + +- (NSString *)titleize { + NSArray *words = [self componentsSeparatedByString:@" "]; + NSMutableString *output = [NSMutableString string]; + for (NSString *word in words) { + [output appendString:[[word substringToIndex:1] uppercaseString]]; + [output appendString:[[word substringFromIndex:1] lowercaseString]]; + [output appendString:@" "]; + } + return [output substringToIndex:[self length]]; +} + +- (NSString *)toClassName { + NSString *result = [self camelize]; + return [result stringByReplacingCharactersInRange:NSMakeRange(0,1) + withString:[[result substringWithRange:NSMakeRange(0,1)] uppercaseString]]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h new file mode 100644 index 0000000..c02e3e0 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h @@ -0,0 +1,12 @@ +//NSData additions from colloquy project + +@interface NSData (NSDataAdditions) ++ (NSData *) dataWithBase64EncodedString:(NSString *) string; +- (id) initWithBase64EncodedString:(NSString *) string; + +- (NSString *) base64Encoding; +- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength; + +- (BOOL) hasPrefix:(NSData *) prefix; +- (BOOL) hasPrefixBytes:(void *) prefix length:(unsigned int) length; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m new file mode 100644 index 0000000..7b281ae --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m @@ -0,0 +1,161 @@ +//NSData additions from colloquy project + +// Created by khammond on Mon Oct 29 2001. +// Formatted by Timothy Hatcher on Sun Jul 4 2004. +// Copyright (c) 2001 Kyle Hammond. All rights reserved. +// Original development by Dave Winer. + +#import "NSData+Additions.h" + +static char encodingTable[64] = { +'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', +'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', +'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', +'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; + +@implementation NSData (NSDataAdditions) ++ (NSData *) dataWithBase64EncodedString:(NSString *) string { + return [[[NSData allocWithZone:nil] initWithBase64EncodedString:string] autorelease]; +} + +- (id) initWithBase64EncodedString:(NSString *) string { + NSMutableData *mutableData = nil; + + if( string ) { + unsigned long ixtext = 0; + unsigned long lentext = 0; + unsigned char ch = 0; + unsigned char inbuf[4], outbuf[3]; + short i = 0, ixinbuf = 0; + BOOL flignore = NO; + BOOL flendtext = NO; + NSData *base64Data = nil; + const unsigned char *base64Bytes = nil; + + // Convert the string to ASCII data. + base64Data = [string dataUsingEncoding:NSASCIIStringEncoding]; + base64Bytes = [base64Data bytes]; + mutableData = [NSMutableData dataWithCapacity:[base64Data length]]; + lentext = [base64Data length]; + + while( YES ) { + if( ixtext >= lentext ) break; + ch = base64Bytes[ixtext++]; + flignore = NO; + + if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; + else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; + else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; + else if( ch == '+' ) ch = 62; + else if( ch == '=' ) flendtext = YES; + else if( ch == '/' ) ch = 63; + else flignore = YES; + + if( ! flignore ) { + short ctcharsinbuf = 3; + BOOL flbreak = NO; + + if( flendtext ) { + if( ! ixinbuf ) break; + if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; + else ctcharsinbuf = 2; + ixinbuf = 3; + flbreak = YES; + } + + inbuf [ixinbuf++] = ch; + + if( ixinbuf == 4 ) { + ixinbuf = 0; + outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); + outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); + outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); + + for( i = 0; i < ctcharsinbuf; i++ ) + [mutableData appendBytes:&outbuf[i] length:1]; + } + + if( flbreak ) break; + } + } + } + + self = [self initWithData:mutableData]; + return self; +} + +#pragma mark - + +- (NSString *) base64Encoding { + return [self base64EncodingWithLineLength:0]; +} + +- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength { + const unsigned char *bytes = [self bytes]; + NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; + unsigned long ixtext = 0; + unsigned long lentext = [self length]; + long ctremaining = 0; + unsigned char inbuf[3], outbuf[4]; + unsigned short i = 0; + unsigned short charsonline = 0, ctcopy = 0; + unsigned long ix = 0; + + while( YES ) { + ctremaining = lentext - ixtext; + if( ctremaining <= 0 ) break; + + for( i = 0; i < 3; i++ ) { + ix = ixtext + i; + if( ix < lentext ) inbuf[i] = bytes[ix]; + else inbuf [i] = 0; + } + + outbuf [0] = (inbuf [0] & 0xFC) >> 2; + outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); + outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); + outbuf [3] = inbuf [2] & 0x3F; + ctcopy = 4; + + switch( ctremaining ) { + case 1: + ctcopy = 2; + break; + case 2: + ctcopy = 3; + break; + } + + for( i = 0; i < ctcopy; i++ ) + [result appendFormat:@"%c", encodingTable[outbuf[i]]]; + + for( i = ctcopy; i < 4; i++ ) + [result appendString:@"="]; + + ixtext += 3; + charsonline += 4; + + if( lineLength > 0 ) { + if( charsonline >= lineLength ) { + charsonline = 0; + [result appendString:@"\n"]; + } + } + } + + return [NSString stringWithString:result]; +} + +#pragma mark - + +- (BOOL) hasPrefix:(NSData *) prefix { + unsigned int length = [prefix length]; + if( ! prefix || ! length || [self length] < length ) return NO; + return ( memcmp( [self bytes], [prefix bytes], length ) == 0 ); +} + +- (BOOL) hasPrefixBytes:(void *) prefix length:(unsigned int) length { + if( ! prefix || ! length || [self length] < length ) return NO; + return ( memcmp( [self bytes], prefix, length ) == 0 ); +} +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h new file mode 100644 index 0000000..356c003 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h @@ -0,0 +1,35 @@ +// +// NSObject+Properties.h +// +// +// Created by Ryan Daigle on 7/28/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSObject (PropertySupport) + +/** + * Get the names of all properties and thier types declared on this class. + * + */ ++ (NSDictionary *)propertyNamesAndTypes; + +/** + * Get the names of all properties declared on this class. + */ ++ (NSArray *)propertyNames; + +/** + * Get all the properties and their values of this instance. + **/ +- (NSDictionary *)properties; + +/** + * Set this object's property values, overriding any existing + * values. + */ +- (void)setProperties:(NSDictionary *)overrideProperties; + +- (NSString *)className; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m new file mode 100644 index 0000000..82f88b5 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m @@ -0,0 +1,82 @@ +// +// NSObject+Properties.m +// +// +// Created by Ryan Daigle on 7/28/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "objc/runtime.h" +#import "NSObject+PropertySupport.h" + +@interface NSObject() + ++ (NSString *) getPropertyType:(NSString *)attributeString; + +@end + + +@implementation NSObject (PropertySupport) ++ (NSArray *)propertyNames { + return [[self propertyNamesAndTypes] allKeys]; +} + ++ (NSDictionary *)propertyNamesAndTypes { + NSMutableDictionary *propertyNames = [NSMutableDictionary dictionary]; + + //include superclass properties + Class currentClass = [self class]; + while (currentClass != nil) { + // Get the raw list of properties + unsigned int outCount; + objc_property_t *propList = class_copyPropertyList(currentClass, &outCount); + + // Collect the property names + int i; + NSString *propName; + for (i = 0; i < outCount; i++) + { + objc_property_t * prop = propList + i; + NSString *type = [NSString stringWithCString:property_getAttributes(*prop) encoding:NSUTF8StringEncoding]; + propName = [NSString stringWithCString:property_getName(*prop) encoding:NSUTF8StringEncoding]; + if (![propName isEqualToString:@"_mapkit_hasPanoramaID"]) { + [propertyNames setObject:[self getPropertyType:type] forKey:propName]; + } + } + + free(propList); + currentClass = [currentClass superclass]; + } + return propertyNames; +} + +- (NSDictionary *)properties { + return [self dictionaryWithValuesForKeys:[[self class] propertyNames]]; +} + +- (void)setProperties:(NSDictionary *)overrideProperties { + for (NSString *property in [overrideProperties allKeys]) { + [self setValue:[overrideProperties objectForKey:property] forKey:property]; + } +} + ++ (NSString *) getPropertyType:(NSString *)attributeString { + NSString *type = [NSString string]; + NSScanner *typeScanner = [NSScanner scannerWithString:attributeString]; + [typeScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"@"] intoString:NULL]; + + // we are not dealing with an object + if([typeScanner isAtEnd]) { + return @"NULL"; + } + [typeScanner scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"@"] intoString:NULL]; + // this gets the actual object type + [typeScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\""] intoString:&type]; + return type; +} + +- (NSString *)className { + return NSStringFromClass([self class]); +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h new file mode 100644 index 0000000..025f18f --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h @@ -0,0 +1,19 @@ +// +// NSString+Substitute.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSString (GSub) + +/** + * Perform basic substitution of given key -> value pairs + * within this string. + * + * [@"test string substitution" gsub:[NSDictionary withObjectsAndKeys:@"substitution", @"sub"]]; + * //> @"test string sub" + */ +- (NSString *)gsub:(NSDictionary *)keyValues; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m new file mode 100644 index 0000000..ac5ee99 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m @@ -0,0 +1,25 @@ +// +// NSString+Substitute.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+GSub.h" + +@implementation NSString (GSub) + +- (NSString *)gsub:(NSDictionary *)keyValues { + + NSMutableString *subbed = [NSMutableString stringWithString:self]; + + for (NSString *key in keyValues) { + NSString *value = [NSString stringWithFormat:@"%@", [keyValues objectForKey:key]]; + NSArray *splits = [subbed componentsSeparatedByString:key]; + [subbed setString:[splits componentsJoinedByString:value]]; + } + return subbed; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h new file mode 100644 index 0000000..f864971 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h @@ -0,0 +1,27 @@ +// +// ObjectiveResourceDateFormatter.h +// iphone-harvest +// +// Created by James Burka on 10/21/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import + + +@interface ObjectiveResourceDateFormatter : NSObject + +typedef enum { + Date = 0, + DateTime, +} ORSDateFormat; + ++ (void)setSerializeFormat:(ORSDateFormat)dateFormat; ++ (void)setDateFormatString:(NSString *)format; ++ (void)setDateTimeFormatString:(NSString *)format; ++ (void)setDateTimeZoneFormatString:(NSString *)format; ++ (NSString *)formatDate:(NSDate *)date; ++ (NSDate *)parseDate:(NSString *)dateString; ++ (NSDate *)parseDateTime:(NSString *)dateTimeString; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m new file mode 100644 index 0000000..51771b2 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m @@ -0,0 +1,72 @@ +// +// ObjectiveResourceDateFormatter.m +// iphone-harvest +// +// Created by James Burka on 10/21/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import "ObjectiveResourceDateFormatter.h" + + +@implementation ObjectiveResourceDateFormatter + +static NSString *dateTimeFormatString = @"yyyy-MM-dd'T'HH:mm:ss'Z'"; +static NSString *dateTimeZoneFormatString = @"yyyy-MM-dd'T'HH:mm:ssz"; +static NSString *dateFormatString = @"yyyy-MM-dd"; +static ORSDateFormat _dateFormat; + ++ (void)setSerializeFormat:(ORSDateFormat)dateFormat { + _dateFormat = dateFormat; +} + ++ (void)setDateFormatString:(NSString *)format { + dateFormatString = format; +} + ++ (void)setDateTimeFormatString:(NSString *)format { + dateTimeFormatString = format; +} + ++ (void)setDateTimeZoneFormatString:(NSString *)format { + dateTimeZoneFormatString = format; +} + ++ (NSString *)formatDate:(NSDate *)date { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + if(_dateFormat == Date) { + [formatter setDateFormat:dateFormatString]; + } + else { + [formatter setDateFormat:dateTimeFormatString]; + } + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter stringFromDate:date]; + +} + ++ (NSDate *)parseDateTime:(NSString *)dateTimeString { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + NSString *format = ([dateTimeString hasSuffix:@"Z"]) ? dateTimeFormatString : dateTimeZoneFormatString; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:format]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter dateFromString:dateTimeString]; + +} + ++ (NSDate *)parseDate:(NSString *)dateString { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:dateFormatString]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter dateFromString:dateString]; + +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h new file mode 100644 index 0000000..33b8cfe --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h @@ -0,0 +1,11 @@ +// +// ObjectiveSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "CoreSupport.h" +#import "XMLSerializableSupport.h" +#import "JSONSerializableSupport.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h new file mode 100644 index 0000000..78e00a6 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h @@ -0,0 +1,78 @@ +// +// JSONSerializable.h +// +// Created by James Burka on 1/13/09. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@protocol JSONSerializable + +/** + * Instantiate a single instance of this class from the given JSON data. + */ ++ (id)fromJSONData:(NSData *)data; + +/** + * Instantiate a collection of instances of this class from the given JSON data. + */ +//+ (NSArray *)allFromJSONData:(NSData *)data; + +/** + * Get the full JSON representation of this object + * using the default element name: + * + * [myPerson toXMLElement] //> @"Ryan..." + */ +- (NSString *)toJSON; + + +/** + * Gets the full representation of this object minus the elements in the exclusions array + * + * + * + */ +- (NSString *)toJSONExcluding:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name: + * + * [myPerson toXMLElementAs:@"human"] //> @"Ryan..." + */ +- (NSString *)toJSONAs:(NSString *)rootName; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and excluding the given properties. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil]] + * + * //> @"Daigle + */ +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and translating property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"RyanDaigle + */ +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name, excluding the given properties, and translating + * property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil] + * withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"Daigle + */ +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h new file mode 100644 index 0000000..89139f7 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h @@ -0,0 +1,2 @@ +#import "NSObject+JSONSerializableSupport.h" +#import "NSDictionary+JSONSerializableSupport.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h new file mode 100644 index 0000000..69746de --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h @@ -0,0 +1,17 @@ +// +// NSArray+JSONSerializableSupport.h +// objective_support +// +// Created by James Burka on 2/16/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSArray(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m new file mode 100644 index 0000000..1050522 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSArray+JSONSerializableSupport.m +// objective_support +// +// Created by James Burka on 2/16/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "JSONFramework.h" +#import "NSObject+JSONSerializableSupport.h" +#import "NSArray+JSONSerializableSupport.h" + + +@implementation NSArray(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + NSMutableString *values = [NSMutableString stringWithString:@"["]; + BOOL comma = NO; + for (id element in self) { + if(comma) { + [values appendString:@","]; + } + else { + comma = YES; + } + [values appendString:[element toJSONAs:[element jsonClassName] excludingInArray:exclusions withTranslations:keyTranslations]]; + + } + [values appendString:@"]"]; + return values; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h new file mode 100644 index 0000000..4e962d4 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h @@ -0,0 +1,14 @@ +// +// NSDictionary+JSONSerialization.h +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSDictionary(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m new file mode 100644 index 0000000..3b0e7d7 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m @@ -0,0 +1,30 @@ +// +// NSDictionary+JSONSerialization.m +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "JSONFramework.h" +#import "NSDictionary+KeyTranslation.h" +#import "NSDictionary+JSONSerializableSupport.h" +#import "ObjectiveSupport.h" +#import "Serialize.h" + +@implementation NSDictionary(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + NSMutableDictionary *props = [NSMutableDictionary dictionary]; + for (NSString *key in self) { + if(![exclusions containsObject:key]) { + NSString *convertedKey = [[self class] translationForKey:key withTranslations:keyTranslations]; + [props setObject:[[self objectForKey:key] serialize] forKey:[convertedKey underscore]]; + } + } + return [[NSDictionary dictionaryWithObject:props forKey:rootName]JSONRepresentation]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h new file mode 100644 index 0000000..d395166 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h @@ -0,0 +1,25 @@ +// +// NSObject+JSONSerializableSupport.h +// active_resource +// +// Created by vickeryj on 1/8/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import +#import "JSONSerializable.h" + +@interface NSObject (JSONSerializableSupport) + ++ (id)fromJSONData:(NSData *)data; ++ (id) deserializeJSON:(id)jsonObject asClass:(Class) claz; +- (NSString *)toJSON; +- (NSString *)toJSONExcluding:(NSArray *)exclusions; +- (NSString *)toJSONAs:(NSString *)rootName; +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; +- (NSString *) jsonClassName; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m new file mode 100644 index 0000000..ca8dfb8 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m @@ -0,0 +1,149 @@ +// +// NSObject+JSONSerializableSupport.m +// active_resource +// +// Created by vickeryj on 1/8/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "NSObject+JSONSerializableSupport.h" +#import "NSDictionary+JSONSerializableSupport.h" +#import "Serialize.h" +#import "JSONFramework.h" +#import "ObjectiveSupport.h" + +@interface NSObject (JSONSerializableSupport_Private) ++ (id) deserializeJSON:(id)jsonObject; +- (NSString *) convertProperty:(NSString *)propertyName andClassName:(NSString *)className; +@end + +@implementation NSObject (JSONSerializableSupport) + ++ (id)fromJSONData:(NSData *)data { + NSString *jsonString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + id jsonObject = [jsonString JSONValue]; + return [self deserializeJSON:jsonObject]; + +} + +- (NSString *)toJSON { + return [self toJSONAs:[self jsonClassName] excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONExcluding:(NSArray *)exclusions { + return [self toJSONAs:[self jsonClassName] excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName { + return [self toJSONAs:rootName excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions { + return [self toJSONAs:rootName excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations { + return [self toJSONAs:rootName excludingInArray:[NSArray array] withTranslations:keyTranslations]; +} + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + return [[self properties] toJSONAs:rootName excludingInArray:exclusions withTranslations:keyTranslations]; + +} + ++ (id) propertyClass:(NSString *)className { + return NSClassFromString([className toClassName]); +} + ++ (id) deserializeJSON:(id)jsonObject asClass:(Class) claz { + id result = nil; + + if ([jsonObject isKindOfClass:[NSArray class]]) { + //JSON array + result = [NSMutableArray array]; + for (id childObject in jsonObject) { + [result addObject:[self deserializeJSON:childObject asClass:claz]]; + } + } + else if ([jsonObject isKindOfClass:[NSDictionary class]]) { + //this assumes we are dealing with JSON dictionaries without class names + // { property1 : value, property2 : {property 3 : value }} + result = [[[claz alloc] init] autorelease]; + + NSDictionary *objectPropertyNames = [claz propertyNamesAndTypes]; + + for (NSString *property in [jsonObject allKeys]) { + NSString *propertyCamalized = [property camelize]; + if ([[objectPropertyNames allKeys] containsObject:propertyCamalized]) { + Class propertyClass = [self propertyClass:[objectPropertyNames objectForKey:propertyCamalized]]; + if ([[NSDictionary class] isSubclassOfClass:propertyClass]) { + [result setValue:[jsonObject objectForKey:property] forKey:propertyCamalized]; + } + else { + [result setValue:[self deserializeJSON:[propertyClass deserializeJSON:[jsonObject objectForKey:property] asClass:propertyClass]] forKey:propertyCamalized]; + } + } + } + } + else { + //JSON value + result = jsonObject; + } + return result; +} + ++ (id) deserializeJSON:(id)jsonObject { + id result = nil; + if ([jsonObject isKindOfClass:[NSArray class]]) { + //JSON array + result = [NSMutableArray array]; + for (id childObject in jsonObject) { + [result addObject:[self deserializeJSON:childObject]]; + } + } + else if ([jsonObject isKindOfClass:[NSDictionary class]]) { + //JSON object + //this assumes we are dealing with JSON in the form rails provides: + // {className : { property1 : value, property2 : {class2Name : {property 3 : value }}}} + NSString *objectName = [[(NSDictionary *)jsonObject allKeys] objectAtIndex:0]; + + Class objectClass = NSClassFromString([objectName toClassName]); + if (objectClass != nil) { + //classname matches, instantiate a new instance of the class and set it as the current parent object + result = [[[objectClass alloc] init] autorelease]; + } + + NSDictionary *properties = (NSDictionary *)[[(NSDictionary *)jsonObject allValues] objectAtIndex:0]; + + NSDictionary *objectPropertyNames = [objectClass propertyNamesAndTypes]; + + for (NSString *property in [properties allKeys]) { + NSString *propertyCamalized = [[self convertProperty:property andClassName:objectName] camelize]; + if ([[objectPropertyNames allKeys]containsObject:propertyCamalized]) { + Class propertyClass = [self propertyClass:[objectPropertyNames objectForKey:propertyCamalized]]; + [result setValue:[self deserializeJSON:[propertyClass deserialize:[properties objectForKey:property]]] forKey:propertyCamalized]; + } + } + } + else { + //JSON value + result = jsonObject; + } + return result; +} + +- (NSString *) convertProperty:(NSString *)propertyName andClassName:(NSString *)className { + if([propertyName isEqualToString:@"id"]) { + propertyName = [NSString stringWithFormat:@"%@_id",className]; + } + return propertyName; +} + +- (NSString *)jsonClassName { + NSString *className = NSStringFromClass([self class]); + return [[className stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[className substringToIndex:1] lowercaseString]] underscore]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h new file mode 100644 index 0000000..048bd3e --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h @@ -0,0 +1,13 @@ +// +// NSDate+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSDate(Serialize) + ++ (NSDate *) deserialize:(id)value; +- (NSString *) serialize; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m new file mode 100644 index 0000000..3d06aca --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m @@ -0,0 +1,23 @@ +// +// NSDate+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSDate+Serialize.h" +#import "ObjectiveResourceDateFormatter.h" + + +@implementation NSDate(Serialize) + ++ (NSDate *) deserialize:(id)value { + return (value == [NSNull null]) ? nil : [ObjectiveResourceDateFormatter parseDateTime:value]; +} + +- (NSString *) serialize { + return [ObjectiveResourceDateFormatter formatDate:self]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h new file mode 100644 index 0000000..9c7b8e7 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h @@ -0,0 +1,16 @@ +// +// NSDictionary+KeyTranslation.h +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSDictionary(KeyTranslation) + ++ (NSString *)translationForKey:(NSString *)key withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m new file mode 100644 index 0000000..d1b418c --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m @@ -0,0 +1,19 @@ +// +// NSDictionary+KeyTranslation.m +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSDictionary+KeyTranslation.h" + + +@implementation NSDictionary(KeyTranslation) + ++ (NSString *)translationForKey:(NSString *)key withTranslations:(NSDictionary *)keyTranslations { + NSString *newKey = [keyTranslations objectForKey:key]; + return (newKey ? newKey : key); +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h new file mode 100644 index 0000000..5d2b1af --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h @@ -0,0 +1,14 @@ +// +// NSObject+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSObject(Serialize) + ++ (id) deserialize:(id)value; +- (id) serialize; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m new file mode 100644 index 0000000..9401852 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m @@ -0,0 +1,22 @@ +// +// NSObject+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+Serialize.h" + + +@implementation NSObject(Serialize) + ++ (id)deserialize:(id)value { + return value; +} + +- (id) serialize { + return self; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h new file mode 100644 index 0000000..a597706 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h @@ -0,0 +1,16 @@ +// +// NSString+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSString(Deserialize) + ++ (NSString *)deserialize:(id)value; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m new file mode 100644 index 0000000..27bdc8d --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m @@ -0,0 +1,19 @@ +// +// NSString+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSString+Serialize.h" + + +@implementation NSString(Serialize) + ++ (NSString *) deserialize:(id)value { + return [NSString stringWithFormat:@"%@",value]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h new file mode 100644 index 0000000..f0820dc --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h @@ -0,0 +1,11 @@ +// +// Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+Serialize.h" +#import "NSDate+Serialize.h" +#import "NSString+Serialize.h" diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h new file mode 100644 index 0000000..d03cced --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h @@ -0,0 +1,32 @@ +// +// FromXMLElementDelegate.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface FromXMLElementDelegate : NSObject { + Class targetClass; + id parsedObject; + NSString *currentPropertyName; + NSMutableString *contentOfCurrentProperty; + NSMutableArray *unclosedProperties; + NSString *currentPropertyType; +} + +@property (nonatomic, retain) Class targetClass; +@property (nonatomic, retain) id parsedObject; +@property (nonatomic, retain) NSString *currentPropertyName; +@property (nonatomic, retain) NSMutableString *contentOfCurrentProperty; +@property (nonatomic, retain) NSMutableArray *unclosedProperties; +@property (nonatomic, retain) NSString *currentPropertyType; + ++ (FromXMLElementDelegate *)delegateForClass:(Class)targetClass; + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict; +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string; +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName; +- (NSString *)convertElementName:(NSString *)anElementName; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m new file mode 100644 index 0000000..ca30a4f --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m @@ -0,0 +1,188 @@ +// +// FromXMLElementDelegate.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "FromXMLElementDelegate.h" +#import "XMLSerializableSupport.h" +#import "CoreSupport.h" + +@implementation FromXMLElementDelegate + +@synthesize targetClass, parsedObject, currentPropertyName, contentOfCurrentProperty, unclosedProperties, currentPropertyType; + ++ (FromXMLElementDelegate *)delegateForClass:(Class)targetClass { + FromXMLElementDelegate *delegate = [[[self alloc] init] autorelease]; + [delegate setTargetClass:targetClass]; + return delegate; +} + + +- (id)init { + super; + self.unclosedProperties = [NSMutableArray array]; + return self; +} + + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict +{ + if ([@"nil-classes" isEqualToString:elementName]) { + //empty result set, do nothing + } + + //Start of the root object + else if (parsedObject == nil) { + if ([elementName isEqualToString:[self.targetClass xmlElementName]]) { + self.parsedObject = [[[self.targetClass alloc] init] autorelease]; + } + else if ([@"array" isEqualToString:[attributeDict objectForKey:@"type"]]) { + self.parsedObject = [NSMutableArray array]; + } + [self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]]; + self.currentPropertyName = elementName; + } + + else { + //if we are inside another element and it is not the current parent object, + // then create an object for that parent element + if(self.currentPropertyName != nil && (![self.currentPropertyName isEqualToString:[[self.parsedObject class] xmlElementName]])) { + Class elementClass = NSClassFromString([currentPropertyName toClassName]); + if (elementClass != nil) { + //classname matches, instantiate a new instance of the class and set it as the current parent object + self.parsedObject = [[[elementClass alloc] init] autorelease]; + [self.unclosedProperties addObject:[NSArray arrayWithObjects:self.currentPropertyName, self.parsedObject, nil]]; + } + } + + // If we recognize an element that corresponds to a known property of the current parent object, or if the + // current parent is an array then start collecting content for this child element + + if (([self.parsedObject isKindOfClass:[NSArray class]]) || + ([[[self.parsedObject class] propertyNames] containsObject:[[self convertElementName:elementName] camelize]])) { + self.currentPropertyName = [self convertElementName:elementName]; + if ([@"array" isEqualToString:[attributeDict objectForKey:@"type"]]) { + self.parsedObject = [NSMutableArray array]; + [self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]]; + } + else { + self.contentOfCurrentProperty = [NSMutableString string]; + self.currentPropertyType = [attributeDict objectForKey:@"type"]; + } + } + else { + // The element isn't one that we care about, so set the property that holds the + // character content of the current element to nil. That way, in the parser:foundCharacters: + // callback, the string that the parser reports will be ignored. + self.currentPropertyName = nil; + self.contentOfCurrentProperty = nil; + } + } +} + +// Characters (i.e. an element value - retarded, I know) are given to us in chunks, +// so we need to append them onto the current property value. +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + + // If the current property is nil, then we know we're currently at + // an element that we don't know about or don't care about + if (self.contentOfCurrentProperty) { + + // Append string value on since we're given them in chunks + [self.contentOfCurrentProperty appendString:string]; + } +} + +//Basic type conversion based on the ObjectiveResource "type" attribute +- (id) convertProperty:(NSString *)propertyValue toType:(NSString *)type { + if ([type isEqualToString:@"datetime" ]) { + return [NSDate fromXMLDateTimeString:propertyValue]; + } + else if ([type isEqualToString:@"date"]) { + return [NSDate fromXMLDateString:propertyValue]; + } + + // uncomment this if you what to support NSNumber and NSDecimalNumber + // if you do your classId must be a NSNumber since rails will pass it as such + //else if ([type isEqualToString:@"decimal"]) { + // return [NSDecimalNumber decimalNumberWithString:propertyValue]; + //} + //else if ([type isEqualToString:@"integer"]) { + // return [NSNumber numberWithInt:[propertyValue intValue]]; + //} + + else { + return [NSString fromXmlString:propertyValue]; + } +} + +// Converts the Id element to modelNameId +- (NSString *) convertElementName:(NSString *)anElementName { + + if([anElementName isEqualToString:@"id"]) { + return [NSString stringWithFormat:@"%@Id",[[[self.parsedObject class]xmlElementName] camelize]]; + // return [NSString stringWithFormat:@"%@_%@" , [NSStringFromClass([self.parsedObject class]) +// stringByReplacingCharactersInRange:NSMakeRange(0, 1) +// withString:[[NSStringFromClass([self.parsedObject class]) +// substringWithRange:NSMakeRange(0,1)] +// lowercaseString]], anElementName]; + } + else { + + return anElementName; + + } + +} + +// We're done receiving the value of a particular element, so take the value we've collected and +// set it on the current object +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + if(self.contentOfCurrentProperty != nil && self.currentPropertyName != nil) { + [self.parsedObject + setValue:[self convertProperty:self.contentOfCurrentProperty toType:self.currentPropertyType] + forKey:[self.currentPropertyName camelize]]; + } + else if ([self.currentPropertyName isEqualToString:[self convertElementName:elementName]]) { + //element is closed, pop it from the stack + [self.unclosedProperties removeLastObject]; + //check for a parent object on the stack + if ([self.unclosedProperties count] > 0) { + //handle arrays as a special case + if ([[[self.unclosedProperties lastObject] objectAtIndex:1] isKindOfClass:[NSArray class]]) { + [[[self.unclosedProperties lastObject] objectAtIndex:1] addObject:self.parsedObject]; + } + else { + [[[self.unclosedProperties lastObject] objectAtIndex:1] setValue:self.parsedObject forKey:[self convertElementName:[elementName camelize]]]; + } + self.parsedObject = [[self.unclosedProperties lastObject] objectAtIndex:1]; + } + } + + self.contentOfCurrentProperty = nil; + + //set the parent object, if one exists, as the current element + if ([self.unclosedProperties count] > 0) { + self.currentPropertyName = [[self.unclosedProperties lastObject] objectAtIndex:0]; + } +} + + +#pragma mark cleanup + +- (void)dealloc { + [targetClass release]; + [currentPropertyName release]; + [parsedObject release]; + [contentOfCurrentProperty release]; + [unclosedProperties release]; + [currentPropertyType release]; + [super dealloc]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h new file mode 100644 index 0000000..21213af --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h @@ -0,0 +1,13 @@ +// +// NSArray+XMLSerializableSupport.h +// +// +// Created by vickeryj on 9/29/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import + +@interface NSArray (XMLSerializableSupport) +- (NSString *)toXMLValue; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m new file mode 100644 index 0000000..2afaa35 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSArray+XMLSerializableSupport.m +// +// +// Created by vickeryj on 9/29/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import "NSArray+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" + + +@implementation NSArray (XMLSerializableSupport) + +- (NSString *)toXMLValue { + NSMutableString *result = [NSMutableString string]; + + for (id element in self) { + [result appendString:[element toXMLElement]]; + } + + return result; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + NSMutableString *elementValue = [NSMutableString string]; + for (id element in self) { + [elementValue appendString:[element toXMLElementAs:[[element class] xmlElementName] excludingInArray:exclusions withTranslations:keyTranslations]]; + } + return [[self class] buildXmlElementAs:rootName withInnerXml:elementValue andType:@"array"]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h new file mode 100644 index 0000000..1f3ef27 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h @@ -0,0 +1,13 @@ +// +// NSDate+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSDate (XMLSerializableSupport) +- (NSString *)toXMLValue; ++ (NSDate *)fromXMLDateTimeString:(NSString *)xmlString; ++ (NSDate *)fromXMLDateString:(NSString *)xmlString; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m new file mode 100644 index 0000000..5ea5abd --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSDate+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSDate+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "ObjectiveResourceDateFormatter.h" + +@implementation NSDate (XMLSerializableSupport) + +//FIXME we should have one formatter for the entire app + +- (NSString *)toXMLValue { + return [ ObjectiveResourceDateFormatter formatDate:self]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue] andType:[[self class] xmlTypeFor:self]]; +} + ++ (NSDate *)fromXMLDateTimeString:(NSString *)xmlString { + return [ ObjectiveResourceDateFormatter parseDateTime:xmlString]; +} + ++ (NSDate *)fromXMLDateString:(NSString *)xmlString { + return [ ObjectiveResourceDateFormatter parseDate:xmlString]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h new file mode 100644 index 0000000..957bfd1 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h @@ -0,0 +1,17 @@ +// +// NSDictionary+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + + +@interface NSDictionary (XMLSerializableSupport) + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations andType:(NSString *)xmlType; + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m new file mode 100644 index 0000000..f973aef --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m @@ -0,0 +1,37 @@ +// +// NSDictionary+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSDictionary+KeyTranslation.h" + +@implementation NSDictionary (XMLSerializableSupport) + + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations andType:(NSString *)xmlType { + + NSMutableString* elementValue = [NSMutableString string]; + id value; + NSString *propertyRootName; + for (NSString *key in self) { + // Create XML if key not in exclusion list + if(![exclusions containsObject:key]) { + value = [self valueForKey:key]; + propertyRootName = [[self class] translationForKey:key withTranslations:keyTranslations]; + [elementValue appendString:[value toXMLElementAs:propertyRootName]]; + } + } + return [[self class] buildXmlElementAs:rootName withInnerXml:elementValue andType:xmlType]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [self toXMLElementAs:rootName excludingInArray:exclusions withTranslations:keyTranslations andType:nil]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h new file mode 100644 index 0000000..2574f8f --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h @@ -0,0 +1,11 @@ +// +// NSNull+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSNull (XMLSerializableSupport) +- (NSString *)toXMLValue; +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m new file mode 100644 index 0000000..e725858 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m @@ -0,0 +1,17 @@ +// +// NSNull+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSNull+XMLSerializableSupport.h" + +@implementation NSNull (XMLSerializableSupport) + +- (NSString *)toXMLValue { + return @""; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h new file mode 100644 index 0000000..415dfce --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h @@ -0,0 +1,12 @@ +// +// NSNumber+XMLSerializableSupport.h +// objective_support +// +// Created by James Burka on 2/17/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSNumber(XMLSerializableSupport) +- (NSString *)toXMLValue; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m new file mode 100644 index 0000000..7436b34 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m @@ -0,0 +1,25 @@ +// +// NSNumber+XMLSerializableSupport.m +// objective_support +// +// Created by James Burka on 2/17/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSNumber+XMLSerializableSupport.h" + + +@implementation NSNumber(XMLSerializableSupport) + +- (NSString *)toXMLValue { + return [self stringValue]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue] andType:[[self class] xmlTypeFor:self]]; +} + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h new file mode 100644 index 0000000..33a0ff7 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h @@ -0,0 +1,54 @@ +// +// XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "XMLSerializable.h" + +@interface NSObject (XMLSerializableSupport) + ++ (NSString *)xmlTypeFor:(NSObject *)value; ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value andType:(NSString *)xmlType; + +/** + * Construct a string representation of the given value object, assuming + * the given root element name , the NSObjects's toXmlValue is called. + */ ++ (NSString *)buildXMLElementAs:(NSString *)rootName withValue:(NSObject *)value; + +/** + * + * Constructs the actual xml node as a string + * + */ ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value; + +/** + * What is the element name for this object when serialized to XML. + * Defaults to lower case and dasherized form of class name. + * I.e. [[Person class] xmlElementName] //> @"person" + */ ++ (NSString *)xmlElementName; + +/** + * Construct the XML string representation of this particular object. + * Only construct the markup for the value of this object, don't assume + * any naming. For instance: + * + * [myObject toXMLValue] //> @"xmlSerializedValue" + * + * and not + * + * [myObject toXMLValue] //> @"xmlSerializedValue" + * + * For simple objects like strings, numbers etc this will be the text value of + * the corresponding element. For complex objects this can include further markup: + * + * [myPersonObject toXMLValue] //> @"RyanDaigle" + */ +- (NSString *)toXMLValue; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m new file mode 100644 index 0000000..09fee6d --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m @@ -0,0 +1,132 @@ +// +// XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSDictionary+XMLSerializableSupport.h" +#import "CoreSupport.h" +#import "FromXMLElementDelegate.h" + +@implementation NSObject (XMLSerializableSupport) + +# pragma mark XML utility methods + +/** + * Get the appropriate xml type, if any, for the given value. + * I.e. "integer" or "decimal" etc... for use in element attributes: + * + * 1 + */ ++ (NSString *)xmlTypeFor:(NSObject *)value { + + // Can't do this with NSDictionary w/ Class keys. Explore more elegant solutions. + // TODO: Account for NSValue native types here? + if ([value isKindOfClass:[NSDate class]]) { + return @"datetime"; + } else if ([value isKindOfClass:[NSDecimalNumber class]]) { + return @"decimal"; + } else if ([value isKindOfClass:[NSNumber class]]) { + if (0 == strcmp("f",[(NSNumber *)value objCType]) || + 0 == strcmp("d",[(NSNumber *)value objCType])) + { + return @"decimal"; + } + else { + return @"integer"; + } + } else if ([value isKindOfClass:[NSArray class]]) { + return @"array"; + } else { + return nil; + } +} + ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value andType:(NSString *)xmlType{ + NSString *dashedName = [rootName dasherize]; + + if (xmlType != nil) { + return [NSString stringWithFormat:@"<%@ type=\"%@\">%@", dashedName, xmlType, value, dashedName]; + } else { + return [NSString stringWithFormat:@"<%@>%@", dashedName, value, dashedName]; + } +} + ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value { + return [[self class] buildXmlElementAs:rootName withInnerXml:value andType:nil]; +} + ++ (NSString *)buildXMLElementAs:(NSString *)rootName withValue:(NSObject *)value { + return [[self class] buildXmlElementAs:rootName withInnerXml:[value toXMLValue] andType:[self xmlTypeFor:value]]; +} + ++ (NSString *)xmlElementName { + NSString *className = NSStringFromClass(self); + return [[className stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[className substringToIndex:1] lowercaseString]] dasherize]; +} + +# pragma mark XMLSerializable implementation methods + +- (NSString *)toXMLElement { + return [self toXMLElementAs:[[self class] xmlElementName] excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementExcluding:(NSArray *)exclusions { + return [self toXMLElementAs:[[self class] xmlElementName] excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName { + return [self toXMLElementAs:rootName excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions { + return [self toXMLElementAs:rootName excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations { + return [self toXMLElementAs:rootName excludingInArray:[NSArray array] withTranslations:keyTranslations]; +} + +/** + * Override in complex objects to account for nested properties + **/ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self properties] toXMLElementAs:rootName excludingInArray:exclusions withTranslations:keyTranslations andType:[[self class] xmlTypeFor:self]]; +} + +# pragma mark XML Serialization convenience methods + +/** + * Override in objects that need special formatting before being printed to XML + **/ +- (NSString *)toXMLValue { + return [NSString stringWithFormat:@"%@", self]; +} + +# pragma mark XML Serialization input methods + ++ (id)fromXMLData:(NSData *)data { + + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + FromXMLElementDelegate *delegate = [FromXMLElementDelegate delegateForClass:self]; + [parser setDelegate:delegate]; + + // Turn off all those XML nits + [parser setShouldProcessNamespaces:NO]; + [parser setShouldReportNamespacePrefixes:NO]; + [parser setShouldResolveExternalEntities:NO]; + + // Let'er rip + [parser parse]; + [parser release]; + return delegate.parsedObject; +} + ++ (NSArray *)allFromXMLData:(NSData *)data { + return [self fromXMLData:data]; +} +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h new file mode 100644 index 0000000..1868a96 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h @@ -0,0 +1,20 @@ +// +// NSString+XMLSerializableSupport.h +// active_resource +// +// Created by James Burka on 1/6/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSString(XMLSerializableSupport) + ++ (NSString *)fromXmlString:(NSString *)aString; +- (NSString *)toXMLValue; +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m new file mode 100644 index 0000000..8682790 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m @@ -0,0 +1,33 @@ +// +// NSString+XMLSerializableSupport.m +// active_resource +// +// Created by James Burka on 1/6/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSString+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "NSString+GSub.h" + + +@implementation NSString(XMLSerializableSupport) + ++ (NSString *)fromXmlString:(NSString *)aString { + NSDictionary* escapeChars = [NSDictionary dictionaryWithObjectsAndKeys:@"&",@"&",@"\"",@""",@"'",@"'" + ,@"<",@"<",@">",@">",nil]; + return [aString gsub:escapeChars]; + +} + +- (NSString *)toXMLValue { + NSString *temp = [self gsub:[NSDictionary dictionaryWithObject:@"&" forKey:@"&"]]; + NSDictionary* escapeChars = [NSDictionary dictionaryWithObjectsAndKeys:@""",@"\"",@"'",@"'",@"<",@"<",@">",@">",nil]; + return [temp gsub:escapeChars]; +} +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue]]; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h new file mode 100644 index 0000000..59054b9 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h @@ -0,0 +1,79 @@ +// +// XMLSerializable.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@protocol XMLSerializable + +/** + * Instantiate a single instance of this class from the given XML data. + */ ++ (id)fromXMLData:(NSData *)data; + +/** + * Instantiate a collectionof instances of this class from the given XML data. + */ ++ (NSArray *)allFromXMLData:(NSData *)data; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the default element name: + * + * [myPerson toXMLElement] //> @"Ryan..." + */ +- (NSString *)toXMLElement; + + +/** + * Gets the full representation of this object minus the elements in the exclusions array + * + * + * + */ +- (NSString *)toXMLElementExcluding:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name: + * + * [myPerson toXMLElementAs:@"human"] //> @"Ryan..." + */ +- (NSString *)toXMLElementAs:(NSString *)rootName; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and excluding the given properties. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil]] + * + * //> @"Daigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and translating property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"RyanDaigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name, excluding the given properties, and translating + * property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil] + * withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"Daigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h new file mode 100644 index 0000000..267ba42 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h @@ -0,0 +1,14 @@ +// +// XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "XMLSerializable.h" +#import "CoreSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "NSNull+XMLSerializableSupport.h" +#import "NSDate+XMLSerializableSupport.h" +#import "NSString+XMLSerializableSupport.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h new file mode 100644 index 0000000..2a9274d --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h @@ -0,0 +1,11 @@ +// +// JSONFramework.h +// iphoneAndRails1 +// +// Created by vickeryj on 12/11/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" \ No newline at end of file diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h new file mode 100644 index 0000000..038ea8e --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2007 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 + + +/// Adds JSON generation to NSObject subclasses +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m new file mode 100644 index 0000000..df6749b --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m @@ -0,0 +1,57 @@ +/* +Copyright (C) 2007 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+SBJSON.h" +#import "SBJSON.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJSON *generator = [[SBJSON new] autorelease]; + + NSError *error; + NSString *json = [generator stringWithFragment:self error:&error]; + + if (!json) + NSLog(@"%@", error); + return json; +} + +- (NSString *)JSONRepresentation { + SBJSON *generator = [[SBJSON new] autorelease]; + + NSError *error; + NSString *json = [generator stringWithObject:self error:&error]; + + if (!json) + NSLog(@"%@", error); + return json; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h new file mode 100644 index 0000000..69cfa4f --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2007 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 + +/// Adds JSON parsing to NSString +@interface NSString (NSString_SBJSON) + +/// Returns the object represented in the receiver, or nil on error. +- (id)JSONFragmentValue; + +/// Returns the dictionary or array represented in the receiver, or nil on error. +- (id)JSONValue; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m new file mode 100644 index 0000000..69878da --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m @@ -0,0 +1,60 @@ +/* +Copyright (C) 2007 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 "NSString+SBJSON.h" +#import "SBJSON.h" + + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJSON *json = [[SBJSON new] autorelease]; + + NSError *error; + id o = [json fragmentWithString:self error:&error]; + + if (!o) + NSLog(@"%@", error); + return o; +} + +- (id)JSONValue +{ + SBJSON *json = [[SBJSON new] autorelease]; + + NSError *error; + id o = [json objectWithString:self error:&error]; + + if (!o) + NSLog(@"%@", error); + return o; +} + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h new file mode 100644 index 0000000..c931d46 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h @@ -0,0 +1,137 @@ +/* +Copyright (C) 2008 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 + +extern NSString * SBJSONErrorDomain; + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** +@brief A strict JSON parser and generator + +This is the parser and generator underlying the categories added to +NSString and various other objects. + +Objective-C types are mapped to JSON types and back in the following way: + +@li NSNull -> Null -> NSNull +@li NSString -> String -> NSMutableString +@li NSArray -> Array -> NSMutableArray +@li NSDictionary -> Object -> NSMutableDictionary +@li NSNumber (-initWithBool:) -> Boolean -> NSNumber -initWithBool: +@li NSNumber -> Number -> NSDecimalNumber + +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 throw an exception. + +NSNumber instances created with the +numberWithBool: 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. JSON numbers turn into NSDecimalNumber instances, +as we can thus avoid any loss of precision. + +Strictly speaking correctly formed JSON text must have exactly +one top-level container. (Either an Array or an Object.) Scalars, +i.e. nulls, numbers, booleans and strings, are not valid JSON on their own. +It can be quite convenient to pretend that such fragments are valid +JSON however, and this class lets you do so. + +This class does its best to be as strict as possible, both in what it +accepts and what it generates. (Other than the above mentioned support +for JSON fragments.) 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. + +*/ +@interface SBJSON : NSObject { + BOOL humanReadable; + BOOL sortKeys; + NSUInteger maxDepth; + +@private + // Used temporarily during scanning/generation + NSUInteger depth; + const char *c; +} + +/// Whether we are generating human-readable (multiline) JSON +/** + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/// Whether or not to sort the dictionary keys in the output +/** The default is to not sort the keys. */ +@property BOOL sortKeys; + +/// The maximum depth the parser will go to +/** Defaults to 512. */ +@property NSUInteger maxDepth; + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error; + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + +@end diff --git a/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m new file mode 100644 index 0000000..7a6ad54 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m @@ -0,0 +1,740 @@ +/* +Copyright (C) 2008 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 "SBJSON.h" + +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + +@interface SBJSON (Generator) + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error; + +- (NSString*)indent; + +@end + +@interface SBJSON (Scanner) + +- (BOOL)scanValue:(NSObject **)o error:(NSError **)error; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error; +- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error; +- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error; +- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error; +- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error; + +- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error; +- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error; + +- (BOOL)scanIsAtEnd; + +@end + +#pragma mark Private utilities + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + +static NSError *err(int code, NSString *str) { + NSDictionary *ui = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui]; +} + +static NSError *errWithUnderlier(int code, NSError **u, NSString *str) { + if (!u) + return err(code, str); + + NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + *u, NSUnderlyingErrorKey, + nil]; + return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui]; +} + + +@implementation SBJSON + +static char ctrl[0x22]; + ++ (void)initialize +{ + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +- (id)init { + if (self = [super init]) { + [self setMaxDepth:512]; + } + return self; +} + +#pragma mark Generator + + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + NSError *err2 = nil; + if (!allowScalar && ![value isKindOfClass:[NSDictionary class]] && ![value isKindOfClass:[NSArray class]]) { + err2 = err(EFRAGMENT, @"Not valid type for JSON"); + + } else if ([self appendValue:value into:json error:&err2]) { + return json; + } + + if (error) + *error = err2; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value allowScalar:YES error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value allowScalar:NO error:error]; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + + } else { + *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]); + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error { + [json appendString:@"["]; + depth++; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json error:error]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error { + [json appendString:@"{"]; + depth++; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + *error = err(EUNSUPPORTED, @"JSON object key must be string"); + return NO; + } + + if (![self appendString:value into:json error:error]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json error:error]) { + *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"Unsupported value for key %@ in object", value]); + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error { + + static NSMutableCharacterSet *kEscapeChars; + if( ! kEscapeChars ) { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; + } + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + [json appendFormat:@"%C", uc]; + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + +#pragma mark Parser + +/** + 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 + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(id)repr allowScalar:(BOOL)allowScalar error:(NSError**)error { + + if (!repr) { + if (error) + *error = err(EINPUT, @"Input was 'nil'"); + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + NSError *err2 = nil; + if (![self scanValue:&o error:&err2]) { + if (error) + *error = err2; + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + if (error) + *error = err(ETRAILGARBAGE, @"Garbage after JSON"); + return nil; + } + + // If we don't allow scalars, check that the object we've found is a valid JSON container. + if (!allowScalar && ![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + if (error) + *error = err(EFRAGMENT, @"Valid fragment, but not JSON"); + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr allowScalar:YES error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr allowScalar:NO error:error]; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o error:(NSError **)error +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o error:error]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o error:error]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o error:error]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o error:error]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o error:error]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o error:error]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o error:error]; + break; + case '+': + *error = err(EPARSENUM, @"Leading + disallowed in number"); + return NO; + break; + case 0x0: + *error = err(EEOF, @"Unexpected end of string"); + return NO; + break; + default: + *error = err(EPARSE, @"Unrecognised leading character"); + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + *error = err(EPARSE, @"Expected 'true'"); + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + *error = err(EPARSE, @"Expected 'false'"); + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error +{ + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + *error = err(EPARSE, @"Expected 'null'"); + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error +{ + if (maxDepth && ++depth > maxDepth) { + *error = err(EDEPTH, @"Nested too deep"); + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v error:error]) { + *error = errWithUnderlier(EPARSE, error, @"Expected value while parsing array"); + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + *error = err(ETRAILCOMMA, @"Trailing comma disallowed in array"); + return NO; + } + } + } + + *error = err(EEOF, @"End of input while parsing array"); + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error +{ + if (maxDepth && ++depth > maxDepth) { + *error = err(EDEPTH, @"Nested too deep"); + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k error:error])) { + *error = errWithUnderlier(EPARSE, error, @"Object key string expected"); + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + *error = err(EPARSE, @"Expected ':' separating key and value"); + return NO; + } + + c++; + if (![self scanValue:&v error:error]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + *error = errWithUnderlier(EPARSE, error, string); + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + *error = err(ETRAILCOMMA, @"Trailing comma disallowed in object"); + return NO; + } + } + } + + *error = err(EEOF, @"End of input while parsing object"); + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc error:error]) { + *error = errWithUnderlier(EUNICODE, error, @"Broken unicode character"); + return NO; + } + c--; // hack. + break; + default: + *error = err(EESCAPE, [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]); + return NO; + break; + } + [*o appendFormat:@"%C", uc]; + c++; + + } else if (*c < 0x20) { + *error = err(ECTRL, [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]); + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + *error = err(EEOF, @"Unexpected EOF while parsing string"); + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi error:error]) { + *error = err(EUNICODE, @"Missing hex quad"); + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo error:error])) { + *error = errWithUnderlier(EUNICODE, error, @"Missing low character in surrogate pair"); + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + *error = err(EUNICODE, @"Invalid low surrogate char"); + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + *error = err(EUNICODE, @"Invalid high character in surrogate pair"); + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + *error = err(EUNICODE, @"Missing hex digit in quad"); + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + *error = err(EPARSENUM, @"Leading 0 disallowed in number"); + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + *error = err(EPARSENUM, @"No digits after initial minus"); + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + *error = err(EPARSENUM, @"No digits after decimal point"); + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + *error = err(EPARSENUM, @"No digits after exponent"); + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + *error = err(EPARSENUM, @"Failed creating decimal instance"); + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + + +#pragma mark Properties + +@synthesize humanReadable; +@synthesize sortKeys; +@synthesize maxDepth; + +@end diff --git a/code/6-objectiveresource/objectiveresource/LICENSE b/code/6-objectiveresource/objectiveresource/LICENSE new file mode 100644 index 0000000..cddb33c --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008 Y Factorial, LLC + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/code/6-objectiveresource/objectiveresource/README.textile b/code/6-objectiveresource/objectiveresource/README.textile new file mode 100644 index 0000000..370c9f3 --- /dev/null +++ b/code/6-objectiveresource/objectiveresource/README.textile @@ -0,0 +1,39 @@ +h2. Overview + +ObjectiveResource is a port of Rails' ActiveResource framework to Objective-C. + +The primary purpose of this project is to quickly and easily connect +iPhone applications with servers running Rails. + +This project relies on ObjectiveSupport, which aims to provide some popular +Rubyisms to Objective-C. If you checkout this project using git, you can +pull down ObjectiveSupport by doing a "git submodule init" followed by +a "git submodule update". + +h2. Getting Started + +h3. Sample Code + +This project comes bundled with a sample iPhone application and a sample +Rails application. To see how everything works together you can open +up the .xcodeproj and fire up a rails server in sample_rails_app. + +h3. Integrating with your project + +# Download (clone) the objectiveresource project +## If you do a git clone, you will need to follow it up with "git submodule init" and "git submodule update" +# open the .xcodeproj in XCode for both objectiveresource and your iPhone project +# drag the ObjectiveResource and ObjectSupport groups from the objectiveresource project onto your iPhone project, making +sure to check the "copy files" box. + +h2. Contributing + +h3. Running Tests + +Unit testing makes use of the SenTest work-alike from Google Toolbox for Mac. + +To run the tests, select the "Unit Tests" target in XCode and Build and Run. + +You will need to have Rails installed along with the populator and faker +gems, as the project uses a "Run Script" to setup a local Rails +server for testing. diff --git a/code/iphone_app/.gitignore b/code/iphone_app/.gitignore new file mode 100644 index 0000000..db32e85 --- /dev/null +++ b/code/iphone_app/.gitignore @@ -0,0 +1,9 @@ +build +*.pbxuser +*.mode1v3 +*.perspective +*.perspectivev3 +*~.nib +*~.xib +!default.pbxuser +!default.mode1v3 diff --git a/code/iphone_app/Classes/.gitignore b/code/iphone_app/Classes/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/code/iphone_app/Classes/AppHelpers.h b/code/iphone_app/Classes/AppHelpers.h new file mode 100644 index 0000000..73beea8 --- /dev/null +++ b/code/iphone_app/Classes/AppHelpers.h @@ -0,0 +1,22 @@ +#import + +#define TABLE_BACKGROUND_COLOR [UIColor colorWithRed:0.951 green:0.951 blue:0.951 alpha:1.000] + +@interface AppHelpers : NSObject + ++ (NSString *)formatDate:(NSDate *)date; ++ (NSDate *)parseDateTime:(NSString *)dateTimeString; + ++ (NSString *)numberToCurrency:(NSString *)number; ++ (NSString *)penceToDollars:(NSString *)pence; ++ (NSString *)dollarsToPence:(NSString *)dollars; + ++ (UIBarButtonItem *)newCancelButton:(id)target; ++ (UIBarButtonItem *)newSaveButton:(id)target; ++ (UITextField *)newTableCellTextField:(id)delegate; + ++ (void)showAlert:(NSString *)title withMessage:(NSString *)message; ++ (void)showAlertWithError:(NSError *)error; ++ (void)handleRemoteError:(NSError *)error; + +@end diff --git a/code/iphone_app/Classes/AppHelpers.m b/code/iphone_app/Classes/AppHelpers.m new file mode 100644 index 0000000..c2c8c0e --- /dev/null +++ b/code/iphone_app/Classes/AppHelpers.m @@ -0,0 +1,133 @@ +#import "AppHelpers.h" + +@implementation AppHelpers + ++ (NSString *)formatDate:(NSDate *)date { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + [formatter setTimeStyle:NSDateFormatterMediumStyle]; + NSString *result = [formatter stringFromDate:date]; + [formatter release]; + return result; +} + ++ (NSDate *)parseDateTime:(NSString *)dateTimeString { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + NSDate *result = [formatter dateFromString:dateTimeString]; + [formatter release]; + return result; +} + ++ (NSString *)numberToCurrency:(NSString *)number { + if (number == nil) { + return @"$0.00"; + } + + NSDecimalNumber *decimalNumber = + [NSDecimalNumber decimalNumberWithString:number]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:decimalNumber]; + + [formatter release]; + return result; +} + ++ (NSString *)penceToDollars:(NSString *)pence { + if (pence == nil) { + return @"$0.00"; + } + + NSDecimalNumber *penceNumber = + [NSDecimalNumber decimalNumberWithString:pence]; + + NSDecimalNumber *dollars = + [penceNumber decimalNumberByDividingBy: + [NSDecimalNumber decimalNumberWithString:@"100"]]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + [formatter setMinimumFractionDigits:2]; + + NSString *result = [formatter stringFromNumber:dollars]; + + [formatter release]; + return result; +} + ++ (NSString *)dollarsToPence:(NSString *)dollars { + if (dollars == nil) { + return @"$0.00"; + } + + NSDecimalNumber *dollarsNumber = + [NSDecimalNumber decimalNumberWithString:dollars]; + + NSDecimalNumber *pence = + [dollarsNumber decimalNumberByMultiplyingBy: + [NSDecimalNumber decimalNumberWithString:@"100"]]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + + NSString *result = [formatter stringFromNumber:pence]; + + [formatter release]; + return result; +} + ++ (UIBarButtonItem *)newCancelButton:(id)target { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemCancel + target:target + action:@selector(cancel)]; +} + ++ (UIBarButtonItem *)newSaveButton:(id)target { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemSave + target:target + action:@selector(save)]; +} + ++ (UITextField *)newTableCellTextField:(id)delegate { + UITextField *textField = + [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 250, 25)]; + textField.font = [UIFont systemFontOfSize:16]; + textField.delegate = delegate; + textField.returnKeyType = UIReturnKeyDone; + textField.clearsOnBeginEditing = NO; + return textField; +} + ++ (void)showAlert:(NSString *)title withMessage:(NSString *)message { + UIAlertView *alert = + [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; +} + ++ (void)showAlertWithError:(NSError *)error { + NSString *message = + [NSString stringWithFormat:@"Sorry, %@", [error localizedDescription]]; + [self showAlert:@"Error" withMessage:message]; +} + + ++ (void)handleRemoteError:(NSError *)error { + if ([error code] == 401) { + [self showAlert:@"Login Failed" + withMessage:@"Please check your username and password, and try again."]; + } else { + [self showAlertWithError:error]; + } +} + +@end diff --git a/code/iphone_app/Classes/AuthenticationViewController.h b/code/iphone_app/Classes/AuthenticationViewController.h new file mode 100644 index 0000000..6daa99d --- /dev/null +++ b/code/iphone_app/Classes/AuthenticationViewController.h @@ -0,0 +1,17 @@ +@class User; + +@interface AuthenticationViewController : UITableViewController { + UITextField *usernameField; + UITextField *passwordField; + User *user; +} + +@property (nonatomic, retain) UITextField *usernameField; +@property (nonatomic, retain) UITextField *passwordField; +@property (nonatomic, retain) User *user; + +- (id)initWithCurrentUser:(User *)user; + +- (IBAction)save; + +@end diff --git a/code/iphone_app/Classes/AuthenticationViewController.m b/code/iphone_app/Classes/AuthenticationViewController.m new file mode 100644 index 0000000..1c7b057 --- /dev/null +++ b/code/iphone_app/Classes/AuthenticationViewController.m @@ -0,0 +1,163 @@ +#import "AuthenticationViewController.h" + +#import "User.h" +#import "ConnectionManager.h" + +@interface AuthenticationViewController () +- (UITextField *)newUsernameField; +- (UITextField *)newPasswordField; +- (void)authenticateRemote; +@end + +@implementation AuthenticationViewController + +@synthesize usernameField; +@synthesize passwordField; +@synthesize user; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [usernameField release]; + [passwordField release]; + [user release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithCurrentUser:(User *)aUser { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.user = aUser; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Login"; + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + usernameField = [self newUsernameField]; + [usernameField becomeFirstResponder]; + + passwordField = [self newPasswordField]; + + UIBarButtonItem *saveButton = [AppHelpers newSaveButton:self]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)save { + user.login = [usernameField text]; + user.password = [passwordField text]; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(authenticateRemote) + onTarget:self]; +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == usernameField) { + [passwordField becomeFirstResponder]; + } + if (textField == passwordField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.usernameField.text length] > 0) && ([self.passwordField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Table data source methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForFooterInSection:(NSInteger)section { + return @"\nEnter the username and password for your online account."; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + if (indexPath.row == 0) { + [cell.contentView addSubview:usernameField]; + } else { + [cell.contentView addSubview:passwordField]; + } + + return cell; +} + +#pragma mark - +#pragma mark Private methods + +- (void)authenticateRemote { + NSError *error = nil; + BOOL authenticated = [user authenticate:&error]; + if (authenticated == YES) { + [user saveCredentialsToKeychain]; + [self.navigationController performSelectorOnMainThread:@selector(popViewControllerAnimated:) + withObject:[NSNumber numberWithBool:YES] + waitUntilDone:NO]; + [AppHelpers showAlert:@"Welcome!" withMessage:@"Your account information was saved."]; + } else { + [AppHelpers handleRemoteError:error]; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (UITextField *)newUsernameField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Username"; + field.text = self.user.login; + field.autocapitalizationType = UITextAutocapitalizationTypeNone; + field.autocorrectionType = UITextAutocorrectionTypeNo; + field.returnKeyType = UIReturnKeyNext; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +- (UITextField *)newPasswordField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Password"; + field.text = self.user.password; + field.autocapitalizationType = UITextAutocapitalizationTypeNone; + field.autocorrectionType = UITextAutocorrectionTypeNo; + field.secureTextEntry = YES; + field.returnKeyType = UIReturnKeyDone; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +@end diff --git a/code/iphone_app/Classes/Credit.h b/code/iphone_app/Classes/Credit.h new file mode 100644 index 0000000..c942611 --- /dev/null +++ b/code/iphone_app/Classes/Credit.h @@ -0,0 +1,21 @@ +#import "ObjectiveResource.h" + +@interface Credit : NSObject { + NSString *creditId; + NSString *goalId; + NSString *name; + NSString *amount; + NSDate *updatedAt; + NSDate *createdAt; +} + +@property (nonatomic, copy) NSString *creditId; +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, retain) NSDate *updatedAt; +@property (nonatomic, retain) NSDate *createdAt; + +- (NSString *)amountAsCurrency; + +@end diff --git a/code/iphone_app/Classes/Credit.m b/code/iphone_app/Classes/Credit.m new file mode 100644 index 0000000..dd1e002 --- /dev/null +++ b/code/iphone_app/Classes/Credit.m @@ -0,0 +1,59 @@ +#import "Credit.h" + +@implementation Credit + +@synthesize creditId; +@synthesize goalId; + +@synthesize name; +@synthesize amount; +@synthesize createdAt; +@synthesize updatedAt; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [creditId release]; + [goalId release]; + [name release]; + [amount release]; + [createdAt release]; + [updatedAt release]; + [super dealloc]; +} + +- (NSString *)amountAsCurrency { + return [AppHelpers numberToCurrency:self.amount]; +} + +#pragma mark ObjectiveResource overrides to handle nested resources + ++ (NSString *)getRemoteCollectionName { + return @"goals"; +} + +- (NSString *)nestedPath { + NSString *path = [NSString stringWithFormat:@"%@/credits", goalId]; + if (creditId) { + path = [path stringByAppendingFormat:@"/%@", creditId]; + } + return path; +} + +- (BOOL)createRemoteWithResponse:(NSError **)aError { + return [self createRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] + withResponse:aError]; +} + +- (BOOL)updateRemoteWithResponse:(NSError **)aError { + return [self updateRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] + withResponse:aError]; +} + +- (BOOL)destroyRemoteWithResponse:(NSError **)aError { + return [self destroyRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] + withResponse:aError]; +} + +@end diff --git a/code/iphone_app/Classes/CreditDetailViewController.h b/code/iphone_app/Classes/CreditDetailViewController.h new file mode 100644 index 0000000..e4ff7e2 --- /dev/null +++ b/code/iphone_app/Classes/CreditDetailViewController.h @@ -0,0 +1,18 @@ +@class Credit; + +@interface CreditDetailViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Credit *credit; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Credit *credit; + +- (id)initWithCredit:(Credit *)credit; + +- (IBAction)save; +- (IBAction)cancel; + +@end diff --git a/code/iphone_app/Classes/CreditDetailViewController.m b/code/iphone_app/Classes/CreditDetailViewController.m new file mode 100644 index 0000000..c0045a0 --- /dev/null +++ b/code/iphone_app/Classes/CreditDetailViewController.m @@ -0,0 +1,175 @@ +#import "CreditDetailViewController.h" + +#import "Credit.h" +#import "ConnectionManager.h" + +@interface CreditDetailViewController () +- (void)saveRemoteCredit; +- (UITextField *)newNameField; +- (UITextField *)newAmountField; +@end + +@implementation CreditDetailViewController + +@synthesize credit; +@synthesize nameField; +@synthesize amountField; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [nameField release]; + [amountField release]; + [credit release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithCredit:(Credit *)aCredit { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.credit = aCredit; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newNameField]; + [nameField becomeFirstResponder]; + amountField = [self newAmountField]; + + UIBarButtonItem *cancelButton = [AppHelpers newCancelButton:self]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [AppHelpers newSaveButton:self]; + self.navigationItem.rightBarButtonItem = saveButton; + [saveButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + if (credit.creditId) { + self.title = @"Edit Credit"; + nameField.text = credit.name; + amountField.text = credit.amount; + self.navigationItem.rightBarButtonItem.enabled = YES; + } else { + self.title = @"Add Credit"; + self.navigationItem.rightBarButtonItem.enabled = NO; + } +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)save { + credit.name = nameField.text; + credit.amount = amountField.text; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(saveRemoteCredit) + onTarget:self]; +} + +- (IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } + + return cell; +} + +#pragma mark - +#pragma mark Text Field Delegate methods + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Private methods + +- (void)saveRemoteCredit { + // If the model is new, then create will be called. + // Otherwise the model will be updated. + NSError *error = nil; + BOOL saved = [credit saveRemoteWithResponse:&error]; + if (saved == YES) { + [self.navigationController performSelectorOnMainThread:@selector(popViewControllerAnimated:) + withObject:[NSNumber numberWithBool:YES] + waitUntilDone:NO]; + } else { + [AppHelpers handleRemoteError:error]; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (UITextField *)newNameField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Name"; + field.keyboardType = UIKeyboardTypeASCIICapable; + field.returnKeyType = UIReturnKeyNext; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +- (UITextField *)newAmountField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Amount"; + field.keyboardType = UIKeyboardTypeNumberPad; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +@end diff --git a/code/iphone_app/Classes/Goal.h b/code/iphone_app/Classes/Goal.h new file mode 100644 index 0000000..1dcfc42 --- /dev/null +++ b/code/iphone_app/Classes/Goal.h @@ -0,0 +1,20 @@ +#import "ObjectiveResource.h" + +@interface Goal : NSObject { + NSString *goalId; + NSString *name; + NSString *amount; + NSDate *updatedAt; + NSDate *createdAt; +} + +@property (nonatomic, copy) NSString *goalId; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *amount; +@property (nonatomic, retain) NSDate *updatedAt; +@property (nonatomic, retain) NSDate *createdAt; + +- (NSArray *)findAllRemoteCredits; +- (NSString *)amountAsCurrency; + +@end \ No newline at end of file diff --git a/code/iphone_app/Classes/Goal.m b/code/iphone_app/Classes/Goal.m new file mode 100644 index 0000000..1a15528 --- /dev/null +++ b/code/iphone_app/Classes/Goal.m @@ -0,0 +1,35 @@ +#import "Goal.h" + +#import "Credit.h" + +@implementation Goal + +@synthesize goalId; +@synthesize name; +@synthesize amount; +@synthesize updatedAt; +@synthesize createdAt; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goalId release]; + [name release]; + [amount release]; + [updatedAt release]; + [createdAt release]; + [super dealloc]; +} + +- (NSArray *)findAllRemoteCredits { + return [Credit findRemote:[NSString stringWithFormat:@"%@/%@", + goalId, @"credits"]]; +} + +- (NSString *)amountAsCurrency { + return [AppHelpers numberToCurrency:self.amount]; +} + +@end + diff --git a/code/iphone_app/Classes/GoalAddViewController.h b/code/iphone_app/Classes/GoalAddViewController.h new file mode 100644 index 0000000..94b3163 --- /dev/null +++ b/code/iphone_app/Classes/GoalAddViewController.h @@ -0,0 +1,18 @@ +@class Goal; + +@interface GoalAddViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; +} + +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; +@property (nonatomic, retain) Goal *goal; + +- (id)initWithGoal:(Goal *)goal; + +- (IBAction)save; +- (IBAction)cancel; + +@end diff --git a/code/iphone_app/Classes/GoalAddViewController.m b/code/iphone_app/Classes/GoalAddViewController.m new file mode 100644 index 0000000..7ae2519 --- /dev/null +++ b/code/iphone_app/Classes/GoalAddViewController.m @@ -0,0 +1,162 @@ +#import "GoalAddViewController.h" + +#import "Goal.h" +#import "ConnectionManager.h" + +@interface GoalAddViewController () +- (void)createRemoteGoal; +- (UITextField *)newNameField; +- (UITextField *)newAmountField; +@end + +@implementation GoalAddViewController + +@synthesize nameField; +@synthesize amountField; +@synthesize goal; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [nameField release]; + [amountField release]; + [goal release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Add Goal"; + self.tableView.allowsSelection = NO; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newNameField]; + [nameField becomeFirstResponder]; + + amountField = [self newAmountField]; + + UIBarButtonItem *cancelButton = [AppHelpers newCancelButton:self]; + self.navigationItem.leftBarButtonItem = cancelButton; + [cancelButton release]; + + UIBarButtonItem *saveButton = [AppHelpers newSaveButton:self]; + self.navigationItem.rightBarButtonItem = saveButton; + saveButton.enabled = NO; + [saveButton release]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)save { + goal.name = nameField.text; + goal.amount = amountField.text; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(createRemoteGoal) + onTarget:self]; +} + +- (IBAction)cancel { + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Editing + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + if (textField == nameField) { + [amountField becomeFirstResponder]; + } + if (textField == amountField) { + [self save]; + } + return YES; +} + +- (IBAction)textFieldChanged:(id)sender { + BOOL enableSaveButton = + ([self.nameField.text length] > 0) && ([self.amountField.text length] > 0); + [self.navigationItem.rightBarButtonItem setEnabled:enableSaveButton]; +} + +#pragma mark - +#pragma mark Table view methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return 2; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = + [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil] autorelease]; + + if (indexPath.row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } + + return cell; +} + +#pragma mark - +#pragma mark Private methods + +- (void)createRemoteGoal { + NSError *error = nil; + BOOL created = [goal createRemoteWithResponse:&error]; + if (created == YES) { + [self.navigationController performSelectorOnMainThread:@selector(popViewControllerAnimated:) + withObject:[NSNumber numberWithBool:YES] + waitUntilDone:NO]; + } else { + [AppHelpers handleRemoteError:error]; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (UITextField *)newNameField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Name"; + field.keyboardType = UIKeyboardTypeASCIICapable; + field.returnKeyType = UIReturnKeyNext; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +- (UITextField *)newAmountField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.placeholder = @"Amount"; + field.keyboardType = UIKeyboardTypeNumberPad; + [field addTarget:self + action:@selector(textFieldChanged:) + forControlEvents:UIControlEventEditingChanged]; + return field; +} + +@end diff --git a/code/iphone_app/Classes/GoalDetailViewController.h b/code/iphone_app/Classes/GoalDetailViewController.h new file mode 100644 index 0000000..8e385f1 --- /dev/null +++ b/code/iphone_app/Classes/GoalDetailViewController.h @@ -0,0 +1,17 @@ +@class Goal; + +@interface GoalDetailViewController : UITableViewController { + UITextField *nameField; + UITextField *amountField; + Goal *goal; + NSMutableArray *credits; +} + +@property (nonatomic, retain) Goal *goal; +@property (nonatomic, retain) NSMutableArray *credits; +@property (nonatomic, retain) UITextField *nameField; +@property (nonatomic, retain) UITextField *amountField; + +- (id)initWithGoal:(Goal *)goal; + +@end diff --git a/code/iphone_app/Classes/GoalDetailViewController.m b/code/iphone_app/Classes/GoalDetailViewController.m new file mode 100644 index 0000000..e06fac9 --- /dev/null +++ b/code/iphone_app/Classes/GoalDetailViewController.m @@ -0,0 +1,362 @@ +#import "GoalDetailViewController.h" + +#import "Goal.h" +#import "Credit.h" +#import "ConnectionManager.h" +#import "CreditDetailViewController.h" + +@interface GoalDetailViewController () +- (void)fetchRemoteCredits; +- (void)updateRemoteGoal; +- (void)deleteRowsAtIndexPaths:(NSArray *)array; +- (void)destroyRemoteCreditAtIndexPath:(NSIndexPath *)indexPath; +- (void)showCredit:(Credit *)credit; +- (UITableViewCell *)makeCreditCell:(UITableView *)tableView forRow:(NSUInteger)row; +- (UITableViewCell *)makeAddCreditCell:(UITableView *)tableView forRow:(NSUInteger)row; +- (UITableViewCell *)makeGoalCell:(UITableView *)tableView forRow:(NSUInteger)row; +- (UITextField *)newNameField; +- (UITextField *)newAmountField; +@end + +@implementation GoalDetailViewController + +@synthesize nameField; +@synthesize amountField; +@synthesize goal; +@synthesize credits; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [nameField release]; + [amountField release]; + [goal release]; + [credits release]; + [super dealloc]; +} + +enum GoalDetailTableSections { + kGoalSection = 0, + kCreditsSection +}; + +#pragma mark - +#pragma mark View lifecycle + +- (id)initWithGoal:(Goal *)aGoal { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + self.goal = aGoal; + } + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = goal.name; + self.tableView.allowsSelectionDuringEditing = YES; + self.tableView.backgroundColor = TABLE_BACKGROUND_COLOR; + + nameField = [self newNameField]; + amountField = [self newAmountField]; + + self.navigationItem.rightBarButtonItem = self.editButtonItem; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(fetchRemoteCredits) + onTarget:self]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); +} + +#pragma mark - +#pragma mark Editing + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + [super setEditing:editing animated:animated]; + + [self.navigationItem setHidesBackButton:editing animated:YES]; + + nameField.enabled = editing; + amountField.enabled = editing; + + [self.tableView beginUpdates]; + + NSUInteger creditsCount = [credits count]; + + NSArray *creditsInsertIndexPath = + [NSArray arrayWithObject:[NSIndexPath indexPathForRow:creditsCount + inSection:kCreditsSection]]; + + if (editing) { + amountField.text = goal.amount; + + [self.tableView insertRowsAtIndexPaths:creditsInsertIndexPath + withRowAnimation:UITableViewRowAnimationTop]; + } else { + amountField.text = [goal amountAsCurrency]; + + [self.tableView deleteRowsAtIndexPaths:creditsInsertIndexPath + withRowAnimation:UITableViewRowAnimationTop]; + } + + [self.tableView endUpdates]; + + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(updateRemoteGoal) + onTarget:self]; +} + +- (BOOL)textFieldShouldEndEditing:(UITextField *)textField { + if (textField == nameField) { + goal.name = nameField.text; + self.title = goal.name; + } else if (textField == amountField) { + goal.amount = amountField.text; + } + return YES; +} + +#pragma mark - +#pragma mark Table methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSInteger rows = 0; + switch (section) { + case kGoalSection: + rows = 2; + break; + case kCreditsSection: + rows = [credits count]; + if (self.editing) { + rows++; // "Add Credit" cell + } + break; + default: + break; + } + return rows; +} + +- (NSString *)tableView:(UITableView *)tableView +titleForHeaderInSection:(NSInteger)section { + NSString *title = nil; + switch (section) { + case kGoalSection: + title = @"Goal"; + break; + case kCreditsSection: + title = @"Credits"; + break; + } + return title; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + UITableViewCell *cell = nil; + + NSUInteger row = indexPath.row; + + // For the Credits section, create a cell for each credit. + if (indexPath.section == kCreditsSection) { + NSUInteger creditsCount = [credits count]; + if (row < creditsCount) { + cell = [self makeCreditCell:tableView forRow:row]; + } + // If the row is outside the range of the credits, it's + // the row that was added to allow insertion. + else { + cell = [self makeAddCreditCell:tableView forRow:row]; + } + } + // For the Goal section, create a cell for each text field. + else { + cell = [self makeGoalCell:tableView forRow:row]; + } + + return cell; +} + +- (NSIndexPath *)tableView:(UITableView *)tableView + willSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.isEditing && (indexPath.section == kCreditsSection)) { + return indexPath; + } + return nil; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.section == kCreditsSection) { + Credit *credit = nil; + if (indexPath.row < [credits count]) { + credit = [credits objectAtIndex:indexPath.row]; + } else { + credit = [[[Credit alloc] init] autorelease]; + credit.goalId = goal.goalId; + } + [self showCredit:credit]; + } +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCellEditingStyle style = UITableViewCellEditingStyleNone; + // Only allow editing in the Credits section. The last row + // was added automatically for adding a new credit. All + // other rows are eligible for deletion. + if (indexPath.section == kCreditsSection) { + if (indexPath.row == [credits count]) { + style = UITableViewCellEditingStyleInsert; + } else { + style = UITableViewCellEditingStyleDelete; + } + } + return style; +} + + - (void)tableView:(UITableView *)tableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + // Only allow deletion in the Credits section. + if ((editingStyle == UITableViewCellEditingStyleDelete) && + (indexPath.section == kCreditsSection)) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(destroyRemoteCreditAtIndexPath:) + onTarget:self + withArgument:indexPath]; + } +} + +- (void)destroyRemoteCreditAtIndexPath:(NSIndexPath *)indexPath { + Credit *credit = [credits objectAtIndex:indexPath.row]; + NSError *error = nil; + BOOL destroyed = [credit destroyRemoteWithResponse:&error]; + if (destroyed == YES) { + [credits removeObjectAtIndex:indexPath.row]; + [self performSelectorOnMainThread:@selector(deleteRowsAtIndexPaths:) + withObject:[NSArray arrayWithObject:indexPath] + waitUntilDone:NO]; + } else { + [AppHelpers handleRemoteError:error]; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)deleteRowsAtIndexPaths:(NSArray *)array { + [self.tableView deleteRowsAtIndexPaths:array + withRowAnimation:UITableViewRowAnimationTop]; +} + +#pragma mark - +#pragma mark Private methods + +- (void)fetchRemoteCredits { + self.credits = [NSMutableArray arrayWithArray:[goal findAllRemoteCredits]]; + + [self.tableView performSelectorOnMainThread:@selector(reloadData) + withObject:nil + waitUntilDone:NO]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)updateRemoteGoal { + NSError *error = nil; + BOOL updated = [goal updateRemoteWithResponse:&error]; + if (updated == NO) { + [AppHelpers handleRemoteError:error]; + } + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)showCredit:(Credit *)credit { + CreditDetailViewController *controller = + [[CreditDetailViewController alloc] initWithCredit:credit]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +- (UITableViewCell *)makeCreditCell:(UITableView *)tableView forRow:(NSUInteger)row { + static NSString *CreditCellId = @"CreditCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:CreditCellId]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:CreditCellId] autorelease]; + cell.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + + Credit *credit = [credits objectAtIndex:row]; + + cell.textLabel.text = credit.name; + cell.detailTextLabel.text = [credit amountAsCurrency]; + + return cell; +} + +- (UITableViewCell *)makeAddCreditCell:(UITableView *)tableView forRow:(NSUInteger)row { + static NSString *AddCreditCellId = @"AddCreditCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:AddCreditCellId]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:AddCreditCellId] autorelease]; + cell.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + + cell.textLabel.text = @"Add Credit"; + + return cell; +} + +- (UITableViewCell *)makeGoalCell:(UITableView *)tableView forRow:(NSUInteger)row { + static NSString *GoalCellId = @"GoalCellId"; + + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:GoalCellId]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:GoalCellId] autorelease]; + } + + if (row == 0) { + [cell.contentView addSubview:nameField]; + } else { + [cell.contentView addSubview:amountField]; + } + + return cell; +} + +- (UITextField *)newNameField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.text = goal.name; + field.keyboardType = UIKeyboardTypeASCIICapable; + field.enabled = NO; + return field; +} + +- (UITextField *)newAmountField { + UITextField *field = [AppHelpers newTableCellTextField:self]; + field.text = [goal amountAsCurrency]; + field.keyboardType = UIKeyboardTypeNumberPad; + field.enabled = NO; + return field; +} + +@end diff --git a/code/iphone_app/Classes/GoalsViewController.h b/code/iphone_app/Classes/GoalsViewController.h new file mode 100644 index 0000000..61c2ea2 --- /dev/null +++ b/code/iphone_app/Classes/GoalsViewController.h @@ -0,0 +1,12 @@ +@interface GoalsViewController : UIViewController { + NSMutableArray *goals; + UITableView *tableView; +} + +@property (nonatomic, retain) NSArray *goals; +@property (nonatomic, retain) IBOutlet UITableView *tableView; + +- (IBAction)add; +- (IBAction)refresh; + +@end diff --git a/code/iphone_app/Classes/GoalsViewController.m b/code/iphone_app/Classes/GoalsViewController.m new file mode 100644 index 0000000..d84742a --- /dev/null +++ b/code/iphone_app/Classes/GoalsViewController.m @@ -0,0 +1,178 @@ +#import "GoalsViewController.h" + +#import "Goal.h" +#import "ConnectionManager.h" +#import "GoalAddViewController.h" +#import "GoalDetailViewController.h" + +@interface GoalsViewController () +- (void)fetchRemoteGoals; +- (UIBarButtonItem *)newAddButton; +- (void)showGoal:(Goal *)goal; +- (void)deleteRowsAtIndexPaths:(NSArray *)array; +- (void)destroyRemoteGoalAtIndexPath:(NSIndexPath *)indexPath; +@end + +@implementation GoalsViewController + +@synthesize goals; +@synthesize tableView; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [goals release]; + [tableView release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Actions + +- (IBAction)add { + Goal *goal = [[Goal alloc] init]; + GoalAddViewController *controller = + [[GoalAddViewController alloc] initWithGoal:goal]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; + [goal release]; +} + +- (IBAction)refresh { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(fetchRemoteGoals) + onTarget:self]; +} + +#pragma mark - +#pragma mark View lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.title = @"Goals"; + + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [self newAddButton]; + self.navigationItem.rightBarButtonItem = addButton; + [addButton release]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self refresh]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); +} + +#pragma mark - +#pragma mark Editing + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + [super setEditing:editing animated:animated]; + [self.tableView setEditing:editing animated:animated]; +} + +#pragma mark - +#pragma mark Table data source methods + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return [goals count]; +} + +- (UITableViewCell *)tableView:(UITableView *)aTableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *GoalCellId = @"GoalCellId"; + + UITableViewCell *cell = + [aTableView dequeueReusableCellWithIdentifier:GoalCellId]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 + reuseIdentifier:GoalCellId] autorelease]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + + Goal *goal = [goals objectAtIndex:indexPath.row]; + + cell.textLabel.text = goal.name; + cell.detailTextLabel.text = [goal amountAsCurrency]; + + return cell; +} + +- (void)tableView:(UITableView *)aTableView +commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + [aTableView beginUpdates]; + if (editingStyle == UITableViewCellEditingStyleDelete) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + [[ConnectionManager sharedInstance] runJob:@selector(destroyRemoteGoalAtIndexPath:) + onTarget:self + withArgument:indexPath]; + } + [aTableView endUpdates]; +} + +- (void)destroyRemoteGoalAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + NSError *error = nil; + BOOL destroyed = [goal destroyRemoteWithResponse:&error]; + if (destroyed == YES) { + [goals removeObjectAtIndex:indexPath.row]; + [self performSelectorOnMainThread:@selector(deleteRowsAtIndexPaths:) + withObject:[NSArray arrayWithObject:indexPath] + waitUntilDone:NO]; + } else { + [AppHelpers handleRemoteError:error]; + } + + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)deleteRowsAtIndexPaths:(NSArray *)array { + [tableView deleteRowsAtIndexPaths:array + withRowAnimation:UITableViewRowAnimationFade]; +} + +- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + Goal *goal = [goals objectAtIndex:indexPath.row]; + [self showGoal:goal]; +} + +#pragma mark - +#pragma mark Private methods + +- (void)fetchRemoteGoals { + NSError *error = nil; + self.goals = [Goal findAllRemoteWithResponse:&error]; + if (self.goals == nil && error != nil) { + [AppHelpers handleRemoteError:error]; + } + + [self.tableView performSelectorOnMainThread:@selector(reloadData) + withObject:nil + waitUntilDone:NO]; + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +} + +- (void)showGoal:(Goal *)goal { + GoalDetailViewController *controller = + [[GoalDetailViewController alloc] initWithGoal:goal]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +- (UIBarButtonItem *)newAddButton { + return [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(add)]; +} + +@end diff --git a/code/iphone_app/Classes/SaveUpAppDelegate.h b/code/iphone_app/Classes/SaveUpAppDelegate.h new file mode 100644 index 0000000..41a9c36 --- /dev/null +++ b/code/iphone_app/Classes/SaveUpAppDelegate.h @@ -0,0 +1,14 @@ +@class User; + +@interface SaveUpAppDelegate : NSObject { + UIWindow *window; + UINavigationController *navigationController; + User *user; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet UINavigationController *navigationController; +@property (nonatomic, retain) User *user; + +@end + diff --git a/code/iphone_app/Classes/SaveUpAppDelegate.m b/code/iphone_app/Classes/SaveUpAppDelegate.m new file mode 100644 index 0000000..a55c48a --- /dev/null +++ b/code/iphone_app/Classes/SaveUpAppDelegate.m @@ -0,0 +1,97 @@ +#import "SaveUpAppDelegate.h" + +#import "User.h" +#import "AuthenticationViewController.h" +#import "ObjectiveResourceConfig.h" + +@interface SaveUpAppDelegate () +- (void)configureObjectiveResource; +- (void)login; +- (void)showAuthentication:(User *)user; +@end + +@implementation SaveUpAppDelegate + +@synthesize window; +@synthesize navigationController; +@synthesize user; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [navigationController release]; + [window release]; + [user removeObserver:self]; + [user release]; + [super dealloc]; +} + +#pragma mark - +#pragma mark Application lifecycle + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + [window addSubview:[navigationController view]]; + [window makeKeyAndVisible]; + + [self configureObjectiveResource]; + [self login]; +} + +- (void)configureObjectiveResource { +#if TARGET_IPHONE_SIMULATOR + [ObjectiveResourceConfig setSite:@"http://localhost:3000/"]; +#else + [ObjectiveResourceConfig setSite:@"https://saveup.heroku.com/"]; +#endif + [ObjectiveResourceConfig setResponseType:JSONResponse]; + [ObjectiveResourceConfig setUser:[self.user login]]; + [ObjectiveResourceConfig setPassword:[self.user password]]; +} + +#pragma mark - +#pragma mark Authentication + +- (User *)user { + if (user == nil) { + NSURL *url = [NSURL URLWithString:[ObjectiveResourceConfig getSite]]; + self.user = [User currentUserForSite:url]; + [user addObserver:self]; + } + return user; +} + +- (void)showAuthentication:(User *)aUser { + AuthenticationViewController *controller = + [[AuthenticationViewController alloc] initWithCurrentUser:aUser]; + [self.navigationController pushViewController:controller animated:YES]; + [controller release]; +} + +- (void)login { + if ([self.user hasCredentials]) { + NSError *error = nil; + BOOL authenticated = [self.user authenticate:&error]; + if (authenticated == NO) { + [AppHelpers handleRemoteError:error]; + [self showAuthentication:self.user]; + } + } else { + [self showAuthentication:self.user]; + } +} + +#pragma mark - +#pragma mark Key-value observing + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object change:(NSDictionary *)change + context:(void *)context { + if ([keyPath isEqualToString:kUserLoginKey]) { + [ObjectiveResourceConfig setUser:[object valueForKeyPath:keyPath]]; + } else if ([keyPath isEqualToString:kUserPasswordKey]){ + [ObjectiveResourceConfig setPassword:[object valueForKeyPath:keyPath]]; + } +} + +@end \ No newline at end of file diff --git a/code/iphone_app/Classes/Session.h b/code/iphone_app/Classes/Session.h new file mode 100644 index 0000000..39ee759 --- /dev/null +++ b/code/iphone_app/Classes/Session.h @@ -0,0 +1,11 @@ +#import "ObjectiveResource.h" + +@interface Session : NSObject { + NSString *login; + NSString *password; +} + +@property (nonatomic, copy) NSString *login; +@property (nonatomic, copy) NSString *password; + +@end diff --git a/code/iphone_app/Classes/Session.m b/code/iphone_app/Classes/Session.m new file mode 100644 index 0000000..4e5c8b2 --- /dev/null +++ b/code/iphone_app/Classes/Session.m @@ -0,0 +1,14 @@ +#import "Session.h" + +@implementation Session + +@synthesize login; +@synthesize password; + +- (void)dealloc { + [login release]; + [password release]; + [super dealloc]; +} + +@end diff --git a/code/iphone_app/Classes/User.h b/code/iphone_app/Classes/User.h new file mode 100644 index 0000000..2837617 --- /dev/null +++ b/code/iphone_app/Classes/User.h @@ -0,0 +1,24 @@ +#import + +#define kUserLoginKey @"login" +#define kUserPasswordKey @"password" + +@interface User : NSObject { + NSString *login; + NSString *password; + NSURL *siteURL; +} + +@property (nonatomic, copy) NSString *login; +@property (nonatomic, copy) NSString *password; +@property (nonatomic, retain) NSURL *siteURL; + ++ (User *)currentUserForSite:(NSURL *)siteURL; + +- (BOOL)hasCredentials; +- (BOOL)authenticate:(NSError **)error; +- (void)saveCredentialsToKeychain; +- (void)addObserver:(id)observer; +- (void)removeObserver:(id)observer; + +@end diff --git a/code/iphone_app/Classes/User.m b/code/iphone_app/Classes/User.m new file mode 100644 index 0000000..2d08714 --- /dev/null +++ b/code/iphone_app/Classes/User.m @@ -0,0 +1,99 @@ +#import "User.h" + +#import "Session.h" + +@interface User () +- (void)loadCredentialsFromKeychain; +- (NSURLProtectionSpace *)protectionSpace; +@end + +@implementation User + +@synthesize login; +@synthesize password; +@synthesize siteURL; + +#pragma mark - +#pragma mark Memory management + +- (void)dealloc { + [login release]; + [password release]; + [siteURL release]; + [super dealloc]; +} + ++ (User *)currentUserForSite:(NSURL *)aSiteURL { + User *user = [[[self alloc] init] autorelease]; + user.siteURL = aSiteURL; + [user loadCredentialsFromKeychain]; + return user; +} + +- (BOOL)hasCredentials { + return (self.login != nil && self.password != nil); +} + +- (BOOL)authenticate:(NSError **)error { + if (![self hasCredentials]) { + return NO; + } + + Session *session = [[[Session alloc] init] autorelease]; + session.login = self.login; + session.password = self.password; + + return [session createRemoteWithResponse:error]; +} + +- (void)saveCredentialsToKeychain { + NSURLCredential *credentials = + [NSURLCredential credentialWithUser:self.login + password:self.password + persistence:NSURLCredentialPersistencePermanent]; + + [[NSURLCredentialStorage sharedCredentialStorage] + setCredential:credentials forProtectionSpace:[self protectionSpace]]; +} + +#pragma mark - +#pragma mark Key-value observing + +- (void)addObserver:(id)observer { + [self addObserver:observer forKeyPath:kUserLoginKey options:NSKeyValueObservingOptionNew context:nil]; + [self addObserver:observer forKeyPath:kUserPasswordKey options:NSKeyValueObservingOptionNew context:nil]; +} + +- (void)removeObserver:(id)observer { + [self removeObserver:observer forKeyPath:kUserLoginKey]; + [self removeObserver:observer forKeyPath:kUserPasswordKey]; +} + +#pragma mark - +#pragma mark Private methods + +- (void)loadCredentialsFromKeychain { + NSDictionary *credentialInfo = + [[NSURLCredentialStorage sharedCredentialStorage] + credentialsForProtectionSpace:[self protectionSpace]]; + + // Assumes there's only one set of credentials, and since we + // don't have the username key in hand, we pull the first key. + NSArray *keys = [credentialInfo allKeys]; + if ([keys count] > 0) { + NSString *userNameKey = [[credentialInfo allKeys] objectAtIndex:0]; + NSURLCredential *credential = [credentialInfo valueForKey:userNameKey]; + self.login = credential.user; + self.password = credential.password; + } +} + +- (NSURLProtectionSpace *)protectionSpace { + return [[[NSURLProtectionSpace alloc] initWithHost:[siteURL host] + port:[[siteURL port] intValue] + protocol:[siteURL scheme] + realm:@"Web Password" + authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; +} + +@end diff --git a/code/iphone_app/GoalsView.xib b/code/iphone_app/GoalsView.xib new file mode 100644 index 0000000..416fe34 --- /dev/null +++ b/code/iphone_app/GoalsView.xib @@ -0,0 +1,334 @@ + + + + 528 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 292 + + YES + + + 274 + {320, 416} + + + 1 + MSAxIDEAA + + NO + YES + NO + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + 266 + {{0, 416}, {320, 44}} + + NO + NO + IBCocoaTouchFramework + 1 + + YES + + IBCocoaTouchFramework + + 13 + + + + + {320, 460} + + 3 + MQA + + 2 + + + NO + IBCocoaTouchFramework + + + + + YES + + + dataSource + + + + 11 + + + + delegate + + + + 12 + + + + view + + + + 14 + + + + refresh + + + + 18 + + + + tableView + + + + 19 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 13 + + + YES + + + + + + + 8 + + + + + 16 + + + YES + + + + + + 17 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 13.IBEditorWindowLastContentRect + 13.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 8.IBPluginDependency + + + YES + GoalsViewController + UIResponder + {{197, 607}, {320, 460}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 19 + + + + YES + + GoalsViewController + UIViewController + + YES + + YES + add + addCategory + refresh + + + YES + id + id + id + + + + YES + + YES + table + tableView + + + YES + UITableView + UITableView + + + + IBUserSource + + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + Budget.xcodeproj + 3 + 87 + + diff --git a/code/iphone_app/Info.plist b/code/iphone_app/Info.plist new file mode 100644 index 0000000..0fdd06f --- /dev/null +++ b/code/iphone_app/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + icon.png + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + + diff --git a/code/iphone_app/MainWindow.xib b/code/iphone_app/MainWindow.xib new file mode 100644 index 0000000..f270b39 --- /dev/null +++ b/code/iphone_app/MainWindow.xib @@ -0,0 +1,670 @@ + + + + 528 + 10D573 + 762 + 1038.29 + 460.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 87 + + + YES + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + + + 1316 + + YES + + + 1290 + {{0, 436}, {320, 44}} + + NO + NO + IBCocoaTouchFramework + + YES + + Item + IBCocoaTouchFramework + 1 + + + + + + + {320, 480} + + 1 + MSAxIDEAA + + NO + NO + + IBCocoaTouchFramework + + + + + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + 1 + + + YES + + + IBCocoaTouchFramework + + + GoalsView + + + 1 + + IBCocoaTouchFramework + NO + + + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + navigationController + + + + 15 + + + + + YES + + 0 + + + + + + 2 + + + YES + + + + + + -1 + + + File's Owner + + + 3 + + + + + -2 + + + + + 9 + + + YES + + + + + + + 11 + + + + + 13 + + + YES + + + + + + 14 + + + + + 16 + + + YES + + + + + + 17 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 11.IBPluginDependency + 13.CustomClassName + 13.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 9.IBEditorWindowLastContentRect + 9.IBPluginDependency + + + YES + UIApplication + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + GoalsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + + YES + + + {{471, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + SaveUpAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{500, 343}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 17 + + + + YES + + GoalsViewController + UIViewController + + YES + + YES + add + refresh + + + YES + id + id + + + + tableView + UITableView + + + IBProjectSource + Classes/GoalsViewController.h + + + + GoalsViewController + UIViewController + + addCategory + id + + + table + UITableView + + + IBUserSource + + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h + + + + NSObject + + IBProjectSource + Classes/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h + + + + SaveUpAppDelegate + NSObject + + YES + + YES + navigationController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Classes/SaveUpAppDelegate.h + + + + SaveUpAppDelegate + NSObject + + IBUserSource + + + + + + YES + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSError.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSFileManager.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueCoding.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyValueObserving.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSKeyedArchiver.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSNetServices.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSObject.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSPort.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSRunLoop.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSStream.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSThread.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURL.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSURLConnection.h + + + + NSObject + + IBFrameworkSource + Foundation.framework/Headers/NSXMLParser.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIAccessibility.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UINibLoading.h + + + + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIResponder.h + + + + UIApplication + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIApplication.h + + + + UIBarButtonItem + UIBarItem + + IBFrameworkSource + UIKit.framework/Headers/UIBarButtonItem.h + + + + UIBarItem + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UIBarItem.h + + + + UINavigationBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UINavigationBar.h + + + + UINavigationController + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UINavigationController.h + + + + UINavigationItem + NSObject + + + + UIResponder + NSObject + + + + UIScrollView + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIScrollView.h + + + + UISearchBar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UISearchBar.h + + + + UISearchDisplayController + NSObject + + IBFrameworkSource + UIKit.framework/Headers/UISearchDisplayController.h + + + + UITableView + UIScrollView + + IBFrameworkSource + UIKit.framework/Headers/UITableView.h + + + + UIToolbar + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIToolbar.h + + + + UIView + + IBFrameworkSource + UIKit.framework/Headers/UITextField.h + + + + UIView + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIView.h + + + + UIViewController + + + + UIViewController + + IBFrameworkSource + UIKit.framework/Headers/UITabBarController.h + + + + UIViewController + UIResponder + + IBFrameworkSource + UIKit.framework/Headers/UIViewController.h + + + + UIWindow + UIView + + IBFrameworkSource + UIKit.framework/Headers/UIWindow.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + Budget.xcodeproj + 3 + 87 + + diff --git a/code/iphone_app/README.md b/code/iphone_app/README.md new file mode 100644 index 0000000..aa95606 --- /dev/null +++ b/code/iphone_app/README.md @@ -0,0 +1,32 @@ +Save Up iPhone App +================== + +This is the final version of the iPhone app that interacts with a +REST service (a Rails app) to manage goals and their related credits. + +Features +-------- + +This app is intended for educational purposes only, but it sports a few features you may want to consider in your application: + +* Supports all CRUD operations of two resources (Goal and Credit) +* Nested resources +* Asynchronous network requests +* Authentication +* Error handling + +Quickstart +---------- + +1. Fire up the Rails application in the ../rails_app directory: + + $ rake db:migrate + $ rake db:seed + $ rails s + +2. Point your trusty browser at the [running Rails app](http://localhost:3000), and create an account and a goal. + +3. Open the iPhone project and run it! + + $ open SaveUp.xcodeproj + diff --git a/code/iphone_app/SaveUp.xcodeproj/project.pbxproj b/code/iphone_app/SaveUp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..a63b566 --- /dev/null +++ b/code/iphone_app/SaveUp.xcodeproj/project.pbxproj @@ -0,0 +1,676 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */; }; + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 276382E711A5B23A00D817D1 /* Connection.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829411A5B23A00D817D1 /* Connection.m */; }; + 276382E811A5B23A00D817D1 /* ConnectionDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829611A5B23A00D817D1 /* ConnectionDelegate.m */; }; + 276382E911A5B23A00D817D1 /* ConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829811A5B23A00D817D1 /* ConnectionManager.m */; }; + 276382EA11A5B23A00D817D1 /* NSHTTPURLResponse+Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829A11A5B23A00D817D1 /* NSHTTPURLResponse+Error.m */; }; + 276382EB11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829C11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.m */; }; + 276382EC11A5B23A00D817D1 /* NSObject+ObjectiveResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2763829E11A5B23A00D817D1 /* NSObject+ObjectiveResource.m */; }; + 276382ED11A5B23A00D817D1 /* NSString+InflectionSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382A611A5B23A00D817D1 /* NSString+InflectionSupport.m */; }; + 276382EE11A5B23A00D817D1 /* NSData+Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382A811A5B23A00D817D1 /* NSData+Additions.m */; }; + 276382EF11A5B23A00D817D1 /* NSObject+PropertySupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382AA11A5B23A00D817D1 /* NSObject+PropertySupport.m */; }; + 276382F011A5B23A00D817D1 /* NSString+GSub.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382AC11A5B23A00D817D1 /* NSString+GSub.m */; }; + 276382F111A5B23A00D817D1 /* ObjectiveResourceDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382AE11A5B23A00D817D1 /* ObjectiveResourceDateFormatter.m */; }; + 276382F211A5B23A00D817D1 /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382B311A5B23A00D817D1 /* NSObject+SBJSON.m */; }; + 276382F311A5B23A00D817D1 /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382B511A5B23A00D817D1 /* NSString+SBJSON.m */; }; + 276382F411A5B23A00D817D1 /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382B711A5B23A00D817D1 /* SBJSON.m */; }; + 276382F511A5B23A00D817D1 /* NSArray+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382BD11A5B23A00D817D1 /* NSArray+JSONSerializableSupport.m */; }; + 276382F611A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382BF11A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.m */; }; + 276382F711A5B23A00D817D1 /* NSObject+JSONSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382C111A5B23A00D817D1 /* NSObject+JSONSerializableSupport.m */; }; + 276382F811A5B23A00D817D1 /* NSDate+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382C311A5B23A00D817D1 /* NSDate+Serialize.m */; }; + 276382F911A5B23A00D817D1 /* NSDictionary+KeyTranslation.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382C511A5B23A00D817D1 /* NSDictionary+KeyTranslation.m */; }; + 276382FA11A5B23A00D817D1 /* NSObject+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382C711A5B23A00D817D1 /* NSObject+Serialize.m */; }; + 276382FB11A5B23A00D817D1 /* NSString+Serialize.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382C911A5B23A00D817D1 /* NSString+Serialize.m */; }; + 276382FC11A5B23A00D817D1 /* FromXMLElementDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382CD11A5B23A00D817D1 /* FromXMLElementDelegate.m */; }; + 276382FD11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382CF11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.m */; }; + 276382FE11A5B23A00D817D1 /* NSDate+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382D111A5B23A00D817D1 /* NSDate+XMLSerializableSupport.m */; }; + 276382FF11A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382D311A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.m */; }; + 2763830011A5B23A00D817D1 /* NSNull+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382D511A5B23A00D817D1 /* NSNull+XMLSerializableSupport.m */; }; + 2763830111A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382D711A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.m */; }; + 2763830211A5B23A00D817D1 /* NSObject+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382D911A5B23A00D817D1 /* NSObject+XMLSerializableSupport.m */; }; + 2763830311A5B23A00D817D1 /* NSString+XMLSerializableSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382DB11A5B23A00D817D1 /* NSString+XMLSerializableSupport.m */; }; + 2763830411A5B23A00D817D1 /* ObjectiveResourceConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382E011A5B23A00D817D1 /* ObjectiveResourceConfig.m */; }; + 2763830511A5B23A00D817D1 /* Response.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382E211A5B23A00D817D1 /* Response.m */; }; + 2763830611A5B23A00D817D1 /* NSError+Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 276382E411A5B23A00D817D1 /* NSError+Error.m */; }; + 2763830711A5B23A00D817D1 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 276382E511A5B23A00D817D1 /* LICENSE */; }; + 2763830811A5B23A00D817D1 /* README.textile in Resources */ = {isa = PBXBuildFile; fileRef = 276382E611A5B23A00D817D1 /* README.textile */; }; + 277AFEBB0FA1F16B003B28CF /* Goal.m in Sources */ = {isa = PBXBuildFile; fileRef = 277AFEB40FA1F16B003B28CF /* Goal.m */; }; + 277AFEBC0FA1F16B003B28CF /* GoalAddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 277AFEB70FA1F16B003B28CF /* GoalAddViewController.m */; }; + 277AFEBD0FA1F16B003B28CF /* GoalDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 277AFEB90FA1F16B003B28CF /* GoalDetailViewController.m */; }; + 277AFEBE0FA1F16B003B28CF /* GoalsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 277AFEBA0FA1F16B003B28CF /* GoalsViewController.m */; }; + 277AFF350FA1F44E003B28CF /* GoalsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 277AFF330FA1F44E003B28CF /* GoalsView.xib */; }; + 277AFF370FA1F541003B28CF /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 277AFF360FA1F541003B28CF /* icon.png */; }; + 2791004711922E0B005BC315 /* AppHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 2791004611922E0B005BC315 /* AppHelpers.m */; }; + 27A19F4A0FAA00DF00EA701E /* User.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A19F490FAA00DF00EA701E /* User.m */; }; + 27A19F680FAA049000EA701E /* AuthenticationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A19F670FAA049000EA701E /* AuthenticationViewController.m */; }; + 27A19F970FAA1DA900EA701E /* Session.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A19F960FAA1DA900EA701E /* Session.m */; }; + 27E81C6E1056F06800661701 /* Credit.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E81C6D1056F06800661701 /* Credit.m */; }; + 27E81C751056F0B000661701 /* CreditDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E81C741056F0B000661701 /* CreditDetailViewController.m */; }; + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */; }; + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUpAppDelegate.h; sourceTree = ""; }; + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaveUpAppDelegate.m; sourceTree = ""; }; + 1D6058910D05DD3D006BFB54 /* SaveUp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SaveUp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 2763829311A5B23A00D817D1 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = ""; }; + 2763829411A5B23A00D817D1 /* Connection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Connection.m; sourceTree = ""; }; + 2763829511A5B23A00D817D1 /* ConnectionDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionDelegate.h; sourceTree = ""; }; + 2763829611A5B23A00D817D1 /* ConnectionDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConnectionDelegate.m; sourceTree = ""; }; + 2763829711A5B23A00D817D1 /* ConnectionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionManager.h; sourceTree = ""; }; + 2763829811A5B23A00D817D1 /* ConnectionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConnectionManager.m; sourceTree = ""; }; + 2763829911A5B23A00D817D1 /* NSHTTPURLResponse+Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSHTTPURLResponse+Error.h"; sourceTree = ""; }; + 2763829A11A5B23A00D817D1 /* NSHTTPURLResponse+Error.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPURLResponse+Error.m"; sourceTree = ""; }; + 2763829B11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableURLRequest+ResponseType.h"; sourceTree = ""; }; + 2763829C11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableURLRequest+ResponseType.m"; sourceTree = ""; }; + 2763829D11A5B23A00D817D1 /* NSObject+ObjectiveResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+ObjectiveResource.h"; sourceTree = ""; }; + 2763829E11A5B23A00D817D1 /* NSObject+ObjectiveResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+ObjectiveResource.m"; sourceTree = ""; }; + 276382A311A5B23A00D817D1 /* CoreSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreSupport.h; sourceTree = ""; }; + 276382A511A5B23A00D817D1 /* NSString+InflectionSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+InflectionSupport.h"; sourceTree = ""; }; + 276382A611A5B23A00D817D1 /* NSString+InflectionSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+InflectionSupport.m"; sourceTree = ""; }; + 276382A711A5B23A00D817D1 /* NSData+Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Additions.h"; sourceTree = ""; }; + 276382A811A5B23A00D817D1 /* NSData+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Additions.m"; sourceTree = ""; }; + 276382A911A5B23A00D817D1 /* NSObject+PropertySupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PropertySupport.h"; sourceTree = ""; }; + 276382AA11A5B23A00D817D1 /* NSObject+PropertySupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PropertySupport.m"; sourceTree = ""; }; + 276382AB11A5B23A00D817D1 /* NSString+GSub.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+GSub.h"; sourceTree = ""; }; + 276382AC11A5B23A00D817D1 /* NSString+GSub.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+GSub.m"; sourceTree = ""; }; + 276382AD11A5B23A00D817D1 /* ObjectiveResourceDateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResourceDateFormatter.h; sourceTree = ""; }; + 276382AE11A5B23A00D817D1 /* ObjectiveResourceDateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectiveResourceDateFormatter.m; sourceTree = ""; }; + 276382AF11A5B23A00D817D1 /* ObjectiveSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveSupport.h; sourceTree = ""; }; + 276382B111A5B23A00D817D1 /* JSONFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONFramework.h; sourceTree = ""; }; + 276382B211A5B23A00D817D1 /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 276382B311A5B23A00D817D1 /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 276382B411A5B23A00D817D1 /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 276382B511A5B23A00D817D1 /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 276382B611A5B23A00D817D1 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 276382B711A5B23A00D817D1 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 276382BA11A5B23A00D817D1 /* JSONSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONSerializable.h; sourceTree = ""; }; + 276382BB11A5B23A00D817D1 /* JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONSerializableSupport.h; sourceTree = ""; }; + 276382BC11A5B23A00D817D1 /* NSArray+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+JSONSerializableSupport.h"; sourceTree = ""; }; + 276382BD11A5B23A00D817D1 /* NSArray+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+JSONSerializableSupport.m"; sourceTree = ""; }; + 276382BE11A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+JSONSerializableSupport.h"; sourceTree = ""; }; + 276382BF11A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+JSONSerializableSupport.m"; sourceTree = ""; }; + 276382C011A5B23A00D817D1 /* NSObject+JSONSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+JSONSerializableSupport.h"; sourceTree = ""; }; + 276382C111A5B23A00D817D1 /* NSObject+JSONSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+JSONSerializableSupport.m"; sourceTree = ""; }; + 276382C211A5B23A00D817D1 /* NSDate+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Serialize.h"; sourceTree = ""; }; + 276382C311A5B23A00D817D1 /* NSDate+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Serialize.m"; sourceTree = ""; }; + 276382C411A5B23A00D817D1 /* NSDictionary+KeyTranslation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+KeyTranslation.h"; sourceTree = ""; }; + 276382C511A5B23A00D817D1 /* NSDictionary+KeyTranslation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+KeyTranslation.m"; sourceTree = ""; }; + 276382C611A5B23A00D817D1 /* NSObject+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Serialize.h"; sourceTree = ""; }; + 276382C711A5B23A00D817D1 /* NSObject+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Serialize.m"; sourceTree = ""; }; + 276382C811A5B23A00D817D1 /* NSString+Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Serialize.h"; sourceTree = ""; }; + 276382C911A5B23A00D817D1 /* NSString+Serialize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Serialize.m"; sourceTree = ""; }; + 276382CA11A5B23A00D817D1 /* Serialize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Serialize.h; sourceTree = ""; }; + 276382CC11A5B23A00D817D1 /* FromXMLElementDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FromXMLElementDelegate.h; sourceTree = ""; }; + 276382CD11A5B23A00D817D1 /* FromXMLElementDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FromXMLElementDelegate.m; sourceTree = ""; }; + 276382CE11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382CF11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382D011A5B23A00D817D1 /* NSDate+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382D111A5B23A00D817D1 /* NSDate+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382D211A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382D311A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382D411A5B23A00D817D1 /* NSNull+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNull+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382D511A5B23A00D817D1 /* NSNull+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNull+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382D611A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNumber+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382D711A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNumber+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382D811A5B23A00D817D1 /* NSObject+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382D911A5B23A00D817D1 /* NSObject+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382DA11A5B23A00D817D1 /* NSString+XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XMLSerializableSupport.h"; sourceTree = ""; }; + 276382DB11A5B23A00D817D1 /* NSString+XMLSerializableSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XMLSerializableSupport.m"; sourceTree = ""; }; + 276382DC11A5B23A00D817D1 /* XMLSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLSerializable.h; sourceTree = ""; }; + 276382DD11A5B23A00D817D1 /* XMLSerializableSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLSerializableSupport.h; sourceTree = ""; }; + 276382DE11A5B23A00D817D1 /* ObjectiveResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResource.h; sourceTree = ""; }; + 276382DF11A5B23A00D817D1 /* ObjectiveResourceConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveResourceConfig.h; sourceTree = ""; }; + 276382E011A5B23A00D817D1 /* ObjectiveResourceConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectiveResourceConfig.m; sourceTree = ""; }; + 276382E111A5B23A00D817D1 /* Response.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Response.h; sourceTree = ""; }; + 276382E211A5B23A00D817D1 /* Response.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Response.m; sourceTree = ""; }; + 276382E311A5B23A00D817D1 /* NSError+Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+Error.h"; sourceTree = ""; }; + 276382E411A5B23A00D817D1 /* NSError+Error.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+Error.m"; sourceTree = ""; }; + 276382E511A5B23A00D817D1 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 276382E611A5B23A00D817D1 /* README.textile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.textile; sourceTree = ""; }; + 277AFEB30FA1F16B003B28CF /* Goal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Goal.h; sourceTree = ""; }; + 277AFEB40FA1F16B003B28CF /* Goal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Goal.m; sourceTree = ""; }; + 277AFEB50FA1F16B003B28CF /* GoalsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalsViewController.h; sourceTree = ""; }; + 277AFEB60FA1F16B003B28CF /* GoalAddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalAddViewController.h; sourceTree = ""; }; + 277AFEB70FA1F16B003B28CF /* GoalAddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalAddViewController.m; sourceTree = ""; }; + 277AFEB80FA1F16B003B28CF /* GoalDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoalDetailViewController.h; sourceTree = ""; }; + 277AFEB90FA1F16B003B28CF /* GoalDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalDetailViewController.m; sourceTree = ""; }; + 277AFEBA0FA1F16B003B28CF /* GoalsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoalsViewController.m; sourceTree = ""; }; + 277AFF330FA1F44E003B28CF /* GoalsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GoalsView.xib; sourceTree = ""; }; + 277AFF360FA1F541003B28CF /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; + 2791004511922E0B005BC315 /* AppHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppHelpers.h; path = Classes/AppHelpers.h; sourceTree = ""; }; + 2791004611922E0B005BC315 /* AppHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppHelpers.m; path = Classes/AppHelpers.m; sourceTree = ""; }; + 27A19F480FAA00DF00EA701E /* User.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = User.h; sourceTree = ""; }; + 27A19F490FAA00DF00EA701E /* User.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = User.m; sourceTree = ""; }; + 27A19F660FAA048900EA701E /* AuthenticationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthenticationViewController.h; sourceTree = ""; }; + 27A19F670FAA049000EA701E /* AuthenticationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthenticationViewController.m; sourceTree = ""; }; + 27A19F950FAA1DA900EA701E /* Session.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Session.h; sourceTree = ""; }; + 27A19F960FAA1DA900EA701E /* Session.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Session.m; sourceTree = ""; }; + 27E81C6C1056F06800661701 /* Credit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Credit.h; sourceTree = ""; }; + 27E81C6D1056F06800661701 /* Credit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Credit.m; sourceTree = ""; }; + 27E81C731056F0B000661701 /* CreditDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CreditDetailViewController.h; sourceTree = ""; }; + 27E81C741056F0B000661701 /* CreditDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CreditDetailViewController.m; sourceTree = ""; }; + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaveUp_Prefix.pch; sourceTree = ""; }; + 28AD735F0D9D9599002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 2892E4100DC94CBA00A64D0F /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 27E81BB11056E30600661701 /* Helpers */, + 2708D1A51059723A001A1E75 /* Authentication */, + 2754D8C410595BFB00365725 /* Goals */, + 2754D8C310595BF500365725 /* Credits */, + 1D3623240D0F684500981E51 /* SaveUpAppDelegate.h */, + 1D3623250D0F684500981E51 /* SaveUpAppDelegate.m */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* SaveUp.app */, + ); + name = Products; + sourceTree = ""; + }; + 2708D1A51059723A001A1E75 /* Authentication */ = { + isa = PBXGroup; + children = ( + 27A19F480FAA00DF00EA701E /* User.h */, + 27A19F490FAA00DF00EA701E /* User.m */, + 27A19F950FAA1DA900EA701E /* Session.h */, + 27A19F960FAA1DA900EA701E /* Session.m */, + 27A19F660FAA048900EA701E /* AuthenticationViewController.h */, + 27A19F670FAA049000EA701E /* AuthenticationViewController.m */, + ); + name = Authentication; + sourceTree = ""; + }; + 2754D8C310595BF500365725 /* Credits */ = { + isa = PBXGroup; + children = ( + 27E81C6C1056F06800661701 /* Credit.h */, + 27E81C6D1056F06800661701 /* Credit.m */, + 27E81C731056F0B000661701 /* CreditDetailViewController.h */, + 27E81C741056F0B000661701 /* CreditDetailViewController.m */, + ); + name = Credits; + sourceTree = ""; + }; + 2754D8C410595BFB00365725 /* Goals */ = { + isa = PBXGroup; + children = ( + 277AFEB30FA1F16B003B28CF /* Goal.h */, + 277AFEB40FA1F16B003B28CF /* Goal.m */, + 277AFEB50FA1F16B003B28CF /* GoalsViewController.h */, + 277AFEBA0FA1F16B003B28CF /* GoalsViewController.m */, + 277AFEB60FA1F16B003B28CF /* GoalAddViewController.h */, + 277AFEB70FA1F16B003B28CF /* GoalAddViewController.m */, + 277AFEB80FA1F16B003B28CF /* GoalDetailViewController.h */, + 277AFEB90FA1F16B003B28CF /* GoalDetailViewController.m */, + ); + name = Goals; + sourceTree = ""; + }; + 2763829011A5B23A00D817D1 /* objectiveresource */ = { + isa = PBXGroup; + children = ( + 2763829111A5B23A00D817D1 /* Classes */, + 276382E511A5B23A00D817D1 /* LICENSE */, + 276382E611A5B23A00D817D1 /* README.textile */, + ); + path = objectiveresource; + sourceTree = ""; + }; + 2763829111A5B23A00D817D1 /* Classes */ = { + isa = PBXGroup; + children = ( + 2763829211A5B23A00D817D1 /* lib */, + 276382E311A5B23A00D817D1 /* NSError+Error.h */, + 276382E411A5B23A00D817D1 /* NSError+Error.m */, + ); + path = Classes; + sourceTree = ""; + }; + 2763829211A5B23A00D817D1 /* lib */ = { + isa = PBXGroup; + children = ( + 2763829311A5B23A00D817D1 /* Connection.h */, + 2763829411A5B23A00D817D1 /* Connection.m */, + 2763829511A5B23A00D817D1 /* ConnectionDelegate.h */, + 2763829611A5B23A00D817D1 /* ConnectionDelegate.m */, + 2763829711A5B23A00D817D1 /* ConnectionManager.h */, + 2763829811A5B23A00D817D1 /* ConnectionManager.m */, + 2763829911A5B23A00D817D1 /* NSHTTPURLResponse+Error.h */, + 2763829A11A5B23A00D817D1 /* NSHTTPURLResponse+Error.m */, + 2763829B11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.h */, + 2763829C11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.m */, + 2763829D11A5B23A00D817D1 /* NSObject+ObjectiveResource.h */, + 2763829E11A5B23A00D817D1 /* NSObject+ObjectiveResource.m */, + 2763829F11A5B23A00D817D1 /* objective_support */, + 276382DE11A5B23A00D817D1 /* ObjectiveResource.h */, + 276382DF11A5B23A00D817D1 /* ObjectiveResourceConfig.h */, + 276382E011A5B23A00D817D1 /* ObjectiveResourceConfig.m */, + 276382E111A5B23A00D817D1 /* Response.h */, + 276382E211A5B23A00D817D1 /* Response.m */, + ); + path = lib; + sourceTree = ""; + }; + 2763829F11A5B23A00D817D1 /* objective_support */ = { + isa = PBXGroup; + children = ( + 276382A011A5B23A00D817D1 /* Classes */, + ); + path = objective_support; + sourceTree = ""; + }; + 276382A011A5B23A00D817D1 /* Classes */ = { + isa = PBXGroup; + children = ( + 276382A111A5B23A00D817D1 /* lib */, + ); + path = Classes; + sourceTree = ""; + }; + 276382A111A5B23A00D817D1 /* lib */ = { + isa = PBXGroup; + children = ( + 276382A211A5B23A00D817D1 /* Core */, + 276382B011A5B23A00D817D1 /* json-framework */, + 276382B811A5B23A00D817D1 /* Serialization */, + ); + path = lib; + sourceTree = ""; + }; + 276382A211A5B23A00D817D1 /* Core */ = { + isa = PBXGroup; + children = ( + 276382A311A5B23A00D817D1 /* CoreSupport.h */, + 276382A411A5B23A00D817D1 /* Inflections */, + 276382A711A5B23A00D817D1 /* NSData+Additions.h */, + 276382A811A5B23A00D817D1 /* NSData+Additions.m */, + 276382A911A5B23A00D817D1 /* NSObject+PropertySupport.h */, + 276382AA11A5B23A00D817D1 /* NSObject+PropertySupport.m */, + 276382AB11A5B23A00D817D1 /* NSString+GSub.h */, + 276382AC11A5B23A00D817D1 /* NSString+GSub.m */, + 276382AD11A5B23A00D817D1 /* ObjectiveResourceDateFormatter.h */, + 276382AE11A5B23A00D817D1 /* ObjectiveResourceDateFormatter.m */, + 276382AF11A5B23A00D817D1 /* ObjectiveSupport.h */, + ); + path = Core; + sourceTree = ""; + }; + 276382A411A5B23A00D817D1 /* Inflections */ = { + isa = PBXGroup; + children = ( + 276382A511A5B23A00D817D1 /* NSString+InflectionSupport.h */, + 276382A611A5B23A00D817D1 /* NSString+InflectionSupport.m */, + ); + path = Inflections; + sourceTree = ""; + }; + 276382B011A5B23A00D817D1 /* json-framework */ = { + isa = PBXGroup; + children = ( + 276382B111A5B23A00D817D1 /* JSONFramework.h */, + 276382B211A5B23A00D817D1 /* NSObject+SBJSON.h */, + 276382B311A5B23A00D817D1 /* NSObject+SBJSON.m */, + 276382B411A5B23A00D817D1 /* NSString+SBJSON.h */, + 276382B511A5B23A00D817D1 /* NSString+SBJSON.m */, + 276382B611A5B23A00D817D1 /* SBJSON.h */, + 276382B711A5B23A00D817D1 /* SBJSON.m */, + ); + path = "json-framework"; + sourceTree = ""; + }; + 276382B811A5B23A00D817D1 /* Serialization */ = { + isa = PBXGroup; + children = ( + 276382B911A5B23A00D817D1 /* JSON */, + 276382C211A5B23A00D817D1 /* NSDate+Serialize.h */, + 276382C311A5B23A00D817D1 /* NSDate+Serialize.m */, + 276382C411A5B23A00D817D1 /* NSDictionary+KeyTranslation.h */, + 276382C511A5B23A00D817D1 /* NSDictionary+KeyTranslation.m */, + 276382C611A5B23A00D817D1 /* NSObject+Serialize.h */, + 276382C711A5B23A00D817D1 /* NSObject+Serialize.m */, + 276382C811A5B23A00D817D1 /* NSString+Serialize.h */, + 276382C911A5B23A00D817D1 /* NSString+Serialize.m */, + 276382CA11A5B23A00D817D1 /* Serialize.h */, + 276382CB11A5B23A00D817D1 /* XML */, + ); + path = Serialization; + sourceTree = ""; + }; + 276382B911A5B23A00D817D1 /* JSON */ = { + isa = PBXGroup; + children = ( + 276382BA11A5B23A00D817D1 /* JSONSerializable.h */, + 276382BB11A5B23A00D817D1 /* JSONSerializableSupport.h */, + 276382BC11A5B23A00D817D1 /* NSArray+JSONSerializableSupport.h */, + 276382BD11A5B23A00D817D1 /* NSArray+JSONSerializableSupport.m */, + 276382BE11A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.h */, + 276382BF11A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.m */, + 276382C011A5B23A00D817D1 /* NSObject+JSONSerializableSupport.h */, + 276382C111A5B23A00D817D1 /* NSObject+JSONSerializableSupport.m */, + ); + path = JSON; + sourceTree = ""; + }; + 276382CB11A5B23A00D817D1 /* XML */ = { + isa = PBXGroup; + children = ( + 276382CC11A5B23A00D817D1 /* FromXMLElementDelegate.h */, + 276382CD11A5B23A00D817D1 /* FromXMLElementDelegate.m */, + 276382CE11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.h */, + 276382CF11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.m */, + 276382D011A5B23A00D817D1 /* NSDate+XMLSerializableSupport.h */, + 276382D111A5B23A00D817D1 /* NSDate+XMLSerializableSupport.m */, + 276382D211A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.h */, + 276382D311A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.m */, + 276382D411A5B23A00D817D1 /* NSNull+XMLSerializableSupport.h */, + 276382D511A5B23A00D817D1 /* NSNull+XMLSerializableSupport.m */, + 276382D611A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.h */, + 276382D711A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.m */, + 276382D811A5B23A00D817D1 /* NSObject+XMLSerializableSupport.h */, + 276382D911A5B23A00D817D1 /* NSObject+XMLSerializableSupport.m */, + 276382DA11A5B23A00D817D1 /* NSString+XMLSerializableSupport.h */, + 276382DB11A5B23A00D817D1 /* NSString+XMLSerializableSupport.m */, + 276382DC11A5B23A00D817D1 /* XMLSerializable.h */, + 276382DD11A5B23A00D817D1 /* XMLSerializableSupport.h */, + ); + path = XML; + sourceTree = ""; + }; + 27A8F4D61055DA7300366488 /* Vendor */ = { + isa = PBXGroup; + children = ( + 2763829011A5B23A00D817D1 /* objectiveresource */, + ); + name = Vendor; + sourceTree = ""; + }; + 27E81BB11056E30600661701 /* Helpers */ = { + isa = PBXGroup; + children = ( + 2791004511922E0B005BC315 /* AppHelpers.h */, + 2791004611922E0B005BC315 /* AppHelpers.m */, + ); + name = Helpers; + path = ..; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 27A8F4D61055DA7300366488 /* Vendor */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 28A0AAE50D9B0CCF005BE974 /* SaveUp_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 277AFF360FA1F541003B28CF /* icon.png */, + 277AFF330FA1F44E003B28CF /* GoalsView.xib */, + 28AD735F0D9D9599002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + 2892E40F0DC94CBA00A64D0F /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* SaveUp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SaveUp; + productName = Budget; + productReference = 1D6058910D05DD3D006BFB54 /* SaveUp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + en, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* SaveUp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */, + 277AFF350FA1F44E003B28CF /* GoalsView.xib in Resources */, + 277AFF370FA1F541003B28CF /* icon.png in Resources */, + 2763830711A5B23A00D817D1 /* LICENSE in Resources */, + 2763830811A5B23A00D817D1 /* README.textile in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + 1D3623260D0F684500981E51 /* SaveUpAppDelegate.m in Sources */, + 277AFEBB0FA1F16B003B28CF /* Goal.m in Sources */, + 277AFEBC0FA1F16B003B28CF /* GoalAddViewController.m in Sources */, + 277AFEBD0FA1F16B003B28CF /* GoalDetailViewController.m in Sources */, + 277AFEBE0FA1F16B003B28CF /* GoalsViewController.m in Sources */, + 27A19F4A0FAA00DF00EA701E /* User.m in Sources */, + 27A19F680FAA049000EA701E /* AuthenticationViewController.m in Sources */, + 27A19F970FAA1DA900EA701E /* Session.m in Sources */, + 27E81C6E1056F06800661701 /* Credit.m in Sources */, + 27E81C751056F0B000661701 /* CreditDetailViewController.m in Sources */, + 2791004711922E0B005BC315 /* AppHelpers.m in Sources */, + 276382E711A5B23A00D817D1 /* Connection.m in Sources */, + 276382E811A5B23A00D817D1 /* ConnectionDelegate.m in Sources */, + 276382E911A5B23A00D817D1 /* ConnectionManager.m in Sources */, + 276382EA11A5B23A00D817D1 /* NSHTTPURLResponse+Error.m in Sources */, + 276382EB11A5B23A00D817D1 /* NSMutableURLRequest+ResponseType.m in Sources */, + 276382EC11A5B23A00D817D1 /* NSObject+ObjectiveResource.m in Sources */, + 276382ED11A5B23A00D817D1 /* NSString+InflectionSupport.m in Sources */, + 276382EE11A5B23A00D817D1 /* NSData+Additions.m in Sources */, + 276382EF11A5B23A00D817D1 /* NSObject+PropertySupport.m in Sources */, + 276382F011A5B23A00D817D1 /* NSString+GSub.m in Sources */, + 276382F111A5B23A00D817D1 /* ObjectiveResourceDateFormatter.m in Sources */, + 276382F211A5B23A00D817D1 /* NSObject+SBJSON.m in Sources */, + 276382F311A5B23A00D817D1 /* NSString+SBJSON.m in Sources */, + 276382F411A5B23A00D817D1 /* SBJSON.m in Sources */, + 276382F511A5B23A00D817D1 /* NSArray+JSONSerializableSupport.m in Sources */, + 276382F611A5B23A00D817D1 /* NSDictionary+JSONSerializableSupport.m in Sources */, + 276382F711A5B23A00D817D1 /* NSObject+JSONSerializableSupport.m in Sources */, + 276382F811A5B23A00D817D1 /* NSDate+Serialize.m in Sources */, + 276382F911A5B23A00D817D1 /* NSDictionary+KeyTranslation.m in Sources */, + 276382FA11A5B23A00D817D1 /* NSObject+Serialize.m in Sources */, + 276382FB11A5B23A00D817D1 /* NSString+Serialize.m in Sources */, + 276382FC11A5B23A00D817D1 /* FromXMLElementDelegate.m in Sources */, + 276382FD11A5B23A00D817D1 /* NSArray+XMLSerializableSupport.m in Sources */, + 276382FE11A5B23A00D817D1 /* NSDate+XMLSerializableSupport.m in Sources */, + 276382FF11A5B23A00D817D1 /* NSDictionary+XMLSerializableSupport.m in Sources */, + 2763830011A5B23A00D817D1 /* NSNull+XMLSerializableSupport.m in Sources */, + 2763830111A5B23A00D817D1 /* NSNumber+XMLSerializableSupport.m in Sources */, + 2763830211A5B23A00D817D1 /* NSObject+XMLSerializableSupport.m in Sources */, + 2763830311A5B23A00D817D1 /* NSString+XMLSerializableSupport.m in Sources */, + 2763830411A5B23A00D817D1 /* ObjectiveResourceConfig.m in Sources */, + 2763830511A5B23A00D817D1 /* Response.m in Sources */, + 2763830611A5B23A00D817D1 /* NSError+Error.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SaveUp_Prefix.pch; + INFOPLIST_FILE = Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 3.1.3; + PRODUCT_NAME = SaveUp; + SDKROOT = iphonesimulator3.1.3; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 3.2; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = iphonesimulator3.1.3; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 3.2; + PREBINDING = NO; + SDKROOT = iphonesimulator3.1.3; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SaveUp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/code/iphone_app/SaveUp_Prefix.pch b/code/iphone_app/SaveUp_Prefix.pch new file mode 100644 index 0000000..4dc3327 --- /dev/null +++ b/code/iphone_app/SaveUp_Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header for all source files of the 'SaveUp' target in the 'SaveUp' project +// + +#ifdef __OBJC__ + #import + #import + #import "AppHelpers.h" +#endif diff --git a/code/iphone_app/icon.png b/code/iphone_app/icon.png new file mode 100644 index 0000000..255d2ac Binary files /dev/null and b/code/iphone_app/icon.png differ diff --git a/code/iphone_app/main.m b/code/iphone_app/main.m new file mode 100644 index 0000000..5fd4264 --- /dev/null +++ b/code/iphone_app/main.m @@ -0,0 +1,8 @@ +#import + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/code/iphone_app/objectiveresource/Classes/NSError+Error.h b/code/iphone_app/objectiveresource/Classes/NSError+Error.h new file mode 100644 index 0000000..2b3ce8e --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/NSError+Error.h @@ -0,0 +1,13 @@ +// +// NSError+Error.h +// objective_resource +// +// Created by Adam Alexander on 3/10/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +@interface NSError(Error) + +-(NSArray *)errors; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/NSError+Error.m b/code/iphone_app/objectiveresource/Classes/NSError+Error.m new file mode 100644 index 0000000..b42584d --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/NSError+Error.m @@ -0,0 +1,17 @@ +// +// NSError+Error.m +// objective_resource +// +// Created by Adam Alexander on 3/10/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import "NSError+Error.h" + +@implementation NSError(Error) + +-(NSArray *)errors { + return [self.userInfo valueForKey:NSLocalizedRecoveryOptionsErrorKey]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/Connection.h b/code/iphone_app/objectiveresource/Classes/lib/Connection.h new file mode 100644 index 0000000..043e46d --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/Connection.h @@ -0,0 +1,23 @@ +// +// Connection.h +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@class Response; + +@interface Connection : NSObject ++ (void) setTimeout:(float)timeout; ++ (float) timeout; ++ (Response *)post:(NSString *)body to:(NSString *)url; ++ (Response *)post:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)get:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)get:(NSString *)url; ++ (Response *)put:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; ++ (Response *)delete:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password; + ++ (void) cancelAllActiveConnections; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/Connection.m b/code/iphone_app/objectiveresource/Classes/lib/Connection.m new file mode 100644 index 0000000..7098ee9 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/Connection.m @@ -0,0 +1,145 @@ +// +// Connection.m +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "Connection.h" +#import "Response.h" +#import "NSData+Additions.h" +#import "NSMutableURLRequest+ResponseType.h" +#import "ConnectionDelegate.h" + +//#define debugLog(...) NSLog(__VA_ARGS__) +#ifndef debugLog(...) + #define debugLog(...) +#endif + +@implementation Connection + +static float timeoutInterval = 5.0; + +static NSMutableArray *activeDelegates; + ++ (NSMutableArray *)activeDelegates { + if (nil == activeDelegates) { + activeDelegates = [NSMutableArray array]; + [activeDelegates retain]; + } + return activeDelegates; +} + ++ (void)setTimeout:(float)timeOut { + timeoutInterval = timeOut; +} ++ (float)timeout { + return timeoutInterval; +} + ++ (void)logRequest:(NSURLRequest *)request to:(NSString *)url { + debugLog(@"%@ -> %@", [request HTTPMethod], url); + if([request HTTPBody]) { + debugLog([[[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding] autorelease]); + } +} + ++ (Response *)sendRequest:(NSMutableURLRequest *)request withUser:(NSString *)user andPassword:(NSString *)password { + + //lots of servers fail to implement http basic authentication correctly, so we pass the credentials even if they are not asked for + //TODO make this configurable? + NSURL *url = [request URL]; + if(user && password) { + NSString *authString = [[[NSString stringWithFormat:@"%@:%@",user, password] dataUsingEncoding:NSUTF8StringEncoding] base64Encoding]; + [request addValue:[NSString stringWithFormat:@"Basic %@", authString] forHTTPHeaderField:@"Authorization"]; + NSString *escapedUser = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)user, NULL, (CFStringRef)@"@.:", kCFStringEncodingUTF8); + NSString *escapedPassword = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)password, NULL, (CFStringRef)@"@.:", kCFStringEncodingUTF8); + NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@://%@:%@@%@",[url scheme],escapedUser,escapedPassword,[url host],nil]; + if([url port]) { + [urlString appendFormat:@":%@",[url port],nil]; + } + [urlString appendString:[url path]]; + if([url query]){ + [urlString appendFormat:@"?%@",[url query],nil]; + } + [request setURL:[NSURL URLWithString:urlString]]; + [escapedUser release]; + [escapedPassword release]; + } + + + [self logRequest:request to:[url absoluteString]]; + + ConnectionDelegate *connectionDelegate = [[[ConnectionDelegate alloc] init] autorelease]; + + [[self activeDelegates] addObject:connectionDelegate]; + NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:connectionDelegate startImmediately:NO] autorelease]; + connectionDelegate.connection = connection; + + + //use a custom runloop + static NSString *runLoopMode = @"com.yfactorial.objectiveresource.connectionLoop"; + [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:runLoopMode]; + [connection start]; + while (![connectionDelegate isDone]) { + [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.3]]; + } + Response *resp = [Response responseFrom:(NSHTTPURLResponse *)connectionDelegate.response + withBody:connectionDelegate.data + andError:connectionDelegate.error]; + [resp log]; + + [activeDelegates removeObject:connectionDelegate]; + + //if there are no more active delegates release the array + if (0 == [activeDelegates count]) { + NSMutableArray *tempDelegates = activeDelegates; + activeDelegates = nil; + [tempDelegates release]; + } + + return resp; +} + ++ (Response *)post:(NSString *)body to:(NSString *)url { + return [self post:body to:url withUser:@"X" andPassword:@"X"]; +} + ++ (Response *)sendBy:(NSString *)method withBody:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:method]; + [request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (Response *)post:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + return [self sendBy:@"POST" withBody:body to:url withUser:user andPassword:password]; +} + ++ (Response *)put:(NSString *)body to:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password{ + return [self sendBy:@"PUT" withBody:body to:url withUser:user andPassword:password]; +} + ++ (Response *)get:(NSString *)url { + return [self get:url withUser:@"X" andPassword:@"X"]; +} + ++ (Response *)get:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:@"GET"]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (Response *)delete:(NSString *)url withUser:(NSString *)user andPassword:(NSString *)password { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithUrl:[NSURL URLWithString:url] andMethod:@"DELETE"]; + return [self sendRequest:request withUser:user andPassword:password]; +} + ++ (void) cancelAllActiveConnections { + for (ConnectionDelegate *delegate in activeDelegates) { + [delegate performSelectorOnMainThread:@selector(cancel) withObject:nil waitUntilDone:NO]; + } +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.h b/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.h new file mode 100644 index 0000000..6d6e789 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.h @@ -0,0 +1,30 @@ +// +// ConnectionDelegate.h +// iphone-harvest +// +// Created by vickeryj on 1/14/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import + + +@interface ConnectionDelegate : NSObject { + + NSMutableData *data; + NSURLResponse *response; + BOOL done; + NSError *error; + NSURLConnection *connection; + +} + +- (BOOL) isDone; +- (void) cancel; + +@property(nonatomic, retain) NSURLResponse *response; +@property(nonatomic, retain) NSMutableData *data; +@property(nonatomic, retain) NSError *error; +@property(nonatomic, retain) NSURLConnection *connection; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.m b/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.m new file mode 100644 index 0000000..9233b8a --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ConnectionDelegate.m @@ -0,0 +1,89 @@ +// +// ConnectionDelegate.m +// iphone-harvest +// +// Created by vickeryj on 1/14/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ConnectionDelegate.h" + + +@implementation ConnectionDelegate + +@synthesize response, data, error, connection; + +- (id) init +{ + self = [super init]; + if (self != nil) { + self.data = [NSMutableData data]; + done = NO; + } + return self; +} + +- (BOOL) isDone { + return done; +} + +- (void) cancel { + [connection cancel]; + self.response = nil; + self.data = nil; + self.error = nil; + done = YES; +} + +#pragma mark NSURLConnectionDelegate methods +- (NSURLRequest *)connection:(NSURLConnection *)aConnection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { + return request; +} + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + if ([challenge previousFailureCount] > 0) { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } + else { + [[challenge sender] useCredential:[challenge proposedCredential] forAuthenticationChallenge:challenge]; + } +} +- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + done = YES; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse { + self.response = aResponse; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)someData { + [data appendData:someData]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { + done = YES; +} + +- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)aError { + self.error = aError; + done = YES; +} + +//don't cache resources for now +- (NSCachedURLResponse *)connection:(NSURLConnection *)aConnection willCacheResponse:(NSCachedURLResponse *)cachedResponse { + return nil; +} + + +#pragma mark cleanup +- (void) dealloc +{ + [data release]; + [response release]; + [error release]; + [connection release]; + [super dealloc]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.h b/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.h new file mode 100644 index 0000000..a970cd3 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.h @@ -0,0 +1,19 @@ +// +// ConnectionManager.h +// +// Created by Adam Alexander on 2/28/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import + + +@interface ConnectionManager : NSObject { + NSOperationQueue *operationQueue; +} +@property (nonatomic, retain) NSOperationQueue *operationQueue; ++ (ConnectionManager *)sharedInstance; +- (void)cancelAllJobs; +- (void)runJob:(SEL)selector onTarget:(id)target; +- (void)runJob:(SEL)selector onTarget:(id)target withArgument:(id)argument; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.m b/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.m new file mode 100644 index 0000000..e8035ce --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ConnectionManager.m @@ -0,0 +1,87 @@ +// +// ConnectionManager.m +// +// Created by Adam Alexander on 2/28/09. +// Copyright 2009 yFactorial, LLC. All rights reserved. +// + +#import "ConnectionManager.h" + + +@implementation ConnectionManager +@synthesize operationQueue; + +- (void)cancelAllJobs { + [operationQueue cancelAllOperations]; +} + +- (void) runJob:(SEL)selector onTarget:(id)target { + [self runJob:selector onTarget:target withArgument:nil]; +} + +- (void) runJob:(SEL)selector onTarget:(id)target withArgument:(id)argument { + NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:target selector:selector object:argument]; + [operationQueue addOperation:operation]; + [operation release]; +} + +- (id)init { + if ( self = [super init] ) { + self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; + [operationQueue setMaxConcurrentOperationCount:1]; + return self; + } else { + return nil; + } +} + +#pragma mark Standard Singleton Plumbing + +static ConnectionManager *sharedConnectionManager = nil; + ++ (ConnectionManager *)sharedInstance +{ + @synchronized(self) { + if (sharedConnectionManager == nil) { + [[self alloc] init]; // assignment not done here + } + } + return sharedConnectionManager; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + if (sharedConnectionManager == nil) { + sharedConnectionManager = [super allocWithZone:zone]; + return sharedConnectionManager; // assignment and return on first allocation + } + } + return nil; //on subsequent allocation attempts return nil +} + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)retain +{ + return self; +} + +- (unsigned)retainCount +{ + return UINT_MAX; //denotes an object that cannot be released +} + +- (void)release +{ + //do nothing +} + +- (id)autorelease +{ + return self; +} +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h b/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h new file mode 100644 index 0000000..7b8b48b --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.h @@ -0,0 +1,15 @@ +// +// NSHTTPURLResponse+Error.h +// iphone-harvest +// +// Created by James Burka on 12/23/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +@interface NSHTTPURLResponse(Error) + +-(NSError *) errorWithBody:(NSData *)data; +-(BOOL) isSuccess; ++ (NSError *)buildResponseError:(int)statusCode withBody:(NSData *)data; ++ (NSArray *)errorArrayForBody:(NSData *)data; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m b/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m new file mode 100644 index 0000000..39468cb --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSHTTPURLResponse+Error.m @@ -0,0 +1,52 @@ +// +// NSHTTPURLResponse+Error.m +// iphone-harvest +// +// Created by James Burka on 12/23/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import "NSHTTPURLResponse+Error.h" +#import "ObjectiveResourceConfig.h" + +@implementation NSHTTPURLResponse(Error) + ++ (NSError *)buildResponseError:(int)statusCode withBody:(NSData *)data{ + NSMutableDictionary *description = [NSMutableDictionary dictionary]; + [description setObject:[NSString stringWithFormat:@"%i Error",statusCode] forKey:NSLocalizedFailureReasonErrorKey]; + [description setObject:[[self class] localizedStringForStatusCode:statusCode] forKey:NSLocalizedDescriptionKey]; + NSArray *errorArray = [[self class] errorArrayForBody:data]; + if (nil != errorArray) { + [description setObject:errorArray forKey:NSLocalizedRecoveryOptionsErrorKey]; + } + return [NSError errorWithDomain:@"com.yfactorial.objectiveresource" code:statusCode userInfo:description]; +} + ++ (NSArray *)errorArrayForBody:(NSData *)data { + if (@selector(fromJSONData:) == [ObjectiveResourceConfig getParseDataMethod]) { + NSMutableArray *returnStrings = [NSMutableArray array]; + NSArray *errorArrays = [[self class] performSelector:[ObjectiveResourceConfig getParseDataMethod] withObject:data]; + for (NSArray *error in errorArrays) { + [returnStrings addObject:[error componentsJoinedByString:@" "]]; + } + return returnStrings; + } + else { + return nil; + } +} + +-(NSError *) errorWithBody:(NSData *)data { + if([self isSuccess]) { + return nil; + } + else { + return [[self class] buildResponseError:[self statusCode] withBody:data]; + } +} + +-(BOOL) isSuccess { + return [self statusCode] >= 200 && [self statusCode] < 400; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h b/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h new file mode 100644 index 0000000..60879c6 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.h @@ -0,0 +1,13 @@ +// +// NSMutableURLRequest+ResponseType.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSMutableURLRequest(ResponseType) + ++(NSMutableURLRequest *) requestWithUrl:(NSURL *)url andMethod:(NSString*)method; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m b/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m new file mode 100644 index 0000000..89c5516 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSMutableURLRequest+ResponseType.m @@ -0,0 +1,32 @@ +// +// NSMutableURLRequest+ResponseType.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSMutableURLRequest+ResponseType.h" +#import "ObjectiveResource.h" +#import "Connection.h" + +@implementation NSMutableURLRequest(ResponseType) + ++(NSMutableURLRequest *) requestWithUrl:(NSURL *)url andMethod:(NSString*)method { + NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:[Connection timeout]]; + [request setHTTPMethod:method]; + switch ([ObjectiveResourceConfig getResponseType]) { + case JSONResponse: + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; + break; + default: + [request setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; + [request addValue:@"application/xml" forHTTPHeaderField:@"Accept"]; + break; + } + return request; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h b/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h new file mode 100644 index 0000000..dc68586 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.h @@ -0,0 +1,84 @@ +// +// NSObject+ObjectiveResource.h +// objectivesync +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import + +@interface NSObject (ObjectiveResource) + + +// Response Formats +typedef enum { + XmlResponse = 0, + JSONResponse, +} ORSResponseFormat; + +// Resource configuration ++ (NSString *)getRemoteSite; ++ (void)setRemoteSite:(NSString*)siteURL; ++ (NSString *)getRemoteUser; ++ (void)setRemoteUser:(NSString *)user; ++ (NSString *)getRemotePassword; ++ (void)setRemotePassword:(NSString *)password; ++ (SEL)getRemoteParseDataMethod; ++ (void)setRemoteParseDataMethod:(SEL)parseMethod; ++ (SEL) getRemoteSerializeMethod; ++ (void) setRemoteSerializeMethod:(SEL)serializeMethod; ++ (NSString *)getRemoteProtocolExtension; ++ (void)setRemoteProtocolExtension:(NSString *)protocolExtension; ++ (void)setRemoteResponseType:(ORSResponseFormat) format; ++ (ORSResponseFormat)getRemoteResponseType; + +// Prefix additions ++ (NSString *)getLocalClassesPrefix; ++ (void)setLocalClassesPrefix:(NSString *)prefix; + +// Finders ++ (NSArray *)findAllRemote; ++ (NSArray *)findAllRemoteWithResponse:(NSError **)aError; ++ (id)findRemote:(NSString *)elementId; ++ (id)findRemote:(NSString *)elementId withResponse:(NSError **)aError; + +// URL construction accessors ++ (NSString *)getRemoteElementName; ++ (NSString *)getRemoteCollectionName; ++ (NSString *)getRemoteElementPath:(NSString *)elementId; ++ (NSString *)getRemoteCollectionPath; ++ (NSString *)getRemoteCollectionPathWithParameters:(NSDictionary *)parameters; ++ (NSString *)populateRemotePath:(NSString *)path withParameters:(NSDictionary *)parameters; + +// Instance-specific methods +- (id)getRemoteId; +- (void)setRemoteId:(id)orsId; +- (NSString *)getRemoteClassIdName; +- (BOOL)createRemote; +- (BOOL)createRemoteWithResponse:(NSError **)aError; +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters; +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters andResponse:(NSError **)aError; +- (BOOL)destroyRemote; +- (BOOL)destroyRemoteWithResponse:(NSError **)aError; +- (BOOL)updateRemote; +- (BOOL)updateRemoteWithResponse:(NSError **)aError; +- (BOOL)saveRemote; +- (BOOL)saveRemoteWithResponse:(NSError **)aError; + + +- (BOOL)createRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; +- (BOOL)updateRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; +- (BOOL)destroyRemoteAtPath:(NSString *)path withResponse:(NSError **)aError; + +// Instance helpers for getting at commonly used class-level values +- (NSString *)getRemoteCollectionPath; +- (NSString *)convertToRemoteExpectedType; + +//Equality test for remote enabled objects based on class name and remote id +- (BOOL)isEqualToRemote:(id)anObject; +- (NSUInteger)hashForRemote; + +- (NSArray *)excludedPropertyNames; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m b/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m new file mode 100644 index 0000000..39af45e --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/NSObject+ObjectiveResource.m @@ -0,0 +1,362 @@ +// +// NSObject+ObjectiveResource.m +// objectivesync +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "NSObject+ObjectiveResource.h" +#import "Connection.h" +#import "Response.h" +#import "CoreSupport.h" +#import "XMLSerializableSupport.h" +#import "JSONSerializableSupport.h" + +static NSString *_activeResourceSite = nil; +static NSString *_activeResourceUser = nil; +static NSString *_activeResourcePassword = nil; +static SEL _activeResourceParseDataMethod = nil; +static SEL _activeResourceSerializeMethod = nil; +static NSString *_activeResourceProtocolExtension = @".xml"; +static ORSResponseFormat _format; +static NSString *_activeResourcePrefix = nil; + + +@implementation NSObject (ObjectiveResource) + +#pragma mark configuration methods ++ (NSString *)getRemoteSite { + return _activeResourceSite; +} + ++ (void)setRemoteSite:(NSString *)siteURL { + if (_activeResourceSite != siteURL) { + [_activeResourceSite autorelease]; + _activeResourceSite = [siteURL copy]; + } +} + ++ (NSString *)getRemoteUser { + return _activeResourceUser; +} + ++ (void)setRemoteUser:(NSString *)user { + if (_activeResourceUser != user) { + [_activeResourceUser autorelease]; + _activeResourceUser = [user copy]; + } +} + ++ (NSString *)getRemotePassword { + return _activeResourcePassword; +} + ++ (void)setRemotePassword:(NSString *)password { + if (_activeResourcePassword != password) { + [_activeResourcePassword autorelease]; + _activeResourcePassword = [password copy]; + } +} + ++ (void)setRemoteResponseType:(ORSResponseFormat) format { + _format = format; + switch (format) { + case JSONResponse: + [[self class] setRemoteProtocolExtension:@".json"]; + [[self class] setRemoteParseDataMethod:@selector(fromJSONData:)]; + [[self class] setRemoteSerializeMethod:@selector(toJSONExcluding:)]; + break; + default: + [[self class] setRemoteProtocolExtension:@".xml"]; + [[self class] setRemoteParseDataMethod:@selector(fromXMLData:)]; + [[self class] setRemoteSerializeMethod:@selector(toXMLElementExcluding:)]; + break; + } +} + ++ (ORSResponseFormat)getRemoteResponseType { + return _format; +} + ++ (SEL)getRemoteParseDataMethod { + return (nil == _activeResourceParseDataMethod) ? @selector(fromXMLData:) : _activeResourceParseDataMethod; +} + ++ (void)setRemoteParseDataMethod:(SEL)parseMethod { + _activeResourceParseDataMethod = parseMethod; +} + ++ (SEL) getRemoteSerializeMethod { + return (nil == _activeResourceSerializeMethod) ? @selector(toXMLElementExcluding:) : _activeResourceSerializeMethod; +} + ++ (void) setRemoteSerializeMethod:(SEL)serializeMethod { + _activeResourceSerializeMethod = serializeMethod; +} + ++ (NSString *)getRemoteProtocolExtension { + return _activeResourceProtocolExtension; +} + ++ (void)setRemoteProtocolExtension:(NSString *)protocolExtension { + if (_activeResourceProtocolExtension != protocolExtension) { + [_activeResourceProtocolExtension autorelease]; + _activeResourceProtocolExtension = [protocolExtension copy]; + } +} + +// Prefix additions ++ (NSString *)getLocalClassesPrefix { + return _activeResourcePrefix; +} + ++ (void)setLocalClassesPrefix:(NSString *)prefix { + if (prefix != _activeResourcePrefix) { + [_activeResourcePrefix autorelease]; + _activeResourcePrefix = [prefix copy]; + } +} + +// Find all items ++ (NSArray *)findAllRemoteWithResponse:(NSError **)aError { + Response *res = [Connection get:[self getRemoteCollectionPath] withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + return nil; + } + else { + return [self performSelector:[self getRemoteParseDataMethod] withObject:res.body]; + } +} + ++ (NSArray *)findAllRemote { + NSError *aError; + return [self findAllRemoteWithResponse:&aError]; +} + ++ (id)findRemote:(NSString *)elementId withResponse:(NSError **)aError { + Response *res = [Connection get:[self getRemoteElementPath:elementId] withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + return [self performSelector:[self getRemoteParseDataMethod] withObject:res.body]; +} + ++ (id)findRemote:(NSString *)elementId { + NSError *aError; + return [self findRemote:elementId withResponse:&aError]; +} + ++ (NSString *)getRemoteElementName { + NSString * remoteElementName = NSStringFromClass([self class]); + if (_activeResourcePrefix != nil) { + remoteElementName = [remoteElementName substringFromIndex:[_activeResourcePrefix length]]; + } + return [[remoteElementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) + withString:[[remoteElementName substringWithRange:NSMakeRange(0, 1)] + lowercaseString]] + underscore]; +} + ++ (NSString *)getRemoteCollectionName { + return [[self getRemoteElementName] stringByAppendingString:@"s"]; +} + ++ (NSString *)getRemoteElementPath:(NSString *)elementId { + return [NSString stringWithFormat:@"%@%@/%@%@", [self getRemoteSite], [self getRemoteCollectionName], elementId, [self getRemoteProtocolExtension]]; +} + ++ (NSString *)getRemoteCollectionPath { + return [[[self getRemoteSite] stringByAppendingString:[self getRemoteCollectionName]] stringByAppendingString:[self getRemoteProtocolExtension]]; +} + ++ (NSString *)getRemoteCollectionPathWithParameters:(NSDictionary *)parameters { + return [self populateRemotePath:[self getRemoteCollectionPath] withParameters:parameters]; +} + ++ (NSString *)populateRemotePath:(NSString *)path withParameters:(NSDictionary *)parameters { + + // Translate each key to have a preceeding ":" for proper URL param notiation + NSMutableDictionary *parameterized = [NSMutableDictionary dictionaryWithCapacity:[parameters count]]; + for (NSString *key in parameters) { + [parameterized setObject:[parameters objectForKey:key] forKey:[NSString stringWithFormat:@":%@", key]]; + } + return [path gsub:parameterized]; +} + +- (NSString *)getRemoteCollectionPath { + return [[self class] getRemoteCollectionPath]; +} + +// Converts the object to the data format expected by the server +- (NSString *)convertToRemoteExpectedType { + return [self performSelector:[[self class] getRemoteSerializeMethod] withObject:[self excludedPropertyNames]]; +} + + +#pragma mark default equals methods for id and class based equality +- (BOOL)isEqualToRemote:(id)anObject { + return [NSStringFromClass([self class]) isEqualToString:NSStringFromClass([anObject class])] && + [anObject respondsToSelector:@selector(getRemoteId)] && [[anObject getRemoteId]isEqualToString:[self getRemoteId]]; +} +- (NSUInteger)hashForRemote { + return [[self getRemoteId] intValue] + [NSStringFromClass([self class]) hash]; +} + +#pragma mark Instance-specific methods +- (id)getRemoteId { + id result = nil; + SEL idMethodSelector = NSSelectorFromString([self getRemoteClassIdName]); + if ([self respondsToSelector:idMethodSelector]) { + result = [self performSelector:idMethodSelector]; + if ([result respondsToSelector:@selector(stringValue)]) { + result = [result stringValue]; + } + } + return result; +} +- (void)setRemoteId:(id)orsId { + SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@Id:",[self className]]); + if ([self respondsToSelector:setter]) { + [self performSelector:setter withObject:orsId]; + } +} + + +- (NSString *)getRemoteClassIdName { + NSString * remoteElementName = NSStringFromClass([self class]); + if (_activeResourcePrefix != nil) { + remoteElementName = [remoteElementName substringFromIndex:[_activeResourcePrefix length]]; + } + return [NSString stringWithFormat:@"%@Id", + [remoteElementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) + withString:[[remoteElementName substringWithRange:NSMakeRange(0,1)] lowercaseString]]]; +} + +- (BOOL)createRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection post:[self convertToRemoteExpectedType] to:path withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + if ([res isSuccess]) { + NSDictionary *newProperties = [[[self class] performSelector:[[self class] getRemoteParseDataMethod] withObject:res.body] properties]; + [self setProperties:newProperties]; + return YES; + } + else { + return NO; + } +} + +-(BOOL)updateRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection put:[self convertToRemoteExpectedType] to:path + withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + if ([res isSuccess]) { + if([(NSString *)[res.headers objectForKey:@"Content-Length"] intValue] > 1) { + NSDictionary *newProperties = [[[self class] performSelector:[[self class] getRemoteParseDataMethod] withObject:res.body] properties]; + [self setProperties:newProperties]; + } + return YES; + } + else { + return NO; + } + +} + +- (BOOL)destroyRemoteAtPath:(NSString *)path withResponse:(NSError **)aError { + Response *res = [Connection delete:path withUser:[[self class] getRemoteUser] andPassword:[[self class] getRemotePassword]]; + if([res isError] && aError) { + *aError = res.error; + } + return [res isSuccess]; +} + +- (BOOL)createRemoteWithResponse:(NSError **)aError { + return [self createRemoteAtPath:[self getRemoteCollectionPath] withResponse:aError]; +} + +- (BOOL)createRemote { + NSError *error; + return [self createRemoteWithResponse:&error]; +} + +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters andResponse:(NSError **)aError { + return [self createRemoteAtPath:[[self class] getRemoteCollectionPathWithParameters:parameters] withResponse:aError]; +} + +- (BOOL)createRemoteWithParameters:(NSDictionary *)parameters { + NSError *error; + return [self createRemoteWithParameters:parameters andResponse:&error]; +} + + +- (BOOL)destroyRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil != myId) { + return [self destroyRemoteAtPath:[[self class] getRemoteElementPath:myId] withResponse:aError]; + } + else { + // this should return a error + return NO; + } +} + +- (BOOL)destroyRemote { + NSError *error; + return [self destroyRemoteWithResponse:&error]; +} + +- (BOOL)updateRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil != myId) { + return [self updateRemoteAtPath:[[self class] getRemoteElementPath:myId] withResponse:aError]; + } + else { + // this should return an error + return NO; + } +} + +- (BOOL)updateRemote { + NSError *error; + return [self updateRemoteWithResponse:&error]; +} + +- (BOOL)saveRemoteWithResponse:(NSError **)aError { + id myId = [self getRemoteId]; + if (nil == myId) { + return [self createRemoteWithResponse:aError]; + } + else { + return [self updateRemoteWithResponse:aError]; + } +} + +- (BOOL)saveRemote { + NSError *error; + return [self saveRemoteWithResponse:&error]; +} + +/* + Override this in your model class to extend or replace the excluded properties + eg. + - (NSArray *)excludedPropertyNames + { + NSArray *exclusions = [NSArray arrayWithObjects:@"extraPropertyToExclude", nil]; + return [[super excludedPropertyNames] arrayByAddingObjectsFromArray:exclusions]; + } +*/ + +- (NSArray *)excludedPropertyNames +{ + // exclude id , created_at , updated_at + return [NSArray arrayWithObjects:[self getRemoteClassIdName],@"createdAt",@"updatedAt",nil]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResource.h b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResource.h new file mode 100644 index 0000000..b0bb8bb --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResource.h @@ -0,0 +1,11 @@ +// +// ObjectiveResource.h +// +// +// Created by Ryan Daigle on 7/24/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+ObjectiveResource.h" +#import "ObjectiveResourceConfig.h" +#import "NSError+Error.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.h b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.h new file mode 100644 index 0000000..1a2cca1 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.h @@ -0,0 +1,30 @@ +// +// ObjectiveResourceConfig.h +// objective_resource +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ObjectiveResource.h" + +@interface ObjectiveResourceConfig : NSObject + ++ (NSString *)getSite; ++ (void)setSite:(NSString*)siteURL; ++ (NSString *)getUser; ++ (void)setUser:(NSString *)user; ++ (NSString *)getPassword; ++ (void)setPassword:(NSString *)password; ++ (SEL)getParseDataMethod; ++ (void)setParseDataMethod:(SEL)parseMethod; ++ (SEL) getSerializeMethod; ++ (void) setSerializeMethod:(SEL)serializeMethod; ++ (NSString *)protocolExtension; ++ (void)setProtocolExtension:(NSString *)protocolExtension; ++ (void)setResponseType:(ORSResponseFormat) format; ++ (ORSResponseFormat)getResponseType; ++ (NSString *)getLocalClassesPrefix; ++ (void)setLocalClassesPrefix:(NSString *)prefix; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.m b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.m new file mode 100644 index 0000000..bbf2dd9 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/ObjectiveResourceConfig.m @@ -0,0 +1,56 @@ +// +// ObjectiveResourceConfig.m +// objective_resource +// +// Created by vickeryj on 1/29/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "ObjectiveResourceConfig.h" + +@implementation ObjectiveResourceConfig + ++ (NSString *)getSite { + return [self getRemoteSite]; +} ++ (void)setSite:(NSString*)siteURL { + [self setRemoteSite:siteURL]; +} ++ (NSString *)getUser { + return [self getRemoteUser]; +} ++ (void)setUser:(NSString *)user { + [self setRemoteUser:user]; +} ++ (NSString *)getPassword { + return [self getRemotePassword]; +} ++ (void)setPassword:(NSString *)password { + [self setRemotePassword:password]; +} ++ (SEL)getParseDataMethod { + return [self getRemoteParseDataMethod]; +} ++ (void)setParseDataMethod:(SEL)parseMethod { + [self setRemoteParseDataMethod:parseMethod]; +} ++ (SEL) getSerializeMethod { + return [self getRemoteSerializeMethod]; +} ++ (void) setSerializeMethod:(SEL)serializeMethod { + [self setRemoteSerializeMethod:serializeMethod]; +} ++ (NSString *)protocolExtension { + return [self getRemoteProtocolExtension]; +} ++ (void)setProtocolExtension:(NSString *)protocolExtension { + [self setRemoteProtocolExtension:protocolExtension]; +} ++ (void)setResponseType:(ORSResponseFormat) format { + [self setRemoteResponseType:format]; +} ++ (ORSResponseFormat)getResponseType { + return [self getRemoteResponseType]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/Response.h b/code/iphone_app/objectiveresource/Classes/lib/Response.h new file mode 100644 index 0000000..9d196a0 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/Response.h @@ -0,0 +1,34 @@ +// +// Response.h +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// +#ifdef __OBJC__ +//setup debug only logging +#ifdef DEBUG +#define debugLog(...) NSLog(__VA_ARGS__) +#else +#define debugLog(...) +#endif +#endif + +@interface Response : NSObject { + NSData *body; + NSDictionary *headers; + NSInteger statusCode; + NSError *error; +} + +@property (nonatomic, retain) NSData *body; +@property (nonatomic, retain) NSDictionary *headers; +@property (assign, nonatomic) NSInteger statusCode; +@property (nonatomic, retain) NSError *error; + ++ (id)responseFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError; +- (id)initFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError; +- (BOOL)isSuccess; +- (BOOL)isError; +- (void)log; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/Response.m b/code/iphone_app/objectiveresource/Classes/lib/Response.m new file mode 100644 index 0000000..feaa2bf --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/Response.m @@ -0,0 +1,73 @@ +// +// Response.m +// +// +// Created by Ryan Daigle on 7/30/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "Response.h" +#import "NSHTTPURLResponse+Error.h" + +@implementation Response + +@synthesize body, headers, statusCode, error; + ++ (id)responseFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError { + return [[[self alloc] initFrom:response withBody:data andError:aError] autorelease]; +} + +- (void)normalizeError:(NSError *)aError withBody:(NSData *)data{ + switch ([aError code]) { + case NSURLErrorUserCancelledAuthentication: + self.statusCode = 401; + self.error = [NSHTTPURLResponse buildResponseError:401 withBody:data]; + break; + default: + self.error = aError; + break; + } +} + +- (id)initFrom:(NSHTTPURLResponse *)response withBody:(NSData *)data andError:(NSError *)aError { + [self init]; + self.body = data; + if(response) { + self.statusCode = [response statusCode]; + self.headers = [response allHeaderFields]; + self.error = [response errorWithBody:data]; + } + else { + [self normalizeError:aError withBody:data]; + } + return self; +} + +- (BOOL)isSuccess { + return statusCode >= 200 && statusCode < 400; +} + +- (BOOL)isError { + return ![self isSuccess]; +} + +- (void)log { + if ([self isSuccess]) { + debugLog(@"<= %@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); + } + else { + NSLog(@"<= %@", [[[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding] autorelease]); + } +} + +#pragma mark cleanup + +- (void) dealloc +{ + [body release]; + [headers release]; + [super dealloc]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h new file mode 100644 index 0000000..243a6bc --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/CoreSupport.h @@ -0,0 +1,11 @@ +// +// CoreSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+GSub.h" +#import "NSString+InflectionSupport.h" +#import "NSObject+PropertySupport.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h new file mode 100644 index 0000000..d2b9bb3 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.h @@ -0,0 +1,42 @@ +// +// NSString+InflectionSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSString (InflectionSupport) + +/** + * Return the dashed form af this camelCase string: + * + * [@"camelCase" dasherize] //> @"camel-case" + */ +- (NSString *)dasherize; + +/** + * Return the underscored form af this camelCase string: + * + * [@"camelCase" underscore] //> @"camel_case" + */ +- (NSString *)underscore; + +/** + * Return the camelCase form af this dashed/underscored string: + * + * [@"camel-case_string" camelize] //> @"camelCaseString" + */ +- (NSString *)camelize; + +/** + * Return a copy of the string suitable for displaying in a title. Each word is downcased, with the first letter upcased. + */ +- (NSString *)titleize; + +/** + * Return a copy of the string with the first letter capitalized. + */ +- (NSString *)toClassName; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m new file mode 100644 index 0000000..620c5c3 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/Inflections/NSString+InflectionSupport.m @@ -0,0 +1,94 @@ +// +// NSString+InflectionSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+InflectionSupport.h" + +@implementation NSString (InflectionSupport) + +- (NSCharacterSet *)capitals { + return [NSCharacterSet uppercaseLetterCharacterSet]; +} + +- (NSString *)deCamelizeWith:(NSString *)delimiter { + + unichar *buffer = calloc([self length], sizeof(unichar)); + [self getCharacters:buffer ]; + NSMutableString *underscored = [NSMutableString string]; + + NSString *currChar; + for (int i = 0; i < [self length]; i++) { + currChar = [NSString stringWithCharacters:buffer+i length:1]; + if([[self capitals] characterIsMember:buffer[i]]) { + [underscored appendFormat:@"%@%@", delimiter, [currChar lowercaseString]]; + } else { + [underscored appendString:currChar]; + } + } + + free(buffer); + return underscored; +} + + +- (NSString *)dasherize { + return [self deCamelizeWith:@"-"]; +} + +- (NSString *)underscore { + return [self deCamelizeWith:@"_"]; +} + +- (NSCharacterSet *)camelcaseDelimiters { + return [NSCharacterSet characterSetWithCharactersInString:@"-_"]; +} + +- (NSString *)camelize { + + unichar *buffer = calloc([self length], sizeof(unichar)); + [self getCharacters:buffer ]; + NSMutableString *underscored = [NSMutableString string]; + + BOOL capitalizeNext = NO; + NSCharacterSet *delimiters = [self camelcaseDelimiters]; + for (int i = 0; i < [self length]; i++) { + NSString *currChar = [NSString stringWithCharacters:buffer+i length:1]; + if([delimiters characterIsMember:buffer[i]]) { + capitalizeNext = YES; + } else { + if(capitalizeNext) { + [underscored appendString:[currChar uppercaseString]]; + capitalizeNext = NO; + } else { + [underscored appendString:currChar]; + } + } + } + + free(buffer); + return underscored; + +} + +- (NSString *)titleize { + NSArray *words = [self componentsSeparatedByString:@" "]; + NSMutableString *output = [NSMutableString string]; + for (NSString *word in words) { + [output appendString:[[word substringToIndex:1] uppercaseString]]; + [output appendString:[[word substringFromIndex:1] lowercaseString]]; + [output appendString:@" "]; + } + return [output substringToIndex:[self length]]; +} + +- (NSString *)toClassName { + NSString *result = [self camelize]; + return [result stringByReplacingCharactersInRange:NSMakeRange(0,1) + withString:[[result substringWithRange:NSMakeRange(0,1)] uppercaseString]]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h new file mode 100644 index 0000000..c02e3e0 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.h @@ -0,0 +1,12 @@ +//NSData additions from colloquy project + +@interface NSData (NSDataAdditions) ++ (NSData *) dataWithBase64EncodedString:(NSString *) string; +- (id) initWithBase64EncodedString:(NSString *) string; + +- (NSString *) base64Encoding; +- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength; + +- (BOOL) hasPrefix:(NSData *) prefix; +- (BOOL) hasPrefixBytes:(void *) prefix length:(unsigned int) length; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m new file mode 100644 index 0000000..7b281ae --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSData+Additions.m @@ -0,0 +1,161 @@ +//NSData additions from colloquy project + +// Created by khammond on Mon Oct 29 2001. +// Formatted by Timothy Hatcher on Sun Jul 4 2004. +// Copyright (c) 2001 Kyle Hammond. All rights reserved. +// Original development by Dave Winer. + +#import "NSData+Additions.h" + +static char encodingTable[64] = { +'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', +'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', +'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', +'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; + +@implementation NSData (NSDataAdditions) ++ (NSData *) dataWithBase64EncodedString:(NSString *) string { + return [[[NSData allocWithZone:nil] initWithBase64EncodedString:string] autorelease]; +} + +- (id) initWithBase64EncodedString:(NSString *) string { + NSMutableData *mutableData = nil; + + if( string ) { + unsigned long ixtext = 0; + unsigned long lentext = 0; + unsigned char ch = 0; + unsigned char inbuf[4], outbuf[3]; + short i = 0, ixinbuf = 0; + BOOL flignore = NO; + BOOL flendtext = NO; + NSData *base64Data = nil; + const unsigned char *base64Bytes = nil; + + // Convert the string to ASCII data. + base64Data = [string dataUsingEncoding:NSASCIIStringEncoding]; + base64Bytes = [base64Data bytes]; + mutableData = [NSMutableData dataWithCapacity:[base64Data length]]; + lentext = [base64Data length]; + + while( YES ) { + if( ixtext >= lentext ) break; + ch = base64Bytes[ixtext++]; + flignore = NO; + + if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; + else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; + else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; + else if( ch == '+' ) ch = 62; + else if( ch == '=' ) flendtext = YES; + else if( ch == '/' ) ch = 63; + else flignore = YES; + + if( ! flignore ) { + short ctcharsinbuf = 3; + BOOL flbreak = NO; + + if( flendtext ) { + if( ! ixinbuf ) break; + if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; + else ctcharsinbuf = 2; + ixinbuf = 3; + flbreak = YES; + } + + inbuf [ixinbuf++] = ch; + + if( ixinbuf == 4 ) { + ixinbuf = 0; + outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); + outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); + outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); + + for( i = 0; i < ctcharsinbuf; i++ ) + [mutableData appendBytes:&outbuf[i] length:1]; + } + + if( flbreak ) break; + } + } + } + + self = [self initWithData:mutableData]; + return self; +} + +#pragma mark - + +- (NSString *) base64Encoding { + return [self base64EncodingWithLineLength:0]; +} + +- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength { + const unsigned char *bytes = [self bytes]; + NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; + unsigned long ixtext = 0; + unsigned long lentext = [self length]; + long ctremaining = 0; + unsigned char inbuf[3], outbuf[4]; + unsigned short i = 0; + unsigned short charsonline = 0, ctcopy = 0; + unsigned long ix = 0; + + while( YES ) { + ctremaining = lentext - ixtext; + if( ctremaining <= 0 ) break; + + for( i = 0; i < 3; i++ ) { + ix = ixtext + i; + if( ix < lentext ) inbuf[i] = bytes[ix]; + else inbuf [i] = 0; + } + + outbuf [0] = (inbuf [0] & 0xFC) >> 2; + outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); + outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); + outbuf [3] = inbuf [2] & 0x3F; + ctcopy = 4; + + switch( ctremaining ) { + case 1: + ctcopy = 2; + break; + case 2: + ctcopy = 3; + break; + } + + for( i = 0; i < ctcopy; i++ ) + [result appendFormat:@"%c", encodingTable[outbuf[i]]]; + + for( i = ctcopy; i < 4; i++ ) + [result appendString:@"="]; + + ixtext += 3; + charsonline += 4; + + if( lineLength > 0 ) { + if( charsonline >= lineLength ) { + charsonline = 0; + [result appendString:@"\n"]; + } + } + } + + return [NSString stringWithString:result]; +} + +#pragma mark - + +- (BOOL) hasPrefix:(NSData *) prefix { + unsigned int length = [prefix length]; + if( ! prefix || ! length || [self length] < length ) return NO; + return ( memcmp( [self bytes], [prefix bytes], length ) == 0 ); +} + +- (BOOL) hasPrefixBytes:(void *) prefix length:(unsigned int) length { + if( ! prefix || ! length || [self length] < length ) return NO; + return ( memcmp( [self bytes], prefix, length ) == 0 ); +} +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h new file mode 100644 index 0000000..356c003 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.h @@ -0,0 +1,35 @@ +// +// NSObject+Properties.h +// +// +// Created by Ryan Daigle on 7/28/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSObject (PropertySupport) + +/** + * Get the names of all properties and thier types declared on this class. + * + */ ++ (NSDictionary *)propertyNamesAndTypes; + +/** + * Get the names of all properties declared on this class. + */ ++ (NSArray *)propertyNames; + +/** + * Get all the properties and their values of this instance. + **/ +- (NSDictionary *)properties; + +/** + * Set this object's property values, overriding any existing + * values. + */ +- (void)setProperties:(NSDictionary *)overrideProperties; + +- (NSString *)className; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m new file mode 100644 index 0000000..82f88b5 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSObject+PropertySupport.m @@ -0,0 +1,82 @@ +// +// NSObject+Properties.m +// +// +// Created by Ryan Daigle on 7/28/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "objc/runtime.h" +#import "NSObject+PropertySupport.h" + +@interface NSObject() + ++ (NSString *) getPropertyType:(NSString *)attributeString; + +@end + + +@implementation NSObject (PropertySupport) ++ (NSArray *)propertyNames { + return [[self propertyNamesAndTypes] allKeys]; +} + ++ (NSDictionary *)propertyNamesAndTypes { + NSMutableDictionary *propertyNames = [NSMutableDictionary dictionary]; + + //include superclass properties + Class currentClass = [self class]; + while (currentClass != nil) { + // Get the raw list of properties + unsigned int outCount; + objc_property_t *propList = class_copyPropertyList(currentClass, &outCount); + + // Collect the property names + int i; + NSString *propName; + for (i = 0; i < outCount; i++) + { + objc_property_t * prop = propList + i; + NSString *type = [NSString stringWithCString:property_getAttributes(*prop) encoding:NSUTF8StringEncoding]; + propName = [NSString stringWithCString:property_getName(*prop) encoding:NSUTF8StringEncoding]; + if (![propName isEqualToString:@"_mapkit_hasPanoramaID"]) { + [propertyNames setObject:[self getPropertyType:type] forKey:propName]; + } + } + + free(propList); + currentClass = [currentClass superclass]; + } + return propertyNames; +} + +- (NSDictionary *)properties { + return [self dictionaryWithValuesForKeys:[[self class] propertyNames]]; +} + +- (void)setProperties:(NSDictionary *)overrideProperties { + for (NSString *property in [overrideProperties allKeys]) { + [self setValue:[overrideProperties objectForKey:property] forKey:property]; + } +} + ++ (NSString *) getPropertyType:(NSString *)attributeString { + NSString *type = [NSString string]; + NSScanner *typeScanner = [NSScanner scannerWithString:attributeString]; + [typeScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"@"] intoString:NULL]; + + // we are not dealing with an object + if([typeScanner isAtEnd]) { + return @"NULL"; + } + [typeScanner scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"@"] intoString:NULL]; + // this gets the actual object type + [typeScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\""] intoString:&type]; + return type; +} + +- (NSString *)className { + return NSStringFromClass([self class]); +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h new file mode 100644 index 0000000..025f18f --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.h @@ -0,0 +1,19 @@ +// +// NSString+Substitute.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSString (GSub) + +/** + * Perform basic substitution of given key -> value pairs + * within this string. + * + * [@"test string substitution" gsub:[NSDictionary withObjectsAndKeys:@"substitution", @"sub"]]; + * //> @"test string sub" + */ +- (NSString *)gsub:(NSDictionary *)keyValues; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m new file mode 100644 index 0000000..ac5ee99 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/NSString+GSub.m @@ -0,0 +1,25 @@ +// +// NSString+Substitute.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSString+GSub.h" + +@implementation NSString (GSub) + +- (NSString *)gsub:(NSDictionary *)keyValues { + + NSMutableString *subbed = [NSMutableString stringWithString:self]; + + for (NSString *key in keyValues) { + NSString *value = [NSString stringWithFormat:@"%@", [keyValues objectForKey:key]]; + NSArray *splits = [subbed componentsSeparatedByString:key]; + [subbed setString:[splits componentsJoinedByString:value]]; + } + return subbed; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h new file mode 100644 index 0000000..f864971 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.h @@ -0,0 +1,27 @@ +// +// ObjectiveResourceDateFormatter.h +// iphone-harvest +// +// Created by James Burka on 10/21/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import + + +@interface ObjectiveResourceDateFormatter : NSObject + +typedef enum { + Date = 0, + DateTime, +} ORSDateFormat; + ++ (void)setSerializeFormat:(ORSDateFormat)dateFormat; ++ (void)setDateFormatString:(NSString *)format; ++ (void)setDateTimeFormatString:(NSString *)format; ++ (void)setDateTimeZoneFormatString:(NSString *)format; ++ (NSString *)formatDate:(NSDate *)date; ++ (NSDate *)parseDate:(NSString *)dateString; ++ (NSDate *)parseDateTime:(NSString *)dateTimeString; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m new file mode 100644 index 0000000..51771b2 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveResourceDateFormatter.m @@ -0,0 +1,72 @@ +// +// ObjectiveResourceDateFormatter.m +// iphone-harvest +// +// Created by James Burka on 10/21/08. +// Copyright 2008 Burkaprojects. All rights reserved. +// + +#import "ObjectiveResourceDateFormatter.h" + + +@implementation ObjectiveResourceDateFormatter + +static NSString *dateTimeFormatString = @"yyyy-MM-dd'T'HH:mm:ss'Z'"; +static NSString *dateTimeZoneFormatString = @"yyyy-MM-dd'T'HH:mm:ssz"; +static NSString *dateFormatString = @"yyyy-MM-dd"; +static ORSDateFormat _dateFormat; + ++ (void)setSerializeFormat:(ORSDateFormat)dateFormat { + _dateFormat = dateFormat; +} + ++ (void)setDateFormatString:(NSString *)format { + dateFormatString = format; +} + ++ (void)setDateTimeFormatString:(NSString *)format { + dateTimeFormatString = format; +} + ++ (void)setDateTimeZoneFormatString:(NSString *)format { + dateTimeZoneFormatString = format; +} + ++ (NSString *)formatDate:(NSDate *)date { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + if(_dateFormat == Date) { + [formatter setDateFormat:dateFormatString]; + } + else { + [formatter setDateFormat:dateTimeFormatString]; + } + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter stringFromDate:date]; + +} + ++ (NSDate *)parseDateTime:(NSString *)dateTimeString { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + NSString *format = ([dateTimeString hasSuffix:@"Z"]) ? dateTimeFormatString : dateTimeZoneFormatString; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:format]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter dateFromString:dateTimeString]; + +} + ++ (NSDate *)parseDate:(NSString *)dateString { + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:dateFormatString]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; + return [formatter dateFromString:dateString]; + +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h new file mode 100644 index 0000000..33b8cfe --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Core/ObjectiveSupport.h @@ -0,0 +1,11 @@ +// +// ObjectiveSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "CoreSupport.h" +#import "XMLSerializableSupport.h" +#import "JSONSerializableSupport.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h new file mode 100644 index 0000000..78e00a6 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializable.h @@ -0,0 +1,78 @@ +// +// JSONSerializable.h +// +// Created by James Burka on 1/13/09. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@protocol JSONSerializable + +/** + * Instantiate a single instance of this class from the given JSON data. + */ ++ (id)fromJSONData:(NSData *)data; + +/** + * Instantiate a collection of instances of this class from the given JSON data. + */ +//+ (NSArray *)allFromJSONData:(NSData *)data; + +/** + * Get the full JSON representation of this object + * using the default element name: + * + * [myPerson toXMLElement] //> @"Ryan..." + */ +- (NSString *)toJSON; + + +/** + * Gets the full representation of this object minus the elements in the exclusions array + * + * + * + */ +- (NSString *)toJSONExcluding:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name: + * + * [myPerson toXMLElementAs:@"human"] //> @"Ryan..." + */ +- (NSString *)toJSONAs:(NSString *)rootName; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and excluding the given properties. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil]] + * + * //> @"Daigle + */ +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and translating property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"RyanDaigle + */ +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name, excluding the given properties, and translating + * property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil] + * withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"Daigle + */ +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h new file mode 100644 index 0000000..89139f7 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/JSONSerializableSupport.h @@ -0,0 +1,2 @@ +#import "NSObject+JSONSerializableSupport.h" +#import "NSDictionary+JSONSerializableSupport.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h new file mode 100644 index 0000000..69746de --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.h @@ -0,0 +1,17 @@ +// +// NSArray+JSONSerializableSupport.h +// objective_support +// +// Created by James Burka on 2/16/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSArray(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m new file mode 100644 index 0000000..1050522 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSArray+JSONSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSArray+JSONSerializableSupport.m +// objective_support +// +// Created by James Burka on 2/16/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "JSONFramework.h" +#import "NSObject+JSONSerializableSupport.h" +#import "NSArray+JSONSerializableSupport.h" + + +@implementation NSArray(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + NSMutableString *values = [NSMutableString stringWithString:@"["]; + BOOL comma = NO; + for (id element in self) { + if(comma) { + [values appendString:@","]; + } + else { + comma = YES; + } + [values appendString:[element toJSONAs:[element jsonClassName] excludingInArray:exclusions withTranslations:keyTranslations]]; + + } + [values appendString:@"]"]; + return values; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h new file mode 100644 index 0000000..4e962d4 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.h @@ -0,0 +1,14 @@ +// +// NSDictionary+JSONSerialization.h +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSDictionary(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m new file mode 100644 index 0000000..3b0e7d7 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSDictionary+JSONSerializableSupport.m @@ -0,0 +1,30 @@ +// +// NSDictionary+JSONSerialization.m +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "JSONFramework.h" +#import "NSDictionary+KeyTranslation.h" +#import "NSDictionary+JSONSerializableSupport.h" +#import "ObjectiveSupport.h" +#import "Serialize.h" + +@implementation NSDictionary(JSONSerializableSupport) + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + NSMutableDictionary *props = [NSMutableDictionary dictionary]; + for (NSString *key in self) { + if(![exclusions containsObject:key]) { + NSString *convertedKey = [[self class] translationForKey:key withTranslations:keyTranslations]; + [props setObject:[[self objectForKey:key] serialize] forKey:[convertedKey underscore]]; + } + } + return [[NSDictionary dictionaryWithObject:props forKey:rootName]JSONRepresentation]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h new file mode 100644 index 0000000..d395166 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.h @@ -0,0 +1,25 @@ +// +// NSObject+JSONSerializableSupport.h +// active_resource +// +// Created by vickeryj on 1/8/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import +#import "JSONSerializable.h" + +@interface NSObject (JSONSerializableSupport) + ++ (id)fromJSONData:(NSData *)data; ++ (id) deserializeJSON:(id)jsonObject asClass:(Class) claz; +- (NSString *)toJSON; +- (NSString *)toJSONExcluding:(NSArray *)exclusions; +- (NSString *)toJSONAs:(NSString *)rootName; +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; +- (NSString *) jsonClassName; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m new file mode 100644 index 0000000..ca8dfb8 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/JSON/NSObject+JSONSerializableSupport.m @@ -0,0 +1,149 @@ +// +// NSObject+JSONSerializableSupport.m +// active_resource +// +// Created by vickeryj on 1/8/09. +// Copyright 2009 Joshua Vickery. All rights reserved. +// + +#import "NSObject+JSONSerializableSupport.h" +#import "NSDictionary+JSONSerializableSupport.h" +#import "Serialize.h" +#import "JSONFramework.h" +#import "ObjectiveSupport.h" + +@interface NSObject (JSONSerializableSupport_Private) ++ (id) deserializeJSON:(id)jsonObject; +- (NSString *) convertProperty:(NSString *)propertyName andClassName:(NSString *)className; +@end + +@implementation NSObject (JSONSerializableSupport) + ++ (id)fromJSONData:(NSData *)data { + NSString *jsonString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + id jsonObject = [jsonString JSONValue]; + return [self deserializeJSON:jsonObject]; + +} + +- (NSString *)toJSON { + return [self toJSONAs:[self jsonClassName] excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONExcluding:(NSArray *)exclusions { + return [self toJSONAs:[self jsonClassName] excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName { + return [self toJSONAs:rootName excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions { + return [self toJSONAs:rootName excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toJSONAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations { + return [self toJSONAs:rootName excludingInArray:[NSArray array] withTranslations:keyTranslations]; +} + +- (NSString *)toJSONAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + + return [[self properties] toJSONAs:rootName excludingInArray:exclusions withTranslations:keyTranslations]; + +} + ++ (id) propertyClass:(NSString *)className { + return NSClassFromString([className toClassName]); +} + ++ (id) deserializeJSON:(id)jsonObject asClass:(Class) claz { + id result = nil; + + if ([jsonObject isKindOfClass:[NSArray class]]) { + //JSON array + result = [NSMutableArray array]; + for (id childObject in jsonObject) { + [result addObject:[self deserializeJSON:childObject asClass:claz]]; + } + } + else if ([jsonObject isKindOfClass:[NSDictionary class]]) { + //this assumes we are dealing with JSON dictionaries without class names + // { property1 : value, property2 : {property 3 : value }} + result = [[[claz alloc] init] autorelease]; + + NSDictionary *objectPropertyNames = [claz propertyNamesAndTypes]; + + for (NSString *property in [jsonObject allKeys]) { + NSString *propertyCamalized = [property camelize]; + if ([[objectPropertyNames allKeys] containsObject:propertyCamalized]) { + Class propertyClass = [self propertyClass:[objectPropertyNames objectForKey:propertyCamalized]]; + if ([[NSDictionary class] isSubclassOfClass:propertyClass]) { + [result setValue:[jsonObject objectForKey:property] forKey:propertyCamalized]; + } + else { + [result setValue:[self deserializeJSON:[propertyClass deserializeJSON:[jsonObject objectForKey:property] asClass:propertyClass]] forKey:propertyCamalized]; + } + } + } + } + else { + //JSON value + result = jsonObject; + } + return result; +} + ++ (id) deserializeJSON:(id)jsonObject { + id result = nil; + if ([jsonObject isKindOfClass:[NSArray class]]) { + //JSON array + result = [NSMutableArray array]; + for (id childObject in jsonObject) { + [result addObject:[self deserializeJSON:childObject]]; + } + } + else if ([jsonObject isKindOfClass:[NSDictionary class]]) { + //JSON object + //this assumes we are dealing with JSON in the form rails provides: + // {className : { property1 : value, property2 : {class2Name : {property 3 : value }}}} + NSString *objectName = [[(NSDictionary *)jsonObject allKeys] objectAtIndex:0]; + + Class objectClass = NSClassFromString([objectName toClassName]); + if (objectClass != nil) { + //classname matches, instantiate a new instance of the class and set it as the current parent object + result = [[[objectClass alloc] init] autorelease]; + } + + NSDictionary *properties = (NSDictionary *)[[(NSDictionary *)jsonObject allValues] objectAtIndex:0]; + + NSDictionary *objectPropertyNames = [objectClass propertyNamesAndTypes]; + + for (NSString *property in [properties allKeys]) { + NSString *propertyCamalized = [[self convertProperty:property andClassName:objectName] camelize]; + if ([[objectPropertyNames allKeys]containsObject:propertyCamalized]) { + Class propertyClass = [self propertyClass:[objectPropertyNames objectForKey:propertyCamalized]]; + [result setValue:[self deserializeJSON:[propertyClass deserialize:[properties objectForKey:property]]] forKey:propertyCamalized]; + } + } + } + else { + //JSON value + result = jsonObject; + } + return result; +} + +- (NSString *) convertProperty:(NSString *)propertyName andClassName:(NSString *)className { + if([propertyName isEqualToString:@"id"]) { + propertyName = [NSString stringWithFormat:@"%@_id",className]; + } + return propertyName; +} + +- (NSString *)jsonClassName { + NSString *className = NSStringFromClass([self class]); + return [[className stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[className substringToIndex:1] lowercaseString]] underscore]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h new file mode 100644 index 0000000..048bd3e --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.h @@ -0,0 +1,13 @@ +// +// NSDate+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSDate(Serialize) + ++ (NSDate *) deserialize:(id)value; +- (NSString *) serialize; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m new file mode 100644 index 0000000..3d06aca --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDate+Serialize.m @@ -0,0 +1,23 @@ +// +// NSDate+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSDate+Serialize.h" +#import "ObjectiveResourceDateFormatter.h" + + +@implementation NSDate(Serialize) + ++ (NSDate *) deserialize:(id)value { + return (value == [NSNull null]) ? nil : [ObjectiveResourceDateFormatter parseDateTime:value]; +} + +- (NSString *) serialize { + return [ObjectiveResourceDateFormatter formatDate:self]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h new file mode 100644 index 0000000..9c7b8e7 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.h @@ -0,0 +1,16 @@ +// +// NSDictionary+KeyTranslation.h +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSDictionary(KeyTranslation) + ++ (NSString *)translationForKey:(NSString *)key withTranslations:(NSDictionary *)keyTranslations; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m new file mode 100644 index 0000000..d1b418c --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSDictionary+KeyTranslation.m @@ -0,0 +1,19 @@ +// +// NSDictionary+KeyTranslation.m +// active_resource +// +// Created by James Burka on 1/15/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSDictionary+KeyTranslation.h" + + +@implementation NSDictionary(KeyTranslation) + ++ (NSString *)translationForKey:(NSString *)key withTranslations:(NSDictionary *)keyTranslations { + NSString *newKey = [keyTranslations objectForKey:key]; + return (newKey ? newKey : key); +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h new file mode 100644 index 0000000..5d2b1af --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.h @@ -0,0 +1,14 @@ +// +// NSObject+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSObject(Serialize) + ++ (id) deserialize:(id)value; +- (id) serialize; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m new file mode 100644 index 0000000..9401852 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSObject+Serialize.m @@ -0,0 +1,22 @@ +// +// NSObject+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+Serialize.h" + + +@implementation NSObject(Serialize) + ++ (id)deserialize:(id)value { + return value; +} + +- (id) serialize { + return self; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h new file mode 100644 index 0000000..a597706 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.h @@ -0,0 +1,16 @@ +// +// NSString+Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSString(Deserialize) + ++ (NSString *)deserialize:(id)value; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m new file mode 100644 index 0000000..27bdc8d --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/NSString+Serialize.m @@ -0,0 +1,19 @@ +// +// NSString+Deserialize.m +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSString+Serialize.h" + + +@implementation NSString(Serialize) + ++ (NSString *) deserialize:(id)value { + return [NSString stringWithFormat:@"%@",value]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h new file mode 100644 index 0000000..f0820dc --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/Serialize.h @@ -0,0 +1,11 @@ +// +// Deserialize.h +// active_resource +// +// Created by James Burka on 1/19/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+Serialize.h" +#import "NSDate+Serialize.h" +#import "NSString+Serialize.h" diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h new file mode 100644 index 0000000..d03cced --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.h @@ -0,0 +1,32 @@ +// +// FromXMLElementDelegate.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface FromXMLElementDelegate : NSObject { + Class targetClass; + id parsedObject; + NSString *currentPropertyName; + NSMutableString *contentOfCurrentProperty; + NSMutableArray *unclosedProperties; + NSString *currentPropertyType; +} + +@property (nonatomic, retain) Class targetClass; +@property (nonatomic, retain) id parsedObject; +@property (nonatomic, retain) NSString *currentPropertyName; +@property (nonatomic, retain) NSMutableString *contentOfCurrentProperty; +@property (nonatomic, retain) NSMutableArray *unclosedProperties; +@property (nonatomic, retain) NSString *currentPropertyType; + ++ (FromXMLElementDelegate *)delegateForClass:(Class)targetClass; + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict; +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string; +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName; +- (NSString *)convertElementName:(NSString *)anElementName; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m new file mode 100644 index 0000000..ca30a4f --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/FromXMLElementDelegate.m @@ -0,0 +1,188 @@ +// +// FromXMLElementDelegate.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "FromXMLElementDelegate.h" +#import "XMLSerializableSupport.h" +#import "CoreSupport.h" + +@implementation FromXMLElementDelegate + +@synthesize targetClass, parsedObject, currentPropertyName, contentOfCurrentProperty, unclosedProperties, currentPropertyType; + ++ (FromXMLElementDelegate *)delegateForClass:(Class)targetClass { + FromXMLElementDelegate *delegate = [[[self alloc] init] autorelease]; + [delegate setTargetClass:targetClass]; + return delegate; +} + + +- (id)init { + super; + self.unclosedProperties = [NSMutableArray array]; + return self; +} + + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict +{ + if ([@"nil-classes" isEqualToString:elementName]) { + //empty result set, do nothing + } + + //Start of the root object + else if (parsedObject == nil) { + if ([elementName isEqualToString:[self.targetClass xmlElementName]]) { + self.parsedObject = [[[self.targetClass alloc] init] autorelease]; + } + else if ([@"array" isEqualToString:[attributeDict objectForKey:@"type"]]) { + self.parsedObject = [NSMutableArray array]; + } + [self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]]; + self.currentPropertyName = elementName; + } + + else { + //if we are inside another element and it is not the current parent object, + // then create an object for that parent element + if(self.currentPropertyName != nil && (![self.currentPropertyName isEqualToString:[[self.parsedObject class] xmlElementName]])) { + Class elementClass = NSClassFromString([currentPropertyName toClassName]); + if (elementClass != nil) { + //classname matches, instantiate a new instance of the class and set it as the current parent object + self.parsedObject = [[[elementClass alloc] init] autorelease]; + [self.unclosedProperties addObject:[NSArray arrayWithObjects:self.currentPropertyName, self.parsedObject, nil]]; + } + } + + // If we recognize an element that corresponds to a known property of the current parent object, or if the + // current parent is an array then start collecting content for this child element + + if (([self.parsedObject isKindOfClass:[NSArray class]]) || + ([[[self.parsedObject class] propertyNames] containsObject:[[self convertElementName:elementName] camelize]])) { + self.currentPropertyName = [self convertElementName:elementName]; + if ([@"array" isEqualToString:[attributeDict objectForKey:@"type"]]) { + self.parsedObject = [NSMutableArray array]; + [self.unclosedProperties addObject:[NSArray arrayWithObjects:elementName, self.parsedObject, nil]]; + } + else { + self.contentOfCurrentProperty = [NSMutableString string]; + self.currentPropertyType = [attributeDict objectForKey:@"type"]; + } + } + else { + // The element isn't one that we care about, so set the property that holds the + // character content of the current element to nil. That way, in the parser:foundCharacters: + // callback, the string that the parser reports will be ignored. + self.currentPropertyName = nil; + self.contentOfCurrentProperty = nil; + } + } +} + +// Characters (i.e. an element value - retarded, I know) are given to us in chunks, +// so we need to append them onto the current property value. +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + + // If the current property is nil, then we know we're currently at + // an element that we don't know about or don't care about + if (self.contentOfCurrentProperty) { + + // Append string value on since we're given them in chunks + [self.contentOfCurrentProperty appendString:string]; + } +} + +//Basic type conversion based on the ObjectiveResource "type" attribute +- (id) convertProperty:(NSString *)propertyValue toType:(NSString *)type { + if ([type isEqualToString:@"datetime" ]) { + return [NSDate fromXMLDateTimeString:propertyValue]; + } + else if ([type isEqualToString:@"date"]) { + return [NSDate fromXMLDateString:propertyValue]; + } + + // uncomment this if you what to support NSNumber and NSDecimalNumber + // if you do your classId must be a NSNumber since rails will pass it as such + //else if ([type isEqualToString:@"decimal"]) { + // return [NSDecimalNumber decimalNumberWithString:propertyValue]; + //} + //else if ([type isEqualToString:@"integer"]) { + // return [NSNumber numberWithInt:[propertyValue intValue]]; + //} + + else { + return [NSString fromXmlString:propertyValue]; + } +} + +// Converts the Id element to modelNameId +- (NSString *) convertElementName:(NSString *)anElementName { + + if([anElementName isEqualToString:@"id"]) { + return [NSString stringWithFormat:@"%@Id",[[[self.parsedObject class]xmlElementName] camelize]]; + // return [NSString stringWithFormat:@"%@_%@" , [NSStringFromClass([self.parsedObject class]) +// stringByReplacingCharactersInRange:NSMakeRange(0, 1) +// withString:[[NSStringFromClass([self.parsedObject class]) +// substringWithRange:NSMakeRange(0,1)] +// lowercaseString]], anElementName]; + } + else { + + return anElementName; + + } + +} + +// We're done receiving the value of a particular element, so take the value we've collected and +// set it on the current object +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + if(self.contentOfCurrentProperty != nil && self.currentPropertyName != nil) { + [self.parsedObject + setValue:[self convertProperty:self.contentOfCurrentProperty toType:self.currentPropertyType] + forKey:[self.currentPropertyName camelize]]; + } + else if ([self.currentPropertyName isEqualToString:[self convertElementName:elementName]]) { + //element is closed, pop it from the stack + [self.unclosedProperties removeLastObject]; + //check for a parent object on the stack + if ([self.unclosedProperties count] > 0) { + //handle arrays as a special case + if ([[[self.unclosedProperties lastObject] objectAtIndex:1] isKindOfClass:[NSArray class]]) { + [[[self.unclosedProperties lastObject] objectAtIndex:1] addObject:self.parsedObject]; + } + else { + [[[self.unclosedProperties lastObject] objectAtIndex:1] setValue:self.parsedObject forKey:[self convertElementName:[elementName camelize]]]; + } + self.parsedObject = [[self.unclosedProperties lastObject] objectAtIndex:1]; + } + } + + self.contentOfCurrentProperty = nil; + + //set the parent object, if one exists, as the current element + if ([self.unclosedProperties count] > 0) { + self.currentPropertyName = [[self.unclosedProperties lastObject] objectAtIndex:0]; + } +} + + +#pragma mark cleanup + +- (void)dealloc { + [targetClass release]; + [currentPropertyName release]; + [parsedObject release]; + [contentOfCurrentProperty release]; + [unclosedProperties release]; + [currentPropertyType release]; + [super dealloc]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h new file mode 100644 index 0000000..21213af --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.h @@ -0,0 +1,13 @@ +// +// NSArray+XMLSerializableSupport.h +// +// +// Created by vickeryj on 9/29/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import + +@interface NSArray (XMLSerializableSupport) +- (NSString *)toXMLValue; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m new file mode 100644 index 0000000..2afaa35 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSArray+XMLSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSArray+XMLSerializableSupport.m +// +// +// Created by vickeryj on 9/29/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import "NSArray+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" + + +@implementation NSArray (XMLSerializableSupport) + +- (NSString *)toXMLValue { + NSMutableString *result = [NSMutableString string]; + + for (id element in self) { + [result appendString:[element toXMLElement]]; + } + + return result; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + NSMutableString *elementValue = [NSMutableString string]; + for (id element in self) { + [elementValue appendString:[element toXMLElementAs:[[element class] xmlElementName] excludingInArray:exclusions withTranslations:keyTranslations]]; + } + return [[self class] buildXmlElementAs:rootName withInnerXml:elementValue andType:@"array"]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h new file mode 100644 index 0000000..1f3ef27 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.h @@ -0,0 +1,13 @@ +// +// NSDate+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSDate (XMLSerializableSupport) +- (NSString *)toXMLValue; ++ (NSDate *)fromXMLDateTimeString:(NSString *)xmlString; ++ (NSDate *)fromXMLDateString:(NSString *)xmlString; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m new file mode 100644 index 0000000..5ea5abd --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDate+XMLSerializableSupport.m @@ -0,0 +1,35 @@ +// +// NSDate+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSDate+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "ObjectiveResourceDateFormatter.h" + +@implementation NSDate (XMLSerializableSupport) + +//FIXME we should have one formatter for the entire app + +- (NSString *)toXMLValue { + return [ ObjectiveResourceDateFormatter formatDate:self]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue] andType:[[self class] xmlTypeFor:self]]; +} + ++ (NSDate *)fromXMLDateTimeString:(NSString *)xmlString { + return [ ObjectiveResourceDateFormatter parseDateTime:xmlString]; +} + ++ (NSDate *)fromXMLDateString:(NSString *)xmlString { + return [ ObjectiveResourceDateFormatter parseDate:xmlString]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h new file mode 100644 index 0000000..957bfd1 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.h @@ -0,0 +1,17 @@ +// +// NSDictionary+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + + +@interface NSDictionary (XMLSerializableSupport) + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations andType:(NSString *)xmlType; + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m new file mode 100644 index 0000000..f973aef --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSDictionary+XMLSerializableSupport.m @@ -0,0 +1,37 @@ +// +// NSDictionary+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSDictionary+KeyTranslation.h" + +@implementation NSDictionary (XMLSerializableSupport) + + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations andType:(NSString *)xmlType { + + NSMutableString* elementValue = [NSMutableString string]; + id value; + NSString *propertyRootName; + for (NSString *key in self) { + // Create XML if key not in exclusion list + if(![exclusions containsObject:key]) { + value = [self valueForKey:key]; + propertyRootName = [[self class] translationForKey:key withTranslations:keyTranslations]; + [elementValue appendString:[value toXMLElementAs:propertyRootName]]; + } + } + return [[self class] buildXmlElementAs:rootName withInnerXml:elementValue andType:xmlType]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [self toXMLElementAs:rootName excludingInArray:exclusions withTranslations:keyTranslations andType:nil]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h new file mode 100644 index 0000000..2574f8f --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.h @@ -0,0 +1,11 @@ +// +// NSNull+XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@interface NSNull (XMLSerializableSupport) +- (NSString *)toXMLValue; +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m new file mode 100644 index 0000000..e725858 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNull+XMLSerializableSupport.m @@ -0,0 +1,17 @@ +// +// NSNull+XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSNull+XMLSerializableSupport.h" + +@implementation NSNull (XMLSerializableSupport) + +- (NSString *)toXMLValue { + return @""; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h new file mode 100644 index 0000000..415dfce --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.h @@ -0,0 +1,12 @@ +// +// NSNumber+XMLSerializableSupport.h +// objective_support +// +// Created by James Burka on 2/17/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +@interface NSNumber(XMLSerializableSupport) +- (NSString *)toXMLValue; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m new file mode 100644 index 0000000..7436b34 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSNumber+XMLSerializableSupport.m @@ -0,0 +1,25 @@ +// +// NSNumber+XMLSerializableSupport.m +// objective_support +// +// Created by James Burka on 2/17/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSNumber+XMLSerializableSupport.h" + + +@implementation NSNumber(XMLSerializableSupport) + +- (NSString *)toXMLValue { + return [self stringValue]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue] andType:[[self class] xmlTypeFor:self]]; +} + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h new file mode 100644 index 0000000..33a0ff7 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.h @@ -0,0 +1,54 @@ +// +// XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "XMLSerializable.h" + +@interface NSObject (XMLSerializableSupport) + ++ (NSString *)xmlTypeFor:(NSObject *)value; ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value andType:(NSString *)xmlType; + +/** + * Construct a string representation of the given value object, assuming + * the given root element name , the NSObjects's toXmlValue is called. + */ ++ (NSString *)buildXMLElementAs:(NSString *)rootName withValue:(NSObject *)value; + +/** + * + * Constructs the actual xml node as a string + * + */ ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value; + +/** + * What is the element name for this object when serialized to XML. + * Defaults to lower case and dasherized form of class name. + * I.e. [[Person class] xmlElementName] //> @"person" + */ ++ (NSString *)xmlElementName; + +/** + * Construct the XML string representation of this particular object. + * Only construct the markup for the value of this object, don't assume + * any naming. For instance: + * + * [myObject toXMLValue] //> @"xmlSerializedValue" + * + * and not + * + * [myObject toXMLValue] //> @"xmlSerializedValue" + * + * For simple objects like strings, numbers etc this will be the text value of + * the corresponding element. For complex objects this can include further markup: + * + * [myPersonObject toXMLValue] //> @"RyanDaigle" + */ +- (NSString *)toXMLValue; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m new file mode 100644 index 0000000..09fee6d --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSObject+XMLSerializableSupport.m @@ -0,0 +1,132 @@ +// +// XMLSerializableSupport.m +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "NSObject+XMLSerializableSupport.h" +#import "NSDictionary+XMLSerializableSupport.h" +#import "CoreSupport.h" +#import "FromXMLElementDelegate.h" + +@implementation NSObject (XMLSerializableSupport) + +# pragma mark XML utility methods + +/** + * Get the appropriate xml type, if any, for the given value. + * I.e. "integer" or "decimal" etc... for use in element attributes: + * + * 1 + */ ++ (NSString *)xmlTypeFor:(NSObject *)value { + + // Can't do this with NSDictionary w/ Class keys. Explore more elegant solutions. + // TODO: Account for NSValue native types here? + if ([value isKindOfClass:[NSDate class]]) { + return @"datetime"; + } else if ([value isKindOfClass:[NSDecimalNumber class]]) { + return @"decimal"; + } else if ([value isKindOfClass:[NSNumber class]]) { + if (0 == strcmp("f",[(NSNumber *)value objCType]) || + 0 == strcmp("d",[(NSNumber *)value objCType])) + { + return @"decimal"; + } + else { + return @"integer"; + } + } else if ([value isKindOfClass:[NSArray class]]) { + return @"array"; + } else { + return nil; + } +} + ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value andType:(NSString *)xmlType{ + NSString *dashedName = [rootName dasherize]; + + if (xmlType != nil) { + return [NSString stringWithFormat:@"<%@ type=\"%@\">%@", dashedName, xmlType, value, dashedName]; + } else { + return [NSString stringWithFormat:@"<%@>%@", dashedName, value, dashedName]; + } +} + ++ (NSString *)buildXmlElementAs:(NSString *)rootName withInnerXml:(NSString *)value { + return [[self class] buildXmlElementAs:rootName withInnerXml:value andType:nil]; +} + ++ (NSString *)buildXMLElementAs:(NSString *)rootName withValue:(NSObject *)value { + return [[self class] buildXmlElementAs:rootName withInnerXml:[value toXMLValue] andType:[self xmlTypeFor:value]]; +} + ++ (NSString *)xmlElementName { + NSString *className = NSStringFromClass(self); + return [[className stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[className substringToIndex:1] lowercaseString]] dasherize]; +} + +# pragma mark XMLSerializable implementation methods + +- (NSString *)toXMLElement { + return [self toXMLElementAs:[[self class] xmlElementName] excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementExcluding:(NSArray *)exclusions { + return [self toXMLElementAs:[[self class] xmlElementName] excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName { + return [self toXMLElementAs:rootName excludingInArray:[NSArray array] withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions { + return [self toXMLElementAs:rootName excludingInArray:exclusions withTranslations:[NSDictionary dictionary]]; +} + +- (NSString *)toXMLElementAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations { + return [self toXMLElementAs:rootName excludingInArray:[NSArray array] withTranslations:keyTranslations]; +} + +/** + * Override in complex objects to account for nested properties + **/ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self properties] toXMLElementAs:rootName excludingInArray:exclusions withTranslations:keyTranslations andType:[[self class] xmlTypeFor:self]]; +} + +# pragma mark XML Serialization convenience methods + +/** + * Override in objects that need special formatting before being printed to XML + **/ +- (NSString *)toXMLValue { + return [NSString stringWithFormat:@"%@", self]; +} + +# pragma mark XML Serialization input methods + ++ (id)fromXMLData:(NSData *)data { + + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + FromXMLElementDelegate *delegate = [FromXMLElementDelegate delegateForClass:self]; + [parser setDelegate:delegate]; + + // Turn off all those XML nits + [parser setShouldProcessNamespaces:NO]; + [parser setShouldReportNamespacePrefixes:NO]; + [parser setShouldResolveExternalEntities:NO]; + + // Let'er rip + [parser parse]; + [parser release]; + return delegate.parsedObject; +} + ++ (NSArray *)allFromXMLData:(NSData *)data { + return [self fromXMLData:data]; +} +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h new file mode 100644 index 0000000..1868a96 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.h @@ -0,0 +1,20 @@ +// +// NSString+XMLSerializableSupport.h +// active_resource +// +// Created by James Burka on 1/6/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import + + +@interface NSString(XMLSerializableSupport) + ++ (NSString *)fromXmlString:(NSString *)aString; +- (NSString *)toXMLValue; +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m new file mode 100644 index 0000000..8682790 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/NSString+XMLSerializableSupport.m @@ -0,0 +1,33 @@ +// +// NSString+XMLSerializableSupport.m +// active_resource +// +// Created by James Burka on 1/6/09. +// Copyright 2009 Burkaprojects. All rights reserved. +// + +#import "NSString+XMLSerializableSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "NSString+GSub.h" + + +@implementation NSString(XMLSerializableSupport) + ++ (NSString *)fromXmlString:(NSString *)aString { + NSDictionary* escapeChars = [NSDictionary dictionaryWithObjectsAndKeys:@"&",@"&",@"\"",@""",@"'",@"'" + ,@"<",@"<",@">",@">",nil]; + return [aString gsub:escapeChars]; + +} + +- (NSString *)toXMLValue { + NSString *temp = [self gsub:[NSDictionary dictionaryWithObject:@"&" forKey:@"&"]]; + NSDictionary* escapeChars = [NSDictionary dictionaryWithObjectsAndKeys:@""",@"\"",@"'",@"'",@"<",@"<",@">",@">",nil]; + return [temp gsub:escapeChars]; +} +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations { + return [[self class] buildXmlElementAs:rootName withInnerXml:[self toXMLValue]]; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h new file mode 100644 index 0000000..59054b9 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializable.h @@ -0,0 +1,79 @@ +// +// XMLSerializable.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +@protocol XMLSerializable + +/** + * Instantiate a single instance of this class from the given XML data. + */ ++ (id)fromXMLData:(NSData *)data; + +/** + * Instantiate a collectionof instances of this class from the given XML data. + */ ++ (NSArray *)allFromXMLData:(NSData *)data; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the default element name: + * + * [myPerson toXMLElement] //> @"Ryan..." + */ +- (NSString *)toXMLElement; + + +/** + * Gets the full representation of this object minus the elements in the exclusions array + * + * + * + */ +- (NSString *)toXMLElementExcluding:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name: + * + * [myPerson toXMLElementAs:@"human"] //> @"Ryan..." + */ +- (NSString *)toXMLElementAs:(NSString *)rootName; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and excluding the given properties. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil]] + * + * //> @"Daigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name and translating property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"RyanDaigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName withTranslations:(NSDictionary *)keyTranslations; + +/** + * Get the full XML representation of this object (minus the xml directive) + * using the given element name, excluding the given properties, and translating + * property names with the keyTranslations mapping. + * + * [myPerson toXMLElementAs:@"human" excludingInArray:[NSArray arrayWithObjects:@"firstName", nil] + * withTranslations:[NSDictionary dictionaryWithObjectsAndKeys:@"lastName", @"surname", nil]] + * + * //> @"Daigle + */ +- (NSString *)toXMLElementAs:(NSString *)rootName excludingInArray:(NSArray *)exclusions + withTranslations:(NSDictionary *)keyTranslations; + +@end \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h new file mode 100644 index 0000000..267ba42 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/Serialization/XML/XMLSerializableSupport.h @@ -0,0 +1,14 @@ +// +// XMLSerializableSupport.h +// +// +// Created by Ryan Daigle on 7/31/08. +// Copyright 2008 yFactorial, LLC. All rights reserved. +// + +#import "XMLSerializable.h" +#import "CoreSupport.h" +#import "NSObject+XMLSerializableSupport.h" +#import "NSNull+XMLSerializableSupport.h" +#import "NSDate+XMLSerializableSupport.h" +#import "NSString+XMLSerializableSupport.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h new file mode 100644 index 0000000..2a9274d --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/JSONFramework.h @@ -0,0 +1,11 @@ +// +// JSONFramework.h +// iphoneAndRails1 +// +// Created by vickeryj on 12/11/08. +// Copyright 2008 Joshua Vickery. All rights reserved. +// + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" \ No newline at end of file diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h new file mode 100644 index 0000000..038ea8e --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2007 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 + + +/// Adds JSON generation to NSObject subclasses +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m new file mode 100644 index 0000000..df6749b --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSObject+SBJSON.m @@ -0,0 +1,57 @@ +/* +Copyright (C) 2007 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+SBJSON.h" +#import "SBJSON.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJSON *generator = [[SBJSON new] autorelease]; + + NSError *error; + NSString *json = [generator stringWithFragment:self error:&error]; + + if (!json) + NSLog(@"%@", error); + return json; +} + +- (NSString *)JSONRepresentation { + SBJSON *generator = [[SBJSON new] autorelease]; + + NSError *error; + NSString *json = [generator stringWithObject:self error:&error]; + + if (!json) + NSLog(@"%@", error); + return json; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h new file mode 100644 index 0000000..69cfa4f --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2007 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 + +/// Adds JSON parsing to NSString +@interface NSString (NSString_SBJSON) + +/// Returns the object represented in the receiver, or nil on error. +- (id)JSONFragmentValue; + +/// Returns the dictionary or array represented in the receiver, or nil on error. +- (id)JSONValue; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m new file mode 100644 index 0000000..69878da --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/NSString+SBJSON.m @@ -0,0 +1,60 @@ +/* +Copyright (C) 2007 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 "NSString+SBJSON.h" +#import "SBJSON.h" + + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJSON *json = [[SBJSON new] autorelease]; + + NSError *error; + id o = [json fragmentWithString:self error:&error]; + + if (!o) + NSLog(@"%@", error); + return o; +} + +- (id)JSONValue +{ + SBJSON *json = [[SBJSON new] autorelease]; + + NSError *error; + id o = [json objectWithString:self error:&error]; + + if (!o) + NSLog(@"%@", error); + return o; +} + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h new file mode 100644 index 0000000..c931d46 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.h @@ -0,0 +1,137 @@ +/* +Copyright (C) 2008 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 + +extern NSString * SBJSONErrorDomain; + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** +@brief A strict JSON parser and generator + +This is the parser and generator underlying the categories added to +NSString and various other objects. + +Objective-C types are mapped to JSON types and back in the following way: + +@li NSNull -> Null -> NSNull +@li NSString -> String -> NSMutableString +@li NSArray -> Array -> NSMutableArray +@li NSDictionary -> Object -> NSMutableDictionary +@li NSNumber (-initWithBool:) -> Boolean -> NSNumber -initWithBool: +@li NSNumber -> Number -> NSDecimalNumber + +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 throw an exception. + +NSNumber instances created with the +numberWithBool: 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. JSON numbers turn into NSDecimalNumber instances, +as we can thus avoid any loss of precision. + +Strictly speaking correctly formed JSON text must have exactly +one top-level container. (Either an Array or an Object.) Scalars, +i.e. nulls, numbers, booleans and strings, are not valid JSON on their own. +It can be quite convenient to pretend that such fragments are valid +JSON however, and this class lets you do so. + +This class does its best to be as strict as possible, both in what it +accepts and what it generates. (Other than the above mentioned support +for JSON fragments.) 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. + +*/ +@interface SBJSON : NSObject { + BOOL humanReadable; + BOOL sortKeys; + NSUInteger maxDepth; + +@private + // Used temporarily during scanning/generation + NSUInteger depth; + const char *c; +} + +/// Whether we are generating human-readable (multiline) JSON +/** + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/// Whether or not to sort the dictionary keys in the output +/** The default is to not sort the keys. */ +@property BOOL sortKeys; + +/// The maximum depth the parser will go to +/** Defaults to 512. */ +@property NSUInteger maxDepth; + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error; + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + +@end diff --git a/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m new file mode 100644 index 0000000..7a6ad54 --- /dev/null +++ b/code/iphone_app/objectiveresource/Classes/lib/objective_support/Classes/lib/json-framework/SBJSON.m @@ -0,0 +1,740 @@ +/* +Copyright (C) 2008 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 "SBJSON.h" + +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + +@interface SBJSON (Generator) + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error; + +- (NSString*)indent; + +@end + +@interface SBJSON (Scanner) + +- (BOOL)scanValue:(NSObject **)o error:(NSError **)error; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error; +- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error; +- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error; +- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error; +- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error; + +- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error; +- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error; + +- (BOOL)scanIsAtEnd; + +@end + +#pragma mark Private utilities + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + +static NSError *err(int code, NSString *str) { + NSDictionary *ui = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui]; +} + +static NSError *errWithUnderlier(int code, NSError **u, NSString *str) { + if (!u) + return err(code, str); + + NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + *u, NSUnderlyingErrorKey, + nil]; + return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui]; +} + + +@implementation SBJSON + +static char ctrl[0x22]; + ++ (void)initialize +{ + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +- (id)init { + if (self = [super init]) { + [self setMaxDepth:512]; + } + return self; +} + +#pragma mark Generator + + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + NSError *err2 = nil; + if (!allowScalar && ![value isKindOfClass:[NSDictionary class]] && ![value isKindOfClass:[NSArray class]]) { + err2 = err(EFRAGMENT, @"Not valid type for JSON"); + + } else if ([self appendValue:value into:json error:&err2]) { + return json; + } + + if (error) + *error = err2; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value allowScalar:YES error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value allowScalar:NO error:error]; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json error:error]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + + } else { + *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]); + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error { + [json appendString:@"["]; + depth++; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json error:error]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error { + [json appendString:@"{"]; + depth++; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + *error = err(EUNSUPPORTED, @"JSON object key must be string"); + return NO; + } + + if (![self appendString:value into:json error:error]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json error:error]) { + *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"Unsupported value for key %@ in object", value]); + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error { + + static NSMutableCharacterSet *kEscapeChars; + if( ! kEscapeChars ) { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; + } + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + [json appendFormat:@"%C", uc]; + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + +#pragma mark Parser + +/** + 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 + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(id)repr allowScalar:(BOOL)allowScalar error:(NSError**)error { + + if (!repr) { + if (error) + *error = err(EINPUT, @"Input was 'nil'"); + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + NSError *err2 = nil; + if (![self scanValue:&o error:&err2]) { + if (error) + *error = err2; + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + if (error) + *error = err(ETRAILGARBAGE, @"Garbage after JSON"); + return nil; + } + + // If we don't allow scalars, check that the object we've found is a valid JSON container. + if (!allowScalar && ![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + if (error) + *error = err(EFRAGMENT, @"Valid fragment, but not JSON"); + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +/** + 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 + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr allowScalar:YES error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr allowScalar:NO error:error]; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o error:(NSError **)error +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o error:error]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o error:error]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o error:error]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o error:error]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o error:error]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o error:error]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o error:error]; + break; + case '+': + *error = err(EPARSENUM, @"Leading + disallowed in number"); + return NO; + break; + case 0x0: + *error = err(EEOF, @"Unexpected end of string"); + return NO; + break; + default: + *error = err(EPARSE, @"Unrecognised leading character"); + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + *error = err(EPARSE, @"Expected 'true'"); + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + *error = err(EPARSE, @"Expected 'false'"); + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error +{ + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + *error = err(EPARSE, @"Expected 'null'"); + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error +{ + if (maxDepth && ++depth > maxDepth) { + *error = err(EDEPTH, @"Nested too deep"); + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v error:error]) { + *error = errWithUnderlier(EPARSE, error, @"Expected value while parsing array"); + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + *error = err(ETRAILCOMMA, @"Trailing comma disallowed in array"); + return NO; + } + } + } + + *error = err(EEOF, @"End of input while parsing array"); + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error +{ + if (maxDepth && ++depth > maxDepth) { + *error = err(EDEPTH, @"Nested too deep"); + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k error:error])) { + *error = errWithUnderlier(EPARSE, error, @"Object key string expected"); + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + *error = err(EPARSE, @"Expected ':' separating key and value"); + return NO; + } + + c++; + if (![self scanValue:&v error:error]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + *error = errWithUnderlier(EPARSE, error, string); + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + *error = err(ETRAILCOMMA, @"Trailing comma disallowed in object"); + return NO; + } + } + } + + *error = err(EEOF, @"End of input while parsing object"); + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc error:error]) { + *error = errWithUnderlier(EUNICODE, error, @"Broken unicode character"); + return NO; + } + c--; // hack. + break; + default: + *error = err(EESCAPE, [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]); + return NO; + break; + } + [*o appendFormat:@"%C", uc]; + c++; + + } else if (*c < 0x20) { + *error = err(ECTRL, [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]); + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + *error = err(EEOF, @"Unexpected EOF while parsing string"); + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi error:error]) { + *error = err(EUNICODE, @"Missing hex quad"); + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo error:error])) { + *error = errWithUnderlier(EUNICODE, error, @"Missing low character in surrogate pair"); + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + *error = err(EUNICODE, @"Invalid low surrogate char"); + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + *error = err(EUNICODE, @"Invalid high character in surrogate pair"); + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + *error = err(EUNICODE, @"Missing hex digit in quad"); + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + *error = err(EPARSENUM, @"Leading 0 disallowed in number"); + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + *error = err(EPARSENUM, @"No digits after initial minus"); + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + *error = err(EPARSENUM, @"No digits after decimal point"); + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + *error = err(EPARSENUM, @"No digits after exponent"); + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + *error = err(EPARSENUM, @"Failed creating decimal instance"); + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + + +#pragma mark Properties + +@synthesize humanReadable; +@synthesize sortKeys; +@synthesize maxDepth; + +@end diff --git a/code/iphone_app/objectiveresource/LICENSE b/code/iphone_app/objectiveresource/LICENSE new file mode 100644 index 0000000..cddb33c --- /dev/null +++ b/code/iphone_app/objectiveresource/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008 Y Factorial, LLC + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/code/iphone_app/objectiveresource/README.textile b/code/iphone_app/objectiveresource/README.textile new file mode 100644 index 0000000..370c9f3 --- /dev/null +++ b/code/iphone_app/objectiveresource/README.textile @@ -0,0 +1,39 @@ +h2. Overview + +ObjectiveResource is a port of Rails' ActiveResource framework to Objective-C. + +The primary purpose of this project is to quickly and easily connect +iPhone applications with servers running Rails. + +This project relies on ObjectiveSupport, which aims to provide some popular +Rubyisms to Objective-C. If you checkout this project using git, you can +pull down ObjectiveSupport by doing a "git submodule init" followed by +a "git submodule update". + +h2. Getting Started + +h3. Sample Code + +This project comes bundled with a sample iPhone application and a sample +Rails application. To see how everything works together you can open +up the .xcodeproj and fire up a rails server in sample_rails_app. + +h3. Integrating with your project + +# Download (clone) the objectiveresource project +## If you do a git clone, you will need to follow it up with "git submodule init" and "git submodule update" +# open the .xcodeproj in XCode for both objectiveresource and your iPhone project +# drag the ObjectiveResource and ObjectSupport groups from the objectiveresource project onto your iPhone project, making +sure to check the "copy files" box. + +h2. Contributing + +h3. Running Tests + +Unit testing makes use of the SenTest work-alike from Google Toolbox for Mac. + +To run the tests, select the "Unit Tests" target in XCode and Build and Run. + +You will need to have Rails installed along with the populator and faker +gems, as the project uses a "Run Script" to setup a local Rails +server for testing. diff --git a/code/rails_app/.gitignore b/code/rails_app/.gitignore new file mode 100644 index 0000000..af64fae --- /dev/null +++ b/code/rails_app/.gitignore @@ -0,0 +1,4 @@ +.bundle +db/*.sqlite3 +log/*.log +tmp/**/* diff --git a/code/rails_app/Gemfile b/code/rails_app/Gemfile new file mode 100644 index 0000000..995daf9 --- /dev/null +++ b/code/rails_app/Gemfile @@ -0,0 +1,26 @@ +source 'http://rubygems.org' + +gem 'rails', '3.0.0.beta3' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' + +gem 'sqlite3-ruby', :require => 'sqlite3' + +# Use unicorn as the web server +# gem 'unicorn' + +# Deploy with Capistrano +# gem 'capistrano' + +# Bundle the extra gems: +# gem 'bj' +# gem 'nokogiri', '1.4.1' +# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'aws-s3', :require => 'aws/s3' + +# Bundle gems for certain environments: +# gem 'rspec', :group => :test +# group :test do +# gem 'webrat' +# end diff --git a/code/rails_app/README b/code/rails_app/README new file mode 100644 index 0000000..ded8570 --- /dev/null +++ b/code/rails_app/README @@ -0,0 +1,244 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" templates +that are primarily responsible for inserting pre-built data in between HTML tags. +The model contains the "smart" domain objects (such as Account, Product, Person, +Post) that holds all the business logic and knows how to persist themselves to +a database. The controller handles the incoming requests (such as Save New Account, +Update Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, start a new Rails application using the rails command + and your application name. Ex: rails myapp +2. Change directory into myapp and start the web server: rails server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel if it's installed when started with rails server, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails +with a variety of other web servers. + +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or +Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use +FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. + +== Apache .htaccess example for FCGI/CGI + +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands running +on the server.log and development.log. Rails will automatically display debugging +and runtime information to these files. Debugging info will also be shown in the +browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code using +the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: + +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two online (and free) books will bring you up to speed on the Ruby language +and also on programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your Mongrel or +Webrick server with --debugger. This means that you can break out of execution at any point +in the code, investigate and change the model, AND then resume execution! +You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' +Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you enter "cont" + + +== Console + +You can interact with the domain model by starting the console through rails console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like rails console production. + +To reload your controllers and models after launching the console run reload! + +== dbconsole + +You can go to the command line of your database directly through rails dbconsole. +You would be connected to the database with the credentials defined in database.yml. +Starting the script without arguments will connect you to the development database. Passing an +argument will connect you to a different database, like rails dbconsole production. +Currently works for mysql, postgresql and sqlite. + +== Description of Contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.html.erb. Inside default.html.erb, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using rails generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate command, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + If the app has frozen rails, those gems also go here, under vendor/rails/. + This directory is in the load path. diff --git a/code/rails_app/Rakefile b/code/rails_app/Rakefile new file mode 100644 index 0000000..9cb2046 --- /dev/null +++ b/code/rails_app/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +Rails::Application.load_tasks diff --git a/code/rails_app/app/controllers/application_controller.rb b/code/rails_app/app/controllers/application_controller.rb new file mode 100644 index 0000000..f133dce --- /dev/null +++ b/code/rails_app/app/controllers/application_controller.rb @@ -0,0 +1,16 @@ +class ApplicationController < ActionController::Base + protect_from_forgery + layout 'application' + + include Authentication + include SslRequirement + +protected + + # Overridden from SslRequirement to allow local requests + def ssl_required? + return false unless Rails.env == 'production' + super + end + +end diff --git a/code/rails_app/app/controllers/credits_controller.rb b/code/rails_app/app/controllers/credits_controller.rb new file mode 100644 index 0000000..52bd8d1 --- /dev/null +++ b/code/rails_app/app/controllers/credits_controller.rb @@ -0,0 +1,106 @@ +# START:nested +class CreditsController < ApplicationController +# END:nested + before_filter :login_required +# START:nested + + before_filter :find_goal +# END:nested + + def index + @credits = @goal.credits.all + + respond_to do |format| + format.html + format.xml { render :xml => @credits } + format.json { render :json => @credits } + end + end +# START:nested + + def show + @credit = @goal.credits.find(params[:id]) + + respond_to do |format| + format.html + format.xml { render :xml => @credit } + format.json { render :json => @credit } + end + end +# END:nested + + def new + @credit = @goal.credits.build + + respond_to do |format| + format.html + format.xml { render :xml => @credit } + format.json { render :json => @credit } + end + end + + def edit + @credit = @goal.credits.find(params[:id]) + end + + def create + @credit = @goal.credits.build(params[:credit]) + + respond_to do |format| + if @credit.save + format.html { redirect_to @goal } + format.xml { render :xml => @credit, :status => :created, :location => [@goal, @credit] } + format.json { render :json => @credit, :status => :created, :location => [@goal, @credit] } + format.js + else + format.html { render :action => "new" } + format.xml { render :xml => @credit.errors, :status => :unprocessable_entity } + format.json { render :json => @credit.errors, :status => :unprocessable_entity } + format.js do + render :update do |page| + flash[:error] = @credit.errors.full_messages.to_sentence + page.redirect_to @goal + end + end + end + end + end + + def update + @credit = @goal.credits.find(params[:id]) + + respond_to do |format| + if @credit.update_attributes(params[:credit]) + format.html { redirect_to @goal } + format.any(:xml, :json) { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @credit.errors, :status => :unprocessable_entity } + format.json { render :json => @credit.errors, :status => :unprocessable_entity } + end + end + end + + def destroy + @credit = @goal.credits.find(params[:id]) + @credit.destroy + + respond_to do |format| + format.html { redirect_to @goal } + format.js { render 'create'} + format.any(:xml, :json) { head :ok } + end + end +# START:nested + +protected + + def find_goal + @goal = current_user.goals.find(params[:goal_id]) + rescue ActiveRecord::RecordNotFound + flash[:error] = "Invalid goal." + redirect_to @goal + end + +end +# END:nested diff --git a/code/rails_app/app/controllers/goals_controller.rb b/code/rails_app/app/controllers/goals_controller.rb new file mode 100644 index 0000000..4eb225b --- /dev/null +++ b/code/rails_app/app/controllers/goals_controller.rb @@ -0,0 +1,87 @@ +# START:authentication +class GoalsController < ApplicationController + + before_filter :login_required + + def index + @goals = current_user.goals.all + + respond_to do |format| + format.html + format.xml { render :xml => @goals } + format.json { render :json => @goals } + end + end + +# END:authentication + + def show + @goal = current_user.goals.find(params[:id]) + @credit = Credit.new + + respond_to do |format| + format.html + format.xml { render :xml => @goal } + format.json { render :json => @goal } + end + end + + def new + @goal = current_user.goals.build + + respond_to do |format| + format.html + format.xml { render :xml => @goal } + format.json { render :json => @goal } + end + end + + def edit + @goal = current_user.goals.find(params[:id]) + end + + def create + @goal = current_user.goals.build(params[:goal]) + + respond_to do |format| + if @goal.save + format.html { redirect_to @goal } + format.xml { render :xml => @goal, :status => :created, :location => @goal } + format.json { render :json => @goal, :status => :created, :location => @goal } + else + format.html { render :action => "new" } + format.xml { render :xml => @goal.errors, :status => :unprocessable_entity } + format.json { render :xml => @goal.errors, :status => :unprocessable_entity } + end + end + end + + def update + @goal = current_user.goals.find(params[:id]) + + respond_to do |format| + if @goal.update_attributes(params[:goal]) + format.html { redirect_to @goal } + format.any(:xml, :json) { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @goal.errors, :status => :unprocessable_entity } + format.json { render :json => @goal.errors, :status => :unprocessable_entity } + end + end + end + + def destroy + @goal = current_user.goals.find(params[:id]) + @goal.destroy + + respond_to do |format| + format.html { redirect_to(goals_url) } + format.any(:xml, :json) { head :ok } + end + end + +# START:authentication +end +# END:authentication + diff --git a/code/rails_app/app/controllers/info_controller.rb b/code/rails_app/app/controllers/info_controller.rb new file mode 100644 index 0000000..b36c6c2 --- /dev/null +++ b/code/rails_app/app/controllers/info_controller.rb @@ -0,0 +1,6 @@ +class InfoController < ApplicationController + + def index + end + +end \ No newline at end of file diff --git a/code/rails_app/app/controllers/sessions_controller.rb b/code/rails_app/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..33834cb --- /dev/null +++ b/code/rails_app/app/controllers/sessions_controller.rb @@ -0,0 +1,37 @@ +class SessionsController < ApplicationController + + ssl_required :new, :create + + def new + end + + def create + @login = params[:session][:login] + @password = params[:session][:password] + + user = User.authenticate(@login, @password) + + respond_to do |format| + if user + format.html do + reset_session + session[:user_id] = user.id + redirect_back_or_default root_url + end + format.any(:xml, :json) { head :ok } + else + format.html do + flash.now[:error] = "Invalid login or password." + render :action => :new + end + format.any(:xml, :json) { request_http_basic_authentication } + end + end + end + + def destroy + session[:user_id] = nil + redirect_to login_url + end + +end diff --git a/code/rails_app/app/controllers/users_controller.rb b/code/rails_app/app/controllers/users_controller.rb new file mode 100644 index 0000000..62176e9 --- /dev/null +++ b/code/rails_app/app/controllers/users_controller.rb @@ -0,0 +1,16 @@ +class UsersController < ApplicationController + + def new + @user = User.new + end + + def create + @user = User.new(params[:user]) + if @user.save + session[:user_id] = @user.id + redirect_to root_url + else + render :action => :new + end + end +end diff --git a/code/rails_app/app/helpers/application_helper.rb b/code/rails_app/app/helpers/application_helper.rb new file mode 100644 index 0000000..3a2b877 --- /dev/null +++ b/code/rails_app/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end \ No newline at end of file diff --git a/code/rails_app/app/helpers/error_messages_helper.rb b/code/rails_app/app/helpers/error_messages_helper.rb new file mode 100644 index 0000000..01ea2b6 --- /dev/null +++ b/code/rails_app/app/helpers/error_messages_helper.rb @@ -0,0 +1,25 @@ +module ErrorMessagesHelper + + def error_messages_for(object, options={}) + if object && object.errors.any? + options[:header_message] ||= "Oops, there were errors while processing this form." + if object.errors[:base].any? + options[:message] ||= object.errors[:base] + else + options[:message] ||= "Please correct the errors below and try again!" + end + content_tag(:div, + content_tag(:h4, options[:header_message]) + + content_tag(:p, options[:message]), + :id => 'flash_error', :class => 'error_messages') + end + end + + module FormBuilderAdditions + def error_messages(options = {}) + @template.error_messages_for(@object, options) + end + end +end + +ActionView::Helpers::FormBuilder.send(:include, ErrorMessagesHelper::FormBuilderAdditions) \ No newline at end of file diff --git a/code/rails_app/app/helpers/goals_helper.rb b/code/rails_app/app/helpers/goals_helper.rb new file mode 100644 index 0000000..f78045f --- /dev/null +++ b/code/rails_app/app/helpers/goals_helper.rb @@ -0,0 +1,7 @@ +module GoalsHelper + + def goal_total_class(goal) + goal.reached? ? 'over' : 'under' + end + +end diff --git a/code/rails_app/app/helpers/layout_helper.rb b/code/rails_app/app/helpers/layout_helper.rb new file mode 100644 index 0000000..4f89c72 --- /dev/null +++ b/code/rails_app/app/helpers/layout_helper.rb @@ -0,0 +1,37 @@ +module LayoutHelper + + def banner(name, *actions) + @page_title = "Save Up | #{h(name)}" + actions = content_tag(:span, actions.join(separator).html_safe, :class => 'actions') + content_for(:banner) do + content_tag(:div, content_tag(:h2, actions + h(name)), :id => 'banner') + end + end + + def navigation_links + links = [] + if logged_in? + links << link_to("Goals", goals_path) + links << link_to("About", about_path) + links << link_to("Logout", logout_path) + else + links << link_to("Join", join_path) + links << link_to("About", about_path) + end + content_tag(:span, links.join(separator).html_safe, :class => 'actions') + end + + def separator + %{ | }.html_safe + end + + def labeled_form_for(*args, &block) + options = args.extract_options!.merge(:builder => LabeledFormBuilder) + form_for(*(args + [options]), &block) + end + + def cancel_link(url) + link_to 'cancel', url, :class => 'destructive' + end + +end \ No newline at end of file diff --git a/code/rails_app/app/models/credit.rb b/code/rails_app/app/models/credit.rb new file mode 100644 index 0000000..872eae2 --- /dev/null +++ b/code/rails_app/app/models/credit.rb @@ -0,0 +1,24 @@ +class Credit < ActiveRecord::Base + belongs_to :goal + + validates_presence_of :name, :amount + validates_numericality_of :amount, :greater_than => 0 + + def to_xml(options={}) + default_serialization_options(options) + super(options) + end + + def to_json(options={}) + default_serialization_options(options) + super(options) + end + +protected + + def default_serialization_options(options={}) + options[:only] = [:id, :name, :goal_id, :amount, :updated_at, :created_at] + end + +end + diff --git a/code/rails_app/app/models/goal.rb b/code/rails_app/app/models/goal.rb new file mode 100644 index 0000000..7b34b88 --- /dev/null +++ b/code/rails_app/app/models/goal.rb @@ -0,0 +1,42 @@ +class Goal < ActiveRecord::Base + + belongs_to :user + has_many :credits, :order => 'updated_at', :dependent => :destroy + + validates_presence_of :name, :amount + validates_numericality_of :amount, :greater_than => 0.0 + + def self.search(query) + where("name like ?", "%#{query}%") + end + + def saved + credits.inject(0) {|sum, credit| sum + credit.amount } + end + + def remaining + amount - saved + end + + def reached? + saved >= amount + end + + def to_xml(options={}) + default_serialization_options(options) + super(options) + end + + def to_json(options={}) + default_serialization_options(options) + super(options) + end + +protected + + def default_serialization_options(options={}) + options[:only] = [:id, :name, :amount, :updated_at, :created_at] + options[:methods] = [:saved, :remaining] if self.amount + end + +end diff --git a/code/rails_app/app/models/user.rb b/code/rails_app/app/models/user.rb new file mode 100644 index 0000000..76783c2 --- /dev/null +++ b/code/rails_app/app/models/user.rb @@ -0,0 +1,74 @@ +# START:security +class User < ActiveRecord::Base + +# END:security + + has_many :goals, :order => 'name', :dependent => :destroy + + attr_accessible :username, :email, :password, :password_confirmation + attr_accessor :password + + validates_presence_of :username, :email + validates_uniqueness_of :username, :email + validates_format_of :email, :with => /\A(\S+)@(\S+)\Z/, :allow_nil => true + + validates_presence_of :password, :on => :create + validates_presence_of :password_confirmation, :on => :create + + validates_length_of :password, :minimum => 6 + validates_confirmation_of :password + + before_save :encrypt_password + + def self.authenticate(login, password) + return nil if login.blank? || password.blank? + user = find_by_username(login) || find_by_email(login) + (user && user.authenticated?(password)) ? user : nil + end + + def authenticated?(password) + self.password_hash == User.encrypted_password(password, self.password_salt || "") + end + + # Serialization, overridden for security! + + # START:security + def to_xml(options={}) + default_serialization_options(options) + super(options) + end + + def to_json(options={}) + default_serialization_options(options) + super(options) + end + + def default_serialization_options(options={}) + options[:only] = [:username, :user_id, :updated_at, :created_at] + end + + # END:security + +private + + def self.secure_digest(*args) + Digest::SHA1.hexdigest(args.flatten.join('--')) + end + + def self.make_token + secure_digest(Time.now, (1..10).map { rand.to_s }) + end + + def self.encrypted_password(password, salt) + secure_digest(password, salt) + end + + def encrypt_password + return if self.password.blank? + self.password_salt = User.make_token + self.password_hash = User.encrypted_password(self.password, self.password_salt) + end + +# START:security +end +# END:security diff --git a/code/rails_app/app/views/credits/_form.html.erb b/code/rails_app/app/views/credits/_form.html.erb new file mode 100644 index 0000000..67d6c64 --- /dev/null +++ b/code/rails_app/app/views/credits/_form.html.erb @@ -0,0 +1,8 @@ +<%= labeled_form_for([@goal, @credit]) do |f| %> +
+ <%= f.error_messages %> + <%= f.text_field :name %> + <%= f.text_field :amount, :size => 10 %> + <%= f.submit %> +
+<% end %> diff --git a/code/rails_app/app/views/credits/create.js.rjs b/code/rails_app/app/views/credits/create.js.rjs new file mode 100644 index 0000000..d51f787 --- /dev/null +++ b/code/rails_app/app/views/credits/create.js.rjs @@ -0,0 +1,10 @@ +page[:credits].replace_html :partial => 'goals/credits', + :locals => {:goal => @goal, :credits => @goal.credits} + +page[:summary].replace_html :partial => 'goals/summary', + :locals => {:goal => @goal} + +page.select('#flash_error').each { |e| e.hide} +page[:summary].highlight +page[:new_credit].reset +page[:credit_amount].focus \ No newline at end of file diff --git a/code/rails_app/app/views/credits/edit.html.erb b/code/rails_app/app/views/credits/edit.html.erb new file mode 100644 index 0000000..a1beb46 --- /dev/null +++ b/code/rails_app/app/views/credits/edit.html.erb @@ -0,0 +1,3 @@ +<% banner "Edit Credit for #{h(@goal.name)}" %> + +<%= render 'form' %> diff --git a/code/rails_app/app/views/credits/index.html.erb b/code/rails_app/app/views/credits/index.html.erb new file mode 100644 index 0000000..5ed1c3b --- /dev/null +++ b/code/rails_app/app/views/credits/index.html.erb @@ -0,0 +1,4 @@ +<% banner "#{h(@goal.name)} Credits", + link_to('Create new credit', new_goal_credit_path(@goal)) %> + +<%= render 'goals/credits', :credits => @goal.credits, :goal => @goal %> diff --git a/code/rails_app/app/views/credits/new.html.erb b/code/rails_app/app/views/credits/new.html.erb new file mode 100644 index 0000000..36a3d12 --- /dev/null +++ b/code/rails_app/app/views/credits/new.html.erb @@ -0,0 +1,3 @@ +<% banner "New #{@goal.name} Credit" %> + +<%= render 'form' %> diff --git a/code/rails_app/app/views/credits/show.html.erb b/code/rails_app/app/views/credits/show.html.erb new file mode 100644 index 0000000..c92856f --- /dev/null +++ b/code/rails_app/app/views/credits/show.html.erb @@ -0,0 +1,21 @@ +<% banner "#{@goal.name} Credit", + link_to('Edit', edit_goal_credit_path(@goal, @credit)), + link_to('Delete', goal_credit_path(@goal, @credit), + :confirm => 'Sure?', :method => :delete) %> + + + + + + +
+ <%= link_to @credit.name, + goal_credit_path(@credit.goal, @credit) %> + + <%= number_to_currency(@credit.amount) %> +
+ +

+<%= link_to 'All credits for this goal', + goal_credits_path(@goal) %> +

diff --git a/code/rails_app/app/views/goals/_credits.html.erb b/code/rails_app/app/views/goals/_credits.html.erb new file mode 100644 index 0000000..9ad4434 --- /dev/null +++ b/code/rails_app/app/views/goals/_credits.html.erb @@ -0,0 +1,19 @@ + +<% credits.each do |credit| -%> + + + + + +<% end -%> +
+ <%= link_to credit.name, + edit_goal_credit_path(credit.goal, credit) %> + + <%= number_to_currency(credit.amount) %> + + <%= link_to 'x', goal_credit_url(goal, credit), + :method => :delete, + :remote => true, + :class => 'destructive' %> +
\ No newline at end of file diff --git a/code/rails_app/app/views/goals/_form.html.erb b/code/rails_app/app/views/goals/_form.html.erb new file mode 100644 index 0000000..8257666 --- /dev/null +++ b/code/rails_app/app/views/goals/_form.html.erb @@ -0,0 +1,8 @@ +<%= labeled_form_for @goal do |f| %> +
+ <%= f.error_messages %> + <%= f.text_field :name %> + <%= f.text_field :amount, :size => 10 %> + <%= f.submit %> +
+<% end %> diff --git a/code/rails_app/app/views/goals/_summary.html.erb b/code/rails_app/app/views/goals/_summary.html.erb new file mode 100644 index 0000000..e54ccb1 --- /dev/null +++ b/code/rails_app/app/views/goals/_summary.html.erb @@ -0,0 +1,9 @@ + + <%= number_to_currency(goal.saved) %> + + + of <%= number_to_currency(goal.amount) %> + + + (<%= number_to_currency(goal.remaining.abs) %>) + diff --git a/code/rails_app/app/views/goals/edit.html.erb b/code/rails_app/app/views/goals/edit.html.erb new file mode 100644 index 0000000..6374f21 --- /dev/null +++ b/code/rails_app/app/views/goals/edit.html.erb @@ -0,0 +1,3 @@ +<% banner "Edit Goal" %> + +<%= render 'form' %> diff --git a/code/rails_app/app/views/goals/index.html.erb b/code/rails_app/app/views/goals/index.html.erb new file mode 100644 index 0000000..3464829 --- /dev/null +++ b/code/rails_app/app/views/goals/index.html.erb @@ -0,0 +1,30 @@ +<% banner 'Goals', link_to('Create new goal', new_goal_path) %> + +<% unless @goals.any? %> +

+ No goals (yet). Go on, <%= link_to('create one already!', new_goal_path) %> +

+<% end %> + + +<% @goals.each do |goal| %> + + + + + +<% end %> +
+ <%= link_to goal.name, goal %> + + + <%= number_to_currency(goal.saved) %> + + + of <%= number_to_currency(goal.amount) %> + + + + (<%= number_to_currency(goal.remaining.abs) %>) + +
diff --git a/code/rails_app/app/views/goals/new.html.erb b/code/rails_app/app/views/goals/new.html.erb new file mode 100644 index 0000000..ff10731 --- /dev/null +++ b/code/rails_app/app/views/goals/new.html.erb @@ -0,0 +1,3 @@ +<% banner "New Goal" %> + +<%= render 'form' %> \ No newline at end of file diff --git a/code/rails_app/app/views/goals/show.html.erb b/code/rails_app/app/views/goals/show.html.erb new file mode 100644 index 0000000..62e2114 --- /dev/null +++ b/code/rails_app/app/views/goals/show.html.erb @@ -0,0 +1,37 @@ +<% banner "Goal: #{h(@goal.name)}", + link_to('Edit', edit_goal_path(@goal)), + link_to('Delete', @goal, :confirm => 'Sure?', :method => :delete) %> + +
+ +
+ <%= render 'summary', :goal => @goal %> +
+ +
+ +
+ <% if @goal.credits.any? %> + <%= render 'credits', :credits => @goal.credits, :goal => @goal %> + <% else %> +

+ No goal credits (yet). Go on, add one already! +

+ <% end %> +
+ +
+ +

Add a credit

+ +<%= form_for([@goal, @credit], :remote => true) do |f| %> +

+ $<%= f.text_field :amount, :size => 6 %> + on + <%= f.text_field :name %> +

+ <%= f.submit 'Add this credit' %> or + <%= cancel_link goals_path %> +<% end %> + +
\ No newline at end of file diff --git a/code/rails_app/app/views/info/index.html.erb b/code/rails_app/app/views/info/index.html.erb new file mode 100644 index 0000000..a90a01f --- /dev/null +++ b/code/rails_app/app/views/info/index.html.erb @@ -0,0 +1,34 @@ +
+ +

+ If you skipped buying those fancy lattes this week, how much closer would you be to affording a + tropical vacation this summer? How many lattes would you give up if you were to take that vacation? +

+

+ Money is an abstract concept. When faced with the temptation to spend it now or save it for later, + we often don't realize what we're giving up or what we're gaining. We spend our money today. We talk + about saving it for the future. And in the meantime, we complain that we can't afford the + things we're most interested in. +

+

+ Save Up makes money more concrete by helping you think of it in terms of + substitutions. You say "I like vacations in the Bahamas, gadgets made by Apple, and + charitable causes." Then, when you're tempted to buy something, that thing is translated in + terms of the things you are interested in. By thinking about our spending differently—by + comparing all the things we're interested in—perhaps we can make better decisions. +

+

+ The idea for this app was inspired by Dan Ariely's Making + Money Less Abstract video on Big Think. It's a good video. You should watch it: +

+ +
+<%= link_to image_tag('video.png', :width => 514, :height => 288), + 'http://bigthink.com/ideas/17458', :target => 'new' %> +
+ +

+ <%= link_to "Create an account", join_path %> to start recording credits toward your money goals! +

+ +
\ No newline at end of file diff --git a/code/rails_app/app/views/layouts/application.html.erb b/code/rails_app/app/views/layouts/application.html.erb new file mode 100644 index 0000000..a8af5d1 --- /dev/null +++ b/code/rails_app/app/views/layouts/application.html.erb @@ -0,0 +1,34 @@ + + + + + <%= @page_title || "Save Up - Making money less abstract" %> + <%= stylesheet_link_tag 'application' %> + <%= javascript_include_tag :defaults %> + <%= csrf_meta_tag %> + + +
+ +
+ <%= yield :banner %> + <% flash.each do |name, message| %> + <%= content_tag :div, message, :id => "flash_#{name}" %> + <% end %> + <%= yield :layout %> +
+ +
+ + diff --git a/code/rails_app/app/views/sessions/new.html.erb b/code/rails_app/app/views/sessions/new.html.erb new file mode 100644 index 0000000..97f649d --- /dev/null +++ b/code/rails_app/app/views/sessions/new.html.erb @@ -0,0 +1,22 @@ +<% banner "Login" %> + +

+ Don't have an account yet? + <%= link_to "Join now!", join_path %> +

+ +<%= form_tag sessions_path do %> +
+

+ <%= label_tag :login, 'Login or E-mail', :class => 'required' %> + <%= text_field_tag 'session[login]', @login, :size => 20 %> +

+

+ <%= label_tag :password, 'Password', :class => 'required' %> + <%= password_field_tag 'session[password]', @password, :size => 20 %> +

+

+ <%= submit_tag 'Log In', :class => 'button' %> +

+
+<% end %> diff --git a/code/rails_app/app/views/users/new.html.erb b/code/rails_app/app/views/users/new.html.erb new file mode 100644 index 0000000..d0d552b --- /dev/null +++ b/code/rails_app/app/views/users/new.html.erb @@ -0,0 +1,31 @@ +<% banner "Join" %> + +

+ Already have an account? + <%= link_to "Log in now!", login_path %> +

+ +<%= form_for @user do |f| %> +
+ <%= f.error_messages %> +

+ <%= f.label :username, nil, :class => 'required' %> + <%= f.text_field :username %> +

+

+ <%= f.label :email, "E-mail Address", :class => 'required' %> + <%= f.text_field :email %> +

+

+ <%= f.label :password, nil, :class => 'required' %> + <%= f.password_field :password %> +

+

+ <%= f.label :password_confirmation, "Password Again", :class => 'required' %> + <%= f.password_field :password_confirmation %> +

+

+ <%= f.submit 'Create Account' %> +

+
+<% end %> diff --git a/code/rails_app/config.ru b/code/rails_app/config.ru new file mode 100644 index 0000000..b205e95 --- /dev/null +++ b/code/rails_app/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Saveup::Application diff --git a/code/rails_app/config/application.rb b/code/rails_app/config/application.rb new file mode 100644 index 0000000..e76227a --- /dev/null +++ b/code/rails_app/config/application.rb @@ -0,0 +1,46 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# If you have a Gemfile, require the gems listed there, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) if defined?(Bundler) + +module Saveup + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{config.root}/extras ) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # Configure generators values. Many other options are available, be sure to check the documentation. + # config.generators do |g| + # g.orm :active_record + # g.template_engine :erb + # g.test_framework :test_unit, :fixture => true + # end + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/code/rails_app/config/boot.rb b/code/rails_app/config/boot.rb new file mode 100644 index 0000000..712b098 --- /dev/null +++ b/code/rails_app/config/boot.rb @@ -0,0 +1,6 @@ +require 'rubygems' +# Set up gems listed in the Gemfile. +if File.exist?(File.expand_path('../../Gemfile', __FILE__)) + require 'bundler' + Bundler.setup +end diff --git a/code/rails_app/config/database.yml b/code/rails_app/config/database.yml new file mode 100644 index 0000000..025d62a --- /dev/null +++ b/code/rails_app/config/database.yml @@ -0,0 +1,22 @@ +# SQLite version 3.x +# gem install sqlite3-ruby (not necessary on OS X Leopard) +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + pool: 5 + timeout: 5000 diff --git a/code/rails_app/config/environment.rb b/code/rails_app/config/environment.rb new file mode 100644 index 0000000..3eb5656 --- /dev/null +++ b/code/rails_app/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Saveup::Application.initialize! diff --git a/code/rails_app/config/environments/development.rb b/code/rails_app/config/environments/development.rb new file mode 100644 index 0000000..ffcd778 --- /dev/null +++ b/code/rails_app/config/environments/development.rb @@ -0,0 +1,19 @@ +Saveup::Application.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false +end diff --git a/code/rails_app/config/environments/production.rb b/code/rails_app/config/environments/production.rb new file mode 100644 index 0000000..d08ba00 --- /dev/null +++ b/code/rails_app/config/environments/production.rb @@ -0,0 +1,42 @@ +Saveup::Application.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! +end diff --git a/code/rails_app/config/environments/test.rb b/code/rails_app/config/environments/test.rb new file mode 100644 index 0000000..5f5734d --- /dev/null +++ b/code/rails_app/config/environments/test.rb @@ -0,0 +1,32 @@ +Saveup::Application.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql +end diff --git a/code/rails_app/config/initializers/backtrace_silencers.rb b/code/rails_app/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/code/rails_app/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/code/rails_app/config/initializers/form_errors.rb b/code/rails_app/config/initializers/form_errors.rb new file mode 100644 index 0000000..2451134 --- /dev/null +++ b/code/rails_app/config/initializers/form_errors.rb @@ -0,0 +1,9 @@ +# For inline form-field error messages +ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| + if html_tag =~ /type="hidden"/ || html_tag =~ /