Использование C# для определения того, считается ли символ имени файла международным

StackOverflow https://stackoverflow.com/questions/2482134

Вопрос

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

Позвольте мне предоставить некоторую справочную информацию о том, почему это необходимо.Так получилось, что датский символ Å имеет две разные кодировки в UTF-8, обе представляют один и тот же символ.Они известны как кодировки NFC и NFD.Windows и Linux по умолчанию создают кодировку NFC, но учитывают любую заданную кодировку.Mac преобразует все имена (при сохранении в раздел HFS+) в NFD и, следовательно, возвращает другой поток байтов для имени файла, созданного в Windows.Это эффективно нарушает работу Subversion, Git и множества других утилит, которые не хотят должным образом обрабатывать этот сценарий.

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

Моя текущая реализация:

public class Checker
{
    private Dictionary<char, string> internationals = new Dictionary<char, string>();
    private List<char> keep = new List<char>();
    private List<char> seen = new List<char>();

    public Checker()
    {
        internationals.Add( 'æ', "ae" );
        internationals.Add( 'ø', "oe" );
        internationals.Add( 'å', "aa" );
        internationals.Add( 'Æ', "Ae" );
        internationals.Add( 'Ø', "Oe" );
        internationals.Add( 'Å', "Aa" );

        internationals.Add( 'ö', "o" );
        internationals.Add( 'ü', "u" );
        internationals.Add( 'ä', "a" );
        internationals.Add( 'é', "e" );
        internationals.Add( 'è', "e" );
        internationals.Add( 'ê', "e" );

        internationals.Add( '¦', "" );
        internationals.Add( 'Ã', "" );
        internationals.Add( '©', "" );
        internationals.Add( ' ', "" );
        internationals.Add( '§', "" );
        internationals.Add( '¡', "" );
        internationals.Add( '³', "" );
        internationals.Add( '­', "" );
        internationals.Add( 'º', "" );

        internationals.Add( '«', "-" );
        internationals.Add( '»', "-" );
        internationals.Add( '´', "'" );
        internationals.Add( '`', "'" );
        internationals.Add( '"', "'" );
        internationals.Add( Encoding.UTF8.GetString( new byte[] { 226, 128, 147 } )[ 0 ], "-" );
        internationals.Add( Encoding.UTF8.GetString( new byte[] { 226, 128, 148 } )[ 0 ], "-" );
        internationals.Add( Encoding.UTF8.GetString( new byte[] { 226, 128, 153 } )[ 0 ], "'" );
        internationals.Add( Encoding.UTF8.GetString( new byte[] { 226, 128, 166 } )[ 0 ], "." );

        keep.Add( '-' );
        keep.Add( '=' );
        keep.Add( '\'' );
        keep.Add( '.' );
    }

    public bool IsInternationalCharacter( char c )
    {
        var s = c.ToString();
        byte[] bytes = Encoding.UTF8.GetBytes( s );
        if( bytes.Length > 1 && ! internationals.ContainsKey( c ) && ! seen.Contains( c ) )
        {
            Console.WriteLine( "X '{0}' ({1})", c, string.Join( ",", bytes ) );
            seen.Add( c );
            if( ! keep.Contains( c ) )
            {
                internationals[ c ] = "";
            }
        }
        return internationals.ContainsKey( c );
    }

    public bool HasInternationalCharactersInName( string name, out string safeName )
    {
        StringBuilder sb = new StringBuilder();
        Array.ForEach( name.ToCharArray(), c => sb.Append( IsInternationalCharacter( c ) ? internationals[ c ] : c.ToString() ) );
        int length = sb.Length;
        sb.Replace( "  ", " " );
        while( sb.Length != length )
        {
            sb.Replace( "  ", " " );
        }
        safeName = sb.ToString().Trim();
        string namePart = Path.GetFileNameWithoutExtension( safeName );
        if( namePart.EndsWith( "." ) )
            safeName = namePart.Substring( 0, namePart.Length - 1 ) + Path.GetExtension( safeName );
        return name != safeName;
    }
}

И это будет вызываться следующим образом:

FileInfo file = new File( "Århus.txt" );
string safeName;    
if( checker.HasInternationalCharactersInName( file.Name, out safeName ) )
{
    // rename file 
}
Это было полезно?

Решение

Печальная проблема в наше время.Очевидно, что форма NFD, которую использует MAC, вызывает у вас эту головную боль.Вы могли бы рассмотреть возможность удаления диакритических знаков из глифов, из-за которых NFD отличается от NFC.

Я не уверен на 100%, что это абсолютно точно (особенно для азиатских сценариев), но должно быть близко:

public static string RemoveDiacriticals(string txt) {
  string nfd = txt.Normalize(NormalizationForm.FormD);
  StringBuilder retval = new StringBuilder(nfd.Length);
  foreach (char ch in nfd) {
    if (ch >= '\u0300' && ch <= '\u036f') continue;
    if (ch >= '\u1dc0' && ch <= '\u1de6') continue;
    if (ch >= '\ufe20' && ch <= '\ufe26') continue;
    if (ch >= '\u20d0' && ch <= '\u20f0') continue;
    retval.Append(ch);
  }
  return retval.ToString();
}

Другие советы

(просто.Проверьте наличие кодовых точек, превышающих 127.

(б) Попытаться нормализовать НКФД и/или uni2ascii.

Если вы не против грубой силы, вы можете попробовать что-то вроде этого:

string name = "Århus.txt";
string kd = name.Normalize(NormalizationForm.FormKD);
byte[] kd_bytes = Encoding.Unicode.GetBytes(kd);
byte[] ascii_bytes = Encoding.Convert(Encoding.Unicode, Encoding.ASCII, kd_bytes);
string flattened = Encoding.ASCII.GetString(ascii_bytes);

Это приведет к преобразованию Århus.txt в A?rhus.txt, поскольку форма KD разбивает Å на части, а при преобразовании в 7-битный ASCII диакритический знак теряется.Что делать с остатками? - решать вам.

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

РЕДАКТИРОВАТЬ:

Я только что попробовал æÆØ, и все они конвертировались в ?, так что для вас это может быть слишком потеряно.Тем не менее, это может дать вам некоторые подсказки, которые приведут к ответу.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top