├── Example1 ├── APPL.icns ├── IsSecureIcon.png ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.nib │ │ └── keyedobjects.nib ├── Example1_Prefix.pch ├── Example1.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── main.m ├── OrderViewController.h ├── AppController.h ├── OrderViewController.m ├── Info.plist └── AppController.m ├── Example2 ├── APPL.icns ├── IsSecureIcon.png ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.nib │ │ └── keyedobjects.nib ├── Example2_Prefix.pch ├── Example2.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── DeepMutableCopy.h ├── NSArray_DeepMutableCopy.h ├── main.m ├── NSDictionary_DeepMutableCopy.h ├── MGTemplateStandardFilters.h ├── MGTemplateFilter.h ├── OrderView.css ├── AppController.h ├── MGTemplateStandardMarkers.h ├── Info.plist ├── OrderView.html ├── NSArray_DeepMutableCopy.m ├── NSDictionary_DeepMutableCopy.m ├── ICUTemplateMatcher.h ├── MGTemplateMarker.h ├── AppController.m ├── MGTemplateStandardFilters.m ├── MGTemplateEngine.h ├── RegexKitLite.h ├── Source Code License.rtf ├── ICUTemplateMatcher.m ├── MGTemplateEngine README.txt ├── RegexKitLite.m └── MGTemplateStandardMarkers.m ├── TestApp ├── APPL.icns ├── IsSecureIcon.png ├── toolbarPreview.png ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.nib │ │ └── keyedobjects.nib ├── TestApp_Prefix.pch ├── TestApp.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── OrderView.h ├── main.m ├── OrderViewController.h ├── OrderView.m ├── Info.plist ├── AppController.h ├── OrderViewController.m └── AppController.m ├── .gitignore ├── README ├── example1_screenshot.png ├── example2_screenshot.png ├── testapp_results_screenshot.png └── testapp_settings_screenshot.png ├── FsprgEmbeddedStoreStyle ├── paypal │ └── About.txt ├── window.xhtml └── style.css ├── FsprgEmbeddedStore ├── Tests │ ├── Model │ │ ├── FsprgOrderTest.h │ │ ├── FsprgOrderTest.m │ │ └── complicated.plist │ ├── FsprgStoreParametersTest.h │ └── FsprgStoreParametersTest.m ├── FsprgEmbeddedStore.h ├── FsprgOrderDocumentRepresentation.h ├── FsprgOrderView.h ├── Model │ ├── FsprgFileDownload.h │ ├── FsprgLicense.h │ ├── FsprgFulfillment.h │ ├── FsprgOrder.h │ ├── FsprgFileDownload.m │ ├── FsprgOrderItem.h │ ├── FsprgFulfillment.m │ ├── FsprgLicense.m │ ├── FsprgOrderItem.m │ └── FsprgOrder.m ├── FsprgOrderDocumentRepresentation.m ├── FsprgOrderView.m ├── FsprgEmbeddedStoreController.h ├── FsprgEmbeddedStoreDelegate.h ├── FsprgStoreParameters.h ├── FsprgStoreParameters.m └── FsprgEmbeddedStoreController.m ├── License.txt └── README.mdown /Example1/APPL.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example1/APPL.icns -------------------------------------------------------------------------------- /Example2/APPL.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example2/APPL.icns -------------------------------------------------------------------------------- /TestApp/APPL.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/TestApp/APPL.icns -------------------------------------------------------------------------------- /Example1/IsSecureIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example1/IsSecureIcon.png -------------------------------------------------------------------------------- /Example2/IsSecureIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example2/IsSecureIcon.png -------------------------------------------------------------------------------- /TestApp/IsSecureIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/TestApp/IsSecureIcon.png -------------------------------------------------------------------------------- /TestApp/toolbarPreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/TestApp/toolbarPreview.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Example1/build/ 2 | Example2/build/ 3 | TestApp/build 4 | *.mode1v3 5 | *.pbxuser 6 | xcuserdata 7 | .DS_Store -------------------------------------------------------------------------------- /README/example1_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/README/example1_screenshot.png -------------------------------------------------------------------------------- /README/example2_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/README/example2_screenshot.png -------------------------------------------------------------------------------- /README/testapp_results_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/README/testapp_results_screenshot.png -------------------------------------------------------------------------------- /README/testapp_settings_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/README/testapp_settings_screenshot.png -------------------------------------------------------------------------------- /Example1/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example1/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Example2/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example2/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /TestApp/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/TestApp/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Example1/English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example1/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Example2/English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/Example2/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /TestApp/English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/js/FsprgEmbeddedStoreMac/master/TestApp/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Example1/Example1_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Example1' target in the 'Example1' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Example2/Example2_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Example2' target in the 'Example2' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /TestApp/TestApp_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'TestApp' target in the 'TestApp' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Example1/Example1.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example2/Example2.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestApp/TestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FsprgEmbeddedStoreStyle/paypal/About.txt: -------------------------------------------------------------------------------- 1 | If one of the following images file names is present in this directory: 2 | 3 | header.png, header.gif, header.jpg 4 | 5 | Then that image will be used as the header image for customers that checkout using PayPal. -------------------------------------------------------------------------------- /Example2/DeepMutableCopy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * DeepMutableCopy.h 3 | * 4 | * Created by Matt Gemmell on 02/05/2008. 5 | * Copyright 2008 Instinctive Code. All rights reserved. 6 | * 7 | */ 8 | 9 | #import "NSArray_DeepMutableCopy.h" 10 | #import "NSDictionary_DeepMutableCopy.h" 11 | -------------------------------------------------------------------------------- /TestApp/OrderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrderView.h 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 3/10/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface OrderView : NSView { 13 | 14 | } 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Example2/NSArray_DeepMutableCopy.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray_DeepMutableCopy.h 3 | // 4 | // Created by Matt Gemmell on 02/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | @interface NSArray (DeepMutableCopy) 9 | 10 | - (NSMutableArray *)deepMutableCopy; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Example1/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example1 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright Buzzard AG 2010. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Example2/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example2 4 | // 5 | // Created by Lars Steiger on 3/11/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /TestApp/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 3/3/10. 6 | // Copyright Buzzard AG 2010. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Example2/NSDictionary_DeepMutableCopy.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary_DeepMutableCopy.h 3 | // 4 | // Created by Matt Gemmell on 02/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | @interface NSDictionary (DeepMutableCopy) 9 | 10 | - (NSMutableDictionary *)deepMutableCopy; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Example2/MGTemplateStandardFilters.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTemplateStandardFilters.h 3 | // 4 | // Created by Matt Gemmell on 13/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "MGTemplateFilter.h" 9 | 10 | 11 | @interface MGTemplateStandardFilters : NSObject { 12 | 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example1/OrderViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrderViewController.h 3 | // Example1 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface OrderViewController : NSViewController { 13 | 14 | } 15 | 16 | - (IBAction)showLicense:(id)sender; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Example2/MGTemplateFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MGTemplateFilter.h 3 | * 4 | * Created by Matt Gemmell on 12/05/2008. 5 | * Copyright 2008 Instinctive Code. All rights reserved. 6 | * 7 | */ 8 | 9 | @protocol MGTemplateFilter 10 | 11 | - (NSArray *)filters; 12 | - (NSObject *)filterInvoked:(NSString *)filter withArguments:(NSArray *)args onValue:(NSObject *)value; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Tests/Model/FsprgOrderTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderTest.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FsprgOrder.h" 11 | 12 | 13 | @interface FsprgOrderTest : SenTestCase { 14 | FsprgOrder *order; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Tests/FsprgStoreParametersTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgStoreParametersTest.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 3/1/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FsprgStoreParameters.h" 11 | 12 | 13 | @interface FsprgStoreParametersTest : SenTestCase { 14 | FsprgStoreParameters *params; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /TestApp/OrderViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrderViewController.h 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface OrderViewController : NSViewController { 13 | NSString *fileName; 14 | } 15 | 16 | - (NSString *)fileName; 17 | - (void)setFileName:(NSString *)aFileName; 18 | 19 | - (IBAction)saveAs:(id)sender; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgEmbeddedStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgEmbeddedStore.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgEmbeddedStoreController.h" 10 | #import "FsprgEmbeddedStoreDelegate.h" 11 | #import "FsprgStoreParameters.h" 12 | 13 | // Model 14 | #import "FsprgOrder.h" 15 | #import "FsprgOrderItem.h" 16 | #import "FsprgFulfillment.h" 17 | #import "FsprgLicense.h" 18 | #import "FsprgFileDownload.h" 19 | -------------------------------------------------------------------------------- /TestApp/OrderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrderView.m 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 3/10/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "OrderView.h" 10 | 11 | 12 | @implementation OrderView 13 | 14 | - (id)initWithFrame:(NSRect)frame { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | } 18 | return self; 19 | } 20 | 21 | - (void)drawRect:(NSRect)dirtyRect { 22 | [[NSColor windowBackgroundColor] set]; 23 | [NSBezierPath fillRect: [self bounds]]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Example2/OrderView.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Lucida Grande; 3 | } 4 | 5 | .thankYouNote { 6 | margin: 50px; 7 | text-align: center; 8 | font-size: 22px; 9 | } 10 | 11 | .orderItemsTitle { 12 | display: none; 13 | font-size: 14px; 14 | font-weight: bold; 15 | margin-bottom: 0px; 16 | border-bottom: 1px solid black; 17 | } 18 | 19 | .orderItem { 20 | display: none; 21 | padding-top: 10px; 22 | margin-left: 20px; 23 | } 24 | 25 | .productName { 26 | font-size: 14px; 27 | } 28 | 29 | .licenseKey { 30 | font-family: Courier New; 31 | font-size: 12px; 32 | } -------------------------------------------------------------------------------- /Example2/AppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.h 3 | // Example2 4 | // 5 | // Created by Lars Steiger on 3/11/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgEmbeddedStore.h" 12 | 13 | 14 | @interface AppController : NSObject { 15 | IBOutlet WebView* storeView; 16 | FsprgEmbeddedStoreController *storeController; 17 | } 18 | 19 | - (FsprgEmbeddedStoreController *)storeController; 20 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController; 21 | 22 | - (IBAction)load:(id)sender; 23 | 24 | @end -------------------------------------------------------------------------------- /Example1/AppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.h 3 | // Example1 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgEmbeddedStore.h" 12 | 13 | 14 | @interface AppController : NSObject { 15 | IBOutlet WebView* storeView; 16 | FsprgEmbeddedStoreController *storeController; 17 | } 18 | 19 | - (FsprgEmbeddedStoreController *)storeController; 20 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController; 21 | 22 | - (IBAction)load:(id)sender; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example1/OrderViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrderViewController.m 3 | // Example1 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "OrderViewController.h" 10 | #import "FsprgEmbeddedStore.h" 11 | 12 | 13 | @implementation OrderViewController 14 | 15 | - (IBAction)showLicense:(id)sender 16 | { 17 | FsprgOrder *order = [self representedObject]; 18 | 19 | NSString *message = [NSString stringWithFormat:@"Name: %@\nEmail: %@", [order customerLastName], [order customerEmail]]; 20 | NSRunInformationalAlertPanel(@"Your License", message, @"OK", nil, nil); 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgOrderDocumentRepresentation.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderDocumentRepresentation.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgOrder.h" 12 | 13 | 14 | /*! 15 | * WebDocumentRepresentation that calls FsprgEmbeddedStoreDelegate on receiving the order. 16 | */ 17 | @interface FsprgOrderDocumentRepresentation : NSObject { 18 | FsprgOrder *order; 19 | } 20 | 21 | - (FsprgOrder *)order; 22 | - (void)setOrder:(FsprgOrder *)anOrder; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgOrderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderView.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/18/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /*! 14 | * WebDocumentView implementation encapsulating the order 15 | * confirmation view of FsprgEmbeddedStoreDelegate. 16 | */ 17 | @interface FsprgOrderView : NSView { 18 | WebDataSource *dataSource; 19 | BOOL needsLayout; 20 | } 21 | 22 | - (WebDataSource *)dataSource; 23 | - (void)setDataSource:(WebDataSource *)aDataSource; 24 | - (BOOL)needsLayout; 25 | - (void)setNeedsLayout:(BOOL)flag; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgFileDownload.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgFileDownload.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /*! 13 | * File download information. FsprgFileDownload is backed by a NSMutableDictionary that 14 | * can be accessed and modified via the raw and setRaw: methods. 15 | */ 16 | @interface FsprgFileDownload : NSObject { 17 | NSDictionary *raw; 18 | } 19 | 20 | + (FsprgFileDownload *)fileDownloadWithDictionary:(NSDictionary *)aDictionary; 21 | 22 | - (FsprgFileDownload *)initWithDictionary:(NSDictionary *)aDictionary; 23 | - (NSDictionary *)raw; 24 | - (void)setRaw:(NSDictionary *)aDictionary; 25 | 26 | - (NSURL *)fileURL; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Example2/MGTemplateStandardMarkers.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTemplateStandardMarkers.h 3 | // 4 | // Created by Matt Gemmell on 13/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "MGTemplateEngine.h" 9 | #import "MGTemplateMarker.h" 10 | 11 | @interface MGTemplateStandardMarkers : NSObject { 12 | MGTemplateEngine *engine; // weak ref 13 | NSMutableArray *forStack; 14 | NSMutableArray *sectionStack; 15 | NSMutableArray *ifStack; 16 | NSMutableArray *commentStack; 17 | NSMutableDictionary *cycles; 18 | } 19 | 20 | - (BOOL)currentBlock:(NSDictionary *)blockInfo matchesTopOfStack:(NSMutableArray *)stack; 21 | - (BOOL)argIsNumeric:(NSString *)arg intValue:(int *)val checkVariables:(BOOL)checkVars; 22 | - (BOOL)argIsTrue:(NSString *)arg; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example1/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | APPL.icns 11 | CFBundleIdentifier 12 | com.fastspring.Example1 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | FSPR 21 | CFBundleVersion 22 | 1.0.8 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example2/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | APPL.icns 11 | CFBundleIdentifier 12 | com.fastspring.Example2 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | FSPR 21 | CFBundleVersion 22 | 1.0.8 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgLicense.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgLicense.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /*! 13 | * License information. FsprgLicense is backed by a NSMutableDictionary that 14 | * can be accessed and modified via the raw and setRaw: methods. 15 | */ 16 | @interface FsprgLicense : NSObject { 17 | NSDictionary *raw; 18 | } 19 | 20 | + (FsprgLicense *)licenseWithDictionary:(NSDictionary *)aDictionary; 21 | 22 | - (FsprgLicense *)initWithDictionary:(NSDictionary *)aDictionary; 23 | - (NSDictionary *)raw; 24 | - (void)setRaw:(NSDictionary *)aDictionary; 25 | 26 | - (NSString *)licenseName; 27 | - (NSString *)licenseEmail; 28 | - (NSString *)licenseCompany; 29 | - (NSString *)firstLicenseCode; 30 | - (NSArray *)licenseCodes; 31 | - (NSDictionary *)licensePropertyList; 32 | - (NSURL *)licenseURL; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | APPL.icns 11 | CFBundleIdentifier 12 | com.fastspring.TestApp 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | FSPR 21 | CFBundleVersion 22 | 1.0.8 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example2/OrderView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Order 5 | 6 | 7 | 13 | 14 | 15 |
Thanks for your order {{ order.customerFirstName }}!
16 |
Ordered items
17 | {% for orderItem in order.orderItems %} 18 |
19 |
20 | {{ orderItem.productName }} 21 | {% if orderItem.quantity > 1 %} ({{ orderItem.quantity }}) {% /if %} 22 |
23 |
Your license key: {{ orderItem.license.firstLicenseCode }}
24 |
25 | {% /for %} 26 | 27 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgFulfillment.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgFulfillment.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /*! 13 | * Order fulfillment information. FsprgFulfillment is backed by a NSMutableDictionary that 14 | * can be accessed and modified via the raw and setRaw: methods. 15 | */ 16 | @interface FsprgFulfillment : NSObject { 17 | NSDictionary *raw; 18 | } 19 | 20 | + (FsprgFulfillment *)fulfillmentWithDictionary:(NSDictionary *)aDictionary; 21 | 22 | - (FsprgFulfillment *)initWithDictionary:(NSDictionary *)aDictionary; 23 | - (NSDictionary *)raw; 24 | - (void)setRaw:(NSDictionary *)aDictionary; 25 | 26 | /*! 27 | * @param aKey type of fulfillment (e.g. license, download) 28 | * @result Specific fulfillment information (FsprgLicense, FsprgFileDownload). 29 | */ 30 | - (id)valueForKey:(NSString *)aKey; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /TestApp/AppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.h 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgEmbeddedStore.h" 12 | 13 | 14 | @interface AppController : NSObject { 15 | IBOutlet NSWindow *window; 16 | IBOutlet NSView* settingsView; 17 | IBOutlet NSView* previewView; 18 | IBOutlet NSTextField* previewURL; 19 | IBOutlet NSTextField* previewPageType; 20 | IBOutlet WebView* previewWebView; 21 | FsprgStoreParameters *params; 22 | FsprgEmbeddedStoreController *storeController; 23 | } 24 | 25 | - (FsprgStoreParameters *)params; 26 | - (void)setParams:(FsprgStoreParameters *)aParams; 27 | - (FsprgEmbeddedStoreController *)storeController; 28 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController; 29 | - (IBAction)switchToSettings:(id)sender; 30 | - (IBAction)switchToPreview:(id)sender; 31 | - (IBAction)reloadPreview:(id)sender; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /FsprgEmbeddedStoreStyle/window.xhtml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Store 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | Next 19 |
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License: http://en.wikipedia.org/wiki/MIT_License 2 | 3 | Copyright (c) 2010 FastSpring, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgOrder.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrder.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FsprgOrderItem.h" 11 | 12 | 13 | /*! 14 | * Order information. FsprgOrder is backed by a NSMutableDictionary that 15 | * can be accessed and modified via the raw and setRaw: methods. 16 | */ 17 | @interface FsprgOrder : NSObject { 18 | NSDictionary *raw; 19 | } 20 | 21 | + (FsprgOrder *)orderFromData:(NSData *)aData; 22 | 23 | - (FsprgOrder *)initWithDictionary:(NSDictionary *)aDictionary; 24 | - (NSDictionary *)raw; 25 | - (void)setRaw:(NSDictionary *)aDictionary; 26 | 27 | - (BOOL)orderIsTest; 28 | - (NSString *)orderReference; 29 | - (NSString *)orderLanguage; 30 | - (NSString *)orderCurrency; 31 | - (NSNumber *)orderTotal; 32 | - (NSNumber *)orderTotalUSD; 33 | - (NSString *)customerFirstName; 34 | - (NSString *)customerLastName; 35 | - (NSString *)customerCompany; 36 | - (NSString *)customerEmail; 37 | 38 | /*! 39 | * Shortcut for [[self orderItems] objectAtIndex:0]. 40 | * @result First item. 41 | */ 42 | - (FsprgOrderItem *)firstOrderItem; 43 | - (NSArray *)orderItems; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Example2/NSArray_DeepMutableCopy.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray_DeepMutableCopy.m 3 | // 4 | // Created by Matt Gemmell on 02/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "NSArray_DeepMutableCopy.h" 9 | 10 | 11 | @implementation NSArray (DeepMutableCopy) 12 | 13 | 14 | - (NSMutableArray *)deepMutableCopy; 15 | { 16 | NSMutableArray *newArray; 17 | unsigned int index, count; 18 | 19 | count = [self count]; 20 | newArray = [[NSMutableArray allocWithZone:[self zone]] initWithCapacity:count]; 21 | for (index = 0; index < count; index++) { 22 | id anObject; 23 | 24 | anObject = [self objectAtIndex:index]; 25 | if ([anObject respondsToSelector:@selector(deepMutableCopy)]) { 26 | anObject = [anObject deepMutableCopy]; 27 | [newArray addObject:anObject]; 28 | [anObject release]; 29 | } else if ([anObject respondsToSelector:@selector(mutableCopyWithZone:)]) { 30 | anObject = [anObject mutableCopyWithZone:nil]; 31 | [newArray addObject:anObject]; 32 | [anObject release]; 33 | } else { 34 | [newArray addObject:anObject]; 35 | } 36 | } 37 | 38 | return newArray; 39 | } 40 | 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgFileDownload.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgFileDownload.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgFileDownload.h" 10 | 11 | 12 | @implementation FsprgFileDownload 13 | 14 | + (FsprgFileDownload *)fileDownloadWithDictionary:(NSDictionary *)aDictionary 15 | { 16 | return [[[FsprgFileDownload alloc] initWithDictionary:aDictionary] autorelease]; 17 | } 18 | 19 | - (FsprgFileDownload *)initWithDictionary:(NSDictionary *)aDictionary 20 | { 21 | self = [super init]; 22 | if (self != nil) { 23 | [self setRaw:aDictionary]; 24 | } 25 | return self; 26 | } 27 | - (NSDictionary *)raw 28 | { 29 | return [[raw retain] autorelease]; 30 | } 31 | - (void)setRaw:(NSDictionary *)aDictionary 32 | { 33 | if (raw != aDictionary) { 34 | [raw release]; 35 | raw = [aDictionary retain]; 36 | } 37 | } 38 | 39 | - (NSURL *)fileURL 40 | { 41 | return [NSURL URLWithString:[[self raw] valueForKey:@"FileURL"]]; 42 | } 43 | 44 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 45 | { 46 | // Don't need KVO as data won't change. Prevent having to keep (retain) instance variables. 47 | return FALSE; 48 | } 49 | 50 | - (void)dealloc 51 | { 52 | [self setRaw:nil]; 53 | 54 | [super dealloc]; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Example2/NSDictionary_DeepMutableCopy.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary_DeepMutableCopy.m 3 | // 4 | // Created by Matt Gemmell on 02/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "NSDictionary_DeepMutableCopy.h" 9 | 10 | 11 | @implementation NSDictionary (DeepMutableCopy) 12 | 13 | 14 | - (NSMutableDictionary *)deepMutableCopy; 15 | { 16 | NSMutableDictionary *newDictionary; 17 | NSEnumerator *keyEnumerator; 18 | id anObject; 19 | id aKey; 20 | 21 | newDictionary = [self mutableCopy]; 22 | // Run through the new dictionary and replace any objects that respond to -deepMutableCopy or -mutableCopy with copies. 23 | keyEnumerator = [[newDictionary allKeys] objectEnumerator]; 24 | while ((aKey = [keyEnumerator nextObject])) { 25 | anObject = [newDictionary objectForKey:aKey]; 26 | if ([anObject respondsToSelector:@selector(deepMutableCopy)]) { 27 | anObject = [anObject deepMutableCopy]; 28 | [newDictionary setObject:anObject forKey:aKey]; 29 | [anObject release]; 30 | } else if ([anObject respondsToSelector:@selector(mutableCopyWithZone:)]) { 31 | anObject = [anObject mutableCopyWithZone:nil]; 32 | [newDictionary setObject:anObject forKey:aKey]; 33 | [anObject release]; 34 | } else { 35 | [newDictionary setObject:anObject forKey:aKey]; 36 | } 37 | } 38 | 39 | return newDictionary; 40 | } 41 | 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Example2/ICUTemplateMatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // ICUTemplateMatcher.h 3 | // 4 | // Created by Matt Gemmell on 19/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "MGTemplateEngine.h" 9 | 10 | /* 11 | This is an example Matcher for MGTemplateEngine, implemented using libicucore on Leopard, 12 | via the RegexKitLite library: http://regexkit.sourceforge.net/#RegexKitLite 13 | 14 | This project includes everything you need, as long as you're building on Mac OS X 10.5 or later. 15 | 16 | Other matchers can easily be implemented using the MGTemplateEngineMatcher protocol, 17 | if you prefer to use another regex framework, or use another matching method entirely. 18 | */ 19 | 20 | @interface ICUTemplateMatcher : NSObject { 21 | MGTemplateEngine *engine; 22 | NSString *markerStart; 23 | NSString *markerEnd; 24 | NSString *exprStart; 25 | NSString *exprEnd; 26 | NSString *filterDelimiter; 27 | NSString *templateString; 28 | NSString *regex; 29 | } 30 | 31 | @property(assign) MGTemplateEngine *engine; // weak ref 32 | @property(retain) NSString *markerStart; 33 | @property(retain) NSString *markerEnd; 34 | @property(retain) NSString *exprStart; 35 | @property(retain) NSString *exprEnd; 36 | @property(retain) NSString *filterDelimiter; 37 | @property(retain) NSString *templateString; 38 | @property(retain) NSString *regex; 39 | 40 | + (ICUTemplateMatcher *)matcherWithTemplateEngine:(MGTemplateEngine *)theEngine; 41 | 42 | - (NSArray *)argumentsFromString:(NSString *)argString; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgOrderDocumentRepresentation.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderDocumentRepresentation.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgOrderDocumentRepresentation.h" 10 | #import "FsprgEmbeddedStoreController.h" 11 | 12 | 13 | @implementation FsprgOrderDocumentRepresentation 14 | 15 | - (id) init 16 | { 17 | self = [super init]; 18 | if (self != nil) { 19 | [self setOrder:nil]; 20 | } 21 | return self; 22 | } 23 | 24 | - (FsprgOrder *)order 25 | { 26 | return [[order retain] autorelease]; 27 | } 28 | 29 | - (void)setOrder:(FsprgOrder *)anOrder 30 | { 31 | if (order != anOrder) { 32 | [order release]; 33 | order = [anOrder retain]; 34 | } 35 | } 36 | 37 | - (NSString *)title 38 | { 39 | return @""; 40 | } 41 | - (NSString *)documentSource 42 | { 43 | return nil; 44 | } 45 | - (BOOL)canProvideDocumentSource 46 | { 47 | return FALSE; 48 | } 49 | 50 | - (void)setDataSource:(WebDataSource *)aDataSource 51 | { 52 | } 53 | 54 | - (void)receivedData:(NSData *)aData withDataSource:(WebDataSource *)aDataSource 55 | { 56 | [self setOrder:[FsprgOrder orderFromData:aData]]; 57 | FsprgEmbeddedStoreController *delegate = [[[aDataSource webFrame] webView] frameLoadDelegate]; 58 | [[delegate delegate] didReceiveOrder:[self order]]; 59 | } 60 | 61 | - (void)receivedError:(NSError *)anError withDataSource:(WebDataSource *)aDataSource 62 | { 63 | } 64 | 65 | - (void)finishedLoadingWithDataSource:(WebDataSource *)aDataSource 66 | { 67 | } 68 | 69 | - (void)dealloc 70 | { 71 | [self setOrder:nil]; 72 | 73 | [super dealloc]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgOrderItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderItem.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FsprgFulfillment.h" 11 | #import "FsprgLicense.h" 12 | #import "FsprgFileDownload.h" 13 | 14 | 15 | /*! 16 | * Order item information. FsprgOrderItem is backed by a NSMutableDictionary that 17 | * can be accessed and modified via the raw and setRaw: methods. 18 | */ 19 | @interface FsprgOrderItem : NSObject { 20 | NSDictionary *raw; 21 | } 22 | 23 | + (FsprgOrderItem *)itemWithDictionary:(NSDictionary *)aDictionary; 24 | 25 | - (FsprgOrderItem *)initWithDictionary:(NSDictionary *)aDictionary; 26 | - (NSDictionary *)raw; 27 | - (void)setRaw:(NSDictionary *)aDictionary; 28 | 29 | - (NSString *)productName; 30 | - (NSString *)productDisplay; 31 | - (NSNumber *)quantity; 32 | - (NSNumber *)itemTotal; 33 | - (NSNumber *)itemTotalUSD; 34 | 35 | /*! 36 | * This reference can be used to make calls to FastSpring's Subscription API. 37 | * See https://support.fastspring.com/entries/236487-api-subscriptions 38 | */ 39 | - (NSString *)subscriptionReference; 40 | 41 | /*! 42 | * This URL can be presented to the customer to manage their subscription. 43 | */ 44 | - (NSURL *)subscriptionCustomerURL; 45 | 46 | - (FsprgFulfillment *)fulfillment; 47 | 48 | /*! 49 | * Shortcut for [[self fulfillment] valueForKey:@"license"]. 50 | * @result License information. 51 | */ 52 | - (FsprgLicense *)license; 53 | 54 | /*! 55 | * Shortcut for [[self fulfillment] valueForKey:@"download"]. 56 | * @result Download information. 57 | */ 58 | - (FsprgFileDownload *)download; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgFulfillment.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgFulfillment.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgFulfillment.h" 10 | #import "FsprgLicense.h" 11 | #import "FsprgFileDownload.h" 12 | 13 | 14 | @implementation FsprgFulfillment 15 | 16 | + (FsprgFulfillment *)fulfillmentWithDictionary:(NSDictionary *)aDictionary 17 | { 18 | return [[[FsprgFulfillment alloc] initWithDictionary:aDictionary] autorelease]; 19 | } 20 | 21 | - (FsprgFulfillment *)initWithDictionary:(NSDictionary *)aDictionary 22 | { 23 | self = [super init]; 24 | if (self != nil) { 25 | [self setRaw:aDictionary]; 26 | } 27 | return self; 28 | } 29 | - (NSDictionary *)raw 30 | { 31 | return [[raw retain] autorelease]; 32 | } 33 | 34 | - (void)setRaw:(NSDictionary *)aDictionary 35 | { 36 | if (raw != aDictionary) { 37 | [raw release]; 38 | raw = [aDictionary retain]; 39 | } 40 | } 41 | 42 | - (id)valueForKey:(NSString *)aKey 43 | { 44 | NSDictionary *anItem = [[self raw] valueForKey:aKey]; 45 | 46 | if([[anItem valueForKey:@"FulfillmentType"] isEqual:@"License"]) { 47 | return [FsprgLicense licenseWithDictionary:anItem]; 48 | } 49 | if([[anItem valueForKey:@"FulfillmentType"] isEqual:@"File"]) { 50 | return [FsprgFileDownload fileDownloadWithDictionary:anItem]; 51 | } 52 | 53 | return anItem; 54 | } 55 | 56 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 57 | { 58 | // Don't need KVO as data won't change. Prevent having to keep (retain) instance variables. 59 | return FALSE; 60 | } 61 | 62 | - (void)dealloc 63 | { 64 | [self setRaw:nil]; 65 | 66 | [super dealloc]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /TestApp/OrderViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrderViewController.m 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "OrderViewController.h" 10 | 11 | 12 | @implementation OrderViewController 13 | 14 | - (NSString *)fileName 15 | { 16 | return [[fileName retain] autorelease]; 17 | } 18 | 19 | - (void)setFileName:(NSString *)aFileName 20 | { 21 | if (fileName != aFileName) { 22 | [fileName release]; 23 | fileName = [aFileName retain]; 24 | } 25 | } 26 | 27 | - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo 28 | { 29 | if (returnCode == NSOKButton) { 30 | [self setFileName:[[sheet URL] path]]; 31 | 32 | NSError *error = nil; 33 | [[self representedObject] writeToFile:[self fileName] atomically:FALSE encoding:NSUTF8StringEncoding error:&error]; 34 | } 35 | } 36 | 37 | - (IBAction)saveAs:(id)sender 38 | { 39 | NSSavePanel* savePanel = [NSSavePanel savePanel]; 40 | 41 | [savePanel setAllowedFileTypes:[NSArray arrayWithObject:@"plist"]]; 42 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 43 | [savePanel beginSheetModalForWindow:[[self view] window] 44 | completionHandler:^(NSInteger result) { 45 | [self savePanelDidEnd:savePanel returnCode:result contextInfo:nil]; 46 | }]; 47 | #else 48 | [savePanel beginSheetForDirectory:nil 49 | file:[[self fileName] lastPathComponent] 50 | modalForWindow:[[self view] window] 51 | modalDelegate:self 52 | didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) 53 | contextInfo:nil]; 54 | #endif 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | [self setFileName:nil]; 60 | 61 | [super dealloc]; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgLicense.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgLicense.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgLicense.h" 10 | 11 | 12 | @implementation FsprgLicense 13 | 14 | + (FsprgLicense *)licenseWithDictionary:(NSDictionary *)aDictionary 15 | { 16 | return [[[FsprgLicense alloc] initWithDictionary:aDictionary] autorelease]; 17 | } 18 | 19 | - (FsprgLicense *)initWithDictionary:(NSDictionary *)aDictionary 20 | { 21 | self = [super init]; 22 | if (self != nil) { 23 | [self setRaw:aDictionary]; 24 | } 25 | return self; 26 | } 27 | - (NSDictionary *)raw 28 | { 29 | return [[raw retain] autorelease]; 30 | } 31 | 32 | - (void)setRaw:(NSDictionary *)aDictionary 33 | { 34 | if (raw != aDictionary) { 35 | [raw release]; 36 | raw = [aDictionary retain]; 37 | } 38 | } 39 | 40 | - (NSString *)licenseName 41 | { 42 | return [[self raw] valueForKey:@"LicenseName"]; 43 | } 44 | 45 | - (NSString *)licenseEmail 46 | { 47 | return [[self raw] valueForKey:@"LicenseEmail"]; 48 | } 49 | 50 | - (NSString *)licenseCompany 51 | { 52 | return [[self raw] valueForKey:@"LicenseCompany"]; 53 | } 54 | 55 | - (NSString *)firstLicenseCode 56 | { 57 | return [[self licenseCodes] objectAtIndex:0]; 58 | } 59 | 60 | - (NSArray *)licenseCodes 61 | { 62 | return [[self raw] valueForKey:@"LicenseCodes"]; 63 | } 64 | 65 | - (NSDictionary *)licensePropertyList 66 | { 67 | return [[self raw] valueForKey:@"LicensePropertyList"]; 68 | } 69 | 70 | - (NSURL *)licenseURL 71 | { 72 | return [NSURL URLWithString:[[self raw] valueForKey:@"LicenseURL"]]; 73 | } 74 | 75 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 76 | { 77 | // Don't need KVO as data won't change. Prevent having to keep (retain) instance variables. 78 | return FALSE; 79 | } 80 | 81 | - (void)dealloc 82 | { 83 | [self setRaw:nil]; 84 | 85 | [super dealloc]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Tests/Model/FsprgOrderTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderTest.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgOrderTest.h" 10 | 11 | 12 | @implementation FsprgOrderTest 13 | 14 | - (void) setUp 15 | { 16 | NSString *orderFilePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"complicated" ofType:@"plist"]; 17 | NSData *orderData = [NSData dataWithContentsOfFile:orderFilePath]; 18 | order = [[FsprgOrder orderFromData:orderData] retain]; 19 | } 20 | 21 | - (void) tearDown 22 | { 23 | [order release]; 24 | } 25 | 26 | - (void)testCustomerEmail 27 | { 28 | STAssertEqualObjects(@"ryan@dewell.org", [order customerEmail], nil); 29 | STAssertEqualObjects(@"ryan@dewell.org", [order valueForKeyPath:@"customerEmail"], nil); 30 | } 31 | 32 | - (void)testProductName 33 | { 34 | STAssertEqualObjects(@"ABC Book Organizer", [[order firstOrderItem] productName], nil); 35 | STAssertEqualObjects(@"ABC Book Organizer", [order valueForKeyPath:@"firstOrderItem.productName"], nil); 36 | } 37 | 38 | - (void)testLicenseCode 39 | { 40 | STAssertEqualObjects(@"QEHCDK-375722-EVDEAM-744794-5934", [[[[[order firstOrderItem] fulfillment] valueForKey:@"supportLicense"] licenseCodes] objectAtIndex:0], nil); 41 | STAssertEqualObjects(@"QEHCDK-375722-EVDEAM-744794-5934", [order valueForKeyPath:@"firstOrderItem.fulfillment.supportLicense.firstLicenseCode"], nil); 42 | STAssertEquals(1, [[order valueForKeyPath:@"firstOrderItem.fulfillment.supportLicense.licenseCodes.@count"] intValue], nil); 43 | } 44 | 45 | - (void)testFileURL 46 | { 47 | STAssertEqualObjects(@"https://localhost:8443/bm-hosted/demo/order/dl/DM100219-7871-29151/8508", 48 | [[[[[order firstOrderItem] fulfillment] valueForKey:@"windows_file"] fileURL] description], nil); 49 | STAssertEqualObjects(@"https://localhost:8443/bm-hosted/demo/order/dl/DM100219-7871-29151/8508", 50 | [[order valueForKeyPath:@"firstOrderItem.fulfillment.windows_file.fileURL"] description], nil); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgOrderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderView.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/18/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgOrderView.h" 10 | #import "FsprgOrderDocumentRepresentation.h" 11 | #import "FsprgOrder.h" 12 | #import "FsprgEmbeddedStoreController.h" 13 | 14 | 15 | @implementation FsprgOrderView 16 | 17 | - (id)initWithFrame:(NSRect)frame 18 | { 19 | self = [super initWithFrame:frame]; 20 | if (self) { 21 | [self setDataSource:nil]; 22 | [self setNeedsLayout:FALSE]; 23 | [self setAutoresizesSubviews:TRUE]; 24 | [self setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 25 | } 26 | return self; 27 | } 28 | 29 | - (WebDataSource *)dataSource 30 | { 31 | return [[dataSource retain] autorelease]; 32 | } 33 | - (void)setDataSource:(WebDataSource *)aDataSource 34 | { 35 | if (dataSource != aDataSource) { 36 | [dataSource release]; 37 | dataSource = [aDataSource retain]; 38 | } 39 | } 40 | - (void)dataSourceUpdated:(WebDataSource *)aDataSource 41 | { 42 | [self setDataSource:aDataSource]; 43 | } 44 | 45 | - (BOOL)needsLayout 46 | { 47 | return needsLayout; 48 | } 49 | 50 | - (void)setNeedsLayout:(BOOL)flag 51 | { 52 | needsLayout = flag; 53 | } 54 | 55 | - (void)drawRect:(NSRect)aRect 56 | { 57 | if([self needsLayout]) { 58 | [self setNeedsLayout:FALSE]; 59 | [self layout]; 60 | } 61 | [super drawRect:aRect]; 62 | } 63 | 64 | - (void)layout 65 | { 66 | if([[self subviews] count] == 0) { 67 | [self setFrame:[[self superview] frame]]; 68 | 69 | FsprgOrderDocumentRepresentation *representation = [[self dataSource] representation]; 70 | FsprgOrder *order = [representation order]; 71 | 72 | FsprgEmbeddedStoreController *delegate = [[[[self dataSource] webFrame] webView] UIDelegate]; 73 | NSView *newSubview = [[delegate delegate] viewWithFrame:[self frame] forOrder:order]; 74 | [self addSubview:newSubview]; 75 | } 76 | } 77 | 78 | - (void)viewDidMoveToHostWindow 79 | { 80 | } 81 | - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow 82 | { 83 | } 84 | 85 | - (void)dealloc 86 | { 87 | [self setDataSource:nil]; 88 | 89 | [super dealloc]; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgEmbeddedStoreController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgEmbeddedStoreController.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgEmbeddedStoreDelegate.h" 12 | 13 | 14 | /*! 15 | * Controller for FastSpring's embedded store. 16 | */ 17 | @interface FsprgEmbeddedStoreController : NSObject { 18 | WebView* webView; 19 | id delegate; 20 | NSString *storeHost; 21 | NSMutableDictionary *hostCertificates; 22 | NSMapTable *connectionsToRequests; 23 | } 24 | 25 | - (WebView *)webView; 26 | /*! 27 | * Connects this controller to a web view. 28 | * @param aWebView Web view to connect. 29 | */ 30 | - (void)setWebView:(WebView *)aWebView; 31 | 32 | - (id )delegate; 33 | /*! 34 | * Sets a delegate to which it has a weak reference. 35 | * @param aDelegate Delegate to set. 36 | */ 37 | - (void)setDelegate:(id )aDelegate; 38 | 39 | /*! 40 | * Loads the store using the given parameters. 41 | * @param parameters Parameters that get passed to the store. 42 | */ 43 | - (void)loadWithParameters:(FsprgStoreParameters *)parameters; 44 | 45 | /*! 46 | * Loads the store with content of a file (XML plist). Useful to develop and test the order confirmation view. You can create that plist file by using the bundeled TestApp.app. 47 | * @param aPath File path. 48 | */ 49 | - (void)loadWithContentsOfFile:(NSString *)aPath; 50 | 51 | /*! 52 | * Useful to trigger e.g. the hidden flag of a progress bar. 53 | * @result TRUE if loading a page. 54 | */ 55 | - (BOOL)isLoading; 56 | 57 | /*! 58 | * Useful to provide the value for a progress bar. 59 | * @result The loading progress in percent of a page (0 - 100) 60 | */ 61 | - (double)estimatedLoadingProgress; 62 | 63 | /** 64 | * Useful to show a secure icon. 65 | * @result TRUE if connection is secure (SSL) 66 | */ 67 | - (BOOL)isSecure; 68 | 69 | /** 70 | * 71 | * @result NSArray containing SecCertificateRef objects for the host of the main frame, if it was loaded via https. 72 | */ 73 | - (NSArray *)securityCertificates; 74 | 75 | /*! 76 | * Host that delivers the store (e.g. sites.fastspring.com). 77 | * @result nil until the store has been loaded. 78 | */ 79 | - (NSString *)storeHost; 80 | 81 | @end -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgOrderItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrderItem.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/24/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgOrderItem.h" 10 | 11 | 12 | @implementation FsprgOrderItem 13 | 14 | + (FsprgOrderItem *)itemWithDictionary:(NSDictionary *)aDictionary 15 | { 16 | return [[[FsprgOrderItem alloc] initWithDictionary:aDictionary] autorelease]; 17 | } 18 | 19 | - (FsprgOrderItem *)initWithDictionary:(NSDictionary *)aDictionary 20 | { 21 | self = [super init]; 22 | if (self != nil) { 23 | [self setRaw:aDictionary]; 24 | } 25 | return self; 26 | } 27 | - (NSDictionary *)raw 28 | { 29 | return [[raw retain] autorelease]; 30 | } 31 | 32 | - (void)setRaw:(NSDictionary *)aDictionary 33 | { 34 | if (raw != aDictionary) { 35 | [raw release]; 36 | raw = [aDictionary retain]; 37 | } 38 | } 39 | 40 | - (NSString *)productName 41 | { 42 | return [[self raw] valueForKey:@"ProductName"]; 43 | } 44 | 45 | - (NSString *)productDisplay 46 | { 47 | return [[self raw] valueForKey:@"ProductDisplay"]; 48 | } 49 | 50 | - (NSNumber *)quantity 51 | { 52 | return [[self raw] valueForKey:@"Quantity"]; 53 | } 54 | 55 | - (NSNumber *)itemTotal 56 | { 57 | return [[self raw] valueForKey:@"ItemTotal"]; 58 | } 59 | 60 | - (NSNumber *)itemTotalUSD 61 | { 62 | return [[self raw] valueForKey:@"ItemTotalUSD"]; 63 | } 64 | 65 | - (NSString *)subscriptionReference 66 | { 67 | return [[self raw] valueForKey:@"SubscriptionReference"]; 68 | } 69 | 70 | - (NSString *)subscriptionCustomerURL 71 | { 72 | return [NSURL URLWithString:[[self raw] valueForKey:@"SubscriptionCustomerURL"]]; 73 | } 74 | 75 | - (FsprgFulfillment *)fulfillment 76 | { 77 | return [FsprgFulfillment fulfillmentWithDictionary:[[self raw] valueForKey:@"Fulfillment"]]; 78 | } 79 | 80 | - (FsprgLicense *)license 81 | { 82 | return [[self fulfillment] valueForKey:@"license"]; 83 | } 84 | 85 | - (FsprgFileDownload *)download 86 | { 87 | return [[self fulfillment] valueForKey:@"download"]; 88 | } 89 | 90 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 91 | { 92 | // Don't need KVO as data won't change. Prevent having to keep (retain) instance variables. 93 | return FALSE; 94 | } 95 | 96 | - (void)dealloc 97 | { 98 | [self setRaw:nil]; 99 | 100 | [super dealloc]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Example2/MGTemplateMarker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MGTemplateMarker.h 3 | * 4 | * Created by Matt Gemmell on 12/05/2008. 5 | * Copyright 2008 Instinctive Code. All rights reserved. 6 | * 7 | */ 8 | 9 | #import "MGTemplateEngine.h" 10 | 11 | @protocol MGTemplateMarker 12 | @required 13 | - (id)initWithTemplateEngine:(MGTemplateEngine *)engine; // to avoid retain cycles, use a weak reference for engine. 14 | - (NSArray *)markers; // array of markers (each unique across all markers) this object handles. 15 | - (NSArray *)endMarkersForMarker:(NSString *)marker; // returns the possible corresponding end-markers for a marker which has just started a block. 16 | - (NSObject *)markerEncountered:(NSString *)marker withArguments:(NSArray *)args inRange:(NSRange)markerRange 17 | blockStarted:(BOOL *)blockStarted blockEnded:(BOOL *)blockEnded 18 | outputEnabled:(BOOL *)outputEnabled nextRange:(NSRange *)nextRange 19 | currentBlockInfo:(NSDictionary *)blockInfo newVariables:(NSDictionary **)newVariables; 20 | /* Notes for -markerEncountered:... method 21 | Arguments: 22 | marker: marker encountered by the template engine 23 | args: arguments to the marker, in order 24 | markerRange: the range of the marker encountered in the engine's templateString 25 | blockStarted: pointer to BOOL. Set it to YES if the marker just started a block. 26 | blockEnded: pointer to BOOL. Set it to YES if the marker just ended a block. 27 | Note: you should never set both blockStarted and blockEnded in the same call. 28 | outputEnabled: pointer to BOOL, indicating whether the engine is currently outputting. Can be changed to switch output on/off. 29 | nextRange: the next range in the engine's templateString which will be searched. Can be modified if necessary. 30 | currentBlockInfo: information about the current block, if the block was started by this handler; otherwise nil. 31 | Note: if supplied, will include a dictionary of variables set for the current block. 32 | newVariables: variables to set in the template context. If blockStarted is YES, these will be scoped only within the new block. 33 | Note: if currentBlockInfo was specified, variables set in the return dictionary will override/update any variables of 34 | the same name in currentBlockInfo's variables. This is for ease of updating loop-counters or such. 35 | Returns: 36 | A return value to insert into the template output, or nil if nothing should be inserted. 37 | */ 38 | 39 | - (void)engineFinishedProcessingTemplate; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Tests/Model/complicated.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | OrderIsTestOrderReferenceDM100219-7871-29151OrderLanguageenOrderCurrencyUSDOrderTotal47.42CustomerFirstNameRyanCustomerLastNameDewellCustomerCompanyCustomerEmailryan@dewell.orgOrderItemsProductNameABC Book OrganizerProductDisplayABC Book OrganizerQuantity1Fulfillmentwindows_fileFulfillmentTypeFileFileURLhttps://localhost:8443/bm-hosted/demo/order/dl/DM100219-7871-29151/8508supportLicenseFulfillmentTypeLicenseLicenseCount1LicenseCodeQEHCDK-375722-EVDEAM-744794-5934LicenseCodesQEHCDK-375722-EVDEAM-744794-5934fileFulfillmentTypeFileFileURLhttps://localhost:8443/bm-hosted/demo/order/dl/DM100219-7871-29151/8679licenseFulfillmentTypeLicenseLicenseNameRyan DewellLicenseCount1LicenseURLhttps://localhost:8443/bm-hosted/demo/order/dl/DM100219-7871-29151/7358LicensePropertyList 4 | Name 5 | Ryan Dewell 6 | Order 7 | DM100219-7871-29151 8 | Product 9 | Bob 10 | Quantity 11 | 1 12 | Timestamp 13 | Thu, 18 Feb 2010 16:00:00 -0800 14 | Signature 15 | iudvEgPqbs5CpmkYi71BUJeDjA9xQHlacpmQL8i5/lADRq2FWWhUZdOUFSX7EDAZBrWmPCYlfhYS 16 | E2Kwgn0u37H/lN957ylHvYCDwSOBg3lgkYxB2joW13Yl6J3rkGq97t3rmUOJKfCoqWuUWJ33tLqz 17 | ldgGza8PH73u3U9ANTs= 18 | 19 | ProductNameABC Photo OrganizerProductDisplayABC Photo OrganizerQuantity1Fulfillment -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgEmbeddedStoreDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgEmbeddedStoreDelegate.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/22/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FsprgStoreParameters.h" 12 | #import "FsprgOrder.h" 13 | 14 | /*! Type for didLoadPage:ofType: */ 15 | typedef enum { 16 | FsprgPageFS, 17 | FsprgPagePayPal, 18 | FsprgPageUnknown 19 | } FsprgPageType; 20 | 21 | @class FsprgEmbeddedStoreController; 22 | 23 | /*! 24 | * Delegate protocol for FsprgEmbeddedStoreController. 25 | */ 26 | @protocol FsprgEmbeddedStoreDelegate 27 | 28 | /*! 29 | * Gets called on initial load of the store. 30 | * @param url URL of the loaded store 31 | */ 32 | - (void)didLoadStore:(NSURL *)url; 33 | 34 | /*! 35 | * Gets called on subsequent page loads. 36 | * @param url URL of the loaded page 37 | * @param pageType Type of page this url is pointing to 38 | */ 39 | - (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType; 40 | 41 | /*! 42 | * Gets called after finishing the order process. 43 | * @param order Order information 44 | */ 45 | - (void)didReceiveOrder:(FsprgOrder *)order; 46 | 47 | /*! 48 | * Gets called to present the order confirmation. 49 | * @param frame The frame rectangle for the view object 50 | * @param order Order information 51 | * @result The view presenting the order confirmation 52 | */ 53 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order; 54 | 55 | /*! 56 | * Invoked if an error occurs when starting to load data for a page. 57 | * @param sender The web view containing the frame. 58 | * @param error Specifies the type of error that occurred during the load. 59 | * @param frame The frame being loaded. 60 | */ 61 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame; 62 | 63 | /*! 64 | * Invoked when an error occurs loading a committed data source. 65 | * @param sender The web view containing the frame. 66 | * @param error The type of error that occurred during the load. 67 | * @param frame The frame being loaded. 68 | */ 69 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame; 70 | 71 | @optional 72 | /*! 73 | * Gets called after loading the page and upon resizing the window. 74 | * The default embedded store layout assumes that the content div's height will get shortened 75 | * to make room for the navigation bar. If your layout doesn't require that assumption, you can 76 | * return NO here to avoid this manual height fixing. 77 | * @param controller The store controller in question 78 | * @result YES if you'd like the store controller to fix the content div's height, NO otherwise. 79 | */ 80 | - (BOOL)shouldStoreControllerFixContentDivHeight:(FsprgEmbeddedStoreController *)controller; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Model/FsprgOrder.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgOrder.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgOrder.h" 10 | 11 | 12 | @implementation FsprgOrder 13 | 14 | + (FsprgOrder *)orderFromData:(NSData *)aData 15 | { 16 | NSPropertyListFormat *format = nil; 17 | NSString *errorDesc = nil; 18 | 19 | NSDictionary *aDict = [NSPropertyListSerialization propertyListFromData:aData 20 | mutabilityOption:NSPropertyListImmutable 21 | format:format 22 | errorDescription:&errorDesc]; 23 | 24 | 25 | return [[[FsprgOrder alloc] initWithDictionary:aDict] autorelease]; 26 | } 27 | 28 | - (FsprgOrder *)initWithDictionary:(NSDictionary *)aDictionary 29 | { 30 | self = [super init]; 31 | if (self != nil) { 32 | [self setRaw:aDictionary]; 33 | } 34 | return self; 35 | } 36 | - (NSDictionary *)raw 37 | { 38 | return [[raw retain] autorelease]; 39 | } 40 | - (void)setRaw:(NSDictionary *)aDictionary 41 | { 42 | if (raw != aDictionary) { 43 | [raw release]; 44 | raw = [aDictionary retain]; 45 | } 46 | } 47 | 48 | - (BOOL)orderIsTest 49 | { 50 | return [[[self raw] valueForKey:@"OrderIsTest"] boolValue]; 51 | } 52 | 53 | - (NSString *)orderReference 54 | { 55 | return [[self raw] valueForKey:@"OrderReference"]; 56 | } 57 | 58 | - (NSString *)orderLanguage 59 | { 60 | return [[self raw] valueForKey:@"OrderLanguage"]; 61 | } 62 | 63 | - (NSString *)orderCurrency 64 | { 65 | return [[self raw] valueForKey:@"OrderCurrency"]; 66 | } 67 | 68 | - (NSNumber *)orderTotal 69 | { 70 | return [[self raw] valueForKey:@"OrderTotal"]; 71 | } 72 | 73 | - (NSNumber *)orderTotalUSD 74 | { 75 | return [[self raw] valueForKey:@"OrderTotalUSD"]; 76 | } 77 | 78 | - (NSString *)customerFirstName 79 | { 80 | return [[self raw] valueForKey:@"CustomerFirstName"]; 81 | } 82 | 83 | - (NSString *)customerLastName 84 | { 85 | return [[self raw] valueForKey:@"CustomerLastName"]; 86 | } 87 | 88 | - (NSString *)customerCompany 89 | { 90 | return [[self raw] valueForKey:@"CustomerCompany"]; 91 | } 92 | 93 | - (NSString *)customerEmail 94 | { 95 | return [[self raw] valueForKey:@"CustomerEmail"]; 96 | } 97 | 98 | - (FsprgOrderItem *)firstOrderItem 99 | { 100 | NSArray *items = [[self raw] valueForKey:@"OrderItems"]; 101 | return [FsprgOrderItem itemWithDictionary:[items objectAtIndex:0]]; 102 | } 103 | 104 | - (NSArray *)orderItems 105 | { 106 | NSArray *items = [[self raw] valueForKey:@"OrderItems"]; 107 | NSMutableArray *orderItems = [NSMutableArray arrayWithCapacity:[items count]]; 108 | 109 | NSUInteger i, count = [items count]; 110 | for (i = 0; i < count; i++) { 111 | NSDictionary *anItem = [items objectAtIndex:i]; 112 | [orderItems addObject:[FsprgOrderItem itemWithDictionary:anItem]]; 113 | } 114 | 115 | return orderItems; 116 | } 117 | 118 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 119 | { 120 | // Don't need KVO as data won't change. Prevent having to keep (retain) instance variables. 121 | return FALSE; 122 | } 123 | 124 | - (void)dealloc 125 | { 126 | [self setRaw:nil]; 127 | 128 | [super dealloc]; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /FsprgEmbeddedStoreStyle/style.css: -------------------------------------------------------------------------------- 1 | /* FastSpring Mac Embedded Store Styles */ 2 | body { 3 | font: -webkit-control; 4 | font-family: "Lucida Grande", helvetica, sans-serif; 5 | color: -webkit-text; 6 | background: white; 7 | margin: 0px; 8 | padding-bottom: 58px; 9 | } 10 | body:before { 11 | content: ""; 12 | position: fixed; 13 | top: -10px; 14 | left: 0; 15 | width: 100%; 16 | height: 10px; 17 | -webkit-box-shadow: 0px 0px 10px rgba(0,0,0,.8); 18 | box-shadow: 0px 0px 10px rgba(0,0,0,.8); 19 | z-index: 100; 20 | } 21 | h1,h2,h3,h4 { 22 | font-family: "Gill Sans", helvetica, sans-serif; 23 | font-weight: 500; 24 | } 25 | h3 { 26 | font-size: 1.3em; 27 | } 28 | 29 | #FsprgResizableContent { 30 | padding: 20px 30px; 31 | overflow: auto; 32 | } 33 | 34 | .store-page-navigation { 35 | position:fixed; 36 | left: 0; 37 | bottom: 0; 38 | width: 100%; 39 | height: 48px; 40 | padding:0; 41 | margin: 0; 42 | border-top: 1px solid #7f7f7f; 43 | background: -webkit-gradient( 44 | linear, 45 | left bottom, 46 | left top, 47 | color-stop(0.0, rgb(153,153,153)), 48 | color-stop(0.97, rgb(197,197,197)), 49 | color-stop(1, rgb(234,234,234))); 50 | } 51 | .store-page-navigation .store-action-command { 52 | float: right; 53 | margin-right: 30px; 54 | margin-top: 10px; 55 | } 56 | .store-product-detail-image img, 57 | .store-product-list-item-image img { 58 | margin-right: 1em; 59 | padding: .5em; 60 | } 61 | .store-product-list-item-description-short { 62 | margin-top: .5em; 63 | } 64 | 65 | .store-decorate-silk input[type=text] { 66 | height: 18px; 67 | -webkit-border-radius: 4px; 68 | -webkit-appearance: textfield; 69 | background: white; 70 | font: -webkit-control; 71 | padding: 2px 3px; 72 | } 73 | 74 | .store-decorate-silk input[type=password] { 75 | height: 18px; 76 | -webkit-border-radius: 0px; 77 | -webkit-appearance: textfield; 78 | background: white; 79 | font: -webkit-control; 80 | } 81 | 82 | .store-decorate-silk input[type=submit] { 83 | -webkit-appearance: button; 84 | font: -webkit-control; 85 | color: -webkit-text; 86 | padding: 5px; 87 | } 88 | 89 | .core-form-field-label { 90 | margin: 0 0 4px 0; 91 | } 92 | .core-form-field-body { 93 | padding-left: 0px; 94 | margin: 0px; 95 | } 96 | .core-form-field-body-note { 97 | color: #555; 98 | padding-top: 3px; 99 | } 100 | #store-variation-selection-body { 101 | -webkit-appearance: listbox; 102 | } 103 | 104 | a { 105 | text-decoration: none; 106 | } 107 | .store-action-command > a:hover { 108 | color: -webkit-text; 109 | } 110 | .store-action-command > a:visited { 111 | color: -webkit-text; 112 | } 113 | .store-order-item-base-title { 114 | font-weight: 500; 115 | font-size: 1.2em; 116 | } 117 | .store-action-command, 118 | .store-product-detail-offer-title { 119 | -webkit-appearance: button; 120 | -webkit-user-select: none; 121 | -webkit-user-drag: none; 122 | font: -webkit-control; 123 | color: -webkit-text; 124 | cursor: default; 125 | padding: 5px; 126 | } 127 | .store-action-title { 128 | font: -webkit-control; 129 | color: -webkit-text; 130 | } 131 | 132 | #store-processing-element { 133 | font: -webkit-control; 134 | background-image: none; 135 | color: -webkit-text; 136 | padding-right: 10px; 137 | } -------------------------------------------------------------------------------- /Example2/AppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.m 3 | // Example2 4 | // 5 | // Created by Lars Steiger on 3/11/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "AppController.h" 10 | #import "MGTemplateEngine.h" 11 | #import "ICUTemplateMatcher.h" 12 | 13 | 14 | @implementation AppController 15 | 16 | - (id) init 17 | { 18 | self = [super init]; 19 | if (self != nil) { 20 | [self setStoreController:[[[FsprgEmbeddedStoreController alloc] init] autorelease]]; 21 | [[self storeController] setDelegate:self]; 22 | } 23 | return self; 24 | } 25 | 26 | - (void)awakeFromNib 27 | { 28 | [[self storeController] setWebView:storeView]; 29 | [self load:nil]; 30 | } 31 | 32 | - (FsprgEmbeddedStoreController *)storeController 33 | { 34 | return [[storeController retain] autorelease]; 35 | } 36 | 37 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController 38 | { 39 | if (storeController != aStoreController) { 40 | [storeController release]; 41 | storeController = [aStoreController retain]; 42 | } 43 | } 44 | 45 | - (IBAction)load:(id)sender 46 | { 47 | FsprgStoreParameters *parameters = [FsprgStoreParameters parameters]; 48 | [parameters setOrderProcessType:kFsprgOrderProcessDetail]; 49 | [parameters setStoreId:@"your_store" withProductId:@"your_product"]; 50 | [parameters setMode:kFsprgModeTest]; 51 | 52 | [[self storeController] loadWithParameters:parameters]; 53 | } 54 | 55 | // FsprgEmbeddedStoreDelegate 56 | 57 | - (void)didLoadStore:(NSURL *)url 58 | { 59 | } 60 | 61 | - (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType 62 | { 63 | } 64 | 65 | - (void)didReceiveOrder:(FsprgOrder *)order 66 | { 67 | NSLog(@"Order from %@ successfully received.", [order customerEmail]); 68 | } 69 | 70 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order 71 | { 72 | MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; 73 | [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; 74 | 75 | NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"OrderView" ofType:@"html"]; 76 | NSDictionary *variables = [NSDictionary dictionaryWithObject:order forKey:@"order"]; 77 | NSString *htmlString = [engine processTemplateInFileAtPath:templatePath withVariables:variables]; 78 | 79 | NSString *templateDirectory = [templatePath substringToIndex:[templatePath length]-[[templatePath lastPathComponent] length]]; 80 | NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@", templateDirectory]]; 81 | 82 | WebFrame *webFrame = [[[WebView alloc] initWithFrame:frame] mainFrame]; 83 | [webFrame loadHTMLString:htmlString baseURL:baseURL]; 84 | 85 | return [webFrame frameView]; 86 | } 87 | 88 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 89 | { 90 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 91 | } 92 | 93 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 94 | { 95 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 96 | } 97 | 98 | - (void)dealloc 99 | { 100 | [[self storeController] setDelegate:nil]; 101 | [self setStoreController:nil]; 102 | 103 | [super dealloc]; 104 | } 105 | 106 | @end -------------------------------------------------------------------------------- /Example1/AppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.m 3 | // Example1 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "AppController.h" 10 | #import 11 | #import "OrderViewController.h" 12 | 13 | 14 | @implementation AppController 15 | 16 | - (id) init 17 | { 18 | self = [super init]; 19 | if (self != nil) { 20 | [self setStoreController:[[[FsprgEmbeddedStoreController alloc] init] autorelease]]; 21 | [[self storeController] setDelegate:self]; 22 | } 23 | return self; 24 | } 25 | 26 | - (void)awakeFromNib 27 | { 28 | [[self storeController] setWebView:storeView]; 29 | [self load:nil]; 30 | } 31 | 32 | - (FsprgEmbeddedStoreController *)storeController 33 | { 34 | return [[storeController retain] autorelease]; 35 | } 36 | 37 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController 38 | { 39 | if (storeController != aStoreController) { 40 | [storeController release]; 41 | storeController = [aStoreController retain]; 42 | } 43 | } 44 | 45 | - (IBAction)load:(id)sender 46 | { 47 | FsprgStoreParameters *parameters = [FsprgStoreParameters parameters]; 48 | [parameters setOrderProcessType:kFsprgOrderProcessDetail]; 49 | [parameters setStoreId:@"your_store" withProductId:@"your_product"]; 50 | [parameters setMode:kFsprgModeTest]; 51 | 52 | ABPerson *me = [[ABAddressBook sharedAddressBook] me]; 53 | [parameters setContactFname:[me valueForProperty:kABFirstNameProperty]]; 54 | [parameters setContactLname:[me valueForProperty:kABLastNameProperty]]; 55 | [parameters setContactCompany:[me valueForProperty:kABOrganizationProperty]]; 56 | 57 | ABMultiValue *allEmails = [me valueForProperty:kABEmailProperty]; 58 | NSString *email = [allEmails valueAtIndex:[allEmails indexForIdentifier:[allEmails primaryIdentifier]]]; 59 | [parameters setContactEmail:email]; 60 | 61 | ABMultiValue *allPhones = [me valueForProperty:kABPhoneProperty]; 62 | NSString *phone = [allPhones valueAtIndex:[allPhones indexForIdentifier:[allPhones primaryIdentifier]]]; 63 | [parameters setContactPhone:phone]; 64 | 65 | [[self storeController] loadWithParameters:parameters]; 66 | } 67 | 68 | // FsprgEmbeddedStoreDelegate 69 | 70 | - (void)didLoadStore:(NSURL *)url 71 | { 72 | } 73 | 74 | - (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType 75 | { 76 | } 77 | 78 | - (void)didReceiveOrder:(FsprgOrder *)order 79 | { 80 | NSLog(@"Order from %@ successfully received.", [order customerEmail]); 81 | } 82 | 83 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order 84 | { 85 | OrderViewController *orderViewController = [[OrderViewController alloc] initWithNibName:@"OrderView" bundle:nil]; 86 | [orderViewController setRepresentedObject:order]; 87 | 88 | [[orderViewController view] setFrame:frame]; 89 | return [orderViewController view]; 90 | } 91 | 92 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 93 | { 94 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 95 | } 96 | 97 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 98 | { 99 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 100 | } 101 | 102 | - (void)dealloc 103 | { 104 | [[self storeController] setDelegate:nil]; 105 | [self setStoreController:nil]; 106 | 107 | [super dealloc]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /Example2/MGTemplateStandardFilters.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTemplateStandardFilters.m 3 | // 4 | // Created by Matt Gemmell on 13/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "MGTemplateStandardFilters.h" 9 | 10 | 11 | #define UPPERCASE @"uppercase" 12 | #define LOWERCASE @"lowercase" 13 | #define CAPITALIZED @"capitalized" 14 | #define DATE_FORMAT @"date_format" 15 | #define COLOR_FORMAT @"color_format" 16 | 17 | 18 | @implementation MGTemplateStandardFilters 19 | 20 | 21 | - (NSArray *)filters 22 | { 23 | return [NSArray arrayWithObjects: 24 | UPPERCASE, LOWERCASE, CAPITALIZED, 25 | DATE_FORMAT, COLOR_FORMAT, 26 | nil]; 27 | } 28 | 29 | 30 | - (NSObject *)filterInvoked:(NSString *)filter withArguments:(NSArray *)args onValue:(NSObject *)value 31 | { 32 | if ([filter isEqualToString:UPPERCASE]) { 33 | return [[NSString stringWithFormat:@"%@", value] uppercaseString]; 34 | 35 | } else if ([filter isEqualToString:LOWERCASE]) { 36 | return [[NSString stringWithFormat:@"%@", value] lowercaseString]; 37 | 38 | } else if ([filter isEqualToString:CAPITALIZED]) { 39 | return [[NSString stringWithFormat:@"%@", value] capitalizedString]; 40 | 41 | } else if ([filter isEqualToString:DATE_FORMAT]) { 42 | // Formats NSDates according to Unicode syntax: 43 | // http://unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns 44 | // e.g. "dd MM yyyy" etc. 45 | if ([value isKindOfClass:[NSDate class]] && [args count] == 1) { 46 | NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; 47 | [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; 48 | NSString *format = [args objectAtIndex:0]; 49 | [dateFormatter setDateFormat:format]; 50 | return [dateFormatter stringFromDate:(NSDate *)value]; 51 | } 52 | 53 | } else if ([filter isEqualToString:COLOR_FORMAT]) { 54 | #if TARGET_OS_IPHONE 55 | if ([value isKindOfClass:[UIColor class]] && [args count] == 1) { 56 | #else 57 | if ([value isKindOfClass:[NSColor class]] && [args count] == 1) { 58 | #endif 59 | NSString *format = [[args objectAtIndex:0] lowercaseString]; 60 | if ([format isEqualToString:@"hex"]) { 61 | // Output color in hex format RRGGBB (without leading # character). 62 | #if TARGET_OS_IPHONE 63 | CGColorRef color = [(UIColor *)value CGColor]; 64 | CGColorSpaceRef colorSpace = CGColorGetColorSpace(color); 65 | CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace); 66 | 67 | if (colorSpaceModel != kCGColorSpaceModelRGB) 68 | return @"000000"; 69 | 70 | const CGFloat *components = CGColorGetComponents(color); 71 | NSString *colorHex = [NSString stringWithFormat:@"%02x%02x%02x", 72 | (int)(components[0] * 255), 73 | (int)(components[1] * 255), 74 | (int)(components[2] * 255)]; 75 | return colorHex; 76 | #else 77 | NSColor *color = [(NSColor *)value colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; 78 | if (!color) { // happens if the colorspace couldn't be converted 79 | return @"000000"; // black 80 | } else { 81 | NSString *colorHex = [NSString stringWithFormat:@"%02x%02x%02x", 82 | (int)([color redComponent] * 255), 83 | (int)([color greenComponent] * 255), 84 | (int)([color blueComponent] * 255)]; 85 | return colorHex; 86 | } 87 | #endif 88 | } 89 | } 90 | 91 | } 92 | 93 | return value; 94 | } 95 | 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/Tests/FsprgStoreParametersTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgStoreParametersTest.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 3/1/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgStoreParametersTest.h" 10 | 11 | 12 | @implementation FsprgStoreParametersTest 13 | 14 | - (void) setUp 15 | { 16 | params = [[FsprgStoreParameters parameters] retain]; 17 | [params setOrderProcessType:kFsprgOrderProcessDetail]; 18 | } 19 | 20 | - (void) tearDown 21 | { 22 | [params release]; 23 | } 24 | 25 | - (void)testEmpty 26 | { 27 | STAssertEqualObjects(@"http://sites.fastspring.com//product/", [[params toURL] description], nil); 28 | } 29 | 30 | - (void)testNoParam 31 | { 32 | [params setStoreId:@"storeId" withProductId:@"productId"]; 33 | 34 | STAssertEqualObjects(@"http://sites.fastspring.com/storeId/product/productId", [[params toURL] description], nil); 35 | } 36 | 37 | - (void)testOneParam 38 | { 39 | [params setStoreId:@"storeId" withProductId:@"productId"]; 40 | [params setMode:kFsprgModeTest]; 41 | 42 | STAssertEqualObjects(@"http://sites.fastspring.com/storeId/product/productId?mode=test", [[params toURL] description], nil); 43 | } 44 | 45 | - (void)testTwoParams 46 | { 47 | [params setStoreId:@"storeId" withProductId:@"productId"]; 48 | [params setMode:kFsprgModeTest]; 49 | [params setCampaign:@"aCampaign"]; 50 | 51 | STAssertEqualObjects(@"http://sites.fastspring.com/storeId/product/productId?campaign=aCampaign&mode=test", [[params toURL] description], nil); 52 | } 53 | 54 | - (void)testAllParams 55 | { 56 | [params setLanguage:@"aLanguage"]; 57 | [params setStoreId:@"storeId" withProductId:@"productId"]; 58 | [params setMode:kFsprgModeTest]; 59 | [params setCampaign:@"aCampaign"]; 60 | [params setOption:@"anOption"]; 61 | [params setReferrer:@"aReferrer"]; 62 | [params setSource:@"aSource"]; 63 | 64 | STAssertEqualObjects(@"http://sites.fastspring.com/storeId/product/productId?campaign=aCampaign&language=aLanguage&mode=test&option=anOption&referrer=aReferrer&source=aSource", 65 | [[params toURL] description], nil); 66 | } 67 | 68 | - (void)testSpecialChars 69 | { 70 | [params setStoreId:@"ä" withProductId:@"ö"]; 71 | [params setCampaign:@"ü"]; 72 | [params setOption:@">"]; 73 | [params setReferrer:@"<"]; 74 | [params setSource:@"%"]; 75 | 76 | STAssertEqualObjects(@"http://sites.fastspring.com/%C3%A4/product/%C3%B6?campaign=%C3%BC&option=%3E&referrer=%3C&source=%25", 77 | [[params toURL] description], nil); 78 | } 79 | 80 | - (void)testParamViaRaw 81 | { 82 | [params setStoreId:@"storeId" withProductId:@"productId"]; 83 | [[params raw] setValue:@"aValue" forKey:@"additional"]; 84 | 85 | STAssertEqualObjects(@"http://sites.fastspring.com/storeId/product/productId?additional=aValue", [[params toURL] description], nil); 86 | } 87 | 88 | - (void)testContactDefaults 89 | { 90 | [params setStoreId:@"storeId" withProductId:@"productId"]; 91 | [params setContactFname:@"fname"]; 92 | [params setContactLname:@"lname"]; 93 | [params setContactEmail:@"email"]; 94 | [params setContactCompany:@"company"]; 95 | [params setContactPhone:@"phone"]; 96 | 97 | STAssertEqualObjects(@"https://sites.fastspring.com/storeId/product/productId?contact_company=company&contact_email=email&contact_fname=fname&contact_lname=lname&contact_phone=phone", [[params toURL] description], nil); 98 | } 99 | 100 | - (void)testInstantOrderProcess 101 | { 102 | [params setOrderProcessType:kFsprgOrderProcessInstant]; 103 | [params setStoreId:@"storeId" withProductId:@"productId"]; 104 | 105 | STAssertEqualObjects(@"https://sites.fastspring.com/storeId/instant/productId", [[params toURL] description], nil); 106 | } 107 | 108 | - (void)testUnknownOrderProcess 109 | { 110 | [params setOrderProcessType:@"foo"]; 111 | [params setStoreId:@"storeId" withProductId:@"productId"]; 112 | 113 | STAssertThrows([params toURLRequest], nil); 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgStoreParameters.h: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgStoreParameters.h 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/19/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /*! Constants for setOrderProcessType: */ 12 | extern NSString * const kFsprgOrderProcessDetail; 13 | extern NSString * const kFsprgOrderProcessInstant; 14 | extern NSString * const kFsprgOrderProcessCheckout; 15 | 16 | /*! Constants for setMode: */ 17 | extern NSString * const kFsprgModeActive; 18 | extern NSString * const kFsprgModeActiveTest; 19 | extern NSString * const kFsprgModeTest; 20 | 21 | 22 | /*! 23 | * FastSpring store parameters. FsprgStoreParameters is backed by a NSMutableDictionary that 24 | * can be accessed and modified via the raw and setRaw: methods. 25 | */ 26 | @interface FsprgStoreParameters : NSObject { 27 | BOOL hasContactDefaults; 28 | NSMutableDictionary *raw; 29 | } 30 | 31 | + (FsprgStoreParameters *)parameters; 32 | + (FsprgStoreParameters *)parametersWithRaw:(NSMutableDictionary *)aRaw; 33 | 34 | - (NSURLRequest *)toURLRequest; 35 | - (NSURL *)toURL; 36 | 37 | - (id)initWithRaw:(NSMutableDictionary *)aRaw; 38 | - (NSMutableDictionary *)raw; 39 | - (void)setRaw:(NSMutableDictionary *)aRaw; 40 | 41 | /*! 42 | * Pass a language code via the URL to bypass automatic language detection. 43 | * Example: de 44 | */ 45 | - (NSString *)language; 46 | - (void)setLanguage:(NSString *)aLanguage; 47 | 48 | /*! 49 | * Use kFsprgOrderProcessDetail or kFsprgOrderProcessInstant. 50 | */ 51 | - (NSString *)orderProcessType; 52 | - (void)setOrderProcessType:(NSString *)anOrderProcessType; 53 | 54 | /*! 55 | * Store path name and product path name. 56 | * These are found in a full product URL such as sites.fastspring.com//product/ 57 | */ 58 | - (void)setStoreId:(NSString *)aStoreId withProductId:(NSString *)aProductId; 59 | - (NSString *)storeId; 60 | - (void)setStoreId:(NSString *)aStoreId; 61 | - (NSString *)productId; 62 | - (void)setProductId:(NSString *)aProductId; 63 | 64 | /*! 65 | * Use kFsprgModeActive, kFsprgModeActiveTest or kFsprgModeTest. 66 | */ 67 | - (NSString *)mode; 68 | - (void)setMode:(NSString *)aMode; 69 | 70 | /*! 71 | * Used for "External Tracking". Go to "Link Sources" inside SpringBoard. 72 | * Example: november_sale_post 73 | */ 74 | - (NSString *)campaign; 75 | - (void)setCampaign:(NSString *)aCampaign; 76 | 77 | /*! 78 | * Used for advanced and atypical store configuration options. 79 | */ 80 | - (NSString *)option; 81 | - (void)setOption:(NSString *)anOption; 82 | 83 | /*! 84 | * Pass a custom referrer via the URL to override the automatically detected referring URL (HTTP_REFERER). 85 | * The value passed in this parameter is available in notifications and data exports. If a value is 86 | * passed in this parameter then no data will be stored from the HTTP_REFERER header. 87 | * Example: xyz123 88 | */ 89 | - (NSString *)referrer; 90 | - (void)setReferrer:(NSString *)aReferrer; 91 | 92 | /*! 93 | * Used for "External Tracking". Go to "Link Sources" inside SpringBoard. 94 | * Example: my_blog 95 | */ 96 | - (NSString *)source; 97 | - (void)setSource:(NSString *)aSource; 98 | 99 | /*! 100 | * Pass a coupon code via the URL to automatically apply a coupon to the order so that the customer 101 | * does not need to enter it. A corresponding coupon code must be setup and associated with a promotion. 102 | * Example: DECSPECIAL987 103 | */ 104 | - (NSString *)coupon; 105 | - (void)setCoupon:(NSString *)aCoupon; 106 | 107 | - (BOOL)hasContactDefaults; 108 | - (NSString *)contactFname; 109 | - (void)setContactFname:(NSString *)aContactFname; 110 | - (NSString *)contactLname; 111 | - (void)setContactLname:(NSString *)aContactLname; 112 | - (NSString *)contactEmail; 113 | - (void)setContactEmail:(NSString *)aContactEmail; 114 | - (NSString *)contactCompany; 115 | - (void)setContactCompany:(NSString *)aContactCompany; 116 | - (NSString *)contactPhone; 117 | - (void)setContactPhone:(NSString *)aContactPhone; 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /Example2/MGTemplateEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTemplateEngine.h 3 | // 4 | // Created by Matt Gemmell on 11/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | // Keys in blockInfo dictionaries passed to delegate methods. 9 | #define BLOCK_NAME_KEY @"name" // NSString containing block name (first word of marker) 10 | #define BLOCK_END_NAMES_KEY @"endNames" // NSArray containing names of possible ending-markers for block 11 | #define BLOCK_ARGUMENTS_KEY @"args" // NSArray of further arguments in block start marker 12 | #define BLOCK_START_MARKER_RANGE_KEY @"startMarkerRange" // NSRange (as NSValue) of block's starting marker 13 | #define BLOCK_VARIABLES_KEY @"vars" // NSDictionary of variables 14 | 15 | #define TEMPLATE_ENGINE_ERROR_DOMAIN @"MGTemplateEngineErrorDomain" 16 | 17 | @class MGTemplateEngine; 18 | @protocol MGTemplateEngineDelegate 19 | @optional 20 | - (void)templateEngine:(MGTemplateEngine *)engine blockStarted:(NSDictionary *)blockInfo; 21 | - (void)templateEngine:(MGTemplateEngine *)engine blockEnded:(NSDictionary *)blockInfo; 22 | - (void)templateEngineFinishedProcessingTemplate:(MGTemplateEngine *)engine; 23 | - (void)templateEngine:(MGTemplateEngine *)engine encounteredError:(NSError *)error isContinuing:(BOOL)continuing; 24 | @end 25 | 26 | // Keys in marker dictionaries returned from Matcher methods. 27 | #define MARKER_NAME_KEY @"name" // NSString containing marker name (first word of marker) 28 | #define MARKER_TYPE_KEY @"type" // NSString, either MARKER_TYPE_EXPRESSION or MARKER_TYPE_MARKER 29 | #define MARKER_TYPE_MARKER @"marker" 30 | #define MARKER_TYPE_EXPRESSION @"expression" 31 | #define MARKER_ARGUMENTS_KEY @"args" // NSArray of further arguments in marker, if any 32 | #define MARKER_FILTER_KEY @"filter" // NSString containing name of filter attached to marker, if any 33 | #define MARKER_FILTER_ARGUMENTS_KEY @"filterArgs" // NSArray of filter arguments, if any 34 | #define MARKER_RANGE_KEY @"range" // NSRange (as NSValue) of marker's range 35 | 36 | @protocol MGTemplateEngineMatcher 37 | @required 38 | - (id)initWithTemplateEngine:(MGTemplateEngine *)engine; 39 | - (void)engineSettingsChanged; // always called at least once before beginning to process a template. 40 | - (NSDictionary *)firstMarkerWithinRange:(NSRange)range; 41 | @end 42 | 43 | #import "MGTemplateMarker.h" 44 | #import "MGTemplateFilter.h" 45 | 46 | @interface MGTemplateEngine : NSObject { 47 | @public 48 | NSString *markerStartDelimiter; // default: {% 49 | NSString *markerEndDelimiter; // default: %} 50 | NSString *expressionStartDelimiter; // default: {{ 51 | NSString *expressionEndDelimiter; // default: }} 52 | NSString *filterDelimiter; // default: | example: {{ myVar|uppercase }} 53 | NSString *literalStartMarker; // default: literal 54 | NSString *literalEndMarker; // default: /literal 55 | @private 56 | NSMutableArray *_openBlocksStack; 57 | NSMutableDictionary *_globals; 58 | int _outputDisabledCount; 59 | int _templateLength; 60 | NSMutableDictionary *_filters; 61 | NSMutableDictionary *_markers; 62 | NSMutableDictionary *_templateVariables; 63 | BOOL _literal; 64 | @public 65 | NSRange remainingRange; 66 | id delegate; 67 | id matcher; 68 | NSString *templateContents; 69 | } 70 | 71 | @property(retain) NSString *markerStartDelimiter; 72 | @property(retain) NSString *markerEndDelimiter; 73 | @property(retain) NSString *expressionStartDelimiter; 74 | @property(retain) NSString *expressionEndDelimiter; 75 | @property(retain) NSString *filterDelimiter; 76 | @property(retain) NSString *literalStartMarker; 77 | @property(retain) NSString *literalEndMarker; 78 | @property(assign, readonly) NSRange remainingRange; 79 | @property(assign) id delegate; // weak ref 80 | @property(retain) id matcher; 81 | @property(retain, readonly) NSString *templateContents; 82 | 83 | // Creation. 84 | + (NSString *)version; 85 | + (MGTemplateEngine *)templateEngine; 86 | 87 | // Managing persistent values. 88 | - (void)setObject:(id)anObject forKey:(id)aKey; 89 | - (void)addEntriesFromDictionary:(NSDictionary *)dict; 90 | - (id)objectForKey:(id)aKey; 91 | 92 | // Configuration and extensibility. 93 | - (void)loadMarker:(NSObject *)marker; 94 | - (void)loadFilter:(NSObject *)filter; 95 | 96 | // Utilities. 97 | - (NSObject *)resolveVariable:(NSString *)var; 98 | - (NSDictionary *)templateVariables; 99 | 100 | // Processing templates. 101 | - (NSString *)processTemplate:(NSString *)templateString withVariables:(NSDictionary *)variables; 102 | - (NSString *)processTemplateInFileAtPath:(NSString *)templatePath withVariables:(NSDictionary *)variables; 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Example2/RegexKitLite.h: -------------------------------------------------------------------------------- 1 | // 2 | // RegexKitLite.h 3 | // http://regexkit.sourceforge.net/ 4 | // Licensesd under the terms of the BSD License, as specified below. 5 | // 6 | 7 | /* 8 | Copyright (c) 2008, John Engelhart 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | 22 | * Neither the name of the Zang Industries nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 32 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifdef __OBJC__ 40 | 41 | #import 42 | #import 43 | #import 44 | 45 | #endif // __OBJC__ 46 | 47 | #include 48 | #include 49 | 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | 54 | // For Mac OS X < 10.5. 55 | #ifndef NSINTEGER_DEFINED 56 | #define NSINTEGER_DEFINED 57 | #ifdef __LP64__ || NS_BUILD_32_LIKE_64 58 | typedef long NSInteger; 59 | typedef unsigned long NSUInteger; 60 | #define NSIntegerMin LONG_MIN 61 | #define NSIntegerMax LONG_MAX 62 | #define NSUIntegerMax ULONG_MAX 63 | #else 64 | typedef int NSInteger; 65 | typedef unsigned int NSUInteger; 66 | #define NSIntegerMin INT_MIN 67 | #define NSIntegerMax INT_MAX 68 | #define NSUIntegerMax UINT_MAX 69 | #endif 70 | #endif // NSINTEGER_DEFINED 71 | 72 | #ifndef _REGEXKITLITE_H_ 73 | #define _REGEXKITLITE_H_ 74 | 75 | #ifdef __OBJC__ 76 | 77 | @class NSError; 78 | 79 | // NSError error domains and user info keys. 80 | extern NSString * const RKLICURegexErrorDomain; 81 | 82 | extern NSString * const RKLICURegexErrorNameErrorKey; 83 | extern NSString * const RKLICURegexLineErrorKey; 84 | extern NSString * const RKLICURegexOffsetErrorKey; 85 | extern NSString * const RKLICURegexPreContextErrorKey; 86 | extern NSString * const RKLICURegexPostContextErrorKey; 87 | extern NSString * const RKLICURegexRegexErrorKey; 88 | extern NSString * const RKLICURegexRegexOptionsErrorKey; 89 | 90 | // These must be idential to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html 91 | enum { 92 | RKLNoOptions = 0, 93 | RKLCaseless = 2, 94 | RKLComments = 4, 95 | RKLDotAll = 32, 96 | RKLMultiline = 8, 97 | RKLUnicodeWordBoundaries = 256 98 | }; 99 | typedef uint32_t RKLRegexOptions; 100 | 101 | @interface NSString (RegexKitLiteAdditions) 102 | 103 | + (void)clearStringCache; 104 | 105 | + (NSInteger)captureCountForRegex:(NSString *)regexString; 106 | + (NSInteger)captureCountForRegex:(NSString *)regexString options:(RKLRegexOptions)options error:(NSError **)error; 107 | 108 | - (BOOL)isMatchedByRegex:(NSString *)regexString; 109 | - (BOOL)isMatchedByRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error; 110 | 111 | - (NSRange)rangeOfRegex:(NSString *)regexString; 112 | - (NSRange)rangeOfRegex:(NSString *)regexString capture:(NSInteger)capture; 113 | - (NSRange)rangeOfRegex:(NSString *)regexString inRange:(NSRange)range; 114 | - (NSRange)rangeOfRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; 115 | 116 | - (NSString *)stringByMatching:(NSString *)regexString; 117 | - (NSString *)stringByMatching:(NSString *)regexString capture:(NSInteger)capture; 118 | - (NSString *)stringByMatching:(NSString *)regexString inRange:(NSRange)range; 119 | - (NSString *)stringByMatching:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; 120 | 121 | @end 122 | 123 | #endif // _REGEXKITLITE_H_ 124 | 125 | #endif // __OBJC__ 126 | 127 | #ifdef __cplusplus 128 | } // extern "C" 129 | #endif 130 | 131 | -------------------------------------------------------------------------------- /TestApp/AppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.m 3 | // TestApp 4 | // 5 | // Created by Lars Steiger on 2/28/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "AppController.h" 10 | #import "OrderViewController.h" 11 | 12 | static NSString * const kParams = @"params"; 13 | 14 | @implementation AppController 15 | 16 | - (id) init 17 | { 18 | self = [super init]; 19 | if (self != nil) { 20 | [self setStoreController:[[[FsprgEmbeddedStoreController alloc] init] autorelease]]; 21 | 22 | [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setBool:TRUE forKey:@"WebKitDeveloperExtras"]; 23 | 24 | NSDictionary *defaultParams = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] dictionaryForKey:kParams]; 25 | if(defaultParams == nil) { 26 | [self setParams:[FsprgStoreParameters parameters]]; 27 | [[self params] setOrderProcessType:kFsprgOrderProcessDetail]; 28 | [[self params] setMode:kFsprgModeTest]; 29 | } else { 30 | [self setParams:[FsprgStoreParameters parametersWithRaw:(NSMutableDictionary *)defaultParams]]; 31 | } 32 | 33 | [[self storeController] setDelegate:self]; 34 | } 35 | return self; 36 | } 37 | 38 | - (FsprgEmbeddedStoreController *)storeController 39 | { 40 | return [[storeController retain] autorelease]; 41 | } 42 | 43 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController 44 | { 45 | if (storeController != aStoreController) { 46 | [storeController release]; 47 | storeController = [aStoreController retain]; 48 | } 49 | } 50 | 51 | - (FsprgStoreParameters *)params 52 | { 53 | return [[params retain] autorelease]; 54 | } 55 | 56 | - (void)setParams:(FsprgStoreParameters *)aParams 57 | { 58 | if (params != aParams) { 59 | [params release]; 60 | params = [aParams retain]; 61 | } 62 | } 63 | 64 | - (void)awakeFromNib 65 | { 66 | [[self storeController] setWebView:previewWebView]; 67 | 68 | NSToolbarItem *firstItem = [[[window toolbar] items] objectAtIndex:0]; 69 | [[window toolbar] setSelectedItemIdentifier:[firstItem itemIdentifier]]; 70 | [[firstItem target] performSelector:[firstItem action]]; 71 | } 72 | 73 | - (IBAction)switchToSettings:(id)sender 74 | { 75 | [window setContentView:settingsView]; 76 | } 77 | 78 | - (IBAction)switchToPreview:(id)sender 79 | { 80 | [window setContentView:previewView]; 81 | [self reloadPreview:sender]; 82 | } 83 | 84 | - (IBAction)reloadPreview:(id)sender 85 | { 86 | [previewWebView setHidden:TRUE]; 87 | [[self storeController] loadWithParameters:[self params]]; 88 | } 89 | 90 | // FsprgEmbeddedStoreDelegate 91 | 92 | - (void)didLoadStore:(NSURL *)url 93 | { 94 | [previewURL setStringValue:[url absoluteString]]; 95 | [previewPageType setStringValue:@"@FastSpring"]; 96 | [previewPageType setHidden:FALSE]; 97 | [previewWebView setHidden:FALSE]; 98 | } 99 | 100 | - (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType; 101 | { 102 | [previewURL setStringValue:[url absoluteString]]; 103 | switch (pageType) { 104 | case FsprgPageFS: 105 | [previewPageType setStringValue:@"@FastSpring"]; 106 | break; 107 | case FsprgPagePayPal: 108 | [previewPageType setStringValue:@"@PayPal"]; 109 | break; 110 | case FsprgPageUnknown: 111 | default: 112 | [previewPageType setStringValue:@"@Unknown"]; 113 | break; 114 | } 115 | } 116 | 117 | - (void)didReceiveOrder:(FsprgOrder *)order 118 | { 119 | } 120 | 121 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order 122 | { 123 | NSString *errorDesc = nil; 124 | NSData *data = [NSPropertyListSerialization dataFromPropertyList:[order raw] 125 | format:NSPropertyListXMLFormat_v1_0 126 | errorDescription:&errorDesc]; 127 | NSString *orderAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 128 | 129 | OrderViewController *orderViewController = [[OrderViewController alloc] initWithNibName:@"OrderView" bundle:nil]; 130 | [orderViewController setRepresentedObject:orderAsStr]; 131 | 132 | [[[orderViewController view] window] setBackgroundColor:[NSColor blackColor]]; 133 | [[orderViewController view] setFrame:frame]; 134 | return [orderViewController view]; 135 | } 136 | 137 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 138 | { 139 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 140 | } 141 | 142 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 143 | { 144 | NSRunAlertPanel(@"Alert", [error localizedDescription], @"OK", nil, nil); 145 | } 146 | 147 | // NSApplicationDelegate 148 | 149 | - (void)applicationWillTerminate:(NSNotification *)aNotification 150 | { 151 | NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; 152 | [defaults setObject:[[self params] raw] forKey:kParams]; 153 | } 154 | 155 | - (void)dealloc 156 | { 157 | [self setParams:nil]; 158 | [[self storeController] setDelegate:nil]; 159 | [self setStoreController:nil]; 160 | 161 | [super dealloc]; 162 | } 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /Example2/Source Code License.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf270 2 | {\fonttbl\f0\fnil\fcharset0 LucidaGrande;} 3 | {\colortbl;\red255\green255\blue255;\red51\green51\blue51;\red0\green180\blue128;\red255\green0\blue0; 4 | \red31\green105\blue199;\red119\green119\blue119;} 5 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid0\'02\'05.;}{\levelnumbers\'01;}}{\listname ;}\listid1}} 6 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} 7 | \deftab720 8 | \pard\pardeftab720\ql\qnatural 9 | 10 | \f0\b\fs24 \cf2 Matt Gemmell / Instinctive Code Source Code License\ 11 | 12 | \b0\fs22 Last updated: 19th May 2008 13 | \fs24 \ 14 | \ 15 | \ 16 | Thanks for downloading some of our source code!\ 17 | \ 18 | This is the license agreement for the source code which this document accompanies (don\'92t worry: you\'92re allowed to use it in your own products, commercial or otherwise).\ 19 | \ 20 | The full license text is further down this page, and you should only use the source code if you agree to the terms in that text. For convenience, though, we\'92ve put together a human-readable 21 | \b non-authoritative 22 | \b0 interpretation of the license which will hopefully answer any questions you have.\ 23 | \ 24 | \ 25 | 26 | \b \cf3 Green 27 | \b0 \cf2 text shows 28 | \b \cf3 what you can do with the code 29 | \b0 \cf2 .\ 30 | 31 | \b \cf4 Red 32 | \b0 \cf2 text means 33 | \b \cf4 restrictions you must abide by 34 | \b0 \cf2 .\ 35 | \ 36 | Basically, the license says that:\ 37 | \ 38 | \pard\tx220\tx720\pardeftab720\li720\fi-720\ql\qnatural 39 | \ls1\ilvl0\cf2 {\listtext 1. }You can 40 | \b \cf3 use the code in your own products, including commercial and/or closed-source products 41 | \b0 \cf2 .\ 42 | {\listtext 2. }You can 43 | \b \cf3 modify the code 44 | \b0 \cf0 as you wish\cf2 , and 45 | \b \cf3 use the modified code in your products 46 | \b0 \cf2 .\ 47 | {\listtext 3. }You can 48 | \b \cf3 redistribute the original, unmodified code 49 | \b0 \cf2 , but you 50 | \b \cf4 have to include the full license text below 51 | \b0 \cf2 .\ 52 | {\listtext 4. }You can 53 | \b \cf3 redistribute the modified code 54 | \b0 \cf2 as you wish ( 55 | \b \cf4 without the full license text below 56 | \b0 \cf2 ).\ 57 | {\listtext 5. }In all cases, you 58 | \b \cf4 must include a credit mentioning Matt Gemmell 59 | \b0 \cf2 as the original author of the source.\ 60 | {\listtext 6. }Matt Gemmell is \cf0 not liable for anything you do with the code\cf2 , no matter what. So be sensible.\ 61 | {\listtext 7. }You 62 | \b \cf4 can\'92t use the name Matt Gemmell, the name Instinctive Code, the Instinctive Code logo or any other related marks to promote your products 63 | \b0 \cf2 based on the code.\ 64 | {\listtext 8. }If you agree to all of that, go ahead and use the source. Otherwise, don\'92t!\ 65 | \pard\pardeftab720\ql\qnatural 66 | \cf2 \ 67 | 68 | \b \ 69 | \ 70 | Suggested Attribution Format\ 71 | 72 | \b0 \ 73 | The license requires that you give credit to Matt Gemmell, as the original author of any of our source that you use. The placement and format of the credit is up to you, but we prefer the credit to be in the software\'92s \'93About\'94 window. Alternatively, you could put the credit in a list of acknowledgements within the software, in the software\'92s documentation, or on the web page for the software. The suggested format for the attribution is:\ 74 | \ 75 | \pard\pardeftab720\ql\qnatural 76 | 77 | \b \cf0 Includes code by {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 Matt Gemmell}}\cf6 . 78 | \b0 \ 79 | \pard\pardeftab720\ql\qnatural 80 | \cf2 \ 81 | where would be replaced by the name of the specific source-code package you made use of. Where possible, please link the text \'93Matt Gemmell\'94 to the following URL, or include the URL as plain text: {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 http://mattgemmell.com/}}\ 82 | \ 83 | \ 84 | 85 | \b Full Source Code License Text\ 86 | \ 87 | 88 | \b0 Below you can find the actual text of the license agreement. 89 | \b \ 90 | \ 91 | \pard\pardeftab720\ql\qnatural 92 | \cf6 \ 93 | License Agreement for Source Code provided by Matt Gemmell 94 | \b0 \ 95 | \ 96 | This software is supplied to you by Matt Gemmell in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this software.\ 97 | \ 98 | In consideration of your agreement to abide by the following terms, and subject to these terms, Matt Gemmell grants you a personal, non-exclusive license, to use, reproduce, modify and redistribute the software, with or without modifications, in source and/or binary forms; provided that if you redistribute the software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the software, and that in all cases attribution of Matt Gemmell as the original author of the source code shall be included in all such resulting software products or distributions.\uc0\u8232 \ 99 | Neither the name, trademarks, service marks or logos of Matt Gemmell or Instinctive Code may be used to endorse or promote products derived from the software without specific prior written permission from Matt Gemmell. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Matt Gemmell herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the software may be incorporated.\ 100 | \ 101 | The software is provided by Matt Gemmell on an "AS IS" basis. MATT GEMMELL AND INSTINCTIVE CODE MAKE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\ 102 | \ 103 | IN NO EVENT SHALL MATT GEMMELL OR INSTINCTIVE CODE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF MATT GEMMELL OR INSTINCTIVE CODE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ 104 | } -------------------------------------------------------------------------------- /Example2/ICUTemplateMatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // ICUTemplateMatcher.m 3 | // 4 | // Created by Matt Gemmell on 19/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "ICUTemplateMatcher.h" 9 | #import "RegexKitLite.h" 10 | 11 | 12 | @implementation ICUTemplateMatcher 13 | 14 | 15 | + (ICUTemplateMatcher *)matcherWithTemplateEngine:(MGTemplateEngine *)theEngine 16 | { 17 | return [[[ICUTemplateMatcher alloc] initWithTemplateEngine:theEngine] autorelease]; 18 | } 19 | 20 | 21 | - (id)initWithTemplateEngine:(MGTemplateEngine *)theEngine 22 | { 23 | if (self = [super init]) { 24 | self.engine = theEngine; // weak ref 25 | } 26 | 27 | return self; 28 | } 29 | 30 | 31 | - (void)dealloc 32 | { 33 | self.engine = nil; 34 | self.templateString = nil; 35 | self.markerStart = nil; 36 | self.markerEnd = nil; 37 | self.exprStart = nil; 38 | self.exprEnd = nil; 39 | self.filterDelimiter = nil; 40 | self.regex = nil; 41 | 42 | [super dealloc]; 43 | } 44 | 45 | 46 | - (void)engineSettingsChanged 47 | { 48 | // This method is a good place to cache settings from the engine. 49 | self.markerStart = engine.markerStartDelimiter; 50 | self.markerEnd = engine.markerEndDelimiter; 51 | self.exprStart = engine.expressionStartDelimiter; 52 | self.exprEnd = engine.expressionEndDelimiter; 53 | self.filterDelimiter = engine.filterDelimiter; 54 | self.templateString = engine.templateContents; 55 | 56 | // Note: the \Q ... \E syntax causes everything inside it to be treated as literals. 57 | // This help us in the case where the marker/filter delimiters have special meaning 58 | // in regular expressions; notably the "$" character in the default marker start-delimiter. 59 | // Note: the (?m) syntax makes ICU enable multiline matching. 60 | NSString *basePattern = @"(\\Q%@\\E)(?:\\s+)?(.*?)(?:(?:\\s+)?\\Q%@\\E(?:\\s+)?(.*?))?(?:\\s+)?\\Q%@\\E"; 61 | NSString *mrkrPattern = [NSString stringWithFormat:basePattern, self.markerStart, self.filterDelimiter, self.markerEnd]; 62 | NSString *exprPattern = [NSString stringWithFormat:basePattern, self.exprStart, self.filterDelimiter, self.exprEnd]; 63 | self.regex = [NSString stringWithFormat:@"(?m)(?:%@|%@)", mrkrPattern, exprPattern]; 64 | } 65 | 66 | 67 | - (NSDictionary *)firstMarkerWithinRange:(NSRange)range 68 | { 69 | NSRange matchRange = [self.templateString rangeOfRegex:self.regex options:RKLNoOptions inRange:range capture:0 error:NULL]; 70 | NSMutableDictionary *markerInfo = nil; 71 | if (matchRange.length > 0) { 72 | markerInfo = [NSMutableDictionary dictionary]; 73 | [markerInfo setObject:[NSValue valueWithRange:matchRange] forKey:MARKER_RANGE_KEY]; 74 | 75 | // Found a match. Obtain marker string. 76 | NSString *matchString = [self.templateString substringWithRange:matchRange]; 77 | NSRange localRange = NSMakeRange(0, [matchString length]); 78 | //NSLog(@"mtch: \"%@\"", matchString); 79 | 80 | // Find type of match 81 | NSString *matchType = nil; 82 | NSRange mrkrSubRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:1 error:NULL]; 83 | BOOL isMarker = (mrkrSubRange.length > 0); // only matches if match has marker-delimiters 84 | int offset = 0; 85 | if (isMarker) { 86 | matchType = MARKER_TYPE_MARKER; 87 | } else { 88 | matchType = MARKER_TYPE_EXPRESSION; 89 | offset = 3; 90 | } 91 | [markerInfo setObject:matchType forKey:MARKER_TYPE_KEY]; 92 | 93 | // Split marker string into marker-name and arguments. 94 | NSRange markerRange = NSMakeRange(0, [matchString length]); 95 | markerRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:2 + offset error:NULL]; 96 | 97 | if (markerRange.length > 0) { 98 | NSString *markerString = [matchString substringWithRange:markerRange]; 99 | NSArray *markerComponents = [self argumentsFromString:markerString]; 100 | if (markerComponents && [markerComponents count] > 0) { 101 | [markerInfo setObject:[markerComponents objectAtIndex:0] forKey:MARKER_NAME_KEY]; 102 | int count = [markerComponents count]; 103 | if (count > 1) { 104 | [markerInfo setObject:[markerComponents subarrayWithRange:NSMakeRange(1, count - 1)] 105 | forKey:MARKER_ARGUMENTS_KEY]; 106 | } 107 | } 108 | 109 | // Check for filter. 110 | NSRange filterRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:3 + offset error:NULL]; 111 | if (filterRange.length > 0) { 112 | // Found a filter. Obtain filter string. 113 | NSString *filterString = [matchString substringWithRange:filterRange]; 114 | 115 | // Convert first : plus any immediately-following whitespace into a space. 116 | localRange = NSMakeRange(0, [filterString length]); 117 | NSString *space = @" "; 118 | NSRange filterArgDelimRange = [filterString rangeOfRegex:@":(?:\\s+)?" options:RKLNoOptions inRange:localRange 119 | capture:0 error:NULL]; 120 | if (filterArgDelimRange.length > 0) { 121 | // Replace found text with space. 122 | filterString = [NSString stringWithFormat:@"%@%@%@", 123 | [filterString substringWithRange:NSMakeRange(0, filterArgDelimRange.location)], 124 | space, 125 | [filterString substringWithRange:NSMakeRange(NSMaxRange(filterArgDelimRange), 126 | localRange.length - NSMaxRange(filterArgDelimRange))]]; 127 | } 128 | 129 | // Split into filter-name and arguments. 130 | NSArray *filterComponents = [self argumentsFromString:filterString]; 131 | if (filterComponents && [filterComponents count] > 0) { 132 | [markerInfo setObject:[filterComponents objectAtIndex:0] forKey:MARKER_FILTER_KEY]; 133 | int count = [filterComponents count]; 134 | if (count > 1) { 135 | [markerInfo setObject:[filterComponents subarrayWithRange:NSMakeRange(1, count - 1)] 136 | forKey:MARKER_FILTER_ARGUMENTS_KEY]; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | return markerInfo; 144 | } 145 | 146 | 147 | - (NSArray *)argumentsFromString:(NSString *)argString 148 | { 149 | // Extract arguments from argString, taking care not to break single- or double-quoted arguments, 150 | // including those containing \-escaped quotes. 151 | NSString *argsPattern = @"\"(.*?)(? 0) { 172 | [args addObject:[argString substringWithRange:matchedRange]]; 173 | } else { 174 | location = NSNotFound; 175 | } 176 | } 177 | 178 | return args; 179 | } 180 | 181 | 182 | @synthesize engine; 183 | @synthesize markerStart; 184 | @synthesize markerEnd; 185 | @synthesize exprStart; 186 | @synthesize exprEnd; 187 | @synthesize filterDelimiter; 188 | @synthesize templateString; 189 | @synthesize regex; 190 | 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /Example2/MGTemplateEngine README.txt: -------------------------------------------------------------------------------- 1 | MGTemplateEngine 2 | By Matt Legend Gemmell 3 | http://mattgemmell.com/ 4 | 5 | 6 | 7 | What is it? 8 | ----------- 9 | 10 | MGTemplateEngine is a native Cocoa system for generating text output based on templates and data. It's a close cousin of systems like Smarty, FreeMarker, Django's template language, and other such systems. 11 | 12 | The default syntax for markers (functions or language-constructs) is: 13 | 14 | {% for 1 to 5 %} foo {% /for %} 15 | 16 | and the default syntax for variables/expressions is: 17 | 18 | {{ foo.bar | uppercase }} 19 | 20 | The pipe-character indicates a filter is being applied; i.e. the value of "foo.bar" will then be fed to the "uppercase" filter before being displayed. You can apply filters to markers as well as variables. 21 | 22 | The marker, variable and filter delimiters are completely customizable, so you're not stuck with the defaults if you prefer different syntax. 23 | 24 | 25 | 26 | Features 27 | -------- 28 | 29 | MGTemplateEngine offers the following features: 30 | 31 | * Native Cocoa implementation. It doesn't use the Scripting Bridge or any external runtimes/frameworks, and as such the core engine itself has no requirements other than Mac OS X Leopard. 32 | 33 | * Very customizable. It's very easy to define new markers (like functions or language-constructs) and new filters (data-formatting capabilities). You can also freely change the syntax of the markers and expressions to suit your own tastes, or to mimic your favorite other templating system. 34 | 35 | * Delegate system to keep you informed. MGTemplateEngine can optionally inform a delegate object of significant events during processing of a template, including beginning/ending blocks, or encountering errors. 36 | 37 | * Global and template-specific variables. You can define a set of variables which exist for the lifetime of the engine, and also specify variables which only apply to a certain template. 38 | 39 | * Access variables using familiar Key-Value Coding (KVC) key-paths, with enhancements. For example, if you had an NSDictionary containing an NSArray for the key "foo", and that array contained 5 NSDictionaries, each of which had an NSString for the key "bar", you could access the value of the fifth dictionary's "bar" object using this syntax: 40 | 41 | foo.4.bar (remembering that array indices are zero-based!) 42 | 43 | 44 | 45 | Requirements 46 | ------------ 47 | 48 | MGTemplateEngine requires Mac OS X 10.5 (Leopard) or later. 49 | 50 | 51 | 52 | License 53 | ------- 54 | 55 | Please see the included Source Code License file for the license this code is released under. Summary: it's an attribution license. Credit me, and you can freely use, modify and redistribute in source or binary forms as you see fit. Closed-source/commercial use is absolutely fine. 56 | 57 | 58 | 59 | Extensibility 60 | ------------- 61 | 62 | MGTemplateEngine offers 3 main types of extensibility, as detailed below. You should also read the included documents specific to each API for more details. 63 | 64 | 1. Markers. You can create new markers, which provide new tags for use in templates. Markers can be standalone or can be complex blocks (like if-else-/if), can iterate/loop, enable or disable output, set new variables within their scope, and much more. 65 | 66 | 2. Filters. Filters modify data for display purposes, for example the built-in "date_format" filter which formats an NSDate as a string using a specified formatting definition. You can easily write new filters to format/transform your data in new ways. 67 | 68 | 3. Matchers. A matcher is a very important object which performs a conceptually simple task: it finds the next marker or expression in a template, and splits it into its components (such as the marker name or variable, extra arguments, and any filter specified). MGTemplateEngine ships with two matchers which you can choose between, or implement your own: 69 | 70 | * RegexKitLIte. This matcher uses RegexKitLite, which is a thin wrapper on libicucore.dylib, included with Mac OS X 10.5 and later. This matcher does not require any additional frameworks or libraries to be included in your application, though you must of course link against libicucore. The sample project does this. 71 | 72 | * RegexKit. This matcher uses RegexKit, which is a framework wrapping the PCRE regular-expressions library. This matcher requires RegexKit to be included in your application. 73 | 74 | You can freely write your own matcher if you don't want to link against libicucore, or don't want to include RegexKit in your application. For example, you could write a matcher which uses NSScanner, or one which uses OgreKit instead. 75 | 76 | 77 | 78 | Standard language features 79 | -------------------------- 80 | 81 | All language features are implemented as plug-in markers, so you can freely inspect and modify how they work. At time of writing, MGTemplateEngine supports the following constructs: 82 | 83 | * for x to y : A standard for-loop beginning at x and incrementing a loop-variable each time through the loop until y is reached. You can also append "reversed" to the command to go from the second value down to the first. 84 | 85 | * for p in q : Creates a new variable p which has each of the values in the collection q successively. You can also append "reversed" to the command to go from last to first (only works for ordered collections which supply a reverseObjectEnumerator, i.e. NSArray and its subclasses). 86 | 87 | Note: both "for" constructs provide several useful variables during the body of the loop, including currentLoop.currentIndex, currentLoop.startIndex and so on, including currentLoop.parentLoop if appropriate. 88 | 89 | * if x / if x == y - else - /if: A standard if-/if or if-else-/if conditional construct. The arguments to the if-statement are processed either as boolean truth-values or numerical comparisons, and can be: 90 | 91 | * x 92 | * x == y 93 | * x = y (same as ==) 94 | * x and y 95 | * x && y (same as and) 96 | * x or y 97 | * x || y (same as or) 98 | * x < y, x > y, x <= y, x >= y 99 | * x % y (returns false if x/y has no remainder, otherwise true) 100 | 101 | * now : creates an NSDate object for the current date and time. 102 | 103 | * literal : begins a block of literal text, within which no markers/expressions will be interpreted, and will instead be echoed directly to the output. Ends with a /literal marker. 104 | 105 | * comment : with no arguments, begins a block comment which ends upon encountering /comment. With 1 or more arguments, this is treated as a self-contained comment. 106 | 107 | * section : begins a named block of the template. When combined with the delegate methods for being informed of blocks beginning and ending, this is useful for being notified of the position and length of certain named blocks in a template, perhaps for extraction and further processing. 108 | 109 | * load : takes a space-separated list of classnames, and will attempt to load them as markers/filters as appropriate. The classes will only be instantiated if they exist, and if they implement either the MGTemplateMarker or MGTemplateFilter protocol as appropriate, and if they haven't already been loaded. 110 | 111 | * cycle : takes a space-separate list of arguments (can be quoted if they contain whitespace), which will be alternated between each time the cycle marker is visited. This is useful within a loop for outputting the next in a set of values each time, for example for alternating row colors or such. 112 | 113 | * set : takes two arguments, the first being a variable-name and the second being a value to set that variable to. Note: remember that variables are scoped within the current block by default, so if you want a variable to survive outwith the current block you should set it to an initial value in the template-variables or global variables before beginning to process the template. 114 | 115 | 116 | 117 | Standard data filters 118 | --------------------- 119 | 120 | There are a few standard filters currently included with MGTemplateEngine, and you can easily add your own. The standard ones are: 121 | 122 | * uppercase, lowercase, capitalized : Returns a string representation of the value, case-transformed as appropriate. 123 | 124 | * date_format : takes a string with formatting characters to format an NSDate object (for example, obtained via the "now" marker). The formatting system used is NSDateFormatter in 10.4+ mode, i.e. as detailed here: 125 | http://unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns 126 | 127 | * color_format : takes a string representing the format to convert the given NSColor object to. The only currently support value is "hex", which provides a web-suitable 6-digit RRGGBB hexadecimal representation of the color (if it could be converted to RGB, and black otherwise). 128 | 129 | 130 | 131 | Feature Requests and Bug Reports 132 | -------------------------------- 133 | 134 | Please submit any feature requests or bug reports to me via email; you can find my address on the About page of my site, here: http://mattgemmell.com/about 135 | 136 | I hope you enjoy using MGTemplateEngine! 137 | 138 | Cheers, 139 | -Matt Legend Gemmell 140 | 141 | http://mattgemmell.com/ 142 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgStoreParameters.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgStoreParameters.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/19/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgStoreParameters.h" 10 | 11 | NSString * const kFsprgOrderProcessDetail = @"detail"; 12 | NSString * const kFsprgOrderProcessInstant = @"instant"; 13 | NSString * const kFsprgOrderProcessCheckout = @"checkout"; 14 | 15 | NSString * const kFsprgModeActive = @"active"; 16 | NSString * const kFsprgModeActiveTest = @"active.test"; 17 | NSString * const kFsprgModeTest = @"test"; 18 | 19 | static NSString * const kLanguage = @"language"; 20 | static NSString * const kOrderProcessType = @"orderProcessType"; 21 | static NSString * const kStoreId = @"storeId"; 22 | static NSString * const kProductId = @"productId"; 23 | static NSString * const kMode = @"mode"; 24 | static NSString * const kCampaign = @"campaign"; 25 | static NSString * const kOption = @"option"; 26 | static NSString * const kReferrer = @"referrer"; 27 | static NSString * const kSource = @"source"; 28 | static NSString * const kCoupon = @"coupon"; 29 | static NSString * const kContactFname = @"contact_fname"; 30 | static NSString * const kContactLname = @"contact_lname"; 31 | static NSString * const kContactEmail = @"contact_email"; 32 | static NSString * const kContactCompany = @"contact_company"; 33 | static NSString * const kContactPhone = @"contact_phone"; 34 | 35 | static NSMutableDictionary *keyPathsForValuesAffecting; 36 | 37 | @implementation FsprgStoreParameters 38 | 39 | + (void)initialize 40 | { 41 | keyPathsForValuesAffecting = [[NSMutableDictionary dictionaryWithCapacity:1] retain]; 42 | 43 | NSSet *toURLSet = [NSSet setWithObjects:NSStringFromSelector(@selector(language)), 44 | NSStringFromSelector(@selector(orderProcessType)), 45 | NSStringFromSelector(@selector(storeId)), 46 | NSStringFromSelector(@selector(productId)), 47 | NSStringFromSelector(@selector(mode)), 48 | NSStringFromSelector(@selector(campaign)), 49 | NSStringFromSelector(@selector(option)), 50 | NSStringFromSelector(@selector(referrer)), 51 | NSStringFromSelector(@selector(source)), 52 | NSStringFromSelector(@selector(coupon)), 53 | NSStringFromSelector(@selector(contactFname)), 54 | NSStringFromSelector(@selector(contactLname)), 55 | NSStringFromSelector(@selector(contactEmail)), 56 | NSStringFromSelector(@selector(contactCompany)), 57 | NSStringFromSelector(@selector(contactPhone)), 58 | nil]; 59 | [keyPathsForValuesAffecting setObject:toURLSet forKey:NSStringFromSelector(@selector(toURL))]; 60 | } 61 | 62 | + (FsprgStoreParameters *)parameters 63 | { 64 | NSMutableDictionary *raw = [NSMutableDictionary dictionaryWithCapacity:15]; 65 | return [[[FsprgStoreParameters alloc] initWithRaw:raw] autorelease]; 66 | } 67 | 68 | + (FsprgStoreParameters *)parametersWithRaw:(NSMutableDictionary *)aRaw 69 | { 70 | return [[[FsprgStoreParameters alloc] initWithRaw:aRaw] autorelease]; 71 | } 72 | 73 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key 74 | { 75 | NSSet *keyPaths = [keyPathsForValuesAffecting valueForKey:key]; 76 | if(keyPaths == nil) { 77 | return [NSSet set]; 78 | } else { 79 | return keyPaths; 80 | } 81 | } 82 | 83 | - (id)initWithRaw:(NSMutableDictionary *)aRaw 84 | { 85 | self = [super init]; 86 | if (self != nil) { 87 | [self setRaw:aRaw]; 88 | } 89 | return self; 90 | } 91 | 92 | - (NSMutableDictionary *)raw 93 | { 94 | return [[raw retain] autorelease]; 95 | } 96 | 97 | - (void)setRaw:(NSMutableDictionary *)aRaw 98 | { 99 | if (raw != aRaw) { 100 | [raw release]; 101 | raw = [aRaw retain]; 102 | } 103 | } 104 | 105 | - (NSURLRequest *)toURLRequest 106 | { 107 | NSURL *toURL = [self toURL]; 108 | if (toURL) { 109 | return [NSMutableURLRequest requestWithURL:toURL]; 110 | } else { 111 | return nil; 112 | } 113 | } 114 | 115 | - (NSURL *)toURL 116 | { 117 | NSString *storeIdEncoded = [[self storeId] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 118 | if(storeIdEncoded == nil) { 119 | storeIdEncoded = @""; 120 | } 121 | NSString *productIdEncoded = [[self productId] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 122 | if(productIdEncoded == nil) { 123 | productIdEncoded = @""; 124 | } 125 | 126 | NSString *urlAsStr; 127 | if([kFsprgOrderProcessDetail isEqualTo:[self orderProcessType]]) { 128 | NSString *protocol = @"http"; 129 | if([self hasContactDefaults]) { 130 | protocol = @"https"; 131 | } 132 | urlAsStr = [NSString stringWithFormat:@"%@://sites.fastspring.com/%@/product/%@", protocol, storeIdEncoded, productIdEncoded]; 133 | } else if([kFsprgOrderProcessInstant isEqualTo:[self orderProcessType]]) { 134 | urlAsStr = [NSString stringWithFormat:@"https://sites.fastspring.com/%@/instant/%@", storeIdEncoded, productIdEncoded]; 135 | } else if ([kFsprgOrderProcessCheckout isEqualTo:[self orderProcessType]]) { 136 | urlAsStr = [NSString stringWithFormat:@"https://sites.fastspring.com/%@/checkout/%@", storeIdEncoded, productIdEncoded]; 137 | } else { 138 | NSAssert1(FALSE, @"OrderProcessType '%@' unknown.", [self orderProcessType]); 139 | return nil; 140 | } 141 | 142 | NSMutableArray *keys = [NSMutableArray arrayWithArray:[[self raw] allKeys]]; 143 | [keys removeObject:kOrderProcessType]; 144 | [keys removeObject:kStoreId]; 145 | [keys removeObject:kProductId]; 146 | [keys sortUsingSelector:@selector(compare:)]; 147 | 148 | NSString *queryStr = @""; 149 | NSUInteger i, count = [keys count]; 150 | for (i = 0; i < count; i++) { 151 | NSString *key = [keys objectAtIndex:i]; 152 | NSString *value = [[self raw] valueForKey:key]; 153 | if(value != nil) { 154 | queryStr = [queryStr stringByAppendingFormat:@"&%@=%@", 155 | key, 156 | [value stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 157 | } 158 | } 159 | 160 | if([queryStr length] > 0) { 161 | urlAsStr = [NSString stringWithFormat:@"%@?%@", urlAsStr, [queryStr substringFromIndex:1]]; 162 | } 163 | 164 | return [NSURL URLWithString:urlAsStr]; 165 | } 166 | 167 | - (void)setObject:(NSString *)anObject forKey:(NSString *)aKey 168 | { 169 | if(anObject == nil) { 170 | [[self raw] removeObjectForKey:aKey]; 171 | } else { 172 | [[self raw] setObject:anObject forKey:aKey]; 173 | } 174 | } 175 | 176 | - (NSString *)language 177 | { 178 | return [[self raw] objectForKey:kLanguage]; 179 | } 180 | 181 | - (void)setLanguage:(NSString *)aLanguage 182 | { 183 | [self setObject:aLanguage forKey:kLanguage]; 184 | } 185 | 186 | - (NSString *)orderProcessType 187 | { 188 | return [[self raw] objectForKey:kOrderProcessType]; 189 | } 190 | - (void)setOrderProcessType:(NSString *)anOrderProcessType 191 | { 192 | [self setObject:anOrderProcessType forKey:kOrderProcessType]; 193 | } 194 | 195 | - (void)setStoreId:(NSString *)aStoreId withProductId:(NSString *)aProductId 196 | { 197 | [self setStoreId:aStoreId]; 198 | [self setProductId:aProductId]; 199 | } 200 | 201 | - (NSString *)storeId 202 | { 203 | return [[self raw] objectForKey:kStoreId]; 204 | } 205 | - (void)setStoreId:(NSString *)aStoreId 206 | { 207 | [self setObject:aStoreId forKey:kStoreId]; 208 | } 209 | 210 | - (NSString *)productId 211 | { 212 | return [[self raw] objectForKey:kProductId]; 213 | } 214 | - (void)setProductId:(NSString *)aProductId 215 | { 216 | [self setObject:aProductId forKey:kProductId]; 217 | } 218 | 219 | - (NSString *)mode 220 | { 221 | return [[self raw] objectForKey:kMode]; 222 | } 223 | - (void)setMode:(NSString *)aMode 224 | { 225 | [self setObject:aMode forKey:kMode]; 226 | } 227 | 228 | - (NSString *)campaign 229 | { 230 | return [[self raw] objectForKey:kCampaign]; 231 | } 232 | - (void)setCampaign:(NSString *)aCampaign 233 | { 234 | [self setObject:aCampaign forKey:kCampaign]; 235 | } 236 | 237 | - (NSString *)option 238 | { 239 | return [[self raw] objectForKey:kOption]; 240 | } 241 | - (void)setOption:(NSString *)anOption 242 | { 243 | [self setObject:anOption forKey:kOption]; 244 | } 245 | 246 | - (NSString *)referrer 247 | { 248 | return [[self raw] objectForKey:kReferrer]; 249 | } 250 | - (void)setReferrer:(NSString *)aReferrer 251 | { 252 | [self setObject:aReferrer forKey:kReferrer]; 253 | } 254 | 255 | - (NSString *)source 256 | { 257 | return [[self raw] objectForKey:kSource]; 258 | } 259 | - (void)setSource:(NSString *)aSource 260 | { 261 | [self setObject:aSource forKey:kSource]; 262 | } 263 | 264 | - (NSString *)coupon 265 | { 266 | return [[self raw] objectForKey:kCoupon]; 267 | } 268 | - (void)setCoupon:(NSString *)aCoupon 269 | { 270 | [self setObject:aCoupon forKey:kCoupon]; 271 | } 272 | 273 | - (BOOL)hasContactDefaults 274 | { 275 | NSArray *allKeys = [[self raw] allKeys]; 276 | 277 | return [allKeys containsObject:kContactFname] || 278 | [allKeys containsObject:kContactLname] || 279 | [allKeys containsObject:kContactEmail] || 280 | [allKeys containsObject:kContactCompany] || 281 | [allKeys containsObject:kContactPhone]; 282 | } 283 | 284 | - (NSString *)contactFname 285 | { 286 | return [[self raw] objectForKey:kContactFname]; 287 | } 288 | - (void)setContactFname:(NSString *)aContactFname 289 | { 290 | [self setObject:aContactFname forKey:kContactFname]; 291 | } 292 | 293 | - (NSString *)contactLname 294 | { 295 | return [[self raw] objectForKey:kContactLname]; 296 | } 297 | - (void)setContactLname:(NSString *)aContactLname 298 | { 299 | [self setObject:aContactLname forKey:kContactLname]; 300 | } 301 | 302 | - (NSString *)contactEmail 303 | { 304 | return [[self raw] objectForKey:kContactEmail]; 305 | } 306 | - (void)setContactEmail:(NSString *)aContactEmail 307 | { 308 | [self setObject:aContactEmail forKey:kContactEmail]; 309 | } 310 | 311 | - (NSString *)contactCompany 312 | { 313 | return [[self raw] objectForKey:kContactCompany]; 314 | } 315 | - (void)setContactCompany:(NSString *)aContactCompany 316 | { 317 | [self setObject:aContactCompany forKey:kContactCompany]; 318 | } 319 | 320 | - (NSString *)contactPhone 321 | { 322 | return [[self raw] objectForKey:kContactPhone]; 323 | } 324 | - (void)setContactPhone:(NSString *)aContactPhone 325 | { 326 | [self setObject:aContactPhone forKey:kContactPhone]; 327 | } 328 | 329 | - (void)dealloc 330 | { 331 | [self setRaw:nil]; 332 | 333 | [super dealloc]; 334 | } 335 | 336 | @end -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Introduction # 2 | 3 | [FastSpring](http://www.fastspring.com) offers an innovative e-commerce engine designed to overcome ease of use, customer service, and cost issues that have plagued software e-commerce companies. 4 | 5 | FastSpring's embedded store consists of a controller with some integration points and WebKit's web view. It's thin and very flexible and lets you integrate FastSpring the way that fits best for your application. 6 | 7 | To get an idea of how it works, the SDK provides two examples and a test application. All source code is released under the MIT license. It is open to contributions and its use is unrestricted. See RELEASE_NOTES.html for the latest changes. 8 | 9 | 10 | 11 | # FsprgEmbeddedStore # 12 | 13 | FsprgEmbeddedStore consists mainly of the `FsprgEmbeddedStoreController` and its delegate protocol, `FsprgEmbeddedStoreDelegate`. 14 | 15 | The `FsprgEmbeddedStoreController` controls the connected `WebView` (WebKit). It provides functionality to load the store, to monitor the page loading progress and to test if the current connection is secure (https). 16 | 17 | @interface FsprgEmbeddedStoreController : NSObject 18 | - (WebView *)webView; 19 | - (void)setWebView:(WebView *)aWebView; 20 | - (id )delegate; 21 | - (void)setDelegate:(id )aDelegate; 22 | 23 | - (void)loadWithParameters:(FsprgStoreParameters *)parameters; 24 | - (void)loadWithContentsOfFile:(NSString *)aPath; 25 | - (BOOL)isLoading; 26 | - (double)estimatedLoadingProgress; 27 | - (BOOL)isSecure; 28 | - (NSString *)storeHost; 29 | @end 30 | 31 | In addition, it has some integration points defined by the `FsprgEmbeddedStoreDelegate` protocol. It gives notification of the initial load of the store, of subsequent page loads and of order completion. There's also the possibility to define a view to present the order confirmation to the user. 32 | 33 | typedef enum { 34 | FsprgPageFS, 35 | FsprgPagePayPal, 36 | FsprgPageUnknown 37 | } FsprgPageType; 38 | 39 | @protocol FsprgEmbeddedStoreDelegate 40 | - (void)didLoadStore:(NSURL *)url; 41 | - (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType; 42 | - (void)didReceiveOrder:(FsprgOrder *)order; 43 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order; 44 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame; 45 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame; 46 | @end 47 | 48 | 49 | ## How-to embed it ## 50 | 51 | 1. [Clone or fork](http://help.github.com/fork-a-repo) our repository. 52 | 2. Open your project in Xcode. 53 | 3. Perform File/Add Files to "Your App"... and choose the directory `FsprgEmbeddedStoreMac/FsprgEmbeddedStore`. We suggest to uncheck the option `Copy items into destination group's folder`. 54 | 4. Delete reference to `FsprgEmbeddedStoreMac/FsprgEmbeddedStore/Tests` (in your project) if you don't need FsprgEmbeddedStore unit tests. 55 | 5. Add WebKit.framework to your Targets. 56 | 57 | 58 | ## How-to use it ## 59 | 60 | 1. Read our [Integration Guide](https://support.fastspring.com/entries/234307-embedded-web-store-sdk) to learn how to enable your store for FsprgEmbeddedStore requests. 61 | 2. Create an `AppController` and use `FsprgEmbeddedStoreController`. Example1.app and Example2.app are showing how to implement an `AppController` in detail. 62 | 3. Open MainMenu.nib. 63 | 4. Create an instance of `AppController` inside Interface Builder. 64 | 5. Drag WebKit control onto screen and connect it to the storeView Outlet of `AppController`. 65 | 66 | ### Example: AppController.h ### 67 | 68 | @interface AppController : NSObject { 69 | IBOutlet WebView* storeView; 70 | FsprgEmbeddedStoreController *storeController; 71 | } 72 | 73 | - (FsprgEmbeddedStoreController *)storeController; 74 | - (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController; 75 | 76 | - (IBAction)load:(id)sender; 77 | 78 | @end 79 | 80 | 81 | ## How-to provide a link to the web-store ## 82 | 83 | Sometimes users prefer to use a web-store instead of an embedded-store. Use `FsprgStoreParameters` to build the web-store URL and open it inside the default browser by using `NSWorkspace`. 84 | 85 | - (IBAction)openWebStoreInBrowser:(id)sender 86 | { 87 | FsprgStoreParameters *parameters = [FsprgStoreParameters parameters]; 88 | [parameters setOrderProcessType:kFsprgOrderProcessDetail]; 89 | [parameters setStoreId:@"your_store" withProductId:@"your_product"]; 90 | [parameters setMode:kFsprgModeTest]; 91 | [[NSWorkspace sharedWorkspace] openURL:[parameters toURL]]; 92 | } 93 | 94 | 95 | ## FsprgOrder API ## 96 | 97 | The FsprgOrder object represents the order confirmation returned via `FsprgEmbeddedStoreDelegate` protocol. To spare you plunging through the headers the following sections contain a real-life example and a compressed API documentation of FsprgOrder and its referred classes. 98 | 99 | ### Example ### 100 | 101 | Here's a real-life example to show the most common case of grabbing the serial number from the fulfilled license. _Thanks to Greg Scown from [SmileOnMyMac](http://www.smileonmymac.com) for sharing._ 102 | 103 | - (void)didReceiveOrder:(FsprgOrder *)order 104 | { 105 | NSEnumerator *e = [[order orderItems] objectEnumerator]; 106 | FsprgOrderItem *item = nil; 107 | while (item = [e nextObject]) { 108 | if ([[item productName] hasPrefix:@"MyItemNamePrefix"]) { 109 | NSString *userName = [[item license] licenseName]; 110 | NSString *serialNumber = [[item license] firstLicenseCode]; 111 | if ([[[item productName] lowercaseString] rangeOfString:@"upgrade"].location != NSNotFound) { 112 | NSLog(@"Upgrade purchase:\nName: %@\nSerial #: %@", userName, serialNumber); 113 | } else { 114 | NSLog(@"Full purchase:\nName: %@\nSerial #: %@", userName, serialNumber); 115 | } 116 | } 117 | } 118 | } 119 | 120 | ### FsprgOrder.h ### 121 | 122 | - (BOOL)orderIsTest; 123 | - (NSString *)orderReference; 124 | - (NSString *)orderLanguage; 125 | - (NSString *)orderCurrency; 126 | - (NSNumber *)orderTotal; 127 | - (NSNumber *)orderTotalUSD; 128 | - (NSString *)customerFirstName; 129 | - (NSString *)customerLastName; 130 | - (NSString *)customerCompany; 131 | - (NSString *)customerEmail; 132 | - (FsprgOrderItem *)firstOrderItem; // Shortcut for [[self orderItems] objectAtIndex:0]. 133 | - (NSArray *)orderItems; 134 | 135 | ### FsprgOrderItem.h ### 136 | 137 | - (NSString *)productName; 138 | - (NSString *)productDisplay; 139 | - (NSNumber *)quantity; 140 | - (NSNumber *)itemTotal; 141 | - (NSNumber *)itemTotalUSD; 142 | - (NSString *)subscriptionReference; // See https://support.fastspring.com/entries/236487-api-subscriptions 143 | - (NSString *)subscriptionCustomerURL; // This URL can be presented to the customer to manage their subscription. 144 | - (FsprgFulfillment *)fulfillment; 145 | - (FsprgLicense *)license; // Shortcut for [[self fulfillment] valueForKey:@"license"] 146 | - (FsprgFileDownload *)download; // Shortcut for [[self fulfillment] valueForKey:@"download"] 147 | 148 | ### FsprgFulfillment.h ### 149 | 150 | /*! 151 | * @param aKey type of fulfillment (e.g. license, download) 152 | * @result Specific fulfillment information (FsprgLicense, FsprgFileDownload). 153 | */ 154 | - (id)valueForKey:(NSString *)aKey; 155 | 156 | ### FsprgLicense.h ### 157 | 158 | - (NSString *)licenseName; 159 | - (NSString *)licenseEmail; 160 | - (NSString *)licenseCompany; 161 | - (NSString *)firstLicenseCode; 162 | - (NSArray *)licenseCodes; 163 | - (NSDictionary *)licensePropertyList; 164 | - (NSURL *)licenseURL; 165 | 166 | ### FsprgFileDownload.h ### 167 | 168 | - (NSURL *)fileURL; 169 | 170 | 171 | 172 | # Example1.app # 173 | 174 | Example1 app defaults contact fields by accessing MacOS' AddressBook. The order confirmation is a View XIB built inside Interface Builder. 175 | 176 | ![Example1.app Screenshot](https://github.com/FastSpring/FsprgEmbeddedStoreMac/raw/master/README/example1_screenshot.png "Example1.app Screenshot") 177 | 178 | 179 | ## How-to implement the AppController ## 180 | 181 | * Set self as delegate on `init` 182 | * Set webView to `FsprgEmbeddedStoreController` on `awakeFromNib` 183 | * Delegate `load:` to `loadWithParameters:` of `FsprgEmbeddedStoreController` 184 | * Implement `viewWithFrame:forOrder:` by using a `NSViewController` (here `OrderViewController`) that uses the View XIB defined inside Interface Builder 185 | 186 | ### Extract from AppController.h ### 187 | 188 | @implementation AppController 189 | 190 | - (id) init 191 | { 192 | self = [super init]; 193 | if (self != nil) { 194 | [self setStoreController:[[[FsprgEmbeddedStoreController alloc] init] autorelease]]; 195 | [[self storeController] setDelegate:self]; 196 | } 197 | return self; 198 | } 199 | 200 | - (void)awakeFromNib 201 | { 202 | [[self storeController] setWebView:storeView]; 203 | [self load:nil]; 204 | } 205 | 206 | - (IBAction)load:(id)sender 207 | { 208 | FsprgStoreParameters *parameters = [FsprgStoreParameters parameters]; 209 | ... 210 | [[self storeController] loadWithParameters:parameters]; 211 | } 212 | 213 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order 214 | { 215 | OrderViewController *orderViewController = [[OrderViewController alloc] initWithNibName:@"OrderView" bundle:nil]; 216 | [orderViewController setRepresentedObject:order]; 217 | 218 | [[orderViewController view] setFrame:frame]; 219 | return [orderViewController view]; 220 | } 221 | 222 | @end 223 | 224 | 225 | ## How-to create the View XIB ## 226 | 227 | * Create class `OrderViewController` by extending `NSViewController` 228 | * Create View XIB 229 | * Set File’s Owner class to `OrderViewController` 230 | * Assign File’s Owner view Outlet to the main "Custom View" 231 | * Bind controls (e.g. label) to File’s Owner representedObject (= `FSOrder`) to present order confirmation data to the user 232 | 233 | 234 | 235 | # Example2.app # 236 | 237 | Example2 presents the order confirmation by using HTML, CSS and JavaScript. It uses Matt Gemmell's [MGTemplateEngine](http://mattgemmell.com/2008/05/20/mgtemplateengine-templates-with-cocoa) to render the HTML. 238 | 239 | ![Example2.app Screenshot](https://github.com/FastSpring/FsprgEmbeddedStoreMac/raw/master/README/example2_screenshot.png "Example2.app Screenshot") 240 | 241 | 242 | ## How-To create order HTML view ## 243 | 244 | The AppController looks like the one in Example1. The only difference is the `viewWithFrame:forOrder:` implementation. It uses `WebFrame`'s `loadHTMLString:baseURL:` method to load the HTML and present it to the user. 245 | 246 | ### Extract from AppController.h ### 247 | 248 | @implementation AppController 249 | 250 | - (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order 251 | { 252 | MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; 253 | [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; 254 | 255 | NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"OrderView" ofType:@"html"]; 256 | NSDictionary *variables = [NSDictionary dictionaryWithObject:order forKey:@"order"]; 257 | NSString *htmlString = [engine processTemplateInFileAtPath:templatePath withVariables:variables]; 258 | 259 | NSString *templateDirectory = [templatePath substringToIndex:[templatePath length]-[[templatePath lastPathComponent] length]]; 260 | NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@", templateDirectory]]; 261 | 262 | WebFrame *webFrame = [[[WebView alloc] initWithFrame:frame] mainFrame]; 263 | [webFrame loadHTMLString:htmlString baseURL:baseURL]; 264 | 265 | return [webFrame frameView]; 266 | } 267 | 268 | @end 269 | 270 | As we set `FsprgOrder` to a variable we can now conveniently access the order information inside the template. The baseURL points to the Resource directory. Thus, we can access CSS files to style the view and JavaScript to add some behavior and nice effects. 271 | 272 | ### OrderView.html ### 273 | 274 | 275 | 276 | 277 | Your Order 278 | 279 | 280 | 286 | 287 | 288 |
Thanks for your order {{ order.customerFirstName }}!
289 |
Ordered items
290 | {% for orderItem in order.orderItems %} 291 |
292 |
293 | {{ orderItem.productName }} 294 | {% if orderItem.quantity > 1 %} ({{ orderItem.quantity }}) {% /if %} 295 |
296 |
Your license key: {{ orderItem.license.firstLicenseCode }}
297 |
298 | {% /for %} 299 | 300 | 301 | 302 | 303 | 304 | # Test.app # 305 | 306 | The Test application lets you explore FastSpring's parameters and shows you the native order confirmation result (XML plist format). 307 | 308 | ![Test.app Settings Screenshot](https://github.com/FastSpring/FsprgEmbeddedStoreMac/raw/master/README/testapp_settings_screenshot.png "Test.app Settings Screenshot")  309 | ![Test.app Results Screenshot](https://github.com/FastSpring/FsprgEmbeddedStoreMac/raw/master/README/testapp_results_screenshot.png "Test.app Results Screenshot") 310 | 311 | You can also store that confirmation result as a plist file and load it by using the `FsprgEmbeddedStoreController`'s `loadWithContentsOfFile:` method. It simplifies the development and testing of the order confirmation view. 312 | -------------------------------------------------------------------------------- /FsprgEmbeddedStore/FsprgEmbeddedStoreController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FsprgEmbeddedStoreController.m 3 | // FsprgEmbeddedStore 4 | // 5 | // Created by Lars Steiger on 2/12/10. 6 | // Copyright 2010 FastSpring. All rights reserved. 7 | // 8 | 9 | #import "FsprgEmbeddedStoreController.h" 10 | #import "FsprgOrderView.h" 11 | #import "FsprgOrderDocumentRepresentation.h" 12 | 13 | 14 | @interface FsprgEmbeddedStoreController (Private) 15 | 16 | - (void)setIsLoading:(BOOL)aFlag; 17 | - (void)setEstimatedLoadingProgress:(double)aProgress; 18 | - (void)setIsSecure:(BOOL)aFlag; 19 | - (void)setStoreHost:(NSString *)aHost; 20 | - (void)resizeContentDivE; 21 | - (void)webViewFrameChanged:(NSNotification *)aNotification; 22 | - (NSMutableDictionary *)hostCertificates; 23 | - (void)hostCertificates:(NSMutableDictionary *)aHostCertificates; 24 | - (NSMapTable *)connectionsToRequests; 25 | - (void)setConnectionsToRequests:(NSMapTable *)aConnectionsToRequests; 26 | 27 | @end 28 | 29 | @implementation FsprgEmbeddedStoreController 30 | 31 | 32 | + (void)initialize 33 | { 34 | [WebView registerViewClass:[FsprgOrderView class] 35 | representationClass:[FsprgOrderDocumentRepresentation class] 36 | forMIMEType:@"application/x-fsprgorder+xml"]; 37 | } 38 | 39 | - (id) init 40 | { 41 | self = [super init]; 42 | if (self != nil) { 43 | [self setWebView:nil]; 44 | [self setDelegate:nil]; 45 | [self setStoreHost:nil]; 46 | [self setHostCertificates:[NSMutableDictionary dictionary]]; 47 | [self setConnectionsToRequests:[NSMapTable mapTableWithStrongToStrongObjects]]; 48 | } 49 | return self; 50 | } 51 | 52 | - (WebView *)webView 53 | { 54 | return [[webView retain] autorelease]; 55 | } 56 | 57 | - (void)setWebView:(WebView *)aWebView 58 | { 59 | if (webView != aWebView) { 60 | [webView setPostsFrameChangedNotifications:FALSE]; 61 | [webView setFrameLoadDelegate:nil]; 62 | [webView setUIDelegate:nil]; 63 | [webView setResourceLoadDelegate:nil]; 64 | [webView setApplicationNameForUserAgent:nil]; 65 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 66 | 67 | [webView release]; 68 | webView = [aWebView retain]; 69 | 70 | if (webView) { 71 | [webView setPostsFrameChangedNotifications:TRUE]; 72 | [webView setFrameLoadDelegate:self]; 73 | [webView setUIDelegate:self]; 74 | [webView setResourceLoadDelegate:self]; 75 | [webView setApplicationNameForUserAgent:@"FSEmbeddedStore/2.0"]; 76 | [[NSNotificationCenter defaultCenter] addObserver:self 77 | selector:@selector(webViewFrameChanged:) 78 | name:NSViewFrameDidChangeNotification 79 | object:webView]; 80 | [[NSNotificationCenter defaultCenter] addObserver:self 81 | selector:@selector(estimatedLoadingProgressChanged:) 82 | name:WebViewProgressStartedNotification 83 | object:webView]; 84 | [[NSNotificationCenter defaultCenter] addObserver:self 85 | selector:@selector(estimatedLoadingProgressChanged:) 86 | name:WebViewProgressEstimateChangedNotification 87 | object:webView]; 88 | } 89 | } 90 | } 91 | 92 | - (id )delegate 93 | { 94 | if(delegate == nil) { 95 | NSLog(@"No delegate has been assigned to FsprgEmbeddedStoreController!"); 96 | } 97 | return delegate; 98 | } 99 | 100 | - (void)setDelegate:(id )aDelegate 101 | { 102 | // Keep a weak reference to delegates to prevent circular references 103 | // See https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-1044135 104 | delegate = aDelegate; 105 | } 106 | 107 | - (void)loadWithParameters:(FsprgStoreParameters *)parameters 108 | { 109 | NSURLRequest *urlRequest = [parameters toURLRequest]; 110 | if (urlRequest == nil) { 111 | return; 112 | } 113 | 114 | NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[urlRequest URL]]; 115 | NSUInteger i, count = [cookies count]; 116 | for (i = 0; i < count; i++) { 117 | NSHTTPCookie *cookie = [cookies objectAtIndex:i]; 118 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; 119 | } 120 | 121 | [self setStoreHost:nil]; 122 | [[webView mainFrame] loadRequest:urlRequest]; 123 | } 124 | 125 | - (void)loadWithContentsOfFile:(NSString *)aPath 126 | { 127 | [self setStoreHost:nil]; 128 | 129 | NSData *data = [NSData dataWithContentsOfFile:aPath]; 130 | if(data == nil) { 131 | NSLog(@"File %@ not found.", aPath); 132 | } else { 133 | [[webView mainFrame] loadData:data MIMEType:@"application/x-fsprgorder+xml" textEncodingName:@"UTF-8" baseURL:nil]; 134 | } 135 | } 136 | 137 | - (BOOL)isLoading 138 | { 139 | return [self estimatedLoadingProgress] < 100; 140 | } 141 | - (void)setIsLoading:(BOOL)aFlag 142 | { 143 | // just triggering change observer 144 | } 145 | - (double)estimatedLoadingProgress 146 | { 147 | return [webView estimatedProgress] * 100; 148 | } 149 | - (void)setEstimatedLoadingProgress:(double)aProgress 150 | { 151 | // just triggering change observer 152 | } 153 | - (void)estimatedLoadingProgressChanged:(NSNotification *)aNotification 154 | { 155 | [self setEstimatedLoadingProgress:-1]; 156 | [self setIsLoading:TRUE]; 157 | } 158 | - (BOOL)isSecure 159 | { 160 | WebDataSource *mainFrameDs = [[[self webView] mainFrame] dataSource]; 161 | return [@"https" isEqualTo:[[[mainFrameDs request] URL] scheme]]; 162 | } 163 | - (void)setIsSecure:(BOOL)aFlag 164 | { 165 | // just triggering change observer 166 | } 167 | 168 | - (NSArray *)securityCertificates 169 | { 170 | if ([self isSecure] == NO) { 171 | return nil; 172 | } 173 | 174 | NSString *mainFrameURL = [[self webView] mainFrameURL]; 175 | NSString *host = [[NSURL URLWithString:mainFrameURL] host]; 176 | return [[self hostCertificates] objectForKey:host]; 177 | } 178 | 179 | - (NSString *)storeHost 180 | { 181 | return [[storeHost retain] autorelease]; 182 | } 183 | 184 | - (void)setStoreHost:(NSString *)aHost 185 | { 186 | if (storeHost != aHost) { 187 | [storeHost release]; 188 | storeHost = [aHost retain]; 189 | } 190 | } 191 | 192 | - (void)resizeContentDivE 193 | { 194 | if ([[self delegate] respondsToSelector:@selector(shouldStoreControllerFixContentDivHeight:)]) { 195 | if ([[self delegate] shouldStoreControllerFixContentDivHeight:self] == NO) { 196 | return; 197 | } 198 | } 199 | 200 | DOMElement *resizableContentE = [[[[self webView] mainFrame] DOMDocument] getElementById:@"FsprgResizableContent"]; 201 | if(resizableContentE == nil) { 202 | return; 203 | } 204 | 205 | float windowHeight = [[self webView] frame].size.height; 206 | id result = [[[self webView] windowScriptObject] evaluateWebScript:@"document.getElementsByClassName('store-page-navigation')[0].clientHeight"]; 207 | if (result == [WebUndefined undefined]) { 208 | return; 209 | } 210 | float pageNavigationHeight = [(NSString *)result floatValue]; 211 | 212 | DOMCSSStyleDeclaration *cssStyle = [[self webView] computedStyleForElement:resizableContentE pseudoElement:nil]; 213 | float paddingTop = [[[cssStyle paddingBottom] substringToIndex:[[cssStyle paddingTop] length]-2] floatValue]; 214 | float paddingBottom = [[[cssStyle paddingBottom] substringToIndex:[[cssStyle paddingBottom] length]-2] floatValue]; 215 | 216 | float newHeight = windowHeight - paddingTop - paddingBottom - pageNavigationHeight; 217 | [[resizableContentE style] setHeight:[NSString stringWithFormat:@"%fpx", newHeight]]; 218 | } 219 | 220 | - (void)webViewFrameChanged:(NSNotification *)aNotification 221 | { 222 | [self resizeContentDivE]; 223 | } 224 | 225 | - (NSMutableDictionary *)hostCertificates 226 | { 227 | return [[hostCertificates retain] autorelease]; 228 | } 229 | - (void)setHostCertificates:(NSMutableDictionary *)anHostCertificates 230 | { 231 | if (hostCertificates != anHostCertificates) { 232 | [anHostCertificates retain]; 233 | [hostCertificates release]; 234 | hostCertificates = anHostCertificates; 235 | } 236 | } 237 | 238 | - (NSMapTable *)connectionsToRequests 239 | { 240 | return [[connectionsToRequests retain] autorelease]; 241 | } 242 | - (void)setConnectionsToRequests:(NSMapTable *)aConnectionsToRequests 243 | { 244 | if (connectionsToRequests != aConnectionsToRequests) { 245 | [aConnectionsToRequests retain]; 246 | [connectionsToRequests release]; 247 | connectionsToRequests = aConnectionsToRequests; 248 | } 249 | } 250 | 251 | 252 | #pragma mark - WebFrameLoadDelegate 253 | 254 | - (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame 255 | { 256 | } 257 | 258 | - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame 259 | { 260 | [self setIsSecure:TRUE]; // just triggering change observer 261 | 262 | [self resizeContentDivE]; 263 | 264 | NSURL *newURL = [[[frame dataSource] request] URL]; 265 | NSString *newStoreHost; 266 | if ([@"file" isEqualTo:[newURL scheme]]) { 267 | newStoreHost = @"file"; 268 | } else { 269 | newStoreHost = [newURL host]; 270 | } 271 | 272 | if([self storeHost] == nil) { 273 | [self setStoreHost:newStoreHost]; 274 | [[self delegate] didLoadStore:newURL]; 275 | } else { 276 | FsprgPageType newPageType; 277 | if([newStoreHost isEqualTo:[self storeHost]]) { 278 | newPageType = FsprgPageFS; 279 | } else if([newStoreHost hasSuffix:@"paypal.com"]) { 280 | newPageType = FsprgPagePayPal; 281 | } else { 282 | newPageType = FsprgPageUnknown; 283 | } 284 | [[self delegate] didLoadPage:newURL ofType:newPageType]; 285 | } 286 | } 287 | 288 | - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 289 | { 290 | [[self delegate] webView:sender didFailProvisionalLoadWithError:error forFrame:frame]; 291 | } 292 | 293 | - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 294 | { 295 | [[self delegate] webView:sender didFailLoadWithError:error forFrame:frame]; 296 | } 297 | 298 | #pragma mark - WebUIDelegate 299 | 300 | - (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame 301 | { 302 | NSString *title = [sender mainFrameTitle]; 303 | NSAlert *alertPanel = [NSAlert alertWithMessageText:title defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", message]; 304 | [alertPanel beginSheetModalForWindow:[sender window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL]; 305 | } 306 | 307 | - (NSUInteger)webView:(WebView *)sender dragDestinationActionMaskForDraggingInfo:(id )draggingInfo 308 | { 309 | return WebDragDestinationActionNone; 310 | } 311 | 312 | - (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request 313 | { 314 | NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,0,0) 315 | styleMask:(NSClosableWindowMask|NSResizableWindowMask) 316 | backing:NSBackingStoreBuffered 317 | defer:NO]; 318 | WebView *subWebView = [[[WebView alloc] initWithFrame:NSMakeRect(0,0,0,0)] autorelease]; 319 | [window setReleasedWhenClosed:TRUE]; 320 | [window setContentView:subWebView]; 321 | [window makeKeyAndOrderFront:sender]; 322 | 323 | return subWebView; 324 | } 325 | 326 | #pragma mark - WebResourceLoadDelegate 327 | 328 | - (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource 329 | { 330 | NSURL *URL = [request URL]; 331 | NSString *host = [URL host]; 332 | if ([[self hostCertificates] objectForKey:host] == nil) 333 | { 334 | NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; 335 | [[self connectionsToRequests] setObject:request forKey:connection]; 336 | } 337 | return request; 338 | } 339 | 340 | #pragma mark - NURLConnection delegate 341 | 342 | - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse 343 | { 344 | return cachedResponse; 345 | } 346 | 347 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 348 | { 349 | } 350 | 351 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 352 | { 353 | } 354 | 355 | - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse 356 | { 357 | return request; 358 | } 359 | 360 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 361 | { 362 | [[self connectionsToRequests] setObject:nil forKey:connection]; 363 | } 364 | 365 | - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace 366 | { 367 | return YES; 368 | } 369 | 370 | - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 371 | { 372 | SecTrustRef trustRef = [[challenge protectionSpace] serverTrust]; 373 | SecTrustResultType resultType; 374 | SecTrustEvaluate(trustRef, &resultType); 375 | CFIndex count = SecTrustGetCertificateCount(trustRef); 376 | 377 | NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:count]; 378 | CFIndex idx; 379 | for (idx = 0; idx < count; idx++) { 380 | SecCertificateRef certificateRef = SecTrustGetCertificateAtIndex(trustRef, idx); 381 | [certificates addObject:(id)certificateRef]; 382 | } 383 | 384 | NSURLRequest *request = [[self connectionsToRequests] objectForKey:connection]; 385 | NSURL *URL = [request URL]; 386 | NSString *host = [URL host]; 387 | [[self hostCertificates] setObject:certificates forKey:host]; 388 | } 389 | 390 | 391 | - (void)dealloc 392 | { 393 | [self setWebView:nil]; 394 | [self setDelegate:nil]; 395 | [self setStoreHost:nil]; 396 | [self setHostCertificates:nil]; 397 | [self setConnectionsToRequests:nil]; 398 | 399 | [super dealloc]; 400 | } 401 | 402 | @end 403 | -------------------------------------------------------------------------------- /Example2/RegexKitLite.m: -------------------------------------------------------------------------------- 1 | // 2 | // RegexKitLite.m 3 | // http://regexkit.sourceforge.net/ 4 | // Licensesd under the terms of the BSD License, as specified below. 5 | // 6 | 7 | /* 8 | Copyright (c) 2008, John Engelhart 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | 22 | * Neither the name of the Zang Industries nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 32 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #import 40 | #import 41 | #import 42 | #import 43 | #import 44 | #import 45 | #import 46 | #import 47 | #import "RegexKitLite.h" 48 | 49 | #ifndef RKL_CACHE_SIZE 50 | #define RKL_CACHE_SIZE 23 51 | #endif 52 | 53 | #ifndef RKL_FIXED_LENGTH 54 | #define RKL_FIXED_LENGTH 2048 55 | #endif 56 | 57 | // Ugly macros to keep other parts clean. 58 | 59 | #define NSRangeInsideRange(inside, within) ({NSRange _inside = (inside), _within = (within); (((_inside.location - _within.location) <= _within.length) && ((NSMaxRange(_inside) - _within.location) <= _within.length));}) 60 | #define NSEqualRanges(range1, range2) ({NSRange _r1 = (range1), _r2 = (range2); ((_r1.location == _r2.location) && (_r1.length == _r2.length));}) 61 | #define NSMakeRange(loc, len) ((NSRange){(NSUInteger)(loc), (NSUInteger)(len)}) 62 | #define CFMakeRange(loc, len) ((CFRange){(CFIndex)(loc), (CFIndex)(len)}) 63 | #define NSMaxRange(r) ({NSRange _r = (r); _r.location + _r.length;}) 64 | #define NSNotFoundRange ((NSRange){NSNotFound, 0}) 65 | #define NSMaxiumRange ((NSRange){0, NSUIntegerMax}) 66 | 67 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 68 | #define CFAutorelease(obj) ({CFTypeRef _obj = (obj); (_obj == NULL) ? NULL : [(id)CFMakeCollectable(_obj) autorelease]; }) 69 | #else 70 | #define CFAutorelease(obj) ({CFTypeRef _obj = (obj); (_obj == NULL) ? NULL : [(id)(_obj) autorelease]; }) 71 | #endif 72 | 73 | #define RKLMakeString(str, hash, len, uc) ((RKLString){(str), (hash), (len), (UniChar *)(uc)}) 74 | #define RKLClearCacheSlotLastString(ce) ({ ce->last = RKLMakeString(NULL, 0, 0, NULL); ce->lastFindRange = NSNotFoundRange; ce->lastMatchRange = NSNotFoundRange; }) 75 | #define RKLGetRangeForCapture(regex, status, capture, range) ({ range.location = (NSUInteger)uregex_start(regex, capture, &status); range.length = (NSUInteger)uregex_end(regex, capture, &status) - range.location; status; }) 76 | #define RKLInternalException [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"An internal error occured at %@:%d", [NSString stringWithUTF8String:__FILE__], __LINE__] userInfo:NULL] 77 | 78 | // Exported symbols. Error domains, keys, etc. 79 | NSString * const RKLICURegexErrorDomain = @"RKLICURegexErrorDomain"; 80 | 81 | NSString * const RKLICURegexErrorNameErrorKey = @"RKLICURegexErrorName"; 82 | NSString * const RKLICURegexLineErrorKey = @"RKLICURegexLine"; 83 | NSString * const RKLICURegexOffsetErrorKey = @"RKLICURegexOffset"; 84 | NSString * const RKLICURegexPreContextErrorKey = @"RKLICURegexPreContext"; 85 | NSString * const RKLICURegexPostContextErrorKey = @"RKLICURegexPostContext"; 86 | NSString * const RKLICURegexRegexErrorKey = @"RKLICURegexRegex"; 87 | NSString * const RKLICURegexRegexOptionsErrorKey = @"RKLICURegexRegexOptions"; 88 | 89 | // Type / struct definitions 90 | 91 | typedef struct uregex uregex; // Opaque ICU regex type. 92 | 93 | #define U_PARSE_CONTEXT_LEN 16 94 | 95 | typedef struct UParseError { 96 | int32_t line; 97 | int32_t offset; 98 | unichar preContext[U_PARSE_CONTEXT_LEN]; 99 | unichar postContext[U_PARSE_CONTEXT_LEN]; 100 | } UParseError; 101 | 102 | typedef struct { 103 | void *string; // Used ONLY for pointer equality tests! Never messaged! 104 | CFHashCode hash; 105 | NSUInteger length; 106 | UniChar *uniChar; 107 | } RKLString; 108 | 109 | typedef struct { 110 | NSString *regexString; 111 | RKLRegexOptions regexOptions; 112 | uregex *icu_regex; 113 | NSInteger captureCount; 114 | 115 | RKLString last; 116 | NSRange lastFindRange; 117 | NSRange lastMatchRange; 118 | } RKLCacheSlot; 119 | 120 | // ICU functions. See http://www.icu-project.org/apiref/icu4c/uregex_8h.html Tweaked slightly from the originals, but functionally identical. 121 | const char * u_errorName (int32_t status); 122 | int32_t u_strlen (const UniChar *s); 123 | void uregex_close (uregex *regexp); 124 | int32_t uregex_end (uregex *regexp, int32_t groupNum, int32_t *status); 125 | BOOL uregex_find (uregex *regexp, int32_t location, int32_t *status); 126 | BOOL uregex_findNext (uregex *regexp, int32_t *status); 127 | int32_t uregex_groupCount (uregex *regexp, int32_t *status); 128 | uregex * uregex_open (const UniChar *pattern, int32_t patternLength, RKLRegexOptions flags, UParseError *parseError, int32_t *status); 129 | void uregex_setText (uregex *regexp, const UniChar *text, int32_t textLength, int32_t *status); 130 | int32_t uregex_start (uregex *regexp, int32_t groupNum, int32_t *status); 131 | 132 | static RKLCacheSlot *getCachedRegex (NSString *regexString, RKLRegexOptions regexOptions, NSError **error); 133 | static NSError *RKLNSErrorForRegex (NSString *regexString, RKLRegexOptions regexOptions, UParseError *parseError, int status); 134 | 135 | // Compile unit local global variables 136 | static OSSpinLock cacheSpinLock = OS_SPINLOCK_INIT; 137 | static RKLCacheSlot RKLCache[RKL_CACHE_SIZE]; 138 | static RKLCacheSlot *lastCacheSlot; 139 | static void *lastRegexString; 140 | static UniChar fixedUniChar[(RKL_FIXED_LENGTH * sizeof(UniChar))]; 141 | static RKLString fixedString = {NULL, 0, 0, &fixedUniChar[0]}; 142 | static RKLString dynamicString; 143 | 144 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. 145 | // IMPORTANT! Should only be called with cacheSpinLock already locked! 146 | // ---------- 147 | 148 | static RKLCacheSlot *getCachedRegex(NSString *regexString, RKLRegexOptions regexOptions, NSError **error) { 149 | CFHashCode regexHash = CFHash(regexString); 150 | RKLCacheSlot *cacheSlot = &RKLCache[regexHash % RKL_CACHE_SIZE]; // Retrieve the cache slot for this regex. 151 | UParseError parseError = (UParseError){-1, -1, {0}, {0}}; 152 | UniChar *regexUniChar = NULL; 153 | CFIndex regexLength = 0; 154 | int32_t status = 0; 155 | 156 | // Return the cached entry if it's a match, otherwise clear the slot and create a new ICU regex in its place. 157 | if((cacheSlot->regexOptions == regexOptions) && (cacheSlot->icu_regex != NULL) && (cacheSlot->regexString != NULL) && (CFEqual(regexString, cacheSlot->regexString) == YES)) { lastCacheSlot = cacheSlot; lastRegexString = regexString; return(cacheSlot); } 158 | 159 | RKLClearCacheSlotLastString(cacheSlot); // Clear any cached string state for this cache slot. 160 | if(cacheSlot->regexString != NULL) { CFRelease(cacheSlot->regexString); cacheSlot->regexString = NULL; cacheSlot->regexOptions = 0; } 161 | if(cacheSlot->icu_regex != NULL) { uregex_close(cacheSlot->icu_regex); cacheSlot->icu_regex = NULL; cacheSlot->captureCount = -1; } 162 | 163 | cacheSlot->regexString = (NSString *)CFStringCreateCopy(NULL, (CFStringRef)regexString); // Get a cheap immutable copy. 164 | cacheSlot->regexOptions = regexOptions; 165 | regexLength = CFStringGetLength((CFStringRef)regexString); // In UTF16 code pairs. 166 | 167 | // Try to quickly obtain the regex string in UTF16 format. Otherwise allocate enough space on the stack and convert to UTF16 using the stack buffer. 168 | if((regexUniChar = (UniChar *)CFStringGetCharactersPtr((CFStringRef)regexString)) == NULL) { 169 | if((regexUniChar = alloca(regexLength * sizeof(UniChar))) == NULL) { return(NULL); } 170 | CFStringGetCharacters((CFStringRef)regexString, CFRangeMake(0, regexLength), regexUniChar); 171 | } 172 | 173 | // Create the ICU regex. If there is a problem, create a NSError if requested. 174 | if(((cacheSlot->icu_regex = uregex_open(regexUniChar, (int32_t)regexLength, regexOptions, &parseError, &status)) == NULL) && (status > 0)) { 175 | if(error != NULL) { *error = RKLNSErrorForRegex(regexString, regexOptions, &parseError, status); } 176 | return(NULL); 177 | } 178 | 179 | cacheSlot->captureCount = (NSUInteger)uregex_groupCount(cacheSlot->icu_regex, &status); 180 | lastCacheSlot = cacheSlot; 181 | lastRegexString = regexString; 182 | 183 | return(cacheSlot); 184 | } 185 | 186 | static NSError *RKLNSErrorForRegex(NSString *regexString, RKLRegexOptions regexOptions, UParseError *parseError, int status) { 187 | NSNumber *regexOptionsNumber = [NSNumber numberWithInt:regexOptions]; 188 | NSNumber *lineNumber = [NSNumber numberWithInt:parseError->line]; 189 | NSNumber *offsetNumber = [NSNumber numberWithInt:parseError->offset]; 190 | NSString *preContextString = [NSString stringWithCharacters:&parseError->preContext[0] length:u_strlen(&parseError->preContext[0])]; 191 | NSString *postContextString = [NSString stringWithCharacters:&parseError->postContext[0] length:u_strlen(&parseError->postContext[0])]; 192 | NSString *errorNameString = [NSString stringWithUTF8String:u_errorName(status)]; 193 | NSString *reasonString = [NSString stringWithFormat:@"The error %@ occured at line %d, column %d: %@<>%@", errorNameString, parseError->line, parseError->offset, preContextString, postContextString]; 194 | 195 | // If line == -1, parseError doesn't contain any useful information. Set lineNumber to NULL, 196 | // which will stop adding objects to the dictionary at that point, ignoring everything after. 197 | if(parseError->line == -1) { reasonString = [NSString stringWithFormat:@"The error %@ occured.", errorNameString]; lineNumber = NULL; } 198 | 199 | return([NSError errorWithDomain:RKLICURegexErrorDomain code:(NSInteger)status userInfo:[NSDictionary dictionaryWithObjectsAndKeys: @"There was an error compiling the regular expression.", @"NSLocalizedDescription", reasonString, @"NSLocalizedFailureReason", regexString, RKLICURegexRegexErrorKey, regexOptionsNumber, RKLICURegexRegexOptionsErrorKey, lineNumber, RKLICURegexLineErrorKey, offsetNumber, RKLICURegexOffsetErrorKey, preContextString, RKLICURegexPreContextErrorKey, postContextString, RKLICURegexPostContextErrorKey, errorNameString, RKLICURegexErrorNameErrorKey, NULL]]); 200 | } 201 | 202 | @implementation NSString (RegexKitLiteAdditions) 203 | 204 | + (void)clearStringCache 205 | { 206 | OSSpinLockLock(&cacheSpinLock); 207 | fixedString = RKLMakeString(NULL, 0, 0, fixedString.uniChar); 208 | dynamicString = RKLMakeString(NULL, 0, 0, reallocf(dynamicString.uniChar, 0)); 209 | NSUInteger x = 0; 210 | for(x = 0; x < RKL_CACHE_SIZE; x++) { RKLClearCacheSlotLastString((&RKLCache[x])); } 211 | OSSpinLockUnlock(&cacheSpinLock); 212 | } 213 | 214 | + (NSInteger)captureCountForRegex:(NSString *)regexString 215 | { 216 | return([self captureCountForRegex:regexString options:RKLNoOptions error:NULL]); 217 | } 218 | 219 | + (NSInteger)captureCountForRegex:(NSString *)regexString options:(RKLRegexOptions)options error:(NSError **)error 220 | { 221 | if(error != NULL) { *error = NULL; } 222 | if(regexString == NULL) { [NSException raise:NSInvalidArgumentException format:@"The regular expression argument is NULL."]; } 223 | 224 | RKLCacheSlot *cacheSlot = NULL; 225 | NSInteger captureCount = -1; 226 | 227 | OSSpinLockLock(&cacheSpinLock); 228 | if((cacheSlot = getCachedRegex(regexString, options, error)) != NULL) { captureCount = cacheSlot->captureCount; } 229 | OSSpinLockUnlock(&cacheSpinLock); 230 | 231 | return(captureCount); 232 | } 233 | 234 | - (BOOL)isMatchedByRegex:(NSString *)regexString 235 | { 236 | return([self isMatchedByRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange error:NULL]); 237 | } 238 | 239 | - (BOOL)isMatchedByRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error 240 | { 241 | return(([self rangeOfRegex:regexString options:options inRange:range capture:0 error:error].location == NSNotFound) ? NO : YES); 242 | } 243 | 244 | - (NSString *)stringByMatching:(NSString *)regexString 245 | { 246 | return([self stringByMatching:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:0 error:NULL]); 247 | } 248 | 249 | - (NSString *)stringByMatching:(NSString *)regexString capture:(NSInteger)capture 250 | { 251 | return([self stringByMatching:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:capture error:NULL]); 252 | } 253 | 254 | - (NSString *)stringByMatching:(NSString *)regexString inRange:(NSRange)range 255 | { 256 | return([self stringByMatching:regexString options:RKLNoOptions inRange:range capture:0 error:NULL]); 257 | } 258 | 259 | - (NSString *)stringByMatching:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error 260 | { 261 | NSRange matchedRange = [self rangeOfRegex:regexString options:options inRange:range capture:capture error:error]; 262 | return((matchedRange.location == NSNotFound) ? NULL : CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); 263 | } 264 | 265 | - (NSRange)rangeOfRegex:(NSString *)regexString 266 | { 267 | return([self rangeOfRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:0 error:NULL]); 268 | } 269 | 270 | - (NSRange)rangeOfRegex:(NSString *)regexString capture:(NSInteger)capture 271 | { 272 | return([self rangeOfRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:capture error:NULL]); 273 | } 274 | 275 | - (NSRange)rangeOfRegex:(NSString *)regexString inRange:(NSRange)range 276 | { 277 | return([self rangeOfRegex:regexString options:RKLNoOptions inRange:range capture:0 error:NULL]); 278 | } 279 | 280 | 281 | // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. 282 | // ---------- 283 | 284 | - (NSRange)rangeOfRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error 285 | { 286 | if(error != NULL) { *error = NULL; } 287 | if(regexString == NULL) { [NSException raise:NSInvalidArgumentException format:@"The regular expression argument is NULL."]; } 288 | 289 | NSRange captureRange = NSNotFoundRange; 290 | CFIndex stringLength = CFStringGetLength((CFStringRef)self); // In UTF16 code pairs. 291 | RKLCacheSlot *cacheSlot = NULL; 292 | NSException *exception = NULL; 293 | int32_t status = 0; 294 | 295 | if(range.length == NSUIntegerMax) { range.length = stringLength; } // For convenience. 296 | if((NSUInteger)stringLength < NSMaxRange(range)) { [NSException raise:NSRangeException format:@"The search range exceeds the strings bounds."]; } 297 | 298 | // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS! 299 | 300 | OSSpinLockLock(&cacheSpinLock); // Grab the lock and get cache entry. 301 | // Fast path the common case where this regex is the same one used last time. 302 | // On a miss, do full lookup with getCachedRegex(), which compiles the regex if it's not in the cache. 303 | if((lastCacheSlot != NULL) && (options == lastCacheSlot->regexOptions) && (CFEqual(regexString, lastCacheSlot->regexString) == YES)) { cacheSlot = lastCacheSlot; } 304 | else if((cacheSlot = getCachedRegex(regexString, options, error)) == NULL) { goto exitNow; } 305 | if(cacheSlot->icu_regex == NULL) { exception = RKLInternalException; goto exitNow; } // assertion check. 306 | 307 | if((capture < 0) || (capture > cacheSlot->captureCount)) { exception = [NSException exceptionWithName:NSInvalidArgumentException reason:@"The capture argument is not valid." userInfo:NULL]; goto exitNow; } 308 | 309 | RKLString selfString = RKLMakeString(self, CFHash(self), stringLength, CFStringGetCharactersPtr((CFStringRef)self)); 310 | // *string will point to the most approrpiate buffer. If selfString contains a valid uniChar pointer, that's used. 311 | // Otherwise, use the strings length to determine if the fixed or dynamically sized conversion buffer should be used. 312 | RKLString *string = (selfString.uniChar != NULL) ? &selfString : (stringLength < RKL_FIXED_LENGTH) ? &fixedString : &dynamicString; 313 | 314 | // Check if this regex is already set to this string. 315 | if((cacheSlot->last.uniChar == string->uniChar) && (cacheSlot->last.string == selfString.string) && (cacheSlot->last.hash == selfString.hash) && (cacheSlot->last.length == selfString.length) && (cacheSlot->last.string != NULL)) { goto alreadySetText; } 316 | 317 | // If we didn't get direct UTF16 access, perform any required UTF16 conversions if the current buffer doesn't match this string. 318 | if((string != &selfString) && ((string->string != self) || (string->length != selfString.length) || (string->hash != selfString.hash))) { 319 | *string = RKLMakeString(self, selfString.hash, selfString.length, string->uniChar); 320 | // If this is the dynamically sized buffer, resize the allocation to the correct size. 321 | if((stringLength >= RKL_FIXED_LENGTH) && ((string->uniChar = reallocf(string->uniChar, (selfString.length * sizeof(UniChar)))) == NULL)) { goto exitNow; } 322 | CFStringGetCharacters((CFStringRef)self, CFRangeMake(0, string->length), string->uniChar); // Convert to a UTF16 string. 323 | } 324 | 325 | RKLClearCacheSlotLastString(cacheSlot); // Clear the cached state for this regex. 326 | if(string->uniChar == NULL) { exception = RKLInternalException; goto exitNow; } // assertion check. 327 | uregex_setText(cacheSlot->icu_regex, string->uniChar, string->length, &status); // "set" the ICU regex to this string. 328 | if(status != 0) { goto exitNow; } 329 | cacheSlot->last = *string; // Cache the last string we set this regex to. 330 | 331 | alreadySetText: 332 | if((NSEqualRanges(range, cacheSlot->lastFindRange) == NO)) { // Perform a 'find' if the current range is different than the last find range. 333 | // Using uregex_findNext can be a slight performance win. 334 | BOOL useFindNext = (range.location == (NSMaxRange(cacheSlot->lastMatchRange) + ((cacheSlot->lastMatchRange.length == 0) ? 1 : 0))) ? YES : NO; 335 | 336 | cacheSlot->lastFindRange = NSNotFoundRange; // Cleared the cached search/find range. 337 | if(useFindNext == NO) { if((uregex_find (cacheSlot->icu_regex, range.location, &status) == NO) || (status != 0)) { goto exitNow; } } 338 | else { if((uregex_findNext(cacheSlot->icu_regex, &status) == NO) || (status != 0)) { goto exitNow; } } 339 | 340 | if(RKLGetRangeForCapture(cacheSlot->icu_regex, status, 0, cacheSlot->lastMatchRange) != 0) { goto exitNow; } 341 | cacheSlot->lastFindRange = range; // Cache the successful search/find range. 342 | } 343 | 344 | if(NSRangeInsideRange(cacheSlot->lastMatchRange, range) == NO) { goto exitNow; } // If the regex matched outside the requested range, exit. 345 | if(capture == 0) { captureRange = cacheSlot->lastMatchRange; } else { RKLGetRangeForCapture(cacheSlot->icu_regex, status, capture, captureRange); } 346 | 347 | exitNow: // A bit of advice... 348 | OSSpinLockUnlock(&cacheSpinLock); // Always... no, no... never... forget to unlock your locks. 349 | if(exception != NULL) { [exception raise]; } // I think the young people enjoy it when I "get down" verbally, don't you? 350 | if(status > 0) { [NSException raise:NSInternalInconsistencyException format:@"ICU regular expression error #%d, %s", status, u_errorName(status)]; } 351 | return((status == 0) ? captureRange : NSNotFoundRange); 352 | } 353 | 354 | @end 355 | -------------------------------------------------------------------------------- /Example2/MGTemplateStandardMarkers.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTemplateStandardMarkers.m 3 | // 4 | // Created by Matt Gemmell on 13/05/2008. 5 | // Copyright 2008 Instinctive Code. All rights reserved. 6 | // 7 | 8 | #import "MGTemplateStandardMarkers.h" 9 | #import "MGTemplateFilter.h" 10 | 11 | //============================================================================== 12 | 13 | #define FOR_START @"for" 14 | #define FOR_END @"/for" 15 | 16 | #define FOR_TYPE_ENUMERATOR @"in" // e.g. for thing in things 17 | #define FOR_TYPE_RANGE @"to" // e.g. for 1 to 5 18 | #define FOR_REVERSE @"reversed" 19 | 20 | #define FOR_LOOP_VARS @"currentLoop" 21 | #define FOR_LOOP_CURR_INDEX @"currentIndex" 22 | #define FOR_LOOP_START_INDEX @"startIndex" 23 | #define FOR_LOOP_END_INDEX @"endIndex" 24 | #define FOR_PARENT_LOOP @"parentLoop" 25 | 26 | #define STACK_START_MARKER_RANGE @"markerRange" 27 | #define STACK_START_REMAINING_RANGE @"remainingRange" 28 | #define FOR_STACK_ENUMERATOR @"enumerator" 29 | #define FOR_STACK_ENUM_VAR @"enumeratorVariable" 30 | #define FOR_STACK_DISABLED_OUTPUT @"disabledOutput" 31 | 32 | //============================================================================== 33 | 34 | #define SECTION_START @"section" 35 | #define SECTION_END @"/section" 36 | 37 | //============================================================================== 38 | 39 | #define IF_START @"if" 40 | #define ELSE @"else" 41 | #define IF_END @"/if" 42 | 43 | #define IF_VARS @"currentIf" 44 | #define DISABLE_OUTPUT @"shouldDisableOutput" 45 | #define IF_ARG_TRUE @"argumentTrue" 46 | #define IF_ELSE_SEEN @"elseEncountered" 47 | 48 | //============================================================================== 49 | 50 | #define NOW @"now" 51 | 52 | //============================================================================== 53 | 54 | #define COMMENT_START @"comment" 55 | #define COMMENT_END @"/comment" 56 | 57 | //============================================================================== 58 | 59 | #define LOAD @"load" 60 | 61 | //============================================================================== 62 | 63 | #define CYCLE @"cycle" 64 | #define CYCLE_INDEX @"lastIndex" 65 | #define CYCLE_VALUES @"value" 66 | 67 | //============================================================================== 68 | 69 | #define SET @"set" 70 | 71 | //============================================================================== 72 | 73 | 74 | @implementation MGTemplateStandardMarkers 75 | 76 | 77 | - (id)initWithTemplateEngine:(MGTemplateEngine *)theEngine 78 | { 79 | if (self = [super init]) { 80 | engine = theEngine; 81 | forStack = [[NSMutableArray alloc] init]; 82 | sectionStack = [[NSMutableArray alloc] init]; 83 | ifStack = [[NSMutableArray alloc] init]; 84 | commentStack = [[NSMutableArray alloc] init]; 85 | cycles = [[NSMutableDictionary alloc] init]; 86 | } 87 | return self; 88 | } 89 | 90 | 91 | - (void)dealloc 92 | { 93 | engine = nil; 94 | [forStack release]; 95 | forStack = nil; 96 | [sectionStack release]; 97 | sectionStack = nil; 98 | [ifStack release]; 99 | ifStack = nil; 100 | [commentStack release]; 101 | commentStack = nil; 102 | [cycles release]; 103 | cycles = nil; 104 | 105 | [super dealloc]; 106 | } 107 | 108 | 109 | - (NSArray *)markers 110 | { 111 | return [NSArray arrayWithObjects: 112 | FOR_START, FOR_END, 113 | SECTION_START, SECTION_END, 114 | IF_START, ELSE, IF_END, 115 | NOW, 116 | COMMENT_START, COMMENT_END, 117 | LOAD, 118 | CYCLE, 119 | SET, 120 | nil]; 121 | } 122 | 123 | 124 | - (NSArray *)endMarkersForMarker:(NSString *)marker 125 | { 126 | if ([marker isEqualToString:FOR_START]) { 127 | return [NSArray arrayWithObjects:FOR_END, nil]; 128 | } else if ([marker isEqualToString:SECTION_START]) { 129 | return [NSArray arrayWithObjects:SECTION_END, nil]; 130 | } else if ([marker isEqualToString:IF_START]) { 131 | return [NSArray arrayWithObjects:IF_END, ELSE, nil]; 132 | } else if ([marker isEqualToString:COMMENT_START]) { 133 | return [NSArray arrayWithObjects:COMMENT_END, nil]; 134 | } 135 | return nil; 136 | } 137 | 138 | 139 | - (NSObject *)markerEncountered:(NSString *)marker withArguments:(NSArray *)args inRange:(NSRange)markerRange 140 | blockStarted:(BOOL *)blockStarted blockEnded:(BOOL *)blockEnded 141 | outputEnabled:(BOOL *)outputEnabled nextRange:(NSRange *)nextRange 142 | currentBlockInfo:(NSDictionary *)blockInfo newVariables:(NSDictionary **)newVariables 143 | { 144 | if ([marker isEqualToString:FOR_START]) { 145 | if (args && [args count] >= 3) { 146 | // Determine which type of loop this is. 147 | BOOL isRange = YES; 148 | if ([[args objectAtIndex:1] isEqualToString:FOR_TYPE_ENUMERATOR]) { 149 | isRange = NO; 150 | } 151 | BOOL reversed = NO; 152 | if ([args count] == 4 && [[args objectAtIndex:3] isEqualToString:FOR_REVERSE]) { 153 | reversed = YES; 154 | } 155 | 156 | // Determine if we have acceptable parameters. 157 | NSObject *loopEnumObject = nil; 158 | BOOL valid = NO; 159 | NSString *startArg = [args objectAtIndex:0]; 160 | NSString *endArg = [args objectAtIndex:2]; 161 | int startIndex, endIndex; 162 | if (isRange) { 163 | // Check to see if either the arg itself is numeric, or it corresponds to a numeric variable. 164 | valid = [self argIsNumeric:startArg intValue:&startIndex checkVariables:YES]; 165 | if (valid) { 166 | valid = [self argIsNumeric:endArg intValue:&endIndex checkVariables:YES]; 167 | if (valid) { 168 | // Check startIndex and endIndex are sensible. 169 | valid = (startIndex <= endIndex); 170 | } 171 | } 172 | } else { 173 | startIndex = 0; 174 | 175 | // Check that endArg is a collection. 176 | NSObject *obj = [engine resolveVariable:endArg]; 177 | if (obj && [obj respondsToSelector:@selector(objectEnumerator)] && [obj respondsToSelector:@selector(count)]) { 178 | endIndex = [(NSArray *)obj count]; 179 | if (endIndex > 0) { 180 | loopEnumObject = obj; 181 | valid = YES; 182 | } 183 | } 184 | } 185 | 186 | if (valid) { 187 | *blockStarted = YES; 188 | 189 | // Set up for-stack frame for this loop. 190 | NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys: 191 | [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE, 192 | [NSValue valueWithRange:*nextRange], STACK_START_REMAINING_RANGE, 193 | nil]; 194 | [forStack addObject:stackFrame]; 195 | 196 | // Set up variables for the block. 197 | int currentIndex = (reversed) ? endIndex : startIndex; 198 | NSMutableDictionary *loopVars = [NSMutableDictionary dictionaryWithObjectsAndKeys: 199 | [NSNumber numberWithInt:startIndex], FOR_LOOP_START_INDEX, 200 | [NSNumber numberWithInt:endIndex], FOR_LOOP_END_INDEX, 201 | [NSNumber numberWithInt:currentIndex], FOR_LOOP_CURR_INDEX, 202 | [NSNumber numberWithBool:reversed], FOR_REVERSE, 203 | nil]; 204 | NSMutableDictionary *blockVars = [NSMutableDictionary dictionaryWithObjectsAndKeys: 205 | loopVars, FOR_LOOP_VARS, 206 | nil]; 207 | 208 | // Add enumerator variable if appropriate. 209 | if (!isRange) { 210 | NSEnumerator *enumerator; 211 | if (reversed && [loopEnumObject respondsToSelector:@selector(reverseObjectEnumerator)]) { 212 | enumerator = [(NSArray *)loopEnumObject reverseObjectEnumerator]; 213 | } else { 214 | enumerator = [(NSArray *)loopEnumObject objectEnumerator]; 215 | } 216 | [stackFrame setObject:enumerator forKey:FOR_STACK_ENUMERATOR]; 217 | [stackFrame setObject:startArg forKey:FOR_STACK_ENUM_VAR]; 218 | [blockVars setObject:[enumerator nextObject] forKey:startArg]; 219 | } 220 | 221 | // Add parentLoop if it exists. 222 | if (blockInfo) { 223 | NSDictionary *parentLoop; 224 | parentLoop = (NSDictionary *)[engine resolveVariable:FOR_LOOP_VARS]; // in case parent loop isn't in the first parent stack-frame. 225 | if (parentLoop) { 226 | [loopVars setObject:parentLoop forKey:FOR_PARENT_LOOP]; 227 | } 228 | } 229 | 230 | *newVariables = blockVars; 231 | } else { 232 | // Disable output for this block. 233 | *blockStarted = YES; 234 | NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys: 235 | [NSNumber numberWithBool:YES], FOR_STACK_DISABLED_OUTPUT, 236 | [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE, 237 | [NSValue valueWithRange:*nextRange], STACK_START_REMAINING_RANGE, 238 | nil]; 239 | [forStack addObject:stackFrame]; 240 | *outputEnabled = NO; 241 | } 242 | } 243 | 244 | } else if ([marker isEqualToString:FOR_END]) { 245 | // Decide whether to loop back or terminate. 246 | if ([self currentBlock:blockInfo matchesTopOfStack:forStack]) { 247 | NSMutableDictionary *frame = [forStack lastObject]; 248 | 249 | // Check to see if this was a block with an invalid looping condition. 250 | NSNumber *disabledOutput = (NSNumber *)[frame objectForKey:FOR_STACK_DISABLED_OUTPUT]; 251 | if (disabledOutput && [disabledOutput boolValue]) { 252 | *outputEnabled = YES; 253 | *blockEnded = YES; 254 | [forStack removeLastObject]; 255 | } 256 | 257 | // This is the same loop that's on top of our stack. Check to see if we need to loop back. 258 | BOOL loop = NO; 259 | NSDictionary *blockVars = [blockInfo objectForKey:BLOCK_VARIABLES_KEY]; 260 | if ([blockVars count] == 0) { 261 | *blockEnded = YES; 262 | return nil; 263 | } 264 | NSMutableDictionary *loopVars = [[[blockVars objectForKey:FOR_LOOP_VARS] mutableCopy] autorelease]; 265 | BOOL reversed = [[loopVars objectForKey:FOR_REVERSE] boolValue]; 266 | NSEnumerator *loopEnum = [frame objectForKey:FOR_STACK_ENUMERATOR]; 267 | NSObject *newEnumValue = nil; 268 | int currentIndex = [[loopVars objectForKey:FOR_LOOP_CURR_INDEX] intValue]; 269 | if (loopEnum) { 270 | // Enumerator type. 271 | newEnumValue = [loopEnum nextObject]; 272 | if (newEnumValue) { 273 | loop = YES; 274 | } 275 | } else { 276 | // Range type. 277 | if (reversed) { 278 | int minIndex = [[loopVars objectForKey:FOR_LOOP_START_INDEX] intValue]; 279 | if (currentIndex > minIndex) { 280 | loop = YES; 281 | } 282 | } else { 283 | int maxIndex = [[loopVars objectForKey:FOR_LOOP_END_INDEX] intValue]; 284 | if (currentIndex < maxIndex) { 285 | loop = YES; 286 | } 287 | } 288 | } 289 | 290 | if (loop) { 291 | // Set remainingRange from stack dict 292 | *nextRange = [[frame objectForKey:STACK_START_REMAINING_RANGE] rangeValue]; 293 | 294 | // Set new currentIndex 295 | if (reversed) { 296 | currentIndex--; 297 | } else { 298 | currentIndex++; 299 | } 300 | [loopVars setObject:[NSNumber numberWithInt:currentIndex] forKey:FOR_LOOP_CURR_INDEX]; 301 | 302 | // Set new val for enumVar if specified 303 | NSMutableDictionary *newVars = [NSMutableDictionary dictionaryWithObjectsAndKeys: 304 | loopVars, FOR_LOOP_VARS, 305 | nil]; 306 | if (newEnumValue) { 307 | [newVars setObject:newEnumValue forKey:[frame objectForKey:FOR_STACK_ENUM_VAR]]; 308 | } 309 | 310 | *newVariables = newVars; 311 | } else { 312 | // Don't need to do much here, since: 313 | // 1. Each blockStack frame for a "for" has its own currentLoop dict. 314 | // 2. Parent loop's enum-vars are still in place in the parent stack's vars. 315 | 316 | // End block. 317 | *blockEnded = YES; 318 | [forStack removeLastObject]; 319 | } 320 | 321 | // Return immediately. 322 | return nil; 323 | } 324 | *blockEnded = YES; 325 | 326 | } else if ([marker isEqualToString:SECTION_START]) { 327 | if (args && [args count] == 1) { 328 | *blockStarted = YES; 329 | 330 | // Set up for-stack frame for this section. 331 | NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys: 332 | [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE, 333 | nil]; 334 | [sectionStack addObject:stackFrame]; 335 | } 336 | 337 | } else if ([marker isEqualToString:SECTION_END]) { 338 | if ([self currentBlock:blockInfo matchesTopOfStack:sectionStack]) { 339 | // This is the same section that's on top of our stack. Remove from stack. 340 | [sectionStack removeLastObject]; 341 | } 342 | *blockEnded = YES; 343 | 344 | } else if ([marker isEqualToString:IF_START]) { 345 | if (args && ([args count] == 1 || [args count] == 3)) { 346 | *blockStarted = YES; 347 | 348 | // Determine appropriate values for outputEnabled and for our if-stack frame. 349 | BOOL elseEncountered = NO; 350 | BOOL argTrue = NO; 351 | if ([args count] == 1) { 352 | argTrue = [self argIsTrue:[args objectAtIndex:0]]; 353 | } else if ([args count] == 2 && [[[args objectAtIndex:0] lowercaseString] isEqualToString:@"not"]) { 354 | // e.g. if not x 355 | argTrue = ![self argIsTrue:[args objectAtIndex:1]]; 356 | } else if ([args count] == 3) { 357 | // Assumed to be of the form: operand comparison operand, e.g. x == y 358 | NSString *firstArg = [args objectAtIndex:0]; 359 | NSString *secondArg = [args objectAtIndex:2]; 360 | BOOL firstTrue = [self argIsTrue:firstArg]; 361 | BOOL secondTrue = [self argIsTrue:secondArg]; 362 | int num1, num2; 363 | BOOL firstNumeric, secondNumeric; 364 | firstNumeric = [self argIsNumeric:firstArg intValue:&num1 checkVariables:YES]; 365 | secondNumeric = [self argIsNumeric:secondArg intValue:&num2 checkVariables:YES]; 366 | if (!firstNumeric) { 367 | num1 = ([engine resolveVariable:firstArg]) ? 1 : 0; 368 | } 369 | if (!secondNumeric) { 370 | num2 = ([engine resolveVariable:secondArg]) ? 1 : 0; 371 | } 372 | NSString *op = [[args objectAtIndex:1] lowercaseString]; 373 | 374 | if ([op isEqualToString:@"and"] || [op isEqualToString:@"&&"]) { 375 | argTrue = (firstTrue && secondTrue); 376 | } else if ([op isEqualToString:@"or"] || [op isEqualToString:@"||"]) { 377 | argTrue = (firstTrue || secondTrue); 378 | } else if ([op isEqualToString:@"="] || [op isEqualToString:@"=="]) { 379 | argTrue = (num1 == num2); 380 | } else if ([op isEqualToString:@"!="] || [op isEqualToString:@"<>"]) { 381 | argTrue = (num1 != num2); 382 | } else if ([op isEqualToString:@">"]) { 383 | argTrue = (num1 > num2); 384 | } else if ([op isEqualToString:@"<"]) { 385 | argTrue = (num1 < num2); 386 | } else if ([op isEqualToString:@">="]) { 387 | argTrue = (num1 >= num2); 388 | } else if ([op isEqualToString:@"<="]) { 389 | argTrue = (num1 <= num2); 390 | } else if ([op isEqualToString:@"\%"]) { 391 | argTrue = ((num1 % num2) > 0); 392 | } 393 | } 394 | 395 | BOOL shouldDisableOutput = *outputEnabled; 396 | if (shouldDisableOutput && !argTrue) { 397 | *outputEnabled = NO; 398 | } 399 | 400 | // Create variables. 401 | NSMutableDictionary *ifVars = [NSMutableDictionary dictionaryWithObjectsAndKeys: 402 | [NSNumber numberWithBool:argTrue], IF_ARG_TRUE, 403 | [NSNumber numberWithBool:shouldDisableOutput], DISABLE_OUTPUT, 404 | [NSNumber numberWithBool:elseEncountered], IF_ELSE_SEEN, 405 | nil]; 406 | 407 | // Set up for-stack frame for this if-statement. 408 | NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys: 409 | [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE, 410 | ifVars, IF_VARS, 411 | nil]; 412 | [ifStack addObject:stackFrame]; 413 | } 414 | 415 | } else if ([marker isEqualToString:ELSE]) { 416 | if ([self currentBlock:blockInfo matchesTopOfStack:ifStack]) { 417 | NSMutableDictionary *frame = [[ifStack lastObject] objectForKey:IF_VARS]; 418 | BOOL elseSeen = [[frame objectForKey:IF_ELSE_SEEN] boolValue]; 419 | BOOL argTrue = [[frame objectForKey:IF_ARG_TRUE] boolValue]; 420 | BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue]; 421 | 422 | if (!elseSeen) { 423 | if (modifyOutput) { 424 | // Only make changes if we've not already seen an 'else' for this block, 425 | // and if we're modifying output state at all. 426 | *outputEnabled = !argTrue; // either turning it off, or turning it back on. 427 | } 428 | 429 | // Note that we've now seen the else marker. 430 | [frame setObject:[NSNumber numberWithBool:YES] forKey:IF_ELSE_SEEN]; 431 | } 432 | } 433 | 434 | } else if ([marker isEqualToString:IF_END]) { 435 | if ([self currentBlock:blockInfo matchesTopOfStack:ifStack]) { 436 | NSMutableDictionary *frame = [[ifStack lastObject] objectForKey:IF_VARS]; 437 | BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue]; 438 | if (modifyOutput) { 439 | // If we're modifying output, it was enabled when this block started. 440 | // Thus, it should be enabled after the block ends. 441 | // If it's already enabled, this will have no harmful effect. 442 | *outputEnabled = YES; 443 | } 444 | 445 | // End block. 446 | [ifStack removeLastObject]; 447 | *blockEnded = YES; 448 | } 449 | *blockEnded = YES; 450 | 451 | } else if ([marker isEqualToString:NOW]) { 452 | return [NSDate date]; 453 | 454 | } else if ([marker isEqualToString:COMMENT_START]) { 455 | // Work out if we need to start a block. 456 | if (!args || [args count] == 0) { 457 | *blockStarted = YES; 458 | 459 | // Determine appropriate values for outputEnabled and for our stack frame. 460 | BOOL shouldDisableOutput = *outputEnabled; 461 | if (shouldDisableOutput) { 462 | *outputEnabled = NO; 463 | } 464 | 465 | // Set up for-stack frame for this if-statement. 466 | NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys: 467 | [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE, 468 | [NSNumber numberWithBool:shouldDisableOutput], DISABLE_OUTPUT, 469 | nil]; 470 | [commentStack addObject:stackFrame]; 471 | } 472 | 473 | } else if ([marker isEqualToString:COMMENT_END]) { 474 | // Check this is block on top of stack. 475 | if ([self currentBlock:blockInfo matchesTopOfStack:commentStack]) { 476 | NSMutableDictionary *frame = [commentStack lastObject]; 477 | BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue]; 478 | if (modifyOutput) { 479 | // If we're modifying output, it was enabled when this block started. 480 | // Thus, it should be enabled after the block ends. 481 | // If it's already enabled, this will have no harmful effect. 482 | *outputEnabled = YES; 483 | } 484 | 485 | // End block. 486 | [commentStack removeLastObject]; 487 | *blockEnded = YES; 488 | } 489 | *blockEnded = YES; 490 | 491 | } else if ([marker isEqualToString:LOAD]) { 492 | if (args && [args count] > 0) { 493 | for (NSString *className in args) { 494 | Class class = NSClassFromString(className); 495 | if (class && [(id)class isKindOfClass:[NSObject class]]) { 496 | if ([class conformsToProtocol:@protocol(MGTemplateFilter)]) { 497 | // Instantiate and load filter. 498 | NSObject *obj = [[[class alloc] init] autorelease]; 499 | [engine loadFilter:obj]; 500 | } else if ([class conformsToProtocol:@protocol(MGTemplateMarker)]) { 501 | // Instantiate and load marker. 502 | NSObject *obj = [[[class alloc] initWithTemplateEngine:engine] autorelease]; 503 | [engine loadMarker:obj]; 504 | } 505 | } 506 | } 507 | } 508 | 509 | } else if ([marker isEqualToString:CYCLE]) { 510 | if (args && [args count] > 0) { 511 | // Check to see if it's an existing cycle. 512 | NSString *rangeKey = NSStringFromRange(markerRange); 513 | NSMutableDictionary *cycle = [cycles objectForKey:rangeKey]; 514 | if (cycle) { 515 | NSArray *vals = [cycle objectForKey:CYCLE_VALUES]; 516 | int currIndex = [[cycle objectForKey:CYCLE_INDEX] intValue]; 517 | currIndex++; 518 | if (currIndex >= [vals count]) { 519 | currIndex = 0; 520 | } 521 | [cycle setObject:[NSNumber numberWithInt:currIndex] forKey:CYCLE_INDEX]; 522 | return [vals objectAtIndex:currIndex]; 523 | } else { 524 | // New cycle. Create and output appropriately. 525 | cycle = [NSMutableDictionary dictionaryWithCapacity:2]; 526 | [cycle setObject:[NSNumber numberWithInt:0] forKey:CYCLE_INDEX]; 527 | [cycle setObject:args forKey:CYCLE_VALUES]; 528 | [cycles setObject:cycle forKey:rangeKey]; 529 | return [args objectAtIndex:0]; 530 | } 531 | } 532 | } else if ([marker isEqualToString:SET]) { 533 | if (args && [args count] == 2 && *outputEnabled) { 534 | // Set variable arg1 to value arg2. 535 | NSDictionary *newVar = [NSDictionary dictionaryWithObject:[args objectAtIndex:1] 536 | forKey:[args objectAtIndex:0]]; 537 | if (newVar) { 538 | *newVariables = newVar; 539 | } 540 | } 541 | } 542 | 543 | return nil; 544 | } 545 | 546 | 547 | - (BOOL)currentBlock:(NSDictionary *)blockInfo matchesTopOfStack:(NSMutableArray *)stack 548 | { 549 | if (blockInfo && [stack count] > 0) { // end-tag should always have blockInfo, and correspond to a stack frame. 550 | NSDictionary *frame = [stack lastObject]; 551 | NSRange stackSectionRange = [[frame objectForKey:STACK_START_MARKER_RANGE] rangeValue]; 552 | NSRange thisSectionRange = [[blockInfo objectForKey:BLOCK_START_MARKER_RANGE_KEY] rangeValue]; 553 | if (NSEqualRanges(stackSectionRange, thisSectionRange)) { 554 | return YES; 555 | } 556 | } 557 | return NO; 558 | } 559 | 560 | 561 | - (BOOL)argIsTrue:(NSString *)arg 562 | { 563 | BOOL argTrue = NO; 564 | if (arg) { 565 | NSObject *val = [engine resolveVariable:arg]; 566 | if (val) { 567 | if ([val isKindOfClass:[NSNumber class]]) { 568 | argTrue = [(NSNumber *)val boolValue]; 569 | } else { 570 | argTrue = YES; 571 | } 572 | } 573 | } 574 | return argTrue; 575 | } 576 | 577 | 578 | - (BOOL)argIsNumeric:(NSString *)arg intValue:(int *)val checkVariables:(BOOL)checkVars 579 | { 580 | BOOL numeric = NO; 581 | int value = 0; 582 | 583 | if (arg && [arg length] > 0) { 584 | if ([[arg substringToIndex:1] isEqualToString:@"0"] || [arg intValue] != 0) { 585 | numeric = YES; 586 | value = [arg intValue]; 587 | } else if (checkVars) { 588 | // Check to see if arg is a variable with an intValue. 589 | NSObject *argObj = [engine resolveVariable:arg]; 590 | NSString *argStr = [NSString stringWithFormat:@"%@", argObj]; 591 | if (argObj && [argObj respondsToSelector:@selector(intValue)] && 592 | [self argIsNumeric:argStr intValue:&value checkVariables:NO]) { // avoid recursion 593 | numeric = YES; 594 | } 595 | } 596 | } 597 | 598 | if (val) { 599 | *val = value; 600 | } 601 | return numeric; 602 | } 603 | 604 | 605 | - (void)engineFinishedProcessingTemplate 606 | { 607 | // Clean up stacks etc. 608 | [forStack release]; 609 | forStack = [[NSMutableArray alloc] init]; 610 | [sectionStack release]; 611 | sectionStack = [[NSMutableArray alloc] init]; 612 | [ifStack release]; 613 | ifStack = [[NSMutableArray alloc] init]; 614 | [commentStack release]; 615 | commentStack = [[NSMutableArray alloc] init]; 616 | cycles = [[NSMutableDictionary alloc] init]; 617 | } 618 | 619 | 620 | @end 621 | --------------------------------------------------------------------------------