Как зафиксировать более 10 вещей с помощью Swift 5.7 RegexBuilder?
Допустим, у меня есть файл, в котором хранится информация о людях, и одна из строк выглядит так:
Sweeper 30 1992-09-22 China/Beijing - 0 2020-07-07 Mary/Linda - Pizza/Lemon
Слева направо это имя, возраст, дата рождения, страна рождения, город рождения, количество детей, дата брака (необязательно), имя жены (необязательно), имя бывшей жены (необязательно), любимая еда, наименее любимая еда.
Я хочу получить всю информацию из строки с помощью модуля Swift 5.7 RegexBuilder, я пробовал:
let regex = Regex {
/([a-zA-Z ]+)/ // Name
" "
TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
" "
Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
" "
/([a-zA-Z ]+)/ // Country of Birth
"/"
/([a-zA-Z ]+)/ // City of Birth
" - "
TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count
Optionally {
" "
Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
Optionally {
" "
/([a-zA-Z ]+)/ // Wife
Optionally {
"/"
/([a-zA-Z ]+)/ // Ex-wife
}
}
}
" - "
/([a-zA-Z ]+)/ // Favourite food
"/"
/([a-zA-Z ]+)/ // Least Favourite Food
}
Однако Swift говорит, что не может проверить это за разумное время.
Я знаю, причина этого в том, чтоRegexComponentBuilder
(построитель результатов для компонентов регулярных выражений) имеет перегрузки только до 10 "C" или что-то в этом роде (не слишком уверен в деталях):
static func buildPartialBlock<W0, W1, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, R0, R1>(
accumulated: R0,
next: R1) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10)> where R0 : RegexComponent, R1 : RegexComponent, R0.RegexOutput == (W0, C1, C2, C3), R1.RegexOutput == (W1, C4, C5, C6, C7, C8, C9, C10
)
Если я сделаю всеOptionally
требуются детали , сообщение об ошибке становится немного более очевидным.
Неоднозначное использование 'buildPartialBlock(accumulated:next:)'
SwiftUI имеет аналогичную проблему, когда количество представлений в построителе представлений не может превышать 10, и в этом случае вы просто используетеGroup
чтобы сделать некоторые из представлений одним представлением. Можете ли вы сделать что-то подобное в RegexBuilder? Сделать некоторые снимки одним? Кажется, это как-то связаноAnyRegexOutput
, но я не уверен, как его использовать.
Как устранить эту ошибку компилятора?
Чтобы избежать проблемы XY:
У меня есть файл данных, в котором данные отформатированы очень бессистемно, то есть вообще не очень машиночитаемы, как CSV или JSON. Строки пишутся во всевозможных форматах. Случайные разделители используются в случайных местах.
Тогда другая строка в файле будет содержать ту же информацию, но отформатированную по-другому.
Что я хочу сделать, так это преобразовать этот странно отформатированный файл в удобный для работы формат, такой как CSV. Я решил сделать это с помощью Swift 5.7 RegexBuilder API. Я бы нашел строку в файле, написал регулярное выражение, соответствующее этой строке, преобразовал все строки файла, соответствующие этому регулярному выражению, в CSV, затем промыл и повторил.
Поэтому я хотел бы избежать использования нескольких регулярных выражений для анализа одной строки, так как это означало бы, что я буду писать намного больше регулярных выражений.
Я не уверен, что такой парсер, как ANTLR4, решит мою проблему. Учитывая, насколько случайным образом отформатирован файл, мне пришлось бы много менять синтаксический анализатор , заставляя файлы генерироваться снова и снова. Я не думаю, что это будет так же удобно, как использование RegexBuilder.
1 ответ
В качестве хака можно создать обобщенный CustomConsumingRegexComponent
реализация, которая принимает
- любой
RegexComponent
построенный от строителя, у которого всегда есть(Substring, A, B, C ...)
кортеж в качестве вывода - преобразование, которое преобразует этот кортеж в желаемый тип
По сути, мы можем создать компонент регулярного выражения, который принимает некоторое регулярное выражение и выводит любой тип.T
мы хотим, по сути, «группировать» захваты.
Также можно просто не выполнять преобразование, и вы получите вложенные кортежи, но мне это не нравится.
struct Group<RegexOutput, Component: RegexComponent>: CustomConsumingRegexComponent {
let component: () -> Component
let transform: (Component.RegexOutput) -> RegexOutput
init(@RegexComponentBuilder _ regexBuilder: @escaping () -> Component, transform: @escaping (Component.RegexOutput) -> RegexOutput) {
component = regexBuilder
self.transform = transform
}
func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: RegexOutput)? {
let innerRegex = Regex(component)
guard let match = input[index...].prefixMatch(of: innerRegex) else { return nil }
let upperBound = match.range.upperBound
let output = match.output
let transformedOutput = transform(output)
return (upperBound, transformedOutput)
}
}
Причина, по которой это всего лишь хак, заключается в том, что регулярное выражение внутри фактически не знает о вещах за пределами , поэтому квантификаторы внутри не будут возвращаться назад, чтобы попытаться сопоставить вещи за пределами .
Например, чтобы исправить код в вопросе, я могу поместить всю информацию, связанную с браком, в файл , но мне нужно добавить внутрь:
struct Marriage {
let marriageDate: Date
let wife: Substring?
let exWife: Substring?
}
let r = Regex {
/([a-zA-Z ]+)/ // Name
" "
TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
" "
Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
" "
/([a-zA-Z ]+)/ // Country of Birth
"/"
/([a-zA-Z ]+)/ // City of Birth
" - "
TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count
Optionally {
" "
Capture(Group {
Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
Optionally {
" "
/([a-zA-Z ]+)/ // Wife
Optionally {
"/"
/([a-zA-Z ]+)/ // Ex-wife
}
}
Lookahead(" - ")
} transform: { (_, date, wife, exWife) in
Marriage(marriageDate: date, wife: wife, exWife: exWife as? Substring) // unwrap the double optional
})
}
" - "
/([a-zA-Z ]+)/ // Favourite food
"/"
/([a-zA-Z ]+)/ // Least Favourite Food
}
Без предпросмотра происходит вот что:
Сокровенный[a-zA-Z ]+
будет соответствоватьLinda
, а также пробел после него, в результате чего" - "
не совпадать. Обычно это вызывало бы откат, но поскольку вещи внутри не знают о вещах за пределамиGroup
, возврата здесь не происходит, и полное совпадение не выполняется.