How to group search regular expressions using swift
Asked Answered
S

5

16

In regular expressions you can group different matches to easily "pattern match" a given match.

while match != nil {
  match = source.rangeOfString(regex, options: .RegularExpressionSearch)
  if let m = match {
    result.append(source.substringWithRange(m)
      source.replaceRange(m, with: "") 
  }
}

The above works find to find a range of the match, but it cannot tell me the group. For instance if I search for words encapsulated in "" I would like to match a "word" but quickly fetch only word

Is it possible to do so in swift?

Shrievalty answered 21/11, 2014 at 18:0 Comment(0)
D
34

Swift is pretty ugly right now with regular expressions -- let's hope for more-native support soon! The method on NSRegularExpression you want is matchesInString. Here's how to use it:

let string = "This is my \"string\" of \"words\"."
let re = NSRegularExpression(pattern: "\"(.+?)\"", options: nil, error: nil)!
let matches = re.matchesInString(string, options: nil, range: NSRange(location: 0, length: string.utf16Count))

println("number of matches: \(matches.count)")

for match in matches as [NSTextCheckingResult] {
    // range at index 0: full match
    // range at index 1: first capture group
    let substring = (string as NSString).substringWithRange(match.rangeAtIndex(1))
    println(substring)
}

Output:

number of matches: 2
string
words
Dickens answered 21/11, 2014 at 18:16 Comment(1)
thanks. This will have to do for now. I was hoping for a more native solution, but well, swift is and will be "extension based", so I don't think it will be implemented in the near future, except for the 3rd part developerShrievalty
F
7

You can use this if you want to collect the matched strings. (My answer is derived from Nate Cooks very helpful answer.)

Updated for Swift 2.1

extension String {
    func regexMatches(pattern: String) -> Array<String> {
        let re: NSRegularExpression
        do {
            re = try NSRegularExpression(pattern: pattern, options: [])
        } catch {
            return []
        }

        let matches = re.matchesInString(self, options: [], range: NSRange(location: 0, length: self.utf16.count))
        var collectMatches: Array<String> = []
        for match in matches {
            // range at index 0: full match
            // range at index 1: first capture group
            let substring = (self as NSString).substringWithRange(match.rangeAtIndex(1))
            collectMatches.append(substring)
        }
        return collectMatches
    }
}

Updated for Swift 3.0

extension String {
func regexMatches(pattern: String) -> Array<String> {
    let re: NSRegularExpression
    do {
        re = try NSRegularExpression(pattern: pattern, options: [])
    } catch {
        return []
    }

    let matches = re.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
    var collectMatches: Array<String> = []
    for match in matches {
        // range at index 0: full match
        // range at index 1: first capture group
        let substring = (self as NSString).substring(with: match.rangeAt(1))
        collectMatches.append(substring)
    }
    return collectMatches
}}
Foreknow answered 12/5, 2015 at 19:9 Comment(1)
This is great, thank you very much. It's buggy with the following though: "This is my \"string\" of \"words\".".regexMatches("(my )|(of )\"(.+?)\""). The two ranges it finds are: ["(8,3)", "(9223372036854775807,0)"]Bourassa
P
4

how about this guys, add as extension to String? )) all matches, all groups ) self = String if you want to add not as extension then add String parameter and replace all self to your parameter :)

 func matchesForRegexInTextAll(regex: String!) -> [[String]] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = self as NSString

        var resultsFinal = [[String]]()

        let results = regex.matchesInString(self,
            options: [], range: NSMakeRange(0, nsString.length))

        for result in results {
            var internalString = [String]()
            for var i = 0; i < result.numberOfRanges; ++i{
                internalString.append(nsString.substringWithRange(result.rangeAtIndex(i)))
            }
            resultsFinal.append(internalString)
        }

        return resultsFinal
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}
Precarious answered 18/11, 2015 at 15:51 Comment(0)
P
4

All the answers provided are good, but nonetheless I am going to provide my String extension written in Swift 2.2.

Noted differences:

  • only use the first match
  • supports multiple captured groups
  • a more accurate function name (it is capture groups, not matches)

.

extension String {
    func capturedGroups(withRegex pattern: String) -> [String]? {
        var regex: NSRegularExpression
        do {
            regex = try NSRegularExpression(pattern: pattern, options: [])
        } catch {
            return nil
        }

        let matches = regex.matchesInString(self, options: [], range: NSRange(location:0, length: self.characters.count))

        guard let match = matches.first else { return nil }

        // Note: Index 1 is 1st capture group, 2 is 2nd, ..., while index 0 is full match which we don't use
        let lastRangeIndex = match.numberOfRanges - 1
        guard lastRangeIndex >= 1 else { return nil }

        var results = [String]()

        for i in 1...lastRangeIndex {
            let capturedGroupIndex = match.rangeAtIndex(i)
            let matchedString = (self as NSString).substringWithRange(capturedGroupIndex)
            results.append(matchedString)
        }

        return results
    }
}

To use:

// Will match "bcde"
"abcdefg".capturedGroups(withRegex: "a(.*)f")
Put answered 21/7, 2016 at 8:31 Comment(2)
I upvoted, but finally this code is not reliable. With the pattern "([NSEW]|\\-?)\\s*([0-9]{1,3}(\\.[0-9]+)?)(,\\s*|\\s+)([NSEW]|\\-?)\\s*([0-9]{1,3}(\\.[0-9]+)?)" and the string "1 2" it crashes (capturedGroupIndex.location is too high for the last match).Bertrando
Adding a check to capturedGroupIndex.location != NSNotFound and returning an empty string fixed the problem. Please consider updating your answer for the others.Bertrando
U
1

Updated for Swift 4

/**
 String extension that extract the captured groups with a regex pattern

 - parameter pattern: regex pattern
 - Returns: captured groups
 */
public func capturedGroups(withRegex pattern: String) -> [String] {
    var results = [String]()

    var regex: NSRegularExpression
    do {
        regex = try NSRegularExpression(pattern: pattern, options: [])
    } catch {
        return results
    }
    let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.count))

    guard let match = matches.first else { return results }

    let lastRangeIndex = match.numberOfRanges - 1
    guard lastRangeIndex >= 1 else { return results }

    for i in 1...lastRangeIndex {
        let capturedGroupIndex = match.range(at: i)
        let matchedString = (self as NSString).substring(with: capturedGroupIndex)
        results.append(matchedString)
    }

    return results
}

To use:

// Will match "bcde"
"abcdefg".capturedGroups(withRegex: "a(.*)f")

Gist on github: https://gist.github.com/unshapedesign/1b95f78d7f74241f706f346aed5384ff

Undersurface answered 1/12, 2017 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.