Regular Expression Capture Groups in Swift

Regular expressions can be used to extract structured data from expected formats using capture groups. This post presents examples of using named and unnamed regular expression capture groups in Swift:

  1. Capture Groups
    a. Create A NSRegularExpression
    b. Find Capture Groups
    c. Extract Capture Groups
  2. Named Capture Groups
    a. Create A NSRegularExpression With Named Capture Groups
    b. Find Named Capture Groups
    c. Extract Named Capture Groups

Capture Groups

Capture groups are a feature of regular expressions that allow data to be extracted from structured formats. For example, in the string "firstName middleName lastName" the substring "firstName" can be extracted using a capture group.

When creating a regular expression, use parentheses to form a capture group. In the following example, the first capture group is ([a-zA-Z-]+) which matches one or more lowercase or uppercase letters.

Create A NSRegularExpression

let name = "firstName middleName lastName"
let nameRange = NSRange(
    name.startIndex..<name.endIndex,
    in: name
)

// Create A NSRegularExpression
let capturePattern = #"([a-zA-Z-]+) ?.* ([a-zA-Z-]+)?"#
let captureRegex = try! NSRegularExpression(
    pattern: capturePattern,
    options: []
)

Find Capture Groups

// Find the matching capture groups
let matches = captureRegex.matches(
    in: name,
    options: [],
    range: nameRange
)

guard let match = matches.first else {
    // Handle exception
    throw NSError(domain: "", code: 0, userInfo: nil)
}

Extract Capture Groups

Capture groups are available as ranges on a NSTextCheckingResult. Use numberOfRanges to determine the number of matches and range(at:) to get the NSRange corresponding to the capture group at a specific index.

var names: [String] = []

// For each matched range, extract the capture group
for rangeIndex in 0..<match.numberOfRanges {
    let matchRange = match.range(at: rangeIndex)
    
    // Ignore matching the entire username string
    if matchRange == nameRange { continue }
    
    // Extract the substring matching the capture group
    if let substringRange = Range(matchRange, in: name) {
        let capture = String(name[substringRange])
        names.append(capture)
    }
}

names.first // firstName
names.last // lastName

Named Capture Groups

Named capture groups enable access to capture groups using a name, instead of a range index. Named capture groups may improve code readability for complex regular expressions and regular expressions with multiple capture groups.

Create A NSRegularExpression With Named Capture Groups

let birthday = "01/02/2003"
let birthdayRange = NSRange(
    birthday.startIndex..<birthday.endIndex,
    in: birthday
)

// Create A NSRegularExpression
let capturePattern = 
    #"(?<month>\d{1,2})\/"# + 
    #"(?<day>\d{1,2})\/"# + 
    #"(?<year>\d{1,4})"#

let birthdayRegex = try! NSRegularExpression(
    pattern: capturePattern,
    options: []
)

Find Named Capture Groups

// Find the matching capture groups
let matches = birthdayRegex.matches(
    in: birthday,
    options: [],
    range: birthdayRange
)

guard let match = matches.first else {
    // Handle exception
    throw NSError(domain: "", code: 0, userInfo: nil)
}

Extract Named Capture Groups

To get the NSRange associated with a named capture group, pass the name of the capture group to range(withName:) on a NSTextCheckingResult.

var captures: [String: String] = [:]

// For each matched range, extract the named capture group
for name in ["month", "day", "year"] {
    let matchRange = match.range(withName: name)
    
    // Extract the substring matching the named capture group
    if let substringRange = Range(matchRange, in: birthday) {
        let capture = String(birthday[substringRange])
        captures[name] = capture
    }
}

captures["month"] // 01
captures["day"] // 02
captures["year"] // 2003

Capture Groups in Swift

That’s it! By using NSRegularExpression you can extract data using regular expression capture groups in Swift.