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

JoeOKQE 7年前
   <ul>     <li>iOS开发如何使用正则表达式?</li>     <li>使用正则表达式匹配Markdown</li>     <li>配合YYTextView实现语法高亮</li>    </ul>    <p>本文是作者在独立开发一款Markdown编辑器App时所写,读完本文你将可以实现如下效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7054a8c6601bc09f051017c6a467151b.png"></p>    <p>IMG_3528.PNG</p>    <h2>什么是正则表达式?</h2>    <p>正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。</p>    <p>如果有同学写过爬虫,应该对正则表达式很熟悉,强大的匹配功能让很多问题引刃而解.运用正则表达式可以验证用户输入(手机号,邮箱,密码)提取特定规则字符串.</p>    <p>举个最简单的栗子:</p>    <pre>  <code class="language-objectivec">" [\\u4e00-\\u9fa5]"   //匹配中文  " ^[A-Za-z0-9]+$"   //匹配由数字和26个英文字母组成的字符串</code></pre>    <p>附上简单的正则语法: <a href="/misc/goto?guid=4959739457355473109" rel="nofollow,noindex">NSRegularExpression-Cheatsheet.pdf</a></p>    <p>推荐一本好书:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2230e6e0e002eeba8ecdaadb70d5d978.jpg"></p>    <p>精通正则表达式</p>    <p>作者也仅是看过一部分,书前半部分讲原理,一共500多页,略多。</p>    <p>正则匹配如何实现的呢?</p>    <p>通过正则引擎来实现,正则文法对应于有限状态自动机,又分确定型有限状态自动机(DFA)和非确定型有限状态自动机(NFA),这两种状态机的能力是一样的,都能识别正则语言。什么是DFA与NFA呢?这方面属于编译原理的知识,作者由于还没有上过这门课,所以这方面就不误人子弟了。</p>    <p>感兴趣的同学可以看看下面这本书: Parsing Techniques 。这本书主要讲前端,大家熟知的可能是龙书,但是龙书不太适合新手,所以就不推荐了。后端方面还有各种鲸书,虎书。</p>    <h2>iOS开发如何使用正则匹配</h2>    <p>iOS开发中,使用正则匹配的场景不是很多:</p>    <ul>     <li>注册检查帐号是是手机号,避免多次请求服务器</li>     <li>密码强度检查</li>     <li>验证码检查</li>    </ul>    <p>举个栗子:检查输入的是否手机号</p>    <pre>  <code class="language-objectivec">//匹配以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;    }</code></pre>    <p>上面仅仅是正则表达式的一个简单应用。还可以使用正则表达式来进行实时文本搜索高亮,语法高亮,提取特定字符串。作者目前正在独立开发一个简单的Markdown编辑App,通过用正则表达式完成了语法高亮。</p>    <h2>使用正则表达式匹配Markdown语法</h2>    <p>作者在匹配Markdown语法时由于水平限制只匹配了一部分,另外一部分:公式,checkBox没有匹配。如果哪位</p>    <p>朋友能够完成希望指点一下。</p>    <p>我们匹配时使用的正则表达式如下:</p>    <pre>  <code class="language-objectivec">//# 五级标题  @"^((\\#{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]*$"</code></pre>    <h2>如何使用?</h2>    <p>说一种最简单但效率最低的方法</p>    <ul>     <li>使用TextView代理方法,每次文本更改都进行匹配</li>     <li>使用TextKit进行富文本的生成,需要用到匹配结果得到的 range ,TextKit教程请自行搜索;</li>    </ul>    <p>或者使用更方便的YYTextView;</p>    <p>这种每次更改都要匹配的显然很低效,但在这个基础上,我们仍然可以进行一些优化:</p>    <ul>     <li>匹配空字符串,如果输入的是空字符串,不再继续匹配其他语法</li>     <li>如果用户粘贴文段时,不匹配。</li>    </ul>    <p>如果使用编译原理知识来进行语法高亮就可以提高很多性能。但作者学识尚浅,未能完成相关的工作。</p>    <h2>性能劣势</h2>    <p>性能问题在上文已经说了,经过测试,当文字超过7000字时,就会出现0.4秒左右的延迟,内存占用也会逐渐变高。使用YYTextView以后内存急剧增加,通常7000字时就会达到100M。但是YYTextView提供了很多方便。考虑到实用性还是选择了YYTextView;</p>    <h2>配合YYTextView实现语法高亮</h2>    <p>YYTextView拥有Parser的协议,只需要遵守该协议就可以实现一个Parser。同时还需要设置Parser属性;</p>    <pre>  <code class="language-objectivec">//该方法会传入一个富文本,在这个方法里写入我们需要匹配的代码,然后调用相关方法就可以进行实时语法高亮  - (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];            }];        }</code></pre>    <p>至此,我们的Markdown编辑器语法高亮就实现了,使用同样的方法我们还可以实现搜索时的文本实时高亮。正则表达式实在太强大,熟悉掌握可以给我们减去很多麻烦。如果有想跟我探讨的相关问题的同学可以联系我,这个App尚在开发中,如果有美工愿意同我一起开发请给我发邮件:)lztuna04@gmail.com</p>    <p> </p>    <p> </p>    <p> </p>