1
0
mirror of https://github.com/lint/TFDidThatSay synced 2025-07-01 15:36:46 +00:00

Add Alien Blue support

This commit is contained in:
lint
2019-12-02 01:41:43 -05:00
parent ab771d8317
commit 2ce6b7f3f8
23 changed files with 4853 additions and 7 deletions

40
tweak/assets/MMDocument.h Normal file
View File

@ -0,0 +1,40 @@
//
// MMDocument.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class MMElement;
NS_ASSUME_NONNULL_BEGIN
@interface MMDocument : NSObject
@property (copy, nonatomic, readonly) NSString *markdown;
@property (copy, nonatomic, readonly) NSArray<MMElement *> *elements;
+ (id)documentWithMarkdown:(NSString *)markdown;
- (id)initWithMarkdown:(NSString *)markdown;
@end
NS_ASSUME_NONNULL_END

70
tweak/assets/MMDocument.m Normal file
View File

@ -0,0 +1,70 @@
//
// MMDocument.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMDocument.h"
#import "MMDocument_Private.h"
@interface MMDocument ()
@property (copy, nonatomic) NSArray *elements;
@end
@implementation MMDocument
{
NSMutableArray *_elements;
}
#pragma mark - Public Methods
+ (id)documentWithMarkdown:(NSString *)markdown
{
return [[self.class alloc] initWithMarkdown:markdown];
}
- (id)initWithMarkdown:(NSString *)markdown
{
self = [super init];
if (self)
{
_markdown = markdown;
_elements = [NSMutableArray new];
}
return self;
}
#pragma mark - Private Methods
- (void)addElement:(MMElement *)anElement
{
[self willChangeValueForKey:@"elements"];
[_elements addObject:anElement];
[self didChangeValueForKey:@"elements"];
}
@end

View File

@ -0,0 +1,39 @@
//
// MMDocument_Private.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMDocument.h"
@class MMElement;
NS_ASSUME_NONNULL_BEGIN
@interface MMDocument (MMDocumentPrivate)
@property (copy, nonatomic) NSArray<MMElement *> *elements;
- (void)addElement:(MMElement *)anElement;
@end
NS_ASSUME_NONNULL_END

92
tweak/assets/MMElement.h Normal file
View File

@ -0,0 +1,92 @@
//
// MMElement.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class MMElement;
typedef enum
{
MMElementTypeNone,
MMElementTypeHeader,
MMElementTypeParagraph,
MMElementTypeBlockquote,
MMElementTypeNumberedList,
MMElementTypeBulletedList,
MMElementTypeListItem,
MMElementTypeCodeBlock,
MMElementTypeHorizontalRule,
MMElementTypeHTML,
MMElementTypeLineBreak,
MMElementTypeStrikethrough,
MMElementTypeStrong,
MMElementTypeEm,
MMElementTypeCodeSpan,
MMElementTypeImage,
MMElementTypeLink,
MMElementTypeMailTo,
MMElementTypeDefinition,
MMElementTypeEntity,
MMElementTypeTable,
MMElementTypeTableHeader,
MMElementTypeTableHeaderCell,
MMElementTypeTableRow,
MMElementTypeTableRowCell,
} MMElementType;
typedef NS_ENUM(NSInteger, MMTableCellAlignment)
{
MMTableCellAlignmentNone,
MMTableCellAlignmentLeft,
MMTableCellAlignmentCenter,
MMTableCellAlignmentRight,
};
@interface MMElement : NSObject
@property (assign, nonatomic) NSRange range;
@property (assign, nonatomic) MMElementType type;
@property (copy, nonatomic) NSArray *innerRanges;
@property (assign, nonatomic) MMTableCellAlignment alignment;
@property (assign, nonatomic) NSUInteger level;
@property (copy, nonatomic) NSString *href;
@property (copy, nonatomic) NSString *title;
@property (copy, nonatomic) NSString *identifier;
@property (copy, nonatomic) NSString *stringValue;
@property (weak, nonatomic) MMElement *parent;
@property (copy, nonatomic) NSArray<MMElement *> *children;
@property (copy, nonatomic) NSString *language;
- (void)addInnerRange:(NSRange)aRange;
- (void)removeLastInnerRange;
- (void)addChild:(MMElement *)aChild;
- (void)removeChild:(MMElement *)aChild;
- (MMElement *)removeLastChild;
@end

171
tweak/assets/MMElement.m Normal file
View File

