[C#] iniファイルを読み込む方法たち

2018-05-15 (火)

kernel32.dll(Win32API)、INIFileParser(NuGet)、独自実装(IniFile.cs)のパターンで取得します。

環境

  • Windows 10 Pro 64bit 1709
  • Visual Studio Community 2017 15.5.7
  • .NET Framework 4.6.1
  • C# 7.0

結果

読み込むiniファイルの内容です。

;セクションに属さない値は無効
RootKey = root

[Global]
;Comment = コメント行です
Count = 100

;セクション名の前後のスペースは無視される
[ Section Space Name ]
fullscreen = false

;大文字小文字は無視される
[IgnoreCase]
name =   前後のスペースはトリム   

[日本語セクション]
files = img.png;title.jpg;

読み取り結果です。使用例は後述。

変数: 取得時の指定方法kernel32.dllINIFileParser.dllIniFile.cs
s1: “RootKey”""rootroot
s2: “Section Space Name”falsefalsefalse
s3: " Section Space Name "falsenullnull
s4: “IgnoreCase”(文字化けで取得可)前後のスペースはトリム前後のスペースはトリム
s5: “ignorecase”(文字化けで取得可)null前後のスペースはトリム
s6: “日本語セクション”""img.png;title.jpg;img.png;title.jpg;

実装

kernel32.dll

注意

日本語環境ではShift_JISで動作するため、iniファイルはShift_JISで保存する必要があります。

using System.IO;
using System.Runtime.InteropServices;
using System.Text;

public class IniFile
{
    [DllImport("kernel32.dll")]
    public static extern uint GetPrivateProfileString(
        string lpAppName, string lpKeyName, string lpDefault,
        StringBuilder lpReturnedString, uint nSize, string lpFileName);

    private readonly StringBuilder _builder = new StringBuilder(255);
    public string FullName { get; set; }

    public IniFile(string filePath)
    {
        FullName = Path.GetFullPath(filePath);
    }

    public string Read(string section, string key, string defaultValue = null)
    {
        _builder.Clear();
        GetPrivateProfileString(section, key, defaultValue, _builder, 255, FullName);
        return _builder.ToString();
    }
}

使用例

var ini = new IniFile("Sample.ini");

var s1 = ini.Read("", "RootKey"); // ""
var s2 = ini.Read("Section Space Name", "fullscreen"); // false
var s3 = ini.Read(" Section Space Name ", "fullscreen"); // false
var s4 = ini.Read("IgnoreCase", "name"); // (文字化けで取得可)
var s5 = ini.Read("ignorecase", "name"); // (文字化けで取得可)
var s6 = ini.Read("日本語セクション", "files"); // ""

// s1のケースでSectionをnullで指定すると・・・
var s1_null = ini.Read(null, "RootKey"); // Section Space Name

INIFileParser.dll

Nugetから取得できます。
rickyah/ini-parser: Read/Write an INI file the easy way!

使用例

var parser = new IniParser.FileIniDataParser();
var data = parser.ReadFile("Sample.ini");

var s1 = data.Global["RootKey"]; // root
var s2 = data["Section Space Name"]["fullscreen"]; // false
var s3 = data[" Section Space Name "]["fullscreen"]; // null
var s4 = data["IgnoreCase"]["name"]; // 前後のスペースはトリム
var s5 = data["ignorecase"]["name"]; // null
var s6 = data["日本語セクション"]["files"]; // img.png;title.jpg;

IniFile.cs (私的メモ)

都合により、少し特殊仕様なIniの実装です。
末尾に+がある場合は次の行の文字列も連結し、\++として扱います。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

public class IniFile
{
    private const StringComparison Comparison = StringComparison.Ordinal;
    private static readonly StringComparer Comparer = StringComparer.OrdinalIgnoreCase;

    private readonly Dictionary<string, Dictionary<string, string>> _ini = new Dictionary<string, Dictionary<string, string>>(Comparer);

    public IniFile(string file, Encoding encoding = null)
    {
        var lines = File.ReadLines(file, encoding ?? Encoding.GetEncoding("shift_jis"))
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Select(x => x.Trim())
            .Where(x => !x.StartsWith(";", Comparison));

        var currentSection = new Dictionary<string, string>(Comparer);
        var key = "";
        var isNextLine = false;

        _ini[""] = currentSection;

        foreach (var line in lines)
        {
            if (isNextLine)
            {
                isNextLine = IsNextLine(line);
                currentSection[key] += GetValue(line, 0, isNextLine).TrimEnd();
                continue;
            }

            if (line[0] == '[' && line[line.Length - 1] == ']')
            {
                currentSection = new Dictionary<string, string>(Comparer);
                var section = line.Substring(1, line.Length - 2).Trim();
                _ini[section] = currentSection;
                continue;
            }

            var index = line.IndexOf("=", Comparison);
            if (index == -1) continue;

            key = line.Substring(0, index).Trim();
            isNextLine = IsNextLine(line);
            currentSection[key] = GetValue(line, index + 1, isNextLine).Trim();
        }

        const char nextLineChar = '+';
        const char escapeChar = '\\';

        bool IsNextLine(string line) => line[line.Length - 1] == nextLineChar && line.Length >= 2 &&
                                        line[line.Length - 2] != escapeChar;

        string GetValue(string line, int startIndex, bool isNextLineChar)
        {
            var length = line.Length - startIndex - (isNextLineChar ? 1 : 0);

            var sb = new StringBuilder();
            var escape = false;
            var ignoreSpace = false;

            // "\Ima+ ge\+123.png"のようになっていたら、"\Image+123.png"と見なす
            foreach (var c in line.Skip(startIndex).Take(length))
            {
                if (escape)
                {
                    if (c != nextLineChar)
                        sb.Append(escapeChar);
                }
                else
                {
                    switch (c)
                    {
                        case escapeChar:
                            escape = true;
                            ignoreSpace = false;
                            continue;
                        case nextLineChar:
                            ignoreSpace = true;
                            continue;
                        case ' ' when ignoreSpace:
                            continue;
                    }
                }

                escape = false;
                ignoreSpace = false;
                sb.Append(c);
            }

            return sb.ToString();
        }
    }

    public string GetValueRoot(string key, string @default = null) => GetValue("", key, @default);

    public string GetValue(string section, string key = "", string @default = null)
    {
        return _ini.TryGetValue(section, out var iniSection) &&
                iniSection.TryGetValue(key, out var value)
            ? value
            : @default;
    }

    public IEnumerable<string> GetKeys(string section)
    {
        return _ini.TryGetValue(section, out var iniSection) ? iniSection.Keys : Enumerable.Empty<string>();
    }

    public IEnumerable<string> GetSections() => _ini.Keys.Where(x => x != "");
}

使用例

var m = new IniFile("Sample.ini"); // root
var x1 = m.GetValue("", "RootKey"); // false
var x2 = m.GetValue("Section Space Name", "fullscreen"); // null
var x3 = m.GetValue(" Section Space Name ", "fullscreen"); // ""
var x4 = m.GetValue("IgnoreCase", "name"); // 前後のスペースはトリム
var x5 = m.GetValue("ignorecase", "name"); // 前後のスペースはトリム
var x6 = m.GetValue("日本語セクション", "files"); // img.png;title.jpg;

感謝

2018-05-15 (火)