Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Commit

Permalink
Markdown parsing changes
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisreimann committed Jun 11, 2013
1 parent 841d2f6 commit e2730e2
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 101 deletions.
4 changes: 2 additions & 2 deletions Classes/Extensions/NSAttributedString+GHFMarkdown.h
Expand Up @@ -9,6 +9,6 @@
#import <Foundation/Foundation.h>

@interface NSAttributedString (GHFMarkdown)
+ (NSAttributedString *)attributedStringFromMarkdown:(NSString *)markdownString;
+ (NSAttributedString *)attributedStringFromMarkdown:(NSString *)markdownString attributes:(NSDictionary *)attributes;
+ (NSAttributedString *)attributedStringFromGHFMarkdown:(NSString *)markdownString;
+ (NSAttributedString *)attributedStringFromGHFMarkdown:(NSString *)markdownString attributes:(NSDictionary *)attributes;
@end
32 changes: 13 additions & 19 deletions Classes/Extensions/NSAttributedString+GHFMarkdown.m
Expand Up @@ -10,21 +10,15 @@
#import "NSAttributedString+GHFMarkdown.h"
#import "NSMutableAttributedString+GHFMarkdown.h"
#import "NSMutableString+GHFMarkdown.h"
#import "NSString+GHFMarkdown.h"

@implementation NSAttributedString (GHFMarkdown)

static NSString *const MarkdownBoldItalicRegex = @"(?:^|\\s)([*_]{3}(.+?)[*_]{3})(?:$|\\s)";
static NSString *const MarkdownBoldRegex = @"(?:^|\\s)([*_]{2}(.+?)[*_]{2})(?:$|\\s)";
static NSString *const MarkdownItalicRegex = @"(?:^|\\s)([*_]{1}(.+?)[*_]{1})(?:$|\\s)";
static NSString *const MarkdownQuotedRegex = @"(?:^>\\s?)(.+)";
static NSString *const MarkdownCodeBlockRegex = @"(?:`{3}|<pre>)(.+?)(?:`{3}|</pre>)";
static NSString *const MarkdownCodeInlineRegex = @"(?:`{1}|<code>)(.+?)(?:`{1}|</code>)";

+ (NSAttributedString *)attributedStringFromMarkdown:(NSString *)markdownString {
return [self attributedStringFromMarkdown:markdownString attributes:nil];
+ (NSAttributedString *)attributedStringFromGHFMarkdown:(NSString *)markdownString {
return [self attributedStringFromGHFMarkdown:markdownString attributes:nil];
}

+ (NSAttributedString *)attributedStringFromMarkdown:(NSString *)markdownString attributes:(NSDictionary *)attributes {
+ (NSAttributedString *)attributedStringFromGHFMarkdown:(NSString *)markdownString attributes:(NSDictionary *)attributes {
NSMutableAttributedString *output = [[NSMutableAttributedString alloc] initWithString:markdownString attributes:attributes];
NSMutableString *string = output.mutableString;
UIFont *font = [attributes valueForKey:(NSString *)kCTFontAttributeName];
Expand All @@ -40,15 +34,15 @@ + (NSAttributedString *)attributedStringFromMarkdown:(NSString *)markdownString
NSDictionary *codeAttributes = [NSDictionary dictionaryWithObjects:@[[UIFont fontWithName:@"Courier" size:fontSize], (id)[[UIColor darkGrayColor] CGColor]] forKeys:@[(NSString *)kCTFontAttributeName, (NSString *)kCTForegroundColorAttributeName]];
NSDictionary *quoteAttributes = [NSDictionary dictionaryWithObjects:@[(id)[[UIColor grayColor] CGColor]] forKeys:@[(NSString *)kCTForegroundColorAttributeName]];
CFRelease(fontRef);
[string substituteMarkdownLinks];
[string substituteMarkdownTasks];
[output substituteHeadlinesWithBaseFont:font];
[output substitutePattern:MarkdownQuotedRegex options:(NSRegularExpressionAnchorsMatchLines) andAddAttributes:quoteAttributes];
[output substitutePattern:MarkdownBoldItalicRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:boldItalicAttributes];
[output substitutePattern:MarkdownBoldRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:boldAttributes];
[output substitutePattern:MarkdownItalicRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:italicAttributes];
[output substitutePattern:MarkdownCodeBlockRegex options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators) andAddAttributes:codeAttributes];
[output substitutePattern:MarkdownCodeInlineRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:codeAttributes];
[string substituteGHFMarkdownLinks];
[string substituteGHFMarkdownTasks];
[output substituteGHFMarkdownHeadlinesWithBaseFont:font];
[output substitutePattern:GHFMarkdownQuotedRegex options:(NSRegularExpressionAnchorsMatchLines) andAddAttributes:quoteAttributes];
[output substitutePattern:GHFMarkdownBoldItalicRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:boldItalicAttributes];
[output substitutePattern:GHFMarkdownBoldRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:boldAttributes];
[output substitutePattern:GHFMarkdownItalicRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:italicAttributes];
[output substitutePattern:GHFMarkdownCodeBlockRegex options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators) andAddAttributes:codeAttributes];
[output substitutePattern:GHFMarkdownCodeInlineRegex options:(NSRegularExpressionCaseInsensitive) andAddAttributes:codeAttributes];
return output;
}

