UnityでXMLデータを使いたいな。
だからXMLデータを読み込む処理を作らなくっちゃ。
でもUnityの機能でXMLデータを読み込むようなものなんてあったけな。
んー、無かったような気がするなぁ。
でもC#.Net内の名前空間やクラスの中で、XMLの単語を見たことがあった気がする。
おっしゃ、じゃあその辺を調べてパパっと組み込んでしまおうか。
そう……、僕はこの時、軽い気持ちでXMLデータを読み込む処理を作ろうとしたんだ。
それがまさか、あんな争いに巻き込まれるだなんて……。
この記事にはUnity2017.4.1f1を使用しています。
- なぜかXMLを扱うクラスが複数種類ある
- XmlDocumentを使ってみよう
- XDocumentを使ってみよう
- XmlSerializerを使ってみよう
- XmlDocumentとXDocumentと争いの火種
- そしてXMLへ
なぜかXMLを扱うクラスが複数種類ある
XMLは別に新しいフォーマットでもない。
随分前からあるものだ。
UnityやらC#で既に使っている人も多いことだろう。
最先端の技術を学ぼうとしてるわけではないので、ネット上を調べればそこら中に情報が転がっているはずだ。
ググってしらべるか。
……。
うん、案の定いっぱい情報を入手できた。
えーと何々、XmlDocument
というそのまんまの名前のクラスを使うことでXMLデータを読み込むことが出来そうだね。
一つのページからだけでは情報が不確かなので、複数のページから情報を確認しておこう。
んーと、こっちのページではXmlSerializer
というクラスを使ってXMLデータを扱っているね。
ん? こっちのページではXDocument
というクラスを使っているようだ。
何故XMLを扱うクラスが複数個あるんだ!
- XmlDocument
- XDocument
- XmlSerializer
どのクラスもXMLデータを扱うという点は一緒なのに、お互いが干渉しあってない。
各々が独自でXMLを読み込む処理を持っているようだ。
そもそも名前空間が全部違うしな。
そもそもXmlDocument
とXDocument
なんて、名前が似すぎてて混乱しか招かないだろう!
なんだこれ、なんかめんどくさそうだな。
まあ、一つ一つ使ってみてみようか。
とりあえず使うXMLのデータとして、このブログのRSSデータでも使おうか。
<?xml version="1.0"?> <rss version="2.0"> <channel> <title>うら干物書き</title> <link>http://www.urablog.xyz/</link> <description>ゲームを作りたい!</description> <lastBuildDate>Sat, 24 Mar 2018 14:04:23 +0900</lastBuildDate> <docs>http://blogs.law.harvard.edu/tech/rss</docs> <generator>Hatena::Blog</generator> <item> <title>【Unity】アセット読書会に行ってきたよ。NativeArrayってなんだろう?</title> <link>http://www.urablog.xyz/entry/2018/03/24/140423</link> <description><p> 京都で行われるUnity技術者の集いである</description> <pubDate>Sat, 24 Mar 2018 14:04:23 +0900</pubDate> <guid isPermalink="false">hatenablog://entry/17391345971628867392</guid> <category>Unityメモ</category> <category>Unity</category> <enclosure url="https://cdn-ak.f.st-hatena.com/images/fotolife/u/urahimono/20180324/20180324135415.png" type="image/png" length="0" /> </item> <item> <title>【Unity】プロジェクト内のデータをビルド出力時にそのままの形で</title> <link>http://www.urablog.xyz/entry/2018/02/18/212849</link> <description><p> Unityで作ったゲームが、外部の実行</description> <pubDate>Sun, 18 Feb 2018 21:28:49 +0900</pubDate> <guid isPermalink="false">hatenablog://entry/17391345971617600609</guid> <category>Unityメモ</category> <category>Unity</category> <enclosure url="https://cdn-ak.f.st-hatena.com/images/fotolife/u/urahimono/20180218/20180218212318.png" type="image/png" length="0" /> </item> <item> <title>【Unity】ゲーム中に常時必要なGameObjectがどのシーンから始めても存在するようにしてみよう</title> <link>http://www.urablog.xyz/entry/2018/02/11/164734</link> <description><p> <em>ゲームジャム中の会話にて</description> <pubDate>Sun, 11 Feb 2018 16:47:34 +0900</pubDate> <guid isPermalink="false">hatenablog://entry/17391345971615406264</guid> <category>Unityメモ</category> <category>Unity</category> <enclosure url="https://cdn-ak.f.st-hatena.com/images/fotolife/u/urahimono/20180211/20180211164205.png" type="image/png" length="0" /> </item> </channel> </rss>
XmlDocumentを使ってみよう
https://msdn.microsoft.com/ja-jp/library/system.xml.xmldocument.aspx
えーと、まずはXmlDocument
から見ていこう。
名前空間はSystem.Xml
か。
XmlDocument
にXMLデータを渡すと、各タグの情報はXmlElement
などのクラスなって、それがXmlNode
でつながっていると……。
まあ、サンプルがいろいろと転がっているから、コードを作るのは簡単そうだ。
XMLController.cs
using UnityEngine; public class XMLController : MonoBehaviour { [SerializeField] private TextAsset m_xmlAsset = null; [SerializeField] private string m_xmlFilePath = null; private void Start() { if( m_xmlAsset != null ) { LoadFromText( m_xmlAsset.text ); } if( !string.IsNullOrEmpty( m_xmlFilePath ) ) { LoadFromPath( m_xmlFilePath ); } } /// <summary> /// xmlのテキストデータを読み込む場合。 /// </summary> private void LoadFromText( string i_xmlText ) { try { var xml = new System.Xml.XmlDocument(); xml.LoadXml( i_xmlText ); System.Xml.XmlElement root = xml.DocumentElement; // xmlデータをログに表示するよ。 ShowData( root ); // 名前を指定して検索する場合は、この関数を使おう! var titles = root.GetElementsByTagName( "title" ); if( titles != null ) { foreach( var title in titles ) { var titlelement = title as System.Xml.XmlElement; if( titlelement != null ) { string nameText = titlelement.Name; string valueText = titlelement.FirstChild != null ? titlelement.FirstChild.Value : ""; Debug.LogFormat( "name:{0}, value:{1}", nameText, valueText ); } } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } /// <summary> /// xmlのファイルパスから読み込む場合。 /// </summary> private void LoadFromPath( string i_path ) { try { var xml = new System.Xml.XmlDocument(); xml.Load( i_path ); System.Xml.XmlElement root = xml.DocumentElement; // xmlデータをログに表示するよ。 ShowData( root ); // 名前を指定して検索する場合は、この関数を使おう! var titles = root.GetElementsByTagName( "title" ); if( titles != null ) { foreach( var title in titles ) { var titlelement = title as System.Xml.XmlElement; if( titlelement != null ) { string nameText = titlelement.Name; string valueText = titlelement.FirstChild != null ? titlelement.FirstChild.Value : ""; Debug.LogFormat( "name:{0}, value:{1}", nameText, valueText ); } } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } private void ShowData( System.Xml.XmlElement i_element ) { string nameText = i_element.Name; string valueText = i_element.FirstChild != null ? i_element.FirstChild.Value : ""; string atrText = string.Empty; // アトリビュートがある場合。 if( i_element.HasAttributes ) { foreach( var attribute in i_element.Attributes ) { var xmlAttribute = attribute as System.Xml.XmlAttribute; if( xmlAttribute != null ) { atrText += string.Format( "\t\t{0}={1}\n", xmlAttribute.Name, xmlAttribute.Value ); } } } Debug.LogFormat( "name:{0}\n\tvalue:{1}\n\tattribute:\n{2}", nameText, valueText, atrText ); // 子ノードも探そう。 foreach( var child in i_element.ChildNodes ) { var childElement = child as System.Xml.XmlElement; if( childElement != null ) { ShowData( childElement ); } } } } // class XMLController
うん、読み込めたようだね。
XDocumentを使ってみよう
https://msdn.microsoft.com/ja-jp/library/system.xml.linq.xdocument.aspx
次にXDocument
を見てみよう。
……何故こんな似たような名前になってしまったのだろう。
名前空間はSystem.Xml.Linq
。
XDocument
にデータを渡して、各タグの情報はXElement
になっていると。
使い方もXmlDocument
に似てるなぁ。
XMLController.cs
using UnityEngine; public class XMLController : MonoBehaviour { [SerializeField] private TextAsset m_xmlAsset = null; [SerializeField] private string m_xmlFilePath = null; private void Start() { if( m_xmlAsset != null ) { LoadFromText( m_xmlAsset.text ); } if( !string.IsNullOrEmpty( m_xmlFilePath ) ) { LoadFromPath( m_xmlFilePath ); } } /// <summary> /// xmlのテキストデータを読み込む場合。 /// </summary> private void LoadFromText( string i_xmlText ) { try { System.Xml.Linq.XDocument xml = System.Xml.Linq.XDocument.Parse( i_xmlText ); System.Xml.Linq.XElement root = xml.Root; // xmlデータをログに表示するよ。 ShowData( root ); // 名前を指定して検索する場合は、この関数を使おう! var titles = root.Descendants( "title" ); if( titles != null ) { foreach( var title in titles ) { string nameText = title.Name.LocalName; string valueText = title.Value; Debug.LogFormat( "name:{0}, value:{1}", nameText, valueText ); } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } /// <summary> /// xmlのファイルパスから読み込む場合。 /// </summary> private void LoadFromPath( string i_path ) { try { System.Xml.Linq.XDocument xml = System.Xml.Linq.XDocument.Load( i_path ); System.Xml.Linq.XElement root = xml.Root; // xmlデータをログに表示するよ。 ShowData( root ); // 名前を指定して検索する場合は、この関数を使おう! var titles = root.Descendants( "title" ); if( titles != null ) { foreach( var title in titles ) { string nameText = title.Name.LocalName; string valueText = title.Value; Debug.LogFormat( "name:{0}, value:{1}", nameText, valueText ); } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } private void ShowData( System.Xml.Linq.XElement i_element ) { string nameText = i_element.Name.LocalName; string valueText = i_element.Value; string atrText = string.Empty; // アトリビュートがある場合。 if( i_element.HasAttributes ) { foreach( var attribute in i_element.Attributes() ) { atrText += string.Format( "\t\t{0}={1}\n", attribute.Name, attribute.Value ); } } Debug.LogFormat( "name:{0}\n\tvalue:{1}\n\tattribute:\n{2}", nameText, valueText, atrText ); // 子ノードも探そう。 foreach( var child in i_element.Elements() ) { ShowData( child ); } } } // class XMLController
うん、出来た。
若干XmlDocument
と使い方が違うところもあったけど概ね同じように作れた。
キャストする処理が必要ない分、少し楽にXmlDocument
より楽にかけた感はあるかな。
XmlSerializerを使ってみよう
https://msdn.microsoft.com/ja-jp/library/system.xml.serialization.xmlserializer.aspx
最後にXmlSerializer
を使おう。
これは前述二つのクラスとは使い方が違うね。
読み込むXMLデータの作りに対応したクラス構成を作成して、そのクラスに変換するといった感じだろうか。
JsonデータをUnityのJsonUtility
を使って変換するときと同じだね。
というわけでまずXMLデータに対応するクラスを作ってっと。
XMLData.cs
[System.Xml.Serialization.XmlRoot( "rss" )] public class XMLData { [System.Xml.Serialization.XmlElement( "channel" )] public ChannelData channel; public class ChannelData { [System.Xml.Serialization.XmlElement( "title" )] public string title; [System.Xml.Serialization.XmlElement( "link" )] public string link; [System.Xml.Serialization.XmlElement( "description" )] public string description; [System.Xml.Serialization.XmlElement( "lastBuildDate" )] public string lastBuildDate; [System.Xml.Serialization.XmlElement( "item" )] public ItemData item; } public class ItemData { [System.Xml.Serialization.XmlElement( "title" )] public string title; [System.Xml.Serialization.XmlElement( "link" )] public string link; [System.Xml.Serialization.XmlElement( "enclosure" )] public EnclosureData enclosure; } public class EnclosureData { [System.Xml.Serialization.XmlAttribute( "url" )] public string url; [System.Xml.Serialization.XmlAttribute( "type" )] public string type; } }
JsonUtility
を使う時は、SerializeField
やSystem.Serializable
を指定したけど、
今回の場合は、System.Xml.Serialization.XmlAttribute
やSystem.Xml.Serialization.XmlElement
を指定するみたいだね。
で、このクラスをXmlSerializer
で使うっと。
XMLController.cs
using UnityEngine; public class XMLController : MonoBehaviour { [SerializeField] private TextAsset m_xmlAsset = null; [SerializeField] private string m_xmlFilePath = null; private void Start() { if( m_xmlAsset != null ) { LoadFromText( m_xmlAsset.text ); } if( !string.IsNullOrEmpty( m_xmlFilePath ) ) { LoadFromPath( m_xmlFilePath ); } } /// <summary> /// xmlのテキストデータを読み込む場合。 /// </summary> private void LoadFromText( string i_xmlText ) { try { using( var stringReader = new System.IO.StringReader( i_xmlText ) ) { var serializer = new System.Xml.Serialization.XmlSerializer( typeof( XMLData ) ); XMLData xmlData = (XMLData)serializer.Deserialize( stringReader ); if( xmlData != null ) { // クラスに変換後はご自由に。 } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } /// <summary> /// xmlのファイルパスから読み込む場合。 /// </summary> private void LoadFromPath( string i_path ) { try { using( var fileStream = new System.IO.FileStream( i_path, System.IO.FileMode.Open ) ) { var serializer = new System.Xml.Serialization.XmlSerializer( typeof( XMLData ) ); XMLData xmlData = (XMLData)serializer.Deserialize( fileStream ); if( xmlData != null ) { // クラスに変換後はご自由に。 } } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } } // class XMLController
出来た出来た。
Jsonの変換にJsonUtility
を使っている身としては、これが一番わかりやすいかな。
XmlDocumentとXDocumentと争いの火種
調べ終わったけど、妙なのはXmlDocument
とXDocument
の関係だ。
ほぼほぼ一緒の気がするんだけど。
何がどう違うのかって、使ってみたけど説明がしにくい。
そしてどうやら、この辺りは争いの火種になっているようで……。
LINQ to XML vs. DOM (C#)
LINQ to XML vs. DOM | Microsoft Docs
うわぁ……、コード上の争いはめんどくさいんだよ。
どっちも譲ってくんないからなぁ。
この手の争いには極力関わらないようにしたほうがいい気が……。
まあ、もう少し見てみましょうか。
ToString()の違い
ToString()
を実装しておいてもらえると、デバッグログとか出すときにそのままクラスを渡せばいいだけなので楽で助かります。
その辺どうなっているのでしょう。
XMLController.cs
using UnityEngine; public class XMLController : MonoBehaviour { [SerializeField] private TextAsset m_xmlAsset = null; private void Start() { if( m_xmlAsset != null ) { UseXmlDocument( m_xmlAsset.text ); UseXDocument( m_xmlAsset.text ); } } private void UseXmlDocument( string i_xmlText ) { try { var xml = new System.Xml.XmlDocument(); xml.LoadXml( i_xmlText ); System.Xml.XmlElement root = xml.DocumentElement; // 先頭の"title"のタグの情報のみをToStringを使って結果をみてみよう。 var titles = root.GetElementsByTagName( "title" ); foreach( var title in titles ) { var titlelement = title as System.Xml.XmlElement; Debug.LogFormat( "XmlDocument:{0}", titlelement ); break; } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } private void UseXDocument( string i_xmlText ) { try { System.Xml.Linq.XDocument xml = System.Xml.Linq.XDocument.Parse( i_xmlText ); System.Xml.Linq.XElement root = xml.Root; // 先頭の"title"のタグの情報のみをToStringを使って結果をみてみよう。 var titles = root.Descendants( System.Xml.Linq.XName.Get( "title" ) ); foreach( var title in titles ) { Debug.LogFormat( "XDocument:{0}", title ); break; } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } } // class XMLController
うん、XmlDocument
は対応していないのでクラス名がそのまま出力されてるけど、XDocument
は対応しており、プロパティの情報が表示されている。
やるではないか。
まあ、MSDNを見る限りXDocument
の方が新しいものっぽいからなぁ。
Linqの違い
C#を使うなら、Linq
は使いたいですよね。
その辺はどうでしょう。
XMLController.cs
using UnityEngine; using System.Linq; public class XMLController : MonoBehaviour { [SerializeField] private TextAsset m_xmlAsset = null; private void Start() { if( m_xmlAsset != null ) { UseXmlDocument( m_xmlAsset.text ); UseXDocument( m_xmlAsset.text ); } } private void UseXmlDocument( string i_xmlText ) { try { var xml = new System.Xml.XmlDocument(); xml.LoadXml( i_xmlText ); System.Xml.XmlElement root = xml.DocumentElement; // エラー // XmlNodeListに対してLinqは使えない。 var titles = root.ChildNodes.FirstOrDefault( value => value.Name == "item" ); } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } private void UseXDocument( string i_xmlText ) { try { System.Xml.Linq.XDocument xml = System.Xml.Linq.XDocument.Parse( i_xmlText ); System.Xml.Linq.XElement root = xml.Root; // IEnumerable<XElement>で返ってくるため、Linqが使える。 // 以下の場合は、"item"タグ内にある"title"のみを検索している。 var titles = root.Descendants().Where( value => value.Name.LocalName == "item" ).Select( value => value.Element( "title" ) ); foreach( var title in titles ) { Debug.LogFormat( "XDocument:{0}", title ); } } catch( System.Exception i_exception ) { Debug.LogErrorFormat( "うーむ、このXML情報は読み込めなかったらしい。エラーの詳細を添付しておくよ。{0}", i_exception ); } } } // class XMLController
うん、まあそうなるよね。
そりゃ、XDocument
は名前空間的にLinq
は使えるだろうね。
多分だけど、XmlDocument
をLinq
で使えるようにしたのがXDocument
だろうね、恐らく。
そしてXMLへ
まあ好きなものを使ってXMLを読めばいいんじゃないかな。
えっ、僕は何派に入るんだって?
いや、別にどの派閥に入るとかないんですが。
そもそも僕がこのXML読み込み関連に関わったのは今回が初めてだしね。
今来たばっかだから、このXML論争の歴史なんてさっぱりわからんわけですよ。
その割にはXDocument
を擁護する文面が多いんじゃないかって?
いや、それはたぶん気のせいだと思いますよ。
別にXmlDocument
が使えないとか言ってるわけではないので。
……案の定めんどくさいことになった。
僕はこの手の争い苦手なのに……。
よし、じゃあこの際だ。
この決着はバブルサッカーでつけようじゃないか。
えっ、バブルサッカー知らない?
透明のバランスボールみたいなのを装着して、サッカーするアレだよ。
あれなら思いっきりぶつかっても、ケガとかしにくいから安全だと思うよ。
えっ、僕はやんないのかって?
うん、僕は腰痛持ちだから遠慮しておくよ。
その代わりに審判をやるから心配しないで。
それじゃ、キックオフ!