はじめに
Stack Overflow に “Can .NET load and parse a properties file equivalent to Java Properties class?” (January, 2009) という質問があり、Java の properties ファイルを C# で読む方法を尋ねている。質問者のように、既存の properties ファイルに変更を加えずにそのまま C# で読みたいというケースは存在する。
当該質問にはコード付きの回答が幾つか寄せられているが、私の回答を除き、いずれの実装も実用には耐えられない。存在しない仕様を勝手に追加しているものまである。;
や '
をコメント行開始文字として扱ったり、プロパティー値を囲む引用符を勝手に削除したり。
やはり自作するしかない。
Properties ファイルの仕様
Java properties ファイルの仕様は java.util.Properties.load(java.io.Reader)
の JavaDoc に書かれている。問題は、みんなが想像するよりも少し仕様が複雑だということだ。全てを網羅しているわけではないが、特に考慮すべき点は次のとおり。
- 行には、_自然行_と_論理行_の二種類がある。
- 自然行は
\n
,\r
,\r\n
もしくはストリームの末尾で区切られる。 - 論理行は、バックスラッシュを使用して行末記号シーケンスをエスケープすることで、隣接する複数の自然行にまたがることがある。
- 論理行内の二番目以降の自然行の先頭の空白文字群は捨てられる。
- 空白文字とは、スペース (
\u0020
)、タブ (\t
,\u0009
)、ラインフィード (\f
,\u000C
) である。 - 仕様で明確に述べられているように、「行末記号がエスケープされているかどうかを判定する場合、行末記号シーケンスの前の文字を調べるだけでは十分ではありません。行末記号がエスケープされるためには、連続した奇数のバックスラッシュが存在する必要があります。入力は左から右に処理されるため、行末記号の前(またはほかの場所)に連続したバックスラッシュが2n (ゼロでない偶数)個存在する場合、エスケープ処理後にn個のバックスラッシュがエンコードされます。」
-
=
がキーと値の区切り文字として使用される。 -
:
もキーと値の区切り文字として使用される。 - キーと値の間の区切り文字は省略可能。
- コメント行は、最初の非空白文字として
#
もしくは!
を持つ。つまり、#
や!
の前の空白文字群は許容される。 - 行末記号をバックスラッシュでエスケープしても、コメント行を複数行にまたがらせることはできない。
- 仕様で明確に述べらているとおり、
=
や:
、空白文字もバックスラッシュでエスケープすることによりキーに埋め込むことができる。 - 行末記号でさえ、
\r
と\n
というエスケースシーケンスを用いることで含めることができる。 - 値が省略された場合、空文字列として扱う。
-
\uxxxx
は Unicode 文字をあらわす。 - 有効なエスケープ文字を構成しない文字の前に置かれたバックスラッシュはエラーとして扱われず、単にドロップされる。
Properties ファイルの例
そういうわけで、例えば test.properties
が次の内容を持っている場合
# A comment line that starts with '#'.
# This is a comment line having leading white spaces.
! A comment line that starts with '!'.
key1=value1
key2 : value2
key3 value3
key\
4=value\
4
\u006B\u0065\u00795=\u0076\u0061\u006c\u0075\u00655
\k\e\y\6=\v\a\lu\e\6
\:\ \= = \\colon\\space\\equal
次のキーバリューペア群として解釈されるべきである。
キー | 値 |
---|---|
key1 |
value1 |
key2 |
value2 |
key3 |
value3 |
key4 |
value4 |
key5 |
value5 |
key6 |
value6 |
: = |
\colon\space\equal |
コード例
NuGet パッケージ「Authlete.Authlete」に含まれる PropertiesLoader
クラスは Properties ファイルの仕様を解釈することができる。次のコード例は
using System;
using System.IO;
using System.Collections.Generic;
using Authlete.Util;
namespace MyApp
{
class Program
{
public static void Main(string[] args)
{
string file = "test.properties";
IDictionary<string, string> properties;
using (TextReader reader = new StreamReader(file))
{
properties = PropertiesLoader.Load(reader);
}
foreach (var entry in properties)
{
Console.WriteLine($"{entry.Key} = {entry.Value}");
}
}
}
}
次の出力を生成する。
key1 = value1
key2 = value2
key3 = value3
key4 = value4
key5 = value5
key6 = value6
: = = \colon\space\equal
Java での同様のコードは次のとおり。
import java.util.*;
import java.io.*;
public class Program
{
public static void main(String[] args) throws IOException
{
String file = "test.properties";
Properties properties = new Properties();
try (Reader reader = new FileReader(file))
{
properties.load(reader);
}
for (Map.Entry<Object, Object> entry : properties.entrySet())
{
System.out.format("%s = %s\n", entry.getKey(), entry.getValue());
}
}
}
ソースコード
PropertiesLoader.cs
のソースコードは authlete-csharp 内に存在する。また、PropertiesLoader
用の xUnit テスト群は PropertiesLoaderTest.cs
内に記述されている。