InjectionIII 中 replacingOccurrences 使用

874 阅读1分钟

InjectionIII 中,有下面的代码对字符串进行了扩展,这篇文章是对这些方法的分析。

fileprivate extension StringProtocol {
    subscript(range: NSRange) -> String? {
        return Range(range, in: String(self)).flatMap { String(self[$0]) }
    }
    
    func escaping(_ chars: String, with template: String = "\\$0") -> String {
        return self.replacingOccurrences(of: "[\(chars)]",
            with: template.replacingOccurrences(of: "\\", with: "\\\\"),
            options: [.regularExpression])
    }
    func unescape() -> String {
        return replacingOccurrences(of: #"\\(.)"#, with: "$1",
                                    options: .regularExpression)
    }
}

其中 subscript 的功能相对简单,下面来分析一下 escapingunescape 方法

正则匹配中的 $0\\

func escaping(_ chars: String, with template: String = "\\$0") -> String {
    return self.replacingOccurrences(of: "[\(chars)]",
        with: template.replacingOccurrences(of: "\\", with: "\\\\"),
        options: [.regularExpression])
}

其中 template 参数的默认值为 "\0",其中一个 `\` 为反义符,所以 template 的真实值时 `\0。同理,template.replacingOccurrences(of: "\", with: "\\") 的结果为:\$0`。

所以,在 template 缺省的情况下,方法可以的执行效果为:

func escaping(_ chars: String) -> String {
    return self.replacingOccurrences(of: "[\(chars)]", 
        with: "\\\\$0"),
        options: [.regularExpression])
}

在字符串的替换方法 replacingOccurrences 中的 options 参数为 [.regularExpression],也就是通过正则表达式的方式进行匹配替换。在正则表达式中,需要提取的内容为 [\(chars)],也就是将包含在 chars 字符,替换成为 \\$0

\\$0 是什么意思呢?这个字符串又可以分为两部分 \\$0。在正则表达式中,\ 又是反义符,所以 \\ 表达的意思为 \ 字符。$0 为一个模板,将会用匹配到的字符串替换掉该模板。下面通过一个示例来说明 $0 的作用:

let string = "abc"
print(string.replacingOccurrences(of: "b", with: "$B$"))

输出:

a$B$c

有了 $0,是不是有 $1$2 呢?下面会讲。

也就是说 \\$0 表达的意思是:在匹配到的字符串前面加上 \。所以 escaping(_ chars:) 的方法的作用为:将包含在 chars 中的字符的前面加上 \

字符串前后的 #

func unescape() -> String {
    return replacingOccurrences(of: #"\\(.)"#, with: "$1",
                                options: .regularExpression)
}

在这个方法的参数中,先看 #"\\(.)"#

扩展字符串分隔符

字符串和字符 中有一段对描述:

您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(")中并用数字符号(#)括起来。例如,打印字符串文字 #"Line 1 \nLine 2"# 会打印换行符转义序列(\n)而不是给文字换行。

如果需要字符串文字中字符的特殊效果,请匹配转义字符(\)后面添加与起始位置个数相匹配的 # 符。 例如,如果您的字符串是 #"Line 1 \nLine 2"# 并且您想要换行,则可以使用 #"Line 1 \#nLine 2"# 来代替。 同样,###"Line1 \###nLine2"### 也可以实现换行效果。

扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 """,覆盖原有的结束文字的默认行为。例如:

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#

print(threeQuotationMarks)

输入出:Here are three more double quotes: """

总结来说,我们可以通过 扩展字符串分隔符将字符串中的特殊字符直接包含而非转义后的效果

所以参数中的 #"\\(.)"# 就是想表达 \\(.) 的字符串。同样因为正则表达式中 \ 有反义符的作用,所以在正则表达式中表示的内容为 \(.)

正则表达式中的分组

在正则表达式中可以使用括号 () 进行分组,想要获取到各个分组中的内容,就是使用 $ + 分组序列。所以$1 就是配到字符串中第 1 分组中的内容。

在正在表达式中 . 表示匹配除 \n\r 之外的任何单个字符。

所以正则表达式想要查找的内容为:如果发现一个字符前面有 \,则只保留这个字符,也就是去掉字符前面的 \

参考

How to replace occurences in string using groups in Swift? juejin.cn/post/684490…