Expand Down
2 changes: 1 addition & 1 deletion Classes/Extensions/NSMutableAttributedString+GHFMarkdown.h
Expand Up @@ -10,5 +10,5 @@

@interface NSMutableAttributedString (GHFMarkdown)
- (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOptions)options andAddAttributes:(NSDictionary *)attributes;
- (void)substituteHeadlinesWithBaseFont:(UIFont *)baseFont;
- (void)substituteGHFMarkdownHeadlinesWithBaseFont:(UIFont *)baseFont;
@end
34 changes: 13 additions & 21 deletions Classes/Extensions/NSMutableAttributedString+GHFMarkdown.m
Expand Up @@ -8,11 +8,10 @@

#import <CoreText/CoreText.h>
#import "NSMutableAttributedString+GHFMarkdown.h"
#import "NSString+GHFMarkdown.h"

@implementation NSMutableAttributedString (GHFMarkdown)

static NSString *const MarkdownHeadlineRegex = @"^(#{1,6})\\s++(.+)$";

// Performs substitution in the given pattern and adds the attributes to the resulting substitution.
// I.e. you can use this to remove the stars/underscores surrounding bold and italic words.
// The substitution pattern must have either one or two matches. In case it has two, it uses the first
Expand All @@ -36,29 +35,22 @@ - (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOption
}
}

