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

使用C#检测文件名字符是否被视为国际字符,c#,unicode,ascii,character,substitution,C#,Unicode,Ascii,Character,Substitution,我已经编写了一个小的控制台应用程序(下面的源代码)来定位并有选择地重命名包含国际字符的文件,因为它们是大多数源代码控制系统(下面的一些背景代码)经常遇到的问题。我正在使用的代码有一个简单的字典,其中包含要查找和替换的字符(并且会对使用超过一个字节存储的所有其他字符进行核处理),但它感觉非常粗糙。找出一个角色是否是国际角色的正确方法是什么?(b)最好的ASCII替换字符是什么 让我提供一些背景资料,说明为什么需要这样做。碰巧丹麦Å字符在UTF-8中有两种不同的编码,都代表相同的符号。这些被称为NF

我已经编写了一个小的控制台应用程序(下面的源代码)来定位并有选择地重命名包含国际字符的文件,因为它们是大多数源代码控制系统(下面的一些背景代码)经常遇到的问题。我正在使用的代码有一个简单的字典,其中包含要查找和替换的字符(并且会对使用超过一个字节存储的所有其他字符进行核处理),但它感觉非常粗糙。找出一个角色是否是国际角色的正确方法是什么?(b)最好的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;
    }
}
(a) 简单。检查是否存在大于127的任何代码点


(b) 尝试NKFD规范化和/或。

如果您不介意暴力,您可以尝试以下方法:

FileInfo file = new File( "Århus.txt" );
string safeName;    
if( checker.HasInternationalCharactersInName( file.Name, out safeName ) )
{
    // rename file 
}
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转换为?rhus.txt,因为KD形式将Å分开,而转换为7位ASCII将丢失变音符号。剩下的那个小家伙该怎么办由你决定

你的里程数可能会因其他角色而异,但我想KD规范化应该可以做到这一点。我已经多年没有从事代码页转换工作了,但我发现这个问题很有趣

编辑:


我刚试过,它们都转换成了?,所以这对你来说可能太浪费了。不过,它可能会给你一些线索,让你找到答案。

在这个时代,有一个令人悲伤的问题。很明显,MAC使用的NFD格式让你头疼。有一件事你可以考虑的是从符号中去掉那些导致NFD与NFC不同的符号。< /P> 我不是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();
}
publicstaticstringremovediacriticals(stringtxt){
字符串nfd=txt.Normalize(NormalizationForm.FormD);
StringBuilder retval=新StringBuilder(nfd.Length);
foreach(nfd中的字符){

如果(ch>='\u0300'&&ch='\u1dc0'&&ch='\ufe20'&&ch='\u20d0'&&ch),请注意从“”到“”的映射实际上在单引号之间包含一个字符。我真的很惊讶,我可以从控制台窗口将它们复制到Visual Studio中,然后通过Chrome将它们复制到StackOverflow,并且仍然让它们看起来完全正确。但是,一旦我们谈论的是文件名而不是内容,那么我们就回到了20世纪80年代。哪个字节是代码点?我可以研究一下,但如果你知道我希望得到一个提示的话。uni2ascii实用程序似乎不适用于Windows,尽管提供了C源代码,所以我可以看看。我更愿意不必通过自己实现规范化来发明轮子-没有C库或Windows API吗?Unicode Code点是一个21位的数字。它可以用UTF-8、1-2 UTF-16或1 UTF-32编码单元编码为1-4字节。所有这三种编码单元都使用0-127范围内的单代码单元来表示ASCII字符。Windows API有一个名为NormalizeString的函数。这看起来就像我正在寻找的。我想我可能会采用一种方法,将使用不同的标准字符串并比较结果。这与dan04的答案相结合,应该可以解决难题的第一部分。我仍然需要找出最好的ASCII替换字符是什么,最好是使用不需要表格或字典的代码解决方案。一旦我有了一些更新,我将发布一个新问题编辑代码以显示。谢谢,我将尝试使用此方法进行实验。