2008年1月29日火曜日

XMLファイルを読み込む(C#)

プログラムを開発する上で、設定ファイルを作成することは非常に重要なことです。

ソースコードに直接埋め込む場合と違って、設定ファイルからパラメータを
読み込むことで、実行環境の違いを吸収したりできます。
(逆に言えば、設定ファイルを変更することで、プログラムが動かなくなったり
 します。)

Windowsでは古くから設定ファイルとして"INIファイル"が用いられてきました。
拡張子が".ini"となってるファイルです。

ところが、ネットワークの発展と共に、XMLが普及しだすと、
設定ファイルとしてもXMLファイルが広く用いられるようになりました。

そこで、今回はXMLファイルを読み込む方法を紹介します。


"コンソール アプリケーション"プロジェクトを作成します。
今回は"XMLParse"とします。

メニューの"プロジェクト"から"新しい項目の追加"を選択します。

"XML ファイル"を選択して、適当にファイル名をつけます。
例では、"server.xml"としておきます。

/*** server.xml ***/
<?xml version="1.0" encoding="utf-8" ?>
<server>
<address>
192.168.10.1
</address>
<port>
80
</port>
</server>

ネットワーク上に配置されたサーバの設定を保持していると想定して下さい。

次に、XMLファイルを解読するクラスを作成します。

先ほどと同様の手順で、クラスファイルを選択して下さい。
名前は"XmlParser.cs"としておきます。

"XmlParser"クラスは、最初にXMLファイルを読み込み、
その値を保持するユーティリティクラスとして作成したいと思います。

/*** XmlParser ***/
class XmlParser
{
private static IPAddress ip;
private static int port;

public static IPAddress Ip
{
get { return ip; }
}

public static int Port
{
get { return port; }
}
}

このクラスには静的フィールド変数として、IPアドレスを表す"ip"と、
ポート番号を表す"port"があります。

さらに、それらの値を返す静的プロパティが2つあります。

ユーティリティクラスのため、全てのメンバが静的です。

このクラスにXMLファイルを解析する処理を持たせるわけですが、
読み込むためのメソッドを作成して、それを呼び出せば簡単に済みます。

しかし、今回のようにファイルを一度だけ読み込む場合、
どこで一番最初にXMLファイルを解析させるかを考える必要があります。

もし、その順番が間違ってしまえば、不正な値を持つかもしれません。

そこで、今回のような場合は"静的コンストラクタ"を用います。

静的コンストラクタ
静的コンストラクタとは、クラスを初期化したい場合に利用します。
静的コンストラクタは以下の場合に呼び出されます。
  • 最初のインスタンスが生成される前

  • 静的メンバが参照される前
静的コンストラクタを用いれば、静的プロパティにアクセスするだけで、
何らかの処理を行わせることができます。


今回のケースにはぴったりですね。

静的コンストラクタは通常のコンストラクタと違い、
修飾子として"static"のみを付与します。

なお、"System.Xml"を"using"を持ちいて省略しておいて下さい。

/*** XmlParser.cs ***/
static XmlParser()
{
// 空白を無視するように設定する
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;

// リーダー
XmlReader reader = XmlReader.Create("../../server.xml", settings);

while (reader.Read())
{
// ノードの種類が要素かどうかを判定
if(reader.NodeType == XmlNodeType.Element)
{
if(reader.LocalName.Equals("address"))
{
Console.WriteLine(reader.ReadString() + "を読み込みました。");
// TryParseを利用してIPアドレスを読み込む
bool flag = IPAddress.TryParse(reader.ReadString(), out ip);
if (!flag)
ip = IPAddress.None;
}
else if (reader.LocalName.Equals("port"))
{
Console.WriteLine(reader.ReadString() + "を読み込みました。");
// TryParseを利用してポート番号を読み込む
bool flag = int.TryParse(reader.ReadString(), out port);
if (!flag)
port = int.MaxValue;
}
}
}
reader.Close();
}

"reader.LocalName"にはタグの要素名が入るため、
文字列比較を行うことで判定を行うことができます。


また、"TryParase"メソッドは、第一引数に与えられた文字列を、
指定したクラスに合うよう変換しつつ、それが正しいかを判定してくれます。
変換後の値は、第二引数で与えられたフィールドに格納されます。

out
"out"キーワードを用いると、"ref"と同様、参照渡しを行うことができます。

"ref"との違いは、"out"に渡すフィールドを明示的に初期化する必要が無い点です。
ただし、そのメソッド内で必ず"out"で参照されたフィールドに対して、
何らかの値を代入してやる必要があります。

"ref"が参照するために参照渡しを実現するのに対し、
"out"は出力するために参照渡しを実現しています。


"XmlReader"を作成する箇所で、XMLファイルへは相対パスで指定しています。

プロジェクト内の"server.xml"ですが、指定には"../"を2つ必要です。

これはプロジェクトの先頭を表すフォルダと、
デバッグ / ビルドを行う実行ファイルを持つフォルダが違うためです。

実際、プロジェクトが収まっているフォルダを開いてみて下さい。
(プロジェクトフォルダが無い場合は、メニューの"ファイル"から
 "全てを保存"を選択して下さい。)

"マイ ドキュメント" > "Visual Stuidio 2008" > "Project" > "XMLParse" >
"XMLParse"と選んだ先がプロジェクトのトップ階層です。

さらに、"bin" > "Debug"と選んだ先に、デバッグモードでの実行ファイルがあります。

ここが基点として、相対パスは認識されます。

よって、その2階層上にある"sever.xml"を指定する場合には、
"../../server.xml"と記述する必要があります。


出力用の処理を"Main"メソッド内に記述します。

/*** Program.cs ***/
static void Main(string[] args)
{
Console.WriteLine("ip = " + XmlParser.Ip + "を取得しました。");
Console.WriteLine("port = " + XmlParser.Port + "を取得しました。");
System.Threading.Thread.Sleep(5000);
}

実行します。















あれ?IPアドレスやポートがエラー値を示している。

と言うよりは、なぜか正しく設定してあるはずなのに、空白も読み込んでる?

・・・なぜ?


"XmlReader"の方で対処できないのであれば、読み込んだ後に対処しましょう。

全ての"reader.ReadString()"を以下のようにして下さい。

/*** XmlParser.cs ***/
string tmp = reader.ReadString().Trim();
reader.ReadString -> tmp
(宣言せずにメソッドに直接メソッドの結果を渡すと
 うまくいかない場合があります。)

"Trim"メソッドは、指定の文字列の先頭、及び末尾にある全ての空白を
削除してくれる素晴らしいメソッドです。

また、"TrimStart"、"TrimEnd"を用いれば、先頭、末尾のどちらかを
指定して削除することもできます。


再び、実行します。















今度は正しく動きました。
(原因が未だにわからないんですが、良しとして下さい^^;)


XMLは、今後インターネットの世界だけではなく、
様々なアプリケーションに使われていきます。

それも、今回のような設定ファイルだけではなく、
互換性の向上のために、メタ情報を記述した保存用ファイルや、
データ転送のための共通規格として利用されます。

XMLを用いることでプログラムに柔軟性と拡張性を持たせることができます。


参照:
out (C#) : msdn

0 件のコメント: