Почему Đ не сглаживается до D при удалении акцентов/диакритических знаков

Я использую этот метод для удаления акцентов из моих строк:

static string RemoveAccents(string input)
{
    string normalized = input.Normalize(NormalizationForm.FormKD);
    StringBuilder builder = new StringBuilder();
    foreach (char c in normalized)
    {
        if (char.GetUnicodeCategory(c) !=
        UnicodeCategory.NonSpacingMark)
        {
            builder.Append(c);
        }
    }
    return builder.ToString();
}

но этот метод оставляет đ как đ и не меняет его на d, хотя d является его базовым символом. вы можете попробовать это с помощью этой входной строки "æøåáâăäĺćçčéęëěíîďđńňóôőöřůúűüýţ"

Что такого особенного в букве đ?


person Mladen Prajdic    schedule 02.03.2010    source источник
comment
Не могли бы вы показать, как переписать строковый литерал в виде \uxxxx и т. д.? Это облегчит воспроизведение, не беспокоясь о комбинировании символов и т. д.   -  person Jon Skeet    schedule 02.03.2010
comment
Это турецкий (или другой восточноевропейский персонаж)?   -  person leppie    schedule 02.03.2010
comment
@jon, так как именно мне это сделать?   -  person Mladen Prajdic    schedule 02.03.2010
comment
Мне кажется, что это буква Eth, которая сама по себе является буквой.   -  person TRiG    schedule 12.09.2010


Ответы (5)


Ответ на вопрос, почему это не работает, состоит в том, что утверждение, что "d является его базовым символом", неверно. U + 0111 (СТРОЧНАЯ ЛАТИНСКАЯ БУКВА D СО ШТРИХОМ) имеет категорию Unicode «Буква, нижний регистр» и не имеет отображения декомпозиции (т. Е. Он не разлагается на «d», за которым следует объединяющий знак).

"đ".Normalize(NormalizationForm.FormD) просто возвращает "đ", который не удаляется циклом, потому что это не знак пробела.

Аналогичная проблема будет существовать для «ø» и других букв, для которых Unicode не обеспечивает сопоставление декомпозиции. (И если вы пытаетесь найти «лучший» символ ASCII для представления буквы Unicode, этот подход вообще не будет работать для кириллицы, греческого, китайского или других нелатинских алфавитов; вы также столкнетесь с проблемами, если например, вы хотели транслитерировать «ß» в «ss». Может помочь использование такой библиотеки, как UnidecodeSharp.)

person Bradley Grainger    schedule 28.11.2010

Я должен признать, что я не уверен, почему это работает, но, похоже,

var str = "æøåáâăäĺćçčéęëěíîďđńňóôőöřůúűüýţ";
var noApostrophes = Encoding.ASCII.GetString(Encoding.GetEncoding("Cyrillic").GetBytes(str)); 

=> "ааааааалккцеееииидднноооорууууйт"

person Jonas Elfström    schedule 02.03.2010
comment
Кодировка кириллицы, по-видимому, имеет небольшую таблицу резервных символов, которые она будет использовать, когда вводимый символ не появляется в кодовой странице 1251; это похоже на злоупотребление этим недокументированным поведением. Он также преобразует ß (и любой другой нераспознанный символ) в ?, что может быть неуместным (как и преобразование «æ» в «a»). Для (почти полной) транслитерации Unicode см. unidecode.codeplex.com. - person Bradley Grainger; 29.11.2010
comment
Да, это конечно взлом. Чем Unidecode отличается от Iconv //TRANSLIT? - person Jonas Elfström; 29.11.2010
comment
Я не тестировал Unidecode против iconv //TRANSLIT; Я предложил Unidecode в этом случае, потому что это библиотека .NET (поэтому для ее использования не требуется P/Invoke). - person Bradley Grainger; 02.12.2010

«D с обводкой" (Википедия) используется на нескольких языках и, по-видимому, считаться отдельной буквой во всех них — и поэтому она остается неизменной.

person Martin B    schedule 02.03.2010
comment
Кроме того, eth в староанглийском превратился в th в английском, а в норвежском — в d. Помимо внешнего сходства с заглавной д, это совершенно другое. - person Frank Shearar; 02.03.2010
comment
да, но то же самое относится к č или ć, которые также являются отдельной буквой. - person Mladen Prajdic; 02.03.2010
comment
В частности, Unicode не определяет отображения декомпозиции для đ (в то время как оно определяет для č и å, которые некоторые другие алфавиты считают разными буквами). - person Bradley Grainger; 29.11.2010

string.Normalize(NormalizationForm) — это простой способ удалить «настоящие» диакритические знаки (Wiki), но многие буквы вам могут понадобиться для преобразования не затрагиваются этим.

