这个连接完整: https://www.jianshu.com/p/c86...javascript
原文: NSRegularExpressionhtml
原做者:Nate Cookjava
遇到问题,哦,要用NSRegularExpression了。git
其实呢,有一些,是要注意的。github
正则表达式是一种DSL, 有一些讨论。说他很差,毕竟Regex里面都是各类符号。说他好,Regex简明强大,用途普遍。正则表达式
公认的是,Cocoa 给NSRegularExpression 设计了一套冗长的API. 先比对下Ruby,这段Ruby代码的做用是,从HTML代码片断中提取URL.express
htmlSource = "Questions? Corrections? <a href=\"https://twitter.com/NSHipster\"> @NSHipsteror</a> or <a href=\"https://github.com/NSHipster/articles\">on GitHub.</a>" linkRegex = /]*href="([^"]*)"[^>]*>/i links = htmlSource.scan(linkRegex) puts(links) # https://twitter.com/NSHipste # https://github.com/NSHipster/articles
Ruby 三行代码实现。数组
如今看Swift 中用 NSRegularExpression ,一样的功能实现(从HTML代码片断中提取URL.)闭包
let htmlSource = "Questions? Corrections? <a href=\"https://twitter.com/NSHipster\"> @NSHipsteror</a> or <a href=\"https://github.com/NSHipster/articles\">on GitHub.</a>" let linkRegexPattern = "]*href=\"([^\"]*)\"[^>]*>" // 比起Ruby 的, 多了一个转义字符 '\' let linkRegex = try! NSRegularExpression(pattern: linkRegexPattern, options: .caseInsensitive ) let matches = linkRegex.matches(in: htmlSource, range: NSRange(location: 0, length: htmlSource.utf16.count)) let links = matches.map{ result -> String in let hrefRange = result.rangeAt(1) let start = String.UTF16Index(encodedOffset: hrefRange.location) let end = String.UTF16Index(encodedOffset: hrefRange.location + hrefRange.length) return String(htmlSource.utf16[start.. } print(links) // ["https://twitter.com/NSHipster", "https://github.com/NSHipster/articles"]
{app
效果图: 简单说明: 第一段, <a\\s+ , 先找 <a 两个特定字符, 再来一个转义,寻找一到多个空格。 第二段, [^>]* , 要求 紧接着的任意的字符串中,不能包含 > . 第三段, href=\" . 寻找紧接着 href=\" 第四段, ([^\"]*),紧接着的任意字符串不得包含 \" 第五段, \"[^>]*> , 先来一个 转义,再要求紧接着的字符串知足 ,* 和 > 之间, 不包含 > .
}
NSRegularExpression 很差的,就说到这里。
原文(英文原版)不会深刻浅出地讲解正则表达式(要本身学习 通配符‘*’ ‘+’ , 反向引用‘^’ ,提早量‘[]’ ,等等 )
Swift 中的 Regex 学习, NSRegularExpression, NSTextCheckingResult , 注意下难点、特例, 就能够了。
字符串方法 , NSString Methods
上手Cocoa中的正则,固然是不用 NSRegularExpression .
NSString 中的range(of:...) 方法 可实现轻量级的字符串查找,须要用.regularExpression 切换 regular expression mode . ( OC 的 NSString, 对应 Swift 中的 String)
let source="For NSSet and NSDictionary, the breaking..." // Matches anything that looks like a Cocoa type: // UIButton, NSCharacterSet, NSURLSession, etc. let typePattern = "[A-Z]{3,}[A-Za-z0-9]+" if let typeRange = source.range(of: typePattern , options: .regularExpression){ print("First type: \(source[typeRange])") // First type: NSSet }
{
link: https://regex101.com/r/U7TC8v/1
第一段, [A-Z]{3,} , 用于匹配至少3个A-Z 中的字符。
第二段, [A-Za-z0-9]+ , 用于匹配至少一个该集合中的字符,A-Z 之间 加上 a-z 之间, 再加上 0-9 之间
}
替换也是经常使用的功能,一样的选项option, 使用 replacingOccurrences(of:with:...) .
下面,用一个看起来怪的代码,在上句中的coco 类型单词外面加括号。看起来清楚一些吧。
let markedUpSource = source.replacingOccurrences(of: typePattern, with: "`$0`", options: .regularExpression) print(markedUpSource) // "For `NSSet` and `NSDictionary`, the breaking...""
{
说明:
这里有一个 正则表达式中,获取正则分段的概念。
能够参见 这个连接: https://stackoverflow.com/que...
}
用上面的替换模版,正则能够处理推导分组。西方有一个关于元音的字母转换,
let ourcesay = source.replacingOccurrences(of: "([bcdfghjklmnpqrstvwxyz]*)([a-z]+)", with: "$2$1ay", options: [.regularExpression,.caseInsensitive])
print(ourcesay)
// "orFay etNSSay anday ictionaryNSDay, ethay eakingbray..."
{
link : https://regex101.com/r/lZxWuY/2
第一段, ([bcdfghjklmnpqrstvwxyz]*) , 匹配不限长度的 不含 a e i o u 的 任意英文字母。
第二段, ([a-z]+) , 匹配 至少一个长度的 任意英文字母
}
不少须要运用正则的场景下,上面两个方法就能够了。复杂的功能实现,就要用到NSRegularExpression这个类了。首先, 解决Swift中的一个正则新手易犯错误。
NSRangeand Swift
比起 Foundation 的 NSString , Swift有着做用域更大、更复杂的API ,来处理字符串的字符和子串。Swift的标准库有四种接口来处理字符数据,能够用字符、Unicode 标量、UTF-8 码、 UTF-16 码 来获取字符串的数据。
这与 NSRegularExpression 相关,不少 NSRegularExpression 方法使用 NSRange, 用 NSTextCheckingResult 对象保存匹配到的数据。 NSRange 使用整型 integer ,记录他的起始点 location 和 字符长度 length 。可是字符串 String 是不用整型 integer 做为索引的
let range = NSRange(location: 4, length: 5) // 下面的代码,是编不过的 source[range] source.characters[range] source.substring(with:range) source.substring(with:range.toRange()!)
接着来。
Swift 中的 String 实际上是经过 utf16 接口操做的,同 Foundation 框架下 NSString 的 API 同样。能够经过 utf16 接口的方法,用整型 integer 建立索引。
let start = String.UTF16Index(encodedOffset: range.location) let end = String.UTF16Index(encodedOffset: range.location + range.length) let substring = String(source.utf16[start.. // substring 如今是 "NSSet"
下面放一些 String 的 Util 代码,调用 Swift 相关正则的语法糖, 有 Objective-C 的感受
extension String{ /// 这个 nsrange 属性 ,包含了字符串的全部范围 var nsrange: NSRange{ return NSRange(location:0,length:utf16.count) } /// 用以前给出的 nsrange 属性,返回一个字串。 /// 若是字符串中没有这个范围, 就 nil 了 func substring( with nsrange: NSRange) -> String?{ guard let range = Range(nsrange, in: self) else { return nil } return String( self[range] ) } /// 返回 与以前掏出来的 nsrange 属性,等同的 range /// 若是字符串中没有这个范围, 就 nil 了 func range(from nsrange: NSRange) -> Range?{ guard let range = Range(nsrange, in: self) else { return nil } return range } }
接下来体验的 NSRegularExpression ,有用到上面的 Util 方法。
NSRegularExpression 和 NSTextCheckingResult
以前学习了在字符串中找出第一个匹配到的数据,与匹配到的数据之间的替换。复杂些的状况,就要用到 NSRegularExpression 了。先造一个简单的文本各式匹配 miniPattern ,找出文本中的 bold 和 italic
造一个 NSRegularExpression 对象,要传入一个匹配规则的字符串 pattern ,还有一些选项能够设置。miniPattern 用星号 * 或 下划线 _ 开始查找匹配的单词。找到星号或下划线后,就匹配一个到多个字符的格式,用找到的第一个匹配的字符再次match终止一次查找。匹配到的首字母和文本,都会被保存到查询结果中。
let miniPattern = "([*_])(.+?)\\1" let miniFormatter = try! NSRegularExpression(pattern: miniPattern, options: .dotMatchesLineSeparators) // 若是 miniPattern 有误, NSRegularExpression 初始化就会抛异常。
若是 pattern有误, NSRegularExpression 初始化就会抛异常。一旦 NSRegularExpression 对象建好了,就能够用它处理不一样的字符串。
{
说明:
"([*_])(.+?)\1" , 这个正则表达式 分三段,
第一段([_]) ,匹配 中括号 中的 任意一个字符, 就是 或者 _ ;
第二段(.+?) , 匹配 长度大于1的 任意字符串;
第三段 \1, 有一个转义字符, 匹配以前获取到的第一个同等字符串
}
let text = "MiniFormatter handles *bold* and _italic_ text." let matches = miniFormatter.matches(in: text, options: [], range: text.nsrange ) // matches.count == 2
调用matches(in:options:range:) 方法,能够取出包含 NSTextCheckingResult 元素的数组。 多种文本处理类都有用到NSTextCheckingResult 类型,譬如 NSDataDetector 和 NSSpellChecker . 返回的数组中,一个匹配有一个NSTextCheckingResult .
一般要取得的是匹配到的范围,就在每一个结果的range属性里面。一般要取得的还有,正则表达式中任意匹配到的范围。 能够经过numberOfRanges 属性 和rangeAt(_:) 方法,找出指定的范围。
range(at:) Returns the result type that the range represents.
range(at:) 方法, 返回的结果就是对应的范围 Discussion A result must have at least one
range, but may optionally have more (for example, to represent regular
expression capture groups). Passingrange(at:)the value 0 always
returns the value of the therangeproperty. Additional ranges, if any,
will have indexes from 1 to numberOfRanges-1. 讨论下,
返回的结果,至少有一个范围。每每有更多,可选的。( 正则表达式捕获组,对应的) range(at:) 方法返回的第一个结果,就是 range
属性的值。若是有额外的,返回的结果对应的索引就是从 1 到 numberOfRanges-1
引用下苹果文档,
https://developer.apple.com/d...
range 0 是彻底匹配到的范围,也是确定能取到的。
而后从第1个到 第(numberOfRanges - 1)个的 ranges 数组中的值,就是分段,对应每一段正则匹配的结果。
使用以前给出的NSRange的取子串方法,就能够用 range 来取出匹配到的结果。
for match in matches { let stringToFormat = text.substring(with: match.range(at: 2) )! switch text.substring(with: match.range(at: 1) )! { case "*" : print("Make bold: '\(stringToFormat)'") case "_": print("Make italic: '\(stringToFormat)'") default: break } } // 打印出 // Make bold: 'bold' // Make italic: 'italic'
对于基础的替换,直接用stringByReplacingMatches(in:options:range:with:) 方法,String.replacingOccurences(of:with:options:) 的增强版 。上例中,不一样的正则匹配 ( bold , italic),用不一样的替换模版。
按照倒叙,循环访问这些匹配结果,这样就不会把后面的 match 范围搞乱。
var formattedText = text Format: for match inmatches.reversed () { let template: String switch text.substring(with: match.range(at:1) ) ?? ""{ case "*": template = "$2" case "_": template = "$2" default: break Format } let matchRange = formattedText.range(from:match.range)! // see above let replacement = miniFormatter.replacementString( for: match, in: formattedText, offset: 0, template: template) formattedText.replaceSubrange( matchRange , with: replacement) } // 'formattedText' is now: // "MiniFormatter handles bold and italic text."
经过自定义的模版,调用miniFormatter.replacementString(for:in:...) 方法, 而后呢,每个NSTextCheckingResult 实例会随之产生一个对应的替换字符串。
Expression and Matching Options , 表达式与匹配选项
NSRegularExpression 是高度可配置的。弄一个实例,或者调用执行正则匹配的方法,均可以传不一样选项的组合。
NSRegularExpression.Options
NSRegularExpression.MatchingOptions 正则表达式的匹配选项
一个 NSRegularExpression 正则表达式实例中,能够传入选项来调整匹配的方法。
static var withTransparentBounds: NSRegularExpression.MatchingOptionsSpecifies that matching may examine parts of the string beyond the
bounds of the search range, for purposes such as word boundary
detection, lookahead, etc. This constant has no effect if the search
range contains the entire string.SeeenumerateMatches(in:options:range:using:)for a description of the
constant in context.
苹果 连接:
https://developer.apple.com/d...
Partial Matching 部分匹配
最后, NSRegularExpression 最强大的特性之一是,仅扫描字符串中须要的部分。处理长文本,挺有用的。处理耗资源的正则匹配,也是。
不要用这两个方法firstMatch(in:...) 和 matches(in:...) , 调用 enumerateMatches(in:options:range:using:) ,用闭包处理对应的匹配。
func enumerateMatches( instring :String, options:NSRegularExpression.MatchingOptions= [], range:NSRange, usingblock: (NSTextCheckingResult?,NSRegularExpression.MatchingFlags,UnsafeMutablePointer<ObjCBool>) ->Void)
苹果连接: https://developer.apple.com/d...
这个闭包接收三个参数,匹配的正则结果,一组标志选项, 一个布尔指针。 这个 bool 指针是一个只出参数,能够经过它在设定的时机中止处理。
能够用这个方法在 Dostoevsky 的 Karamazov兄弟一书中, 查找开始的几个名字。名字听从的规则是,首名,中间父姓 ( 例如: “Ivan Fyodorovitch” )
let nameRegex = try! NSRegularExpression( pattern: "([A-Z]\\S+)\\s+([A-Z]\\S+(vitch|vna))" ) let bookString = ... var names:Set = [] nameRegex.enumerateMatches( in: bookString, range: bookString.nsrange ){ ( result , _ , stopPointer ) in guard let result = result else { return } let name = nameRegex.replacementString( for: result , in: bookString , offset : 0 , template: "$1 $2" ) names.insert(name) // stop once we've found six unique names ,经过 Set 确保,6个不同的名字文本 stopPointer.pointee = ObjCBool( names.count==6 ) } // names.sorted(): // ["Adelaïda Ivanovna", "Alexey Fyodorovitch", "Dmitri Fyodorovitch", // "Fyodor Pavlovitch", "Pyotr Alexandrovitch", "Sofya Ivanovna"]
经过这种途径,咱们只需查找前 45 个匹配,而不是把全书中接近1300个名字都找一遍。性能显著提升。
一旦有所认识,NSRegularExpression 就会超级有用。除了 NSRegularExpression , 还有一个类NSDataDetector.NSDataDetector是一个用于识别有用信息的类,能够用来处理用户相关的文本,查找日期,地址与手机号码。经过Fundation 框架处理文本,NSRegularExpression 强大,健壮,有简洁的接口,也有深刻
说明: 为了有意思一些, 我采起了意译。并加入了一些 Regex 细节与扩展资料。
文中 出现的 Swift 代码, 已校订到 Swift 4 .
谢谢观看
PS: 参考资料
文中的 Swift 代码,github 地址: https://github.com/dengV/rege...
超好用的正则网站:101 ( https://regex101.com/ )
熟悉的 ray wenderlich tutorial: ( https://www.raywenderlich.com... )
我升级了对应的Swift代码,github 连接 ( https://github.com/BoxDengJZ/... )