forked from lint/TFDidThatSay
		
	Add Alien Blue support
This commit is contained in:
		
							
								
								
									
										60
									
								
								tweak/AlienBlue.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								tweak/AlienBlue.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
 | 
			
		||||
/* -- Voteable Interfaces -- */
 | 
			
		||||
 | 
			
		||||
@interface VoteableElement
 | 
			
		||||
@property(strong, nonatomic) NSString *ident;
 | 
			
		||||
@property(strong, nonatomic) NSString *author;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface Comment : VoteableElement
 | 
			
		||||
@property(strong, nonatomic) NSString *body;
 | 
			
		||||
@property(strong, nonatomic) NSString *bodyHTML;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface Post : VoteableElement
 | 
			
		||||
@property(strong, nonatomic) NSString *selftext;
 | 
			
		||||
@property(strong, nonatomic) NSString *selftextHtml;
 | 
			
		||||
@property(assign, nonatomic) BOOL selfPost;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
/* -- UI Interfaces -- */
 | 
			
		||||
 | 
			
		||||
@interface NCommentCell
 | 
			
		||||
@property(strong, nonatomic) Comment *comment;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface NCommentPostHeaderCell
 | 
			
		||||
@property(strong, nonatomic) Post *post;
 | 
			
		||||
@property(strong, nonatomic) id node;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface CommentsViewController
 | 
			
		||||
-(void) respondToStyleChange;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface CommentPostHeaderNode
 | 
			
		||||
@property(strong, nonatomic) Comment *comment;
 | 
			
		||||
@property(strong, nonatomic) Post *post;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface CommentOptionsDrawerView
 | 
			
		||||
@property(strong, nonatomic) NSMutableArray *buttons;
 | 
			
		||||
@property(assign, nonatomic) BOOL isPostHeader;
 | 
			
		||||
@property(strong, nonatomic) id delegate;
 | 
			
		||||
-(void) addButton:(id) arg1;
 | 
			
		||||
 | 
			
		||||
//custom elements
 | 
			
		||||
@property(strong, nonatomic) Comment *comment;
 | 
			
		||||
@property(strong, nonatomic) Post *post;
 | 
			
		||||
@property(strong, nonatomic) CommentPostHeaderNode *commentNode;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
/* -- Other Interfaces -- */
 | 
			
		||||
 | 
			
		||||
@interface MarkupEngine
 | 
			
		||||
+(id) markDownHTML:(id) arg1 forSubreddit:(id) arg2;
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@interface Resources
 | 
			
		||||
+(BOOL) isNight;
 | 
			
		||||
@end
 | 
			
		||||
							
								
								
									
										216
									
								
								tweak/AlienBlue.xm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								tweak/AlienBlue.xm
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
 | 
			
		||||
#import "AlienBlue.h"
 | 
			
		||||
#import "assets/MMMarkdown.h"
 | 
			
		||||
 | 
			
		||||
static BOOL isAlienBlueEnabled;
 | 
			
		||||
static BOOL isAlienBlueDeletedOnly;
 | 
			
		||||
static CGFloat pushshiftRequestTimeoutValue;
 | 
			
		||||
 | 
			
		||||
%group AlienBlue
 | 
			
		||||
 | 
			
		||||
%hook NCommentCell
 | 
			
		||||
 | 
			
		||||
