From 83768c973e1586bda569f9413e5772f06919533e Mon Sep 17 00:00:00 2001 From: Liam Xu Date: Fri, 25 Nov 2016 14:40:43 -0800 Subject: [PATCH 1/6] added GitHub flavored checklist(task list) markdown --- MMMarkdown.xcodeproj/project.pbxproj | 6 + Source/MMElement.h | 4 + Source/MMGenerator.m | 15 ++ Source/MMMarkdown.h | 3 +- Source/MMParser.m | 356 +++++++++++++++++++++++++++ Tests/MMChecklistTests.m | 307 +++++++++++++++++++++++ Tests/MMExtensionTests.m | 18 -- Tests/MMTestCase.h | 24 ++ 8 files changed, 714 insertions(+), 19 deletions(-) create mode 100644 Tests/MMChecklistTests.m diff --git a/MMMarkdown.xcodeproj/project.pbxproj b/MMMarkdown.xcodeproj/project.pbxproj index 7bfb7aa..f11b0ce 100644 --- a/MMMarkdown.xcodeproj/project.pbxproj +++ b/MMMarkdown.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + B9FD4F131DE8D48E00DD679D /* MMChecklistTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B9FD4F121DE8D48E00DD679D /* MMChecklistTests.m */; }; BE23E8FC1C0CF4190087F587 /* MMMarkdown.h in Headers */ = {isa = PBXBuildFile; fileRef = BE813DE214F893EB00EC9469 /* MMMarkdown.h */; }; BE23E8FD1C0CF4190087F587 /* MMMarkdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BE813DE314F893EB00EC9469 /* MMMarkdown.m */; }; BE23E8FE1C0CF4190087F587 /* MMMarkdown-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = BE813DE114F893EB00EC9469 /* MMMarkdown-Prefix.pch */; }; @@ -272,6 +273,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + B9FD4F121DE8D48E00DD679D /* MMChecklistTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMChecklistTests.m; sourceTree = ""; }; BE1DB5D41514184000B26092 /* MMSpanTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMSpanTests.m; sourceTree = ""; }; BE1DB5D915143F4200B26092 /* MMBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMBlockTests.m; sourceTree = ""; }; BE1DB5DC1515032F00B26092 /* MMLinkTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMLinkTests.m; sourceTree = ""; }; @@ -497,6 +499,7 @@ BEAAEB421549DD1700EF1EA0 /* MMImageTests.m */, BE1DB5DC1515032F00B26092 /* MMLinkTests.m */, BE813E9A15031B4000EC9469 /* MMListTests.m */, + B9FD4F121DE8D48E00DD679D /* MMChecklistTests.m */, BE1DB5D41514184000B26092 /* MMSpanTests.m */, BE813DF714F893EB00EC9469 /* MMMarkdownTests.h */, BE813DF814F893EB00EC9469 /* MMMarkdownTests.m */, @@ -913,6 +916,7 @@ BED761871B37783100687B36 /* MMImageTests.m in Sources */, BED761821B37783100687B36 /* MMBlockTests.m in Sources */, BED7618C1B37783100687B36 /* MMPHPMarkdownTests.m in Sources */, + B9FD4F131DE8D48E00DD679D /* MMChecklistTests.m in Sources */, BED7618B1B37783100687B36 /* MMMarkdownTests.m in Sources */, BED761851B37783100687B36 /* MMExtensionTests.m in Sources */, ); @@ -1346,6 +1350,7 @@ BE23E8FA1C0CF3EF0087F587 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; BE23E9181C0CF77D0087F587 /* Build configuration list for PBXNativeTarget "MMMarkdown (watchOS)" */ = { isa = XCConfigurationList; @@ -1354,6 +1359,7 @@ BE23E91A1C0CF77D0087F587 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; BE813DD014F893EB00EC9469 /* Build configuration list for PBXProject "MMMarkdown" */ = { isa = XCConfigurationList; diff --git a/Source/MMElement.h b/Source/MMElement.h index 5492d74..d98a364 100644 --- a/Source/MMElement.h +++ b/Source/MMElement.h @@ -36,6 +36,10 @@ typedef enum MMElementTypeNumberedList, MMElementTypeBulletedList, MMElementTypeListItem, + MMElementTypeNumberedChecklist, + MMElementTypeBulletedChecklist, + MMElementTypeChecklistCheckedItem, + MMElementTypeChecklistUncheckedItem, MMElementTypeCodeBlock, MMElementTypeHorizontalRule, MMElementTypeHTML, diff --git a/Source/MMGenerator.m b/Source/MMGenerator.m index 810015f..5c5d050 100644 --- a/Source/MMGenerator.m +++ b/Source/MMGenerator.m @@ -92,6 +92,14 @@ return @"
    \n"; case MMElementTypeListItem: return @"
  1. "; + case MMElementTypeBulletedChecklist: + return @"
      \n"; + case MMElementTypeNumberedChecklist: + return @"
        \n"; + case MMElementTypeChecklistCheckedItem: + return @"
      1. "; + case MMElementTypeChecklistUncheckedItem: + return @"
      2. "; case MMElementTypeBlockquote: return @"
        \n"; case MMElementTypeCodeBlock: @@ -167,6 +175,13 @@ return @"
      \n"; case MMElementTypeListItem: return @"\n"; + case MMElementTypeBulletedChecklist: + return @"
    \n"; + case MMElementTypeNumberedChecklist: + return @"
\n"; + case MMElementTypeChecklistCheckedItem: + case MMElementTypeChecklistUncheckedItem: + return @"\n"; case MMElementTypeBlockquote: return @"\n"; case MMElementTypeCodeBlock: diff --git a/Source/MMMarkdown.h b/Source/MMMarkdown.h index 08b02a3..8a9dfce 100644 --- a/Source/MMMarkdown.h +++ b/Source/MMMarkdown.h @@ -45,8 +45,9 @@ typedef NS_OPTIONS(NSUInteger, MMMarkdownExtensions) // MMMarkdownExtensionsTableCaptions = 1 << 7, MMMarkdownExtensionsTables = 1 << 8, MMMarkdownExtensionsUnderscoresInWords = 1 << 9, + MMMarkdownExtensionsChecklist = 1 << 10, // https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments - MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords, + MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords|MMMarkdownExtensionsChecklist, }; NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MMParser.m b/Source/MMParser.m index 663157b..26270db 100644 --- a/Source/MMParser.m +++ b/Source/MMParser.m @@ -240,6 +240,22 @@ - (MMElement *)_parseBlockElementWithScanner:(MMScanner *)scanner if (element) return element; + // checklist has more sophisticated leading structure + // it trumps code block. + if (self.extensions & MMMarkdownExtensionsChecklist) + { + // check list - github flavored markdown: + // + // - [ ] check item 1 + // - [X] check item 2 + // + [scanner beginTransaction]; + element = [self _parseChecklistWithScanner:scanner]; + [scanner commitTransaction:element != nil]; + if (element) + return element; + } + // Check code first because its four-space behavior trumps most else [scanner beginTransaction]; element = [self _parseCodeBlockWithScanner:scanner]; @@ -710,6 +726,345 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner return element; } + +#pragma mark - Checklist + +/** + Check the following markers: + + - [ ] + - [x] + - [X] + + [ ] + * [ ] + 1. [ ] + + @param scanner MMScanner + @return ture if the check list marker exist, false otherwise. + */ +- (BOOL)_parseChecklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked +{ + // checking + if(expectedListType == MMElementTypeNone || expectedListType == MMElementTypeBulletedChecklist) + { + [scanner beginTransaction]; + unichar nextChar = scanner.nextCharacter; + if (nextChar == '*' || nextChar == '-' || nextChar == '+') + { + [scanner advance]; + if (scanner.nextCharacter == ' ') + { + [scanner advance]; + if (scanner.nextCharacter == '[') + { + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar == ' ' || nextChar == 'X' || nextChar == 'x') + { + BOOL foundx = nextChar == 'X' || nextChar == 'x'; + [scanner advance]; + if (scanner.nextCharacter == ']') + { + [scanner advance]; + if (scanner.nextCharacter == ' ') + { + [scanner commitTransaction:YES]; + *type = MMElementTypeBulletedChecklist; + if(checked != NULL){ + *checked = foundx; + } + return YES; + } + } + } + } + } + } + [scanner commitTransaction:NO]; + } + + + // checking numbered checklist + if(expectedListType == MMElementTypeNone || expectedListType == MMElementTypeNumberedChecklist) + { + [scanner beginTransaction]; + NSUInteger numOfNums = [scanner skipCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet]]; + if (numOfNums != 0) + { + unichar nextChar = scanner.nextCharacter; + if (nextChar == '.') + { + [scanner advance]; + if (scanner.nextCharacter == ' ') + { + [scanner advance]; + if (scanner.nextCharacter == '[') + { + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar == ' ' || nextChar == 'X' || nextChar == 'x') + { + BOOL foundx = nextChar == 'X' || nextChar == 'x'; + [scanner advance]; + if (scanner.nextCharacter == ']') + { + [scanner advance]; + if (scanner.nextCharacter == ' ') + { + [scanner commitTransaction:YES]; + *type = MMElementTypeNumberedChecklist; + if(checked != NULL){ + *checked = foundx; + } + return YES; + } + } + } + } + } + } + } + [scanner commitTransaction:NO]; + } + + + return NO; +} + + + +- (MMElement *)_parseChecklistItemWithScanner:(MMScanner *)scanner listType:(MMElementType)thelistType +{ + BOOL canContainBlocks = NO; + + if ([scanner skipEmptyLines]) + { + canContainBlocks = YES; + } + + [scanner skipIndentationUpTo:7]; // Optional space + MMElementType listType = MMElementTypeNone; + BOOL checked = NO; + BOOL foundAnItem = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:&checked]; + if (!foundAnItem) + return nil; + + [scanner skipWhitespace]; + + MMElement *element = [MMElement new]; + element.type = checked ? MMElementTypeChecklistCheckedItem : MMElementTypeChecklistUncheckedItem; + + BOOL afterBlankLine = NO; + NSUInteger nestedListIndex = NSNotFound; + NSUInteger nestedListIndentation = 0; + while (!scanner.atEndOfString) + { + // Skip over any empty lines + [scanner beginTransaction]; + NSUInteger numOfEmptyLines = [scanner skipEmptyLines]; + afterBlankLine = numOfEmptyLines != 0; + + // Check for a horizontal rule + [scanner beginTransaction]; + BOOL newRule = [self _parseHorizontalRuleWithScanner:scanner] != nil; + [scanner commitTransaction:NO]; + if (newRule) + { + [scanner commitTransaction:NO]; + break; + } + + // Check for the start of a new list item + [scanner beginTransaction]; + [scanner skipIndentationUpTo:1]; + BOOL newMarker = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + [scanner commitTransaction:NO]; + if (newMarker) + { + [scanner commitTransaction:NO]; + if (afterBlankLine) + { + canContainBlocks = YES; + } + break; + } + + // Check for a nested list + [scanner beginTransaction]; + NSUInteger indentation = [scanner skipIndentationUpTo:4]; + [scanner beginTransaction]; + BOOL newList = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + [scanner commitTransaction:NO]; + if (indentation >= 2 && newList && nestedListIndex == NSNotFound) + { + [element addInnerRange:NSMakeRange(scanner.location, 0)]; + nestedListIndex = element.innerRanges.count; + [element addInnerRange:scanner.currentRange]; + + [scanner commitTransaction:YES]; + [scanner commitTransaction:YES]; + [scanner advanceToNextLine]; + nestedListIndentation = indentation; + continue; + } + [scanner commitTransaction:NO]; + + if (afterBlankLine) + { + // Must be 4 spaces past the indentation level to start a new paragraph + [scanner beginTransaction]; + NSUInteger indentation = [scanner skipIndentationUpTo:4]; + if (indentation < 4) + { + [scanner commitTransaction:NO]; + [scanner commitTransaction:NO]; + break; + } + [scanner commitTransaction:YES]; + [scanner commitTransaction:YES]; + + [element addInnerRange:NSMakeRange(scanner.location, 0)]; + canContainBlocks = YES; + } + else + { + [scanner commitTransaction:YES]; + + // Don't skip past where a nested list would start because that list + // could have its own nested list, so the whitespace will be needed. + [scanner skipIndentationUpTo:nestedListIndentation]; + } + + if (nestedListIndex != NSNotFound) + { + [element addInnerRange:scanner.currentRange]; + [scanner advanceToNextLine]; + } + else + { + [self _addTextLineToElement:element withScanner:scanner]; + } + + [scanner beginTransaction]; + [scanner skipIndentationUpTo:4]; + + // + // checklist will ignore blockquote rules + // + // if (scanner.nextCharacter == '>') + // { + // // + // // If next line is start with blockquote mark + // // then break current list parsering. + // // + // // for example: + // // + // // > 123 + // // + abc + // // + // // "+ abs" should not consider as part of blockquote + // // + // // > 234 + // // 567 + // // + // // "567" is part of the blockquote + // // + // [scanner commitTransaction:NO]; + // break; + // } + // + + [scanner commitTransaction:NO]; + + } + + element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation); + + if (element.innerRanges.count > 0) + { + if (nestedListIndex != NSNotFound) + { + NSArray *preListRanges = [element.innerRanges subarrayWithRange:NSMakeRange(0, nestedListIndex)]; + NSArray *postListRanges = [element.innerRanges subarrayWithRange:NSMakeRange(nestedListIndex, element.innerRanges.count - nestedListIndex)]; + MMScanner *preListScanner = [MMScanner scannerWithString:scanner.string lineRanges:preListRanges]; + MMScanner *postListScanner = [MMScanner scannerWithString:scanner.string lineRanges:postListRanges]; + + if (canContainBlocks) + { + element.children = [self _parseElementsWithScanner:preListScanner]; + } + else + { + element.children = [self.spanParser parseSpansInBlockElement:element withScanner:preListScanner]; + } + + element.children = [element.children arrayByAddingObjectsFromArray:[self _parseElementsWithScanner:postListScanner]]; + } + else + { + MMScanner *innerScanner = [MMScanner scannerWithString:scanner.string lineRanges:element.innerRanges]; + if (canContainBlocks) + { + element.children = [self _parseElementsWithScanner:innerScanner]; + } + else + { + element.children = [self.spanParser parseSpansInBlockElement:element withScanner:innerScanner]; + } + } + } + + return element; +} + + + +- (MMElement *)_parseChecklistWithScanner:(MMScanner *)scanner +{ + [scanner beginTransaction]; + [scanner skipIndentationUpTo:7]; // checklist allow optional 7 leading space or 1 tab. above that it's code block. + MMElementType listType = MMElementTypeNone; + BOOL hasMarker = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:MMElementTypeNone checked:NULL]; + [scanner commitTransaction:NO]; + + if (!hasMarker) + return nil; + + MMElement *element = [MMElement new]; + element.type = listType; + + while (!scanner.atEndOfString) + { + [scanner beginTransaction]; + + // Check for a horizontal rule first -- they look like a list marker + [scanner skipEmptyLines]; + MMElement *rule = [self _parseHorizontalRuleWithScanner:scanner]; + + [scanner commitTransaction:NO]; + if (rule) + break; + + [scanner beginTransaction]; + MMElement *item = [self _parseChecklistItemWithScanner:scanner listType: listType]; + if (!item) + { + [scanner commitTransaction:NO]; + break; + } + [scanner commitTransaction:YES]; + + [element addChild:item]; + } + + element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation); + + return element; +} + + +#pragma mark - List + - (BOOL)_parseListMarkerWithScanner:(MMScanner *)scanner listType:(MMListType)listType { switch (listType) @@ -932,6 +1287,7 @@ - (MMElement *)_parseListItemWithScanner:(MMScanner *)scanner listType:(MMListTy return element; } + - (MMElement *)_parseListWithScanner:(MMScanner *)scanner { [scanner beginTransaction]; diff --git a/Tests/MMChecklistTests.m b/Tests/MMChecklistTests.m new file mode 100644 index 0000000..3e071e7 --- /dev/null +++ b/Tests/MMChecklistTests.m @@ -0,0 +1,307 @@ +// +// MMChecklistTests.m +// MMMarkdown +// +// Created by Liam Xu on 25/11/2016. +// +// + +#import +#import "MMTestCase.h" + +@interface MMChecklistTests : XCTestCase + +@end + +@implementation MMChecklistTests + +#pragma mark - Tests + +- (void)testBasicChecklist_bulletedWithDashes +{ + NSString *markdown = @"- [ ] One\n" + "- [ ] Two\n" + "- [ ] Three\n"; + NSString *html = @"
  • One
  • Two
  • Three
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_bulletedWithStars +{ + NSString *markdown = @"* [ ] One\n" + "* [ ] Two\n" + "* [ ] Three\n"; + NSString *html = @"
  • One
  • Two
  • Three
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_numbered +{ + NSString *markdown = @"0. [ ] One\n" + "1. [ ] Two\n" + "2. [ ] Three\n"; + NSString *html = @"
  1. One
  2. Two
  3. Three
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_bulletedWithDashes_checked +{ + NSString *markdown = @"- [X] One\n" + "- [x] Two\n" + "- [ ] Three\n"; + NSString *html = @"
  • One
  • Two
  • Three
"; + + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_bulletedWithStars_checked +{ + NSString *markdown = @"* [ ] One\n" + "* [x] Two\n" + "* [X] Three\n"; + NSString *html = @"
  • One
  • Two
  • Three
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_numbered_checked +{ + NSString *markdown = @"0. [x] One\n" + "1. [ ] Two\n" + "2. [X] Three\n"; + NSString *html = @"
  1. One
  2. Two
  3. Three
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testBasicChecklist_bulletedWithParagraphs +{ + NSString *markdown = @"- [ ] One\n" + "\n" + "- [ ] Two\n" + "\n" + "- [ ] Three\n"; + NSString *html = @"
  • One

  • " + "
  • Two

  • " + "
  • Three

"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); +} + + +- (void)testChecklist_carriageReturn +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One\r- [ ] Two\r", @"
  • One
  • \n
  • Two
"); +} + + +- (void)testInvalidChecklist_emptylist +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]\n- [ ]\n- [ ]\n", @"
    \n
  • [ ]
  • \n
  • [ ]
  • \n
  • [ ]
  • \n
"); +} + +- (void)testValidChecklist_emptylist +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] \n- [ ] \n- [ ] \n", @"
  • \n
"); +} + +- (void)testNestedChecklists +{ + NSString *markdown = @"- [ ] 1\n" + " - [ ] A\n" + " - [ ] B\n" + "- [ ] 2\n" + "- [ ] 3\n"; + + NSString *html = @"
  • 1\n
    • A
    • B
  • " + "
  • 2
  • " + "
  • 3
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); +} + + +- (void)testNestedChecklists_checked +{ + NSString *markdown = @"- [ ] 1\n" + " - [X] A\n" + " - [x] B\n" + "- [ ] 2\n" + "- [ ] 3\n"; + + NSString *html = @"
  • 1\n
    • A
    • B
  • " + "
  • 2
  • " + "
  • 3
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); +} + + + +- (void)testNestedChecklists_multipleLevels_checked +{ + NSString *markdown = @"- [ ] 1\n" + " - [X] A\n" + " - [ ] X\n" + " - [X] Y\n" + " - [x] B\n" + "- [ ] 2\n" + "- [ ] 3\n"; + + NSString *html = @"
    " + "
  • 1\n" + "
      " + "
    • A\n" + "
        " + "
      • X
      • " + "
      • Y
      • " + "
      " + "
    • " + "
    • B
    • " + "
    " + "
  • " + "
  • 2
  • " + "
  • 3
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); +} + + +- (void)testChecklist_followedByHorizontalRule +{ + NSString *markdown = @"* [ ] One\n" + "* [ ] Two\n" + "* [ ] Three\n" + "\n" + " * * * "; + NSString *html = @"
  • One
  • " + "
  • Two
  • " + "
  • Three
" + "
"; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); +} + +- (void)testChecklist_invalidMarker +{ + // First element + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[]One\n", @"

*[]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[]One\n", @"

-[]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[]One\n", @"

+[]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[]One\n", @"

1.[]One

"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* []One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- []One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ []One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. []One\n", @"
  1. []One
"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[ ]One\n", @"

*[ ]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[ ]One\n", @"

-[ ]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[ ]One\n", @"

+[ ]One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[ ]One\n", @"

1.[ ]One

"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[] One\n", @"

*[] One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[] One\n", @"

-[] One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[] One\n", @"

+[] One

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[] One\n", @"

1.[] One

"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [ ]One\n", @"
  • [ ]One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]One\n", @"
  • [ ]One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [ ]One\n", @"
  • [ ]One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [ ]One\n", @"
  1. [ ]One
"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [] One\n", @"
  • [] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [] One\n", @"
  • [] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [] One\n", @"
  • [] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [] One\n", @"
  1. [] One
"); + + // Second element + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[ ] One\n*Two", @"

*[ ] One\n*Two

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[ ] One\n-Two", @"

-[ ] One\n-Two

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[ ] One\n+Two", @"

+[ ] One\n+Two

"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[ ] One\n1.Two", @"

1.[ ] One\n1.Two

"); + + // Check with tabs + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*\t[]One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-\t[]One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+\t[]One\n", @"
  • []One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.\t[]One\n", @"
  1. []One
"); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*\t[ ] One\n", @"
  • [ ] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-\t[ ] One\n", @"
  • [ ] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+\t[ ] One\n", @"
  • [ ] One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.\t[ ] One\n", @"
  1. [ ] One
"); + + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [\t] One\n", @"
  1. [ ] One
"); + +} + + +- (void)testChecklist_validMarker +{ + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [ ]\tOne\n", @"
  • One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]\tOne\n", @"
  • One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [ ]\tOne\n", @"
  • One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [ ]\tOne\n", @"
  1. One
"); + +#warning these suppose to be invalid checklist items according to Github + + // + // these suppose to be invalid checklist items + // but seems that _removeTabsFromString function made it works. + // + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [\t] One\n", @"
  • One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [\t] One\n", @"
  • One
"); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [\t] One\n", @"
  • One
"); +} + + +- (void)testChecklist_withLeadingSpace +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @" - [ ] One\n - [ ] Two", @"
  • One
  • Two
"); +} + +- (void)testChecklist_withBold +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One **Bold**\n- [ ] Two", @"
  • One Bold
  • Two
"); +} + +- (void)testChecklist_withCode +{ + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One `Code`\n- [ ] Two", @"
  • One Code
  • Two
"); +} + + +- (void)testChecklistFollowingAnotherChecklist +{ + NSString *markdown = + @"- [ ] A\n" + "- [ ] B\n" + "\n" + "1. [ ] 1\n" + "1. [ ] 2\n"; + NSString *HTML = + @"
    " + "
  • A
  • " + "
  • B
  • " + "
" + "
    " + "
  1. 1
  2. " + "
  3. 2
  4. " + "
"; + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, HTML); +} + +@end diff --git a/Tests/MMExtensionTests.m b/Tests/MMExtensionTests.m index e903169..3c115d6 100644 --- a/Tests/MMExtensionTests.m +++ b/Tests/MMExtensionTests.m @@ -26,24 +26,6 @@ #import "MMTestCase.h" -#define MMAssertExtendedMarkdownEqualsHTML(_extensions, markdown, html) \ - do { \ - id a1value = (markdown); \ - id a2value = (html); \ - \ - NSError *error; \ - NSString *output = [MMMarkdown HTMLStringWithMarkdown:a1value extensions:(_extensions) error:&error]; \ - NSString *html2 = a2value;\ - \ - /* Add root elements for parsing */ \ - output = [NSString stringWithFormat:@"%@", output]; \ - html2 = [NSString stringWithFormat:@"%@", html2]; \ - \ - NSXMLDocument *actual = [[NSXMLDocument alloc] initWithXMLString:output options:0 error:nil]; \ - NSXMLDocument *expected = [[NSXMLDocument alloc] initWithXMLString:html2 options:0 error:nil]; \ - XCTAssertEqualObjects(actual, expected); \ - } while(0) - @interface MMGitHubTests : MMTestCase @end diff --git a/Tests/MMTestCase.h b/Tests/MMTestCase.h index 9f30e31..2cc80f6 100644 --- a/Tests/MMTestCase.h +++ b/Tests/MMTestCase.h @@ -65,6 +65,30 @@ XCTAssertEqualObjects(actual, expected); \ } while(0) + +#define MMAssertExtendedMarkdownEqualsHTML(_extensions, markdown, html) \ + do { \ + id a1value = (markdown); \ + id a2value = (html); \ + \ + NSError *error; \ + NSString *output = [MMMarkdown HTMLStringWithMarkdown:a1value extensions:(_extensions) error:&error]; \ + NSString *html2 = a2value;\ + \ + /* Add root elements for parsing */ \ + output = [NSString stringWithFormat:@"%@", output]; \ + html2 = [NSString stringWithFormat:@"%@", html2]; \ + \ + NSError *actualError; \ + NSError *expectedError; \ + NSXMLDocument *actual = [[NSXMLDocument alloc] initWithXMLString:output options:0 error:&actualError]; \ + NSXMLDocument *expected = [[NSXMLDocument alloc] initWithXMLString:html2 options:0 error:&expectedError]; \ + XCTAssertNotNil(actual, "%@", actualError); \ + XCTAssertNotNil(expected, "%@", expectedError); \ + XCTAssertEqualObjects(actual, expected); \ + } while(0) + + @interface MMTestCase : XCTestCase - (NSString *)stringWithContentsOfFile:(NSString *)aString inDirectory:(NSString *)aDirectory; From 2d59441d3e7efe5f87473bc308674ef14dc11a1a Mon Sep 17 00:00:00 2001 From: Xu Lian Date: Sun, 4 Dec 2016 11:18:25 -0800 Subject: [PATCH 2/6] change some comments. refactor some code. --- Source/MMMarkdown.h | 4 +- Source/MMParser.m | 22 +++---- Tests/MMChecklistTests.m | 132 +++++++++++++++++++-------------------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/Source/MMMarkdown.h b/Source/MMMarkdown.h index 8a9dfce..7251f3e 100644 --- a/Source/MMMarkdown.h +++ b/Source/MMMarkdown.h @@ -45,9 +45,9 @@ typedef NS_OPTIONS(NSUInteger, MMMarkdownExtensions) // MMMarkdownExtensionsTableCaptions = 1 << 7, MMMarkdownExtensionsTables = 1 << 8, MMMarkdownExtensionsUnderscoresInWords = 1 << 9, - MMMarkdownExtensionsChecklist = 1 << 10, // https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments + MMMarkdownExtensionsTaskList = 1 << 10, // https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments - MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords|MMMarkdownExtensionsChecklist, + MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords|MMMarkdownExtensionsTaskList, }; NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MMParser.m b/Source/MMParser.m index 26270db..6c73bd3 100644 --- a/Source/MMParser.m +++ b/Source/MMParser.m @@ -242,7 +242,7 @@ - (MMElement *)_parseBlockElementWithScanner:(MMScanner *)scanner // checklist has more sophisticated leading structure // it trumps code block. - if (self.extensions & MMMarkdownExtensionsChecklist) + if (self.extensions & MMMarkdownExtensionsTaskList) { // check list - github flavored markdown: // @@ -730,7 +730,7 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner #pragma mark - Checklist /** - Check the following markers: + Check the following tasklist markers: - [ ] - [x] @@ -742,10 +742,10 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner @param scanner MMScanner @return ture if the check list marker exist, false otherwise. */ -- (BOOL)_parseChecklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked +- (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked { - // checking - if(expectedListType == MMElementTypeNone || expectedListType == MMElementTypeBulletedChecklist) + // checking unordered list marker + if (expectedListType == MMElementTypeNone || expectedListType == MMElementTypeBulletedChecklist) { [scanner beginTransaction]; unichar nextChar = scanner.nextCharacter; @@ -784,8 +784,8 @@ - (BOOL)_parseChecklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElemen } - // checking numbered checklist - if(expectedListType == MMElementTypeNone || expectedListType == MMElementTypeNumberedChecklist) + // checking numbered tasklist marker + if (expectedListType == MMElementTypeNone || expectedListType == MMElementTypeNumberedChecklist) { [scanner beginTransaction]; NSUInteger numOfNums = [scanner skipCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet]]; @@ -845,7 +845,7 @@ - (MMElement *)_parseChecklistItemWithScanner:(MMScanner *)scanner listType:(MME [scanner skipIndentationUpTo:7]; // Optional space MMElementType listType = MMElementTypeNone; BOOL checked = NO; - BOOL foundAnItem = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:&checked]; + BOOL foundAnItem = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:&checked]; if (!foundAnItem) return nil; @@ -877,7 +877,7 @@ - (MMElement *)_parseChecklistItemWithScanner:(MMScanner *)scanner listType:(MME // Check for the start of a new list item [scanner beginTransaction]; [scanner skipIndentationUpTo:1]; - BOOL newMarker = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + BOOL newMarker = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; [scanner commitTransaction:NO]; if (newMarker) { @@ -893,7 +893,7 @@ - (MMElement *)_parseChecklistItemWithScanner:(MMScanner *)scanner listType:(MME [scanner beginTransaction]; NSUInteger indentation = [scanner skipIndentationUpTo:4]; [scanner beginTransaction]; - BOOL newList = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + BOOL newList = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; [scanner commitTransaction:NO]; if (indentation >= 2 && newList && nestedListIndex == NSNotFound) { @@ -1024,7 +1024,7 @@ - (MMElement *)_parseChecklistWithScanner:(MMScanner *)scanner [scanner beginTransaction]; [scanner skipIndentationUpTo:7]; // checklist allow optional 7 leading space or 1 tab. above that it's code block. MMElementType listType = MMElementTypeNone; - BOOL hasMarker = [self _parseChecklistMarkerWithScanner:scanner listType:&listType expectedListType:MMElementTypeNone checked:NULL]; + BOOL hasMarker = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:MMElementTypeNone checked:NULL]; [scanner commitTransaction:NO]; if (!hasMarker) diff --git a/Tests/MMChecklistTests.m b/Tests/MMChecklistTests.m index 3e071e7..1670cb0 100644 --- a/Tests/MMChecklistTests.m +++ b/Tests/MMChecklistTests.m @@ -24,7 +24,7 @@ - (void)testBasicChecklist_bulletedWithDashes "- [ ] Three\n"; NSString *html = @"
  • One
  • Two
  • Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -35,7 +35,7 @@ - (void)testBasicChecklist_bulletedWithStars "* [ ] Three\n"; NSString *html = @"
  • One
  • Two
  • Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -46,7 +46,7 @@ - (void)testBasicChecklist_numbered "2. [ ] Three\n"; NSString *html = @"
  1. One
  2. Two
  3. Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -58,7 +58,7 @@ - (void)testBasicChecklist_bulletedWithDashes_checked NSString *html = @"
  • One
  • Two
  • Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -69,7 +69,7 @@ - (void)testBasicChecklist_bulletedWithStars_checked "* [X] Three\n"; NSString *html = @"
  • One
  • Two
  • Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -80,7 +80,7 @@ - (void)testBasicChecklist_numbered_checked "2. [X] Three\n"; NSString *html = @"
  1. One
  2. Two
  3. Three
"; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -95,24 +95,24 @@ - (void)testBasicChecklist_bulletedWithParagraphs "
  • Two

  • " "
  • Three

  • "; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist ,markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } - (void)testChecklist_carriageReturn { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One\r- [ ] Two\r", @"
    • One
    • \n
    • Two
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One\r- [ ] Two\r", @"
    • One
    • \n
    • Two
    "); } - (void)testInvalidChecklist_emptylist { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]\n- [ ]\n- [ ]\n", @"
      \n
    • [ ]
    • \n
    • [ ]
    • \n
    • [ ]
    • \n
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]\n- [ ]\n- [ ]\n", @"
      \n
    • [ ]
    • \n
    • [ ]
    • \n
    • [ ]
    • \n
    "); } - (void)testValidChecklist_emptylist { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] \n- [ ] \n- [ ] \n", @"
    • \n
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] \n- [ ] \n- [ ] \n", @"
    • \n
    "); } - (void)testNestedChecklists @@ -127,7 +127,7 @@ - (void)testNestedChecklists "
  • 2
  • " "
  • 3
  • "; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } @@ -143,7 +143,7 @@ - (void)testNestedChecklists_checked "
  • 2
  • " "
  • 3
  • "; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } @@ -173,7 +173,7 @@ - (void)testNestedChecklists_multipleLevels_checked "
  • 2
  • " "
  • 3
  • "; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } @@ -189,61 +189,61 @@ - (void)testChecklist_followedByHorizontalRule "
  • Three
  • " "
    "; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, html); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } - (void)testChecklist_invalidMarker { // First element - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[]One\n", @"

    *[]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[]One\n", @"

    -[]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[]One\n", @"

    +[]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[]One\n", @"

    1.[]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*[]One\n", @"

    *[]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-[]One\n", @"

    -[]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+[]One\n", @"

    +[]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.[]One\n", @"

    1.[]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* []One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- []One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ []One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. []One\n", @"
    1. []One
    "); - - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[ ]One\n", @"

    *[ ]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[ ]One\n", @"

    -[ ]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[ ]One\n", @"

    +[ ]One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[ ]One\n", @"

    1.[ ]One

    "); - - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[] One\n", @"

    *[] One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[] One\n", @"

    -[] One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[] One\n", @"

    +[] One

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[] One\n", @"

    1.[] One

    "); - - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [ ]One\n", @"
    • [ ]One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]One\n", @"
    • [ ]One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [ ]One\n", @"
    • [ ]One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [ ]One\n", @"
    1. [ ]One
    "); - - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [] One\n", @"
    • [] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [] One\n", @"
    • [] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [] One\n", @"
    • [] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [] One\n", @"
    1. [] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* []One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- []One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ []One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. []One\n", @"
    1. []One
    "); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*[ ]One\n", @"

    *[ ]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-[ ]One\n", @"

    -[ ]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+[ ]One\n", @"

    +[ ]One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.[ ]One\n", @"

    1.[ ]One

    "); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*[] One\n", @"

    *[] One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-[] One\n", @"

    -[] One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+[] One\n", @"

    +[] One

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.[] One\n", @"

    1.[] One

    "); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [ ]One\n", @"
    • [ ]One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]One\n", @"
    • [ ]One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [ ]One\n", @"
    • [ ]One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. [ ]One\n", @"
    1. [ ]One
    "); + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [] One\n", @"
    • [] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [] One\n", @"
    • [] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [] One\n", @"
    • [] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. [] One\n", @"
    1. [] One
    "); // Second element - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*[ ] One\n*Two", @"

    *[ ] One\n*Two

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-[ ] One\n-Two", @"

    -[ ] One\n-Two

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+[ ] One\n+Two", @"

    +[ ] One\n+Two

    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.[ ] One\n1.Two", @"

    1.[ ] One\n1.Two

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*[ ] One\n*Two", @"

    *[ ] One\n*Two

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-[ ] One\n-Two", @"

    -[ ] One\n-Two

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+[ ] One\n+Two", @"

    +[ ] One\n+Two

    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.[ ] One\n1.Two", @"

    1.[ ] One\n1.Two

    "); // Check with tabs - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*\t[]One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-\t[]One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+\t[]One\n", @"
    • []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.\t[]One\n", @"
    1. []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*\t[]One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-\t[]One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+\t[]One\n", @"
    • []One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.\t[]One\n", @"
    1. []One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"*\t[ ] One\n", @"
    • [ ] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"-\t[ ] One\n", @"
    • [ ] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+\t[ ] One\n", @"
    • [ ] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1.\t[ ] One\n", @"
    1. [ ] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"*\t[ ] One\n", @"
    • [ ] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"-\t[ ] One\n", @"
    • [ ] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+\t[ ] One\n", @"
    • [ ] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1.\t[ ] One\n", @"
    1. [ ] One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [\t] One\n", @"
    1. [ ] One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. [\t] One\n", @"
    1. [ ] One
    "); } @@ -251,10 +251,10 @@ - (void)testChecklist_invalidMarker - (void)testChecklist_validMarker { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [ ]\tOne\n", @"
    • One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ]\tOne\n", @"
    • One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [ ]\tOne\n", @"
    • One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"1. [ ]\tOne\n", @"
    1. One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [ ]\tOne\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]\tOne\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [ ]\tOne\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. [ ]\tOne\n", @"
    1. One
    "); #warning these suppose to be invalid checklist items according to Github @@ -262,25 +262,25 @@ - (void)testChecklist_validMarker // these suppose to be invalid checklist items // but seems that _removeTabsFromString function made it works. // - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"* [\t] One\n", @"
    • One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [\t] One\n", @"
    • One
    "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"+ [\t] One\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [\t] One\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [\t] One\n", @"
    • One
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [\t] One\n", @"
    • One
    "); } - (void)testChecklist_withLeadingSpace { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @" - [ ] One\n - [ ] Two", @"
    • One
    • Two
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @" - [ ] One\n - [ ] Two", @"
    • One
    • Two
    "); } - (void)testChecklist_withBold { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One **Bold**\n- [ ] Two", @"
    • One Bold
    • Two
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One **Bold**\n- [ ] Two", @"
    • One Bold
    • Two
    "); } - (void)testChecklist_withCode { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, @"- [ ] One `Code`\n- [ ] Two", @"
    • One Code
    • Two
    "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One `Code`\n- [ ] Two", @"
    • One Code
    • Two
    "); } @@ -301,7 +301,7 @@ - (void)testChecklistFollowingAnotherChecklist "
  • 1
  • " "
  • 2
  • " ""; - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsChecklist, markdown, HTML); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, HTML); } @end From 6e93b5c9e296436e1c21836d32b127cc6ab11304 Mon Sep 17 00:00:00 2001 From: Xu Lian Date: Sun, 4 Dec 2016 11:35:12 -0800 Subject: [PATCH 3/6] refactor comment and code. --- Source/MMParser.m | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Source/MMParser.m b/Source/MMParser.m index 6c73bd3..a340dfa 100644 --- a/Source/MMParser.m +++ b/Source/MMParser.m @@ -729,18 +729,28 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner #pragma mark - Checklist -/** + +/*! Check the following tasklist markers: - - - [ ] - - [x] - - [X] - + [ ] - * [ ] - 1. [ ] - - @param scanner MMScanner - @return ture if the check list marker exist, false otherwise. + + - [ ] text + - [x] text + - [X] text + + [ ] text + * [ ] text + 1. [ ] text + + + @param scanner + A MMScanner, Must not be nil. + @param type + A list type enum pointer. If found a valid tasklist marker, the type value will be save into this point. + @param expectedListType + Expected type enum. MMElementTypeNone value means that we are expecting any valid tasklist marker + @param checked + A nullable boolean pointer. If found a valid tasklist marker, the tasklist checking status will be save into this point. + @result + Returns a boolean value. ture if found a valid tasklist marker, false otherwise. */ - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked { @@ -833,7 +843,7 @@ - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElement -- (MMElement *)_parseChecklistItemWithScanner:(MMScanner *)scanner listType:(MMElementType)thelistType +- (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMElementType)thelistType { BOOL canContainBlocks = NO; @@ -1046,7 +1056,7 @@ - (MMElement *)_parseChecklistWithScanner:(MMScanner *)scanner break; [scanner beginTransaction]; - MMElement *item = [self _parseChecklistItemWithScanner:scanner listType: listType]; + MMElement *item = [self _parseTasklistItemWithScanner:scanner listType: listType]; if (!item) { [scanner commitTransaction:NO]; From 554e2aa227e78f0dd61db04f034dd931a9264004 Mon Sep 17 00:00:00 2001 From: Xu Lian Date: Thu, 16 May 2019 23:22:49 -0700 Subject: [PATCH 4/6] make _parseTasklistMarkerWithScanner early return style. --- Source/MMParser.m | 151 +++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 76 deletions(-) diff --git a/Source/MMParser.m b/Source/MMParser.m index a340dfa..c856e75 100644 --- a/Source/MMParser.m +++ b/Source/MMParser.m @@ -755,90 +755,89 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked { // checking unordered list marker - if (expectedListType == MMElementTypeNone || expectedListType == MMElementTypeBulletedChecklist) + if (expectedListType != MMElementTypeNone && expectedListType != MMElementTypeBulletedChecklist && expectedListType != MMElementTypeNumberedChecklist) + return NO; + + [scanner beginTransaction]; + unichar nextChar = scanner.nextCharacter; + if (nextChar != '*' && nextChar != '-' && nextChar != '+' && !isdigit(nextChar)) { - [scanner beginTransaction]; - unichar nextChar = scanner.nextCharacter; - if (nextChar == '*' || nextChar == '-' || nextChar == '+') - { + [scanner commitTransaction:NO]; + return NO; + } + + BOOL numbered = isdigit(nextChar); + + if (numbered) { + do{ [scanner advance]; - if (scanner.nextCharacter == ' ') - { - [scanner advance]; - if (scanner.nextCharacter == '[') - { - [scanner advance]; - nextChar = scanner.nextCharacter; - if (nextChar == ' ' || nextChar == 'X' || nextChar == 'x') - { - BOOL foundx = nextChar == 'X' || nextChar == 'x'; - [scanner advance]; - if (scanner.nextCharacter == ']') - { - [scanner advance]; - if (scanner.nextCharacter == ' ') - { - [scanner commitTransaction:YES]; - *type = MMElementTypeBulletedChecklist; - if(checked != NULL){ - *checked = foundx; - } - return YES; - } - } - } - } - } + nextChar = scanner.nextCharacter; + } while(isdigit(nextChar)); + + if(nextChar != '.') + { + [scanner commitTransaction:NO]; + return NO; } + } + + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar != ' ') + { [scanner commitTransaction:NO]; + return NO; } - - - // checking numbered tasklist marker - if (expectedListType == MMElementTypeNone || expectedListType == MMElementTypeNumberedChecklist) + + + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar != '[') { - [scanner beginTransaction]; - NSUInteger numOfNums = [scanner skipCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet]]; - if (numOfNums != 0) - { - unichar nextChar = scanner.nextCharacter; - if (nextChar == '.') - { - [scanner advance]; - if (scanner.nextCharacter == ' ') - { - [scanner advance]; - if (scanner.nextCharacter == '[') - { - [scanner advance]; - nextChar = scanner.nextCharacter; - if (nextChar == ' ' || nextChar == 'X' || nextChar == 'x') - { - BOOL foundx = nextChar == 'X' || nextChar == 'x'; - [scanner advance]; - if (scanner.nextCharacter == ']') - { - [scanner advance]; - if (scanner.nextCharacter == ' ') - { - [scanner commitTransaction:YES]; - *type = MMElementTypeNumberedChecklist; - if(checked != NULL){ - *checked = foundx; - } - return YES; - } - } - } - } - } - } - } [scanner commitTransaction:NO]; + return NO; } - - - return NO; + + + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar != ' ' && nextChar != 'X' && nextChar != 'x') + { + [scanner commitTransaction:NO]; + return NO; + } + + BOOL foundx = nextChar == 'X' || nextChar == 'x'; + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar != ']') + { + [scanner commitTransaction:NO]; + return NO; + } + + [scanner advance]; + nextChar = scanner.nextCharacter; + if (nextChar != ' ') + { + [scanner commitTransaction:NO]; + return NO; + } + + if(numbered){ + *type = MMElementTypeNumberedList; + } + else{ + *type = MMElementTypeBulletedChecklist; + } + + if(checked != NULL){ + *checked = foundx; + } + + [scanner commitTransaction:YES]; + + return YES; } From aafb9ab38229594c4374a31ea8339f09dc58debb Mon Sep 17 00:00:00 2001 From: Xu Lian Date: Fri, 17 May 2019 00:23:43 -0700 Subject: [PATCH 5/6] Update my name to Xu Lian. Fix some unit test issues. --- Source/MMElement.h | 4 ++- Source/MMGenerator.m | 12 ++++++-- Source/MMParser.m | 54 ++++++++++++++++++++++++---------- Tests/MMChecklistTests.m | 62 +++++++++++++++++++++++----------------- 4 files changed, 87 insertions(+), 45 deletions(-) diff --git a/Source/MMElement.h b/Source/MMElement.h index d98a364..4292b35 100644 --- a/Source/MMElement.h +++ b/Source/MMElement.h @@ -36,8 +36,10 @@ typedef enum MMElementTypeNumberedList, MMElementTypeBulletedList, MMElementTypeListItem, + MMElementTypeMinusChecklist, + MMElementTypePlusChecklist, + MMElementTypeAsteriskChecklist, MMElementTypeNumberedChecklist, - MMElementTypeBulletedChecklist, MMElementTypeChecklistCheckedItem, MMElementTypeChecklistUncheckedItem, MMElementTypeCodeBlock, diff --git a/Source/MMGenerator.m b/Source/MMGenerator.m index 5c5d050..9777213 100644 --- a/Source/MMGenerator.m +++ b/Source/MMGenerator.m @@ -92,8 +92,12 @@ return @"
      \n"; case MMElementTypeListItem: return @"
    1. "; - case MMElementTypeBulletedChecklist: - return @"
        \n"; + case MMElementTypeMinusChecklist: + return @"
          \n"; + case MMElementTypePlusChecklist: + return @"
            \n"; + case MMElementTypeAsteriskChecklist: + return @"
              \n"; case MMElementTypeNumberedChecklist: return @"
                \n"; case MMElementTypeChecklistCheckedItem: @@ -175,7 +179,9 @@ return @"
              \n"; case MMElementTypeListItem: return @"\n"; - case MMElementTypeBulletedChecklist: + case MMElementTypeMinusChecklist: + case MMElementTypePlusChecklist: + case MMElementTypeAsteriskChecklist: return @"
            \n"; case MMElementTypeNumberedChecklist: return @"
    \n"; diff --git a/Source/MMParser.m b/Source/MMParser.m index c856e75..05e3959 100644 --- a/Source/MMParser.m +++ b/Source/MMParser.m @@ -755,20 +755,23 @@ - (MMElement *)_parseHorizontalRuleWithScanner:(MMScanner *)scanner - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElementType*)type expectedListType:(MMElementType)expectedListType checked:(nullable BOOL*)checked { // checking unordered list marker - if (expectedListType != MMElementTypeNone && expectedListType != MMElementTypeBulletedChecklist && expectedListType != MMElementTypeNumberedChecklist) + if (expectedListType != MMElementTypeNone && + expectedListType != MMElementTypeMinusChecklist && + expectedListType != MMElementTypePlusChecklist && + expectedListType != MMElementTypeAsteriskChecklist && + expectedListType != MMElementTypeNumberedChecklist) return NO; [scanner beginTransaction]; unichar nextChar = scanner.nextCharacter; + unichar fistChar = nextChar; if (nextChar != '*' && nextChar != '-' && nextChar != '+' && !isdigit(nextChar)) { [scanner commitTransaction:NO]; return NO; } - BOOL numbered = isdigit(nextChar); - - if (numbered) { + if (isdigit(nextChar)) { do{ [scanner advance]; nextChar = scanner.nextCharacter; @@ -824,11 +827,20 @@ - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElement return NO; } - if(numbered){ - *type = MMElementTypeNumberedList; + if(fistChar == '-') + { + *type = MMElementTypeMinusChecklist; + } + else if(fistChar == '+') + { + *type = MMElementTypePlusChecklist; + } + else if(fistChar == '*') + { + *type = MMElementTypeAsteriskChecklist; } else{ - *type = MMElementTypeBulletedChecklist; + *type = MMElementTypeNumberedChecklist; } if(checked != NULL){ @@ -842,7 +854,7 @@ - (BOOL)_parseTasklistMarkerWithScanner:(MMScanner *)scanner listType:(MMElement -- (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMElementType)thelistType +- (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMElementType*)listType expectedListType:(MMElementType)thelistType { BOOL canContainBlocks = NO; @@ -852,12 +864,14 @@ - (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMEl } [scanner skipIndentationUpTo:7]; // Optional space - MMElementType listType = MMElementTypeNone; + MMElementType thislistType = MMElementTypeNone; BOOL checked = NO; - BOOL foundAnItem = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:&checked]; + BOOL foundAnItem = [self _parseTasklistMarkerWithScanner:scanner listType:&thislistType expectedListType:thelistType checked:&checked]; if (!foundAnItem) return nil; - + + *listType = thislistType; + [scanner skipWhitespace]; MMElement *element = [MMElement new]; @@ -886,7 +900,7 @@ - (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMEl // Check for the start of a new list item [scanner beginTransaction]; [scanner skipIndentationUpTo:1]; - BOOL newMarker = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + BOOL newMarker = [self _parseTasklistMarkerWithScanner:scanner listType:&thislistType expectedListType:thelistType checked:NULL]; [scanner commitTransaction:NO]; if (newMarker) { @@ -902,7 +916,7 @@ - (MMElement *)_parseTasklistItemWithScanner:(MMScanner *)scanner listType:(MMEl [scanner beginTransaction]; NSUInteger indentation = [scanner skipIndentationUpTo:4]; [scanner beginTransaction]; - BOOL newList = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:thelistType checked:NULL]; + BOOL newList = [self _parseTasklistMarkerWithScanner:scanner listType:&thislistType expectedListType:thelistType checked:NULL]; [scanner commitTransaction:NO]; if (indentation >= 2 && newList && nestedListIndex == NSNotFound) { @@ -1033,9 +1047,12 @@ - (MMElement *)_parseChecklistWithScanner:(MMScanner *)scanner [scanner beginTransaction]; [scanner skipIndentationUpTo:7]; // checklist allow optional 7 leading space or 1 tab. above that it's code block. MMElementType listType = MMElementTypeNone; + MMElementType nextListType = MMElementTypeNone; + BOOL hasMarker = [self _parseTasklistMarkerWithScanner:scanner listType:&listType expectedListType:MMElementTypeNone checked:NULL]; [scanner commitTransaction:NO]; - + + if (!hasMarker) return nil; @@ -1055,12 +1072,19 @@ - (MMElement *)_parseChecklistWithScanner:(MMScanner *)scanner break; [scanner beginTransaction]; - MMElement *item = [self _parseTasklistItemWithScanner:scanner listType: listType]; + MMElement *item = [self _parseTasklistItemWithScanner:scanner listType: &nextListType expectedListType:listType]; if (!item) { [scanner commitTransaction:NO]; break; } + + if(nextListType != listType) + { + [scanner commitTransaction:NO]; + break; + } + [scanner commitTransaction:YES]; [element addChild:item]; diff --git a/Tests/MMChecklistTests.m b/Tests/MMChecklistTests.m index 1670cb0..7b08f91 100644 --- a/Tests/MMChecklistTests.m +++ b/Tests/MMChecklistTests.m @@ -2,7 +2,7 @@ // MMChecklistTests.m // MMMarkdown // -// Created by Liam Xu on 25/11/2016. +// Created by Xu Lian on 25/11/2016. // // @@ -22,7 +22,7 @@ - (void)testBasicChecklist_bulletedWithDashes NSString *markdown = @"- [ ] One\n" "- [ ] Two\n" "- [ ] Three\n"; - NSString *html = @"
    • One
    • Two
    • Three
    "; + NSString *html = @"
    • One
    • Two
    • Three
    "; MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -33,7 +33,7 @@ - (void)testBasicChecklist_bulletedWithStars NSString *markdown = @"* [ ] One\n" "* [ ] Two\n" "* [ ] Three\n"; - NSString *html = @"
    • One
    • Two
    • Three
    "; + NSString *html = @"
    • One
    • Two
    • Three
    "; MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -55,7 +55,7 @@ - (void)testBasicChecklist_bulletedWithDashes_checked NSString *markdown = @"- [X] One\n" "- [x] Two\n" "- [ ] Three\n"; - NSString *html = @"
    • One
    • Two
    • Three
    "; + NSString *html = @"
    • One
    • Two
    • Three
    "; MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); @@ -67,7 +67,7 @@ - (void)testBasicChecklist_bulletedWithStars_checked NSString *markdown = @"* [ ] One\n" "* [x] Two\n" "* [X] Three\n"; - NSString *html = @"
    • One
    • Two
    • Three
    "; + NSString *html = @"
    • One
    • Two
    • Three
    "; MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList ,markdown, html); } @@ -91,7 +91,7 @@ - (void)testBasicChecklist_bulletedWithParagraphs "- [ ] Two\n" "\n" "- [ ] Three\n"; - NSString *html = @"
    • One

    • " + NSString *html = @"
      • One

      • " "
      • Two

      • " "
      • Three

      "; @@ -101,18 +101,27 @@ - (void)testBasicChecklist_bulletedWithParagraphs - (void)testChecklist_carriageReturn { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One\r- [ ] Two\r", @"
      • One
      • \n
      • Two
      "); + NSString *markdown = @"- [ ] One\r- [ ] Two\r"; + NSString *html = @"
      • One
      • \n
      • Two
      "; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } - (void)testInvalidChecklist_emptylist { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]\n- [ ]\n- [ ]\n", @"
        \n
      • [ ]
      • \n
      • [ ]
      • \n
      • [ ]
      • \n
      "); + NSString *markdown = @"- [ ]\n- [ ]\n- [ ]\n"; + NSString *html = @"
        \n
      • [ ]
      • \n
      • [ ]
      • \n
      • [ ]
      • \n
      "; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } - (void)testValidChecklist_emptylist { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] \n- [ ] \n- [ ] \n", @"
      • \n
      "); + NSString *markdown = @"- [ ] \n- [ ] \n- [ ] \n"; + NSString *html = @"
      • \n
      "; + + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, html); } - (void)testNestedChecklists @@ -123,7 +132,7 @@ - (void)testNestedChecklists "- [ ] 2\n" "- [ ] 3\n"; - NSString *html = @"
      • 1\n
        • A
        • B
      • " + NSString *html = @"
        • 1\n
          • A
          • B
        • " "
        • 2
        • " "
        • 3
        "; @@ -139,7 +148,7 @@ - (void)testNestedChecklists_checked "- [ ] 2\n" "- [ ] 3\n"; - NSString *html = @"
        • 1\n
          • A
          • B
        • " + NSString *html = @"
          • 1\n
            • A
            • B
          • " "
          • 2
          • " "
          • 3
          "; @@ -158,11 +167,11 @@ - (void)testNestedChecklists_multipleLevels_checked "- [ ] 2\n" "- [ ] 3\n"; - NSString *html = @"
            " + NSString *html = @"
              " "
            • 1\n" - "
                " + "
                  " "
                • A\n" - "
                    " + "
                      " "
                    • X
                    • " "
                    • Y
                    • " "
                    " @@ -184,7 +193,7 @@ - (void)testChecklist_followedByHorizontalRule "* [ ] Three\n" "\n" " * * * "; - NSString *html = @"
                    • One
                    • " + NSString *html = @"
                      • One
                      • " "
                      • Two
                      • " "
                      • Three
                      " "
                      "; @@ -251,9 +260,9 @@ - (void)testChecklist_invalidMarker - (void)testChecklist_validMarker { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [ ]\tOne\n", @"
                      • One
                      "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]\tOne\n", @"
                      • One
                      "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [ ]\tOne\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [ ]\tOne\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ]\tOne\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [ ]\tOne\n", @"
                      • One
                      "); MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"1. [ ]\tOne\n", @"
                      1. One
                      "); #warning these suppose to be invalid checklist items according to Github @@ -262,25 +271,25 @@ - (void)testChecklist_validMarker // these suppose to be invalid checklist items // but seems that _removeTabsFromString function made it works. // - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [\t] One\n", @"
                      • One
                      "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [\t] One\n", @"
                      • One
                      "); - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [\t] One\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"* [\t] One\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [\t] One\n", @"
                      • One
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"+ [\t] One\n", @"
                      • One
                      "); } - (void)testChecklist_withLeadingSpace { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @" - [ ] One\n - [ ] Two", @"
                      • One
                      • Two
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @" - [ ] One\n - [ ] Two", @"
                      • One
                      • Two
                      "); } - (void)testChecklist_withBold { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One **Bold**\n- [ ] Two", @"
                      • One Bold
                      • Two
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One **Bold**\n- [ ] Two", @"
                      • One Bold
                      • Two
                      "); } - (void)testChecklist_withCode { - MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One `Code`\n- [ ] Two", @"
                      • One Code
                      • Two
                      "); + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, @"- [ ] One `Code`\n- [ ] Two", @"
                      • One Code
                      • Two
                      "); } @@ -293,14 +302,15 @@ - (void)testChecklistFollowingAnotherChecklist "1. [ ] 1\n" "1. [ ] 2\n"; NSString *HTML = - @"
                        " + @"
                          " "
                        • A
                        • " - "
                        • B
                        • " + "
                        • B

                        • " "
                        " "
                          " "
                        1. 1
                        2. " "
                        3. 2
                        4. " "
                        "; + MMAssertExtendedMarkdownEqualsHTML(MMMarkdownExtensionsTaskList, markdown, HTML); } From 4cc9848eb13227b7a63d1d9f4429cb7a3dc4964c Mon Sep 17 00:00:00 2001 From: Xu Lian Date: Fri, 17 May 2019 00:33:01 -0700 Subject: [PATCH 6/6] remove defaultConfigurationName --- MMMarkdown.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/MMMarkdown.xcodeproj/project.pbxproj b/MMMarkdown.xcodeproj/project.pbxproj index f11b0ce..aa2a096 100644 --- a/MMMarkdown.xcodeproj/project.pbxproj +++ b/MMMarkdown.xcodeproj/project.pbxproj @@ -1350,7 +1350,6 @@ BE23E8FA1C0CF3EF0087F587 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; }; BE23E9181C0CF77D0087F587 /* Build configuration list for PBXNativeTarget "MMMarkdown (watchOS)" */ = { isa = XCConfigurationList; @@ -1359,7 +1358,6 @@ BE23E91A1C0CF77D0087F587 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; }; BE813DD014F893EB00EC9469 /* Build configuration list for PBXProject "MMMarkdown" */ = { isa = XCConfigurationList;