在Swift中解码包括utf8字头的字符串,如'\xc3\xa6'?

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($0, 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')

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

你是如何给文本中的十六进制字元划定界限的? 它们后面总是会有一个空格吗?
@flanker 很遗憾,它们只是在文本里面。中间或后面没有空格或任何东西。 所以一个常见的字符串可以是 "Netv\xc3\xa6rk 5GHz",表示 "Netværk 5GHz"
循环往复,用regex找到下一个字的范围,提取该范围并解码,然后用解码后的版本替换该范围? 如果没有人想出一个可行的解决方案,我稍后将进行破解。
@flanker, 谢谢,我希望有一个更简单的Swift选项,就像下面的Python例子一样。因为我在Regex方面不是很强。不过,如果没有人有答案,我一定会感谢你的努力
答案1

据我所知,这没有原生的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($0, 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 来捕获一个失败的转换。 你可能想在生产代码中以更好的方式来处理这个问题(虽然,除非重码是错误的,否则应该永远不会发生!)。 这里需要有某种形式的中断或错误抛出,否则就会出现无限循环。

如果你从你的regex和 "separateBy "中删除2个\,使其let regex = #"(?:\x[a-zAZ0-9]{2})+"# 和 separatedBy。"\x" 这样就能完美地工作了,谢谢!
是的,我太粗心了。 我在玩铰链的时候,把已经转义的ring转义了,结果出现了双转义......然后就把它复制过来了!我已经编辑了答案。 我已经编辑了答案。
答案2

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

extension String {
    var decodeUTF8: String {
        let bytes = self.components(separatedBy: "\\x")
            .dropFirst()
            .compactMap { UInt8($0, 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)
}