如何用Swift 5.7的RegexBuilder捕获10个以上内容?
解决Swift RegexBuilder捕获项超过10个的编译错误
问题场景
假设我们有一行人员信息:
Sweeper 30 1992-09-22 China/Beijing - 0 2020-07-07 Mary/Linda - Pizza/Lemon
从左到右依次对应:姓名、年龄、出生日期、出生国家、出生城市、子女数量、结婚日期(可选)、妻子姓名(可选)、前妻姓名(可选)、喜爱食物、最不喜爱食物。
使用Swift 5.7的RegexBuilder编写匹配正则时,会遇到无法在合理时间内完成类型检查的错误(若将可选部分设为必填,错误会明确为Ambiguous use of 'buildPartialBlock(accumulated:next:)')。
原因分析
RegexComponentBuilder的buildPartialBlock重载仅支持最多10个顶层捕获项,当正则中的捕获项(包括Capture/TryCapture以及隐式捕获的分组)超过这个数量时,编译器无法解析对应的类型,从而报错。
解决方案:用Group合并捕获项
和SwiftUI中用Group合并视图的思路一致,我们可以把逻辑相关的多个捕获项打包到Group中,将其转化为一个复合捕获元组(或自定义结构体实例),从而减少顶层捕获项的总数,使其控制在10个以内。
修改后的正则代码
import RegexBuilder import Foundation // 可根据需求定义结构体优化数据提取(可选) struct BirthPlace { let country: Substring let city: Substring } struct MarriageDetails { let date: Date let spouse: Substring? let exSpouse: Substring? } struct FoodPreferences { let favourite: Substring let leastFavourite: Substring } let regex = Regex { // 1. 姓名 Capture { /[a-zA-Z ]+/ } " " // 2. 年龄(转Int) TryCapture { OneOrMore(.digit) } transform: { Int($0) } " " // 3. 出生日期(转Date) Capture(.iso8601Date(timeZone: .gmt)) " " // 4. 出生地(合并为结构体) Group { /[a-zA-Z ]+/ "/" /[a-zA-Z ]+/ } transform: { BirthPlace(country: $0.0, city: $0.1) } " - " // 5. 子女数量(转Int) TryCapture { OneOrMore(.digit) } transform: { Int($0) } // 6. 婚姻信息(可选,合并为结构体) Optionally { " " Group { Capture(.iso8601Date(timeZone: .gmt)) Optionally { " " Group { /[a-zA-Z ]+/ Optionally { "/" /[a-zA-Z ]+/ } } } } transform: { MarriageDetails( date: $0.0, spouse: $0.1?.0, exSpouse: $0.1?.1 ) } } " - " // 7. 食物偏好(合并为结构体) Group { /[a-zA-Z ]+/ "/" /[a-zA-Z ]+/ } transform: { FoodPreferences(favourite: $0.0, leastFavourite: $0.1) } }
代码说明
- 合并捕获项:将出生国家/城市、婚姻相关信息、食物偏好分别打包到
Group中,原本的多个捕获项被合并为单个顶层捕获(或结构体实例),顶层捕获总数降至7个,符合RegexBuilder的限制。 - 结构体优化:通过
transform将Group的捕获结果转为自定义结构体,后续提取数据时无需手动解析嵌套元组,代码更易读维护。 - 保持单一正则:全程用一个正则处理整行内容,避免拆分多个正则增加复杂度,适合处理格式杂乱的文件场景。
提取匹配结果示例
let input = "Sweeper 30 1992-09-22 China/Beijing - 0 2020-07-07 Mary/Linda - Pizza/Lemon" if let match = input.firstMatch(of: regex) { let name = match.1 let age = match.2 let birthDate = match.3 let birthPlace = match.4 let childrenCount = match.5 let marriageDetails = match.6 let foodPreferences = match.7 // 输出示例 print("姓名:\(name)") print("出生地:\(birthPlace.country)-\(birthPlace.city)") if let marriage = marriageDetails { print("结婚日期:\(marriage.date)") print("配偶:\(marriage.spouse ?? "无")") print("前配偶:\(marriage.exSpouse ?? "无")") } }
内容的提问来源于stack exchange,提问作者Sweeper
相关产品推荐
相关产品推荐

