using System.Linq;
using System.Dynamic;
using System.Xml.Linq;
namespace Ufcpp.Dynamic
{
///
/// XML を PowerShell の [xml] 並に動的簡単読み出しするための DynamicObject。
/// 今のところ読み取り専用。
///
public class DynamicXml : DynamicObject
{
XElement element;
///
/// XElement を与えて初期化。
///
/// 読み取り対象の XElement。
public DynamicXml(XElement element) { this.element = element; }
///
/// XDocument を与えて初期化。
/// ルート要素を読み出し。
///
/// 読み取り対象の XDocument。
public DynamicXml(XDocument doc) : this(doc.Root) { }
///
/// ファイルのパスを与えて初期化。
///
/// 読み取り対象の XML ファイル名。
public DynamicXml(string uri) : this(XDocument.Load(uri)) { }
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
var name = binder.Name;
// 属性値は _属性名 で取得。文字列として返す。
if (name.StartsWith("_"))
{
var attName = name.Substring(1);
result = element.Attribute(attName).Value;
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 DynamicXml(e);
return true;
}
// 要素が複数ある時はリストで要素一覧を返す。
var es = subElements.Select(x => new DynamicXml(x));
result = es.ToList();
return true;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
// string へのキャストで、要素の値を取得。
if (binder.Type == typeof(string))
{
result = element.Value;
return true;
}
// int へのキャストで int.Parse。
// Parse できないときは例外丸投げ。
if (binder.Type == typeof(int))
{
result = int.Parse(element.Value);
return true;
}
// 要素単体に対して foreach やっちゃったときでもエラーにならないように、IEnumerable へのキャストを定義。
// これやっとかないと、元々複数要素あったのに XML を修正して要素が1個だけになった時に挙動おかしくなる。
if (binder.Type == typeof(System.Collections.IEnumerable))
{
result = new[] { this };
return true;
}
result = null;
return false;
}
public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
{
switch (binder.Name)
{
case "GetEnumerator": // IEnumerable へのキャストと同様の理由。
result = new[] { this }.GetEnumerator();
return true;
case "All": // All() 呼び出しで、子要素を全部取得できるようにする。
result = element.Elements().Select(x => new DynamicXml(x)).ToList();
return true;
case "Name": // Name() で要素名を取得。
result = element.Name.ToString();
return true;
}
return base.TryInvokeMember(binder, args, out result);
}
public override string ToString()
{
return element.Value;
}
}
}