- 旧来の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( "キー名" );