当源代码包含Unicode字符时,Swift正则表达式匹配失败

8

我正在尝试使用NSRegularExpression进行简单的正则表达式匹配,但当源字符串包含多字节字符时,我遇到了一些问题:

let string = "D 9"

// The following matches (any characters)(SPACE)(numbers)(any characters)
let pattern = "([\\s\\S]*) ([0-9]*)(.*)"

let slen : Int = string.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)

var error: NSError? = nil

var regex = NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions.DotMatchesLineSeparators, error: &error)

var result = regex?.stringByReplacingMatchesInString(string, options: nil, range: NSRange(location:0,
length:slen), withTemplate: "First \"$1\" Second: \"$2\"")

上面的代码按预期返回"D"和"9"

如果我现在更改第一行,将英国“镑”货币符号包含在内:

let string = "£ 9"

然后,即使表达式中的([\\s\\S]*)部分仍应匹配任何前导字符,但匹配不起作用。

我知道£符号将占用两个字节,但通配符前导匹配不应该忽略它们吗?

有人能解释一下这里发生了什么吗?


我不熟悉Swift及其正则表达式引擎,但通常情况下,当涉及Unicode时,发现\s\S等同于.,我会感到非常惊讶。你为什么不在第一组中使用.*呢?话虽如此,我并不完全相信问题就出在这里;我认为更可能的是[0-9]无法匹配Unicode数字,而不是\S无法匹配任意非空格Unicode字符。 - Kyle Strand
Swift 确实 支持 \d 字符类,那么为什么你要使用 [0-9] 呢?如果你尝试使用 (.*) (\d*)(.*) 进行匹配,你能得到一个匹配吗? - Kyle Strand
谢谢Kyle。我之前使用\s\S是因为误读了一篇关于“.”字符误用的文章。我已将其更改为“(.) (\d)(.*)”,但仍无法匹配。我开始怀疑这是Swift实现中的一个错误 - 任何其他字符都可以正常匹配 - 例如“D$+@ 9”,但当我在要匹配的字符串中放置一个“£”符号时,它就失败了! - NEIL STRONG
2个回答

14

这可能会让人感到困惑。 stringByReplacingMatchesInString() 的第一个参数从 Objective-C 中的 NSString 映射到 Swift 中的 String,但 range: 参数仍然是 NSRange。因此,您必须使用 NSString 使用的单位(即 UTF-16 代码点的数量)来指定范围:

var result = regex?.stringByReplacingMatchesInString(string,
        options: nil,
        range: NSRange(location:0, length:(string as NSString).length),
        withTemplate: "First \"$1\" Second: \"$2\"")

或者您可以使用count(string.utf16)代替(string as NSString).length

完整示例:

let string = "£ 9"

let pattern = "([\\s\\S]*) ([0-9]*)(.*)"
var error: NSError? = nil
let regex = NSRegularExpression(pattern: pattern,
        options: NSRegularExpressionOptions.DotMatchesLineSeparators,
        error: &error)!

let result = regex.stringByReplacingMatchesInString(string,
    options: nil,
    range: NSRange(location:0, length:(string as NSString).length),
    withTemplate: "First \"$1\" Second: \"$2\"")
println(result)
// First "£" Second: "9"

谢谢Martin - 这解释了为什么包含货币符号的字符串长度被报告为4而不是3。现在它正确地报告了长度,但很遗憾,表达式仍然不匹配。 - NEIL STRONG
1
抱歉,Martin - 或许现在还太早了 - 你的解决方案确实有效!! 非常感谢!! :-) - NEIL STRONG
非常感谢!我花了很多时间来解决这个问题。 - 3li

0
我遇到过这个问题几次,Martin的答案帮助我理解了问题。这是对我有用的解决方案的快速版本。
如果您的正则表达式函数包括像这样构建的范围参数:
NSRange(location: 0, length: yourString.count)

你可以将它修改为这个:

NSRange(location: 0, length: yourString.utf16.count)

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