正則表達式NSRegularExpression

正則表達式又稱為正規(guī)表示法、規(guī)則表達式、常規(guī)表示法,英語為Regular Expression,常簡寫為regex、regexp或RE。正則表達式使用單個字符串來描述,匹配一系列符合某個句法規(guī)則的字符串,也就是正則表達式可以用簡明的符號來描述大量的可能性。

在文本編輯器中,正則表達式可以用來檢索、替換匹配某個模式的文本。如,三個字母后跟三位數(shù)字,三個字母后跟冒號。使用這種模式匹配可以驗證電話號碼、郵箱地址、用戶輸入文本等內(nèi)容是否符合格式要求,也可以執(zhí)行替換等高級操作。

通過這篇文章,你將學會如何使用正則表達式檢索符合某個模式的文本、替換文本,檢驗用戶輸入內(nèi)容是否符合要求格式,查找并突出顯示文本。

基本語法

一個正則表達式通常被稱為一個模式(pattern),為用來描述或匹配符合某個句法規(guī)則的字符串。例如:pro648Pro648這兩個字符串都可以由(p|P)ro648這個模式描述。大部分正則表達式的形式都有如下結(jié)構(gòu):

選擇

豎直分隔符|代表選擇。例如gray|grey可以匹配graygrey

匹配

圓括號 ( )可以用來定義操作符的范圍和優(yōu)先級。例如:gr(a|e)ygray|grey相同,(grand)?father匹配fathergrandfather

限定符

限定符用來指定正則表達式的一個給定組建必需要出現(xiàn)多少次才能滿足匹配。最常見的數(shù)量限定符包括+?*。不添加限定符表示出現(xiàn)且只出現(xiàn)一次。

  • +:代表前面的字符至少出現(xiàn)一次(1次、或多次)。如:Goo+gle可以匹配googlegoooglegoooogle等,但不匹配gogle
  • ?:代表前面的字符最多出現(xiàn)一次(0次,或1次)。如:colou?r可以匹配colorcolour
  • *:代表前面的字符可以不出現(xiàn)、出現(xiàn)一次、多次(0次,或1次,或多次)。如:p*ro648可以匹配ro648pro648ppro648pppro648等。

在這篇文章會創(chuàng)建一系列Regex,如果你想更直觀觀察、測試結(jié)果,可以使用在線正則表達式工具。

上述的這些構(gòu)造子可以自由組合,下面是常用正則表達式符號,

NSRegularExpression參照表

正則表達式語法很簡潔,經(jīng)常會出現(xiàn)多種符號結(jié)合使用。下面是常用正則表達式符號含義匯總表。你可以點擊這里收藏這份正則表達式語法含義表。

  • 特殊字符
* ? + [ ] ( ) { } ^ $ | \ . / 

如果需要匹配這些特殊符號,需要在其前面用\標志出。

  • 元字符
