Swift extract regex matches
Asked Answered
B

16

232

I want to extract substrings from a string that match a regex pattern.

So I'm looking for something like this:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {
   ???
}

So this is what I have:

func matchesForRegexInText(regex: String!, text: String!) -> [String] {

    var regex = NSRegularExpression(pattern: regex, 
        options: nil, error: nil)

    var results = regex.matchesInString(text, 
        options: nil, range: NSMakeRange(0, countElements(text))) 
            as Array<NSTextCheckingResult>

    /// ???

    return ...
}

The problem is, that matchesInString delivers me an array of NSTextCheckingResult, where NSTextCheckingResult.range is of type NSRange.

NSRange is incompatible with Range<String.Index>, so it prevents me of using text.substringWithRange(...)

Any idea how to achieve this simple thing in swift without too many lines of code?

Boredom answered 10/1, 2015 at 20:4 Comment(4)
@JoakimDanielson "Fattie wants to reward an existing answer"Accused
@JoakimDanielson yeah, but it is described on the reasons pageAccused
Feel free to delete resolved commentsBatch
Feel free to delete resolved comments @JoakimDanielson TYBatch
S
384

Even if the matchesInString() method takes a String as the first argument, it works internally with NSString, and the range parameter must be given using the NSString length and not as the Swift string length. Otherwise it will fail for "extended grapheme clusters" such as "flags".

As of Swift 4 (Xcode 9), the Swift standard library provides functions to convert between Range<String.Index> and NSRange.

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Example:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

Note: The forced unwrap Range($0.range, in: text)! is safe because the NSRange refers to a substring of the given string text. However, if you want to avoid it then use

        return results.flatMap {
            Range($0.range, in: text).map { String(text[$0]) }
        }

instead.


(Older answer for Swift 3 and earlier:)

So you should convert the given Swift string to an NSString and then extract the ranges. The result will be converted to a Swift string array automatically.

(The code for Swift 1.2 can be found in the edit history.)

Swift 2 (Xcode 7.3.1) :

func matchesForRegexInText(regex: String, text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text,
                                            options: [], range: NSMakeRange(0, nsString.length))
        return results.map { nsString.substringWithRange($0.range)}
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Example:

let string = "🇩🇪€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]

Swift 3 (Xcode 8)

func matches(for regex: String, in text: String) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex)
        let nsString = text as NSString
        let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range)}
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

Example:

let string = "🇩🇪€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]
Swope answered 10/1, 2015 at 20:12 Comment(22)
You saved me from becoming insane. Not kidding. Thank you so much!Boredom
@MathijsSegers: I have updated the code for Swift 1.2/Xcode 6.3. Thanks for letting me know!Swope
but what if i want to search for strings between a tag? I need the same result (match information) like: regex101.com/r/cU6jX8/2. which regex pattern would you suggest?Sievers
The update is for Swift 1.2, not Swift 2. The code doesn't compile with Swift 2.Rupe
@pnollet: Strange. I have just double-checked that the "Swift 2" version compiles and runs with Xcode 7 GM, and it worked as expected. What error message do you get?Swope
Thanks! What if you only want to extract what's actually between () in the regex? For example, in "[0-9]{3}([0-9]{6})" I'd only want to get the last 6 numbers.Woke
If I try using this method to extract text between parentheses I get compile errors (using Swift 2) - the reg ex I am passing in is "\((.*?)\)"can anyone help?Cyprinid
@KevinMann Double backslash the special characters. You need to escape the string and the special characterSylviasylviculture
The Swift 3 example here does not compile. The try statement gives a compile error "Errors thrown from here are not handled"Whitman
@FuadKamal: That is strange. I have double-checked it with Xcode 8, and it compiles and runs as expected.Swope
@MartinR it works for me now as well. Didn't even have to do a clean...Xcode strangeness I guess ¯_(ツ)_/¯ Thanks for checking!Whitman
@MartinR shouldn't you check for nil?Adulthood
@VyachaslavGerchicov: $0.range is an NSRange returned from regex.matches(...) as the range in the string which matches the given pattern. Therefore I think it is safe to assume that it can be converted back to a Range<String.Index>.Swope
In Swift 4, the line text.substring(with: Range($0.range, in: text)!) generates a deprecated warning. I looked here developer.apple.com/documentation/foundation/nsstring/… and I don't see anything about deprecation. This answer has a solution to a slightly different problem, but I can't apply it to silence the warning. #45563162Nonce
@Adrian: You are right. The new version should work without warnings. Thanks for the notice!Swope
It would be great if you could provide a solution without to force-unwrap the range!Probationer
@ixany: Have a look!Swope
So much hassle just to extract a substring with a regex. It should be possible to do this with a single line of code. Unbelievable! Thanks!Regretful
I like the NSRange(text.startIndex..., in: text) open range. +1Fosterfosterage
Not working if I am trying to find multiple quoted words in a string: #57853415Urina
You can also remove the explicit unwrap by doing return results.map { (text as NSString).substring(with: $0.range) } I submitted an edit to the answer.Hallerson
Still saving a lot of lives! :DMisapply
G
75

