連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

iniファイル読み込みと文字コード

公開日 2017/08/19
最終更新 2017/08/29
  • 旧来のWindowsが使っていた ini形式のファイルを C# で読み込みたい。
  • 今さらiniファイルをアプリのデータ保存に使う気はないので、既存のファイルを読み込めればいい。

Win32での方法は見つかるのだけど

 C# にはiniファイルを読み込むためのクラスは用意されてない。ぐぐれば旧来の Win32API で読み込む方法はたくさんみつかる。

 でも C# からの Win32API 呼び出しって見苦しいから極力書きたくないし、Win32のini関連の関数群って1項目読み込むたびにファイルを開きなおしそうな作り(キャッシュしてるかもしれんけど、それはそれでキモイ)なので、昔から好きじゃない。

 なので、そもそもごく単純なテキスト形式なんだし、自前で簡単な読み込みクラスを作る事にした。OSや他のアプリが作ったiniから情報を取り出したいだけなので、書き込む事は一切考慮しない。

iniファイルの文字コード

 iniファイルは Unicode が普及する前から使われてるので、Win32API の関数で書き出す場合、デフォルトでは UTF-8 ではなく、Windows の動作言語ごとのデフォルトのマルチバイト文字コード で書き出される。例えば日本語で動作中なら ShiftJIS (932)。

 で、これを C# でテキストファイルとして読み込む場合、何も指定しないと UTF-8 で読み込もうとするので、文字コードを明示的に指定しないといけない。

reader = new StreamReader( path, Encoding.GetEncoding( "shift_jis" ) );

などと決め打ちで指定するか、もしくは

reader = new StreamReader( path, Encoding.Default );

とすれば、現在のデフォルトの文字コードで読み込まれるので、自動的に全言語対応になる。が、どちらにしろ指定した文字コード以外の文字列は正常には読み込めない。そもそも多言語を考慮するなら今さらiniファイルなんて使うな(もしくは後から追加されたUTF16版を使え)って事ですな。

 ちなみに上記コードで UTF-8 や UTF-16 のiniファイルを読み込ませた場合、ファイルの先頭にBOMさえ付いていれば指示したコードを無視して UTF-8/16LE/16BE で読み込んでくれるので、この1行だけで割と多岐の文字コードに対応できる。C#は便利だなぁ。

結局できたもの

 と、結局、iniファイルを読み込む上で悩むのは文字コードだけで、他に特筆すべき事もないんですが、どうせだから出来上がった簡易iniリーダーを載せときます。ごく単純な、悪意のないiniファイルしか考慮してません。

using System;
using System.Collections.Generic;
using System.Text;

public class IniFileReader
{
    // 大文字小文字を区別するか?
    // Win32の実装では区別しないので、それに合わせておく
    public bool             IgnoreCase { get; set; } = true;

    private List<string>    mLines = new List<string>();
    private int             mSectionTop = -1;

    public IniFileReader(
        string path,
        Encoding encoding = null)
    {
        // UTF-8ではなくデフォルトANSI(日本ならShiftJIS)にしとく
        // ※テキストの先頭に UTF8/16LE/16BE のBOMが付いてる場合は、
        // この指定は無視して Unicode で読み込まれる。
        if ( encoding == null )
            { encoding = Encoding.Default; }

        using ( var reader = new System.IO.StreamReader( path, encoding ) )
        {
            var line = reader.ReadLine();
            while ( line != null )
            {
                // 行頭行末のスペース、空行とコメント行は最初から省いておく
                line = line.Trim();
                if ( !String.IsNullOrEmpty( line ) && (line[0] != ';') )
                    { mLines.Add( line ); }
                line = reader.ReadLine();
            }
        }
    }

    // セクションを設定
    public bool
    SetSection(
        string section)
    {
        var minLength = section.Length + 2;
        for ( int i = 0; i < mLines.Count; i++ )
        {
            var line = mLines[i];
            if ( line[0] != '[' )
                { continue; }

            if ( (line.Length >= minLength) && (line[1+section.Length] == ']') &&
                 (String.Compare(section,0, line,1, section.Length, IgnoreCase) == 0) )
            {
                mSectionTop = i + 1;
                return true;
            }
        }

        mSectionTop = -1;
        return false;
    }

    // 値を文字列で取得
    public string
    GetValue(
        string key,
        string defaultValue = null)
    {
        if ( mSectionTop < 0 )
            { return defaultValue; }

        for ( int i = mSectionTop; i < mLines.Count; i++ )
        {
            var        line = mLines[i];
            // セクション終了
            if ( line[0] == '[' )
                { return defaultValue; }

            if ( String.Compare( key, 0, line, 0, key.Length, IgnoreCase ) != 0 )
                { continue; }

            // キーとイコールの間のスペースを容認
            var        valueTop = key.Length;
            while ( valueTop < line.Length )
            {
                char    c = line[valueTop];
                if ( c == '=' )
                    { return line.Substring( valueTop + 1 ).Trim(); }
                else if ( Char.IsWhiteSpace( c ) )
                    { valueTop++; }
                else
                    { break; }
            }
        }

        return defaultValue;
    }
}

使い方

var reader = new IniFileReader( "ファイル名" );

reader.SetSection( "セクション名" );
var value = reader.GetValue( "キー名" );