Skip to content

Instantly share code, notes, and snippets.

@jon
Created January 11, 2009 02:04
Show Gist options
  • Save jon/45607 to your computer and use it in GitHub Desktop.
Save jon/45607 to your computer and use it in GitHub Desktop.
//
// UIWebView+SmartJS.h
//
// Created by Jon Olson on 12/27/08.
// Copyright 2008-2009 Ballistic Pigeon, LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString * const SmartJSErrorDomain;
enum {
SmartJSSyntaxError = -1,
SmartJSTypeError = -2,
SmartJSRangeError = -3,
SmartJSURIError = -4,
SmartJSReferenceError = -5,
SmartJSEvalError = -100000L
};
@interface UIWebView (SmartJS)
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string error:(NSError **)error;
@end
//
// UIWebView+SmartJS.m
//
// Created by Jon Olson on 12/27/08.
// Copyright 2008-2009 Ballistic Pigeon, LLC. All rights reserved.
//
#import "UIWebView+SmartJS.h"
NSString * const SmartJSErrorDomain = @"com.ballisticpigeon.smartjavascript.SmartJSErrorDomain";
// Naïve but correct implementation.
// It would be substantially better to do all the replacements in one pass
// This requires dealing natively with Unicode (or at least UTF8)
// There's also the questions of how many times we end up copying the string (4 right now, maybe?)
// We are still linear time, though, inasmuch as we do our replacment exactly 4 times
// On the whole, this is a mess best solved by a proper regular expression processor
static NSString *EscapeStringAsJavascriptCode(NSString *string) {
NSMutableString *mutable = [[string mutableCopyWithZone:nil] autorelease];
[mutable replaceOccurrencesOfString:@"\"" withString:@"\\\"" options:0 range:NSMakeRange(0, [string length])];
[mutable replaceOccurrencesOfString:@"\\" withString:@"\\\\" options:0 range:NSMakeRange(0, [string length])];
[mutable replaceOccurrencesOfString:@"\r" withString:@"\\r" options:0 range:NSMakeRange(0, [string length])];
[mutable replaceOccurrencesOfString:@"\n" withString:@"\\n" options:0 range:NSMakeRange(0, [string length])];
return [NSString stringWithFormat:@"\"%@\"", mutable];
}
static NSString const *safeJSFormat = @"(function() { try { return 'success:' + eval(%@); } catch (e) { return 'error:' + e.name + ':' + e.message})()";
@implementation UIWebView (SmartJS)
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string error:(NSError **)error {
NSString *code = [NSString stringWithFormat:(NSString *)safeJSFormat, string];
NSString *result = [self stringByEvaluatingJavaScriptFromString:code]; // This should ALWAYS work
NSRange faultStatusRange = [result rangeOfString:@":"];
if (faultStatusRange.location == NSNotFound)
[NSException raise:@"SmartJSResultParseException" format:@"Unable to parse the result from SmartJavaScript evaluator. This indicates a bug in the SmartJavaScript extension. The resulting text was: %@", result];
NSString *faultStatus = [result substringToIndex:faultStatusRange.location-1];
NSString *body = [result substringFromIndex:faultStatusRange.location+1];
if ([faultStatus isEqualToString:@"success"]) {
*error = nil; // No error
return body; // Return their requested result
}
NSRange errorNameRange = [body rangeOfString:@":"];
if (errorNameRange.location == NSNotFound)
[NSException raise:@"SmartJSResultParseException" format:@"Unable to parse the result from SmartJavaScript evaluator. This indicates a bug in the SmartJavaScript extension. The resulting text was: %@", result]; // Return the full result as the : detected earlier could have been part of whatever unparsable string was returned
NSString *errorName = [result substringToIndex:errorNameRange.location-1];
NSString *errorDescription = [result substringFromIndex:errorNameRange.location+1];
NSDictionary *errorUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:errorName, NSLocalizedDescriptionKey, errorDescription, NSLocalizedFailureReasonErrorKey, nil];
if ([errorName isEqualToString:@"SyntaxError"])
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSSyntaxError userInfo:errorUserInfo];
else if ([errorName isEqualToString:@"TypeError"])
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSTypeError userInfo:errorUserInfo];
else if ([errorName isEqualToString:@"RangeError"])
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSRangeError userInfo:errorUserInfo];
else if ([errorName isEqualToString:@"URIError"])
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSURIError userInfo:errorUserInfo];
else if ([errorName isEqualToString:@"ReferenceError"])
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSReferenceError userInfo:errorUserInfo];
else
*error = [NSError errorWithDomain:SmartJSErrorDomain code:SmartJSEvalError userInfo:errorUserInfo];
return nil;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment