Доступ к дополнительному захвату по имени при использовании Swift Regex Builder
Я только начинаю работать с регулярными выражениями и Swift Regex, так что имейте в виду, что моя терминология может быть неверной. Я свел эту проблему к очень простой задаче:
У меня есть строки ввода, которые либо содержат одно слово (имя), либо начинаются со слова «Тест», за которым следует один пробел, а затем имя. Я хочу извлечь имя, а также иметь доступ — без использования индексов совпадений — к совпадению «Тест» (которое может быть нулевым). Вот код, который лучше описывает проблему:
import RegexBuilder
let line1 = "Test John"
let line2 = "Robert"
let nameReference = Reference(String.self)
let testReference = Reference(String.self)
let regex = Regex {
Optionally {
Capture(as:testReference) {
"Test "
} transform : { text in
String(text)
}
}
Capture(as:nameReference) {
OneOrMore(.any)
} transform : { text in
String(text)
}
}
if let matches = try? regex.wholeMatch(in: line1) { // USE line1 OR line2 HERE
let theName = matches[nameReference]
print("Name is \(theName)")
// using index to access the test flag works fine for both line1 and line2:
if let flag = matches.1, flag == "Test " {
print("Using index: This is a test line")
} else {
print("Using index: Not a test line")
}
// but for line2, attempting to access with testReference crashes:
if matches[testReference] == "Test " { // crashes for line2 (not surprisingly)
print("Using reference: This is a test line")
} else {
print("Using reference: Not a test line")
}
}
Когда regex.wholeMatch() вызывается сline1
все работает как положено с выводом:
Name is John
Using index: This is a test line
Using reference: This is a test line
но при вызове сline2
он падает с SIGABRT и выводит:
Name is Robert
Using index: Not a test line
Could not cast value of type 'Swift.Optional<Swift.Substring>' (0x7ff84bf06f20) to 'Swift.String' (0x7ff84ba6e918).
Крушение неудивительно, потому чтоCapture(as:testReference)
никогда не совпадал.
Мой вопрос: есть ли способ сделать это без использования индексов соответствия (matches.1
)? Ответ с использованием Regex Builder будет очень признателен :-)
В документации говоритсяRegex.Match
имеетsubscript(String)
метод, который «возвращает nil, если захвата с таким именем нет». Это было бы идеально, но это работает только тогда, когда вывод совпадения имеет типAnyRegexOutput
.
1 ответ
Я не думаю, что вы можете обойтись без использования индексов или, по крайней мере, кода, который знает индекс, но может его скрыть. Синтаксический анализ регулярных выражений работает так же на любом языке, потому что всегда предполагается, что вы знаете порядок элементов в выражении.
Для чего-то подобного ваш пример можно упростить до чего-то вроде
let nameRegex = Regex {
ZeroOrMore("Test ")
Capture { OneOrMore(.anyNonNewline) }
}
if let matches = try? nameRegex.wholeMatch(in: line2) {
let (_, name) = matches.output
print("Name: \(name)")
}
Это работает для обеих ваших линий образцов. let (_, name)
не использует числовой индекс, но фактически это одно и то же, поскольку он использует индекс 1 в качестве значения дляname
.
Если ваши данные такие простые, как в этих примерах, регулярное выражение может оказаться излишним. Вы могли бы работать сif line1.hasPrefix("Test ")
для обнаружения линий сTest
а затем отбросить первые 5 символов, например.