-(void) setDrawerView:(id) arg1 {
 | 
			
		||||
	[arg1 setComment:[self comment]];
 | 
			
		||||
	%orig;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
%end
 | 
			
		||||
 | 
			
		||||
%hook NCommentPostHeaderCell
 | 
			
		||||
 | 
			
		||||
-(void) setDrawerView:(id) arg1 {
 | 
			
		||||
	[arg1 setPost:[self post]];
 | 
			
		||||
	[arg1 setCommentNode:[self node]];
 | 
			
		||||
	%orig;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
%end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%hook CommentOptionsDrawerView
 | 
			
		||||
%property(strong, nonatomic) Comment *comment;
 | 
			
		||||
%property(strong, nonatomic) Post *post;
 | 
			
		||||
%property(strong, nonatomic) CommentPostHeaderNode *commentNode;
 | 
			
		||||
 | 
			
		||||
-(id) initWithNode:(id) arg1 {
 | 
			
		||||
	id orig = %orig;
 | 
			
		||||
	
 | 
			
		||||
	NSString *body;
 | 
			
		||||
	
 | 
			
		||||
	if ([self post]) {
 | 
			
		||||
		body = [[self post] selftext];
 | 
			
		||||
	} else if ([self comment]){
 | 
			
		||||
		body = [[self comment] body];
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if ((isAlienBlueDeletedOnly && ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"])) || !isAlienBlueDeletedOnly) {
 | 
			
		||||
	
 | 
			
		||||
		CGSize refSize = [[self buttons][0] frame].size;
 | 
			
		||||
 | 
			
		||||
		UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
 | 
			
		||||
		[undeleteButton setFrame:CGRectMake(0, 0, refSize.width, refSize.height)];
 | 
			
		||||
		
 | 
			
		||||
		if ([self isPostHeader]){
 | 
			
		||||
			[undeleteButton addTarget:self action:@selector(didTapPostUndeleteButton) forControlEvents:UIControlEventTouchUpInside];
 | 
			
		||||
		} else {
 | 
			
		||||
			[undeleteButton addTarget:self action:@selector(didTapCommentUndeleteButton) forControlEvents:UIControlEventTouchUpInside];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if ([%c(Resources) isNight]) {
 | 
			
		||||
			[undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"] forState:UIControlStateNormal];
 | 
			
		||||
		} else {
 | 
			
		||||
			[undeleteButton setImage:[UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160light.png"] forState:UIControlStateNormal];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		undeleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
 | 
			
		||||
		undeleteButton.imageEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10);
 | 
			
		||||
		
 | 
			
		||||
		[self addButton:undeleteButton];
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return orig;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
%new 
 | 
			
		||||
-(void) didTapCommentUndeleteButton {
 | 
			
		||||
	
 | 
			
		||||
	Comment *comment = [self comment];
 | 
			
		||||
	
 | 
			
		||||
	NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
 | 
			
		||||
	NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 | 
			
		||||
 | 
			
		||||
	[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body",[comment ident]]]];
 | 
			
		||||
	[request setHTTPMethod:@"GET"];
 | 
			
		||||
	[request setTimeoutInterval:pushshiftRequestTimeoutValue];
 | 
			
		||||
 | 
			
		||||
	[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
 | 
			
		||||
	
 | 
			
		||||
		NSString *author = @"[author]";
 | 
			
		||||
		NSString *body = @"[body]";
 | 
			
		||||
 | 
			
		||||
		if (data != nil && error == nil){
 | 
			
		||||
			id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
 | 
			
		||||
			if ([[jsonData objectForKey:@"data"] count] != 0){
 | 
			
		||||
				author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"];
 | 
			
		||||
				body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"];
 | 
			
		||||
				if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){
 | 
			
		||||
					body = @"[pushshift was unable to archive this]";
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				body = @"[pushshift has not archived this yet]";
 | 
			
		||||
			}
 | 
			
		||||
		} else if (error != nil || data == nil){
 | 
			
		||||
			body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		NSString *bodyHTML = [%c(MMMarkdown) HTMLStringWithMarkdown:body extensions:MMMarkdownExtensionsGitHubFlavored error:nil];
 | 
			
		||||
		
 | 
			
		||||
		[comment setAuthor:author];
 | 
			
		||||
		[comment setBody:body];
 | 
			
		||||
		[comment setBodyHTML:bodyHTML];
 | 
			
		||||
		
 | 
			
		||||
		[[self delegate] performSelectorOnMainThread:@selector(respondToStyleChange) withObject:nil waitUntilDone:NO];
 | 
			
		||||
	}];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
%new 
 | 
			
		||||
-(void) didTapPostUndeleteButton {
 | 
			
		||||
	
 | 
			
		||||
	Post *post = [self post];
 | 
			
		||||
	Comment *postComment = [[self commentNode] comment]; //Don't know why he used a comment to store info about a post, but it exists
 | 
			
		||||
	
 | 
			
		||||
	NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
 | 
			
		||||
	NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 | 
			
		||||
 | 
			
		||||
	[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/submission/?ids=%@&fields=author,selftext",[post ident]]]];
 | 
			
		||||
	[request setHTTPMethod:@"GET"];
 | 
			
		||||
	[request setTimeoutInterval:pushshiftRequestTimeoutValue];
 | 
			
		||||
 | 
			
		||||
	[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
 | 
			
		||||
		
 | 
			
		||||
		NSString *author = @"[author]";
 | 
			
		||||
		NSString *body = @"[body]";
 | 
			
		||||
 | 
			
		||||
		if (data != nil && error == nil){
 | 
			
		||||
			id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
 | 
			
		||||
			if ([[jsonData objectForKey:@"data"] count] != 0){
 | 
			
		||||
				author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"];
 | 
			
		||||
				body = [[jsonData objectForKey:@"data"][0] objectForKey:@"selftext"];
 | 
			
		||||
				if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){
 | 
			
		||||
					body = @"[pushshift was unable to archive this]";
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				body = @"[pushshift has not archived this yet]";
 | 
			
		||||
			}
 | 
			
		||||
		} else if (error != nil || data == nil){
 | 
			
		||||
			body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]];
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		NSString *bodyHTML = [%c(MMMarkdown) HTMLStringWithMarkdown:body extensions:MMMarkdownExtensionsGitHubFlavored error:nil];
 | 
			
		||||
		
 | 
			
		||||
		[post setAuthor:author];
 | 
			
		||||
		[post setSelftext:body];
 | 
			
		||||
		[postComment setBodyHTML:bodyHTML];
 | 
			
		||||
		
 | 
			
		||||
		[[self delegate] performSelectorOnMainThread:@selector(respondToStyleChange) withObject:nil waitUntilDone:NO];
 | 
			
		||||
		
 | 
			
		||||
	}];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
%end
 | 
			
		||||
 | 
			
		||||
%end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void loadPrefs(){
 | 
			
		||||
	NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
 | 
			
		||||
	
 | 
			
		||||
	if (prefs){
 | 
			
		||||
		
 | 
			
		||||
		if ([prefs objectForKey:@"isAlienBlueEnabled"] != nil){
 | 
			
		||||
			isAlienBlueEnabled = [[prefs objectForKey:@"isAlienBlueEnabled"] boolValue];
 | 
			
		||||
		} else {
 | 
			
		||||
			isAlienBlueEnabled = YES;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if ([prefs objectForKey:@"isAlienBlueDeletedOnly"] != nil){
 | 
			
		||||
			isAlienBlueDeletedOnly = [[prefs objectForKey:@"isAlienBlueDeletedOnly"] boolValue];
 | 
			
		||||
		} else {
 | 
			
		||||
			isAlienBlueDeletedOnly = YES;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if ([prefs objectForKey:@"requestTimeoutValue"] != nil){
 | 
			
		||||
			pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue];
 | 
			
		||||
		} else {
 | 
			
		||||
			pushshiftRequestTimeoutValue = 10;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	} else {
 | 
			
		||||
		isAlienBlueEnabled = YES;
 | 
			
		||||
		isAlienBlueDeletedOnly = YES;
 | 
			
		||||
		pushshiftRequestTimeoutValue = 10;
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
 | 
			
		||||
  loadPrefs();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%ctor {
 | 
			
		||||
	loadPrefs();
 | 
			
		||||
	
 | 
			
		||||
	NSString* processName = [[NSProcessInfo processInfo] processName];
 | 
			
		||||
 | 
			
		||||
	if ([processName isEqualToString:@"AlienBlue"]){		
 | 
			
		||||
		if (isAlienBlueEnabled){
 | 
			
		||||
			
 | 
			
		||||
			CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
 | 
			
		||||
			
 | 
			
		||||
			%init(AlienBlue);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										40
									
								
								tweak/assets/MMDocument.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								tweak/assets/MMDocument.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										70
									
								
								tweak/assets/MMDocument.m
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										39
									
								
								tweak/assets/MMDocument_Private.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tweak/assets/MMDocument_Private.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										92
									
								
								tweak/assets/MMElement.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										171
									
								
								tweak/assets/MMElement.m
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										37
									
								
								tweak/assets/MMGenerator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								tweak/assets/MMGenerator.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										282
									
								
								tweak/assets/MMGenerator.m
									
									
									
									
									
										Normal 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:@"&"
 | 
			
		||||
                               options:NSLiteralSearch
 | 
			
		||||
                                 range:NSMakeRange(0, result.length)];
 | 
			
		||||
    [result replaceOccurrencesOfString:@"\""
 | 
			
		||||
                            withString:@"""
 | 
			
		||||
                               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
 | 
			
		||||
							
								
								
									
										40
									
								
								tweak/assets/MMHTMLParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								tweak/assets/MMHTMLParser.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										324
									
								
								tweak/assets/MMHTMLParser.m
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										7
									
								
								tweak/assets/MMMarkdown-Prefix.pch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tweak/assets/MMMarkdown-Prefix.pch
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										82
									
								
								tweak/assets/MMMarkdown.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										73
									
								
								tweak/assets/MMMarkdown.m
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										41
									
								
								tweak/assets/MMParser.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										1321
									
								
								tweak/assets/MMParser.m
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										79
									
								
								tweak/assets/MMScanner.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								tweak/assets/MMScanner.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										499
									
								
								tweak/assets/MMScanner.m
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										45
									
								
								tweak/assets/MMSpanParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								tweak/assets/MMSpanParser.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										1298
									
								
								tweak/assets/MMSpanParser.m
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user