Swift 正则Regex
在Swift 5.7版本中,Apple在Swift对正则表达式的支持做了一个重大的更新,不但提供了一个Regex的新类型代表正则表达式,同时还提供了一个DSL语法来创建正则表达式。
- SE-0350 introduces a new
Regex
type
- SE-0351 introduces a result builder-powered DSL for creating regular expressions.
- SE-0354 adds the ability co create a regular expression using
/.../
rather than going throughRegex
and a string.
- SE-0357 adds many new string processing algorithms based on regular expressions.
这对编写正则表达式程序提供了极大的方便。在Swift之前的版本创建正则表达式仍然是使用NSRegularExpression,创建方式如下:
// Match and capture one or more digits
let pattern = #"(\d+)"#
let regex = try Regex(pattern)
使用NSRegularExpression的不足:
-
正则表达式字符串需要过滤正则表达式规则符号,比如/
-
正则表达式是否合规只能在运行时检测创建时是否有异常抛出
现在,可以直接使用正则表达式创建一个Regex,并且在编译期就可以校验正则表达式是否合规。
let regex = /(\w+)\s+(\d+)/
针对输入字符串可以提取单词和数字组合。
let input = "Tom 123 xyz"
if let result = input.firstMatch(of: regex) {
print(result.0) // Tom 123
print(result.1) // Tom
print(result.2) // 123
}
正则表达式提取的结果存储在一个子串数组,0下标的第一个元素是匹配到的所有内容,第一个匹配内容存储在1下标元素中,以此类推。
在提取多个元素时,可以通过读取匹配结果的output属性来获取匹配的tuple元组:
if let match = input.firstMatch(of: regex) {
let (matched, name, count) = match.output
}
可以对待提取的元组属性进行命名:
let regex = /(?<name>\w+)\s+(?<count>\d+)/
if let match = input.firstMatch(of: regex) {
print(match.name) // Tom
print(match.count) // 123
}
在编写多行正则表达式时,我们也可以使用#...#来包裹正则表达式字符串,从而使得正则表达式有更好的可读性。
let regex = #/(?<name> \w+) \s+
(?<count> \d+)/#
对正则表达式查找结果可以有不同的方式方式:
input.firstMatch(of: regex)
input.wholeMatch(of: regex)
input.prefixMatch(of: regex)
使用Regex还可以进行文本的替换,剪裁或者分割:
let line = "Tom 1234"
let line1 = line.replacing(/\s+/,with:",") // Tom,1234let
line2 = line.trimmingPrefix(/\w+\s+/) // 1234
let fields = line.split(separator: /\s+/) // ["Tom","1234"]
Regex Builder
Swift 5.7中对正则表达式的更新,除了引入Regex类方便正则表达式的使用,还设计了一个更强大的DSL构建工具:Regex Builder。传统的正则表达式大家很难掌握正则表达式的各种规则,尤其是对匹配元素的条件和组合。但在Regex Builder中,允许你用结构化,更接近自然语义的方式来构造正则表达式。以上面 /(\w+)\s+(\d+)/的正则表达式为例,在Regex Builder中可以通过下面的语句进行组装:
import RegexBuilder
let regex = Regex {
Capture {
OneOrMore(.word)
}
OneOrMore(.whitespace)
Capture {
OneOrMore(.digit)
}
}
let input = "Tom 123 xyz"
if let match = input.firstMatch(of: regex) {
let name = match.1 // Tom
let count = match.2 // 123
}
regex的语法规则通过大括号{}来进行组装,实际上是使用Swift的语法糖将每个条件规则包装到了闭包block中,使用
One
, OneOrMore
, ZeroOrMore
, ChoiceOf
, and Optionally
. 等可读性更强,更语义化的组件使得组装正则表达变得更容易,同时代码也更容易维护和理解。举个例子:
let colorRegex = Regex {
Capture {
ChoiceOf {
"red"
"green"
"blue"
}
}
":"
One(.whitespace)
Capture(OneOrMore(.digit))
ZeroOrMore(.whitespace)
Optionally(OneOrMore(.hexDigit))
}
使用该正则表达式解析json:
let colors = [ "red: 255 FF", "green: 0", "blue: 128 80"]
for color in colors {
if let match = color.wholeMatch(of: colorRegex) {
print(match.1, match.2)
}
}
// red 255
// green 0
// blue 128
除了对正则表达的组装,Regex builder还支持转换语法:
let regex = Regex {
Capture {
OneOrMore(.digit)
} transform: {
Int($0) // Int?
}}
通过转换函数可以将结果提取转换为需要的类型,将Capture替换为TryCapture,还可以避免输出无值的可选结果:
let regex = Regex {
TryCapture {
OneOrMore(.digit)
} transform: {
Int($0) // Int
}
}
正则表达式匹配模式
在正式表达式在匹配的时候有个重要的模式:贪婪匹配与懒惰匹配
当正则表达式中包含能接受重复的限定符时,通常的行为是匹配尽可能多的字符。这被称为贪婪匹配。如果是匹配尽可能少的字符,则称为懒惰匹配。一般正则表达式默认使用贪婪模式进行匹配。
以一个提取数字的正则表达式为例:
let regex = Regex {
OneOrMore(.any, .eager)
Capture(OneOrMore(.digit))
}
let line = "hello world 99 ----> 42"
if let match = line.wholeMatch(of: regex) {
let count = match.1 // 2
}
在第一规则OneOrMore作用是,使用贪婪匹配会匹配所有字符串直到数字4,留下最后的数字2满足第二规则OneOrMore(.digit),并被捕获为结果。所以输出结果为2。
如果改变第一规则为懒惰匹配:
let regex = Regex {
OneOrMore(.any, .reluctant)
Capture(OneOrMore(.digit))
}
let line = "hello world 99 ----> 42"
if let match = line.wholeMatch(of: regex) {
let count = match.1 // 42
}
则第一个规则在满足第二规则的贪婪匹配42后,会截至到4的前面空格。
要改变一个正则表达式的匹配模式可以在表达式后追加repetitionBehavior修改器:
Regex {}
.repetitionBehavior(.reluctant)
附录
推荐一个工具:https://swiftregex.com/
可以将正则表达式转换为Regex builder代码,还可以直接在线测试效果
网友评论