I revamped the UI for advanced MathJax configuration for the upcoming Marked 2.5 update I mentioned recently. The configuration is simply a field where you can input additional inline configuration options using the JSON syntax you’d find in a script tag in an HTML document.

When writing JSON in a text field without the benefit of syntax highlighting and linters, though, it’s easy to make a mistake, and Marked would only let you know that when refreshing a document and attempting to implement the configuration during load. I wanted it to be able to provide immediate feedback.

Cocoa provides some decent JSON parsing libraries, but all I needed to know was whether it was valid syntax for a JavaScript engine to parse. I ended up using JavaScriptCore to quickly allocate and validate the string. It works quite well, though I haven’t benchmarked it very thoroughly.

This below code is from an NSTextView subclass. It takes the field’s current string, trims it and removes newlines, then attempts to run a JSON.stringify() in a JavaScriptCore context.

- (void)didChangeText
{
  [super didChangeText];

  NSColor *errColor = [NSColor colorWithCalibratedRed:0.445 
                                                green:0.009 
                                                 blue:0.027 
                                                alpha:1.000];
  NSColor *okColor = [NSColor blackColor];

  NSString *input = [[[self string] straightenQuotes] trim];
  input = [input replace:RX(@"[\\n\\r]+") with:@""];
  if (![input isMatch:RX(@"^\\{.*?\\}$")])
  {
    input = [NSString stringWithFormat:@"{%@}", 
                                       input];
  }

  NSString *scriptString = [NSString stringWithFormat:@"JSON.stringify(%@)", 
                                                      input];
  JSGlobalContextRef jsx = JSGlobalContextCreate(NULL);
  JSStringRef scriptJS = JSStringCreateWithCFString(
                                      (CFStringRef)CFStringCreateWithCString(
                                        kCFAllocatorDefault,
                                        scriptString.UTF8String,
                                        kCFStringEncodingUTF8
                                      )
                                    );

  BOOL valid = JSCheckScriptSyntax(jsx, scriptJS, NULL, 0, NULL);

  JSStringRelease(scriptJS);

  if (!valid)
      [self setTextColor:errColor];
  else
      [self setTextColor:okColor];
}

For reference, the RX() methods come from Objective-C Regex Categories by Josh Wright. They are very handy shortcuts for the NSRegularExpression syntax1. Also note that it calls a method called “straightenQuotes”, which is from an NSString category and simply dumbs down smart quotes:

- (NSString *)straightenQuotes
{
	NSString *input = [self replace:RX(@"[“”]") with:@"\""]; 
	input = [input replace:RX(@"[‘’]") with:@"'"];
	return input;
}

This is because even though the text field disables smart quotes, system settings can override it, and users can paste them into the field, which can be a hard bug to track down in customer support.

If this is useful to you, great. If you see any major issues with the choice or in the implementation, I welcome the input.

Here’s a good (though aging) intro to JavaScriptCore.

  1. Yes, I should be working toward Swift, but this codebase is quite well grounded in Obj-C, and I’m easily intimidated by large undertakings that will break things that are working.