解码字符串,包括 Swift 中的 '\xc3\xa6' 等 utf8 文字?

Decoding strings including utf8-literals like '\xc3\xa6' in Swift?

跟进我之前 关于 UTF-8 文字的问题:

已确定您可以像这样从仅包含 UTF-8 文字的字符串中解码 UTF-8 文字:

let s = "\xc3\xa6"
let bytes = s
    .components(separatedBy: "\x")
    // components(separatedBy:) would produce an empty string as the first element
    // because the string starts with "\x". We drop this
    .dropFirst() 
    .compactMap { UInt8([=12=], radix: 16) }
if let decoded = String(bytes: bytes, encoding: .utf8) {
    print(decoded)
} else {
    print("The UTF8 sequence was invalid!")
}

然而,这仅在字符串仅包含 UTF-8 文字时有效。当我获取包含这些 UTF-8 文字的 Wi-Fi 名称列表时,我该如何解码整个字符串?

示例:

let s = "This is a WiFi Name \xc3\xa6 including UTF-8 literals \xc3\xb8"

预期结果:

print(s)
> This is a WiFi Name æ including UTF-8 literals ø

在 Python 中有一个简单的解决方案:

contents = source_file.read()
uni = contents.decode('unicode-escape')
enc = uni.encode('latin1')
dec = enc.decode('utf-8')

在Swift5中是否有类似的方法来解码这些字符串?

首先将解码代码添加到字符串扩展中作为计算 属性(或创建一个函数)

extension String {
    var decodeUTF8: String {
        let bytes = self.components(separatedBy: "\x")
            .dropFirst()
            .compactMap { UInt8([=10=], radix: 16) }
        return String(bytes: bytes, encoding: .utf8) ?? self
    }
}

然后使用正则表达式并使用while循环匹配来替换所有匹配值

while let range = string.range(of: #"(\x[a-f0-9]{2}){2}"#, options: [.regularExpression, .caseInsensitive]) {
    string.replaceSubrange(range, with: String(string[range]).decodeUTF8)
}

据我所知,没有本地 Swift 解决方案。为了使它在调用站点看起来像 Python 版本一样紧凑,您可以在 String 上构建一个扩展以隐藏复杂性

extension String {
   func replacingUtf8Literals() -> Self {

      let regex = #"(\x[a-zAZ0-9]{2})+"#
      
      var str = self
      
      while let range = str.range(of: regex, options: .regularExpression) {
         let literalbytes = str[range]
            .components(separatedBy: "\x")
            .dropFirst()
            .compactMap{UInt8([=10=], radix: 16)}
         guard let actuals = String(bytes: literalbytes, encoding: .utf8) else {
            fatalError("Regex error")
         }
         str.replaceSubrange(range, with: actuals)
      }
      return str
   }
}

这样你就可以调用

print(s.replacingUtf8Literals()). 

//prints: This is a WiFi Name æ including UTF-8 literals ø

为了方便起见,我用 fatalError 捕获了一个失败的转换。您可能希望在生产代码中以更好的方式处理此问题(尽管,除非正则表达式错误,否则它永远不会发生!)。这里需要有某种形式的中断或错误抛出,否则你有一个无限循环。