@ -0,0 +1,171 @@
//
// MMElement.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMElement.h"
static NSString * __MMStringFromElementType(MMElementType type)
{
switch (type)
{
case MMElementTypeNone:
return @"none";
case MMElementTypeHeader:
return @"header";
case MMElementTypeParagraph:
return @"paragraph";
case MMElementTypeBlockquote:
return @"blockquote";
case MMElementTypeNumberedList:
return @"ol";
case MMElementTypeBulletedList:
return @"ul";
case MMElementTypeListItem:
return @"li";
case MMElementTypeCodeBlock:
return @"code";
case MMElementTypeHorizontalRule:
return @"hr";
case MMElementTypeHTML:
return @"html";
case MMElementTypeLineBreak:
return @"br";
case MMElementTypeStrikethrough:
return @"del";
case MMElementTypeStrong:
return @"strong";
case MMElementTypeEm:
return @"em";
case MMElementTypeCodeSpan:
return @"code";
case MMElementTypeImage:
return @"image";
case MMElementTypeLink:
return @"link";
case MMElementTypeMailTo:
return @"mailto";
case MMElementTypeEntity:
return @"entity";
case MMElementTypeDefinition:
return @"definition";
default:
return @"unknown";
}
}
@implementation MMElement
{
NSMutableArray *_innerRanges;
NSMutableArray *_children;
}
#pragma mark - NSObject
- (id)init
{
self = [super init];
if (self)
{
_innerRanges = [NSMutableArray new];
_children = [NSMutableArray new];
}
return self;
}
- (void)dealloc
{
[self.children makeObjectsPerformSelector:@selector(setParent:) withObject:nil];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; type=%@; range=%@>",
NSStringFromClass(self.class), self, __MMStringFromElementType(self.type), NSStringFromRange(self.range)];
}
#pragma mark - Public Methods
- (void)addInnerRange:(NSRange)aRange
{
[self willChangeValueForKey:@"innerRanges"];
[_innerRanges addObject:[NSValue valueWithRange:aRange]];
[self didChangeValueForKey:@"innerRanges"];
}
- (void)removeLastInnerRange
{
[self willChangeValueForKey:@"innerRanges"];
[_innerRanges removeLastObject];
[self didChangeValueForKey:@"innerRanges"];
}
- (void)addChild:(MMElement *)aChild
{
[self willChangeValueForKey:@"children"];
[_children addObject:aChild];
aChild.parent = self;
[self didChangeValueForKey:@"children"];
}
- (void)removeChild:(MMElement *)aChild
{
[self willChangeValueForKey:@"children"];
[_children removeObjectIdenticalTo:aChild];
aChild.parent = nil;
[self didChangeValueForKey:@"children"];
}
- (MMElement *)removeLastChild
{
MMElement *child = [self.children lastObject];
[_children removeLastObject];
return child;
}
#pragma mark - Public Properties
- (void)setInnerRanges:(NSArray *)innerRanges
{
_innerRanges = [innerRanges mutableCopy];
}
- (void)setChildren:(NSArray *)children
{
for (MMElement *child in _children) {
child.parent = nil;
}
_children = [children mutableCopy];
for (MMElement *child in _children) {
child.parent = self;
}
}
@end

View File

@ -0,0 +1,37 @@
//
// MMGenerator.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class MMDocument;
NS_ASSUME_NONNULL_BEGIN
@interface MMGenerator : NSObject
- (NSString *)generateHTML:(MMDocument *)aDocument;
@end
NS_ASSUME_NONNULL_END

282
tweak/assets/MMGenerator.m Normal file
View File

