jjc99
jjc99

Reputation: 3569

Swift replace substring regex

I am attempting to use regular expression to replace all occurrences of UK car registrations within a string.

The following swift code works perfectly for a when the string matches the regex exactly as below.

var myString = "DD11 AAA"
var stringlength = countElements(myString) 
var ierror: NSError?
var regex:NSRegularExpression = NSRegularExpression(pattern: "^([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}$", options: NSRegularExpressionOptions.CaseInsensitive, error: &ierror)!
var modString = regex.stringByReplacingMatchesInString(myString, options: nil, range: NSMakeRange(0, stringlength), withTemplate: "XX")
print(modString)

The result is XX

However, the following does not work and the string is not modifed

var myString = "my car reg 1 - DD11 AAA  my car reg 2 - AA22 BBB"
var stringlength = countElements(myString) 
var ierror: NSError?
var regex:NSRegularExpression = NSRegularExpression(pattern: "^([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}$", options: NSRegularExpressionOptions.CaseInsensitive, error: &ierror)!
var modString = regex.stringByReplacingMatchesInString(myString, options: nil, range: NSMakeRange(0, stringlength), withTemplate: "XX")
print(modString)

The result is my car reg 1 - DD11 AAA my car reg 2 - AA22 BBB

Can anyone give me any pointers?

Upvotes: 45

Views: 64775

Answers (9)

Markus
Markus

Reputation: 3363

Swift 5.7 update

let myString = "my car reg 1 - DD11 AAA  my car reg 2 - AA22 BBB"
let regex = #/([A-HK-PRSVWY][A-HJ-PR-Y])\s?([0][2-9]|[1-9][0-9])\s?[A-HJ-PR-Z]{3}/#
let modString = myString.replacing(regex.ignoresCase(), with: "XX")

print(modString)
// Output: "my car reg 1 - XX  my car reg 2 - XX"

Upvotes: 5

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119128

Simple extension:

extension String {

    func replacingRegex(
        matching pattern: String,
        findingOptions: NSRegularExpression.Options = .caseInsensitive,
        replacingOptions: NSRegularExpression.MatchingOptions = [],
        with template: String
    ) throws -> String {

        let regex = try NSRegularExpression(pattern: pattern, options: findingOptions)
        let range = NSRange(startIndex..., in: self)
        return regex.stringByReplacingMatches(in: self, options: replacingOptions, range: range, withTemplate: template)
    }
 }

āœ… Advantages to other answers

  • Exposed throwing error to the caller
  • Exposed finding options to the caller with default for the ease of use
  • Exposed replacing options to the caller with default for the ease of use
  • Fixed the range BUG šŸž in the original answer

Upvotes: 6

Shiki Suen
Shiki Suen

Reputation: 89

A notice to all answers that uses .count in their answers:

This will cause problems in cases that the operating target range has surrogate-paired characters.

Please fix your answers by using .utf16.count instead.

Here's Ryan Brodie 's answer with this fix. It works with Swift 5.5.

private extension String {
    mutating func regReplace(pattern: String, replaceWith: String = "") {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
            let range = NSRange(location: 0, length: self.utf16.count)
            self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
        } catch { return }
    }
}

Update: If considering @coyer 's concerns:

private extension String {
    mutating func regReplace(pattern: String, replaceWith: String = "") {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
            let range = NSRange(self.startIndex..., in: self)
            self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
        } catch { return }
    }
}

Also: to @Martin R' : It is okay to use ^ and $ in Regex as long as you have enabled the ".anchorsMatchLines" in the Regex options. I already applied this option in the codeblocks above.

Upvotes: 3

Ryan Brodie
Ryan Brodie

Reputation: 6620

Let's use a class extension to wrap this up in Swift 3 syntax:

extension String {
    mutating func removingRegexMatches(pattern: String, replaceWith: String = "") {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
            let range = NSRange(location: 0, length: count)
            self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
        } catch { return }
    }
}

var phoneNumber = "+1 07777777777"
phoneNumber.removingRegexMatches(pattern: "\\+\\d{1,4} (0)?")

Results in 7777777777 (thus removing country code from phone number)

Upvotes: 33

coyer
coyer

Reputation: 4367

Warning

Do not use NSRange(location: 0, length: myString.count) as all examples above quoted.

Use NSRange(myString.startIndex..., in: myString) instead!

.count will count newline characters like \r\n as one character - this may result in a shortened, thus invalid, NSRange that does not match the whole string.

(.length should work)

Upvotes: 7

black_pearl
black_pearl

Reputation: 2699

Swift 4.2 Updated

let myString = "my car reg 1 - DD11 AAA  my car reg 2 - AA22 BBB"
if let regex = try? NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: .caseInsensitive) {
    let modString = regex.stringByReplacingMatches(in: myString, options: [], range: NSRange(location: 0, length:  myString.count), withTemplate: "XX")
    print(modString)
}

Upvotes: 11

DarkDust
DarkDust

Reputation: 92306

You need to remove the ^ and $ anchors.

The ^ means start of string and $ means end of string (or line, depending on the options). That's why your first example works: in the first test string, the start of the string is really followed by your pattern and ends with it.

In the second test string, the pattern is found in the middle of the string, thus the ^... can't apply. If you would just remove the ^, the $ would apply on the second occurrence of the registration number and the output would be my car reg 1 - DD11 AAA my car reg 2 - XX.

let myString = "my car reg 1 - DD11 AAA  my car reg 2 - AA22 BBB"
let regex = try! NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: NSRegularExpression.Options.caseInsensitive)
let range = NSMakeRange(0, myString.count)
let modString = regex.stringByReplacingMatches(in: myString, options: [], range: range, withTemplate: "XX")
print(modString)
// Output: "my car reg 1 - XX  my car reg 2 - XX"

Upvotes: 56

Daniel J
Daniel J

Reputation: 446

Update for Swift 2.1:

var myString = "my car reg 1 - DD11 AAA  my car reg 2 - AA22 BBB"
if let regex = try? NSRegularExpression(pattern: "([A-HK-PRSVWY][A-HJ-PR-Y])\\s?([0][2-9]|[1-9][0-9])\\s?[A-HJ-PR-Z]{3}", options: .CaseInsensitive) {
    let modString = regex.stringByReplacingMatchesInString(myString, options: .WithTransparentBounds, range: NSMakeRange(0, myString.characters.count), withTemplate: "XX")
    print(modString)
}

Upvotes: 9

Martin R
Martin R

Reputation: 539685

With pattern: "^ ... $" you have specified that the pattern is anchored to the start and end of the string, in other words, the entire string must match the pattern. Just remove ^ and $ from the pattern and you'll get the expected result.

Upvotes: 4

Related Questions