【C#】独自クラスをXML形式で保存する

こんちゃ!洋梨🍐です。

今回はアプリ内でデーターを保存するときにXML形式を用いた時の手順及びソースコードをメモとして書きたいと思います。

XML形式のファイルを読み取る

ソースコード・XMLFinder.cs

 /* Copyright(c) 2018 YounashiP
 * Released under the MIT license.
 * see https://opensource.org/licenses/MIT
 * XML_Controller version 0.1.1 */

public class XMLFinder
{
    string str;
    public XMLFinder(string str)
    {
        this.str = str;
    }

    public XMLs GetTags(string tag) // GET Simple
    {
        List<string> findStr = new List<string>();

        int n = 0;
        while (true)
        {
            n = str.IndexOf(tag, n);
            if (n != -1)
            {
                int n2 = str.IndexOf(tag, n + tag.Length);
                if (n2 != -1)
                {
                    findStr.Add(str.Substring(n + tag.Length, n2 - n - tag.Length));
                    n = n2 + tag.Length;
                }
                else break;
            }
            else break;
        }
        return new XMLs(findStr.ToArray());
    }

    public Dictionary<string, string[]> Get_Kvp() // KEY : tag  Value : str[]
    {
        Find();
        Dictionary<string, string[]> k = new Dictionary<string, string[]>();
        foreach (KeyValuePair<string, List<string>> kvp in tagInfoTmp)
        {
            k.Add(kvp.Key, kvp.Value.ToArray());
        }
        return k;
    }

    private void Find()
    {
        int n1 = 0, n2 = 0;
        while (n1 != -1)
        {
            n1 = str.IndexOf("<", n2);
            if (n1 == -1) break;

            n2 = str.IndexOf(">", n1);
            if (n2 == -1) throw new System.Exception("Not Found Close Tag.");

            TagFound(n1, n2);

        }
    }

    Dictionary<string, TagMark> tagTmp = new Dictionary<string, TagMark>();
    private void TagFound(int openIndex, int closeIndex)
    {
        int abs = closeIndex - openIndex + 1;

        string tag = str.Substring(openIndex + 1, abs - 2);

        if (tag[0] == '/') // 閉じタグの場合
        {
            tag = tag.Substring(1, tag.Length - 1);// '/'を除外
            if (!tagTmp.ContainsKey(tag)) return; //throw new System.Exception("Not Found Key");
            int index = tagTmp[tag].Get();
            int indexAbs = (openIndex - 1) - index + 1; // [<ooo>infomes</ooo>] -> 'i'-'s' = abs
            TagAdd(tag, str.Substring(index, indexAbs));
        }
        else // 開始タグの場合
        {
            if (tagTmp.ContainsKey(tag)) tagTmp[tag].Set(closeIndex + 1); // 既にキーがある(入れ子)の時 // '>'の次からが内容
            else tagTmp.Add(tag, new TagMark(closeIndex + 1));
        }

    }

    Dictionary<string, List<string>> tagInfoTmp = new Dictionary<string, List<string>>();
    private void TagAdd(string tag, string str)
    {
        if (tagInfoTmp.ContainsKey(tag)) tagInfoTmp[tag].Add(str);
        else
        {
            tagInfoTmp.Add(tag, new List<string>());
            tagInfoTmp[tag].Add(str);
        }
    }

    private class TagMark
    {
        List<int> indexList = new List<int>();
        public TagMark(int index)
        {
            Set(index);
        }

        public void Set(int index)
        {
            indexList.Add(index);
        }
        public int Get()
        {
            if (indexList.Count == 0) throw new System.Exception("Null List.");
            int n = indexList[indexList.Count - 1];
            indexList.RemoveAt(indexList.Count - 1);
            return n;
        }
    }

    public string[] GetRows(string key)
    {
        List<string> sl = new List<string>();
        string[] strs = str.Replace("\r\n", "\n").Split('\n');
        int n = 0;
        foreach (string s in strs)
        {
            n = s.IndexOf(key);
            if (n != -1) sl.Add(s.Substring(n + key.Length));
        }
        return sl.ToArray();
    }
}

この一応ソースコードのほとんどは使わない部分だと思いますが、とりあえず取得するときはGet_Kvp()を使ってキーと値のセットを取得できます(一応)。また、その値をもとに再検索し入れ子に対応させるという使い方も一応できます。

XML形式で保存する

XML形式に保存するにはただ<>で囲み、</>で閉じるだけです。

