(C#) XDocumentと戯れる
概要
前回の記事((C#) オブジェクトをXML文字列にする - scrap book)の続き。
作業中にXDocumentというものを見つけて、ざっと調べたら、簡単にXMLの操作や参照ができるクラスらしかったので、使ってみる。試したことは以下の目次参照。
バージョン
.NET Framework | 4.7.2 |
C# | 4.7 |
目次
ソースコード
XML文字列化クラス(前回の流用)
オブジェクトをXML文字列にするだけのクラス。
using System; using System.IO; using System.Xml.Serialization; namespace ObjectToXmlString { /// <summary> /// XML文字列ヘルパー /// </summary> public static class XmlStringHelper { /// <summary> /// XML文字列化 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="throwException">例外送出状態</param> /// <returns>XML文字列</returns> public static string ConvertToXmlStirng(object obj, bool throwException = false) { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, obj); xmlString = writer.ToString(); } } catch { if (throwException) { throw; } } return xmlString; } } }
StringBuilder拡張クラス
StringBuilderに用意されているメソッドが「書式指定付きで追加」か「改行付きで追加」しか無いので、両方付きのメソッドを用意しただけ。今回の主題とは何も関係ない。
using System; using System.Globalization; using System.Text; namespace ObjectToXmlString { public static class StringBuilderExtensions { public static void AppendFormatLine(this StringBuilder stringBuilder, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}", string.Format(format, args), Environment.NewLine); } } }
XDocumentお試しコード1~4
ここからが本題。
お試し1 .. XML文字列 から XDocumentインスタンス生成
まずはXML文字列からXDocumentの生成。XDocument#Parse()で可能だった。このほかにも、XDocument#Load()でファイルから読み込めたりする。
ついでにそのまま XDocument.ToString() して分かった((2)の部分)けど、 ToString() ではXML本文のみが出力されるみたい。XML宣言部は (1) で出力できる。
using System.Linq; using System.Text; using System.Xml.Linq; namespace ObjectToXmlString { public static class XDocumentTrial { /// <summary> /// XML文字列 から XDocumentインスタンス生成 /// &文字列出力 /// </summary> public static string Trial1(object obj) { var sb = new StringBuilder(); // オブジェクトをXML文字列化 var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); // XML文字列を解析 XDocument xDoc = XDocument.Parse(xmlString); // (1) sb.AppendLine("[ Declaration ]"); sb.AppendLine(xDoc.Declaration.ToString()); sb.AppendLine(); // (2) sb.AppendLine("[ ToString() ]"); sb.AppendLine(xDoc.ToString()); return sb.ToString(); } } }
お試し2 .. 属性(Attribute)や要素(Element)の表示
属性や要素の表示を試したコード。それぞれXElement.Attributes()、XElement.Elements()でコレクションとして取得できた。ほか、XElement.Annotations()で注釈(コメントのこと?)も取得できそう。
ちなみに再帰的に辿らずにRoot要素にだけ使ってみたら、リストの部分は値が繋がってしまった。これについてはお試し4でうまく表示できた。
/// <summary> /// 属性(Attribute)や要素(Element)の表示 /// </summary> public static string Trial2(object obj) { var sb = new StringBuilder(); var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); XDocument xDoc = XDocument.Parse(xmlString); // (1) 属性の出力 foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("Attributes[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); } sb.AppendLine(); // (2) 要素の出力 foreach (var (item, index) in xDoc.Root.Elements().Select((item, index) => (item, index))) { sb.AppendFormatLine("Elements[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); sb.AppendFormatLine(" HasAttributes = {0}", item.HasAttributes); sb.AppendFormatLine(" HasElements = {0}", item.HasElements); } return sb.ToString(); }
お試し3 .. 属性の削除
出力結果を見ていて、xmlns名前空間の属性が邪魔だったので、消してみた。(1)の部分。
/// <summary> /// xmlns名前空間の属性を削除 /// </summary> public static string Trial3(object obj) { var sb = new StringBuilder(); var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); XDocument xDoc = XDocument.Parse(xmlString); // (1) 名前空間が"xmlns"の属性をすべて削除 xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); // 属性の出力 foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("Attributes[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); } return sb.ToString(); }
お試し4 .. 再帰的に走査して階層化表示
属性を再帰的に走査して、いい感じに階層化して表示してみた。十分なパターンで試験していないけど、おおむねいい感じ。
/// <summary> /// 再帰的に走査して階層化表示 /// </summary> public static string Trial4(object obj, bool throwException = false) { var sb = new StringBuilder(); try { var xmlString = XmlStringHelper.ConvertToXmlStirng(obj, throwException); XDocument xDoc = XDocument.Parse(xmlString); // 名前空間が"xmlns"の属性をすべて削除 xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); // 再帰的に走査して階層化表示 foreach (var item in xDoc.Elements()) { sb.Append(Trial4Core(item)); } } catch { if (throwException) { throw; } sb.Clear(); } return sb.ToString(); } private static string Trial4Core(XElement xElement, int offset = 0) { var sb = new StringBuilder(); var spaces = new string(' ', offset); // Append Attributes foreach (var (item, index) in xElement.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("{0}Attributes[{1}]", spaces, index); sb.AppendFormatLine("{0} Name.LocalName = {1}", spaces, item.Name.LocalName); sb.AppendFormatLine("{0} Value = {1}", spaces, item.Value); } // Append Elements foreach (var (item, index) in xElement.Elements().Select((item, index) => (item, index))) { // Append Attributes and Elements recursively if (item.HasAttributes || item.HasElements) { sb.AppendFormatLine("{0}{1}", spaces, item.Name.LocalName); sb.Append(Trial4Core(item, offset + 2)); continue; } sb.AppendFormatLine("{0}Elements[{1}]", spaces, index); sb.AppendFormatLine("{0} Name.LocalName = {1}", spaces, item.Name.LocalName); sb.AppendFormatLine("{0} Value = {1}", spaces, item.Value); } return sb.ToString(); }
呼び出し側コード
using System; using System.Xml.Serialization; using System.Collections.Generic; namespace ObjectToXmlString { // // 文字列化対象 // [Serializable] public class Box { public class BoxVolume { [XmlAttribute] public int Value { get; set; } = 10; [XmlAttribute] public string Unit { get; set; } = "L"; } public class BoxItem { public string Name { get; set; } public int Count { get; set; } } [XmlAttribute] public string Name { get; set; } = "box a"; public BoxVolume Volume { get; set; } = new BoxVolume(); public List<BoxItem> Items { get; } = new List<BoxItem>() { new BoxItem(){ Name = "Orange", Count = 1 }, new BoxItem(){ Name = "Apple", Count = 2 }, new BoxItem(){ Name = null, Count = 0 }, }; } // // 実行コード // class Program { static void Main(string[] args) { Box boxA = new Box(); // // XML文字列 // WriteResult("XML文字列", XmlStringHelper.ConvertToXmlStirng(boxA)); // // XDocumentお試し // WriteResult("Trial1", XDocumentTrial.Trial1(boxA)); WriteResult("Trial2", XDocumentTrial.Trial2(boxA)); WriteResult("Trial3", XDocumentTrial.Trial3(boxA)); WriteResult("Trial4", XDocumentTrial.Trial4(boxA)); Console.ReadKey(); } static void WriteResult(string title, string result) { Console.WriteLine("----------------"); Console.WriteLine($"{title}"); Console.WriteLine("----------------"); Console.WriteLine($"{result}"); Console.WriteLine(); } } }
実行結果
---------------- XML文字列 ---------------- <?xml version="1.0" encoding="utf-16"?> <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem> <Name>Apple</Name> <Count>2</Count> </BoxItem> <BoxItem> <Count>0</Count> </BoxItem> </Items> </Box> ---------------- Trial1 ---------------- [ Declaration ] <?xml version="1.0" encoding="utf-16"?> [ ToString() ] <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem> <Name>Apple</Name> <Count>2</Count> </BoxItem> <BoxItem> <Count>0</Count> </BoxItem> </Items> </Box> ---------------- Trial2 ---------------- Attributes[0] Name.NamespaceName = http://www.w3.org/2000/xmlns/ Name.LocalName = xsi Value = http://www.w3.org/2001/XMLSchema-instance Attributes[1] Name.NamespaceName = http://www.w3.org/2000/xmlns/ Name.LocalName = xsd Value = http://www.w3.org/2001/XMLSchema Attributes[2] Name.NamespaceName = Name.LocalName = Name Value = box a Elements[0] Name.NamespaceName = Name.LocalName = Volume Value = HasAttributes = True HasElements = False Elements[1] Name.NamespaceName = Name.LocalName = Items Value = Orange1Apple20 HasAttributes = False HasElements = True ---------------- Trial3 ---------------- Attributes[0] Name.NamespaceName = Name.LocalName = Name Value = box a ---------------- Trial4 ---------------- Attributes[0] Name.LocalName = Name Value = box a Volume Attributes[0] Name.LocalName = Value Value = 10 Attributes[1] Name.LocalName = Unit Value = L Items BoxItem Elements[0] Name.LocalName = Name Value = Orange Elements[1] Name.LocalName = Count Value = 1 BoxItem Elements[0] Name.LocalName = Name Value = Apple Elements[1] Name.LocalName = Count Value = 2 BoxItem Elements[0] Name.LocalName = Count Value = 0
解説?は上述したので割愛。
ところでBoxItem[2].Nameにnull入れてみたのだけど表示されない(XML文字列の時点で無い)。nullならnullと出て欲しいんだけどどうにかならないかな。。たしかNullable<int>やら自前の型なら<Name value=nil />みたいな文字列になったと思うんだけどなぁ。。記憶違いかな。。
参考(その他)
他の方法。XDocumentが使いやすそうだったので今回はパスしたけどメモ。