iOS开发之详解正则表达式

2020年05月19日 阅读数:90
这篇文章主要向大家介绍iOS开发之详解正则表达式,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

本文由Charles翻自raywenderlichRegExCheatSheet-250x250.png
原文:NSRegularExpression Tutorial: Getting Started
更新提示:本教程被James Frost更新到了iOS8和swift。Tutorial团队成员的Soheil Azarpour完成最初发布
html

正则表达式(广为所知的“regex”)是一个字符串或一个字符序列来讲明一种模式,把它做为一个搜索字符串-很是强大!正则表达式

在一个文本编辑器或文字处理器中普通的旧式搜索只容许你进行简单的匹配。正则表达式能够实现这样简单的搜索,它还能让你更进一步地按模式搜索,例如,在两个数字后跟一个字母,或者,三个字母后跟一个连字符。express

这种模式匹配能让你作更有用的事,如验证字段(电话号码,邮箱地址),检查用户输入,执行更高级的文本操做等等。编程

若是你渴望了解更多关于正则表达式在iOS中的用法,看一些本教程以外的内容--不须要有相关的经验。swift

在这篇NSRegularExpression教程中,你将实现一个在文本中按模式搜索的功能,用你但愿的值替代那些匹配的值,验证你的输入信息,在文字块中找到并高亮显示复杂字符串。数组

此外,我还将给你提供一个NSRegularExpression Cheat Sheet PDF,你能够打印出来,在你开发过程当中做为参考,Swift playground 包含了不少例子,你能用它试验出许多不一样形式的正则表达式!实际上,全部正则表达式的例子都会出如今本教程中,用很生动的例子展示在playground中,必定要查看它们哦。app

闲话少说,是时候来处理正则表达式了。

/The (Basics|Introduction)/

Note:若是你已经有正则表达式基础了,能够跳过头部,直接看Implementing Regex in iOS.编程语言

若是你是刚接触正则表达式,而且想知道所说的这些是什么意思,这是一个简短的定义:正则表达式提供了一种在指定文本文档中按指定模式进行搜索,并能基于匹配模式进行修改文本的一种方式。有许多关于正则表达式的有意思的书和教程--在本教程的结尾,你会看到一个简短的列表。

Regular Expressions Playground

playground-screenshot-250x250.png
编辑器

在本教程中,你将会建立许多正则表达式,假使你想要可视化的使用它们,那么用Swift Playground 是一个绝佳的方式 !ide

这个starter project 包含了这个教程用到的playground。下载项目,在Xcode中运行并打开iRegex.playground.你也能够单独下载这个playground(download the playground) 。

Playground 顶部包含许多函数来高亮显示在一小段文本中应用正则表达式的搜索结果,展现了一系列的匹配项和分组,还有替换文本。目前不要担忧这些方法的实现,以后你会再看到它们的,在Basic ExamplesCheat Sheet部分接着看这个例子。

在playground的侧边栏,你会看到每一个例子的匹配结果。好比“highlight”这个例子,你能够把鼠标指针悬浮在结果上并点击“眼”或者空的圆圈图标来显示在文本中高亮的匹配内容。

你以后将学会如何建立NSRegularExpressions,如今你能够用这个playground来感觉下不一样的正则表达式是怎样工做的,也能够试验一下你本身的模式。在任一点上你均可以用Xcode中的EEditor > Reset Playground菜单按钮来重置你的改动。

Examples

让咱们以一个小例子来展现正则表达式的样子。

这是一个来匹配单词“jump”的正则表达式的例子:

1
jump

 这是一个如此简单的正则表达式。你可使用iOS中可用的API来查询一个文本中的字符串来匹配这个正则表达式—一旦你找到了匹配项,你能发现它在哪儿,你也能够替换它。

这是一个略微复杂点的例子—它会匹配单词“jump”或“jumping”:

1
jump(ing)?