My answer builds on top of given answers but makes regex matching more robust by adding additional support:

  • Returns not only matches but returns also all capturing groups for each match (see examples below)
  • Instead of returning an empty array, this solution supports optional matches
  • Avoids do/catch by not printing to the console and makes use of the guard construct
  • Adds matchingStrings as an extension to String

Swift 4.2

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.range(at: $0).location != NSNotFound
                    ? nsString.substring(with: result.range(at: $0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

Swift 3

//: Playground - noun: a place where people can play

import Foundation

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAt($0).location != NSNotFound
                    ? nsString.substring(with: result.rangeAt($0))
                    : ""
            }
        }
    }
}

"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

Swift 2

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
        let nsString = self as NSString
        let results  = regex.matchesInString(self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map { result in
            (0..<result.numberOfRanges).map {
                result.rangeAtIndex($0).location != NSNotFound
                    ? nsString.substringWithRange(result.rangeAtIndex($0))
                    : ""
            }
        }
    }
}
Guardian answered 14/10, 2016 at 10:6 Comment(8)
Good idea about the capture groups. But why is "guard" Swiftier than "do/catch"??Swope
I agree with people such as nshipster.com/guard-and-defer who say Swift 2.0 certainly seems to be encouraging a style of early return [...] rather than nested if statements. The same holds true for nested do/catch statements IMHO.Guardian
try/catch is the native error handling in Swift. try? can be used if you are only interested in the outcome of the call, not in a possible error message. So yes, guard try? .. is fine, but if you want to print the error then you need a do-block. Both ways are Swifty.Swope
I agree that you need do/catch in your example if you want to see the error in the console. Since I want to provide a function that can be reused in production code without modifying it (print is an unwanted side effect for me), guard try? becomes a little Swiftier (if you don't need the side effect) – modified the answer to clarify. Thanks!Guardian
I have added unittests to your nice snippet, gist.github.com/neoneye/03cbb26778539ba5eb609d16200e4522Shown
Was about to write my own based on the @MartinR answer until i saw this. Thanks!Kiss
Don't you think it is time to add Swift 4 version?Requisite
If I use this on a regex which can find quoted words in a sentence/string like this: "hi \"how\", \"are\", you". The result is this: [["\"how\", \"are\"", "how\", \"are"]]. here is the regex which I am using: "\"(.*)\""Urina
T
38

The fastest way to return all matches and capture groups in Swift 5

extension String {
    func match(_ regex: String) -> [[String]] {
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
            (0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
        } ?? []
    }
}

Returns a 2-dimentional array of strings:

"prefix12suffix fix1su".match("fix([0-9]+)su")

returns...

[["fix12su", "12"], ["fix1su", "1"]]

// First element of sub-array is the match
// All subsequent elements are the capture groups
Thereinafter answered 16/6, 2019 at 7:33 Comment(3)
is options: [] really required?Erick
How do we know this is the fastest way to do it?Hoe
Fantastic canned solution if not yet on iOS16Batch
D
15

If you want to extract substrings from a String, not just the position, (but the actual String including emojis). Then, the following maybe a simpler solution.

extension String {
  func regex (pattern: String) -> [String] {
    do {
      let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
      let nsstr = self as NSString
      let all = NSRange(location: 0, length: nsstr.length)
      var matches : [String] = [String]()
      regex.enumerateMatchesInString(self, options: NSMatchingOptions(rawValue: 0), range: all) {
        (result : NSTextCheckingResult?, _, _) in
        if let r = result {
          let result = nsstr.substringWithRange(r.range) as String
          matches.append(result)
        }
      }
      return matches
    } catch {
      return [String]()
    }
  }
} 

Example Usage:

"someText 👿🏅👿⚽️ pig".regex("👿⚽️")

Will return the following:

["👿⚽️"]

Note using "\w+" may produce an unexpected ""

"someText 👿🏅👿⚽️ pig".regex("\\w+")

Will return this String array

["someText", "️", "pig"]
Donelson answered 6/11, 2015 at 13:20 Comment(3)
This is what I wantedSilverts
Nice! It needs a little adjustment for Swift 3, but it's great.Stricklin
@Stricklin what is the adjustment it needs? I'm using swift 5.1.3Overshoe
A
13

Update for iOS 16: Regex, RegexBuilder 👷‍♀️

Xcode previously supported Regex with Apple's NSRegularExpression. The Swift API was verbose and challenging to use correctly, so Apple released Regex Literal support and RegexBuilder this year. The Regex flavor used by Regex types is the same as NSRegularExpression, i.e. the ICU Unicode specification.

The API has been simplified going forward to tidy up complex String range-based parsing logic in iOS 16 / macOS 13 as well as improve performance.

Another advantage of using literals is that we get compile time errors in case we use invalid RegEx syntax: Cannot parse regular expression... with a clear description of the RegEx error. Enjoy!

RegEx literals in Swift 5.7

func parseLine(_ line: Substring) throws -> MailmapEntry {

    let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/

    guard let match = line.prefixMatch(of: regex) else {
        throw MailmapError.badLine
    }

    return MailmapEntry(name: match.1, email: match.2)
}

We are able to match using:

  1. firstMatch(of:): Returns the first match for the regex within this collection, where the regex is created by the given closure (RegEx literal).

  2. prefixMatch(of:): Returns a match if this string is matched by the given regex at its start.

  3. wholeMatch(of:): Matches a regex in its entirety, where the regex is created by the given closure (RegEx literal).

  4. matches(of:): Returns a collection containing all non-overlapping matches of the regex, created by the given closure (RegEx literal).

I've linked to the docs above. The new RegEx literal syntax has multiple new APIs such as trimmingPrefix(), contains() and more, so I do encourage exploring the docs further for more nuanced use cases.

There is equivalent syntax of the above methods where we call prefixMatch(in:) on the Regex literal itself and pass in the string to search in. I prefer the syntax above however choose whichever you prefer.

Example code:

let aOrB = /[ab]+/

if let stringMatch = try aOrB.firstMatch(in: "The year is 2022; last year was 2021.") {
    print(stringMatch.0)
} else {
    print("No match.")
}
// prints "a"

RegexBuilder in Swift 5.7

RegexBuilder is a new API released by Apple aimed at making RegEx code easier to write in Swift. We can translate the Regex literal /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/ from above into a more declarative form using RegexBuilder if we want more readability.

Do note that we can use raw strings in a RegexBuilder and also interleave Regex Literals in the builder if we want to balance readability with conciseness.

import RegexBuilder

let regex = Regex {
    ZeroOrMore(.horizontalWhitespace)
    Optionally {
        Capture(OneOrMore(.noneOf("<#")))
    }
        .repetitionBehavior(.reluctant)
    ZeroOrMore(.horizontalWhitespace)
    "<"
    Capture(OneOrMore(.noneOf(">#")))
    ">"
    ZeroOrMore(.horizontalWhitespace)
    /#|\Z/
}

The RegEx literal /#|\Z/ is equivalent to:

ChoiceOf {
   "#"
   Anchor.endOfSubjectBeforeNewline
}

Composable RegexComponent

RegexBuilder syntax is similar to SwiftUI also in terms of composability because we can reuse RegexComponents within other RegexComponents:

struct MailmapLine: RegexComponent {
    @RegexComponentBuilder
    var regex: Regex<(Substring, Substring?, Substring)> {
        ZeroOrMore(.horizontalWhitespace)
        Optionally {
            Capture(OneOrMore(.noneOf("<#")))
        }
            .repetitionBehavior(.reluctant)
        ZeroOrMore(.horizontalWhitespace)
        "<"
        Capture(OneOrMore(.noneOf(">#")))
        ">"
        ZeroOrMore(.horizontalWhitespace)
        ChoiceOf {
           "#"
            Anchor.endOfSubjectBeforeNewline
        }
    }
}

Source: Some of this code is taken from the WWDC 2022 video "What's new in Swift".

Aten answered 7/6, 2022 at 18:24 Comment(0)
F
12

I found that the accepted answer's solution unfortunately does not compile on Swift 3 for Linux. Here's a modified version, then, that does:

import Foundation

func matches(for regex: String, in text: String) -> [String] {
    do {
        let regex = try RegularExpression(pattern: regex, options: [])
        let nsString = NSString(string: text)
        let results = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
        return results.map { nsString.substring(with: $0.range) }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

The main differences are:

  1. Swift on Linux seems to require dropping the NS prefix on Foundation objects for which there is no Swift-native equivalent. (See Swift evolution proposal #86.)

  2. Swift on Linux also requires specifying the options arguments for both the RegularExpression initialization and the matches method.

  3. For some reason, coercing a String into an NSString doesn't work in Swift on Linux but initializing a new NSString with a String as the source does work.

This version also works with Swift 3 on macOS / Xcode with the sole exception that you must use the name NSRegularExpression instead of RegularExpression.

Frenchpolish answered 17/10, 2016 at 18:45 Comment(0)
M
7

Swift 4 without NSString.

extension String {
    func matches(regex: String) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else { return [] }
        let matches  = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
        return matches.map { match in
            return String(self[Range(match.range, in: self)!])
        }
    }
}
Murrey answered 27/2, 2019 at 7:23 Comment(1)
Be careful with above solution: NSMakeRange(0, self.count) is not correct, because self is a String (=UTF8) and not an NSString (=UTF16). So the self.count is not necessarily the same as nsString.length (as used in other solutions). You can replace the range calculation with NSRange(self.startIndex..., in: self)Misconduct
T
5

@p4bloch if you want to capture results from a series of capture parentheses, then you need to use the rangeAtIndex(index) method of NSTextCheckingResult, instead of range. Here's @MartinR 's method for Swift2 from above, adapted for capture parentheses. In the array that is returned, the first result [0] is the entire capture, and then individual capture groups begin from [1]. I commented out the map operation (so it's easier to see what I changed) and replaced it with nested loops.

func matches(for regex: String!, in text: String!) -> [String] {

    do {
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text, options: [], range: NSMakeRange(0, nsString.length))
        var match = [String]()
        for result in results {
            for i in 0..<result.numberOfRanges {
                match.append(nsString.substringWithRange( result.rangeAtIndex(i) ))
            }
        }
        return match
        //return results.map { nsString.substringWithRange( $0.range )} //rangeAtIndex(0)
    } catch let error as NSError {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

An example use case might be, say you want to split a string of title year eg "Finding Dory 2016" you could do this:

print ( matches(for: "^(.+)\\s(\\d{4})" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]
Thoth answered 6/8, 2016 at 19:18 Comment(2)
This answer made my day. I spent 2 hours searching for a solution that can satisfy regualr expression with the additional capturing of groups.Requisite
This works but it will crash if any range is not found. I modified this code so that the function returns [String?] and in the for i in 0..<result.numberOfRanges block, you have to add a test that only appends the match if the range != NSNotFound, otherwise it should append nil. See: https://mcmap.net/q/23838/-nsregularexpression-is-giving-incorrect-result-from-the-numberofranges-methodSpectrometer
T
4

Most of the solutions above only give the full match as a result ignoring the capture groups e.g.: ^\d+\s+(\d+)

To get the capture group matches as expected you need something like (Swift4) :

public extension String {
    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
    }
}
Tull answered 6/12, 2017 at 10:5 Comment(2)
This is great if you're wanting just the first result, to get each result it needs for index in 0..<matches.count { around let lastRange... results.append(matchedString)}Cecil
the for clause should look like this: for i in 1...lastRangeIndex { let capturedGroupIndex = match.range(at: i) if capturedGroupIndex.location != NSNotFound { let matchedString = (self as NSString).substring(with: capturedGroupIndex) results.append(matchedString.trimmingCharacters(in: .whitespaces)) } }Suddenly
F
2

This is how I did it, I hope it brings a new perspective how this works on Swift.

In this example below I will get the any string between []

var sample = "this is an [hello] amazing [world]"

var regex = NSRegularExpression(pattern: "\\[.+?\\]"
, options: NSRegularExpressionOptions.CaseInsensitive 
, error: nil)

var matches = regex?.matchesInString(sample, options: nil
, range: NSMakeRange(0, countElements(sample))) as Array<NSTextCheckingResult>

for match in matches {
   let r = (sample as NSString).substringWithRange(match.range)//cast to NSString is required to match range format.
    println("found= \(r)")
}
Feeley answered 4/11, 2015 at 17:18 Comment(0)
H
2

This is a very simple solution that returns an array of string with the matches

Swift 3.

internal func stringsMatching(regularExpressionPattern: String, options: NSRegularExpression.Options = []) -> [String] {
        guard let regex = try? NSRegularExpression(pattern: regularExpressionPattern, options: options) else {
            return []
        }

        let nsString = self as NSString
        let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))

        return results.map {
            nsString.substring(with: $0.range)
        }
    }
Hylton answered 2/10, 2017 at 15:47 Comment(1)
Be careful using NSMakeRange(0, self.count), because self is a String (=UTF8) and not an NSString (=UTF16). So the self.count is not necessarily the same as nsString.length (as used in other solutions). You can replace the range calculation with NSRange(self.startIndex..., in: self).Marillin
C
2

update @Mike Chirico's to Swift 5

extension String{



  func regex(pattern: String) -> [String]?{
    do {
        let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options(rawValue: 0))
        let all = NSRange(location: 0, length: count)
        var matches = [String]()
        regex.enumerateMatches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: all) {
            (result : NSTextCheckingResult?, _, _) in
              if let r = result {
                    let nsstr = self as NSString
                    let result = nsstr.substring(with: r.range) as String
                    matches.append(result)
              }
        }
        return matches
    } catch {
        return nil
    }
  }
}
Cudbear answered 13/2, 2021 at 19:57 Comment(0)
G
2

On iOS 16 there is new syntax that makes this way easier. For example, for anything within brackets in this string

let randomLog = "2493875469750,1678798470864,{latitude: 50, longitude: 43}"

if let match = randomLog.firstMatch(of: /\{.*\}/) {
    print(match.output)
}

This prints

"{"latitude": 50, "longitude": 43}"

In order to become a Swift Regex Pro, or just for more info, have a look at WWDC 2022: https://developer.apple.com/videos/play/wwdc2022/110357/

Gnawing answered 15/3, 2023 at 15:43 Comment(1)
The new regex literal Swift syntax is part of Swift 5.7, not iOS 16. And other answers already cover this feature.Dinette
A
1

Big thanks to Lars Blumberg his answer for capturing groups and full matches with Swift 4, which helped me out a lot. I also made an addition to it for the people who do want an error.localizedDescription response when their regex is invalid:

extension String {
    func matchingStrings(regex: String) -> [[String]] {
        do {
            let regex = try NSRegularExpression(pattern: regex)
            let nsString = self as NSString
            let results  = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
            return results.map { result in
                (0..<result.numberOfRanges).map {
                    result.range(at: $0).location != NSNotFound
                        ? nsString.substring(with: result.range(at: $0))
                        : ""
                }
            }
        } catch let error {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
}

For me having the localizedDescription as error helped understand what went wrong with escaping, since it's displays which final regex swift tries to implement.

Aaren answered 5/12, 2018 at 18:0 Comment(0)
R
1

basic phone number matching

let phoneNumbers = ["+79990001101", "+7 (800) 000-11-02", "+34 507 574 147 ", "+1-202-555-0118"]

let match: (String) -> String = {
    $0.replacingOccurrences(of: #"[^\d+]"#, with: "", options: .regularExpression)
}

print(phoneNumbers.map(match))
// ["+79990001101", "+78000001102", "+34507574147", "+12025550118"]
Romanic answered 14/12, 2021 at 8:37 Comment(0)
P
1

You can use matching(regex:) on the string like:

let array = try "Your String To Search".matching(regex: ".")

using this simple extension:

public extension String {
    func matching(regex: String) throws -> [String] {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: self, range: NSRange(startIndex..., in: self))
        return results.map { String(self[Range($0.range, in: self)!]) }
    }
}
Praseodymium answered 23/1, 2023 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.