LoginSignup
3
3

More than 3 years have passed since last update.

動的に変化するXMLファイルをLinQで読み込む【C#】

Last updated at Posted at 2020-11-29

タイトルの件の記事は既出とは思うが、
投稿の練習&後輩の教育用にローカライズされたものが欲しかったので。

プロジェクト作成(本題と関係ないので読み飛ばしてね)

新しいプロジェクトの作成でWindowsフォームアプリケーション(.NET Framework)を選択
image.png
適当に名前つけて作成
image.png
とりあえずテンプレートを用意。
ここからボタンとテキストボックスを追加してXMLを読み込む処理を入れていきます。
image.png

ボタンとテキストボックス、あとイベント追加(本題と関係ないので読み飛ばしてね その2)

コントロール(ボタンとテキストボックス)の追加
それぞれツールボックスからドラッグ&ドロップで追加できる。
image.png
テキストボックスは複数行表示させたいので、
プロパティ > プロパティ > Multiline = True に設定
image.png
そしていい感じにテキストボックスを大きくする。
image.png
ボタンの
プロパティ > イベント > Click の所をダブルクリックして、
クリックイベントを追加する。
image.png
image.png
コードが自動生成される。
image.png


ここから本題:XML読み込みをLINQで実装する

1. XMLファイルの作成

まずは読み込むためのXMLファイルを作成する。
ひな形はこんな感じ

Data.xml
<?xml version="1.0" encoding="utf-8" ?>

別にメモ帳でも秀丸でも書ければ何でもいいけど、
visualStudioで書くなら
ソリューションエクスプローラー > 何もないところで右クリック > 追加 > 新しい項目
(またはCtrl + Shift + A でも良い)
image.png
新しい項目の追加 ウィンドウが開くので XMLファイル を選択し、
適当な名前つけて追加する
image.png
XMLファイルが自動生成される。
まずはここに読み込ませたいデータを書いていきます。
image.png
今回読み込みたいXMLのデータはこんな感じ

Data.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で書いてみます。

Data.xml
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
    <Gosannke>
        <PokeName>Fushigidane</PokeName>
        <PokeName>Hitokage</PokeName>
        <PokeName>Zenigame</PokeName>
    </Gosannke>
</Pokemon>

2. XMLを読み込む

usingを2つ追加し、パスを取得して読み込んでみます。

Form.cs
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");
}

実行してボタンを押すとこんな感じ。
ポケモンの名前を取得できました。
image.png

3. 【応用】取得したタグ名の中身を読みこむ

では本命のこのxmlを読み込んでみましょう

Data.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 がネストしてて冗長に見えますね…。

Form.cs
// 取得したポケモンの名前をテキストボックスに出力
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で書き直してみましょう。
image.png

4. foreach文をLINQにリファクタリングする

foreachのデメリットは、単純にネストが深く可読性が悪くなる点です。
動作を変えず、可読性をよくするために、LINQで書き直します。
分かり易いように、<Type> 要素の部分だけをLINQに書き換えました。

Form.cs
// 取得したポケモンの名前をテキストボックスに出力
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で書いてみましょう。

Form.cs
// 取得したポケモンの名前をテキストボックスに出力
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";
}

タグ要素の取得は、共通の処理なので関数化しましょう。

Form.cs
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"; });
}
Form.cs
// 取得したポケモンの名前をテキストボックスに出力
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に書き直して、ソース全体を見てみましょう。

Form.cs
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"; });
        }
    }
}

動作も全く同じであることが確認できました。
image.png

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3