Cocoa: How to save NSAttributedString to JSON
NSAttributedString has two properties:
- the string
- an array of attribute "runs"
Each "run" has:
- an integer range that it applies to
- a dictionary of key/value attributes
It would be very easy to represent that as JSON, using enumerateAttributesInRange:options:usingBlock:
.
Something like:
{ "string" : "Hello World", "runs" : [ { "range" : [0,3], "attributes" : { "font" : { "name" : "Arial", "size" : 12 } } }, { "range" : [3,6], "attributes" : { "font" : { "name" : "Arial", "size" : 12 }, "color" : [255,0,0] } }, { "range" : [9,2], "attributes" : { "font" : { "name" : "Arial", "size" : 12 } } } ]}
EDIT: here's an example implementation:
// create a basic attributed stringNSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"Hello World" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Arial" size:12]}];[attStr addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(3, 6)];// build array of attribute runsNSMutableArray *attributeRuns = [NSMutableArray array];[attStr enumerateAttributesInRange:NSMakeRange(0, attStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { NSArray *rangeArray = @[[NSNumber numberWithUnsignedInteger:range.location], [NSNumber numberWithUnsignedInteger:range.length]]; NSMutableDictionary *runAttributes = [NSMutableDictionary dictionary]; [attrs enumerateKeysAndObjectsUsingBlock:^(id attributeName, id attributeValue, BOOL *stop) { if ([attributeName isEqual:NSFontAttributeName]) { // convert font values into a dictionary with the name and size attributeName = @"font"; attributeValue = @{@"name": [(NSFont *)attributeValue displayName], @"size": [NSNumber numberWithFloat:[(NSFont *)attributeValue pointSize]]}; } else if ([attributeName isEqualToString:NSForegroundColorAttributeName]) { // convert foreground colour values into an array with red/green/blue as a number from 0 to 255 attributeName = @"color"; attributeValue = @[[NSNumber numberWithInteger:([(NSColor *)attributeValue redComponent] * 255)], [NSNumber numberWithInteger:([(NSColor *)attributeValue greenComponent] * 255)], [NSNumber numberWithInteger:([(NSColor *)attributeValue blueComponent] * 255)]]; } else { // skip unknown attributes NSLog(@"skipping unknown attribute %@", attributeName); return; } [runAttributes setObject:attributeValue forKey:attributeName]; }]; // save the attributes (if there are any) if (runAttributes.count == 0) return; [attributeRuns addObject:@{@"range": rangeArray, @"attributes": runAttributes}];}];// build JSON outputNSDictionary *jsonOutput = @{@"string": attStr.string, @"runs": attributeRuns};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonOutput options:NSJSONWritingPrettyPrinted error:NULL];NSLog(@"%@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);exit(0);
You could try starting with RTFFromRange:
From the documentation: For information about the OS X methods supporting RTF, ..., see NSAttributedString Application Kit Additions Reference.
RTF should be self contained. RTFFromRange: returns NSData; I would think its probably character data in some encoding so should be easy to convert to NSString.
(Sorry, just read that method is MacOS X only).
You could use this simple code snippet to convert NSAttributedString
to XML without actually parsing NSAttributedString
. This can be a human-readable alternative to JSON if you can afford verbose text output.
It can be also used for decoding back to NSAttributedString
.
let data = NSMutableData() let archiver = NSKeyedArchiver(forWritingWithMutableData: data) archiver.outputFormat = .XMLFormat_v1_0 textView.attributedText.encodeWithCoder(archiver) archiver.finishEncoding() let textAsString = NSString(data: data, encoding: NSUTF8StringEncoding)'