我写了一个小型控制台应用程序(下面是源代码),用于查找并可选地重命名包含国际字符的文件,因为它们是大多数源代码控制系统的不断痛点(有关此问题的背景请参见下文)。我使用的代码具有一个简单的字典,其中包含要查找和替换的字符(并消除每个使用多个存储字节的其他字符),但感觉非常hackish。 (a)找出字符是否为国际字符的正确方法是什么?(b)最佳ASCII替换字符是什么?
让我提供一些背景信息,解释为什么需要这样做。恰好,丹麦的Å字符在UTF-8中有两种不同的编码,都代表相同的符号。这些被称为NFC和NFD编码。 Windows和Linux默认创建NFC编码,但尊重任何给定的编码。 Mac将所有名称(保存到HFS +分区时)转换为NFD,因此为Windows创建的文件的名称返回不同的字节流。这实际上破坏了Subversion,Git和许多其他未能妥善处理此方案的实用程序。
我目前正在评估Mercurial,结果发现它处理国际字符的能力甚至更差...由于这些问题而感到相当疲倦,无论是源代码控制还是国际字符都必须消失,因此我们在这里。
我的当前实现:
让我提供一些背景信息,解释为什么需要这样做。恰好,丹麦的Å字符在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
}