I want to have multiple capture groups that can be optional and I want to access the strings they correspond to.
Something that looks/works like this:
let text1 = "something with foo and bar"
let text2 = "something with just bar"
let regex = NSRegularExpression(pattern: "(foo)? (bar)")
for (first?, second) in regex.matches(in:text1) {
print(first) // foo
print(second) // bar
}
for (first?, second) in regex.matches(in:text2) {
print(first) // nil
print(second) // bar
}
Retrieving captured subtext with NSRegularExpression
is not so easy.
First of all, the result of matches(in:range:)
is [NSTextCheckingResult]
, and each NSTextCheckingResult
does not match to tuple like (first?, second)
.
If you want to retrieve captured subtext, you need to get the range from the NSTextCheckingResult
with rangeAt(_:)
method. rangeAt(0)
represents the range matching the whole pattern, rangeAt(1)
for the first capture, rangeAt(2)
for the second, and so on.
And rangeAt(_:)
returns an NSRange
, not Swift Range
. The content (location
and length
) is based on the UTF-16 representation of NSString
.
And this is the most important part for your purpose, rangeAt(_:)
returns NSRange(location: NSNotFound, length: 0)
for each missing capture.
So, you may need to write something like this:
let text1 = "something with foo and bar"
let text2 = "something with just bar"
let regex = try! NSRegularExpression(pattern: "(?:(foo).*)?(bar)") //please find a better example...
for match in regex.matches(in: text1, range: NSRange(0..<text1.utf16.count)) {
let firstRange = match.rangeAt(1)
let secondRange = match.rangeAt(2)
let first = firstRange.location != NSNotFound ? (text1 as NSString).substring(with: firstRange) : nil
let second = (text1 as NSString).substring(with: secondRange)
print(first) // Optioonal("foo")
print(second) // bar
}
for match in regex.matches(in: text2, range: NSRange(0..<text2.utf16.count)) {
let firstRange = match.rangeAt(1)
let secondRange = match.rangeAt(2)
let first = firstRange.location != NSNotFound ? (text2 as NSString).substring(with: firstRange) : nil
let second = (text2 as NSString).substring(with: secondRange)
print(first) // nil
print(second) // bar
}