无法使用带有 unicode 字符的电子邮件正则表达式找到匹配项

Unable to find matches using email regular expression with unicode characters

我们有 regular expression,它在我们的后端用于电子邮件验证:

/^((([a-z]|\d|[!#$%&'*+-/=\?\^{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#$%&'\*\+\-\/=\?\^_{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|.||~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))).)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i

我使用在线工具对其进行了测试,工作正常:

https://regexr.com/3u9tn

但是,我在 swift 项目中使用它时遇到了一些困难。我不得不转义特殊字符并将所有 unicode 字符包装到 {} 中以用作 swift 字符串文字。

这里是游乐场:

import Foundation

let pattern = "/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])|(([a-z]|\d|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])([a-z]|\d|-|\.|_|~|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])*([a-z]|\d|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])))\.)+(([a-z]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])|(([a-z]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])([a-z]|\d|-|\.|_|~|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])*([a-z]|[\u{00A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}])))$/i"

extension String {
    func matches(_ pattern: String) -> Bool {
        do {
            let internalExpression = try NSRegularExpression(pattern: pattern, options: .allowCommentsAndWhitespace)
            let matches = internalExpression.matches(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range:NSMakeRange(0, self.count))
            return matches.count > 0
        } catch let error {
            print(error)
            return false
        }
    }
}

let matches = "test@gmail.com".matches(pattern)
print(matches)

我尝试了不同的匹配选项,但仍然出现错误,现在我有点困惑如何进行这项工作:

The value “/^((([a-z]|\d|[!#$%&'*+-/=\?\^{\|}~]|[ -퟿豈-﷏ﷰ-￯])+(\.([a-z]|\d|[!#$%&'\*\+\-\/=\?\^_{\|}~]|[ -퟿豈-﷏ﷰ-￯])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[ -퟿豈-﷏ﷰ-￯])|(\([\x01-\x09\x0b\x0c\x0d-\x7f]|[ -퟿豈-﷏ﷰ-￯]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[ -퟿豈-﷏ﷰ-￯])|(([a-z]|\d|[ -퟿豈-﷏ﷰ-￯])([a-z]|\d|-|.||~|[ -퟿豈-﷏ﷰ-￯])([a-z]|\d|[ -퟿豈-﷏ﷰ-￯]))).)+(([a-z]|[ -퟿豈-﷏ﷰ-￯])|(([a-z]|[ -퟿豈-﷏ﷰ-￯])([a-z]|\d|-|.|_|~|[ -퟿豈-﷏ﷰ-￯])([a-z]|[ -퟿豈-﷏ﷰ-￯])))$/i” is invalid.

我已经检查过类似的问题,我的问题不是关于使用哪个 regular expression 而是如何使它工作,因为我想保持一致

感谢任何帮助。

要使模式与 ICU 正则表达式引擎一起工作,您需要进行大量更改(ICU 库在 Swift/Objective-C 中提供正则表达式功能)。

  • \u{XXXX} 包裹 \uXXXX 是错误的,您只需要对反斜杠进行两次转义,因为 ICU 正则表达式支持 \uXXXX 符号
  • 不需要转义的字符不要转义在一个字符里面class(只转义\[],其他的不用转义就可以随便放在里面转义,例如 - 应该放在字符 class)
  • 的 start/end 处
  • 合并单独的字符 classes 因为它们都匹配一个字符(即 ([a-c]|[e-g]|\d) = [a-ce-g\d]
  • (\x22): 不需要使用捕获组来包裹单个原子,在这种情况下删除括号(当你想匹配多个序列或备选方案时需要分组)
  • (\x20|\x09)*:单个字符匹配原子应该分组为一个字符class以获得更好的效率,[\x20\x09]*

你可以使用(双重转义后)

^([-a-z\d!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[-a-z\d!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|(\x22((([\x20\x09]*\x0d\x0a)?[\x20\x09]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([\x20\x09]*\x0d\x0a)?[\x20\x09]+)?\x22))@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][-a-z\d._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][-a-z\d._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))$

像这样:

let str = "дима@gmail.com"
let pattern = "(?i)^([-a-z\d!#\$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[-a-z\d!#$%&'*+/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|(\x22((([\x20\x09]*\x0d\x0a)?[\x20\x09]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([\x20\x09]*\x0d\x0a)?[\x20\x09]+)?\x22))@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][-a-z\d._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][-a-z\d._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))$"
print(str.range(of: pattern, options: .regularExpression) != nil) // => true

请注意 (?i) 为正则表达式打开不区分大小写。