3

我编写了一个小型控制台应用程序(下面的源代码)来定位和可选地重命名包含国际字符的文件,因为它们是大多数源代码控制系统不断痛苦的根源(下面有一些背景知识)。我正在使用的代码有一个简单的字典,其中包含要查找和替换的字符(并对使用超过一个字节存储空间的所有其他字符进行核对),但感觉非常hackish。(a) 找出一个角色是否国际化的正确方法是什么?(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;
    }
}

这将像这样被调用:

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

在这个时代遇到的可悲问题。很明显,MAC 使用的 NFD 格式让您头疼。您可以考虑的一件事是从字形中删除导致 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 回答
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 回答