@ -0,0 +1,282 @@
//
// MMGenerator.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMGenerator.h"
#import "MMDocument.h"
#import "MMElement.h"
// This value is used to estimate the length of the HTML output. The length of the markdown document
// is multplied by it to create an NSMutableString with an initial capacity.
static const Float64 kHTMLDocumentLengthMultiplier = 1.25;
static NSString * __HTMLEscapedString(NSString *aString)
{
NSMutableString *result = [aString mutableCopy];
[result replaceOccurrencesOfString:@"&"
withString:@"&amp;"
options:NSLiteralSearch
range:NSMakeRange(0, result.length)];
[result replaceOccurrencesOfString:@"\""
withString:@"&quot;"
options:NSLiteralSearch
range:NSMakeRange(0, result.length)];
return result;
}
static NSString *__obfuscatedEmailAddress(NSString *anAddress)
{
NSMutableString *result = [NSMutableString new];
NSString *(^decimal)(unichar c) = ^(unichar c){ return [NSString stringWithFormat:@"&#%d;", c]; };
NSString *(^hex)(unichar c) = ^(unichar c){ return [NSString stringWithFormat:@"&#x%x;", c]; };
NSString *(^raw)(unichar c) = ^(unichar c){ return [NSString stringWithCharacters:&c length:1]; };
NSArray *encoders = @[ decimal, hex, raw ];
for (NSUInteger idx=0; idx<anAddress.length; idx++)
{
unichar character = [anAddress characterAtIndex:idx];
NSString *(^encoder)(unichar c);
if (character == '@')
{
// Make sure that the @ gets encoded
encoder = [encoders objectAtIndex:arc4random_uniform(2)];
}
else
{
int r = arc4random_uniform(100);
encoder = encoders[(r >= 90) ? 2 : (r >= 45) ? 1 : 0];
}
[result appendString:encoder(character)];
}
return result;
}
static NSString * __HTMLStartTagForElement(MMElement *anElement)
{
switch (anElement.type)
{
case MMElementTypeHeader:
return [NSString stringWithFormat:@"<h%u>", (unsigned int)anElement.level];
case MMElementTypeParagraph:
return @"<p>";
case MMElementTypeBulletedList:
return @"<ul>\n";
case MMElementTypeNumberedList:
return @"<ol>\n";
case MMElementTypeListItem:
return @"<li>";
case MMElementTypeBlockquote:
return @"<blockquote>\n";
case MMElementTypeCodeBlock:
return anElement.language ? [NSString stringWithFormat:@"<pre><code class=\"%@\">", anElement.language] : @"<pre><code>";
case MMElementTypeLineBreak:
return @"<br />";
case MMElementTypeHorizontalRule:
return @"\n<hr />\n";
case MMElementTypeStrikethrough:
return @"<del>";
case MMElementTypeStrong:
return @"<strong>";
case MMElementTypeEm:
return @"<em>";
case MMElementTypeCodeSpan:
return @"<code>";
case MMElementTypeImage:
if (anElement.title != nil)
{
return [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\" title=\"%@\" />",
__HTMLEscapedString(anElement.href),
__HTMLEscapedString(anElement.stringValue),
__HTMLEscapedString(anElement.title)];
}
return [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\" />",
__HTMLEscapedString(anElement.href),
__HTMLEscapedString(anElement.stringValue)];
case MMElementTypeLink:
if (anElement.title != nil)
{
return [NSString stringWithFormat:@"<a title=\"%@\" href=\"%@\">",
__HTMLEscapedString(anElement.title), __HTMLEscapedString(anElement.href)];
}
return [NSString stringWithFormat:@"<a href=\"%@\">", __HTMLEscapedString(anElement.href)];
case MMElementTypeMailTo:
return [NSString stringWithFormat:@"<a href=\"%@\">%@</a>",
__obfuscatedEmailAddress([NSString stringWithFormat:@"mailto:%@", anElement.href]),
__obfuscatedEmailAddress(anElement.href)];
case MMElementTypeEntity:
return anElement.stringValue;
case MMElementTypeTable:
return @"<table>";
case MMElementTypeTableHeader:
return @"<thead><tr>";
case MMElementTypeTableHeaderCell:
return anElement.alignment == MMTableCellAlignmentCenter ? @"<th align='center'>"
: anElement.alignment == MMTableCellAlignmentLeft ? @"<th align='left'>"
: anElement.alignment == MMTableCellAlignmentRight ? @"<th align='right'>"
: @"<th>";
case MMElementTypeTableRow:
return @"<tr>";
case MMElementTypeTableRowCell:
return anElement.alignment == MMTableCellAlignmentCenter ? @"<td align='center'>"
: anElement.alignment == MMTableCellAlignmentLeft ? @"<td align='left'>"
: anElement.alignment == MMTableCellAlignmentRight ? @"<td align='right'>"
: @"<td>";
default:
return nil;
}
}
static NSString * __HTMLEndTagForElement(MMElement *anElement)
{
switch (anElement.type)
{
case MMElementTypeHeader:
return [NSString stringWithFormat:@"</h%u>\n", (unsigned int)anElement.level];
case MMElementTypeParagraph:
return @"</p>\n";
case MMElementTypeBulletedList:
return @"</ul>\n";
case MMElementTypeNumberedList:
return @"</ol>\n";
case MMElementTypeListItem:
return @"</li>\n";
case MMElementTypeBlockquote:
return @"</blockquote>\n";
case MMElementTypeCodeBlock:
return @"</code></pre>\n";
case MMElementTypeStrikethrough:
return @"</del>";
case MMElementTypeStrong:
return @"</strong>";
case MMElementTypeEm:
return @"</em>";
case MMElementTypeCodeSpan:
return @"</code>";
case MMElementTypeLink:
return @"</a>";
case MMElementTypeTable:
return @"</tbody></table>";
case MMElementTypeTableHeader:
return @"</tr></thead><tbody>";
case MMElementTypeTableHeaderCell:
return @"</th>";
case MMElementTypeTableRow:
return @"</tr>";
case MMElementTypeTableRowCell:
return @"</td>";
default:
return nil;
}
}
@interface MMGenerator ()
- (void) _generateHTMLForElement:(MMElement *)anElement
inDocument:(MMDocument *)aDocument
HTML:(NSMutableString *)theHTML
location:(NSUInteger *)aLocation;
@end
@implementation MMGenerator
#pragma mark - Public Methods
- (NSString *)generateHTML:(MMDocument *)aDocument
{
NSString *markdown = aDocument.markdown;
NSUInteger location = 0;
NSUInteger length = markdown.length;
NSMutableString *HTML = [NSMutableString stringWithCapacity:length * kHTMLDocumentLengthMultiplier];
for (MMElement *element in aDocument.elements)
{
if (element.type == MMElementTypeHTML)
{
[HTML appendString:[aDocument.markdown substringWithRange:element.range]];
}
else
{
[self _generateHTMLForElement:element
inDocument:aDocument
HTML:HTML
location:&location];
}
}
return HTML;
}
#pragma mark - Private Methods
- (void)_generateHTMLForElement:(MMElement *)anElement
inDocument:(MMDocument *)aDocument
HTML:(NSMutableString *)theHTML
location:(NSUInteger *)aLocation
{
NSString *startTag = __HTMLStartTagForElement(anElement);
NSString *endTag = __HTMLEndTagForElement(anElement);
if (startTag)
[theHTML appendString:startTag];
for (MMElement *child in anElement.children)
{
if (child.type == MMElementTypeNone)
{
NSString *markdown = aDocument.markdown;
if (child.range.length == 0)
{
[theHTML appendString:@"\n"];
}
else
{
[theHTML appendString:[markdown substringWithRange:child.range]];
}
}
else if (child.type == MMElementTypeHTML)
{
[theHTML appendString:[aDocument.markdown substringWithRange:child.range]];
}
else
{
[self _generateHTMLForElement:child
inDocument:aDocument
HTML:theHTML
location:aLocation];
}
}
if (endTag)
[theHTML appendString:endTag];
}
@end

