Swift替换子字符串正则表达式

45

我正在尝试使用正则表达式在字符串中替换所有的英国车牌号码。

以下 Swift 代码可以完美地处理与下面正则表达式完全匹配的情况。


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)
结果为XX。但是下面的方法不起作用,字符串没有被修改。
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)
结果是我的汽车注册1号-DD11 AAA,我的汽车注册2号-AA22 BBB。 有人能给我一些指南吗?
9个回答

55
你需要移除 ^$ 锚点。 ^ 代表字符串的开头,$ 代表字符串的结尾(或行,具体取决于选项)。这就是为什么您的第一个示例有效的原因:在第一个测试字符串中,字符串的开头确实跟随着您的模式并以其结束。
在第二个测试字符串中,模式位于字符串中间,因此^...... 不适用。如果你只是移除了 ^,那么 $ 将应用于注册号的第二次出现,并且输出将是 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"

2
DarkDust提到的选项是AnchorsMatchLines - onmyway133
由于某种原因,NSRegularExpression 在 Swift 3 中似乎无法工作。你会如何纠正这个问题? - xxmbabanexx

33

让我们使用类扩展来用 Swift 3 语法包装这个功能:

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)?")

结果为7777777777(从电话号码中删除国家代码)


在 Swiftlint 中使用 NSMakeRange 会生成警告:warning: Legacy Constructor Violation: Swift constructors are preferred over legacy convenience functions. (legacy_constructor) - ablarg
@ablarg,您可以通过将该行替换为以下内容来修复它:let range = NSRange(location: 0, length: self.count) - Constantine

11

Swift 4.2 已更新

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)
}

9

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)
}

7

警告

请勿使用上述所有示例中引用的NSRange(location: 0, length: myString.count)

请改用NSRange(myString.startIndex..., in: myString)

.count将计算换行符(如\r\n)作为一个字符 - 这可能导致缩短、因此无效的NSRange,这不会匹配整个字符串。

(.length应该是有效的)


6

简单的扩展:

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)
    }
 }

✅ 与其他答案相比的优点

  • 向调用者抛出error
  • 向调用者公开查找选项,并提供默认值以便使用
  • 向调用者公开替换选项,并提供默认值以便使用
  • 修复原始答案中的range BUG

4
使用pattern: "^ ... $"指定模式是锚定到字符串的开头和结尾,换句话说,整个字符串必须匹配该模式。仅需从模式中删除^$即可获得预期结果。请注意保留HTML标记。

3

针对所有使用.count的答案,请注意:

这将导致在操作目标范围中存在代理配对字符的情况下出现问题。

请使用.utf16.count修正您的答案。

以下是Ryan Brodie的答案,已经进行了修复。此修复适用于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 }
    }
}

更新:如果考虑 @coyer 的担忧:

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 }
    }
}

此外,针对Martin R' 的问题: 只要您在正则表达式选项中启用了 ".anchorsMatchLines",使用 "^" 和 "$" 在正则表达式中就是可以的。我已经在上面的代码块中应用了这个选项。

0
Swift 5.7 更新
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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接