mirror of
https://github.com/lint/TFDidThatSay
synced 2025-07-01 15:36:46 +00:00
Add Alien Blue support
This commit is contained in:
10
Makefile
10
Makefile
@ -3,18 +3,18 @@ ARCHS = arm64 arm64e
|
|||||||
include $(THEOS)/makefiles/common.mk
|
include $(THEOS)/makefiles/common.mk
|
||||||
|
|
||||||
TWEAK_NAME = tfdidthatsay
|
TWEAK_NAME = tfdidthatsay
|
||||||
tfdidthatsay_FILES = $(wildcard tweak/*.xm)
|
tfdidthatsay_FILES = $(wildcard tweak/*.xm tweak/assets/*.m)
|
||||||
tweak/Narwhal.xm_CFLAGS = -fobjc-arc
|
tfdidthatsay_CFLAGS = -fobjc-arc
|
||||||
tfdidthatsay_EXTRA_FRAMEWORKS += Cephei
|
tweak/Reddit.xm_CFLAGS = -fno-objc-arc
|
||||||
|
tweak/Apollo.xm_CFLAGS = -fno-objc-arc
|
||||||
|
|
||||||
include $(THEOS_MAKE_PATH)/tweak.mk
|
include $(THEOS_MAKE_PATH)/tweak.mk
|
||||||
|
|
||||||
SUBPROJECTS += prefs
|
SUBPROJECTS += prefs
|
||||||
include $(THEOS_MAKE_PATH)/aggregate.mk
|
include $(THEOS_MAKE_PATH)/aggregate.mk
|
||||||
|
|
||||||
|
|
||||||
after-install::
|
after-install::
|
||||||
install.exec "killall -9 Reddit"
|
install.exec "killall -9 Reddit"
|
||||||
install.exec "killall -9 Apollo"
|
install.exec "killall -9 Apollo"
|
||||||
install.exec "killall -9 narwhal"
|
install.exec "killall -9 narwhal"
|
||||||
|
install.exec "killall -9 AlienBlue"
|
||||||
|
@ -54,11 +54,27 @@
|
|||||||
<key>label</key>
|
<key>label</key>
|
||||||
<string>Enable Narwhal</string>
|
<string>Enable Narwhal</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PostNotification</key>
|
||||||
|
<string>com.lint.undelete.prefs.changed</string>
|
||||||
|
<key>cell</key>
|
||||||
|
<string>PSSwitchCell</string>
|
||||||
|
<key>default</key>
|
||||||
|
<true/>
|
||||||
|
<key>defaults</key>
|
||||||
|
<string>com.lint.undelete.prefs</string>
|
||||||
|
<key>key</key>
|
||||||
|
<string>isAlienBlueEnabled</string>
|
||||||
|
<key>label</key>
|
||||||
|
<string>Enable Alien Blue</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>cell</key>
|
<key>cell</key>
|
||||||
<string>PSGroupCell</string>
|
<string>PSGroupCell</string>
|
||||||
|
<key>footerText</key>
|
||||||
|
<string>On apps that have the eye button rather than in the menu, enable or disable that button from appearing on only deleted comments/posts.</string>
|
||||||
<key>label</key>
|
<key>label</key>
|
||||||
<string>Apollo </string>
|
<string>visibility</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PostNotification</key>
|
<key>PostNotification</key>
|
||||||
@ -72,7 +88,21 @@
|
|||||||
<key>key</key>
|
<key>key</key>
|
||||||
<string>isApolloDeletedCommentsOnly</string>
|
<string>isApolloDeletedCommentsOnly</string>
|
||||||
<key>label</key>
|
<key>label</key>
|
||||||
<string>Only display eye on deleted comments</string>
|
<string>Apollo | Only on deleted comments</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PostNotification</key>
|
||||||
|
<string>com.lint.undelete.prefs.changed</string>
|
||||||
|
<key>cell</key>
|
||||||
|
<string>PSSwitchCell</string>
|
||||||
|
<key>default</key>
|
||||||
|
<true/>
|
||||||
|
<key>defaults</key>
|
||||||
|
<string>com.lint.undelete.prefs</string>
|
||||||
|
<key>key</key>
|
||||||
|
<string>isAlienBlueDeletedOnly</string>
|
||||||
|
<key>label</key>
|
||||||
|
<string>Alien Blue | Only on deleted comments/posts</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>cell</key>
|
<key>cell</key>
|
||||||
|
Binary file not shown.
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