iOS开发之详解正则表达式
本文由Charles翻自raywenderlich
原文:NSRegularExpression Tutorial: Getting Started
更新提示:本教程被James Frost更新到了iOS8和swift。Tutorial團(tuán)隊成員的Soheil Azarpour完成最初發(fā)布。
正則表達(dá)式(廣為所知的“regex”)是一個字符串或一個字符序列來說明一種模式,把它作為一個搜索字符串-非常強大!
在一個文本編輯器或文字處理器中普通的舊式搜索只允許你進(jìn)行簡單的匹配。正則表達(dá)式可以實現(xiàn)這樣簡單的搜索,它還能讓你更進(jìn)一步地按模式搜索,例如,在兩個數(shù)字后跟一個字母,或者,三個字母后跟一個連字符。
這種模式匹配能讓你做更有用的事,如驗證字段(電話號碼,郵箱地址),檢查用戶輸入,執(zhí)行更高級的文本操作等等。
如果你渴望了解更多關(guān)于正則表達(dá)式在iOS中的用法,看一些本教程之外的內(nèi)容--不需要有相關(guān)的經(jīng)驗。
在這篇NSRegularExpression教程中,你將實現(xiàn)一個在文本中按模式搜索的功能,用你希望的值替代那些匹配的值,驗證你的輸入信息,在文字塊中找到并高亮顯示復(fù)雜字符串。
此外,我還將給你提供一個NSRegularExpression Cheat Sheet PDF,你可以打印出來,在你開發(fā)過程中作為參考,Swift playground?包含了很多例子,你能用它試驗出許多不同形式的正則表達(dá)式!實際上,所有正則表達(dá)式的例子都會出現(xiàn)在本教程中,用很生動的例子展現(xiàn)在playground中,一定要查看它們哦。
閑話少說,是時候來處理正則表達(dá)式了。
/The (Basics|Introduction)/
Note:如果你已經(jīng)有正則表達(dá)式基礎(chǔ)了,可以跳過頭部,直接看Implementing Regex in iOS.
如果你是剛接觸正則表達(dá)式,并且想知道所說的這些是什么意思,這是一個簡短的定義:正則表達(dá)式提供了一種在指定文本文檔中按指定模式進(jìn)行搜索,并能基于匹配模式進(jìn)行修改文本的一種方式。有許多關(guān)于正則表達(dá)式的有意思的書和教程--在本教程的結(jié)尾,你會看到一個簡短的列表。
Regular Expressions Playground
在本教程中,你將會創(chuàng)建許多正則表達(dá)式,假使你想要可視化的使用它們,那么用Swift?Playground 是一個絕佳的方式?!
這個starter project?包含了這個教程用到的playground。下載項目,在Xcode中運行并打開iRegex.playground.你也可以單獨下載這個playground(download the playground)?。
Playground 頂部包含許多函數(shù)來高亮顯示在一小段文本中應(yīng)用正則表達(dá)式的搜索結(jié)果,展示了一系列的匹配項和分組,還有替換文本。目前不要擔(dān)心這些方法的實現(xiàn),之后你會再看到它們的,在Basic Examples和Cheat Sheet部分接著看這個例子。
在playground的側(cè)邊欄,你會看到每個例子的匹配結(jié)果。比如“highlight”這個例子,你可以把鼠標(biāo)指針懸浮在結(jié)果上并點擊“眼”或者空的圓圈圖標(biāo)來顯示在文本中高亮的匹配內(nèi)容。
你之后將學(xué)會如何創(chuàng)建NSRegularExpressions,現(xiàn)在你可以用這個playground來感受下不同的正則表達(dá)式是怎樣工作的,也可以試驗一下你自己的模式。在任一點上你都可以用Xcode中的EEditor > Reset Playground菜單按鈕來重置你的改動。
Examples
讓我們以一個小例子來展示正則表達(dá)式的樣子。
這是一個來匹配單詞“jump”的正則表達(dá)式的例子:
| 1 | jump |
?這是一個如此簡單的正則表達(dá)式。你可以使用iOS中可用的API來查詢一個文本中的字符串來匹配這個正則表達(dá)式—一旦你找到了匹配項,你能發(fā)現(xiàn)它在哪兒,你也可以替換它。
這是一個略微復(fù)雜點的例子—它會匹配單詞“jump”或“jumping”:
| 1 | jump(ing)? |
這是應(yīng)用正則表達(dá)式支持的特殊字符的例子。這個圓括號創(chuàng)建了一個組,這個標(biāo)志是說“匹配前面的元素(這種情況下的組)0次或1次”。
現(xiàn)在來看一個復(fù)雜的例子。它會匹配一對開合的HTML標(biāo)簽和他們之間的內(nèi)容。
| 1 | ]*>(.*?) |
喔,看起來好復(fù)雜,呃?不要擔(dān)心,在本教程的后面你將會學(xué)到正則表達(dá)式中的這些特殊字符,到時候你就能理解這是怎么實現(xiàn)的了!
如果你像了解之前的正則表達(dá)式的更多細(xì)節(jié),請參考this discussion的解釋。
Note:?在實際的應(yīng)用中,你可能不會單獨用正則表達(dá)式來解析HTML(probably shouldn’t use regular expressions alone to parse HTML),相反而是用標(biāo)準(zhǔn)的XML解析器。
Overall Concepts
在看更深入的內(nèi)容之前,理解一些關(guān)于正則表達(dá)式的核心概念很重要。
字面字符(Literal characters)是最簡單地一種正則表達(dá)式。你已經(jīng)很熟悉他們了,比如,文字處理機或文本編輯器中得“find”操作。例如,單個字符的正則表達(dá)式?t 就會找到字母“t”出現(xiàn)的所有地方,正則表達(dá)式?jump?會找出所有出現(xiàn)“jump”的地方。優(yōu)美,簡潔!
就像一種編程語言一樣,正則表達(dá)式的語法中也有一些保留字,如下:
-
[
-
(?and?)
-
\
-
*
-
+
-
?
-
{?and?}
-
^
-
$
-
.
-
|?(pipe)
-
/
這些字符被用作高級模式匹配。如果你想搜索這些字符中的一個,你需要用反斜線(\)轉(zhuǎn)義它,例如,為了搜索一個文本塊中的句號,不是用.,而是用\.。
每種環(huán)境,在Python、Perl、Java、C#、Ruby或者其他環(huán)境,在實現(xiàn)正則表達(dá)式時都有一些特殊的細(xì)微差別,在Swift中也不例外!
無論Objective-C還是Swift,你在字面量字符串中都需要轉(zhuǎn)義一些特殊字符(在他們之前添加\字符)。這其中一個字符就是反斜線自身\!既然這個被用來創(chuàng)建正則表達(dá)式的模式也是字符串,在你處理String?和?NSRegularExpression,你需要轉(zhuǎn)義反斜線時,?這就增加了復(fù)雜性。
這意味著在Swift(或者Objective-C)代碼中標(biāo)準(zhǔn)的\.將會顯示為\\.。
用以下兩點來澄清以上概念:
-
字面的“\\.”定義了一個字符串:\.
-
正則表達(dá)式\.則是匹配一個單個的句號字符.。
截獲圓括號(capturing parentheses) 被用作組模式的一部分。例如:3 (pm|am)會匹配文本“3 pm”?,也會匹配“3 am”。豎線字符(|)執(zhí)行的是或操作。只要你樂意,你可以包含多個豎線字符在你的正則表達(dá)式中。例如,(Tom|Dick|Harry)是一個有效的模式,它能匹配那三個名字中的任一個。
當(dāng)你需要選擇性的匹配特定的字符串時,圓括號組用起來很方便。比方說你要在一個文本中查找“November”,但是它可能被簡寫為“Nov”.你就能定義一個模式?Nov(ember)?,在捕獲圓括號(capturing parentheses)后加上問號,意味著這個圓括號內(nèi)的內(nèi)容是可選的。
這個圓括號(parentheses)被定義為術(shù)語捕獲(capturing)因為他們捕獲匹配的內(nèi)容,并允許在你的正則表達(dá)式的其他地方引用它。
舉個例子,假使你有一個字符串“Say hi to Harry”.如果你創(chuàng)建一個搜索并替換的正則表達(dá)式,用that guy $1?來替換任一處出現(xiàn)的(Tom|Dick|Harry),結(jié)果就會是“Say hi to that guy Harry”.$1允許你引用前面規(guī)則中的第一個截獲組。
捕獲組和非捕獲組是一些高級的話題,在本教程的后面你會遇到關(guān)于他們的例子。
字符組(Character classes)相當(dāng)于一組字符中匹配單個字符。字符組出現(xiàn)在中括號([?和?])之間。
例如,正則表達(dá)式?t[aeiou]會匹配“ta”、“te”、“ti”、“to”或“tu”。你可以放任意多的字符在中括號中,但是請記住,只能匹配一個字符。[aeiou]看起來是五個字符,但它真實意義卻是“a”或”e“或”i“或”o“或”u“。
如果字符連續(xù)出現(xiàn),你也能在字符組中定義一個范圍。例如,為了搜索在100到109的數(shù)字,模式應(yīng)該用10[0-9]。這和10[0123456789]會返回同樣地結(jié)果,不過,使用范圍來定義你的正則表達(dá)式看起來更簡潔和易于理解。
字符組不止局限于數(shù)字,你同樣可以用字符來這樣做。比如,[a-f]會匹配”a“,”b“,”c“,”d“,”e“或”f“。
字符集通常包含你想要匹配的字符,但是如果你想明確指出不要匹配的字符該怎么辦?同樣你能定義除此之外的字符組,把^放在前面。例如,模式t[^o]就會匹配包含”t“并且后面緊跟的字符是非o的字符。
NSRegularExpressions Cheat Sheet
正則表達(dá)式是一個語法簡單但能組合成非常復(fù)雜的結(jié)果的絕佳例子!即使是一個正則表達(dá)式能手,也會再一些古怪的邊界問題上參考一些小抄。
為了能幫助你理解,我們?yōu)槟闾峁┝苏降?raywenderlich.com的NSRegularExpression Cheat SheetPDF!請下載下來查看。
除此之外,下面是cheat sheet的縮小版,和一些簡短的解釋:
-
.匹配任一字符。p.p匹配pop,pup,pmp,p@p等等。
-
\w匹配任意“word-like”字符,包括數(shù)字,字母,下劃線,不過不能匹配標(biāo)點符號和其他字符。hello\w會匹配”hello_“,”hello9”和”helloo”,但不匹配”hello!”。
-
\d?匹配數(shù)字,大部分情況下是[0-9]。\d\d?:\d\d會匹配時間格式的字符串,比如”9:30“和”12:45“。
-
\b?匹配額外的字符,例如空格,標(biāo)點符號。to\b?會匹配”to the moon”和“to!”中得“to”,但是不會匹配“tomorrow”。\b?用在整個單詞的匹配方面和方便。
-
\s?會匹配空白字符,比如,空格,制表符,換行符。hello\s?會匹配“Well,hello there!”中的 “hello ”。
-
^用在一行的開始。記住,這個特殊的^不同于方括號中的^!例如,^Hello?會匹配字符串“Hello there”,而不會去匹配“He said Hello”。
-
$?用在一行的結(jié)束,例如,the end$?會匹配“It was the end”?而不會去匹配 “the end was near”。
-
*?匹配?它之前的元素0次或多次。12*3??會匹配 13, 123, 1223, 122223, 和 1222222223。
-
+?匹配?它之前的元素1次或多次.?12+3??會匹配 ?123, 1223, 122223, 和 1222222223。
-
花括號{}包含了匹配的最大和值最小個數(shù)。例如,10{1,2}1會匹配“101”和“1001”,而不會匹配“10001”,因為匹配的最小個數(shù)為1,最大個數(shù)為2。He[LI]{2,}o會匹配“HeLLo”和“HellLLLIo”和任意其他的“hello”添加多個L的變種,所以沒有限制,因為,最少的個數(shù)是2,最大的個數(shù)沒有設(shè)置。
有了這些基礎(chǔ)知識,就可以繼續(xù)向下學(xué)習(xí)了。
是時候你自己親自體驗一下這些例子了,它們都包含在上面提到的Playground里了。
Implementing Regex in iOS
既然你有了這些基礎(chǔ),就在APP中應(yīng)用正則表達(dá)式吧。
如果你還沒有這樣做,下載?starter project?開始本教程吧。下載下來,用Xcode打開并運行它。
APP的UI部分已經(jīng)完成了大部分,但這個APP的核心功能依賴與正則表達(dá)式,這個還沒有…!你的任務(wù)就是添加正則表達(dá)式來時這個APP更出色。
下面所視的幾個截圖的例子展示了這個應(yīng)用的內(nèi)容:
這個簡單的應(yīng)用涵蓋兩個正則表達(dá)式的通用用例:
1.執(zhí)行搜索:高亮顯示搜索和替換
2.驗證用戶輸入
這就開始直接使用正則表達(dá)式:文本搜索
/Search( and replace)?/
這是一個搜索/替換的簡單功能的概述:
-
搜索視圖控制器SearchViewController?有一個只讀的UITextView,其內(nèi)容是《傲慢與偏見》的一個片段。
-
navigation bar包含一個搜索按鈕,點擊會呈現(xiàn)一個模態(tài)的SearchOptionsViewController。
-
用戶輸入一些信息并點擊“Search”按鈕。
APP會隱藏這個search view?并高亮顯示textview中所有匹配的內(nèi)容。 -
如果用戶選擇了SearchOptionsViewController中的“Replace”選項,APP會執(zhí)行搜索并替換文本中所有匹配的內(nèi)容,不再是高亮顯示結(jié)果。
Note:你的APP會用到UITextView的NSAttributedString屬性來高亮顯示搜索的結(jié)果。更多這方面的內(nèi)容請參考?iOS 6 by Tutorials的第15章--“What’s New with Attributed Strings”。
你也可以用text kit來實現(xiàn)高亮的功能。確保找到Text Kit Tutorial in Swift?來查看更多內(nèi)容。
還有一個“Bookmark”按鈕,允許用戶高亮顯示文本中的日期,時間,位置。為簡單起見,不會涵蓋文本中出現(xiàn)的各種格式的日期時間位置。在教程的結(jié)尾你可以實現(xiàn)這個高亮功能。
開始實現(xiàn)這個功能的第一步是跳轉(zhuǎn)到標(biāo)準(zhǔn)字符串正則表達(dá)式的NSRegularExpression對象。
打開SearchOptionsViewController.swift。SearchViewController模態(tài)顯示這個view controller,且允許用戶鍵入他們的搜索條件,也可以指定是否區(qū)分大小寫。
看一下文件頭部的SearchOptions結(jié)構(gòu)體,SearchOptions是一個封裝了用戶搜索選項的簡答結(jié)構(gòu)體。代碼傳遞SearchOptions的一個實例給SearchViewController。它用這種方式很好的構(gòu)造一個合適的NSRegularExpression,你可以通過運用擴(kuò)展自定義的NSRegularExpression來實現(xiàn)。
選擇File > New > File…?選擇Swift File,命名為RegexHelpers.swift。打開新建的文件并添加如下代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | extension?NSRegularExpression?{ ??convenience?init?(options:?SearchOptions)?{ ????let?searchString?=?options.searchString ????let?isCaseSensitive?=?options.matchCase ????let?isWholeWords?=?options.wholeWords ?? ????let?regexOption:?NSRegularExpressionOptions?=?(isCaseSensitive)???.allZeros?:?.CaseInsensitive ?? ????let?pattern?=?(isWholeWords)???"\\b\(searchString)\\b"?:?searchString ?? ????self.init(pattern:?pattern,?options:?regexOption,?error:?nil) ??} } |
代碼為NSRegularExpression增加了一個便利構(gòu)造方法。它通過SearchOptions實例的不同設(shè)置來做一些正確的配置。
-
當(dāng)用戶請求一個不區(qū)分大小寫的搜索,正則表達(dá)式使用.CaseInsensitive的CaseInsensitiveNSRegularExpressionOptions值。NSRegularExpression默認(rèn)是區(qū)分大小寫的,這個例子中,你使用的是更有好的不區(qū)分大小寫。
-
如果用戶請求一個完整的單詞,APP把正則表達(dá)式包含在\b字符組之內(nèi)。在單詞邊界字符組中放入\b,因此,搜索模式之前和之后加上\b就會返回一個完整的單詞搜索(舉例來說,模式“\bcat\b”只會匹配單詞“cat”,而不會匹配“catch”)。
如果以任何理由都不能創(chuàng)建NSRegularExpression,構(gòu)造函數(shù)就會失敗并返回nil。既然你有了NSRegularExpression對象,你就能伴隨著其他操作來匹配文本了。
打開SearchViewController.swift,找到searchForText,用下面的代碼替換它。
| 1 2 3 4 5 6 7 8 9 10 | func?searchForText(searchText:?String,?replaceWith?replacementText:?String,?inTextView?textView:?UITextView)?{ ??let?beforeText?=?textView.text ??let?range?=?NSMakeRange(0,?countElements(beforeText)) ?? ??if?let?regex?=?NSRegularExpression(options:?self.searchOptions!)?{ ????let?afterText?=?regex.stringByReplacingMatchesInString(beforeText,?options:?.allZeros,?range:?range,?withTemplate:?replacementText) ?? ????textView.text?=?afterText ??} } |
首先,這個方法捕獲UITextView中得當(dāng)前文本,并計算文本的長度。可能會把正則表達(dá)式應(yīng)用在文本的一個子集上,所以你需要指定一個范圍。這種情況下,你要用字符串的整個長度才能保證正則表達(dá)式被運用在整個文本上。
不可思議的事發(fā)生在調(diào)用stringByReplacingMatchesInString的時候。這個方法返回一個新字符串并沒有改變舊字符串。然后,這個方法給UITextView設(shè)置這個新字符串,所以用戶看到了正確的結(jié)果。
繼續(xù)留在SearchViewController,找到highlightText,用下面的代碼替換它。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | func?highlightText(searchText:?String,?inTextView?textView:?UITextView)?{ ??//?1? ??let?attributedText?=?textView.attributedText.mutableCopy()?as?NSMutableAttributedString ??//?2 ??let?attributedTextRange?=?NSMakeRange(0,?attributedText.length) ??attributedText.removeAttribute(NSBackgroundColorAttributeName,?range:?attributedTextRange) ??//?3 ??if?let?regex?=?NSRegularExpression(options:?self.searchOptions!)?{ ????let?range?=?NSMakeRange(0,?countElements(textView.text)) ????let?matches?=?regex.matchesInString(textView.text,?options:?.allZeros,?range:?range) ???//?4 ????for?match?in?matches?as?[NSTextCheckingResult]?{ ??????let?matchRange?=?match.range ?? ??????attributedText.addAttribute(NSBackgroundColorAttributeName,?value:?UIColor.yellowColor(),?range:?matchRange) ????} ??} ??//?5 ??textView.attributedText?=?attributedText.copy()?as?NSAttributedString } |
這兒就一步一步的解釋上面的代碼:
1.首先,得到一個textview的attributedText的可變拷貝,
2.然后,創(chuàng)建一個整個文本長度的NSRange,并刪除已經(jīng)有背景色的文本的背景色,
3.正如找到和替換,緊接著用你的便利構(gòu)造方法創(chuàng)建一個正則表達(dá)式,獲取一個存放正則表達(dá)式與textview中文本匹配的所有匹配項的數(shù)組。
4.?輪詢每一個匹配項(把它們轉(zhuǎn)換成NSTextCheckingResult對象),并為每一項添加黃色背景。
5.最后,用高亮的結(jié)果更新UITextView。
編譯和運行你的APP,試著搜索一些不同的單詞和詞組!整個文本的匹配項都會高亮顯示,就像下面的圖片所示:
試著使用不同的選項(options)搜索單詞“the”看看效果。注意,例如,當(dāng)搜索整個單詞時,‘them’中得‘the’不會高亮顯示。
再者,測試一下搜索和替換功能,看看你的文本字符串是怎樣如期替換的,試一下’match?case‘和‘whole words’選項。
高亮顯示和替換都是很有用的,但是你怎樣在你的APP中更有效的利用正則表達(dá)式呢?
數(shù)據(jù)驗證
許多Apps都有某種用戶輸入,比如用戶輸入Email地址或電話號碼。你會對這個用戶的輸入執(zhí)行某種級別的數(shù)據(jù)驗證,確保數(shù)據(jù)的完整,如果用戶輸入中有錯誤,通知用戶。
對這類數(shù)據(jù)驗證,正則表達(dá)式是可以完美解決的,因為他們在模式匹配方面是如此出色。
有兩個東西你需要添加到你的APP,驗證模式本身,提供一個機制驗證用戶輸入和和這些模式。確保這些對用戶來說簡單易用,你的APP內(nèi)的驗證不區(qū)分大小寫,這樣,你就可以在你的模式中只使用小寫字母。
作為練習(xí),試著想出一個正則表達(dá)式來驗證下面的字符串(不用考慮大小寫問題):
-
First name?-?應(yīng)該包含一到十個字符長度的標(biāo)準(zhǔn)英語字母。
-
Middle initial?-?應(yīng)該包含一個英語字母。
-
Last name?-?應(yīng)該包含標(biāo)準(zhǔn)英語字母加上撇號‘(apostrophe)(如這樣的名字?O’Brian)?并且二到十個字符長度。
-
Date of birth??-?應(yīng)該是以下格式之一:dd/mm/yyyy, dd-mm-yyyy,?或?dd.mm.yyyy,?且要落在?1/1/1900?和?31/12/2099之間.
當(dāng)然了,當(dāng)你開發(fā)的時候,你可以使用iRegex?playground?來試驗?zāi)愕谋磉_(dá)式
你是怎么想到需要的正則表達(dá)式的?如果你在這兒卡住了,回過頭去看看上面的小抄(cheat sheet)上的片段,上面的方案會對你有幫助的.
下面的劇透會展示給你你要用的正則表達(dá)式。在向下看之前,首先你自己先理解它,然后檢查你自己的結(jié)果!
Solution Inside
| 1 2 3 4 | ??"^[a-z]{1,10}$",????//?First?name ??"^[a-z]$",??????????//?Middle?Initial ??"^[a-z']{2,10}$",???//?Last?Name ??"^(0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\\d\\d$"??//?Date?of?Birth |
打開?SignUpViewController.swift?用下面的代碼替換?viewDidLoad?:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | override?func?viewDidLoad()?{ ??super.viewDidLoad() ?? ??textFields?=?[?firstNameField,?middleInitialField,?lastNameField,?dateOfBirthField?] ?? ??let?patterns?=?[?"^[a-z]{1,10}$",??????//?First?name ???????????????????"^[a-z]$",????????????//?Middle?Initial ???????????????????"^[a-z']{2,10}$",?????//?Last?Name ???????????????????"^(0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\\d\\d$"?]??//?Date?of?Birth ?? ??regexes?=?patterns.map?{ ????NSRegularExpression(pattern:?$0,?options:?.CaseInsensitive,?error:?nil) ??} } |
在此view controller中創(chuàng)建一個text fields數(shù)組,和一個字符串模式數(shù)組。然后用swift的map函數(shù)?創(chuàng)建一個NSRegularExpression對象的數(shù)組,一一對應(yīng)。
為了創(chuàng)建正則表達(dá)式來驗證first name?,首先要匹配字符串的開頭,然后匹配一個從a到z范圍的字符組,最后匹配字符串的結(jié)尾來確保它是在1到10個字符的長度。
接下來的第二個模式,middle initial,和last name,遵循同樣地邏輯。middle initial的情況下,你不必指定長度{1}--因為^[a-z]$默認(rèn)匹配一個字符。
Note:此處你不必?fù)?dān)心大小寫問題--當(dāng)實例化正則表達(dá)式時,會處理它。
對于出生日期,可能要麻煩一些。匹配字符串的開頭,然后是“月”部分,你要有一個捕獲組來匹配01,?02,?03,?04,?05,?06,?07,?08,?09,?10,?11?或?12中得一個,后面跟一個捕獲組匹配-,/或.。
?對于”天“部分,你需要另一個捕獲組來匹配01,?02, …?29,?30, or?31,后面跟一個捕獲組匹配-,/或.。
最后,需要一個捕獲組來匹配19或20,后面跟兩個數(shù)字字符。
你就得到了創(chuàng)造性的正則表達(dá)式。也有另外的方式解決上面的問題,比如用\d代替[0-9]。當(dāng)然了,只要能正常工作的方案就是最好的方案。
Note:實際應(yīng)用中,你很可能不會用正則表達(dá)式來驗證時間(更不會在一個日期范圍內(nèi)檢查它)。相反,你很可能會使用NSDateFormatter來從字符串解析成日期,然后比較解析的NSDate和引用的日期。
既然你掌握了這個模式,你需要驗證每個text field中輸入的文本。
還是停留在SignUpViewController.swift,找到validateTextField,并用下面的內(nèi)容替換實現(xiàn)部分。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | func?validateTextField(textField:?UITextField)?{ ??let?index?=?find(textFields,?textField) ??if?let?regex?=?regexes[index!]?{ ????let?text?=?textField.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) ????let?range?=?NSMakeRange(0,?countElements(text)) ?? ????let?matchRange?=?regex.rangeOfFirstMatchInString(text,?options:?.ReportProgress,?range:?range) ?? ????let?valid?=?matchRange.location?!=?NSNotFound ?? ????textField.textColor?=?(valid)???UIColor.trueColor()?:?UIColor.falseColor() ??} } |
這與在SearchViewController.swift所做很相似,從regexes數(shù)組中拿取相關(guān)的正則表達(dá)式開始,去掉用戶在textfield中輸入內(nèi)容中含有的所有空白字符,然后創(chuàng)建一個包含整個文本的范圍。
為了準(zhǔn)確的檢查匹配,代碼測試了rangeOfFirstMatchInString(_:options:range:)的結(jié)果。這可能是檢查匹配最有效的方式,因為這個調(diào)用在它發(fā)現(xiàn)第一個匹配后就早早退出了。然而,假使你需要知道所有的匹配,也有其他的選擇,如numberOfMatchesInString(_:options:range:)
運行程序,點擊右下方的Contacts?按鈕,試著在sign up中輸入一些信息,當(dāng)你完成每個field,你會看到它的文本依據(jù)是否有效變綠或變紅效果如下方截圖:
上面的代碼用了stringByTrimmingCharactersInSet來刪除用戶輸入的開頭和結(jié)尾的空格-否則如果有空格在的話就會驗證失敗。
這是stringByTrimmingCharactersInSet的一個固定用法,但是作為怎么處理正則表達(dá)式的教程,還有一個更有趣的方向,考慮怎樣通過正則表達(dá)式來實現(xiàn)。
去掉空格,一個小插曲。
這種情況你有兩種選擇:????
要么更新你的模式來處理首尾處的空格,要么在你一個用驗證模式之前創(chuàng)建并應(yīng)用另一個模式來去掉首尾的空格。第二種方法保證了驗證模式的簡單簡潔,也可以在它的方法內(nèi)部重構(gòu),你需要的只是一個正則表達(dá)式。
你想到一個去除首位空格的正則表達(dá)式了嗎?在你看下面的結(jié)果前自己試一下。
Solution Inside
| 1 | (?:^\\s+)|(?:\\s+$) |
好極了,模式^\s+會發(fā)現(xiàn)開頭的空格,\s+$會找到結(jié)尾處的空格。現(xiàn)在有了一個匹配空格的正則表達(dá)式,是時候用用它了。在SignUpViewController.swift的底部,類定義花括號外面,添加如下代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | extension?String?{ ??func?stringByTrimmingLeadingAndTrailingWhitespace()?->?String?{ ????let?leadingAndTrailingWhitespacePattern?=?"(?:^\\s+)|(?:\\s+$)" ?? ????if?let?regex?=?NSRegularExpression(pattern:?leadingAndTrailingWhitespacePattern,?options:?.CaseInsensitive,?error:?nil)?{ ??????let?range?=?NSMakeRange(0,?countElements(self)) ??????let?trimmedString?=?regex.stringByReplacingMatchesInString(self,?options:?.ReportProgress,?range:range,?withTemplate:"$1") ?? ??????return?trimmedString ????}?else?{ ??????return?self ????} ??} } |
就像你在NSRegularExpression中所作的一樣,添加一個String的實例方法,在我們看這個方法如何實現(xiàn)之前,改變validateTextField,通過改變下面這個去除空格的行使用這個新方法:
| 1 | let?text?=?textField.text.stringByTrimmingLeadingAndTrailingWhitespace() |
這個String的新方法用上邊的模式創(chuàng)建了一個正則表達(dá)式,返回一個匹配出都用$1替代的新字符串,但是$1是什么意思呢?
當(dāng)你有個正則表達(dá)式的捕獲組(用小括號()來表示),你可以用$和數(shù)字來引用組的內(nèi)容。正則表達(dá)式文檔指出叫反引用。數(shù)子指出引用那個捕獲組。
作為例子,給出一個正則表達(dá)式:
| 1 | Customer?ID:?(\d{3})-(\d{4}) |
匹配下面的文本:
| 1 | Customer?ID:?123-0089 |
$1的值會試123,$2的值會是0089,多有用的工具!
回頭看這個空格的例子,$1只是用它自身--一個捕獲組來取代空格,實際什么也沒做?
這種情況下,圓括號中的?:告訴正則表達(dá)式引擎創(chuàng)建一個非捕獲組。也就是說,匹配的文本不會像正常情況下那樣存在緩沖區(qū)。
既然第一個捕獲組是一個非捕獲組,引擎自然不會捕獲任何東西--因此它是空的!這樣,引擎匹配空格結(jié)束,去掉空格,有效的移除了首尾的空格。
當(dāng)然了,這是捕獲值的特殊用法。實際上你可以只用一個空字符串,“”,作為你的模板值。
More on Capture Groups
舉個更實際的例子,假設(shè)你想要選擇一個文件中你所有的用戶ID,讓四位數(shù)字的部分在三位數(shù)字的前面,你想要兩組數(shù)字之前的間隔更大一些,一個連字符換成兩個?更具體的來說是你想這樣:
| 1 2 3 4 | Bob?ID:?143-5546 Ellen?ID:?447-6091 Piper?ID:?314-1596 Easy?ID:?217-1828 |
變換成:
| 1 2 3 4 | Bob?ID:?5546?--?143 Ellen?ID:?6091?--?447 Piper?ID:?1596?--?314 Easy?ID:?1828?--?217 |
你回怎樣做呢?下面的劇透就是答案。但是你先自己試試。
Solution Inside
| 1 2 3 4 5 6 7 8 9 | let?cutomerIDS?=?["Bob?ID:?143-5546","Ellen?ID:?447-6091","Piper?ID:?314-1596",?"Easy?ID:?217-1828"] //?To?reverse?the?ID's,?you'd?ordinarily?iterate?over?the?array?above, //?but?this?is?what?would?happen?in?each?iteration. let?regexSearchPattern?=?"^(\\w+\\s+ID:\\s+)(\\d{3})-(\\d{4})$" let?regexReplacementPattern?=?"$1$3?--?$2" let?newCustomerID1?=?replaceMatches(regexSearchPattern,?inString:?"Bob?ID:?143-5546",?withString:regexReplacementPattern) let?newCustomerID2?=?replaceMatches(regexSearchPattern,?inString:?"Ellen?ID:?447-6091",?withString:regexReplacementPattern) let?newCustomerID3?=?replaceMatches(regexSearchPattern,?inString:?"Piper?ID:?314-1596",?withString:regexReplacementPattern) let?newCustomerID4?=?replaceMatches(regexSearchPattern,?inString:?"Easy?ID:?217-1828",?withString:regexReplacementPattern) |
在這個例子包含的playground結(jié)尾處,你能看到這個例子的效果。
Note:這個正則表達(dá)式在name和ID之間允許任意數(shù)量的空格,在“ID:”和真正的ID值中間也是如此。
如果你一直糾結(jié)于非捕獲,捕獲和反向引用,在playground中試試下面的不同情況,看看會是什么結(jié)果(建議:你可以使用‘replaceMatches’函數(shù)):
-
用“(^\\s+)|(\\s+$)”替換上面的空格模式,template參數(shù)用“BOO”替換
-
用“(?:^\\s+)|(\\s+$)”替換上面的空格模式,template參數(shù)用“$1BOO”替換
-
用“(?:^\\s+)|(\\s+$)”替換上面的空格模式,template參數(shù)用“$2BOO”替換
Handling Multiple Search Results
還沒有實現(xiàn)導(dǎo)航條上的書簽按鈕。當(dāng)用戶點擊它時,APP應(yīng)該高亮顯示文本中的日期,時間,位置。
打開SearchViewController.swift,找到書簽按鈕的實現(xiàn):
| 1 2 3 4 5 6 7 | //MARK:?Underline?dates,?times,?and?locations ?? @IBAction?func?underlineInterestingData(sender:?AnyObject)?{ ??underlineAllDates() ??underlineAllTimes() ??underlineAllLocations() } |
上面這個方法調(diào)用三個其他的輔助方法來給日期,時間,地點加下劃線。如果你看向者三個輔助方法,你會看到他們都是空方法!
首先,填上每個方法的實現(xiàn)。用下面的內(nèi)容替換他們:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | func?underlineAllDates()?{ ??if?let?regex?=?NSRegularExpression.regularExpressionForDates()?{ ????let?matches?=?matchesForRegularExpression(regex,?inTextView:?textView) ????highlightMatches(matches) ??} } ?? func?underlineAllTimes()?{ ??if?let?regex?=?NSRegularExpression.regularExpressionForTimes()?{ ????let?matches?=?matchesForRegularExpression(regex,?inTextView:?textView) ????highlightMatches(matches) ??} } ?? func?underlineAllLocations()?{ ??if?let?regex?=?NSRegularExpression.regularExpressionForLocations()?{ ????let?matches?=?matchesForRegularExpression(regex,?inTextView:?textView) ????highlightMatches(matches) ??} } |
每個方法調(diào)用NSRegularExpression的一個工廠方法來創(chuàng)建一個合適的正則表達(dá)式。這些還不存在,但是這是一個方便的地方封裝這個行為。這個方法找到匹配項,調(diào)用highlightMatches來給文本中的每個字符串著色和添加下劃線。如果你有興趣看它如何實現(xiàn),查看它的實現(xiàn)。
現(xiàn)在填入正則表達(dá)式方法。打開RegexHelpers.swift?并在NSRegularExpression?的擴(kuò)展內(nèi)添加下面的內(nèi)容。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class?func?regularExpressionForDates()?->?NSRegularExpression??{ ??let?pattern?=?"?" ??return?NSRegularExpression(pattern:?pattern,?options:?.CaseInsensitive,?error:?nil) } ?? class?func?regularExpressionForTimes()?->?NSRegularExpression??{ ??let?pattern?=?"?" ??return?NSRegularExpression(pattern:?pattern,?options:?.CaseInsensitive,?error:?nil) } ?? class?func?regularExpressionForLocations()?->?NSRegularExpression??{ ??let?pattern?=?"?" ??return?NSRegularExpression(pattern:?pattern,?options:?.allZeros,?error:?nil) } |
現(xiàn)在你來實現(xiàn)這些模式,這是一些你需要的內(nèi)容:
Date Requirements:
-
xx/xx/xx or xx.xx.xx or xx-xx-xx格式。日,月,年,的防治不是很重要,因為代碼只是高亮顯示他們。例如:10-05-12.
-
全稱和縮寫月的名字(如,Jan或January,Feb或February等),之后跟著一兩個數(shù)字(如:x或xx).日可能是序數(shù)詞(如:1st, 2nd, 10th, 21st等),之后跟一個逗號作為分隔符,然后是一個四位的數(shù)字(如,xxxx).在日月年兩兩之間可能包含零至多個空白。例如:March 13th,?2001。
Time requirements:
-
找出像“9am”?或?“11?pm”的簡單時間:一兩位數(shù)字跟著一個或多個空格,再后面跟著小寫的“am”?或?“pm”。
Location requirements:
-
至少一個字符的任意單詞,緊跟著一個逗號,再跟著零個或多個空格,再跟著兩個大寫的英語字母組合。例如“Boston, MA”。
你可以用playground試驗一下。看是否能勾勒出需要的正則表達(dá)式!
這是三個簡單的模式。用下面的內(nèi)容替換regularExpressionForDates中的空模式
| 1 | let?pattern?=?"(\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{1,2})|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)\\s*(\\d{1,2}(st|nd|rd|th)?+)?[,]\\s*\\d{4}" |
這個模式被|(或)分成了兩部分。意味著或者第一部分匹配或者第二部分匹配。
第一部分內(nèi)容:(\d{1,2}[-/.]\d{1,2}[-/.]\d{1,2})。意味著兩個數(shù)字之后跟著一個-或/或.。之后再跟著兩個數(shù)字,再跟著-或/或.,最后跟兩個數(shù)字。
第二部分以??(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)開頭,它會匹配一個全稱或簡稱的月的名字。
接下來是?\\s*\\d{1,2}(st|nd|rd|th)?,它會匹配零個或多個空格,后面跟一到兩個數(shù)字,再之后跟著一個可選的序數(shù)詞后綴。例如,它會匹配“1”?和?“1st”。
最后,[,]\\s*\\d{4}會匹配一個逗號,之后會跟著零個或多個空格,再之后跟著一個表示年的四位數(shù)字。
多恐怖的一個正則表達(dá)式!不過,你可以看到正則表達(dá)式的簡潔,和把大量信息包裝成一個看似神秘的字符串的功能的強大!
接下來,是regularExpressionForTimes和regularExpressionForLocations的模式,把下面的內(nèi)容填進(jìn)空白的模式。
| 1 2 3 4 5 | //?Times let?pattern?=?"\\d{1,2}\\s*(pm|am)" ?? //?Locations let?pattern?=?"[a-zA-Z]+[,]\\s*([A-Z]{2})" |
作為練習(xí),看看你能否根據(jù)上面的要求解釋一下這個正則表達(dá)式模式。
編譯并運行這個APP,點擊Bookmark圖標(biāo)。你應(yīng)該會看到高亮顯示的日期,時間,位置,如下所示:
這個例子就到這兒了,你能明白為什么這個對于時間的正則表達(dá)式不能正確進(jìn)行更通用的搜索嗎?按現(xiàn)在的情況,它不會匹配3:15pm,它會匹配28pm。
這是一個有挑戰(zhàn)性的問題!想想怎樣重寫這個關(guān)于時間的正則表達(dá)式,來讓它匹配更通用的時間格式。
具體來說,你的答案應(yīng)該匹配12小時制的ab:cd am/pm時間格式。所以它應(yīng)該能匹配11:45?am,?10:33pm,?04:12am?但不能匹配?2pm,?0:00am?18:44am?9:63pm?或?7:4?am。在am/pm前應(yīng)該有至少一個空格。如果它匹配了14:33am中得4:33am,這也是可以接受的。
下面是一個可行的答案,但是你自己先試一下。在附帶的playground尾部看一下它的效果。
Solution Inside
| 1 | "(1[0-2]|0?[1-9]):([0-5][0-9]\\s?(am|pm))" |
接下來要做什么呢?
是你依據(jù)上邊的教程開發(fā)的最終例子example project?。
恭喜你!現(xiàn)在你已經(jīng)有了一些正則表達(dá)式使用方面的實踐經(jīng)驗。
正則表達(dá)式是強大的,使用它也很有趣,他們很像解決數(shù)學(xué)問題。正則表達(dá)式的彈性讓我們有很多種方法來創(chuàng)建一個模式去適應(yīng)你的需求,例如過濾輸入字符串的空格,在解析前去除HTML或XML標(biāo)簽,或者是,找出特殊的XML或HTML標(biāo)簽等等!
有很多現(xiàn)實世界的字符串例子,你可以用正則表達(dá)式去驗證。
作為最后的練習(xí),試圖解開下面這個正則表達(dá)式來驗證一個郵箱地址(validates an email address):
| 1 | [a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? |
乍看起來,它看起來是一堆雜亂的字符串,但是,用你新發(fā)現(xiàn)的知識(下面的鏈接很有用),你會一步一步理解它,并成為正則表達(dá)式的高手!
這是一些關(guān)于正則表達(dá)式很有用的資源列表:
-
www.regular-expressions.info?是Jan Goyvaerts的一個非常有信息量的站點。他也出版了一些關(guān)于正則表達(dá)式很全面的書。
-
NSRegularExpression Class Reference?也是你用正則表達(dá)式API最好的參考。
-
一些正則模式的快速測試,regexpal.com是很方便的資源。
假使你之前錯過了這些鏈接,看一下這些我們?yōu)槟銣?zhǔn)備的資源:
-
Regular expression playground
-
NSRegularExpression cheat sheet
希望你喜歡這個教程,如果你有任何意見和問題,請加入下面的論壇!
轉(zhuǎn)載于:https://www.cnblogs.com/yjg2014/p/4444194.html
總結(jié)
以上是生活随笔為你收集整理的iOS开发之详解正则表达式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 获取刚插入行id汇总
- 下一篇: linux 内核线程与普通进程的区别