字符 描述
[pattern] 匹配pattern中的任一字符。如:[a-z]匹配a-z的任一字符。
. 匹配\n之外的任何字符。
^ 匹配字符的開始位置。
$ 匹配字符的結(jié)束位置。
\ 將下一個字符標記為一個特殊字符、或一個原義字符、或一個向后引用。如:n匹配字符n\n匹配一個換行符。序列\\匹配\,\(匹配( 。
\b 匹配單詞邊界,邊界發(fā)生在單詞(\w)和非單詞(\W)字符之間的轉(zhuǎn)換處。如:ne\b可以匹配throne中的ne,但不能匹配Chinese中的ne,也能匹配throne!中的ne,因為!是非單詞。
\B 匹配非單詞邊界。ne\B可以匹配Chinese中的ne,不能匹配throne中的ne
\cX 匹配control-X字符。如:\cM匹配一個control-M或回車符。X的值必需為a-z或A-Z之一。否則,c將被視為一個原義的c字符。
\d 匹配一個數(shù)字字符,等價于[0-9]。
\D 匹配一個非數(shù)字字符,等價于[^0-9]。
\f 匹配一個換頁符。
\n 匹配一個換行符。
\s 匹配任何空白符,包括空格、制表符、換頁符等等。等價于[\t\n\f\r\p{Z}]
\S 匹配任何非空字符。
\w 匹配包括下劃線的任何單詞字符。等價于[a-zA-Z0-9]。
\W 匹配任何非單詞字符。等價于[^a-zA-Z0-9]。
  • 運算符
字符 描述
| 或,A |B匹配AB
* 匹配零次、或多次,盡可能多的匹配,即貪婪模式(greediness)。如:zo*能匹配zzozoo等。*等價于{0,}
+ 一次、或多次,盡可能多的匹配。如:zo+能匹配zo以及zoo,但不能匹配z+等價于{1,}
? 匹配零次、一次,優(yōu)先匹配一次。(n)?ever可以匹配never以及ever?等價于{0,1}
{n} 匹配n次。n為非負整數(shù),大括號內(nèi)不能有空格。如:o{2}不能匹配word中的o,但能匹配Google中的兩個o
{n,} 至少匹配n次,盡可能多的匹配。n為非負整數(shù)。0{2,}不能匹配word中的o,但能匹配gooooogle中的所有o
{n,m} 至少匹配n次,最多匹配m次,盡可能多的匹配。n和m均為非負整數(shù),且n<=m。
*? 匹配零次、或多次。盡可能少的匹配,即懶惰模式(laziness)。
+? 匹配一次、或多次,盡可能少的匹配。
?? 匹配零次、或一次,優(yōu)先匹配零次。
{n}? 匹配n次。
{n,}? 至少匹配n次,但不超過整體模式匹配所需。
{n,m}? 匹配n至m次,盡可能少的匹配,但不少于n次。
*+ 匹配零次、或多次。第一次遇到時,盡可能多地匹配,即使整體匹配失敗,也不回溯,稱為Possessive Match。點擊查看possessive match和貪婪匹配區(qū)別。
++ 匹配一次、或多次。Possessive Match
?+ 匹配零次、或一次。Possessive Match
{n}+ 匹配n次。
{n,}+ 至少匹配n次。Possessive Match
{n,m}+ 匹配n至m次,Possessive Match
(pattern) 匹配pattern,并捕獲這一匹配的捕獲組,該子字符串用于向后引用。
(?:pattern) 匹配pattern但不捕獲這一匹配的子字符串,也就是說這是一個不捕獲匹配,不存儲匹配的子字符串用于向后引用,比捕獲組高效。
(?=pattern) 正向肯定預查(Look-ahead assertion)。在任何匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配。如:Windows(?=7 |8 |8.1 |10)能匹配Windows10中的Windows,但不能匹配Windowsxp中的Windows。預查不消耗字符,也就是在一個匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。
(?!pattern) 正向否定預查( Negative look-ahead assertion),在任何不匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配。如:Windows(?!7 |8 |8.1 |10)能匹配Windowsxp中的Windows,但不能匹配Windows10中的Windows。預查不消耗字符。
(?<=pattern) 反向肯定預查(Look-behind assertion),與正向肯定預查類似,只是方向相反。如:(?<=7 |8 |8.1 |10)Windows能匹配7Windows中的Windows,但不能匹配xpWindows中的Windows
(?<!pattern) 反向否定查詢(Negative Look-behind assertion),與正向否定預查類似,只是方向相反。如:(?<!7 |8 |8.1 |10)Windows能匹配xpWindows中的Windows,但不能匹配7Windows中的Windows
  • 其它
符號 描述
\n 向后引用,匹配第n個捕獲組。其中,1<= 正整數(shù)n <=捕捉組總數(shù)。
$n n為非負整數(shù),向后引用第n個捕捉組,0<= n <=捕捉組總數(shù)。$后沒有數(shù)字時該符號沒有任何特殊含義。
  • 優(yōu)先級

在這些運算符同時出現(xiàn)時,按照下面的優(yōu)先級進行操作。

優(yōu)先級 符號
最高 \
( )、(?: )、(?= )、[ ]
*、+、?、{n}、{n,}、{n,m}
^、$、中介字符
最低 |

如果想要收藏上面的參照表備用,打開iOS正則表達式語法全集網(wǎng)頁加入書簽。

在iOS中使用正則表達式

現(xiàn)在我們已經(jīng)了解了regex的基本內(nèi)容,下面開始學習在iOS中如何使用regex。

這篇文章的重點在于regex,這里不再描述其它細節(jié)問題,請直接下載NSRegularExpression模板文件。下載完成后,在xcode中運行該模板。

這個demo的界面部分已經(jīng)完成,只剩Regular Expression部分。

下面是demo界面的運行截圖。

RegexFirst.png
RegexSearch.png
RegexSecond.png
RegexThird.png

在這個demo里,正則表達式有以下三項用途:

  1. 進行文本查找、替換。
  2. 驗證用戶輸入文本是否符合規(guī)范格式。
  3. 自動格式化用戶輸入文本。

查找和替換

在這個demo里,查找和替換功能實現(xiàn)過程如下:

  • FirstViewController上有一個僅讀的文本UITextView,文本視圖內(nèi)包含需要處理的文本。
  • 點擊導航欄上的搜索按鈕,會以模特形式呈現(xiàn)SearchViewController
  • SearchViewController 的文本框中輸入相關(guān)信息,點擊Search按鈕。
  • 此時,app會退出SearchViewController ,并在UITextView突出顯示所有匹配文本。
  • 如果開啟了SearchViewController 中的Replace選項,app會執(zhí)行查找并替換功能。

點擊FirstViewController 導航欄上的Bookmarks按鈕可以突出顯示UITextView中任何日期、時間和地址。為了簡單起見,這里不會包括所有格式的日期、時間和地址。在這篇文章結(jié)束前,再來實現(xiàn)這項功能。

想要實現(xiàn)查找功能,首先需要把pattern字符串轉(zhuǎn)變?yōu)?code>NSRegularExpression對象。

進入FirstViewController.m,將regularExpressionWithString: options: 中的代碼用如下代碼替換。

// Create a regular expression with given string and options
- (NSRegularExpression *)regularExpressionWithString:(NSString *)string options:(NSDictionary *)options
{
    // Create a regular expression
    BOOL isCaseSensitive = [[options objectForKey:kSearchCaseSensitiveKey] boolValue];
    BOOL isWholeWords = [[options objectForKey:kSearchWholeWordsKey] boolValue];
    
    NSRegularExpressionOptions regexOptions = isCaseSensitive ? 0 : NSRegularExpressionCaseInsensitive;
    NSString *placeHolder = isWholeWords ? @"\\b%@\\b" : @"%@" ;
    NSString *pattern = [NSString stringWithFormat:placeHolder,string];
    
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:regexOptions error:&error];
    if (error) {
        NSLog(@"Could't create regex with given string and options");
    }
    
    return regex;
}

