Swift?中的?RegexBuilder學(xué)習(xí)指南
前言
在我們?nèi)粘5捻?xiàng)目開(kāi)發(fā)中,經(jīng)常會(huì)碰到和正則表達(dá)式打交道的時(shí)候。比如用戶密碼,通常會(huì)要求同時(shí)包含小寫字母、大寫字母、數(shù)字,并且長(zhǎng)度不少于 8 位,以此來(lái)提高密碼的安全性。
在 Swift 中,我們可以用正則表達(dá)式的字面量方式來(lái)進(jìn)行實(shí)現(xiàn)。
Regex 字面量
Regex 字面量實(shí)現(xiàn)代碼:
let regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/
let text = "Aa11111111"
print(text.matches(of: regex).first?.output) // Optional("Aa11111111")
通過(guò)上述代碼可以看到,//通過(guò)兩個(gè)斜線就可以來(lái)生成正則的字面量。用字面量的方式確實(shí)可以使代碼很簡(jiǎn)潔,但簡(jiǎn)潔的代價(jià)就是很難看懂,對(duì)后面的代碼維護(hù)也造成了很大的困難。
就像網(wǎng)上盛傳的一句梗一樣:“我有一個(gè)問(wèn)題,所以我寫了一個(gè)正則表達(dá)式?,F(xiàn)在,我有了兩個(gè)問(wèn)題。”??
對(duì)于 Regex 難懂且難維護(hù)的問(wèn)題,Swift 的開(kāi)發(fā)團(tuán)隊(duì)給出的方案就是:RegexBuilder。
RegexBuilder - 像寫代碼一樣寫正則
假設(shè)我們有一個(gè)字符串"name: John Appleseed, user_id: 100",想要提取其中user_id的值。 首先第一步,先導(dǎo)入 RegexBuilder:
import RegexBuilder
接著,通過(guò)結(jié)構(gòu)體 Regex 來(lái)構(gòu)建正則語(yǔ)句:
let regex = Regex {
"user_id:" // 1
OneOrMore(.whitespace) // 2
Capture(.localizedInteger(locale: Locale(identifier: "zh-CN"))) // 3
}
第一行代碼匹配的是固定字符串:"user_id",第二行代碼匹配的是一個(gè)或者多個(gè)空格,第三行代碼則是匹配的整型數(shù)字。
localizedInteger 會(huì)將匹配到的數(shù)字自動(dòng)轉(zhuǎn)為整型,比如下面的例子:
let input = "user_id: 100.11"
let regex = Regex {
Capture(.localizedInteger(locale: Locale(identifier: "zh-CN")))
}
if let match = input.firstMatch(of: regex) {
print("Matched: \(match.0)") // Matched: 100.11
print("User ID: \(match.1)") // User ID: 100
}
雖然匹配的是 100.11,但輸出的仍然是 100。
最后,就可以通過(guò) macth 的相關(guān)函數(shù)來(lái)進(jìn)行數(shù)據(jù)提取了:
if let match = input.firstMatch(of: regex) {
print("Matched: \(match.0)")
print("User ID: \(match.1)")
}
RegexRepetitionBehavior
該結(jié)構(gòu)體是用來(lái)定義匹配的重復(fù)行為的,它有三個(gè)值:
- edger:會(huì)盡可能多的去匹配輸入的字符,必要的時(shí)候會(huì)回溯。默認(rèn)為edger
- reluctant:會(huì)盡可能少的去匹配輸入的字符,它會(huì)根據(jù)你的需求來(lái)一點(diǎn)點(diǎn)增大匹配區(qū)域,以完成匹配。
- possessive:會(huì)盡可能多的去匹配輸入的字符,不會(huì)回溯。
比如下面這個(gè)例子:
let testSuiteTestInputs = [ "2022-06-06 09:41:00.001", "2022-06-06 09:41:00.001.", "2022-06-06 09:41:00.001."]
let regex = Regex {
Capture(OneOrMore(.any))
Optionally(".")
}
for line in testSuiteTestInputs {
if let (dateTime) = line.wholeMatch(of: regex)?.output {
print("Matched: \(dateTime)\"")
}
}
因?yàn)檫@三條數(shù)據(jù)最后的.是不一定有的,所以我們的正則有一個(gè) Optionally(".")。但匹配出來(lái)的 dateTime 還是會(huì)帶 .。因?yàn)?edger 會(huì)匹配所有的字符包含最后的點(diǎn)在內(nèi),這樣 Optionally(".") 根本不會(huì)起作用。
改成 Capture(OneOrMore(.any, .reluctant))則會(huì)修復(fù)這個(gè)問(wèn)題。因?yàn)?reluctant 它是匹配盡可能少的輸入,所以最后的Optionally(".")會(huì)執(zhí)行。
在 Swift 5.7 中,F(xiàn)oundation 框架也對(duì) RegexBuilder 進(jìn)行適配。所以對(duì)于 Date、URL等類型,我們可以借助 Foundation 的強(qiáng)大功能來(lái)進(jìn)行解析。
Foundation 的支持
假如,我們?cè)谧鲆粋€(gè)金融相關(guān)的 APP,為了兼容一些老數(shù)據(jù),需要將一些字符串類型的數(shù)據(jù)轉(zhuǎn)為結(jié)構(gòu)體。
這是我們的字符串?dāng)?shù)據(jù):
let statement = """ CREDIT 2022/03/03 張三 ¥2,000,000.00 DEBIT 03/03/2022 Tom $2,000,000.00 DEBIT
這是我們需要轉(zhuǎn)的結(jié)構(gòu)體:
struct Trade {
let type: String
let date: Date
let name: String
let count: Decimal
}
下面這個(gè)就是我們需要編寫的 Regex:
let regex = Regex {
Capture {
/CREDIT|DEBIT/
}
OneOrMore(.whitespace)
Capture {
One(.date(.numeric, locale: Locale(identifier: "zh_CN"), timeZone: .gmt))
}
OneOrMore(.whitespace)
Capture {
OneOrMore(.word)
}
OneOrMore(.whitespace)
Capture {
One(.localizedCurrency(code: "CNY", locale: Locale(identifier: "zh_CN")))
}
}
首先,我們需要匹配固定的字符串:CREDIT/DEBIT,接著是匹配一個(gè)或者多個(gè)空格。
接下來(lái)就是 Foundation 的重頭戲了,對(duì)于日期類型的字符串,我們并不需要寫一些匹配年月日規(guī)則的正則,只需要借助 Foundation 內(nèi)嵌的功能即可。這樣做不僅省去了我們自己編寫的時(shí)間,更重要的是:官方寫的要比我們自己寫的更能保證代碼的正確性。
需要注意的是,Apple 推薦我們顯式的寫出 locale 屬性,而不是下面這種跟隨系統(tǒng)寫法 :
?
One(.date(.numeric, locale: Locale.current, timeZone: TimeZone.current))
因?yàn)檫@種寫法會(huì)帶來(lái)多種預(yù)期,并不能保證數(shù)據(jù)的確定性。
匹配完日期,接著就是對(duì)空格和用戶名的匹配。最后,是對(duì)交易金額的匹配,金額也是 Foundation 提供的函數(shù)來(lái)進(jìn)行的匹配。
測(cè)試代碼:
let result = statement.matches(of: regex)
var trades = [Trade]()
result.forEach { match in
let (_, type, date, name, count) = match.output
trades.append(Trade(type: String(type), date: date, name: String(name), count: count))
}
print(trades)
// [SwiftDemo.Trade(type: "CREDIT", date: 2022-03-03 00:00:00 +0000, name: "張三", count: 2000000), SwiftDemo.Trade(type: "DEBIT", date: 2022-03-05 00:00:00 +0000, name: "李三", count: 33.27)]
通過(guò)打印可以得知,輸出的結(jié)果并不符合預(yù)期,漏掉了 Tom 那條數(shù)據(jù)。漏掉的原因可以通過(guò)代碼一眼得知:因?yàn)閷?duì)日期和金額我們顯式的指定了是中國(guó)的格式,顯然03/03/2022 這種格式是不符合年月日的格式的。這也體現(xiàn)了顯式指定格式的好處:方便排查問(wèn)題。
我們只要將日期格式轉(zhuǎn)為年月日格式,再將 $ 轉(zhuǎn)為 ¥ 即可讓正則正確匹配。
首先,我們需要根據(jù) currency 來(lái)來(lái)返回正確的 Date 類型:
func pickStrategy(_ currency: Substring) -> Date.ParseStrategy {
switch currency {
case "$": return .date(.numeric, locale: Locale(identifier: "en_US"), timeZone: .gmt)
case "¥": return .date(.numeric, locale: Locale(identifier: "zh_CN"), timeZone: .gmt)
default: fatalError("We found another one!")
}
}
接著,編寫正則表達(dá)式來(lái)獲取相應(yīng)的字符串字段:
let regex1 = #/
(?<date> \d{2} / \d{2} / \d{4})
(?<name> \P{currencySymbol}+)
(?<currency> \p{currencySymbol})
/#
注:#//#格式為 Swift 中運(yùn)行時(shí)正則表達(dá)式的格式。
最后,再調(diào)用 replace 函數(shù)來(lái)進(jìn)行符合正則的字符替換:
statement.replace(regex1) { match -> String in
print(match.currency)
let date = try! Date(String(match.date), strategy: pickStrategy(match.currency))
// ISO 8601, it's the only way to be sure
let newDate = date.formatted(.iso8601.year().month().day())
return newDate + match.name + "¥"
}
statement = statement.replacingOccurrences(of: "-", with: "/")
這樣,我們就能解析出符合我們需求的 Trade 類型的數(shù)據(jù)了。
總結(jié)
- RegexBuilder 會(huì)使代碼更加易讀易維護(hù)
- RegexRepetitionBehavior 的三個(gè)值的區(qū)別
- 盡可能多的使用 Foundation 提供的函數(shù)來(lái)解析數(shù)據(jù)
- 使用 Foundation 時(shí)要指定格式解析數(shù)據(jù),這樣可以保證數(shù)據(jù)的唯一性
參考鏈接
以上就是Swift 中的 RegexBuilder學(xué)習(xí)指南的詳細(xì)內(nèi)容,更多關(guān)于Swift RegexBuilder的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談Swift編程中switch與fallthrough語(yǔ)句的使用
2015-11-11
Swift自動(dòng)調(diào)整視圖布局AutoLayout和AutoresizingMask功能詳解
這篇文章主要為大家介紹了Swift自動(dòng)調(diào)整視圖布局AutoLayout和AutoresizingMask功能及使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
swift實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了swift實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Swift學(xué)習(xí)教程之訪問(wèn)控制詳解
訪問(wèn)控制可以限定你在源文件或模塊中訪問(wèn)代碼的級(jí)別,也就是說(shuō)可以控制哪些代碼你可以訪問(wèn),哪些代碼你不能訪問(wèn)。下面這篇文章主要給大家介紹了關(guān)于Swift學(xué)習(xí)教程之訪問(wèn)控制的相關(guān)資料,需要的朋友可以參考下。2017-08-08
Swift實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了Swift實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Swift中switch語(yǔ)句區(qū)間和元組模式匹配
這篇文章主要介紹了Swift中switch語(yǔ)句區(qū)間和元組模式匹配的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12
Swift開(kāi)發(fā)中switch語(yǔ)句值綁定模式
本文給大家分享Swift開(kāi)發(fā)中switch語(yǔ)句值綁定模式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下2016-12-12
swift5.3 UIColor使用十六進(jìn)制顏色的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于swift5.3 UIColor使用十六進(jìn)制顏色的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

