DynamicXml -- 動態讀取操作XML (一個從XML到Object的通用實現)

最近的一個項目用到很多不同結構的XML文件. 於是就在網上搜索了一些文章, 結合實際遇到的問題寫成自己要的代碼.

既然已經獲取了這方面的知識,不敢獨取, 拿出來共享. 這個也還不是很成熟, 希望大家共同完善, 提出寶貴意見, 共同進步.

目標

基於已經有的XML文件,例如:

<root>
<books>
<book>
<author>John Savacki</author>
<title>E.G.Title</title>
<price>20.50</price>
</book>
<book>
<author>Tom Curly</author>
<title>E.G.Title 2</title>
<price>26.50</price>
</book>
</books>
</root>
或者是類似任意結構的XML文件, 可以實現初始化
dynamic dx = new DynamicXml(xml);

並且讀取屬性值:

dx.books.book[0].author, dx.books.book[2].price

解決方案

自然是選擇C# 4.0 中引入的 DynamicObject, 關鍵是已有的.net框架中不能提供這樣的功能.

那麼就從這個類中繼承生成子類, 同時也需要實現 IEnumerable.

稍微介紹一下:

  • DynamicObject 類使您能夠定義可以動態對象上執行哪些操作以及如何執行這些操作。

如果只需要設置和獲取屬性的操作,您可以只覆蓋 TrySetMember 和 TryGetMember 方法。

 

  • IEnumerable 公開枚舉器,該枚舉器支持在非泛型集合上進行簡單迭代。

應該大家都知道要實現自定義集合的IEnumerable 就要完成

public IEnumerator GetEnumerator()

也就是說, 爲了實現需求, 至少我們需要完成三個函數:

TrySetMember, TryGetMember, GetEnumerator

 

實現

通過上面的分析, 我們知道這個類看起來應該是這樣:

public class DynamicXml : DynamicObject, IEnumerable 
{

public DynamicXml(string text) 

}

  public override bool TryGetMember(GetMemberBinder binder, out object result) 
  { 
}

  public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) 
  { 
  }

  public IEnumerator GetEnumerator() 
  { 
  }

}

 

在構造函數裏我們希望可以讀取XML的內容, 加載所有元素的值.

在TrySetMember中, 我們爲設置成員值的操作提供實現, 以便爲設置屬性值指定動態行爲。在本文中, 我們只考慮讀取XML內容, 所以不需要重寫這個函數.

在TryGetMember中, 我們要爲獲取成員值的操作提供實現。

在 TryGetIndex中,  我們要爲按索引獲取值的操作提供實現.

 

實現的代碼如下:

 


public class DynamicXml : DynamicObject, IEnumerable
{
private readonly List<XElement> _elements;

public DynamicXml(string text)
{
var doc = XDocument.Parse(text);
_elements = new List<XElement> { doc.Root };
}

protected DynamicXml(XElement element)
{
_elements = new List<XElement> { element };
}

protected DynamicXml(IEnumerable<XElement> elements)
{
_elements = new List<XElement>(elements);
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
if (binder.Name == "Value")
result = _elements[0].Value;
else if (binder.Name == "Count")
result = _elements.Count;
else
{
var attr = _elements[0].Attribute(
XName.Get(binder.Name));
if (attr != null)
result = attr;
else
{
var items = _elements.Descendants(
XName.Get(binder.Name));
if (items == null || items.Count() == 0)
return false;
result = new DynamicXml(items);
}
}
return true;
}

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
int ndx = (int)indexes[0];
result = new DynamicXml(_elements[ndx]);
return true;
}

public IEnumerator GetEnumerator()
{
foreach (var element in _elements)
yield return new DynamicXml(element);
}
}

運用

在具體運用中, 有些地方是非常有趣的, 因爲我們有了dynamic, 所以你可以用同一個變量去獲取不同結構的XML文件. 運用代碼如下:

 


static void Main(string[] args)
{
string xml = @"<root>
<books>
<book>
<author>John Savacki</author>
<title>E.G.Title</title>
<price>20.50</price>
</book>
<book>
<author>Tom Curly</author>
<title>E.G.Title 2</title>
<price>26.50</price>
</book>
</books>
</root>"
;
dynamic dx = new DynamicXml(xml);
Console.WriteLine("----- Book List -----");
foreach (dynamic b in dx.books.book)
{
Console.WriteLine("author='{0}'", b.author.Value);
print(b);
}
Console.WriteLine("------ Book List End ------");

string xml2 = @"<root>
<products>
<product>
<title>iPhone 4</title>
<price>2222.50</price>
<quantity>10</quantity>
</product>
<product>
<title>Lenovo IdeaPad</title>
<price>5432.50</price>
<quantity>15</quantity>
</product>
</products>
</root>"
;

dx = new DynamicXml(xml2);

Console.WriteLine("----- Product List -----");
foreach (dynamic b in dx.products.product)
{
Console.WriteLine("quantity='{0}'", b.quantity.Value);
print(b);
}
Console.WriteLine("------ Product List End ------");


Console.Read();
}
 
我把print函數專門拿出來, 故弄玄虛一下, 因爲兩個XML的結構有類似的地方, 所以可以同時使用下面的函數. -- 當然對於這樣的情況,我們也可以爲此定義一個interface.
 

static void print(dynamic b)
{
Console.WriteLine("Title='{0}'", b.title.Value);
Console.WriteLine("price='{0}'", b.price.Value);
}

不是什麼很深奧的東西, 但是希望可以爲有同樣問題的朋友們節省一些時間.

如果可以較好的掌握Dynamic, 可以節省一些反射方面的代碼. 不過這是另外一個話題了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章