通過調(diào)用regularExpressionWithPattern: options: error:方法把字符串轉(zhuǎn)換為NSRegularExpression對象。再此之前,需要注意以下兩點:

  • 如果Match Case開關(guān)狀態(tài)為開啟,設(shè)置NSRegularExpressionOptions的值為0,此時,查找會區(qū)分字母大小寫,即默認區(qū)分字母大小寫。開關(guān)狀態(tài)為關(guān)閉時,設(shè)置NSRegularExpressionOptions的值為NSRegularExpressionCaseInsensitive,此時,查找不區(qū)分字母大小寫。
  • 如果Whole Words開關(guān)狀態(tài)為開啟,在pattern前后添加\b用以匹配單詞邊界,這時查找的會是整個單詞。

把字符串轉(zhuǎn)換為NSRegularExpression對象后,就可以用來匹配文本。

searchAndReplaceText方法中添加如下代碼:

// Search for a searchString and replace it with the replacementString in the given text view with search options.
- (void)searchAndReplaceText:(NSString *)string withText:(NSString *)replacement inTextView:(UITextView *)textView options:(NSDictionary *)options{
    // Text before replacement
    NSString *beforeText = textView.text;
    
    // Create a range for it, We do the replacement on the whole range of the text view, not noly a portion of it.
    NSRange range = NSMakeRange(0, beforeText.length);
    
    // Call the convenience method to create a regex for us with the options we have.
    NSRegularExpression *regex = [self regularExpressionWithString:string options:options];
    
    // Call the NSRegularExpression method to do the replacement for us.
    NSString *afterText = [regex stringByReplacingMatchesInString:beforeText options:0 range:range withTemplate:replacement];
    
    // Update UI
    textView.text = afterText;
}

首先,獲取當前文本視圖的內(nèi)容,得到當前文本的NSRange對象。因為也可以對當前文本的部分字符串使用正則表達式,所以這里需要明確指出使用正則表達式的范圍。在這里指定范圍為整個字符串。