这是应用正则表达式支持的特殊字符的例子。这个圆括号建立了一个组,这个标志是说“匹配前面的元素(这种状况下的组)0次或1次”。

如今来看一个复杂的例子。它会匹配一对开合的HTML标签和他们之间的内容。

1
]*>(.*?)

喔,看起来好复杂,呃?不要担忧,在本教程的后面你将会学到正则表达式中的这些特殊字符,到时候你就能理解这是怎么实现的了!

若是你像了解以前的正则表达式的更多细节,请参考this discussion的解释。

Note: 在实际的应用中,你可能不会单独用正则表达式来解析HTML(probably shouldn’t use regular expressions alone to parse HTML),相反而是用标准的XML解析器。

Overall Concepts

在看更深刻的内容以前,理解一些关于正则表达式的核心概念很重要。

字面字符(Literal characters)是最简单地一种正则表达式。你已经很熟悉他们了,好比,文字处理机或文本编辑器中得“find”操做。例如,单个字符的正则表达式 t 就会找到字母“t”出现的全部地方,正则表达式 jump 会找出全部出现“jump”的地方。优美,简洁!

就像一种编程语言同样,正则表达式的语法中也有一些保留字,以下:

  • [

  • ( and )

  • \

  • *

  • +

  • ?

  • { and }

  • ^

  • $

  • .

  • | (pipe)

  • /

这些字符被用做高级模式匹配。若是你想搜索这些字符中的一个,你须要用反斜线(\)转义它,例如,为了搜索一个文本块中的句号,不是用.,而是用\.

每种环境,在Python、Perl、Java、C#、Ruby或者其余环境,在实现正则表达式时都有一些特殊的细微差异,在Swift中也不例外!

不管Objective-C仍是Swift,你在字面量字符串中都须要转义一些特殊字符(在他们以前添加\字符)。这其中一个字符就是反斜线自身\!既然这个被用来建立正则表达式的模式也是字符串,在你处理String 和 NSRegularExpression,你须要转义反斜线时, 这就增长了复杂性。

这意味着在Swift(或者Objective-C)代码中标准的\.将会显示为\\.。

用如下两点来澄清以上概念:

  • 字面的“\\.”定义了一个字符串:\.

  • 正则表达式\.则是匹配一个单个的句号字符.。

截获圆括号(capturing parentheses) 被用做组模式的一部分。例如:3 (pm|am)会匹配文本“3 pm” ,也会匹配“3 am”。竖线字符(|)执行的是或操做。只要你乐意,你能够包含多个竖线字符在你的正则表达式中。例如,(Tom|Dick|Harry)是一个有效的模式,它能匹配那三个名字中的任一个。

当你须要选择性的匹配特定的字符串时,圆括号组用起来很方便。比方说你要在一个文本中查找“November”,可是它可能被简写为“Nov”.你就能定义一个模式 Nov(ember)?,在捕获圆括号(capturing parentheses)后加上问号,意味着这个圆括号内的内容是可选的。

这个圆括号(parentheses)被定义为术语捕获(capturing)由于他们捕获匹配的内容,并容许在你的正则表达式的其余地方引用它。

举个例子,假使你有一个字符串“Say hi to Harry”.若是你建立一个搜索并替换的正则表达式,用that guy $1 来替换任一处出现的(Tom|Dick|Harry),结果就会是“Say hi to that guy Harry”.$1容许你引用前面规则中的第一个截获组。

捕获组和非捕获组是一些高级的话题,在本教程的后面你会遇到关于他们的例子。

字符组(Character classes)至关于一组字符中匹配单个字符。字符组出如今中括号([ 和 ])之间。

例如,正则表达式 t[aeiou]会匹配“ta”、“te”、“ti”、“to”或“tu”。你能够听任意多的字符在中括号中,可是请记住,只能匹配一个字符。[aeiou]看起来是五个字符,但它真实意义倒是“a”或”e“或”i“或”o“或”u“。

若是字符连续出现,你也能在字符组中定义一个范围。例如,为了搜索在100到109的数字,模式应该用10[0-9]。这和10[0123456789]会返回一样地结果,不过,使用范围来定义你的正则表达式看起来更简洁和易于理解。

字符组不止局限于数字,你一样能够用字符来这样作。好比,[a-f]会匹配”a“,”b“,”c“,”d“,”e“或”f“。

字符集一般包含你想要匹配的字符,可是若是你想明确指出不要匹配的字符该怎么办?一样你能定义除此以外的字符组,把^放在前面。例如,模式t[^o]就会匹配包含”t“而且后面紧跟的字符是非o的字符。

NSRegularExpressions Cheat Sheet

正则表达式是一个语法简单但能组合成很是复杂的结果的绝佳例子!即便是一个正则表达式能手,也会再一些古怪的边界问题上参考一些小抄。

为了能帮助你理解,咱们为你提供了正式的 raywenderlich.com的NSRegularExpression Cheat SheetPDF!请下载下来查看。

除此以外,下面是cheat sheet的缩小版,和一些简短的解释:

  • .匹配任一字符。p.p匹配pop,pup,pmp,p@p等等。

  • \w匹配任意“word-like”字符,包括数字,字母,下划线,不过不能匹配标点符号和其余字符。hello\w会匹配”hello_“,”hello9”和”helloo”,但不匹配”hello!”。

  • \d 匹配数字,大部分状况下是[0-9]\d\d?:\d\d会匹配时间格式的字符串,好比”9:30“和”12:45“。

  • \b 匹配额外的字符,例如空格,标点符号。to\b 会匹配”to the moon”和“to!”中得“to”,可是不会匹配“tomorrow”。\b 用在整个单词的匹配方面和方便。

  • \s 会匹配空白字符,好比,空格,制表符,换行符。hello\s 会匹配“Well,hello there!”中的 “hello ”。

  • ^用在一行的开始。记住,这个特殊的^不一样于方括号中的^!例如,^Hello 会匹配字符串“Hello there”,而不会去匹配“He said Hello”。

  • 用在一行的结束,例如,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。

  • 花括号{}包含了匹配的最大和值最小个数。例如,10{1,2}1会匹配“101”和“1001”,而不会匹配“10001”,由于匹配的最小个数为1,最大个数为2。He[LI]{2,}o会匹配“HeLLo”和“HellLLLIo”和任意其余的“hello”添加多个L的变种,因此没有限制,由于,最少的个数是2,最大的个数没有设置。

有了这些基础知识,就能够继续向下学习了。

是时候你本身亲自体验一下这些例子了,它们都包含在上面提到的Playground里了。

Implementing Regex in iOS

既然你有了这些基础,就在APP中应用正则表达式吧。

若是你尚未这样作,下载 starter project 开始本教程吧。下载下来,用Xcode打开并运行它。

APP的UI部分已经完成了大部分,但这个APP的核心功能依赖与正则表达式,这个尚未…!你的任务就是添加正则表达式来时这个APP更出色。

下面所视的几个截图的例子展现了这个应用的内容:

regex-screenshot-1-700x310.png

这个简单的应用涵盖两个正则表达式的通用用例:
1.执行搜索:高亮显示搜索和替换
2.验证用户输入

这就开始直接使用正则表达式:文本搜索

/Search( and replace)?/

这是一个搜索/替换的简单功能的概述:

  • 搜索视图控制器SearchViewController 有一个只读的UITextView,其内容是《傲慢与偏见》的一个片断。

  • navigation bar包含一个搜索按钮,点击会呈现一个模态的SearchOptionsViewController

  • 用户输入一些信息并点击“Search”按钮。
    APP会隐藏这个search view 并高亮显示textview中全部匹配的内容。

  • 若是用户选择了SearchOptionsViewController中的“Replace”选项,APP会执行搜索并替换文本中全部匹配的内容,再也不是高亮显示结果。

Note:你的APP会用到UITextView的NSAttributedString属性来高亮显示搜索的结果。更多这方面的内容请参考 iOS 6 by Tutorials的第15章--“What’s New with Attributed Strings”。

你也能够用text kit来实现高亮的功能。确保找到Text Kit Tutorial in Swift 来查看更多内容。

还有一个“Bookmark”按钮,容许用户高亮显示文本中的日期,时间,位置。为简单起见,不会涵盖文本中出现的各类格式的日期时间位置。在教程的结尾你能够实现这个高亮功能。

开始实现这个功能的第一步是跳转到标准字符串正则表达式的NSRegularExpression对象。

打开SearchOptionsViewController.swiftSearchViewController模态显示这个view controller,且容许用户键入他们的搜索条件,也能够指定是否区分大小写。

看一下文件头部的SearchOptions结构体,SearchOptions是一个封装了用户搜索选项的简答结构体。代码传递SearchOptions的一个实例给SearchViewController。它用这种方式很好的构造一个合适的NSRegularExpression,你能够经过运用扩展自定义的NSRegularExpression来实现。

选择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增长了一个便利构造方法。它经过SearchOptions实例的不一样设置来作一些正确的配置。

  • 当用户请求一个不区分大小写的搜索,正则表达式使用.CaseInsensitiveCaseInsensitiveNSRegularExpressionOptions值。NSRegularExpression默认是区分大小写的,这个例子中,你使用的是更有好的不区分大小写。

  • 若是用户请求一个完整的单词,APP把正则表达式包含在\b字符组以内。在单词边界字符组中放入\b,所以,搜索模式以前和以后加上\b就会返回一个完整的单词搜索(举例来讲,模式“\bcat\b”只会匹配单词“cat”,而不会匹配“catch”)。

若是以任何理由都不能建立NSRegularExpression,构造函数就会失败并返回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中得当前文本,并计算文本的长度。可能会把正则表达式应用在文本的一个子集上,因此你须要指定一个范围。这种状况下,你要用字符串的整个长度才能保证正则表达式被运用在整个文本上。

难以想象的事发生在调用stringByReplacingMatchesInString的时候。这个方法返回一个新字符串并无改变旧字符串。而后,这个方法给UITextView设置这个新字符串,因此用户看到了正确的结果。

继续留在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.首先,获得一个textviewattributedText的可变拷贝,

2.而后,建立一个整个文本长度的NSRange,并删除已经有背景色的文本的背景色,

3.正如找到和替换,紧接着用你的便利构造方法建立一个正则表达式,获取一个存放正则表达式与textview中文本匹配的全部匹配项的数组。

4. 轮询每个匹配项(把它们转换成NSTextCheckingResult对象),并为每一项添加黄色背景。

5.最后,用高亮的结果更新UITextView

编译和运行你的APP,试着搜索一些不一样的单词和词组!整个文本的匹配项都会高亮显示,就像下面的图片所示:

regex-screenshot-2-180x320.png

试着使用不一样的选项(options)搜索单词“the”看看效果。注意,例如,当搜索整个单词时,‘them’中得‘the’不会高亮显示。
再者,测试一下搜索和替换功能,看看你的文本字符串是怎样如期替换的,试一下’match case‘和‘whole words’选项。

高亮显示和替换都是颇有用的,可是你怎样在你的APP中更有效的利用正则表达式呢?

数据验证

许多Apps都有某种用户输入,好比用户输入Email地址或电话号码。你会对这个用户的输入执行某种级别的数据验证,确保数据的完整,若是用户输入中有错误,通知用户。

对这类数据验证,正则表达式是能够完美解决的,由于他们在模式匹配方面是如此出色。
有两个东西你须要添加到你的APP,验证模式自己,提供一个机制验证用户输入和和这些模式。确保这些对用户来讲简单易用,你的APP内的验证不区分大小写,这样,你就能够在你的模式中只使用小写字母。

做为练习,试着想出一个正则表达式来验证下面的字符串(不用考虑大小写问题):

  • First name - 应该包含一到十个字符长度的标准英语字母。

  • Middle initial - 应该包含一个英语字母。

  • Last name - 应该包含标准英语字母加上撇号‘(apostrophe)(如这样的名字 O’Brian) 而且二到十个字符长度。

  • Date of birth  - 应该是如下格式之一:dd/mm/yyyy, dd-mm-yyyy, 或 dd.mm.yyyy, 且要落在 1/1/1900 和 31/12/2099之间.

固然了,当你开发的时候,你可使用iRegex playground 来试验你的表达式

你是怎么想到须要的正则表达式的?若是你在这儿卡住了,回过头去看看上面的小抄(cheat sheet)上的片断,上面的方案会对你有帮助的.

下面的剧透会展现给你你要用的正则表达式。在向下看以前,首先你本身先理解它,而后检查你本身的结果!

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中建立一个text fields数组,和一个字符串模式数组。而后用swift的map函数 建立一个NSRegularExpression对象的数组,一一对应。

为了建立正则表达式来验证first name ,首先要匹配字符串的开头,而后匹配一个从a到z范围的字符组,最后匹配字符串的结尾来确保它是在1到10个字符的长度。
接下来的第二个模式,middle initial,和last name,遵循一样地逻辑。middle initial的状况下,你没必要指定长度{1}--由于^[a-z]$默认匹配一个字符。
Note:此处你没必要担忧大小写问题--当实例化正则表达式时,会处理它。
对于出生日期,可能要麻烦一些。匹配字符串的开头,而后是“月”部分,你要有一个捕获组来匹配01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11 或 12中得一个,后面跟一个捕获组匹配-,/或.。
 对于”天“部分,你须要另外一个捕获组来匹配01, 02, … 29, 30, or 31,后面跟一个捕获组匹配-/.
最后,须要一个捕获组来匹配19或20,后面跟两个数字字符。
你就获得了创造性的正则表达式。也有另外的方式解决上面的问题,好比用\d代替[0-9]。固然了,只要能正常工做的方案就是最好的方案。
Note:实际应用中,你极可能不会用正则表达式来验证时间(更不会在一个日期范围内检查它)。相反,你极可能会使用NSDateFormatter来从字符串解析成日期,而后比较解析的NSDate和引用的日期。
既然你掌握了这个模式,你须要验证每一个text field中输入的文本。

仍是停留在SignUpViewController.swift,找到validateTextField,并用下面的内容替换实现部分。

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数组中拿取相关的正则表达式开始,去掉用户在textfield中输入内容中含有的全部空白字符,而后建立一个包含整个文本的范围。

为了准确的检查匹配,代码测试了rangeOfFirstMatchInString(_:options:range:)的结果。这多是检查匹配最有效的方式,由于这个调用在它发现第一个匹配后就早早退出了。然而,假使你须要知道全部的匹配,也有其余的选择,如numberOfMatchesInString(_:options:range:)

运行程序,点击右下方的Contacts 按钮,试着在sign up中输入一些信息,当你完成每一个field,你会看到它的文本依据是否有效变绿或变红效果以下方截图:

regex-screenshot-3-180x320.png

上面的代码用了stringByTrimmingCharactersInSet来删除用户输入的开头和结尾的空格-不然若是有空格在的话就会验证失败。

这是stringByTrimmingCharactersInSet的一个固定用法,可是做为怎么处理正则表达式的教程,还有一个更有趣的方向,考虑怎样经过正则表达式来实现。

去掉空格,一个小插曲。

QQ截图20150414152612.png

这种状况你有两种选择:    

要么更新你的模式来处理首尾处的空格,要么在你一个用验证模式以前建立并应用另外一个模式来去掉首尾的空格。第二种方法保证了验证模式的简单简洁,也能够在它的方法内部重构,你须要的只是一个正则表达式。

你想到一个去除首位空格的正则表达式了吗?在你看下面的结果前本身试一下。

Solution Inside

1
(?:^\\s+)|(?:\\s+$)

好极了,模式^\s+会发现开头的空格,\s+$会找到结尾处的空格。如今有了一个匹配空格的正则表达式,是时候用用它了。在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的实例方法,在咱们看这个方法如何实现以前,改变validateTextField,经过改变下面这个去除空格的行使用这个新方法:

1
let text = textField.text.stringByTrimmingLeadingAndTrailingWhitespace()

这个String的新方法用上边的模式建立了一个正则表达式,返回一个匹配出都用$1替代的新字符串,可是$1是什么意思呢?

当你有个正则表达式的捕获组(用小括号()来表示),你能够用$和数字来引用组的内容。正则表达式文档指出叫反引用。数子指出引用那个捕获组。
做为例子,给出一个正则表达式:

1
Customer ID: (\d{3})-(\d{4})

匹配下面的文本:

1
Customer ID: 123-0089

$1的值会试123$2的值会是0089,多有用的工具!

回头看这个空格的例子,$1只是用它自身--一个捕获组来取代空格,实际什么也没作?
这种状况下,圆括号中的?:告诉正则表达式引擎建立一个非捕获组。也就是说,匹配的文本不会像正常状况下那样存在缓冲区。
既然第一个捕获组是一个非捕获组,引擎天然不会捕获任何东西--所以它是空的!这样,引擎匹配空格结束,去掉空格,有效的移除了首尾的空格。

固然了,这是捕获值的特殊用法。实际上你能够只用一个空字符串,“”,做为你的模板值。

More on Capture Groups

举个更实际的例子,假设你想要选择一个文件中你全部的用户ID,让四位数字的部分在三位数字的前面,你想要两组数字以前的间隔更大一些,一个连字符换成两个?更具体的来讲是你想这样:

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结尾处,你能看到这个例子的效果。

Note:这个正则表达式在name和ID之间容许任意数量的空格,在“ID:”和真正的ID值中间也是如此。

若是你一直纠结于非捕获,捕获和反向引用,在playground中试试下面的不一样状况,看看会是什么结果(建议:你可使用‘replaceMatches’函数):

  • 用“(^\\s+)|(\\s+$)”替换上面的空格模式,template参数用“BOO”替换

  • 用“(?:^\\s+)|(\\s+$)”替换上面的空格模式,template参数用“$1BOO”替换

  • 用“(?:^\\s+)|(\\s+$)”替换上面的空格模式,template参数用“$2BOO”替换

Handling Multiple Search Results

尚未实现导航条上的书签按钮。当用户点击它时,APP应该高亮显示文本中的日期,时间,位置。

打开SearchViewController.swift,找到书签按钮的实现:

1
2
3
4
5
6
7
//MARK: Underline dates, times, and locations
  
@IBAction func underlineInterestingData(sender: AnyObject) {
   underlineAllDates()
   underlineAllTimes()
   underlineAllLocations()
}

上面这个方法调用三个其余的辅助方法来给日期,时间,地点加下划线。若是你看向者三个辅助方法,你会看到他们都是空方法!

首先,填上每一个方法的实现。用下面的内容替换他们:

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)
   }
}

