.NET4.0の新機能 dynamic を使用したXMLの動的読み込みクラス
修正したものは 「C# 4.0 dynamic による動的XML読み書きクラス」を参照
修正項目
・UTF-8の標準的なXML
・XML宣言の強制的な文字コード変換をコメント化
ほかバグをいくつか修正
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
色々と使いにくい点があるので、時間があれば修正予定
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Dynamic; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using System.IO; namespace RLib.Common { /// <summary> /// 文字コードが指定可能な StringWriter クラス。 /// </summary> public class MultiStringWriter : StringWriter { /// <summary> /// 文字コード /// </summary> private Encoding _encoding = Encoding.UTF8; /// <summary> /// 指定した文字コードと StringBuilder を元に MultiStringWriter のインスタンスを作成する。 /// </summary> /// <param name="sb"></param> /// <param name="enc"></param> public MultiStringWriter(StringBuilder sb, Encoding enc) : base(sb) { this._encoding = enc; } /// <summary> /// エンコードの取得 /// </summary> public override Encoding Encoding { get { return this._encoding; } } } /// <summary> /// XElementの動的オブジェクト /// </summary> public class DElement : DynamicObject { /// <summary> /// XMLドキュメント /// </summary> private XElement element; /// <summary> /// CData オブジェクト /// </summary> private XCData cdata; /// <summary> /// XPath時の名前空間 /// </summary> internal XmlNamespaceManager Namespace { get; set; } #region 初期化 (XElement) /// <summary> /// DElement を与えて初期化。 /// </summary> /// <param name="element">読み取り対象の XElement。</param> public DElement(DElement element) { this.element = element.element; } /// <summary> /// XElement を与えて初期化。 /// </summary> /// <param name="element">読み取り対象の XElement。</param> public DElement(XElement element) { this.element = element; } /// <summary> /// XDocument を与えて初期化。 /// ルート要素を読み出し。 /// </summary> /// <param name="doc">読み取り対象の XDocument。</param> public DElement(XDocument doc) : this(doc.Root) { } /// <summary> /// ファイルのパスを与えて初期化。 /// </summary> /// <param name="uri">読み取り対象の XML ファイル名。</param> public DElement(XmlReader reader) : this(XDocument.Load(reader)) { } /// <summary> /// ファイルのパスを与えて初期化。 /// </summary> /// <param name="uri">読み取り対象の XML ファイル名。</param> public DElement(string uri) : this(XDocument.Load(uri)) { } /// <summary> /// XElement、XmlNamespacemanager を与えて初期化。 /// </summary> /// <param name="element">読み取り対象の XElement。</param> private DElement(XElement element, XmlNamespaceManager ns) { this.element = element; this.Namespace = ns; } /// <summary> /// XML文字列を与えて初期化されたインスタンスを受け取る。 /// </summary> /// <param name="xml">XML構造の文字列</param> /// <returns>DElement オブジェクト</returns> public static DElement Create(string xml) { return Create(xml, null); } /// <summary> /// XML文字列を与えて初期化されたインスタンスを受け取る。 /// </summary> /// <param name="xml">XML構造の文字列</param> /// <param name="ns">名前空間オブジェクト</param> /// <returns>DElement オブジェクト</returns> public static DElement Create(string xml, XmlNamespaceManager ns) { DElement doc = null; //using (var sr = new StringReader(xml)) //{ // doc = new DElement(XDocument.Load(sr)); // doc.Namespace = ns; //} //if(xml.IndexOf var xdoc = XDocument.Parse(xml); xdoc.Declaration.Encoding = "utf-8"; doc = new DElement(xdoc); doc.Namespace = ns; return doc; } #endregion #region 初期化 (XCdata) /// <summary> /// XCData を与えて初期化。 /// </summary> /// <param name="cdata">読み取り対象の XCData</param> public DElement(XCData cdata) { this.cdata = cdata; } #endregion #region 動的メソッド(取得) /// <summary> /// 動的Getプロパティ /// </summary> /// <param name="binder">動的実行時情報</param> /// <param name="result">返却する値</param> /// <returns>動的実行を成功させる場合は true。それ以外は false。</returns> public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result) { var name = binder.Name; // InnerText的なもの if (name.StartsWith("__Value")) { if (this.element != null) result = this.element.Value; else result = this.cdata.Value; return true; } // XElementのオブジェクトが存在しない場合は処理を中断する。 if (this.element == null) { result = null; return true; } // 属性値は _属性名 で取得。文字列として返す。 if (name.StartsWith("_")) { var attName = name.Substring(1); var att = element.Attribute(attName); if (att != null) { result = att.Value; return true; } result = (string)null; return true; } var subElements = element.Elements(name).ToList(); // 要素がないときは null 返す。 if (subElements.Count == 0) { result = (string)null; return true; } // 要素が1個だけの時は素直にその要素を返す。 if (subElements.Count == 1) { var e = subElements[0]; result = new DElement(e, this.Namespace); return true; } // 要素が複数ある時はリストで要素一覧を返す。 var es = subElements.Select(x => new DElement(x, this.Namespace)); result = es.ToList(); return true; } /// <summary> /// 動的キャスト /// </summary> /// <param name="binder">動的実行時情報</param> /// <param name="result">返却する値</param> /// <returns>動的実行を成功させる場合は true。それ以外は false。</returns> public override bool TryConvert(ConvertBinder binder, out object result) { // string へのキャストで、要素の値を取得。 if (binder.Type == typeof(string)) { if (this.element != null) result = element.Value; else result = cdata.Value; return true; } // int へのキャストで int.Parse。 // Parse できないときは例外丸投げ。 if (binder.Type == typeof(int)) { if (this.element != null) result = int.Parse(element.Value); else result = int.Parse(cdata.Value); return true; } // 要素単体に対して foreach やっちゃったときでもエラーにならないように、IEnumerable へのキャストを定義。 // これやっとかないと、元々複数要素あったのに XML を修正して要素が1個だけになった時に挙動おかしくなる。 if (binder.Type == typeof(System.Collections.IEnumerable)) { result = new[] { this }; return true; } result = null; return false; } /// <summary> /// 動的メソッドの実行 /// </summary> /// <param name="binder">動的実行時情報</param> /// <param name="args">メソッドに渡す値</param> /// <param name="result">返却する値</param> /// <returns>動的実行を成功させる場合は true。それ以外は false。</returns> public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result) { switch (binder.Name) { case "CData": result = this.CData(); return true; case "Name": // Name() で要素名を取得。 result = element.Name.ToString(); return true; case "GetEnumerator": // IEnumerable へのキャストと同様の理由。 result = new[] { this }.GetEnumerator(); return true; case "All": // All() 呼び出しで、子要素を全部取得できるようにする。 result = element.Elements().Select(x => new DElement(x, this.Namespace)).ToList(); return true; case "XPath": // XPath(string, string) で要素を取得出来るようにする。 if (this.XPath(args, out result)) return true; break; } return base.TryInvokeMember(binder, args, out result); } #region TryInvokeMember - 元関数 /// <summary> /// CDataセクションのXMLタグオブジェクトを取得する。 /// </summary> /// <returns>XMLタグオブジェクト</returns> private DElement CData() { if (this.element.Nodes().Count<XNode>() == 1) { var node = this.element.FirstNode; if (node.NodeType == XmlNodeType.CDATA) { return new DElement((XCData)node); } } return this; } /// <summary> /// カレント XML に対し、XPath を実行する。 /// </summary> /// <param name="args">XPath</param> /// <param name="result">XPath結果</param> /// <returns>XPathが成功した場合は true。それ以外は false。</returns> private bool XPath(object[] args, out object result) { result = null; if (args.Length == 1) { IEnumerable<XElement> elements = null; try { if (this.Namespace != null) { elements = element.XPathSelectElements(Convert.ToString(args[0]), this.Namespace); } else { elements = element.XPathSelectElements(Convert.ToString(args[0])); } } catch { System.Diagnostics.Trace.WriteLine("XPathエラー"); return true; } // 要素の変換 if (elements != null && 0 < elements.Count()) { if (1 <= elements.Count()) { var list = elements .Select(x => new DElement(x, this.Namespace)) .Select(x => x.CData()) .ToList(); if (list.Count() == 1) result = list[0]; else result = list; } return true; } return true; } // 引数が一致しない場合はXPathを実行しない return false; } #endregion #endregion #region 動的メソッド(設定) /// <summary> /// 動的Setプロパティ /// </summary> /// <param name="binder">動的実行時情報</param> /// <param name="value">設定する値</param> /// <returns>動的実行を成功させる場合は true。それ以外は false。</returns> public override bool TrySetMember(SetMemberBinder binder, object value) { var name = binder.Name; // InnerText的なもの if (name.StartsWith("__Value")) { if (value.GetType() == typeof(string)) { if(this.element != null) this.element.Value = Convert.ToString(value); else this.cdata.Value = Convert.ToString(value); return true; } return false; } // XElementのオブジェクトが存在しない場合は処理を中断する。 if (this.element == null) { return false; } // 属性値は _属性名 で取得。文字列として返す。 if (name.StartsWith("_")) { var attName = name.Substring(1); var att = this.element.Attribute(attName); if (att != null) { att.Value = Convert.ToString(value); return true; } return false; } // 要素数のチェック var subElements = element.Elements(name).ToList(); if (subElements.Count == 0) return false; // 代入する値が null の場合は要素を削除する。 if (value == null) { subElements.RemoveAll(x => true); } // 要素数が1の場合 if (subElements.Count == 1) { // DElement または XElement オブジェクトが渡された場合は、子要素と置き換える。 if (value.GetType() == typeof(DElement)) { value = ((DElement)value).element; } if (value.GetType() == typeof(XElement)) { subElements[0].ReplaceAll(value); return true; } // 他の型はそのまま突っ込む subElements[0].ReplaceAll(value); return true; } // 要素数が複数ある場合は代入を禁止する。 return false; } #endregion #region 非動的メソッド /// <summary> /// XMLドキュメントを保存する。 /// </summary> /// <param name="writer">保存用Stream</param> public void Save(XmlWriter writer) { if(this.element != null) this.element.Save(writer); } /// <summary> /// XMLドキュメントを保存する。 /// </summary> /// <param name="path">保存先パス</param> public void Save(string path) { if (this.element != null) this.element.Save(path); } #endregion #region override メソッド /// <summary> /// 現在参照している Xmlタグ を文字列として受け取る。 /// </summary> /// <returns>文字列</returns> public override string ToString() { if (this.cdata != null) return this.cdata.ToString(); var sb = new StringBuilder(); using (var stringWrite = new MultiStringWriter(sb, Encoding.UTF8)) using (var xw = XmlWriter.Create(stringWrite)) { this.element.Save(xw); } return sb.ToString(); } #endregion } }
-
-
- -
-