【问题标题】:Normalizing (composing and decomposing) utf8 strings in Swift在 Swift 中规范化(组合和分解)utf8 字符串
【发布时间】:2021-09-11 08:11:57
【问题描述】:

Unicode 字符串中带有重音符号的字符可以用“短”(组合)和“长”(分解)格式表示。这意味着在 Xcode 中,字符串 a 的长度为 8,而字符串 b 的长度为 10,即使它们看起来相同:

let a:String = "δέκα" // 8 bytes
print(a.data(using:String.Encoding.utf8)!.count)

let b:String = "δέκα" // 10 bytes
print(b.data(using:String.Encoding.utf8)!.count)

我需要“缩小”字符串以确保它们始终采用较短的“组合”格式。这是如何在 Swift 中完成的?


脚注:我知道像这样(如下)完全去除重音是可能的。我不想那样做,我只想“组合”角色。

let usPosixLocale = Locale(identifier: "en_US_POSIX")
let out = "δέκα".folding(options: [.caseInsensitive, .diacriticInsensitive], locale: usPosixLocale)

我知道.widthInsensitive 选项,但文档似乎表明它仅适用于亚洲字符。所以具体来说,这 用于组合或分解字符:

let out = a.folding(options: [.widthInsensitive], locale: usPosixLocale)

更新

这是代码的第二个较长版本,为了清楚起见显示了字节差异。

let a:String = String(bytes:[206, 180, 206, 173, 206, 186, 206, 177], encoding:.utf8)!
print(a, a.data(using:String.Encoding.utf8)!.count)

let b:String = String(bytes:[206, 180, 206, 181, 204, 129, 206, 186, 206, 177], encoding:.utf8)!
print(b, b.data(using:String.Encoding.utf8)!.count)

let usPosixLocale = Locale(identifier: "en_US_POSIX")
let out = b.folding(options: [.widthInsensitive], locale: usPosixLocale)
    print(out.data(using:String.Encoding.utf8)!.count)

【问题讨论】:

  • 当我从这里复制/粘贴到 iPad Playground (Swift 5.3) 时,我得到了 8,请参阅 imgur.com/VsXdFpc
  • 您能否使用for unit in a.utf8 { print(unit) } 共享ab 的UTF8 单位输出
  • 也许是 Stackoverflow 或浏览器复制粘贴规范了它。我会想办法把字节放进去。
  • 好的,我已经添加了相同代码的第二个更长(但更难阅读)的副本,其中字符串表示为字节,因此代码绝对是可重现的)
  • 我认为你想要的词是“规范化”。 developer.apple.com/documentation/corefoundation/…

标签: swift string unicode unicode-normalization


【解决方案1】:

precomposedStringWithCanonicalMapping 进行标准化:

let a = "δέκα"
print(a, Data(a.utf8).count) // δέκα 8

let b = "δε\u{0301}κα"
print(b, Data(b.utf8).count) // δέκα 10

let bn = b.precomposedStringWithCanonicalMapping
print(bn, Data(bn.utf8).count) // δέκα 8

“字面”比较表明abn 相同,但与b 不同:

print(b.compare(a, options: .literal) == .orderedSame)  // false
print(bn.compare(a, options: .literal) == .orderedSame) // true

备注: precomposedStringWithCanonicalMapping 生成“Unicode 规范化表格 C”。还有precomposedStringWithCompatibilityMapping,它产生“Unicode 规范化表格 KC”。见

Unicode 标准中的精确差异。粗略地说,后者折叠了更多“在许多情况下被不当区分”的差异。例子:

let c = "\u{fb01}" // LATIN SMALL LIGATURE FI
print(c, c.precomposedStringWithCanonicalMapping, c.precomposedStringWithCompatibilityMapping)
// fi fi fi

let d = "2\u{2075}"
print(d, d.precomposedStringWithCanonicalMapping, d.precomposedStringWithCompatibilityMapping)
// 2⁵ 2⁵ 25

let e = "\u{2165}" // ROMAN NUMERAL SIX
print(e, e.precomposedStringWithCanonicalMapping, e.precomposedStringWithCompatibilityMapping)
// Ⅵ Ⅵ VI

【讨论】:

    【解决方案2】:

    感谢@matt指向CFStringNormalize(_:_:)

    你可以这样做 -

    import Foundation
    import CoreFoundation
    
    extension String {
        func normalizedCanonicallyComposed() -> String {
            let mutable = NSMutableString(string: self) as CFMutableString
            CFStringNormalize(mutable, .KC) // OR .C
            return mutable as String
        }
    }
    

    用法

    let a: String = String(bytes: [206, 180, 206, 173, 206, 186, 206, 177], encoding: .utf8)!
    print(a, a.data(using: .utf8)!.count)
    
    let b: String = String(bytes: [206, 180, 206, 181, 204, 129, 206, 186, 206, 177], encoding: .utf8)!
    print(b, b.data(using: .utf8)!.count)
            
    print("Before - \(b), count: \(b.data(using: .utf8)!.count)")
    let c = b.normalizedCanonicallyComposed()
    print("After - \(c), count: \(c.data(using: .utf8)!.count)")
    

    输出

    δέκα 8
    δέκα 10
    Before - δέκα, count: 10
    After - δέκα, count: 8
    

    【讨论】:

      猜你喜欢
      • 2010-10-10
      • 2023-03-06
      • 2014-03-04
      • 2018-09-29
      • 2020-02-12
      • 2012-04-25
      • 2017-04-13
      • 2015-07-06
      • 2014-05-10
      相关资源
      最近更新 更多