使用C#检测文件名字符是否被视为国际字符

我已经编写了一个小型控制台应用程序(下面的源代码)来定位和选择性地重命名包含国际字符的文件,因为它们是大多数源代码控制系统不断痛苦的根源(下面有一些背景知识)。 我正在使用的代码有一个简单的字典,其中包含要查找和替换的字符(并且使用多个字节的存储来核对每个其他字符),但它感觉非常hackish。 什么是正确的方法(a)找出角色是否是国际性的? (b)最佳ASCII替换字符是什么?

让我提供一些背景信息,说明为什么需要这样做。 碰巧的是,丹麦Å字符在UTF-8中有两种不同的编码,两者都代表相同的符号。 这些被称为NFC和NFD编码。 Windows和Linux默认会创建NFC编码,但要遵守它给出的任何编码。 Mac会将所有名称(保存到HFS +分区时)转换为NFD,因此会为在Windows上创建的文件名返回不同的字节流。 这有效地打破了Subversion,Git和许多其他不关心正确处理这种情况的实用程序。

我目前正在评估Mercurial,后者在处理国际角色方面更加糟糕。对这些问题相当厌倦,无论是源代码控制还是国际角色都必须去,所以我们在这里。

我目前的实施:

public class Checker { private Dictionary internationals = new Dictionary(); private List keep = new List(); private List seen = new List(); 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 } 

在这个时代有悲伤的问题。 很明显,MAC使用的NFDforms让你头疼。 您可以考虑的一件事是从字形中去除变音符号,导致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的代码点。

(b)尝试NKFD标准化和/或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,因为KDforms将Å分开,并且转换为7位ASCII失去了变音符号。 怎么处理这个小小的?剩下的取决于你。

你的里程可能因其他角色而异,但我猜KD规范化应该可以解决问题。 我多年来一直没有进行代码页转换,但我发现这个问题很有趣。

编辑:

我只是试过æÆØ而且他们都转换成了?,所以这对你来说可能太有损了。 不过,它可能会给你一些线索,导致答案。