Как создать Swift Regex, который выводит пользовательский тип?
В видеороликах WWDC было показано, что вы можете сделать что-то подобное с помощьюCapture
с/TryCapture
s в построителе регулярных выражений:
let regex = Regex {
// ...
TryCapture {
OneOrMore(.digit)
} transform: {
Int($0)
}
// ...
}
И вывод будет безопасным по типу. Будет выведенInt
для этой группы вместоSubstring
как это обычно бывает.
Однако я хотел бы изменить весь тип вывода всего, например, применивtransform:
в конце закрытия. Например, чтобы разобрать строку, содержащую имя, возраст и дату рождения человека:
John (30) 1992-09-22
Я хотел бы сделать что-то вроде:
// this doesn't work and is just for illustration - there is no such Regex.init
let regex = Regex {
Capture(/\w+/)
" ("
TryCapture(/\d+/) { Int($0) }
") "
Capture(.iso8601Date(timeZone: .gmt))
} transform: { (_, name, age, dob) in
Person(name: String(name), age: age, dob: dob)
}
И я ожидалregex
быть типаRegex<Person>
, и неRegex<(Substring, Substring, Int, Date)>
. То есть,someString.wholeMatch(of: regex).output
будет строкой, а не кортежем.
Я в основном просто пытаюсь уменьшить встречаемость кортежей, потому что мне очень неудобно с ними работать, особенно безымянными. СRegexComponent
параметризован неограниченным типом, и есть встроенные типы, в которыхRegexOutput
являетсяDate
иDecimal
, конечно, сделать это для произвольных типов с использованием регулярных выражений не невозможно, верно?
Моя попытка была:
struct Person {
let name: String
let age: Int
let dob: Date
}
let line = "John (30) 1992-09-22"
let regex = Regex {
Capture {
Capture(/\w+/)
" ("
TryCapture(/\d+/) { Int($0) }
") "
Capture(.iso8601Date(timeZone: .gmt))
} transform: { (_, name, age, dob) in
Person(name: String(name), age: age, dob: dob)
}
}
line.wholeMatch(of: regex)
но это разбилось во время выполнения, выдав сообщение:
Не удалось преобразовать значение типа «Swift.Substring» (0x7ff865e3ead8) в «(Swift.Substring, Swift.Substring, Swift.Int, Foundation.Date)» (0x7ff863f2e660).
Еще одна моя попытка использоватьCustomConsumingRegexComponent
показано здесь, в этом ответе , но с довольно большим предостережением, а именно с тем, что он не отступает должным образом.
Как я могу создатьRegex
который выводит мой собственный тип?
1 ответ
Из того, что я читал/видел в примерах (например, swift-regex), было бы неплохо создать компонент регулярного выражения, аналогичный.word
,.digit
, но вложенностьcaptures
не кажется, что работает легко.
Вот пример запуска на игровой площадке для созданияPerson struct
пример:
public static func regexBuilderMatching(string: String = "John (30) 1992-09-22") {
struct Person: CustomStringConvertible {
let name: String
let age: Int
let dob: Date
public func dobToFormatterString() -> String {
let dateFormatter = DateFormatter()
// 1992-09-22 04:00:00 +0000
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.string(from: self.dob)
}
var description: String {
return "\(name), age: \(age), has dob: \(dobToFormatterString())"
}
}
func dateFromString(dateString: String) -> Date? {
let formatter = DateFormatter()
formatter.timeStyle = .none // removes time from date
formatter.dateStyle = .full
formatter.dateFormat = "y-MM-d" // 1992-09-22
return formatter.date(from: dateString)
}
let regexWithBasicCapture = Regex {
/* 1. */ Capture { OneOrMore(.word) }
/* 2. */ " ("
/* 3. */ TryCapture { OneOrMore(.digit) }
transform: { match in
Int(match)
}
/* 4. */ ") "
/* 5. */ TryCapture { OneOrMore(.iso8601Date(timeZone: .gmt)) }
transform: { match in
dateFromString(dateString: String(match))
}
}
let matches = string.matches(of: regexWithBasicCapture)
for match in matches {
// shorthand syntax using match output
// https://developer.apple.com/documentation/swift/regex/match
let (_, name, age, date) = match.output
let person = Person(name: String(name), age: age, dob: date)
print(person)
}
}
Приведенный выше код выведет:
John, age: 30, has dob: 1992-09-22