stringByReplacingMatchesInString: options: range: withTemplate:方法內(nèi)執(zhí)行替換操作,該方法沒有修改原來字符串,直接返回一個替換后的字符串,之后把返回的字符串賦值給UITextView,以便更新UI。

修改searchText: inTextView: options:方法如下:

- (void)searchText:(NSString *)string inTextView:(UITextView *)textView options:(NSDictionary *)options{
    // 1.Range of visible text
    NSRange visibleRange = [self visibleRangeOfTextView:textView];
    
    // 2.Get a mutable sub-range of attributed string of the text view that is visible.
    NSMutableAttributedString *visibleAttributedText = [textView.attributedText attributedSubstringFromRange:visibleRange].mutableCopy;
    
    // Get the string of the attributed text.
    NSString *visibleText = visibleAttributedText.string;
    
    // 3.Create a new range for the visible text. This is different from visibleRange. visibleRange is a portion of all textView that is visible, but visibleTextRange is only for visibleText, so it starts at 0 and its length is the length of visibleText.
    NSRange visibleTextRange = NSMakeRange(0, visibleText.length);
    
    // 4.Call the convenient method to create a regex for us with the options we have.
    NSRegularExpression *regex = [self regularExpressionWithString:string options:options];
    
    // 5.Find matches
    NSArray *matches = [regex matchesInString:visibleText options:NSMatchingReportCompletion range:visibleTextRange];
    
    // 6.Iterate through the matches and highlights them.
    for (NSTextCheckingResult *result in matches) {
        NSRange matchRange = result.range;
        [visibleAttributedText addAttribute:NSBackgroundColorAttributeName value:[UIColor yellowColor] range:matchRange];
    }
    
    // 7.Replace the range of the attributed string that we just highlighted. First, create a CFRange from the NSRange of the visible range.
    CFRange visibleRange_CF = CFRangeMake(visibleRange.location, visibleRange.length);
    
    // Get a mutable copy of the attributed text of the text view.
    NSMutableAttributedString *textViewAttributedString = self.textView.attributedText.mutableCopy;
    
    // Replace the visible range.
    CFAttributedStringReplaceAttributedString((__bridge CFMutableAttributedStringRef)textViewAttributedString, visibleRange_CF, (__bridge CFAttributedStringRef)(visibleAttributedText));
    
    // 8.Update UI.
    textView.attributedText = textViewAttributedString;
}

以下是上述代碼的分步說明:

  1. 只對當前屏幕顯示文本進行查找、替換,會比一次查找、替換整個UITextView上文本高效很多。visibleRangeOfTextView:方法會返回當前UITextView上可見文本的NSRange
  2. 獲取文本視圖可見部分屬性的字符串,會在下面使用到。
  3. 為可見文本創(chuàng)建NSRange類型的visibleTextRange對象,第一步創(chuàng)建的visibleRange確定文本視圖的哪一部分可見,但visibleTextRange僅用于提取到的可見文本的子字符串,因此,開始位置為0,長度為visibleText的長度。這個是需要傳入正則表達式引擎的范圍。
  4. 根據(jù)提供的options,調(diào)用regularExpressionWithString: options:方法創(chuàng)建regex
  5. 把所有匹配的文本保存到matches數(shù)組,該數(shù)組內(nèi)的對象都是NSTextCheckingResult類型實例對象。
  6. 遍歷matches數(shù)組,突出顯示匹配的文本。
  7. 替換剛剛突出顯示的屬性字符串。為此,從可見范圍的visibleRange創(chuàng)建CFRange類型的visibleRange_CF。獲取文本視圖中屬性文本的可變副本并執(zhí)行替換。
  8. 用突出顯示的屬性文本替換之前屬性文本,用以更新UITextView

在app中實現(xiàn)搜索、突出顯示和渲染屬性字符串是一個非常實用的功能,但也會帶來性能成本,特被是文本很長時。為了高效實現(xiàn)這些功能,可以只對可見區(qū)域執(zhí)行查找、突出顯示等操作。

然而,當你滑動文本視圖時,可見區(qū)域沒有執(zhí)行查找、突出顯示等操作。所以,需要一個在滑動時不斷更新UI的機制。為實現(xiàn)該功能,需要實現(xiàn)兩個UIScrollViewDelegate代理方法,以便在滑動視圖時更新文本內(nèi)容。

