Сворачивание / нормализация лигатур (например, Æ до ae) с использованием (Core) Foundation - PullRequest
6 голосов
/ 21 февраля 2012

Я пишу помощника, который выполняет ряд преобразований для входной строки, чтобы создать удобное для поиска представление этой строки.

Подумайте о следующем сценарии:

  • Полнотекстовый поиск по немецкому или французскому тексту
  • Записи в вашем хранилище данных содержат
    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 для преобразований , но не вошелслишком много в нем ... по тем причинам, которые я считаю очевидными.

Я знаю, что я мог бы поймать case (2), преобразовав его в прописные, а затем обратно в строчные, что сработало бы в рамках этого конкретного приложения.,Тем не менее, я заинтересован в решении этой проблемы на более фундаментальном уровне, и, надеюсь, учтю также регистрозависимые приложения.

Буду признателен за любые подсказки!

Ответы [ 2 ]

6 голосов
/ 19 марта 2013

Поздравляем, вы нашли один из самых болезненных моментов обработки текста!

Прежде всего, NamesList.txt и CaseFolding.txt являются необходимыми ресурсами для подобных вещей, если вы их еще не видели.

Часть проблемы заключается в том, что вы пытаетесь сделать что-то почти правильное , которое работает на всех языках / локалях, которые вас интересуют, тогда как Unicode больше заботится о том, чтобы делать правильные вещи при отображении строк в один язык-локаль.

Для (2) ß канонически сложено в регистр ss с самого раннего файла CaseFolding.txt, который я могу найти ( 3.0-Update1 / CaseFolding-2.txt ). CFStringFold() и -[NSString stringByFoldingWithOptions:] должны делать правильные вещи, но если нет, то «1019 * независимый от локали», по-видимому, дает разумный ответ на все входные данные (а также обрабатывает печально известное «турецкое I»).

Для (5) вам немного не повезло: Unicode 6.2, по-видимому, не содержит нормативного сопоставления от Æ до AE и изменил с «буквы» на «лигатуры» и обратно (U + 00C6 составляет LATIN CAPITAL LETTER A E в 1,0, LATIN CAPITAL LIGATURE AE в 1,1 и LATIN CAPITAL LETTER AE в 2,0). Вы можете найти в NamesList.txt слово "ligature" и добавить несколько особых случаев.

Примечания:

  • CFStringNormalize() не делает то, что вы хотите. Вы do хотите нормализовать строки перед добавлением их в индекс; Я предлагаю NFKC в начале и в конце другой обработки.
  • CFStringTransform() тоже не совсем то, что вы хотите; все скрипты "латинские"
  • CFStringFold() зависит от порядка: объединение ypogegrammeni и prosgegrammeni разбивается на kCFCompareDiacriticInsensitive, но преобразуется в строчную йоту на kCFCompareCaseInsensitive. Похоже, «правильной» вещью является то, что сначала следует согнуть регистр, а затем другие, хотя его лингвистическое обоснование может иметь больший смысл с лингвистической точки зрения.
  • Вы почти наверняка не захотите использовать kCFCompareLocalized, если не хотите перестраивать поисковый индекс каждый раз, когда меняется языковой стандарт.

Читатели с других языков Примечание: Проверьте, что используемая вами функция не зависит от текущей локали пользователя! Пользователи Java должны использовать что-то вроде s.toUpperCase(Locale.ENGLISH), пользователи .NET должны использовать s.ToUpperInvariant(). Если вам действительно нужен текущий языковой стандарт пользователя, укажите его явно.

0 голосов
/ 18 сентября 2016

Я использовал следующее расширение для 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
    }
}
...