(C#) シリアライズ可能なオブジェクトを階層化して表示
概要
前々回と前回(以下)からの続き。
シリアライズ可能なオブジェクトをXML文字列にしたり、さらにそれをXDocumentを使って解析して、見やすい形で表示したりしてみた。
バージョン
.NET Framework | 4.7.2 |
C# | 4.7 |
目次
ソースコード
StringBuilder拡張クラス
表示用に拡張メソッド用意しただけ。今回の本題とはあまり関係ない。ほぼ前回からの流用。
using System; using System.Globalization; using System.Text; namespace ObjectToXmlString { /// <summary> /// StringBuilder拡張クラス /// </summary> public static class StringBuilderExtensions { /// <summary> /// 書式指定文字列追加 /// 改行付き /// </summary> /// <param name="stringBuilder">StringBuilder</param> /// <param name="format">書式</param> /// <param name="args">可変長引数</param> public static void AppendFormatLine(this StringBuilder stringBuilder, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}", string.Format(format, args), Environment.NewLine); } /// <summary> /// 書式指定文字列追加 /// 改行および先頭空白インデント付き /// </summary> /// <param name="stringBuilder">StringBuilder</param> /// <param name="indent">インデント</param> /// <param name="format">書式</param> /// <param name="args">可変長引数</param> public static void AppendFormatLine(this StringBuilder stringBuilder, int indent, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}{2}", new string(' ', indent), string.Format(format, args), Environment.NewLine); } } }
XML文字列化クラス
オブジェクトをXML文字列にするだけのクラス。前回からの流用。
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="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStirng(object obj, bool enableException = 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 (enableException) { throw; } } return xmlString; } } }
加工して表示するクラス
今回の本題。ただし説明するより実行結果見た方が早そうなので説明なし。
using System; using System.Linq; using System.Text; using System.Xml.Linq; using System.Collections.Generic; namespace ObjectToXmlString { /// <summary> /// ダンプヘルパー /// </summary> public static class DumpHelper { /// <summary> /// XML文字列生成(軽量版) /// XML宣言とルート要素のXMLNS名前空間の属性を削除したXML文字列化 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStirngSlim(object obj, bool enableException = false) { var xmlString = string.Empty; try { XDocument xDoc = XDocument.Parse( XmlStringHelper.GenerateXmlStirng(obj, enableException)); xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); xmlString = xDoc.ToString(); } catch { if (enableException) { throw; } } return xmlString; } /// <summary> /// XML文字列生成(1行版) /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="newLineReplacingString">改行置換文字列</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStringOneLine(object obj, string newLineReplacingString, bool enableException = false) { var xmlString = string.Empty; try { xmlString = GenerateXmlStirngSlim(obj, enableException) .Replace(Environment.NewLine, newLineReplacingString); } catch { if (enableException) { throw; } } return xmlString; } /// <summary> /// 階層化文字列生成 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="enableException">例外送出有無</param> /// <returns>階層化文字列</returns> public static string GenerateLayeredString(object obj, bool enableException = false) { var sb = new StringBuilder(); try { var xmlString = GenerateXmlStirngSlim(obj, enableException); XDocument xDoc = XDocument.Parse(xmlString); // 階層化表示用の文字列を生成 foreach (var item in xDoc.Elements()) { sb.Append(GenerateLayeredStringOfElement(0, item)); } } catch { if (enableException) { throw; } sb.Clear(); } return sb.ToString(); } /// <summary> /// XML要素の階層化文字列生成 /// 再帰呼び出し用 /// </summary> /// <param name="indent">インデント</param> /// <param name="xElm">XML要素</param> /// <param name="xElmIndex">XML要素の配列インデックス</param> /// <returns階層化文字列</returns> private static string GenerateLayeredStringOfElement(int indent, XElement xElm, int xElmIndex = -1) { var sb = new StringBuilder(); // 表示用書式の生成 // 配下のXML属性およびXML要素について最長のローカル名長を取得し // "key=value"のうちkey部分の桁数合わせに使用 var elementsNoChilds = xElm.Elements().Where(e => !e.HasAttributes && !e.HasElements); var maxLocalNameLength = new[] { xElm.HasAttributes ? xElm.Attributes().Max(a => a.Name.LocalName.Length) : 0, elementsNoChilds.Any() ? elementsNoChilds.Max(e => e.Name.LocalName.Length) : 0, }.Max(); var keyValueFormat = $"{{0,-{maxLocalNameLength}}} = {{1}}"; // 配下にXML属性あるいはXML要素をもつ場合、自身のローカル名を表示 if (xElm.HasAttributes || xElm.HasElements) { // 自身が配列の構成要素の場合、末尾にインデックスを付与 if (xElmIndex >= 0) { sb.AppendFormatLine(indent, "{0}[{1}]", xElm.Name.LocalName, xElmIndex); } else { sb.AppendFormatLine(indent, xElm.Name.LocalName); } } // XML属性の表示 foreach (var item in xElm.Attributes()) { sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value); } // 配下のXML要素が配列か判定 var areChildArray = (xElm.Elements().Count() > 1 && xElm.Elements().All(e => e.Name == xElm.Elements().First().Name)); // XML要素の表示 foreach (var (item, index) in xElm.Elements().Select((item, index) => (item, index))) { // 配下にXML属性もXML要素も持たない場合、"key=value"形式で表示 if (!item.HasAttributes && !item.HasElements) { sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value); continue; } // 配下のXML要素が配列の場合、配列インデックスを渡す if (areChildArray) { sb.Append(GenerateLayeredStringOfElement(indent + 2, item, index)); } else { sb.Append(GenerateLayeredStringOfElement(indent + 2, item)); } } 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 enum BoxItemType { Undefined, Fruit, Fish } public class BoxItem { [XmlAttribute] public BoxItemType BoxItemType { get; set; } 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(){ BoxItemType = BoxItemType.Fruit, Name = "Orange", Count = 1 }, new BoxItem(){ BoxItemType = BoxItemType.Fish, Name = "Tuna", Count = 2 }, new BoxItem(){ BoxItemType = BoxItemType.Undefined, Name = null, Count = 0 }, }; } // // 実行コード // class Program { static void Main(string[] args) { Box boxA = new Box(); // // XML文字列 // WriteResult("XML文字列", XmlStringHelper.GenerateXmlStirng(boxA)); // // 加工して表示 // WriteResult("XML文字列(軽量版)", DumpHelper.GenerateXmlStirngSlim(boxA)); WriteResult("XML文字列生成(1行版)", DumpHelper.GenerateXmlStringOneLine(boxA, "#")); WriteResult("階層化文字列", DumpHelper.GenerateLayeredString(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 BoxItemType="Fruit"> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </Box> -------------------------------- XML文字列(軽量版) -------------------------------- <Box Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </Box> -------------------------------- XML文字列生成(1行版) -------------------------------- <Box Name="box a"># <Volume Value="10" Unit="L" /># <Items># <BoxItem BoxItemType="Fruit"># <Name>Orange</Name># <Count>1</Count># </BoxItem># <BoxItem BoxItemType="Fish"># <Name>Tuna</Name># <Count>2</Count># </BoxItem># <BoxItem BoxItemType="Undefined"># <Count>0</Count># </BoxItem># </Items>#</Box> -------------------------------- 階層化文字列 -------------------------------- Box Name = box a Volume Value = 10 Unit = L Items BoxItem[0] BoxItemType = Fruit Name = Orange Count = 1 BoxItem[1] BoxItemType = Fish Name = Tuna Count = 2 BoxItem[2] BoxItemType = Undefined Count = 0
所感
XDocumentを使ってみるついでに見やすい階層化表示なんてやってみたけど、試験不十分なので、下手に表示されないリスク負うくらいなら、素直にXML文字列でもいいような気もする。でも夏休みの暇つぶしにはちょうどよかった。