月別アーカイブ: 2012年10月

[C#] nkf を用いた文字コードの判別

Monday, October 15, 2012

オープンソースである nkf を借用して,テキストファイルの文字コードを簡単に判別できます。バージョン 2.0.9 以降の nkf のライセンスは zlib/libpng License です (極めて良心的なライセンスです)。対応文字コードは次の通りです。

  • Shift_JIS
  • EUC-JP
  • ISO-2022-JP
  • UTF-8
  • UTF-16LE
  • UTF-16BE

nkf32.dll を用意し,実行ファイルと同じディレクトリに置く必要があります。nkf32.dll は Vector からダウンロードできます。元々の nkf とは異なるライセンスですので,配布条件を確認してください (とは言っても緩い条件です)。

nkf.exe nkf32.dll Windows 用
http://www.vector.co.jp/soft/win95/util/se295331.html

文字コードの判別を行うには,私が作った EncodingUtilities クラスを利用してください。まずは使い方の見本から。

using System;
using System.Text;

class Program
{
    static void Main()
    {
        string path = @"c:\works\hoge.txt";

        // 文字コードを判別
        Encoding enc = EncodingUtilities.DetectEncoding(path);

        // テキストエディタを作るならこうしとくといい
        if (enc == null || enc == Encoding.ASCII)
        {
            enc = Encoding.Default;
        }

        // 処理...
    }
}

次のリンクからダウンロードできます。断りなく自由に使っていただいて結構です。

ソースコード: EncodingUtilities.cs

// EncodingUtilities.cs

using System;
using System.IO;
using System.Text;

/// <summary>
/// エンコーディングを取り扱うユーティリティを提供します。
/// </summary>
public static class EncodingUtilities
{
    /// <summary>
    /// テキストファイルで使われている文字コードを判別し,対応する Encoding オブジェクトを返します。
    /// </summary>
    /// <param name="path">テキストファイルへのパス。</param>
    /// <returns>Shift_JIS, EUC-JP, ISO-2022-JP, UTF-8, UTF-16, UTF-16BE, US-ASCII, null のいずれかが返ります。</returns>
    public static Encoding DetectEncoding(string path)
    {
        // -g: 自動判別の結果を出力する。
        // -t: 何もしない。
        SetNkfOption("-gt");

        byte[] bytes;
        
        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            bytes = new byte[fs.Length];
            fs.Read(bytes, 0, bytes.Length);
        }

        unsafe
        {
            fixed (byte* pbs = bytes)
            {
                StringBuilder strBldr = new StringBuilder(1);
                NkfConvert(strBldr, (char*) pbs);
            }
        }

        int nEnc = NkfGetKanjiCode();

        // US-ASCII も ISO-2022-JP と判別されるので,更に篩にかける。
        if (nEnc == nJIS)
        {
            nEnc = nASCII;  // US-ASCII と仮定する
            for (int i = 0; i < bytes.Length; i++)
            {
                if (bytes[i] == 0x1b) // ISO-2022-JP であれば ESC (0x1B) が含まれる
                {
                    nEnc = nJIS;
                    break;
                }
            }
        }

        switch (nEnc)
        {
            case nSJIS:
                return Encoding.GetEncoding("shift_jis");
            case nEUC:
                return Encoding.GetEncoding("euc-jp");
            case nJIS:
                return Encoding.GetEncoding("iso-2022-jp");
            case nUTF8:
                return Encoding.GetEncoding("utf-8");
            case nUTF16LE:
                return Encoding.GetEncoding("utf-16");
            case nUTF16BE:
                return Encoding.GetEncoding("utf-16BE");
            case nASCII:
                return Encoding.ASCII;
            default:
                return null;
        }
    }

    #region nkf functions

    [System.Runtime.InteropServices.DllImport("nkf32.dll")]
    static extern int SetNkfOption(string optStr);

    [System.Runtime.InteropServices.DllImport("nkf32.dll")]
    unsafe static extern void NkfConvert(StringBuilder outStr, char* inStr);

    [System.Runtime.InteropServices.DllImport("nkf32.dll")]
    static extern int NkfGetKanjiCode();

    #endregion

    #region Fields

    // nSJIS, nEUC, nJIS, nUTF8, nUTF16LE, nUTF16BE の値は NkfGetKanjiCode() の戻り値に対応
    const int nSJIS = 0;
    const int nEUC = 1;
    const int nJIS = 2;
    const int nUTF8 = 3;
    const int nUTF16LE = 4;
    const int nUTF16BE = 5;

    const int nASCII = 1001;

    #endregion
}

「null 以外を返した」という結果は「文字コードを完全に特定できた」という意味を持つ訳ではありません。その点だけ注意してください。これは nkf の仕様に因ります。

[C#] char は全ての Unicode 文字を表せる訳ではない

Monday, October 8, 2012

System.Char のドキュメント (*1) を参照して「Unicode 文字を表します。」と書いてあるのを鵜呑みにし,第 4 水準の「𢌞」(廴+囘,U+2231E) という字を char に入れようとして失敗した。

public class Program
{
    public static void Main()
    {
        string s = char.ConvertFromUtf32(0x2231e);
        char c = s[0];

        MessageBox.Show(s.ToString(), "s"); // 表示される
        MessageBox.Show(c.ToString(), "c"); // 文字化けする
    }
}

U_2231E_MsgBox.png

Microsoft の言う「Unicode」とは Unicode ではなく UTF-16LE のことである。ConvertFromUtf32() の返り値が string 型なのを怪しく思いながら,なかなかその理由に気付けなかったことが敗因だった。


(*1) 古いバージョンを見たのがいけなかった。その後「文字をUTF-16 コード単位で表します。」と訂正されている。

[C#] SubItems[0] がなぜか埋まってる

Thursday, October 4, 2012

2012100401.png

ListViewItemSubItemsAdd でサブ項目を追加しようとすると,なぜか SubItems[0] に入れられない。

// 失敗例

ListViewItem item = new ListViewItem();
item.SubItems.Add("hoge");
item.SubItems.Add("piyo");

Console.WriteLine(item.SubItems[0].Text);  // 
Console.WriteLine(item.SubItems[1].Text);  // hoge

最初は Visual C# のバグかと思ったが,MSDN を読んだらそれらしいことが書いてあった。解決法もわかった。サブ項目をコンストラクタに渡せばいい。

// 成功例

ListViewItem item = new ListViewItem(new[] { "hoge", "piyo" });

Console.WriteLine(item.SubItems[0].Text);  // hoge
Console.WriteLine(item.SubItems[1].Text);  // piyo

ListView.Columns は普通に 0 から Add できるのに,ListViewItem.SubItems はできない。結果ずれる。こういう仕様ってどうなんだろ。