C# で UTF-8 INI 読み書き(Linux も対応)
.NET Framework と Mono にて共通の UTF-8 限定 ini 読み書きクラス。
その昔我が SeeMe というアプリ用途に作ったクラスをなんとなく改良。
自分でまた使うかもしれないし、どうしても書き直したくなっただけ。
セクションの順番を保持するという特徴は変わっていない。
reference は mscorlib のみなので扱いも簡単です。
2011-05-05 まるごと作り替え
その昔我が SeeMe というアプリ用途に作ったクラスをなんとなく改良。
自分でまた使うかもしれないし、どうしても書き直したくなっただけ。
セクションの順番を保持するという特徴は変わっていない。
reference は mscorlib のみなので扱いも簡単です。
2011-05-05 まるごと作り替え
内容
ini file Read and Write (ConfigParser or Own class module)
にて Python で作ったのと同じようになる感じに変更した。
共通メソッドはベースクラスに作って派生させる方向で。
以前は Environment.NewLine で OS デフォルト改行コードになるのを忘れていた。
全部 summary を書いた、Visual Studio なら利用時にヒントが出るはず。
メソッドの命名規則は C# 推奨に従っているつもり。
Python 製同様に記述方法を間違えた ini を読み込んだ場合に例外を投げるのみに変更。
しかし動的言語の Python と違い書き込みはコンパイルエラーになるので読み込みのみチェック。
ただ Windows で UTF-8 を扱う場合は Byte Order Mark(BOM) の問題がある。
なので Bom プロパティを追加している、true にすると BOM 付きになる。
それ以外は Python 製と機能は同じである。
細かいことは summary でチェックしてね。
にて Python で作ったのと同じようになる感じに変更した。
共通メソッドはベースクラスに作って派生させる方向で。
以前は Environment.NewLine で OS デフォルト改行コードになるのを忘れていた。
全部 summary を書いた、Visual Studio なら利用時にヒントが出るはず。
メソッドの命名規則は C# 推奨に従っているつもり。
Python 製同様に記述方法を間違えた ini を読み込んだ場合に例外を投げるのみに変更。
しかし動的言語の Python と違い書き込みはコンパイルエラーになるので読み込みのみチェック。
ただ Windows で UTF-8 を扱う場合は Byte Order Mark(BOM) の問題がある。
なので Bom プロパティを追加している、true にすると BOM 付きになる。
それ以外は Python 製と機能は同じである。
細かいことは summary でチェックしてね。
コード
using System; using System.IO; using System.Collections.Generic; using System.Text; namespace IniFile8 { /// <summary> /// Ini ファイル読み書きのベースクラス /// </summary> public abstract class IniFileBase { // 例外用の定数、書き込みミスの場合はコンパイルエラーになるから不要 const string ERROR_READ = "Configuration file READ ERROR\n[{0}]\n{1}=\nvalue is not {2}"; /// <summary> /// セクションとキーを指定して値を文字列で戻すメソッドベース /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> protected virtual string GetValue(string section, string key) { return ""; } /// <summary> /// Ini ファイルから整数値を読み取る /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param> public int ReadInteger(string section, string key, int defaultValue) { string result = GetValue(section, key); if (result == null) return defaultValue; try { return int.Parse(result); } catch(FormatException) { string s = String.Format(ERROR_READ, section, key, "int"); var ex = new FormatException(s); throw ex; } } /// <summary> /// Ini ファイルから浮動小数点数値を読み取る /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param> public float ReadFloat(string section, string key, float defaultValue) { string result = GetValue(section, key); if (result == null) return defaultValue; try { return float.Parse(result); } catch (FormatException) { string s = String.Format(ERROR_READ, section, key, "float"); var ex = new FormatException(s); throw ex; } } /// <summary> /// Ini ファイルから真偽(しんぎ)値を読み取る /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param> public bool ReadBool(string section, string key, bool defaultValue) { string result = GetValue(section, key); if (result == null) return defaultValue; if (result == "1") return true; else if (result == "0") return false; else { string s = String.Format(ERROR_READ, section, key, "bool"); var ex = new FormatException(s); throw ex; } } /// <summary> /// Ini ファイルから文字列を読み取る /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="defaultValue">キーが見つからない場合に戻すデフォルト値</param> public string ReadString(string section, string key, string defaultValue) { string result = GetValue(section, key); if (result == null) return defaultValue; return result; } } /// <summary> /// Ini ファイル中の特定値を取り出す軽量な読み取り専用クラス /// </summary> public class IniFileReader : IniFileBase { private string _fileName; /// <summary> /// コンストラクタ /// </summary> /// <param name="fileName">読み込む INI ファイルのフルパス</param> public IniFileReader(string fileName) { _fileName = fileName; } /// <summary> /// セクションとキーを指定してストリームから値を文字列で戻すメソッド /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <returns>値の文字列</returns> protected override string GetValue(string section, string key) { bool bInSection = false; int len; string buf; using (StreamReader sr = new StreamReader(_fileName)) { while ((buf = sr.ReadLine()) != null) { if (buf == "") continue; if (buf[0] == ';') continue; len = buf.Length; if (bInSection) { int lPos = buf.IndexOf('='); if (lPos > 0) { if (key == buf.Substring(0, lPos)) { return buf.Substring(lPos + 1, len - lPos - 1); } } if (len > 2 && buf[0] == '[' && buf[len - 1] == ']') { return null; //次のセクションが始まってしまったので終了 } } else { if (len > 2 && buf[0] == '[' && buf[len - 1] == ']') { if (section == buf.Substring(1, len - 2)) bInSection = true; } } } } return null; } } /// <summary> /// Ini ファイルを読み書きするクラス /// 保存時にセクションおよびキーの順番は保持される /// </summary> public class IniFile : IniFileBase { /// <summary> /// Byte Order Mark の有無 /// </summary> public bool Bom { get; set; } /// <summary> /// 保存時に先頭にコメントを入れる場合 /// </summary> public string Header { get; set; } List<Dictionary<string, List<Dictionary<string, string> > > > ini; string _fileName; /// <summary> /// コンストラクタ /// </summary> /// <param name="fileName">読み込む INI ファイルのフルパス</param> public IniFile(string fileName) { Bom = false; Header = ""; _fileName = fileName; ini = new List<Dictionary<string, List<Dictionary<string, string> > > >(); if (File.Exists(_fileName)) { bool bInSection = false; int len; string buf; string section = ""; using (StreamReader sr = new StreamReader(_fileName)) { while ((buf = sr.ReadLine()) != null) { if (buf == "") continue; if (buf[0] == ';') continue; len = buf.Length; if (bInSection) { int lPos = buf.IndexOf('='); if (lPos > 0) { string t1 = buf.Substring(0, lPos); string t2 = buf.Substring(lPos + 1, len - lPos - 1); Add(section, t1, t2); } if (len > 2 && buf[0] == '[' && buf[len - 1] == ']') { section = buf.Substring(1, len - 2); } } else { // 最初のセクションが始まるまでガン無視させる if (len > 2 && buf[0] == '[' && buf[len - 1] == ']') { if (section == buf.Substring(1, len - 2)) bInSection = true; } } } } } } /// <summary> /// Ini オブジェクトに追加を行う内部メソッド /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="value">値の文字列</param> private void Add(string section, string key, string value) { foreach (var dic1 in ini) { if (dic1.ContainsKey(section)) { foreach (var dic2 in dic1[section]) { if (dic2.ContainsKey(key)) { dic2[key] = value; return; } } var dic3 = new Dictionary<string, string>(); dic3.Add(key, value); dic1[section].Add(dic3); return; } } var dic4 = new Dictionary<string, string>(); dic4.Add(key, value); var l1 = new List<Dictionary<string, string> >(); l1.Add(dic4); var dic5 = new Dictionary<string, List<Dictionary<string, string> > >(); dic5.Add(section, l1); ini.Add(dic5); } /// <summary> /// セクションとキーを指定して Ini オブジェクトから値を文字列で戻すメソッド /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <returns>値の文字列</returns> protected override string GetValue(string section, string key) { foreach (var dic1 in ini) { if (dic1.ContainsKey(section)) { foreach (var dic2 in dic1[section]) { if (dic2.ContainsKey(key)) { return dic2[key]; } } } } return null; } /// <summary> /// Ini オブジェクトをファイルに書き出すメソッド /// </summary> public void Save() { string crlf = Environment.NewLine; // Linux は LF になる StreamWriter sw = null; try { if (this.Bom) sw = new StreamWriter(_fileName, false, Encoding.UTF8); else sw = new StreamWriter(_fileName, false); if (this.Header != "") sw.Write(this.Header); foreach (var d1 in ini) { foreach (string section in d1.Keys) { sw.Write(String.Format("[{0}]{1}", section, crlf)); foreach (var d2 in d1[section]) { foreach (string key in d2.Keys) { sw.Write(String.Format("{0}={1}{2}", key, d2[key], crlf)); } } sw.Write(crlf); } } } catch (Exception ex) { throw new Exception(ex.Message); } finally { if (sw != null) sw.Close(); } } /// <summary> /// セクションの存在確認 /// </summary> /// <param name="section">セクションの文字列</param> /// <returns>存在する場合は true</returns> public bool SectionExists(string section) { foreach (var dic1 in ini) { if (dic1.ContainsKey(section)) { return true; } } return false; } /// <summary> /// セクションを削除する /// </summary> /// <param name="section">セクションの文字列</param> public void EraseSection(string section) { foreach (var dic1 in ini) { if (dic1.ContainsKey(section)) { dic1.Remove(section); } } } /// <summary> /// Ini オブジェクト内容を書き換え、又は追加 /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="Value">整数値</param> public void WriteInteger(string section, string key, int Value) { this.Add(section, key, Value.ToString()); } /// <summary> /// Ini オブジェクト内容を書き換え、又は追加 /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="Value">浮動小数点数値</param> public void WriteFloat(string section, string key, float Value) { this.Add(section, key, Value.ToString()); } // <summary> /// Ini オブジェクト内容を書き換え、又は追加 /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="Value">真偽値</param> public void WriteBool(string section, string key, bool Value) { if (Value) this.Add(section, key, "1"); else this.Add(section, key, "0"); } // <summary> /// Ini オブジェクト内容を書き換え、又は追加 /// </summary> /// <param name="section">セクションの文字列</param> /// <param name="key">キーの文字列</param> /// <param name="Value">文字列</param> public void WriteString(string section, string key, string Value) { this.Add(section, key, Value); } } }
使い方
たとえばこんなクラスを作ってみます。
と利用できます。
ビルドバッチを含めて ZIP でまとめたものも置いておきます
using System; using System.Collections.Generic; using System.IO; using IniFile8; namespace initest { class TestMain { [STAThread] public static void Main(string[] args) { // EXE 位置に test.ini 作成 var ass = System.Reflection.Assembly.GetEntryAssembly(); string path = Path.Combine(Path.GetDirectoryName(ass.Location), "test.ini"); // IniFile オブジェクト var ini = new IniFile8.IniFile(path); try { ini.WriteString("せくしょん", "きー", "値"); ini.WriteString("二段目", "きー", "値"); ini.WriteInteger("せくしょん", "int", 666); // 書き換えテスト ini.WriteInteger("せくしょん", "int", 4); // Byte Order Mark をつけるなら以下コメントアウトを外す //ini.Bom = true; ini.Save(); } catch (Exception ex) { Console.WriteLine(ex.Message); } // 読み込み例外テスト var err = new IniFile8.IniFileReader(path); try { int r = err.ReadInteger("せくしょん", "きー", 10); Console.WriteLine(r); } catch (FormatException ex) { Console.WriteLine(ex.Message); } } } }Linux でビルドしてみます。
$ gmcs -out:exex.exe *.cs $ ./exex.exe
と利用できます。
ビルドバッチを含めて ZIP でまとめたものも置いておきます
Copyright(C) sasakima-nao All rights reserved 2002 --- 2024.