FirstViewController.m內(nèi)添加scrollViewWillEndDragging: withVelocity: targetContentOffset:方法和scrollViewDidEndDecelerating:方法,代碼如下:

#pragma mark 
#pragma mark UIScrollViewDelegate

// Called when the user finishes scrolling the content.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
    if (CGPointEqualToPoint(velocity, CGPointZero)) {
        if (self.lastSearchString && self.lastSearchOptions && !self.lastReplacementString) {
            [self searchText:self.lastSearchString inTextView:self.textView options:self.lastSearchOptions];
        }
    }
}

// Called when the scroll view has ended decelerating the scrolling movement.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    if (self.lastSearchString && self.lastSearchOptions && !self.lastReplacementString) {
        [self searchText:self.lastSearchString inTextView:self.textView options:self.lastSearchOptions];
    }
}

現(xiàn)在,滑動文本視圖時,匹配的文本會自動突出顯示。

運行demo,在searchTextField中輸入要查找的字符串pattern,按照需要設(shè)置其它選項,點擊Search按鈕進行查找,匹配項顯示如下。

RegexSearchReplace.png
RegexSearchResult.png

滑動文本視圖,可以看到字符串youyour均以黃色突出顯示。你可自行測試查找替換功能是否可用。

數(shù)據(jù)驗證

很多app中都會有提供用戶輸入文本的文本框,如賬戶名、手機號碼、email地址等。一般會對這些輸入的文本進行檢查,用以確保其符合規(guī)范,同時提示用戶。

Regular Expression對于這種數(shù)據(jù)驗證是完美的,因為他們非常適用于解析字符串模式。

要進行數(shù)據(jù)驗證需要添加數(shù)據(jù)驗證pattern,然后使用這些pattern驗證用戶輸入的數(shù)據(jù)。為了方便,這里不區(qū)分字母大小寫,所以這里的pattern只需要處理小寫字母的情況。

作為練習,嘗試為下面文本字符串寫出正則表達式來進行數(shù)據(jù)驗證,不需要處理字母大小寫的情況。

  • First Name:由一至十個英文字母構(gòu)成。
  • Middle Initial:為一個英文字母。
  • Last Name:由英文字母和'構(gòu)成,字符串長度在二至十之間。
  • Date of Birth:生日范圍在1/1/1990和12/31/2099之間,日期格式應為:dd/mm/yyyy,dd-mm-yyyy或dd.mm.yyyy。

你可以自己寫出所需的正則表達式,如果遇到困難,可以查看前面的正則表達式參照表。

進入SecondViewController.m,更新后的viewDidLoad方法如下:

- (void)viewDidLoad {
    ...
    // Array of regex to validate each field.
    self.validations = @[@"^[a-z]{1,10}$",      // First name
                         @"^[a-z]$",            // Middle name
                         @"^[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
                         ];
    ...
}

要用正則表達式驗證First Name,首先從字符串開始匹配,然后匹配字符串范圍az,長度在一至十個字符串之間,最后匹配字符串的末尾。

Middle NameLast Name的正則表達式和First Name類似。只是在Middle Name中,不需要指定字符串長度為{1},因為^[a-z]$只匹配一個字符。

生日部分比前面的復雜些,先匹配字符串開始,之后是月份部分。在月份部分需要匹配01、02、02...09、10、11、12,月份后面是/.-三者之一。后面是日部分,在日部分的捕捉里,需要匹配01、02...29、30、31,日后面是/.-三者之一。最后是年部分,在捕捉內(nèi)匹配1920,后面是任意兩個數(shù)字。最后的的\d也可以用[0-9]替換。

現(xiàn)在,已經(jīng)有了pattern,可以驗證每個文本框中文本了。繼續(xù)在SecondViewController.m中找到validateString: withPattern:方法,添加如下代碼:

- (BOOL)validateString:(NSString *)string withPattern:(NSString *)validatePattern{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:validatePattern options:NSRegularExpressionCaseInsensitive error:&error];
    
    NSAssert(regex, @"Unable to create regex");
    
    NSRange matchRange = [regex rangeOfFirstMatchInString:string options:NSMatchingReportProgress range:NSMakeRange(0, string.length)];
    
    BOOL didValidate = NO;
    
    if (matchRange.location != NSNotFound) {
        didValidate = YES;
    }
    
    return didValidate;
}

