Permalink
Browse files

Markdown: Pre- and suffix quotes with linebreaks

For you, @iosdeveloper ๐Ÿฐ
  • Loading branch information...
1 parent 99e81f1 commit 618b666baa9710f52862eaa03a3583fdcb2f8798 @dennisreimann committed Jun 21, 2013
@@ -22,11 +22,14 @@ extern NSString *const GHFMarkdownItalicRegex;
extern NSString *const GHFMarkdownQuotedRegex;
extern NSString *const GHFMarkdownCodeBlockRegex;
extern NSString *const GHFMarkdownCodeInlineRegex;
+extern NSString *const GHFMarkdownSubstitutionFormat;
+extern NSString *const GHFMarkdownQuoteNewlinePadding;
NSString *GHFMarkdownMD5(NSString *string);
@interface NSString (GHFMarkdown_Private)
- (NSArray *)tasksFromGHFMarkdown;
+- (NSArray *)quotesFromGHFMarkdown;
- (NSArray *)headlinesFromGHFMarkdown;
- (NSArray *)linksFromGHFMarkdownLinks;
- (NSArray *)linksFromGHFMarkdownUsernames;
@@ -37,6 +40,7 @@ NSString *GHFMarkdownMD5(NSString *string);
@interface NSMutableString (GHFMarkdown_Private)
- (void)substituteGHFMarkdownHeadlines;
+- (void)substituteGHFMarkdownQuotes;
- (void)substituteGHFMarkdownLinks;
- (void)substituteGHFMarkdownTasks;
- (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOptions)options;
@@ -47,6 +51,7 @@ NSString *GHFMarkdownMD5(NSString *string);
@interface NSMutableAttributedString (GHFMarkdown_Private)
- (void)substituteGHFMarkdownLinksWithContextRepoId:(NSString *)contextRepoId;
- (void)substituteGHFMarkdownTasks;
+- (void)substituteGHFMarkdownQuotes;
- (void)substituteGHFMarkdownHeadlines;
- (void)substitutePattern:(NSString *)pattern options:(NSRegularExpressionOptions)options addAttributes:(NSDictionary *)attributes;
@end
@@ -17,9 +17,11 @@
NSString *const GHFMarkdownBoldItalicRegex = @"(?:^|\\s)([*_]{3}(.+?)[*_]{3})(?:$|\\s)";
NSString *const GHFMarkdownBoldRegex = @"(?:^|\\s)([*_]{2}(.+?)[*_]{2})(?:$|\\s)";
NSString *const GHFMarkdownItalicRegex = @"(?:^|\\s)([*_]{1}(.+?)[*_]{1})(?:$|\\s)";
-NSString *const GHFMarkdownQuotedRegex = @"(?:^>\\s?)(.+)";
+NSString *const GHFMarkdownQuotedRegex = @"(\n*)(^>.+?)(\n*^)[^>]";
NSString *const GHFMarkdownCodeBlockRegex = @"(?:`{3}(?:\\w+\n)?|<pre>)(.+?)(?:`{3}|</pre>)";
NSString *const GHFMarkdownCodeInlineRegex = @"(?:`{1}|<code>)(.+?)(?:`{1}|</code>)";
+NSString *const GHFMarkdownSubstitutionFormat = @"{GHFMarkdownSubstitution-%@}";
+NSString *const GHFMarkdownQuoteNewlinePadding = @"\n\n";
NSString *GHFMarkdownMD5(NSString *string) {
const char *cStr = [string UTF8String];
@@ -28,8 +28,8 @@ + (NSMutableAttributedString *)mutableAttributedStringFromGHFMarkdown:(NSString
NSDictionary *codeBlocks = [string extractAndSubstituteGHFMarkdownCodeBlocks];
[output substituteGHFMarkdownLinksWithContextRepoId:contextRepoId];
[output substituteGHFMarkdownHeadlines];
+ [output substituteGHFMarkdownQuotes];
[output substituteGHFMarkdownTasks];
- [output substitutePattern:GHFMarkdownQuotedRegex options:(NSRegularExpressionAnchorsMatchLines) addAttributes:@{@"GHFMarkdown_Quote": @YES}];
[output substitutePattern:GHFMarkdownBoldItalicRegex options:(NSRegularExpressionCaseInsensitive) addAttributes:@{@"GHFMarkdown_BoldItalic": @YES}];
[output substitutePattern:GHFMarkdownBoldRegex options:(NSRegularExpressionCaseInsensitive) addAttributes:@{@"GHFMarkdown_Bold": @YES}];
[output substitutePattern:GHFMarkdownItalicRegex options:(NSRegularExpressionCaseInsensitive) addAttributes:@{@"GHFMarkdown_Italic": @YES}];
@@ -96,6 +96,31 @@ - (void)substituteGHFMarkdownHeadlines {
}
}
+- (void)substituteGHFMarkdownQuotes {
+ NSMutableString *string = self.mutableString;
+ NSArray *quotes = [string quotesFromGHFMarkdown];
+ if (quotes.count) {
+ NSEnumerator *enumerator = [quotes reverseObjectEnumerator];
+ for (NSDictionary *quote in enumerator) {
+ NSRange newlinesBeforeRange = [quote[@"newlinesBeforeRange"] rangeValue];
+ NSRange newlinesAfterRange = [quote[@"newlinesAfterRange"] rangeValue];
+ NSRange range = [quote[@"titleRange"] rangeValue];
+ NSDictionary *attributes = [NSDictionary dictionaryWithObject:@YES forKey:@"GHFMarkdown_Quote"];
+ [self addAttributes:attributes range:range];
+ // take into account the hack in which quotesFromGHFMarkdown
+ // appends some extra newlines at the end of the string to
+ // find a quote at the end of the original string
+ BOOL isAppendedNewlines = newlinesAfterRange.location == self.length;
+ if (newlinesAfterRange.length == 1 && !isAppendedNewlines) {
+ [string replaceCharactersInRange:newlinesAfterRange withString:GHFMarkdownQuoteNewlinePadding];
+ }
+ if (newlinesBeforeRange.length == 1) {
+ [string replaceCharactersInRange:newlinesBeforeRange withString:GHFMarkdownQuoteNewlinePadding];
+ }
+ }
+ }
+}
+
// 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
@@ -13,8 +13,6 @@
@implementation NSMutableString (GHFMarkdown)
-static NSString *const GHFMarkdownSubstitutionFormat = @"{GHFMarkdownSubstitution-%@}";
-
- (void)substituteGHFMarkdownHeadlines {
NSArray *headlines = [self headlinesFromGHFMarkdown];
if (headlines.count) {
@@ -54,6 +52,27 @@ - (void)substituteGHFMarkdownTasks {
}
}
+- (void)substituteGHFMarkdownQuotes {
+ NSArray *quotes = [self quotesFromGHFMarkdown];
+ if (quotes.count) {
+ NSEnumerator *enumerator = [quotes reverseObjectEnumerator];
+ for (NSDictionary *quote in enumerator) {
+ NSRange newlinesBeforeRange = [quote[@"newlinesBeforeRange"] rangeValue];
+ NSRange newlinesAfterRange = [quote[@"newlinesAfterRange"] rangeValue];
+ // take into account the hack in which quotesFromGHFMarkdown
+ // appends some extra newlines at the end of the string to
+ // find a quote at the end of the original string
+ BOOL isAppendedNewlines = newlinesAfterRange.location == self.length;
+ if (newlinesAfterRange.length == 1 && !isAppendedNewlines) {
+ [self replaceCharactersInRange:newlinesAfterRange withString:GHFMarkdownQuoteNewlinePadding];
+ }
+ if (newlinesBeforeRange.length == 1) {
+ [self replaceCharactersInRange:newlinesBeforeRange withString:GHFMarkdownQuoteNewlinePadding];
+ }
+ }
+ }
+}
+
// 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
@@ -65,6 +65,29 @@ - (NSArray *)headlinesFromGHFMarkdown {
return results;
}
+- (NSArray *)quotesFromGHFMarkdown {
+ // hack: tappends some extra newlines at the end of the string
+ // to also find a quote at the end of the original string
+ NSString *string = [self stringByAppendingString:GHFMarkdownQuoteNewlinePadding];
+ NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:GHFMarkdownQuotedRegex options:(NSRegularExpressionAnchorsMatchLines|NSRegularExpressionDotMatchesLineSeparators) error:NULL];
+ NSArray *matches = [regex matchesInString:string options:NSMatchingReportCompletion range:NSMakeRange(0, string.length)];
+ if (!matches.count) return @[];
+ NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:matches.count];
+ for (NSTextCheckingResult *match in matches) {
+ NSRange newlinesBeforeRange = [match rangeAtIndex:1];
+ NSRange titleRange = [match rangeAtIndex:2];
+ NSRange newlinesAfterRange = [match rangeAtIndex:3];
+ NSString *title = [string substringWithRange:titleRange];
+ [results addObject:@{
+ @"title": title,
+ @"titleRange": [NSValue valueWithRange:titleRange],
+ @"newlinesAfterRange": [NSValue valueWithRange:newlinesAfterRange],
+ @"newlinesBeforeRange": [NSValue valueWithRange:newlinesBeforeRange],
+ @"range": [NSValue valueWithRange:match.range]}];
+ }
+ return results;
+}
+
- (NSArray *)linksFromGHFMarkdownLinks {
NSString *string = self;
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:GHFMarkdownLinkRegex options:NSRegularExpressionCaseInsensitive error:NULL];
@@ -161,8 +161,22 @@ - (void)testAttributedStringFromGHFMarkdownWithTasksAndLinks {
- (void)testAttributedStringFromGHFMarkdownWithQuote {
NSMutableAttributedString *actual = [NSMutableAttributedString mutableAttributedStringFromGHFMarkdown:@"This has\n\n> Quoted text\n\nas a block."];
- NSMutableAttributedString *expected = [[NSMutableAttributedString alloc] initWithString:@"This has\n\nQuoted text\n\nas a block."];
- [expected addAttributes:@{@"GHFMarkdown_Quote": @YES} range:NSMakeRange(10, 11)];
+ NSMutableAttributedString *expected = [[NSMutableAttributedString alloc] initWithString:@"This has\n\n> Quoted text\n\nas a block."];
+ [expected addAttributes:@{@"GHFMarkdown_Quote": @YES} range:NSMakeRange(10, 13)];
+ expect(actual).to.equal(expected);
+}
+
+- (void)testAttributedStringFromGHFMarkdownWithMultilineQuote {
+ NSMutableAttributedString *actual = [NSMutableAttributedString mutableAttributedStringFromGHFMarkdown:@"This has\n\n> Quoted text\n> And some more\n\nas a block."];
+ NSMutableAttributedString *expected = [[NSMutableAttributedString alloc] initWithString:@"This has\n\n> Quoted text\n> And some more\n\nas a block."];
+ [expected addAttributes:@{@"GHFMarkdown_Quote": @YES} range:NSMakeRange(10, 29)];
+ expect(actual).to.equal(expected);
+}
+
+- (void)testAttributedStringFromGHFMarkdownWithMultilineQuoteAndMissingLinebreaks {
+ NSMutableAttributedString *actual = [NSMutableAttributedString mutableAttributedStringFromGHFMarkdown:@"This has\n> Quoted text\n> And some more\nas a block."];
+ NSMutableAttributedString *expected = [[NSMutableAttributedString alloc] initWithString:@"This has\n\n> Quoted text\n> And some more\n\nas a block."];
+ [expected addAttributes:@{@"GHFMarkdown_Quote": @YES} range:NSMakeRange(10, 29)];
expect(actual).to.equal(expected);
}
@@ -44,6 +44,30 @@ - (void)testSubstituteGHFMarkdownHeadlines {
expect(actual).to.equal(@"This has a headline\n\nAnd text\n\nAnd a subline\nAnd more text.");
}
+- (void)testSubstituteGHFMarkdownQuotes {
+ NSMutableString *actual = [[NSMutableString alloc] initWithString:@"This has\n\n> Quoted text\n\nas a block."];
+ [actual substituteGHFMarkdownQuotes];
+ expect(actual).to.equal(@"This has\n\n> Quoted text\n\nas a block.");
+}
+
+- (void)testSubstituteGHFMarkdownQuotesWithMultilineQuote {
+ NSMutableString *actual = [[NSMutableString alloc] initWithString:@"This has\n\n> Quoted text\n> And some more\n\nas a block."];
+ [actual substituteGHFMarkdownQuotes];
+ expect(actual).to.equal(@"This has\n\n> Quoted text\n> And some more\n\nas a block.");
+}
+
+- (void)testSubstituteGHFMarkdownQuotesWithMultilineQuoteAndMissingLinebreaks {
+ NSMutableString *actual = [[NSMutableString alloc] initWithString:@"This has\n> Quoted text\n> And some more\nas a block."];
+ [actual substituteGHFMarkdownQuotes];
+ expect(actual).to.equal(@"This has\n\n> Quoted text\n> And some more\n\nas a block.");
+}
+
+- (void)testSubstituteGHFMarkdownQuotesAtStringBounds {
+ NSMutableString *actual = [[NSMutableString alloc] initWithString:@"> Quoted text\n\nNormal text\n> And some more"];
+ [actual substituteGHFMarkdownQuotes];
+ expect(actual).to.equal(@"> Quoted text\n\nNormal text\n\n> And some more");
+}
+
- (void)testSubstitutePatternWithSourroundingMatch {
NSMutableString *actual = [[NSMutableString alloc] initWithString:@"This **is** bold."];
NSMutableString *expected = [[NSMutableString alloc] initWithString:@"This is bold."];

0 comments on commit 618b666

Please sign in to comment.