ソースコード(一部)

   List<string> tagList = new List<string>();


    public string Str { get { return str; } }

    public void AddTag(string tagStr)
    {
        str += "<" + tagStr + ">";
        tagList.Add(tagStr);
    }
    public void AddTag(string tagStr, string str, string endStr = "")
    {
        str += "<" + tagStr + ">";
        str += str;
        str += "</" + tagStr + ">" + endStr;
    }

    public void AddStr(string str)
    {
        this.str += str;
    }
    public void CloseTag()
    {
        if (tagList.Count <= 0) return;
        int i = tagList.Count;
        str += "</" + tagList[i - 1] + ">";
        tagList.RemoveAt(i - 1);
    }

XML・独自クラス間の変換を行う

XFile_Converter.cs

/* Copyright(c) 2018 YounashiP
* Released under the MIT license.
* see https://opensource.org/licenses/MIT
* XFile_Converter version 0.1.0 */

public class XFile<T> where T : new()
{
    FieldInfo[] info;

    public XFile()
    {
        info = typeof(T).GetFields();
    }

    public bool ChackXML(string xml, string tagName)
    {
        XMLFinder x = new XMLFinder(xml);
        var keySet = x.Get_Kvp();
        if (!keySet.ContainsKey(tagName)) return false;
        return true;
    }

    public T[] GetFromXML(string xml, string tagName) // tagName : データーとして扱うタグ名 (例:<a>data</a> -> a)
    {
        XMLFinder x = new XMLFinder(xml);
        var keySet = x.Get_Kvp();
        if (!keySet.ContainsKey(tagName)) throw new System.Exception("Not Found Tag [" + tagName + "]");
        T[] Classes = new T[keySet[tagName].Length];
        int index = 0;
        foreach (string s in keySet[tagName])
        {
            string[] sp = s.Trim().Split(',');
            if (sp.Length != info.Length) throw new System.Exception("No Match Index Count. [" + sp.Length + "]!=[" + info.Length + "]");

            T Class = new T();

            for (int i = 0; i < info.Length; i++)
            {
                FieldInfo f = typeof(T).GetField(info[i].Name);
                if (info[i].FieldType == typeof(int)) f.SetValue(Class, int.Parse(sp[i]));
                else if (info[i].FieldType == typeof(float)) f.SetValue(Class, float.Parse(sp[i]));
                else if (info[i].FieldType == typeof(double)) f.SetValue(Class, double.Parse(sp[i]));
                else if (info[i].FieldType == typeof(string)) f.SetValue(Class, sp[i]);
                else
                {
                    var v = Convert.ChangeType(sp[i], info[i].FieldType);
                    f.SetValue(Class, v);
                }
            }
            Classes[index] = Class;
            index++;
        }
        return Classes;
    }

    public string GetFromT(T[] type, string tagName)
    {
        XMLMaker x = new XMLMaker();
        FieldInfo[] info = typeof(T).GetFields();
        foreach (T t in type)
        {
            x.AddTag(tagName);
            string s = "";
            for (int i = 0; i < info.Length; i++)
            {
                FieldInfo f = typeof(T).GetField(info[i].Name);
                string addStr = "";
                if (f.GetValue(t) != null) addStr = f.GetValue(t).ToString();
                s += addStr;
                if (i < info.Length - 1) s += ",";
            }
            x.AddStr(s);
            x.CloseTag();
        }
        return x.GET;

    }
}

このXFile<T>クラスを用いることによりXML、独自クラスでの双方変換が可能になります。

使い方

上記スクリプトを使った使い方を簡単に説明します。

まずこのような独自クラスがあったとします。

public class UserDATA
{
    public string Name;
    public int Score;
    public float Ave;
    public bool isMen;
}

このUserDATA配列をXMLに変換したい場合

UserDATA[] userDATAs = { new UserDATA() }; // 元の配列
XFile<UserDATA> xFile = new XFile<UserDATA>();
string xml = xFile.GetFromT(userDATAs, "P"); //XML形式のstring(文字列)

このようにします。逆に戻したいときは

string xml = "";
XFile<UserDATA> xFile = new XFile<UserDATA>();
UserDATA[] userDATAs = xFile.GetFromXML(xml, "P");

このようにします。

なお、XML形式で保存する方法を紹介と言いながらクラス内のデーターはコンマで区切ってます^^

こんな感じです

この保存方法のメリットはXMLのタグを検索すればそのクラスの配列を取得できます。

つまり今回UserDataを”P”というタグで保存した為、UserDataを取得したいときはPタグを取得すればよく、追加したらそのタグを検索すればいいといった感じです。

また、可読性も上がるかなと思いXMLで実装してみたというのもあります👼


ここまで読んでくれてありがとうございました!!

ホームページでは他にも

・様々な記事や作った作品および過程
・ソースコード、素材ファイル
・あらゆる”モノ”の作り方

などなど随時、記事や作品を新規公開・更新していますので是非見ていってくださいね!見ていただけると本当に嬉しいです!