手把手带你实现Markdown编辑器语法高亮


源代码:ZYMarkdownEditor


本文是作者在独立开发一款Markdown编辑器App时所写,读完本文你将可以实现如下效果:

IMG_3528.PNG

<h1 id="1">什么是正则表达式?</h1>

正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。

如果有同学写过爬虫,应该对正则表达式很熟悉,强大的匹配功能让很多问题引刃而解.运用正则表达式可以验证用户输入(手机号,邮箱,密码)提取特定规则字符串.

举个最简单的栗子:

" [\\u4e00-\\u9fa5]"   //匹配中文
" ^[A-Za-z0-9]+$"   //匹配由数字和26个英文字母组成的字符串

附上简单的正则语法:NSRegularExpression-Cheatsheet.pdf
推荐一本好书:

精通正则表达式

作者也仅是看过一部分,书前半部分讲原理,一共500多页,略多。附上豆瓣链接:精通正则表达式

正则匹配如何实现的呢?

通过正则引擎来实现,正则文法对应于有限状态自动机,又分确定型有限状态自动机(DFA)和非确定型有限状态自动机(NFA),这两种状态机的能力是一样的,都能识别正则语言。什么是DFA与NFA呢?这方面属于编译原理的知识,作者由于还没有上过这门课,所以这方面就不误人子弟了。

感兴趣的同学可以看看下面这本书:Parsing Techniques。这本书主要讲前端,大家熟知的可能是龙书,但是龙书不太适合新手,所以就不推荐了。后端方面还有各种鲸书,虎书。

<h1 id="2"> iOS开发如何使用正则匹配</h1>

iOS开发中,使用正则匹配的场景不是很多:

  • 注册检查帐号是是手机号,避免多次请求服务器
  • 密码强度检查
  • 验证码检查

举个栗子:检查输入的是否手机号

//匹配以1开头,第二位为36578,后面还有九位数字的字符串;
NSString *pattern = @"^[1][36578]\\\\d{9}$"

//生成正则表达式
NSRegularExpression *regular = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];

//匹配方法
/*
 (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(void (NS_NOESCAPE ^)(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL *stop))block;

- (NSArray<NSTextCheckingResult *> *)matchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;

- (NSUInteger)numberOfMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;

- (nullable NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;

- (NSRange)rangeOfFirstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
*/


NSArray *array =  [regular matchesInString:string options:0 range:NSMakeRange(0,string.length)];

//判断数组元素个数是否为0
if (array.count == 0) {
  
  self.loginButton.enabled = NO;
    
}
else{

 self.loginButton.enabled = YES;

}

上面仅仅是正则表达式的一个简单应用。还可以使用正则表达式来进行实时文本搜索高亮,语法高亮,提取特定字符串。作者目前正在独立开发一个简单的Markdown编辑App,通过用正则表达式完成了语法高亮。

使用正则表达式匹配Markdown语法

作者在匹配Markdown语法时由于水平限制只匹配了一部分,另外一部分:公式,checkBox没有匹配。如果哪位
朋友能够完成希望指点一下。
我们匹配时使用的正则表达式如下:

//# 五级标题
@"^((\\#{1,5}+\\s+[^#].*))$"
  
//标题\n----
@"^[^-\\n][^\\n]*\\n-+$"

//标题/n==
@"^[^=\\n][^\\n]*\\n=+$"

//`行内代码`
@"(?<!`)(`{1,3})([^`\n]+?)\\1(?!`)"

//多行代码 
@ "``\`([\\s\\S]*?)``\`[\\s]?"

//缩进型代码
   @"(^\\s*$\\n)((( {4}|\\t).*(\\n|\\z))|(^\\s*$\\n))+"

//*强调*  __强调__
@"((?<!\\*)\\*(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*(?!\\*)|(?<!_)_(?=[^ \\t_])(.+?)(?<=[^ \\t_])_(?!_))"

// ***强调***  __强调__
 @"((?<!\\*)\\*{3}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{3}(?!\\*)|(?<!_)_{3}(?=[^ \\t_])(.+?)(?<=[^ \\t_])_{3}(?!_))"

//**text**
  @"(?<!\\*)\\*{2}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{2}(?!\\*)"

// __强调__
  @"(?<!_)__(?=[^ \\t_])(.+?)(?<=[^ \\t_])\\__(?!_)"

// ~~删除~~
@"(?<!~)~~(?=[^ \\t~])(.+?)(?<=[^ \\t~])\\~~(?!~)"

//![图片](域名)
@"!?\\[([^\\[\\]]+)\\](\\(([^\\(\\)]+)\\)|\\[([^\\[\\]]+)\\])"

//[链接]:
@"^[ \\t]*\\[[^\\[\\]]\\]:"

//1.列表 2.列表 3.列表
@"^[ \\t]*([*+-]|\\d+[.])[ \\t]+"

//******分割线
@"^[ \\t]*([*-])[ \\t]*((\\1)[ \\t]*){2,}[ \\t]*$"


<h1 id="3">如何使用?</h1>

说一种最简单但效率最低的方法

  • 使用TextView代理方法,每次文本更改都进行匹配
  • 使用TextKit进行富文本的生成,需要用到匹配结果得到的range,TextKit教程请自行搜索;

或者使用更方便的YYTextView;

这种每次更改都要匹配的显然很低效,但在这个基础上,我们仍然可以进行一些优化:

  • 匹配空字符串,如果输入的是空字符串,不再继续匹配其他语法
  • 如果用户粘贴文段时,不匹配。

如果使用编译原理知识来进行语法高亮就可以提高很多性能。但作者学识尚浅,未能完成相关的工作。

<h1 id="4">性能劣势</h1>

性能问题在上文已经说了,经过测试,当文字超过7000字时,就会出现0.4秒左右的延迟,内存占用也会逐渐变高。使用YYTextView以后内存急剧增加,通常7000字时就会达到100M。但是YYTextView提供了很多方便。考虑到实用性还是选择了YYTextView;

<h1 id="5">配合YYTextView实现语法高亮</h1>

YYTextView拥有Parser的协议,只需要遵守该协议就可以实现一个Parser。同时还需要设置Parser属性;

//该方法会传入一个富文本,在这个方法里写入我们需要匹配的代码,然后调用相关方法就可以进行实时语法高亮
- (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range  {

//举个栗子:高亮标题
    NSRegularExpression *headerRegex = [NSRegularExpression regularExpressionWithPattern:@"^((\\#{1,5}+\\s+[^#].*))$" options:0 error:nil];
    [headerRegex enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
     
            [text yy_setColor:self.headerColor range:result.range];
            [text yy_setFont:self.headerFont range:result.range];
 
        }];



}



至此,我们的Markdown编辑器语法高亮就实现了,使用同样的方法我们还可以实现搜索时的文本实时高亮。正则表达式实在太强大,熟悉掌握可以给我们减去很多麻烦。如果有想跟我探讨的相关问题的同学可以联系我,这个App尚在开发中,如果有美工愿意同我一起开发请给我发邮件:)lztuna04@gmail.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,596评论 4 59
  • 曲奇饼干大概是最适合下午茶的点心之一了 试想在一个高强度工作之后的下午 体力和脑力都即将耗尽 眼前突然出现一叠烤的...
    新食街阅读 354评论 0 0
  • 离人笔短暮光长, 子夜无眠马背凉。 梦里妻儿穿岁月, ...
    A幸运点阅读 306评论 10 5
  • 试用期三个月里,每天都努力工作,因为不想被领导轻视、因为知道自己其实是低头答应了这份工作、因为薪水其实是降低了的。...
    小静静Tracy阅读 268评论 0 0