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
|
||||
|
||||
TWEAK_NAME = tfdidthatsay
|
||||
tfdidthatsay_FILES = $(wildcard tweak/*.xm)
|
||||
tweak/Narwhal.xm_CFLAGS = -fobjc-arc
|
||||
tfdidthatsay_EXTRA_FRAMEWORKS += Cephei
|
||||
|
||||
tfdidthatsay_FILES = $(wildcard tweak/*.xm tweak/assets/*.m)
|
||||
tfdidthatsay_CFLAGS = -fobjc-arc
|
||||
tweak/Reddit.xm_CFLAGS = -fno-objc-arc
|
||||
tweak/Apollo.xm_CFLAGS = -fno-objc-arc
|
||||
|
||||
include $(THEOS_MAKE_PATH)/tweak.mk
|
||||
|
||||
SUBPROJECTS += prefs
|
||||
include $(THEOS_MAKE_PATH)/aggregate.mk
|
||||
|
||||
|
||||
after-install::
|
||||
install.exec "killall -9 Reddit"
|
||||
install.exec "killall -9 Apollo"
|
||||
install.exec "killall -9 narwhal"
|
||||
install.exec "killall -9 AlienBlue"
|
||||
|
@ -54,11 +54,27 @@
|
||||
<key>label</key>
|
||||
<string>Enable Narwhal</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>isAlienBlueEnabled</string>
|
||||
<key>label</key>
|
||||
<string>Enable Alien Blue</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>cell</key>
|
||||
<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>
|
||||
<string>Apollo </string>
|
||||
<string>visibility</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PostNotification</key>
|
||||
@ -72,7 +88,21 @@
|
||||
<key>key</key>
|
||||
<string>isApolloDeletedCommentsOnly</string>
|
||||
<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>
|
||||
<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