Skip to content

Instantly share code, notes, and snippets.

@jaanus
Created March 25, 2015 17:58
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaanus/fdf0e9ab0fc64c029610 to your computer and use it in GitHub Desktop.
Save jaanus/fdf0e9ab0fc64c029610 to your computer and use it in GitHub Desktop.
Create NSAttributedString out of NSString with Markdown-like syntax for bold text and links.
#if TARGET_OS_IPHONE
@import UIKit;
#define JKColor UIColor
#define JKFont UIFont
#else
@import AppKit;
#define JKColor NSColor
#define JKFont NSFont
#endif
@interface NSString (JKAttributer)
/**
A very minimalist partial implementation of parsing Markdown-style links and strong emphasis texts.
Supports exactly two things: hyperlinks in the inline style [link](http://example.com), and **bold text**. Links are turned into URL-s, and strong font attribute is added to the strong parts.
Supports only the “happy path.” Is not a comprehensive Markdown implementation.
*/
- (NSAttributedString *)attributedStringFromLinksAndStrongTextWithBaseFont:(JKFont *)baseFont strongFont:(JKFont *)strongFont urlColor:(JKColor *)urlColor;
@end
@implementation NSString (JKAttributer)
- (NSAttributedString *)attributedStringFromLinksAndStrongTextWithBaseFont:(JKFont *)baseFont strongFont:(JKFont *)strongFont urlColor:(JKColor *)urlColor
{
__block NSString *s = [self copy];
// Part 1. Process URL-s. The outcome is a plaintext string + URL ranges and their targets
NSError *error = NULL;
NSRegularExpression *linkRegex = [NSRegularExpression regularExpressionWithPattern:@"\\[([^]]+)\\]\\(([^\\)]+)\\)" options:0 error:&error];
NSMutableArray *linkRanges = [NSMutableArray array];
NSMutableArray *linkTargets = [NSMutableArray array];
// How much to offset the following match ranges
__block NSInteger offset = 0;
[linkRegex enumerateMatchesInString:self options:0 range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange range = [result rangeAtIndex:0];
range.location += offset;
NSRange urlRangeInTargetString = [result rangeAtIndex:1];
urlRangeInTargetString.location += offset - 1;
s = [s stringByReplacingCharactersInRange:range withString:[self substringWithRange:[result rangeAtIndex:1]]];
[linkRanges addObject:[NSValue valueWithRange:urlRangeInTargetString]];
[linkTargets addObject:[self substringWithRange:[result rangeAtIndex:2]]];
offset -= [result rangeAtIndex:2].length + 4;
}];
// Part 2. Process **strong** parts
NSString *strongInput = [s copy];
__block NSInteger strongOffset = 0;
NSMutableArray *strongRanges = [NSMutableArray array];
NSRegularExpression *strongRegex = [NSRegularExpression regularExpressionWithPattern:@"\\*\\*([^*]+)\\*\\*" options:0 error:nil];
[strongRegex enumerateMatchesInString:strongInput options:0 range:NSMakeRange(0, strongInput.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange rangeInString = [result rangeAtIndex:1];
rangeInString.location += strongOffset - 2;
[strongRanges addObject:[NSValue valueWithRange:rangeInString]];
NSRange needleRange = [result rangeAtIndex:0];
needleRange.location += strongOffset;
NSRange replacementRange = [result rangeAtIndex:1];
s = [s stringByReplacingCharactersInRange:needleRange withString:[strongInput substringWithRange:replacementRange]];
// Shift URL-s back by a step
for (NSInteger i = 0; i < linkRanges.count; i++) {
NSValue *value = linkRanges[i];
NSRange urlRange;
[value getValue:&urlRange];
if (urlRange.location > rangeInString.location) {
urlRange.location -= 4;
[linkRanges replaceObjectAtIndex:i withObject:[NSValue valueWithRange:urlRange]];
}
}
strongOffset -= 4;
}];
// Part 3. Compose the output.
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:s attributes:
@{
NSFontAttributeName: baseFont
}];
[attributedString beginEditing];
for (NSValue *strongValue in strongRanges) {
NSRange strongRange;
[strongValue getValue:&strongRange];
[attributedString addAttribute:NSFontAttributeName value:strongFont range:strongRange];
}
for (NSInteger i = 0; i < linkRanges.count; i++) {
NSString *urlString = linkTargets[i];
NSValue *urlRangeValue = linkRanges[i];
NSRange urlRange;
[urlRangeValue getValue:&urlRange];
[attributedString addAttributes:@{
NSLinkAttributeName: [NSURL URLWithString:urlString],
NSForegroundColorAttributeName: urlColor,
NSFontAttributeName: strongFont
} range:urlRange];
}
[attributedString endEditing];
return attributedString;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment