手把手帶你實現Markdown編輯器語法高亮
- iOS開發如何使用正則表達式?
- 使用正則表達式匹配Markdown
- 配合YYTextView實現語法高亮
本文是作者在獨立開發一款Markdown編輯器App時所寫,讀完本文你將可以實現如下效果:
IMG_3528.PNG
什么是正則表達式?
正則表達式(regular expression)描述了一種字符串匹配的模式,可以用來檢查一個串是否含有某種子串、將匹配的子串做替換或者從某個串中取出符合某個條件的子串等。
如果有同學寫過爬蟲,應該對正則表達式很熟悉,強大的匹配功能讓很多問題引刃而解.運用正則表達式可以驗證用戶輸入(手機號,郵箱,密碼)提取特定規則字符串.
舉個最簡單的栗子:
" [\\u4e00-\\u9fa5]" //匹配中文
" ^[A-Za-z0-9]+$" //匹配由數字和26個英文字母組成的字符串
附上簡單的正則語法: NSRegularExpression-Cheatsheet.pdf
推薦一本好書:
精通正則表達式
作者也僅是看過一部分,書前半部分講原理,一共500多頁,略多。
正則匹配如何實現的呢?
通過正則引擎來實現,正則文法對應于有限狀態自動機,又分確定型有限狀態自動機(DFA)和非確定型有限狀態自動機(NFA),這兩種狀態機的能力是一樣的,都能識別正則語言。什么是DFA與NFA呢?這方面屬于編譯原理的知識,作者由于還沒有上過這門課,所以這方面就不誤人子弟了。
感興趣的同學可以看看下面這本書: Parsing Techniques 。這本書主要講前端,大家熟知的可能是龍書,但是龍書不太適合新手,所以就不推薦了。后端方面還有各種鯨書,虎書。
iOS開發如何使用正則匹配
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;
}</code></pre>
上面僅僅是正則表達式的一個簡單應用。還可以使用正則表達式來進行實時文本搜索高亮,語法高亮,提取特定字符串。作者目前正在獨立開發一個簡單的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]*$"</code></pre>
如何使用?
說一種最簡單但效率最低的方法
- 使用TextView代理方法,每次文本更改都進行匹配
- 使用TextKit進行富文本的生成,需要用到匹配結果得到的 range ,TextKit教程請自行搜索;
或者使用更方便的YYTextView;
這種每次更改都要匹配的顯然很低效,但在這個基礎上,我們仍然可以進行一些優化:
- 匹配空字符串,如果輸入的是空字符串,不再繼續匹配其他語法
- 如果用戶粘貼文段時,不匹配。
如果使用編譯原理知識來進行語法高亮就可以提高很多性能。但作者學識尚淺,未能完成相關的工作。
性能劣勢
性能問題在上文已經說了,經過測試,當文字超過7000字時,就會出現0.4秒左右的延遲,內存占用也會逐漸變高。使用YYTextView以后內存急劇增加,通常7000字時就會達到100M。但是YYTextView提供了很多方便。考慮到實用性還是選擇了YYTextView;
配合YYTextView實現語法高亮
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];
}];
}</code></pre>
至此,我們的Markdown編輯器語法高亮就實現了,使用同樣的方法我們還可以實現搜索時的文本實時高亮。正則表達式實在太強大,熟悉掌握可以給我們減去很多麻煩。如果有想跟我探討的相關問題的同學可以聯系我,這個App尚在開發中,如果有美工愿意同我一起開發請給我發郵件:)lztuna04@gmail.com