每一个方法调用NSRegularExpression的一个工厂方法来建立一个合适的正则表达式。这些还不存在,可是这是一个方便的地方封装这个行为。这个方法找到匹配项,调用highlightMatches来给文本中的每一个字符串着色和添加下划线。若是你有兴趣看它如何实现,查看它的实现。

如今填入正则表达式方法。打开RegexHelpers.swift 并在NSRegularExpression 的扩展内添加下面的内容。

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)
}

如今你来实现这些模式,这是一些你须要的内容:

Date Requirements:

  • xx/xx/xx or xx.xx.xx or xx-xx-xx格式。日,月,年,的防治不是很重要,由于代码只是高亮显示他们。例如:10-05-12.

  • 全称和缩写月的名字(如,Jan或January,Feb或February等),以后跟着一两个数字(如:x或xx).日多是序数词(如:1st, 2nd, 10th, 21st等),以后跟一个逗号做为分隔符,而后是一个四位的数字(如,xxxx).在日月年两两之间可能包含零至多个空白。例如:March 13th, 2001。

Time requirements:

  • 找出像“9am” 或 “11 pm”的简单时间:一两位数字跟着一个或多个空格,再后面跟着小写的“am” 或 “pm”。

Location requirements:

  • 至少一个字符的任意单词,紧跟着一个逗号,再跟着零个或多个空格,再跟着两个大写的英语字母组合。例如“Boston, MA”。