- (void)substituteHeadlinesWithBaseFont:(UIFont *)baseFont {
- (void)substituteGHFMarkdownHeadlinesWithBaseFont:(UIFont *)baseFont {
NSMutableString *string = self.mutableString;
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:MarkdownHeadlineRegex options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionAnchorsMatchLines) error:NULL];
NSArray *matches = [regex matchesInString:string options:NSMatchingReportCompletion range:NSMakeRange(0, string.length)];
if (matches.count) {
NSRegularExpression *endRegex = [[NSRegularExpression alloc] initWithPattern:@"\\s+#{1,6}\\s*$" options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionAnchorsMatchLines) error:NULL];
NSEnumerator *enumerator = [matches reverseObjectEnumerator];
NSArray *headlines = [string headlinesFromGHFMarkdown];
if (headlines.count) {
CGFloat baseSize = baseFont.pointSize;
CTFontRef baseRef = CTFontCreateWithName((__bridge CFStringRef)baseFont.fontName, baseSize, NULL);
for (NSTextCheckingResult *match in enumerator) {
NSRange headRange = [match rangeAtIndex:1];
NSRange textRange = [match rangeAtIndex:2];
CGFloat headSize = headSize = baseSize + (6 - headRange.length);
CTFontRef headRef = headRef = CTFontCreateCopyWithSymbolicTraits(baseRef, headSize, NULL, kCTFontBoldTrait, kCTFontBoldTrait);
NSEnumerator *enumerator = [headlines reverseObjectEnumerator];
for (NSDictionary *headline in enumerator) {
NSString *title = headline[@"title"];
NSRange range = [headline[@"range"] rangeValue];
NSInteger level = [headline[@"level"] integerValue];
CGFloat headSize = baseSize + (6 - level);
CTFontRef headRef = CTFontCreateCopyWithSymbolicTraits(baseRef, headSize, NULL, kCTFontBoldTrait, kCTFontBoldTrait);
NSDictionary *attributes = [NSDictionary dictionaryWithObject:CFBridgingRelease(headRef) forKey:(NSString *)kCTFontAttributeName];
NSString *text = [string substringWithRange:textRange];
NSArray *endMatches = [endRegex matchesInString:text options:NSMatchingReportCompletion range:NSMakeRange(0, text.length)];
if (endMatches.count == 1) {
text = [text substringWithRange:NSMakeRange(0, text.length - [(NSTextCheckingResult *)endMatches[0] rangeAtIndex:0].length)];
}
[string replaceCharactersInRange:match.range withString:text];
textRange = NSMakeRange(match.range.location, text.length);
[self addAttributes:attributes range:textRange];
[self addAttributes:attributes range:range];
[string replaceCharactersInRange:range withString:title];
}
CFRelease(baseRef);
}
Expand Down
7 changes: 5 additions & 2 deletions Classes/Extensions/NSMutableString+GHFMarkdown.h
Expand Up @@ -9,6 +9,9 @@
#import <Foundation/Foundation.h>

@interface NSMutableString (GHFMarkdown)
- (void)substituteMarkdownLinks;
- (void)substituteMarkdownTasks;
- (void)substituteGHFMarkdown;
- (void)substituteGHFMarkdownLinks;
- (void)substituteGHFMarkdownTasks;
- (void)substituteGHFMarkdownHeadlines;
- (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOptions)options;
@end
49 changes: 47 additions & 2 deletions Classes/Extensions/NSMutableString+GHFMarkdown.m
Expand Up @@ -12,7 +12,7 @@

@implementation NSMutableString (GHFMarkdown)

- (void)substituteMarkdownLinks {
- (void)substituteGHFMarkdownLinks {
NSArray *links = [self linksFromGHFMarkdownLinks];
if (links.count) {
NSEnumerator *enumerator = [links reverseObjectEnumerator];
Expand All @@ -24,7 +24,7 @@ - (void)substituteMarkdownLinks {
}
}

- (void)substituteMarkdownTasks {
- (void)substituteGHFMarkdownTasks {
NSArray *tasks = [self tasksFromGHFMarkdown];
if (tasks.count) {
NSEnumerator *enumerator = [tasks reverseObjectEnumerator];
Expand All @@ -36,4 +36,49 @@ - (void)substituteMarkdownTasks {
}
}

- (void)substituteGHFMarkdownHeadlines {
NSArray *headlines = [self headlinesFromGHFMarkdown];
if (headlines.count) {
NSEnumerator *enumerator = [headlines reverseObjectEnumerator];
for (NSDictionary *headline in enumerator) {
NSRange headRange = [headline[@"range"] rangeValue];
NSString *title = headline[@"title"];
[self replaceCharactersInRange:headRange withString:title];
}
}
}

// Performs substitution in the given pattern.
// I.e. you can use this to remove the stars/underscores surrounding bold and italic words.
// The substitution pattern must have either one or two matches. In case it has two, it uses the first
// match to replace its content with the content of the seconds match. If there is only one match, the
// whole match will be replaced by the matched content.
- (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOptions)options {
NSMutableString *string = self;
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:options error:NULL];
NSArray *matches = matches = [regex matchesInString:string options:NSMatchingReportCompletion range:NSMakeRange(0, string.length)];
if (matches.count) {
NSEnumerator *enumerator = [matches reverseObjectEnumerator];
for (NSTextCheckingResult *match in enumerator) {
BOOL hasSubstitutionRange = match.numberOfRanges > 2;
NSRange substituteRange = hasSubstitutionRange ? [match rangeAtIndex:1] : match.range;
NSRange textRange = hasSubstitutionRange ? [match rangeAtIndex:2] : [match rangeAtIndex:1];
NSString *text = [string substringWithRange:textRange];
[string replaceCharactersInRange:substituteRange withString:text];
}
}
}

- (void)substituteGHFMarkdown {
[self substituteGHFMarkdownLinks];
[self substituteGHFMarkdownTasks];
[self substituteGHFMarkdownHeadlines];
[self substitutePattern:GHFMarkdownQuotedRegex options:(NSRegularExpressionAnchorsMatchLines)];
[self substitutePattern:GHFMarkdownBoldItalicRegex options:(NSRegularExpressionCaseInsensitive)];
[self substitutePattern:GHFMarkdownBoldRegex options:(NSRegularExpressionCaseInsensitive)];
[self substitutePattern:GHFMarkdownItalicRegex options:(NSRegularExpressionCaseInsensitive)];
[self substitutePattern:GHFMarkdownCodeBlockRegex options:(NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators)];
[self substitutePattern:GHFMarkdownCodeInlineRegex options:(NSRegularExpressionCaseInsensitive)];
}

@end
14 changes: 14 additions & 0 deletions Classes/Extensions/NSString+GHFMarkdown.h
Expand Up @@ -8,11 +8,25 @@

#import <Foundation/Foundation.h>

extern NSString *const GHFMarkdownLinkAndImageRegex;
extern NSString *const GHFMarkdownShaRegex;
extern NSString *const GHFMarkdownUsernameRegex;
extern NSString *const GHFMarkdownIssueRegex;
extern NSString *const GHFMarkdownTaskRegex;
extern NSString *const GHFMarkdownHeadlineRegex;
extern NSString *const GHFMarkdownBoldItalicRegex;
extern NSString *const GHFMarkdownBoldRegex;
extern NSString *const GHFMarkdownItalicRegex;
extern NSString *const GHFMarkdownQuotedRegex;
extern NSString *const GHFMarkdownCodeBlockRegex;
extern NSString *const GHFMarkdownCodeInlineRegex;

@interface NSString (GHFMarkdown)
- (NSArray *)linksFromGHFMarkdownWithContextRepoId:(NSString *)repoId;
- (NSArray *)linksFromGHFMarkdownLinks;
- (NSArray *)linksFromGHFMarkdownUsernames;
- (NSArray *)linksFromGHFMarkdownShasWithContextRepoId:(NSString *)repoId;
- (NSArray *)linksFromGHFMarkdownIssuesWithContextRepoId:(NSString *)repoId;
- (NSArray *)tasksFromGHFMarkdown;
- (NSArray *)headlinesFromGHFMarkdown;
@end

0 comments on commit e2730e2

Please sign in to comment.