上面代碼與FirstViewController.m中的類似,通過給定的pattern創(chuàng)建regex,因為,這里不需要區(qū)分字母大小寫options的參數(shù)為NSRegularExpressionCaseInsensitive。為了確認匹配,檢查rangeOfFirstmatchInString: options: range:的結(jié)果,因為一些正則表達式可以匹配長度為0的字符串,所以讓匹配結(jié)果與{NSNotFound, 0}進行比較是最為可靠的。

這也許是檢查匹配最高效的方法,因為此調(diào)用會在找到第一個匹配時自動退出。如果你需要匹配的總數(shù),可以使用numberOfMatchesInString: options: range:方法獲取。

這里的數(shù)據(jù)驗證存在一個問題,就是當輸入的字符串開頭或結(jié)尾有空格時,數(shù)據(jù)驗證會失敗。遇到這種問題,有兩種解決辦法,第一種是更新pattern,讓其包括開頭和結(jié)尾的空格。第二種是創(chuàng)建一個新的pattern用以移除字符串開頭和結(jié)尾的空格。

這里采用第二種方法,因為這樣會讓輸入的數(shù)據(jù)簡潔,也可以把移除空格的操作單獨為一個方法。找到stringTrimmedForLeadingAndTrailingWhiteFromString:方法,更新后代碼如下:

- (NSString *)stringTrimmedForLeadingAndTrailingWhiteSpaceFromString:(NSString *)string{
    NSString *leadingTrailingWhiteSpacePattern = @"(?:^\\s+)|(?:\\s+$)";
    
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:leadingTrailingWhiteSpacePattern options:NSRegularExpressionCaseInsensitive error:&error];
    if (error) {
        NSLog(@"Unable to create regex.");
    }
    
    NSString *trimmedString = [regex stringByReplacingMatchesInString:string options:NSMatchingReportProgress range:NSMakeRange(0, string.length) withTemplate:@"$1"];
    
    return trimmedString;
}

Pattern中的^\s+匹配所有開頭的空格,\s+$匹配所有結(jié)尾的空格。

替換部分的方法和FirstViewController.m中的方法一致,但withTemplate:的參數(shù)為@"$1"

這是因為括號內(nèi)的?:為非捕捉匹配,也就是匹配的文本不會被保存。$1為向后引用,它告訴正則表達式引擎將匹配的文本替換為第一個捕捉組中捕獲的內(nèi)容。由于第一個捕獲組是非捕獲匹配,因此它是空的。

最后,引擎匹配空格并用空字符替換空格,這樣就可以有效移除字符串兩端的空格。

運行demo,切換到第二個選項卡。輸入完畢內(nèi)容后,正則表達式會檢查輸入數(shù)據(jù)是否符合要求,app會提示是否通過數(shù)據(jù)驗證。

RegexValidation.png

如果你對非捕捉、捕捉和向后引用有疑惑,可以對比查看以下表達式:

  • Pattern@”(^\\s+)|(\\s+$)”,替換文本為@"BOO"
  • Pattern@”(?:^\\s+)|(\\s+$)”,替換文本為@”$1BOO”
  • Pattern@”(?:^\\s+)|(\\s+$)”,替換文本為@”$2BOO”

處理多種格式模式

在上一部分,為了使用正則表達式匹配日期,pattern指定了dd-mm-yyyy、dd/mm/yyyy或dd.mm.yyyy三種格式。但如果用戶輸入的日期格式為dd\mm\yyyy,此時數(shù)據(jù)驗證就會失敗。如果想要指定某一種格式需要如何做呢?

ThirdViewController.m,用戶會輸入社會保障號碼。在美國,社會保障號碼有固定格式:xxx-xx-xxxx。由數(shù)字09和連接符-組成。

為了強制輸入的數(shù)據(jù)符合指定格式,有以下三種方法:

  1. 顯示默認鍵盤,截取用戶輸入的內(nèi)容。當有無效字符輸入時,不顯示該無效字符。缺點是:不是很用戶友好,用戶輸入非法字符時,沒有任何反應。
  2. 顯示自定義的鍵盤,只顯示符合規(guī)范的字符。缺點:工作量太大。
  3. 顯示數(shù)字鍵盤,截取用戶輸入的內(nèi)容,并在指定位置插入連接符-。缺點:用戶從其它地方復制,在這里直接粘貼時會遇到問題。