View File

@ -0,0 +1,40 @@
//
// MMHTMLParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class MMElement;
@class MMScanner;
NS_ASSUME_NONNULL_BEGIN
@interface MMHTMLParser : NSObject
- (nullable MMElement *)parseBlockTagWithScanner:(MMScanner *)scanner;
- (nullable MMElement *)parseCommentWithScanner:(MMScanner *)scanner;
- (nullable MMElement *)parseInlineTagWithScanner:(MMScanner *)scanner;
@end
NS_ASSUME_NONNULL_END

324
tweak/assets/MMHTMLParser.m Normal file
View File

@ -0,0 +1,324 @@
//
// MMHTMLParser.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMHTMLParser.h"
#import "MMElement.h"
#import "MMScanner.h"
@implementation MMHTMLParser
#pragma mark - Public Methods
- (MMElement *)parseBlockTagWithScanner:(MMScanner *)scanner
{
[scanner beginTransaction];
MMElement *element = [self _parseStrictBlockTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
return element;
return [self _parseLenientBlockTagWithScanner:scanner];
}
- (MMElement *)parseCommentWithScanner:(MMScanner *)scanner
{
if (![scanner matchString:@"<!--"])
return nil;
NSCharacterSet *setToSkip = [[NSCharacterSet characterSetWithCharactersInString:@"-"] invertedSet];
while (!scanner.atEndOfString)
{
if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
{
[scanner skipCharactersFromSet:setToSkip];
if ([scanner matchString:@"-->"])
{
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}
[scanner advance];
}
}
return nil;
}
- (MMElement *)parseInlineTagWithScanner:(MMScanner *)scanner
{
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
if (scanner.nextCharacter == '/')
[scanner advance];
NSRange tagNameRange = [self _parseNameWithScanner:scanner];
if (tagNameRange.length == 0)
return nil;
[self _parseAttributesWithScanner:scanner];
[scanner skipWhitespace];
if (scanner.nextCharacter == '/')
[scanner advance];
if (scanner.nextCharacter != '>')
return nil;
[scanner advance];
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
element.stringValue = [scanner.string substringWithRange:tagNameRange];
return element;
}
#pragma mark - Private Methods
- (MMElement *)_parseStrictBlockTagWithScanner:(MMScanner *)scanner
{
// which starts with a '<'
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
NSSet *htmlBlockTags = [NSSet setWithObjects:
@"p", @"div", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6",
@"blockquote", @"pre", @"table", @"dl", @"ol", @"ul",
@"script", @"noscript", @"form", @"fieldset", @"iframe",
@"math", @"ins", @"del", nil];
NSString *tagName = [scanner nextWord];
if (![htmlBlockTags containsObject:tagName])
return nil;
scanner.location += tagName.length;
[self _parseAttributesWithScanner:scanner];
[scanner skipWhitespace];
if (scanner.nextCharacter != '>')
return nil;
[scanner advance];
NSCharacterSet *boringChars = [[NSCharacterSet characterSetWithCharactersInString:@"<"] invertedSet];
while (1)
{
if (scanner.atEndOfString)
return nil;
[scanner skipCharactersFromSet:boringChars];
if (scanner.atEndOfLine)
{
[scanner advanceToNextLine];
continue;
}
[scanner beginTransaction];
if ([self _parseEndTag:tagName withScanner:scanner])
{
[scanner commitTransaction:YES];
break;
}
[scanner commitTransaction:NO];
MMElement *element;
[scanner beginTransaction];
element = [self _parseStrictBlockTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
[scanner beginTransaction];
element = [self parseCommentWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
[scanner beginTransaction];
element = [self parseInlineTagWithScanner:scanner];
[scanner commitTransaction:element != nil];
if (element)
continue;
return nil;
}
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}
- (BOOL)_parseEndTag:(NSString *)tagName withScanner:(MMScanner *)scanner
{
if (scanner.nextCharacter != '<')
return NO;
[scanner advance];
if (scanner.nextCharacter != '/')
return NO;
[scanner advance];
[scanner skipWhitespace];
if (![scanner matchString:tagName])
return NO;
[scanner skipWhitespace];
if (scanner.nextCharacter != '>')
return NO;
[scanner advance];
return YES;
}
- (MMElement *)_parseLenientBlockTagWithScanner:(MMScanner *)scanner
{
// which starts with a '<'
if (scanner.nextCharacter != '<')
return nil;
[scanner advance];
NSSet *htmlBlockTags = [NSSet setWithObjects:
@"p", @"div", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6",
@"blockquote", @"pre", @"table", @"dl", @"ol", @"ul",
@"script", @"noscript", @"form", @"fieldset", @"iframe",
@"math", @"ins", @"del", nil];
NSString *tagName = scanner.nextWord;
if (![htmlBlockTags containsObject:tagName])
return nil;
scanner.location += tagName.length;
// Find a '>'
while (scanner.nextCharacter != '>')
{
if (scanner.atEndOfString)
return nil;
else if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
[scanner advance];
}
// Skip lines until we come across a blank line
while (!scanner.atEndOfLine)
{
[scanner advanceToNextLine];
}
MMElement *element = [MMElement new];
element.type = MMElementTypeHTML;
element.range = NSMakeRange(scanner.startLocation, scanner.location-scanner.startLocation);
return element;
}
- (NSRange)_parseNameWithScanner:(MMScanner *)scanner
{
NSMutableCharacterSet *nameSet = [NSMutableCharacterSet alphanumericCharacterSet];
[nameSet addCharactersInString:@":-"];
NSRange result = NSMakeRange(scanner.location, 0);
result.length = [scanner skipCharactersFromSet:nameSet];
return result;
}
- (BOOL)_parseStringWithScanner:(MMScanner *)scanner
{
unichar nextChar = [scanner nextCharacter];
if (nextChar != '"' && nextChar != '\'')
return NO;
[scanner advance];
while (scanner.nextCharacter != nextChar)
{
if (scanner.atEndOfString)
return NO;
else if (scanner.atEndOfLine)
[scanner advanceToNextLine];
else
[scanner advance];
}
// skip over the closing quotation mark
[scanner advance];
return YES;
}
- (BOOL)_parseAttributeValueWithScanner:(MMScanner *)scanner
{
NSMutableCharacterSet *characters = [[NSCharacterSet.whitespaceCharacterSet invertedSet] mutableCopy];
[characters removeCharactersInString:@"\"'=><`"];
return [scanner skipCharactersFromSet:characters] > 0;
}
- (void)_parseAttributesWithScanner:(MMScanner *)scanner
{
while ([scanner skipWhitespaceAndNewlines] > 0)
{
NSRange range;
range = [self _parseNameWithScanner:scanner];
if (range.length == 0)
break;
[scanner beginTransaction];
[scanner skipWhitespace];
if (scanner.nextCharacter == '=')
{
[scanner commitTransaction:YES];
[scanner advance];
[scanner skipWhitespace];
if ([self _parseStringWithScanner:scanner])
;
else if ([self _parseAttributeValueWithScanner:scanner])
;
else
break;
}
else
{
[scanner commitTransaction:NO];
}
}
}
@end

View File

@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'MMMarkdown' target in the 'MMMarkdown' project
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

82
tweak/assets/MMMarkdown.h Normal file
View File

@ -0,0 +1,82 @@
//
// MMMarkdown.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
//! Project version number for MMMarkdown.
FOUNDATION_EXPORT double MMMarkdownVersionNumber;
//! Project version string for MMMarkdown.
FOUNDATION_EXPORT const unsigned char MMMarkdownVersionString[];
typedef NS_OPTIONS(NSUInteger, MMMarkdownExtensions)
{
MMMarkdownExtensionsNone = 0,
MMMarkdownExtensionsAutolinkedURLs = 1 << 0,
// MMMarkdownExtensionsCrossReferences = 1 << 1,
// MMMarkdownExtensionsCustomAttributes = 1 << 2,
MMMarkdownExtensionsFencedCodeBlocks = 1 << 3,
// MMMarkdownExtensionsFootnotes = 1 << 4,
MMMarkdownExtensionsHardNewlines = 1 << 5,
MMMarkdownExtensionsStrikethroughs = 1 << 6,
// MMMarkdownExtensionsTableCaptions = 1 << 7,
MMMarkdownExtensionsTables = 1 << 8,
MMMarkdownExtensionsUnderscoresInWords = 1 << 9,
MMMarkdownExtensionsGitHubFlavored = MMMarkdownExtensionsAutolinkedURLs|MMMarkdownExtensionsFencedCodeBlocks|MMMarkdownExtensionsHardNewlines|MMMarkdownExtensionsStrikethroughs|MMMarkdownExtensionsTables|MMMarkdownExtensionsUnderscoresInWords,
};
NS_ASSUME_NONNULL_BEGIN
@interface MMMarkdown : NSObject
/*!
Convert a Markdown string to HTML.
@param string
A Markdown string. Must not be nil.
@param error
Out parameter used if an error occurs while parsing the Markdown. May be NULL.
@result
Returns an HTML string.
*/
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string error:(NSError * __autoreleasing * _Nullable)error;
/*!
Convert a Markdown string to HTML.
@param string
A Markdown string. Must not be nil.
@param extensions
The extensions to enable.
@param error
Out parameter used if an error occurs while parsing the Markdown. May be NULL.
@result
Returns an HTML string.
*/
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string extensions:(MMMarkdownExtensions)extensions error:(NSError * __autoreleasing * _Nullable)error;
@end
NS_ASSUME_NONNULL_END

73
tweak/assets/MMMarkdown.m Normal file
View File

@ -0,0 +1,73 @@
//
// MMMarkdown.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMMarkdown.h"
#import "MMParser.h"
#import "MMGenerator.h"
@implementation MMMarkdown
#pragma mark - Public Methods
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string error:(__autoreleasing NSError **)error
{
return [self HTMLStringWithMarkdown:string extensions:MMMarkdownExtensionsNone fromSelector:_cmd error:error];
}
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string extensions:(MMMarkdownExtensions)extensions error:(NSError *__autoreleasing *)error
{
return [self HTMLStringWithMarkdown:string extensions:extensions fromSelector:_cmd error:error];
}
#pragma mark - Private Methods
+ (NSString *)HTMLStringWithMarkdown:(NSString *)string
extensions:(MMMarkdownExtensions)extensions
fromSelector:(SEL)selector
error:(__autoreleasing NSError **)error
{
if (string == nil)
{
NSString *reason = [NSString stringWithFormat:@"[%@ %@]: nil argument for markdown",
NSStringFromClass(self.class), NSStringFromSelector(selector)];
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
}
if (string.length == 0)
return @"";
MMParser *parser = [[MMParser alloc] initWithExtensions:extensions];
MMGenerator *generator = [MMGenerator new];
MMDocument *document = [parser parseMarkdown:string error:error];
return [generator generateHTML:document];
}
@end

41
tweak/assets/MMParser.h Normal file
View File

@ -0,0 +1,41 @@
//
// MMParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
#import "MMMarkdown.h"
@class MMDocument;
NS_ASSUME_NONNULL_BEGIN
@interface MMParser : NSObject
- (id)initWithExtensions:(MMMarkdownExtensions)extensions;
- (MMDocument *)parseMarkdown:(NSString *)markdown error:(NSError * __autoreleasing * _Nullable)error;
@end
NS_ASSUME_NONNULL_END

1321
tweak/assets/MMParser.m Normal file

File diff suppressed because it is too large Load Diff

79
tweak/assets/MMScanner.h Normal file
View File

@ -0,0 +1,79 @@
//
// MMScanner.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@interface MMScanner : NSObject
// Constant info
@property (strong, nonatomic, readonly) NSString *string;
@property (copy, nonatomic, readonly) NSArray *lineRanges;
// Changing
@property (assign, nonatomic, readonly) NSUInteger startLocation;
@property (assign, nonatomic, readonly) NSRange currentRange;
// Settable
@property (assign, nonatomic) NSUInteger location;
+ (id)scannerWithString:(NSString *)aString;
- (id)initWithString:(NSString *)aString;
+ (id)scannerWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges;
- (id)initWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges;
- (void)beginTransaction;
- (void)commitTransaction:(BOOL)shouldSave;
- (BOOL)atBeginningOfLine;
- (BOOL)atEndOfLine;
- (BOOL)atEndOfString;
- (unichar)previousCharacter;
- (unichar)nextCharacter;
- (NSString *)previousWord;
- (NSString *)nextWord;
- (NSString *)previousWordWithCharactersFromSet:(NSCharacterSet *)set;
- (NSString *)nextWordWithCharactersFromSet:(NSCharacterSet *)set;
- (void)advance;
- (void)advanceToNextLine;
- (BOOL)matchString:(NSString *)string;
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet;
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet max:(NSUInteger)maxToSkip;
- (NSUInteger)skipEmptyLines;
- (NSUInteger)skipIndentationUpTo:(NSUInteger)maxSpacesToSkip;
- (NSUInteger)skipNestedBracketsWithDelimiter:(unichar)delimiter;
- (NSUInteger)skipToEndOfLine;
- (NSUInteger)skipToLastCharacterOfLine;
- (NSUInteger)skipWhitespace;
- (NSUInteger)skipWhitespaceAndNewlines;
@end

499
tweak/assets/MMScanner.m Normal file
View File

@ -0,0 +1,499 @@
//
// MMScanner.m
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "MMScanner.h"
static NSString *__delimitersForCharacter(unichar character)
{
switch (character)
{
case '[':
case ']':
return @"[]";
case '(':
case ')':
return @"()";
case '<':
case '>':
return @"<>";
case '{':
case '}':
return @"{}";
default:
[NSException raise:@"Invalid delimiter character"
format:@"Character '%C' is not a valid delimiter", character];
return nil;
}
}
@interface MMScanner ()
@property (assign, nonatomic) NSUInteger startLocation;
@property (assign, nonatomic) NSRange currentRange;
@property (assign, nonatomic, readonly) NSRange currentLineRange;
@property (assign, nonatomic) NSUInteger rangeIndex;
@property (strong, nonatomic, readonly) NSMutableArray *transactions;
@end
@implementation MMScanner
#pragma mark - Public Methods
+ (id)scannerWithString:(NSString *)aString
{
return [[self.class alloc] initWithString:aString];
}
- (id)initWithString:(NSString *)aString
{
NSArray *lineRanges = [self _lineRangesForString:aString];
return [self initWithString:aString lineRanges:lineRanges];
}
+ (id)scannerWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
return [[self.class alloc] initWithString:aString lineRanges:theLineRanges];
}
- (id)initWithString:(NSString *)aString lineRanges:(NSArray *)theLineRanges
{
NSParameterAssert(theLineRanges.count > 0);
self = [super init];
if (self)
{
_string = aString;
_lineRanges = theLineRanges;
_rangeIndex = 0;
_transactions = [NSMutableArray new];
self.startLocation = 0;
self.currentRange = self.currentLineRange;
}
return self;
}
- (void)beginTransaction
{
NSDictionary *transaction = @{
@"rangeIndex": @(self.rangeIndex),
@"location": @(self.location),
@"startLocation": @(self.startLocation),
};
[self.transactions addObject:transaction];
self.startLocation = self.location;
}
- (void)commitTransaction:(BOOL)shouldSave
{
if (!self.transactions.count)
[NSException raise:@"Transaction underflow" format:@"Could not commit transaction because the stack is empty"];
NSDictionary *transaction = [self.transactions lastObject];
[self.transactions removeLastObject];
self.startLocation = [[transaction objectForKey:@"startLocation"] unsignedIntegerValue];
if (!shouldSave)
{
self.rangeIndex = [[transaction objectForKey:@"rangeIndex"] unsignedIntegerValue];
self.location = [[transaction objectForKey:@"location"] unsignedIntegerValue];
}
}
- (BOOL)atBeginningOfLine
{
return self.location == self.currentLineRange.location;
}
- (BOOL)atEndOfLine
{
return self.location == NSMaxRange(self.currentLineRange);
}
- (BOOL)atEndOfString
{
return self.atEndOfLine && self.rangeIndex == self.lineRanges.count - 1;
}
- (unichar)previousCharacter
{
if (self.atBeginningOfLine)
return '\0';
return [self.string characterAtIndex:self.location - 1];
}
- (unichar)nextCharacter
{
if (self.atEndOfLine)
return '\n';
return [self.string characterAtIndex:self.location];
}
- (NSString *)previousWord
{
return [self previousWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}
- (NSString *)nextWord
{
return [self nextWordWithCharactersFromSet:NSCharacterSet.alphanumericCharacterSet];
}
- (NSString *)previousWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSUInteger start = MAX(self.currentLineRange.location, self.startLocation);
NSUInteger end = self.currentRange.location;
NSRange range = NSMakeRange(start, end-start);
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:NSBackwardsSearch
range:range];
if (result.location == NSNotFound)
return [self.string substringWithRange:range];
NSUInteger wordLocation = NSMaxRange(result);
NSRange wordRange = NSMakeRange(wordLocation, end-wordLocation);
return [self.string substringWithRange:wordRange];
}
- (NSString *)nextWordWithCharactersFromSet:(NSCharacterSet *)set
{
NSRange result = [self.string rangeOfCharacterFromSet:set.invertedSet
options:0
range:self.currentRange];
if (result.location == NSNotFound)
return [self.string substringWithRange:self.currentRange];
NSRange wordRange = self.currentRange;
wordRange.length = result.location - wordRange.location;
return [self.string substringWithRange:wordRange];
}
- (void)advance
{
if (self.atEndOfLine)
return;
self.location += 1;
}
- (void)advanceToNextLine
{
// If at the last line, just go to the end of the line
if (self.rangeIndex == self.lineRanges.count - 1)
{
self.location = NSMaxRange(self.currentLineRange);
}
// Otherwise actually go to the next line
else
{
self.rangeIndex += 1;
self.currentRange = self.currentLineRange;
}
}
- (BOOL)matchString:(NSString *)string
{
if (self.currentRange.length < string.length)
return NO;
NSUInteger location = self.location;
for (NSUInteger idx=0; idx<string.length; idx++)
{
if ([string characterAtIndex:idx] != [self.string characterAtIndex:location+idx])
return NO;
}
self.location += string.length;
return YES;
}
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet
{
NSRange searchRange = self.currentRange;
NSRange range = [self.string rangeOfCharacterFromSet:[aSet invertedSet]
options:0
range:searchRange];
NSUInteger current = self.location;
if (range.location == NSNotFound)
{
self.currentRange = NSMakeRange(NSMaxRange(self.currentRange), 0);
}
else
{
self.currentRange = NSMakeRange(range.location, NSMaxRange(self.currentRange)-range.location);
}
return self.location - current;
}
- (NSUInteger)skipCharactersFromSet:(NSCharacterSet *)aSet max:(NSUInteger)maxToSkip
{
NSUInteger idx=0;
for (; idx<maxToSkip; idx++)
{
unichar character = [self nextCharacter];
if ([aSet characterIsMember:character])
[self advance];
else
break;
}
return idx;
}
- (NSUInteger)skipEmptyLines
{
NSUInteger skipped = 0;
while (![self atEndOfString])
{
[self beginTransaction];
[self skipWhitespace];
if (!self.atEndOfLine)
{
[self commitTransaction:NO];
break;
}
[self commitTransaction:YES];
[self advanceToNextLine];
skipped++;
}
return skipped;
}
- (NSUInteger)skipIndentationUpTo:(NSUInteger)maxSpacesToSkip
{
NSUInteger skipped = 0;
[self beginTransaction];
while (!self.atEndOfLine && skipped < maxSpacesToSkip)
{
unichar character = self.nextCharacter;
if (character == ' ')
skipped += 1;
else if (character == '\t')
{
skipped -= skipped % 4;
skipped += 4;
}
else
break;
[self advance];
}
[self commitTransaction:skipped <= maxSpacesToSkip];
return skipped;
}
- (NSUInteger)skipNestedBracketsWithDelimiter:(unichar)delimiter
{
NSString *delimiters = __delimitersForCharacter(delimiter);
if (delimiters == nil)
return 0;
unichar openDelimiter = [delimiters characterAtIndex:0];
unichar closeDelimeter = [delimiters characterAtIndex:1];
if ([self nextCharacter] != openDelimiter)
return 0;
[self beginTransaction];
NSUInteger location = self.location;
[self advance];
NSString *specialChars = [NSString stringWithFormat:@"%@\\", delimiters];
NSCharacterSet *boringChars = [[NSCharacterSet characterSetWithCharactersInString:specialChars] invertedSet];
NSUInteger nestingLevel = 1;
while (nestingLevel > 0)
{
if (self.atEndOfLine)
{
[self commitTransaction:NO];
return 0;
}
[self skipCharactersFromSet:boringChars];
unichar nextChar = self.nextCharacter;
[self advance];
if (nextChar == openDelimiter)
{
nestingLevel++;
}
else if (nextChar == closeDelimeter)
{
nestingLevel--;
}
else if (nextChar == '\\')
{
// skip a second character after a backslash
[self advance];
}
}
[self commitTransaction:YES];
return self.location - location;
}
- (NSUInteger)skipToEndOfLine
{
NSUInteger length = self.currentRange.length;
self.location = NSMaxRange(self.currentRange);
return length;
}
- (NSUInteger)skipToLastCharacterOfLine
{
NSUInteger length = self.currentRange.length - 1;
self.location = NSMaxRange(self.currentRange) - 1;
return length;
}
- (NSUInteger)skipWhitespace
{
return [self skipCharactersFromSet:NSCharacterSet.whitespaceCharacterSet];
}
- (NSUInteger)skipWhitespaceAndNewlines
{
NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet;
NSUInteger length = 0;
while (!self.atEndOfString)
{
if (self.atEndOfLine)
{
[self advanceToNextLine];
length++;
}
else
{
NSUInteger spaces = [self skipCharactersFromSet:whitespaceSet];
if (spaces == 0)
break;
length += spaces;
}
}
return length;
}
#pragma mark - Public Properties
- (NSUInteger)location
{
return self.currentRange.location;
}
- (void)setLocation:(NSUInteger)location
{
// If the new location isn't a part of the current range, then find the range it belongs to.
if (!NSLocationInRange(location, self.currentLineRange))
{
__block NSUInteger index = 0;
[self.lineRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSRange range = [obj rangeValue];
*stop = NSLocationInRange(location, range) || location == NSMaxRange(range);
index = idx;
}];
self.rangeIndex = index;
}
self.currentRange = NSMakeRange(location, NSMaxRange(self.currentLineRange)-location);
}
#pragma mark - Private Methods
- (NSArray *)_lineRangesForString:(NSString *)aString
{
NSMutableArray *result = [NSMutableArray array];
NSUInteger location = 0;
NSUInteger idx;
for (idx=0; idx<aString.length; idx++)
{
unichar character = [aString characterAtIndex:idx];
if (character == '\r' || character == '\n')
{
NSRange range = NSMakeRange(location, idx-location);
[result addObject:[NSValue valueWithRange:range]];
// If it's a carriage return, check for a line feed too
if (character == '\r')
{
if (idx + 1 < aString.length && [aString characterAtIndex:idx + 1] == '\n')
{
idx += 1;
}
}
location = idx + 1;
}
}
// Add the final line if the string doesn't end with a newline
if (location < aString.length)
{
NSRange range = NSMakeRange(location, aString.length-location);
[result addObject:[NSValue valueWithRange:range]];
}
return result;
}
- (NSUInteger)_locationOfCharacter:(unichar)character inRange:(NSRange)range
{
NSString *characterString = [NSString stringWithCharacters:&character length:1];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:characterString];
NSRange result = [self.string rangeOfCharacterFromSet:characterSet options:0 range:range];
return result.location;
}
#pragma mark - Private Properties
- (NSRange)currentLineRange
{
return [self.lineRanges[self.rangeIndex] rangeValue];
}
@end

View File

@ -0,0 +1,45 @@
//
// MMSpanParser.h
// MMMarkdown
//
// Copyright (c) 2012 Matt Diephouse.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
#import "MMMarkdown.h"
#import "MMParser.h"
@class MMElement;
@class MMScanner;
NS_ASSUME_NONNULL_BEGIN
@interface MMSpanParser : NSObject
- (id)initWithExtensions:(MMMarkdownExtensions)extensions;
- (nullable NSArray *)parseSpansInBlockElement:(MMElement *)block withScanner:(MMScanner *)scanner;
- (nullable NSArray *)parseSpansInTableColumns:(NSArray *)columns withScanner:(MMScanner *)scanner;
@end
NS_ASSUME_NONNULL_END

1298
tweak/assets/MMSpanParser.m Normal file

File diff suppressed because it is too large Load Diff