正則表達式NSRegularExpression

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

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

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

基本語法

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

選擇

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

匹配

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

限定符

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

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

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

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

NSRegularExpression參照表

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

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

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

  • 元字符
字符 描述
[pattern] 匹配pattern中的任一字符。如:[a-z]匹配a-z的任一字符。
. 匹配\n之外的任何字符。
^ 匹配字符的開始位置。
$ 匹配字符的結束位置。
\ 將下一個字符標記為一個特殊字符、或一個原義字符、或一個向后引用。如:n匹配字符n\n匹配一個換行符。序列\\匹配\,\(匹配( 。
\b 匹配單詞邊界,邊界發生在單詞(\w)和非單詞(\W)字符之間的轉換處。如: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 匹配一個數字字符,等價于[0-9]。
\D 匹配一個非數字字符,等價于[^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,}
? 匹配零次、一次,優先匹配一次。(n)?ever可以匹配never以及ever?等價于{0,1}
{n} 匹配n次。n為非負整數,大括號內不能有空格。如:o{2}不能匹配word中的o,但能匹配Google中的兩個o
{n,} 至少匹配n次,盡可能多的匹配。n為非負整數。0{2,}不能匹配word中的o,但能匹配gooooogle中的所有o
{n,m} 至少匹配n次,最多匹配m次,盡可能多的匹配。n和m均為非負整數,且n<=m。
*? 匹配零次、或多次。盡可能少的匹配,即懶惰模式(laziness)。
+? 匹配一次、或多次,盡可能少的匹配。
?? 匹配零次、或一次,優先匹配零次。
{n}? 匹配n次。
{n,}? 至少匹配n次,但不超過整體模式匹配所需。
{n,m}? 匹配n至m次,盡可能少的匹配,但不少于n次。
*+ 匹配零次、或多次。第一次遇到時,盡可能多地匹配,即使整體匹配失敗,也不回溯,稱為Possessive Match。點擊查看possessive match和貪婪匹配區別。
++ 匹配一次、或多次。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。預查不消耗字符,也就是在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。
(?!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<= 正整數n <=捕捉組總數。
$n n為非負整數,向后引用第n個捕捉組,0<= n <=捕捉組總數。$后沒有數字時該符號沒有任何特殊含義。
  • 優先級

在這些運算符同時出現時,按照下面的優先級進行操作。

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

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

在iOS中使用正則表達式

現在我們已經了解了regex的基本內容,下面開始學習在iOS中如何使用regex。

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

這個demo的界面部分已經完成,只剩Regular Expression部分。

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

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

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

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

查找和替換

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

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

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

想要實現查找功能,首先需要把pattern字符串轉變為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;
}

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

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

把字符串轉換為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;
}

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

stringByReplacingMatchesInString: options: range: withTemplate:方法內執行替換操作,該方法沒有修改原來字符串,直接返回一個替換后的字符串,之后把返回的字符串賦值給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. 為可見文本創建NSRange類型的visibleTextRange對象,第一步創建的visibleRange確定文本視圖的哪一部分可見,但visibleTextRange僅用于提取到的可見文本的子字符串,因此,開始位置為0,長度為visibleText的長度。這個是需要傳入正則表達式引擎的范圍。
  4. 根據提供的options,調用regularExpressionWithString: options:方法創建regex
  5. 把所有匹配的文本保存到matches數組,該數組內的對象都是NSTextCheckingResult類型實例對象。
  6. 遍歷matches數組,突出顯示匹配的文本。
  7. 替換剛剛突出顯示的屬性字符串。為此,從可見范圍的visibleRange創建CFRange類型的visibleRange_CF。獲取文本視圖中屬性文本的可變副本并執行替換。
  8. 用突出顯示的屬性文本替換之前屬性文本,用以更新UITextView

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

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

FirstViewController.m內添加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];
    }
}

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

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

RegexSearchReplace.png
RegexSearchResult.png

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

數據驗證

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

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

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

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

  • First Name:由一至十個英文字母構成。
  • Middle Initial:為一個英文字母。
  • Last Name:由英文字母和'構成,字符串長度在二至十之間。
  • 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,日后面是/.-三者之一。最后是年部分,在捕捉內匹配1920,后面是任意兩個數字。最后的的\d也可以用[0-9]替換。

現在,已經有了pattern,可以驗證每個文本框中文本了。繼續在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創建regex,因為,這里不需要區分字母大小寫options的參數為NSRegularExpressionCaseInsensitive。為了確認匹配,檢查rangeOfFirstmatchInString: options: range:的結果,因為一些正則表達式可以匹配長度為0的字符串,所以讓匹配結果與{NSNotFound, 0}進行比較是最為可靠的。

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

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

這里采用第二種方法,因為這樣會讓輸入的數據簡潔,也可以把移除空格的操作單獨為一個方法。找到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+$匹配所有結尾的空格。

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

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

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

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

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,此時數據驗證就會失敗。如果想要指定某一種格式需要如何做呢?

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

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

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

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

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

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

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

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

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

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

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

RegexVerfication.png

處理多個搜索結果

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

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

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

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

@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}),即先是一個或兩個數字,后面是-/.三個符號之一,后面是一個或兩個數字,后面是-/.三個符號之一,最后是兩個數字。

第二部分為(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)?)匹配全寫或縮寫的月份,后面是零個或多個空格,后面是一到兩位數字,后面是可選的st nd rd th四個中的一個,后面是逗號,,后面是零個或多個空格,后面是四個數字。

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

運行app,點擊First選項卡中的書簽按鈕,所有時間、日期和地址均變為藍色。

RegexBookMarks.png

NSRegularExpression是一個非常有效的工具。事實上,你可能已經用它在用戶輸入的文本中查找日期、地址、電話號碼。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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容