你能够用playground试验一下。看是否能勾勒出须要的正则表达式!

这是三个简单的模式。用下面的内容替换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}"

这个模式被|(或)分红了两部分。意味着或者第一部分匹配或者第二部分匹配。

第一部份内容:(\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)?,它会匹配零个或多个空格,后面跟一到两个数字,再以后跟着一个可选的序数词后缀。例如,它会匹配“1” 和 “1st”。

最后,[,]\\s*\\d{4}会匹配一个逗号,以后会跟着零个或多个空格,再以后跟着一个表示年的四位数字。

多恐怖的一个正则表达式!不过,你能够看到正则表达式的简洁,和把大量信息包装成一个看似神秘的字符串的功能的强大!

接下来,是regularExpressionForTimesregularExpressionForLocations的模式,把下面的内容填进空白的模式。

1
2
3
4
5
// Times
let pattern =  "\\d{1,2}\\s*(pm|am)"
  
// Locations
let pattern =  "[a-zA-Z]+[,]\\s*([A-Z]{2})"

做为练习,看看你可否根据上面的要求解释一下这个正则表达式模式。

编译并运行这个APP,点击Bookmark图标。你应该会看到高亮显示的日期,时间,位置,以下所示:

63.png

这个例子就到这儿了,你能明白为何这个对于时间的正则表达式不能正确进行更通用的搜索吗?按如今的状况,它不会匹配3:15pm,它会匹配28pm。
这是一个有挑战性的问题!想一想怎样重写这个关于时间的正则表达式,来让它匹配更通用的时间格式。
具体来讲,你的答案应该匹配12小时制的ab:cd am/pm时间格式。因此它应该能匹配11:45 am, 10:33pm, 04:12am 但不能匹配 2pm, 0:00am 18:44am 9:63pm 或 7:4 am。在am/pm前应该有至少一个空格。若是它匹配了14:33am中得4:33am,这也是能够接受的。

下面是一个可行的答案,可是你本身先试一下。在附带的playground尾部看一下它的效果。
Solution Inside

1
"(1[0-2]|0?[1-9]):([0-5][0-9]\\s?(am|pm))"

接下来要作什么呢?

是你依据上边的教程开发的最终例子example project 。

恭喜你!如今你已经有了一些正则表达式使用方面的实践经验。

正则表达式是强大的,使用它也颇有趣,他们很像解决数学问题。正则表达式的弹性让咱们有不少种方法来建立一个模式去适应你的需求,例如过滤输入字符串的空格,在解析前去除HTML或XML标签,或者是,找出特殊的XML或HTML标签等等!
有不少现实世界的字符串例子,你能够用正则表达式去验证。

做为最后的练习,试图解开下面这个正则表达式来验证一个邮箱地址(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])?

乍看起来,它看起来是一堆杂乱的字符串,可是,用你新发现的知识(下面的连接颇有用),你会一步一步理解它,并成为正则表达式的高手!
这是一些关于正则表达式颇有用的资源列表:

假使你以前错过了这些连接,看一下这些咱们为你准备的资源:

但愿你喜欢这个教程,若是你有任何意见和问题,请加入下面的论坛!

上一篇: 12-C语言字符串
下一篇: C语言程序