這篇文章采用第三種方法,這種方法用戶友好,工作量小。利用正則表達式還可以解決從其它地方復制,在這里粘貼的問題。

打開ThirdViewController.m,在textField: shouldChangeCharactersInRange: replacementString:方法內(nèi)查看app如何截取用戶輸入的內(nèi)容、添加和移除連接符。

你所需要做的所有工作就是添加一個匹配社會保障號碼(xxx-xx-xxxx)pattern,該pattern由三個數(shù)字,后面跟隨一個連接符,后面是兩個數(shù)字,后面跟隨一個連接符,最后是四個數(shù)字。

在實際應用中,社會保障號碼有更多格式要求。對于本篇文章,只需要符合xxx-xx-xxxx格式。

ThirdViewController.m的開頭部分,替換預定義kSocialSecurityNumberPattern如下:

#define kSocialSecurityNumberPattern @"^\\d{3}[-]\\d{2}[-]\\d{4}$"

上面的\d可以用[0-9]替換,效果不變。

運行app,切換到第三個選項卡。粘貼社會保障號碼或使用數(shù)字鍵盤輸入,正則表達式會自動檢查內(nèi)容是否符合規(guī)范。

RegexVerfication.png

處理多個搜索結(jié)果

目前尚未實現(xiàn)FirstViewController導航欄上書簽按鈕的功能,當點擊書簽按鈕時,app突出顯示所有日期、時間、地址字符串。

進入FirstViewController.m,點擊書簽按鈕會執(zhí)行findInterestingData:方法,之后調(diào)用幫助方法查找字符串、突出顯示。

日期
  • 格式為:xx/xx/xx、xx.xx.xx或xx-xx-xx。
  • 首先是月份的縮寫或完整(如,Jan或January),后面是12位數(shù)字,日也可以是1st 2nd 3rd 10st等,后面是一個逗號,,最后是四位數(shù)字。年月日中間可以有零個、一個或多個空格。如,May 31th,2017。
時間
  • 只查找簡單格式的時間,如10pm5 am,一到兩位數(shù)字,后面是零個或多個空格,最后是大寫或小寫的ampm
位置
  • 大于等于一個字母,后面是逗號,,后面是零個或多個空格,后面是兩個大寫字母。如:Boston,MA

這里使用正則表達式進行匹配、突出顯示步驟與第一部分查找、匹配類似,不再敘述。直接替換FirstViewController.m的頭文件和實現(xiàn)文件中的代碼即可。替換后如下:

@end

static NSString * const timePattern = @"\\d{1,2}\\s*(am|pm)";
static NSString * const datePattern = @"(\\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}";
static NSString * const locationPattern = @"[a-zA-Z]+[,]\\s*([A-Z]{2})";

@implementation FirstViewController

因為時間和位置的pattern都很簡單,這里只說一下日期的patterndatePattern|分為兩部分,這意味著只要匹配前后之一即可。

第一部分為(\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)?+[,]\\s*\\d{4},其中(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)匹配全寫或縮寫的月份,后面是零個或多個空格,后面是一到兩位數(shù)字,后面是可選的st nd rd th四個中的一個,后面是逗號,,后面是零個或多個空格,后面是四個數(shù)字。

正則表達式可以用簡明的表達式匹配大量的可能字符串,附帶有多重功能。

運行app,點擊First選項卡中的書簽按鈕,所有時間、日期和地址均變?yōu)樗{色。

RegexBookMarks.png

NSRegularExpression是一個非常有效的工具。事實上,你可能已經(jīng)用它在用戶輸入的文本中查找日期、地址、電話號碼。NSDataDetector是一個NSRegularExpression的子類,其中包含pattern,用于標識有用信息。NSRegularExpression是徹底的、強大的,并且在棘手的界面中有驚人的深度。

Demo名稱:RegularExpression
源碼地址:https://github.com/pro648/BasicDemos-iOS

參考資料:

  1. NSRegularExpression Tutorial and Cheat Sheet
  2. 正則表達式-維基百科
  3. The Stack Overflow Regular Expressions FAQ

本文地址:http://www.lxweimin.com/p/1770aefcbc2a
歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容