У меня были похожие проблемы с Ð & ð (буква Eth), đ, Æ и æ. Чтобы преобразовать их в ANSI (латиницу), используйте Unicode-преобразование!

    private static char[] ConvertUnicodeStringToSpecificEncoding(string input, int resultEncodingCode)
    {
        System.Text.Encoding unicodeEncoding = System.Text.Encoding.Unicode;
        System.Text.Encoding specificEncoding = System.Text.Encoding.GetEncoding(resultEncodingCode);

        byte[] convertedBytes = System.Text.Encoding.Convert(unicodeEncoding, specificEncoding, unicodeEncoding.GetBytes(input));
        char[] convertedChars = new char[specificEncoding.GetCharCount(convertedBytes, 0, convertedBytes.Length)];
        specificEncoding.GetChars(convertedBytes, 0, convertedBytes.Length, convertedChars, 0);
        return convertedChars;
    }

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

Список кодировок: https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=netframework-4.8

Мое решение выглядит так

    // Encoding Types (int Codes) https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=netframework-4.8
    private static readonly char[] charactersToSkip = new char[] { 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü' };
    private static readonly char[] specialCharsToSkip = new char[] { '^', '´', '`', '°', '!', '\'', '§', '$', '%', '&', '/', '(', ')', '=', '{', '[', ']', '}', '\\', '+', '-' };
    private static readonly char[] ambiguousCharsToSkip = new char[] { '?' };   // Chars which might be a result of encoding-conversion and have to be skipped beforehand.
    private static readonly int[] encodingsToRemoveDiacritics = new int[]
    {
        852,    // 852  ibm852  Central European (DOS)
        850,    // 850  ibm850  Western European (DOS)
        860,    // 860  IBM860  Portuguese (DOS)    

        /* Warning:
         * Only append encodings.
         * Changing sort order of encodings may result in malfunctioning.
         */ 
    };

    public static string RemoveDiacritics(this string inputString)
    {
        if (string.IsNullOrEmpty(inputString))
        {
            return inputString;
        }

        var resultStringBuilder = new StringBuilder();

        foreach (char currentChar in inputString)
        {
            if (charactersToSkip.Contains(currentChar) || specialCharsToSkip.Contains(currentChar) || ambiguousCharsToSkip.Contains(currentChar))
            {
                resultStringBuilder.Append(currentChar);
                continue;
            }

            string normalizedString = currentChar.ToString().Normalize(NormalizationForm.FormD);
            foreach (char normalizedChar in normalizedString)
            {
                if (System.Globalization.CharUnicodeInfo.GetUnicodeCategory(normalizedChar) != System.Globalization.UnicodeCategory.NonSpacingMark)
                {
                    string convertedString = normalizedChar.ToString();
                    char[] convertedChars = null;

                    foreach (int encodingCode in encodingsToRemoveDiacritics)
                    {
                        convertedChars = ConvertUnicodeStringToSpecificEncoding(convertedString, encodingCode);

                        if (convertedChars.Contains('?') == false)
                        {
                            convertedString = new string(convertedChars);
                        }
                    }

                    resultStringBuilder.Append(convertedString);
                }
            }
        }

        return resultStringBuilder.ToString();
    }

который создает следующие результаты

"abcdefghijklmnopqrstuvwxzy" -> "abcdefghijklmnopqrstuvwxzy"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" -> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890" -> "1234567890"
"ß" -> "ß"
"ÄÖÜ" -> "ÄÖÜ"
"äöü" -> "äöü"
"!\"§$%&/()=?" -> "!\"§$%&/()=?"
"+-_~'*#" -> "+-_~'*#"
",.;:" -> ",.;:"
"µ" -> "u" // My -> u
"<>|" -> "<>|"
"´`^°" -> "´`^°"
"²" -> "2" // ² -> 2
"³" -> "3" // ³ -> 3
"{}" -> "{}"
"[]" -> "[]"
"\\" -> "\\"
"áàâã" -> "aaaa"
"ÁÀÂÅ" -> "AAAA"
"éèêę" -> "eeee"
"ÉÈÊĚ" -> "EEEE"
"íìîï" -> "iiii"
"ÍÌÎ" -> "III"
"óòôõ" -> "oooo"
"ÓÒÔŌ" -> "OOOO"
"úùû" -> "uuu"
"ÚÙÛ" -> "UUU"
"ÇĆĈČĊ" -> "CCCCC"
"çćĉčċ" -> "ccccc"
"Ñ" -> "N"
"Æ" -> "A"
"æ" -> "a"
"ýÿ" -> "yy"
"ĹĻĽ" -> "LLL"
"Ð" -> "D"
"đ" -> "d"
"ð" -> "d"
person Phil    schedule 31.08.2020

это должно работать

    private static String RemoveDiacritics(string text)
    {
        String normalized = text.Normalize(NormalizationForm.FormD);
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < normalized.Length; i++)
        {
            Char c = normalized[i];
            if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
                sb.Append(c);
        }

        return sb.ToString();
    }
person mare    schedule 28.11.2010
comment
Это выглядит так же, как исходный код постера с заменой FormKD на FormD (с небольшими стилистическими изменениями). Это не сработает по причинам, указанным в других ответах. - person Bradley Grainger; 02.12.2010
comment
Я использовал FormD все время до сих пор и не знал об этой проблеме, однако, как я вижу (я только что проверил), вы правы. Это не работает. - person mare; 02.12.2010