3

私は小さなコンソールアプリケーション(以下のソース)を作成して、国際文字を含むファイルを検索し、オプションで名前を変更しました。これは、ほとんどのソース管理システム(以下の背景)で絶え間ない苦痛の原因となるためです。私が使用しているコードには、検索して置換する文字を含む単純な辞書があります(そして、1バイトを超えるストレージを使用する他のすべての文字を削除します)が、非常にハックな感じがします。(a)キャラクターが国際的であるかどうかを調べる正しい方法は何ですか?(b)最高のASCII置換文字は何でしょうか?

これが必要な理由について、いくつかの背景情報を提供しましょう。デンマーク語のÅ文字には、UTF-8で2つの異なるエンコーディングがあり、どちらも同じシンボルを表しています。これらは、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 
}
4

3 に答える 3

2

(シンプルな。127より大きいコードポイントがないか確認します。

(b)NKFD正規化および/またはuni2asciiを試してください。

于 2010-03-20T06:24:14.407 に答える
1

ブルートフォースを気にしない場合は、次のようなことを試すことができます。

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の正規化でうまくいくと思います。私はここ数年コードページの変換に取り組んでいませんが、質問に興味をそそられました。

編集:

æÆØを試したところ、すべて?に変換されたため、これは損失が大きすぎる可能性があります。それでも、それはあなたに答えにつながるいくつかの手がかりを与えるかもしれません。

于 2010-03-20T07:43:20.333 に答える
1

この時代に抱える悲しい問題。明らかに、MACが使用するNFDフォームがこの頭痛の種を引き起こしています。検討できることの1つは、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();
}
于 2010-03-20T11:48:23.213 に答える