タイトルの件の記事は既出とは思うが、
投稿の練習&後輩の教育用にローカライズされたものが欲しかったので。
プロジェクト作成(本題と関係ないので読み飛ばしてね)
新しいプロジェクトの作成でWindowsフォームアプリケーション(.NET Framework)を選択
適当に名前つけて作成
とりあえずテンプレートを用意。
ここからボタンとテキストボックスを追加してXMLを読み込む処理を入れていきます。
ボタンとテキストボックス、あとイベント追加(本題と関係ないので読み飛ばしてね その2)
コントロール(ボタンとテキストボックス)の追加
それぞれツールボックスからドラッグ&ドロップで追加できる。
テキストボックスは複数行表示させたいので、
プロパティ > プロパティ > Multiline = True
に設定
そしていい感じにテキストボックスを大きくする。
ボタンの
プロパティ > イベント > Click の所をダブルクリックして、
クリックイベントを追加する。
コードが自動生成される。
ここから本題:XML読み込みをLINQで実装する
1. XMLファイルの作成
まずは読み込むためのXMLファイルを作成する。
ひな形はこんな感じ
<?xml version="1.0" encoding="utf-8" ?>
別にメモ帳でも秀丸でも書ければ何でもいいけど、
visualStudioで書くなら
ソリューションエクスプローラー > 何もないところで右クリック > 追加 > 新しい項目
(またはCtrl + Shift + A でも良い)
新しい項目の追加 ウィンドウが開くので XMLファイル を選択し、
適当な名前つけて追加する
XMLファイルが自動生成される。
まずはここに読み込ませたいデータを書いていきます。
今回読み込みたいXMLのデータはこんな感じ
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
<Gosannke>
<PokeName>Fushigidane</PokeName>
<PokeName>Hitokage</PokeName>
<PokeName>Zenigame</PokeName>
</Gosannke>
<Fushigidane>
<Type>kusa</Type>
<Type>doku</Type>
<Waza>Taiatari</Waza>
<Waza>Nakigoe</Waza>
<Waza>Turunomuti</Waza>
</Fushigidane>
<Hitokage>
<Type>honoo</Type>
<Waza>Hikkaku</Waza>
<Waza>Nakigoe</Waza>
</Hitokage>
<Zenigame>
<Type>mizu</Type>
<Waza>Taiatari</Waza>
<Waza>Shippowohuru</Waza>
<Waza>Mizudeppo</Waza>
<Waza>Karanikomoru</Waza>
</Zenigame>
</Pokemon>
表にするとこう
ポケモン名 | タイプ | わざ |
---|---|---|
フシギダネ | くさ / どく | たいあたり / なきごえ / つるのムチ |
ヒトカゲ | ほのお | ひっかく / なきごえ |
ゼニガメ | みず | たいあたり / しっぽをふる / みずでっぽう / からにこもる |
見てわかる通り、タイプとわざの数にばらつきがあります。
(最終的にこういうデータを扱いたいという話)
とりあえず手始めに、以下のデータを読み込む処理をLINQで書いてみます。
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
<Gosannke>
<PokeName>Fushigidane</PokeName>
<PokeName>Hitokage</PokeName>
<PokeName>Zenigame</PokeName>
</Gosannke>
</Pokemon>
2. XMLを読み込む
usingを2つ追加し、パスを取得して読み込んでみます。
using System;
using System.Windows.Forms;
using System.IO;
using System.Data;
using System.Linq; // 追加したusing
using System.Xml.Linq; // 追加したusing
using System.Collections.Generic;
private void Button1_Click(object sender, EventArgs e)
{
// カレントディレクトリを2つ上の階層に設定し直す
Directory.SetCurrentDirectory(@"..\..\");
// Data.xmlのパスを取得
string xmlFilePath = Directory.GetCurrentDirectory() + @"\Data.xml";
// Data.xmlを読み込む
XElement pokeData = XElement.Load(xmlFilePath);
// 御三家ポケモンの名前を全て取得
IEnumerable<string> GosannkeInfo =
from poke in pokeData.Elements("Gosannke").Elements("PokeName") select poke.Value;
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in GosannkeInfo)
{
textBox1.Text += pokeName + "\r\n";
}
// カレントディレクトリを元の場所に戻す
Directory.SetCurrentDirectory(@".\bin\Debug");
}
実行してボタンを押すとこんな感じ。
ポケモンの名前を取得できました。
3. 【応用】取得したタグ名の中身を読みこむ
では本命のこのxmlを読み込んでみましょう
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
<Gosannke>
<PokeName>Fushigidane</PokeName>
<PokeName>Hitokage</PokeName>
<PokeName>Zenigame</PokeName>
</Gosannke>
<Fushigidane>
<Type>kusa</Type>
<Type>doku</Type>
<Waza>Taiatari</Waza>
<Waza>Nakigoe</Waza>
<Waza>Turunomuti</Waza>
</Fushigidane>
<Hitokage>
<Type>honoo</Type>
<Waza>Hikkaku</Waza>
<Waza>Nakigoe</Waza>
</Hitokage>
<Zenigame>
<Type>mizu</Type>
<Waza>Taiatari</Waza>
<Waza>Shippowohuru</Waza>
<Waza>Mizudeppo</Waza>
<Waza>Karanikomoru</Waza>
</Zenigame>
</Pokemon>
先ほどの foreach
の箇所を以下のように書き換えました。
foreach
がネストしてて冗長に見えますね…。
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
textBox1.Text += pokeName + "\r\n";
// <Type>要素の取得
IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
textBox1.Text += "TYPE : " + "\r\n";
foreach (string Type in TypeInfo)
{
textBox1.Text += " " + Type + "\r\n";
}
// <Waza>要素の取得
IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
textBox1.Text += "WAZA : " + "\r\n";
foreach (string Waza in WazaInfo)
{
textBox1.Text += " " + Waza + "\r\n";
}
// 見栄えのための改行
textBox1.Text += "\r\n";
}
一応動作自体は期待通り。
でもコードが気持ち悪いのでforeach
部分をLINQで書き直してみましょう。
4. foreach文をLINQにリファクタリングする
foreach
のデメリットは、単純にネストが深く可読性が悪くなる点です。
動作を変えず、可読性をよくするために、LINQで書き直します。
分かり易いように、<Type>
要素の部分だけをLINQに書き換えました。
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
textBox1.Text += pokeName + "\r\n";
// <Type>要素の取得
IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
textBox1.Text += "TYPE : " + "\r\n";
TypeInfo.ToList().ForEach(Type => { textBox1.Text += " " + Type + "\r\n"; });
// ↑と↓を見比べると分かり易い!
// <Waza>要素の取得
IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
textBox1.Text += "WAZA : " + "\r\n";
foreach (string Waza in WazaInfo)
{
textBox1.Text += " " + Waza + "\r\n";
}
// 見栄えのための改行
textBox1.Text += "\r\n";
}
<Waza>
要素も同様にLINQで書いてみましょう。
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
textBox1.Text += pokeName + "\r\n";
// <Type>要素の取得
IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
textBox1.Text += "TYPE : " + "\r\n";
TypeInfo.ToList().ForEach(Type => { textBox1.Text += " " + Type + "\r\n"; });
// <Waza>要素の取得
IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
textBox1.Text += "WAZA : " + "\r\n";
WazaInfo.ToList().ForEach(Waza => { textBox1.Text += " " + Waza + "\r\n"; });
// 見栄えのための改行
textBox1.Text += "\r\n";
}
タグ要素の取得は、共通の処理なので関数化しましょう。
private void OutputPokeData(XElement pokeData, string pokeName, string tag)
{
IEnumerable<string> Infos = from Info in pokeData.Elements(pokeName).Elements(tag) select Info.Value;
textBox1.Text += tag + " : " + "\r\n";
Infos.ToList().ForEach(Info => { textBox1.Text += " " + Info + "\r\n"; });
}
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
textBox1.Text += pokeName + "\r\n";
// <Type>要素の取得
OutputPokeData(pokeData, pokeName, "Type");
// <Waza>要素の取得
OutputPokeData(pokeData, pokeName, "Waza");
// 見栄えのための改行
textBox1.Text += "\r\n";
}
とっても見やすくなりましたね!
最後にこのforeach
もLINQに書き直して、ソース全体を見てみましょう。
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using System.IO;
namespace XMLReader
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
// カレントディレクトリを2つ上の階層に設定し直す
Directory.SetCurrentDirectory(@"..\..\");
// Data.xmlのパスを取得
string xmlFilePath = Directory.GetCurrentDirectory() + @"\Data.xml";
// Data.xmlを読み込む
XElement pokeData = XElement.Load(xmlFilePath);
// 御三家ポケモンの名前を全て取得
IEnumerable<string> PokeInfo =
from poke in pokeData.Elements("Gosannke").Elements("PokeName") select poke.Value;
// 取得したポケモンの名前をテキストボックスに出力
PokeInfo.ToList().ForEach(pokeName => {
textBox1.Text += pokeName + "\r\n"; // ポケモン名表示
OutputPokeData(pokeData, pokeName, "Type"); // <Type>要素の取得
OutputPokeData(pokeData, pokeName, "Waza"); // <Waza>要素の取得
textBox1.Text += "\r\n"; // 見栄えのための改行
});
// カレントディレクトリを元の場所に戻す
Directory.SetCurrentDirectory(@".\bin\Debug");
}
private void OutputPokeData(XElement pokeData, string pokeName, string tag)
{
IEnumerable<string> Infos = from Info in pokeData.Elements(pokeName).Elements(tag) select Info.Value;
textBox1.Text += tag + " : " + "\r\n";
Infos.ToList().ForEach(Info => { textBox1.Text += " " + Info + "\r\n"; });
}
}
}