【问题标题】:Folding/Normalizing Ligatures (e.g. Æ to ae) Using (Core)Foundation使用 (Core)Foundation 折叠/规范化连字(例如 Æ 到 ae)
【发布时间】:2012-02-21 11:18:29
【问题描述】:

我正在编写一个助手,它对输入字符串执行许多转换,以便创建该字符串的搜索友好表示。

考虑以下场景:

  • 对德语或法语文本进行全文搜索
  • 数据存储中的条目包含
    1. Müller
    2. Großmann
    3. Çingletòn
    4. Bjørk
    5. Æreogramme
  • 搜索应该是模糊的,因为
    1. ullÜll 等匹配 Müller
    2. Gros, groß 等匹配 Großmann
    3. cin 等匹配 Çingletòn
    4. bjöbjo 等匹配 Bjørk
    5. aereo 等匹配 Æreogramme

到目前为止,我在案例(1)、(3)和(4)中都取得了成功。

我想不通的是如何处理(2)和(5)。

到目前为止,我尝试了以下方法均无济于事:

CFStringNormalize() // with all documented normalization forms
CFStringTransform() // using the kCFStringTransformToLatin, kCFStringTransformStripCombiningMarks, kCFStringTransformStripDiacritics
CFStringFold() // using kCFCompareNonliteral, kCFCompareWidthInsensitive, kCFCompareLocalized in a number of combinations -- aside: how on earth do I normalize simply _composing_ already decomposed strings??? as soon as I pack that in, my formerly passing tests fail, as well...

我浏览了ICU User Guide for Transforms,但并没有在上面投入太多……原因很明显。

我知道我可以通过转换为大写然后再转换回小写来捕获大小写 (2),这将在此特定应用程序的范围内工作。不过,我有兴趣从更基础的层面解决这个问题,希望也能支持区分大小写的应用程序。

任何提示将不胜感激!

【问题讨论】:

    标签: unicode transform core-foundation foundation


    【解决方案1】:

    恭喜,您发现了文本处理中最痛苦的部分之一!

    首先,NamesList.txtCaseFolding.txt 是此类事情不可或缺的资源,如果您还没有看过的话。

    部分问题是您正在尝试做的事情几乎正确适用于您关心的所有语言/区域设置,而 Unicode 更关心在显示字符串时做正确的事情单一语言区域设置。

    对于 (2),ß 从我能找到的最早的 CaseFolding.txt (3.0-Update1/CaseFolding-2.txt) 开始规范地大小写折叠为 ssCFStringFold()-[NSString stringByFoldingWithOptions:] 应该做正确的事情,但如果不是,“独立于语言环境的”s.upper().lower() 似乎为所有输入提供了一个明智的答案(并且还处理了臭名昭著的“土耳其语 I”)。

    对于 (5),您有点不走运:Unicode 6.2 似乎不包含从 Æ 到 AE 的规范映射,并且已从“字母”更改为“连字”并再次返回 (U+00C6在 1.0 中是 LATIN CAPITAL LETTER A E,在 1.1 中是 LATIN CAPITAL LIGATURE AE,在 2.0 中是 LATIN CAPITAL LETTER AE)。您可以在 NamesList.txt 中搜索“连字”并添加一些特殊情况。

    注意事项:

    • CFStringNormalize() 不会做你想做的事。您确实想在将字符串添加到索引之前对其进行规范化;我建议在其他处理的开始和结束时使用 NFKC。
    • CFStringTransform() 也不完全符合您的要求;所有的脚本都是“拉丁”
    • CFStringFold() 依赖于顺序:ypogegrammeni and prosgegrammeni 的组合被 kCFCompareDiacriticInsensitive 剥离,但被 kCFCompareCaseInsensitive 转换为小写 iota。 “正确”的做法似乎是先进行大小写折叠,然后再进行其他操作,尽管在语言上剥离它可能更有意义。
    • 您几乎肯定不想使用kCFCompareLocalized,除非您想在每次区域设置更改时重建搜索索引。

    其他语言的读者注意:检查您使用的函数是否依赖于用户当前的语言环境! Java 用户应该使用s.toUpperCase(Locale.ENGLISH) 之类的东西,.NET 用户应该使用s.ToUpperInvariant()。如果您确实想要用户的当前语言环境,请明确指定它。

    【讨论】:

    • +1 太棒了!我已经得出结论,我永远不会得到这个问题的答案。我不再处理这个问题了,所以我需要一些时间才能完全理解这个问题——我想我周末有一些阅读要做!
    【解决方案2】:

    我在 String 上使用了以下扩展,看起来效果很好。

    /// normalized version of string for comparisons and database lookups.  If normalization fails or results in an empty string, original string is returned.
    var normalized: String? {
        // expand ligatures and other joined characters and flatten to simple ascii (æ => ae, etc.) by converting to ascii data and back
        guard let data = self.data(using: String.Encoding.ascii, allowLossyConversion: true) else {
            print("WARNING: Unable to convert string to ASCII Data: \(self)")
            return self
        }
        guard let processed = String(data: data, encoding: String.Encoding.ascii) else {
            print("WARNING: Unable to decode ASCII Data normalizing stirng: \(self)")
            return self
        }
        var normalized = processed
    
        //  // remove non alpha-numeric characters
        normalized = normalized.replacingOccurrences(of: "?", with: "") // educated quotes and the like will be destroyed by above data conversion
        // strip appostrophes
        normalized = normalized.replacingCharacters(in: "'", with: "")
        // replace non-alpha-numeric characters with spaces
        normalized = normalized.replacingCharacters(in: CharacterSet.alphanumerics.inverted, with: " ")
        // lowercase string
        normalized = normalized.lowercased()
    
        // remove multiple spaces and line breaks and tabs and trim
        normalized = normalized.whitespaceCollapsed
    
        // may return an empty string if no alphanumeric characters!  In this case, use the raw string as the "normalized" form
        if normalized == "" {
            return self
        } else {
            return normalized
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-20
      • 2013-09-04
      • 2013-12-19
      • 1970-01-01
      • 1970-01-01
      • 2018-01-09
      • 1970-01-01
      相关资源
      最近更新 更多