程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Code4Fun:通過XML模板替換實現對象的靈活序列化

Code4Fun:通過XML模板替換實現對象的靈活序列化

編輯:關於.NET

前陣子在寫LINQ2Douban的時候碰到關於XML序列化的場景。通過Douban api添加和更新數據的時候都需要Post一個xml entry,如:

添加活動(xml中用%%括起來的部分是需要填寫的部分,為了精簡刪除了xmlns)

01 <?xml version="1.0" encoding="UTF-8"?>
02 <entry>
03   <title>%title%</title>
04   <category scheme="http://www.douban.com/2007#kind" term="http://www.douban.com/2007#%Category%"/>
05   <content>%Content%</content>
06
07   <db:attribute name="invite_only">%IsInviteOnly%</db:attribute>
08   <db:attribute name="can_invite">%CanInvite%</db:attribute>
09   <gd:when endTime="%Duration.End%" startTime="%Duration.Start%"/>
10   <gd:where valueString="%Where%"/>
11 </entry>

一下子能想到的方法有兩個——通過XmlSerializer或在entity class上實現IXmlSerializable接口來實現,但很快發現有幾個問題沒法解決:

1、XmlSerializer不支持泛型集合的序列化,而我在定義entity class時用了不少IList和IDictionary,如db:attribute我就定義成IDictionary

2、XmlSerializer只能生成完整的element和attribute,像上面那段xml裡<category>節點term屬性裡變化的只有後面%Category%部分,這就沒法生成了

3、存在添加和更新的post內容不一樣的情況,這就意味著同一個entity class,存在多種序列化方案

4、douban的xml entry格式可能會更改,而我不希望因此而更改代碼

想來想去,最好的方法是通過XML模板+反射(簡稱XMl模板替換)來生成了,就像上面的xml etnry裡面%%括起來的部分,替換掉就可以了,這樣可以解決上述的四個問題。除了提供和XMLSerializer功能相同的序列化之外,XML模板替換還要滿足下面這些要求:

1、可以序列化實現IEnumerable的集合,這是最常用的集合,當然大多數的泛型集合也是應用了IEnumerable的

2、提供更靈活的替換。XmlSerializer實現的序列化順序是”A(B(c)B)A”,對於子對象的序列化只能是嵌套的模式,而XML模板替換可以實現任何層次的替換。

3、為每種類型的對象提供通用的序列化方式,不需要任何Attribute定義,不需要修改對象的定義。對於給定的object和XML模板,通過發射獲取屬性值進行XML替換後生成XML內容;對於相同的object,提供不同的XML模板就能生成不同的XML。

4、通過修改XML模板即可修改序列化結果

下面給出一個修改過的RSS的XML模板,這次Code4Fun的目的是在最後實現這個模板的替換,並且完成一個能夠實現上述功能的Helper class。

特別的地方:

1、<category>節點:通過”.”可訪問子對象的屬性,如果你希望獲取Domain的長度可以寫成”%Category.Domain.Length%”

2、<noReplacement>節點:該節點不包含任何替換信息,當進行替換處理時應當忽略

3、<skipHours>節點:SkipHours是一個List<int>集合,我們希望能夠根據SkipHours的值,展開多個<hour>節點

4、<as:scope>節點:<scope>是模板定義,聲明<scope>節點內包含的子節點在 Channel.Items對象的作用域中,所有%%(不包括%./Category.Name%)的屬性都是對Items對象的屬性訪問。由於此處 Items對象是List<RssItem>集合,所以將循環生成多個<item>。Scope的含義類似於程序域,支持多個 scope的嵌套,Scope定義不會出現在最後生成的xml中。

5、<channelCategory>節點:<channelCategory>節點在Items的作用域中,但我們可以通過”./”訪問外部scope的屬性,類似dos文件路徑,如果要訪問上上級scope,則是”././”。%./Category.Name%表示訪問Channel對象的Category屬性的Name屬性。

01 <channel>
02     <title>%Title%</title>
03     <link>%Link%</link>
04     <category domain="%Category.Domain%">%Category.Name%</category>
05     <noRelacement>不需要替換</noRelacement>
06     <skipHours>
07       <hour>%SkinHours%</hour>
08     </skipHours>
09     <as:scope xmlns:as="http://xml.allsharing.com/" name="Items" type="AllSharing.Xml.Rss.RssItem">
10       <item>
11         <title>%Title%</title>
12         <link>%Link%</link>
13         <description>%Description%</description>
14         <channelCategory>%./Category.Name%</channelCategory>
15       </item>
16     </as:scope>
17   </channel>

相關class定義:

01 public class RssChannel
02 {
03     public string Title { get; set; }
04         public string Link { get; set; }
05         public RssCategory Category { get; set; }
06         public IList<int> SkinHours { get; set; }
07         public IList<RssItem> Items { get; set; }
08 }
09  
10 public class RssItem
11 {
12         public string Title { get; set; }
13         public string Link { get; set; }
14 }
15
16 public class RssCategory
17 {
18     public string Domain { get; set; }
19         public string Name { get; set; }
20 }

下面將一步步討論如何實現XML模板的替換,會給出部分代碼或偽代碼,完整的代碼在文章最後會給出下載。

一、分析XML模板

XML模板替換最終要是要回歸到用正則表達式替換掉所有的%%,但由於我們要處理的模板包括域、子屬性訪問、循環的信息,所以這不是僅僅的 Regex.Replace就可以搞定的。分析XML模板,就是要遍歷XML模板生成一個Scope樹,上面的XML模板可以生成下面的Scope樹:

XML模板中包含了的三種我們需要處理的元素

1、包含%%的XML Attribute

2、包含%%的XML Element以及Element的子節點

3、Scope節點

從上面的Scope樹可以看出,像<noReplace>這樣不需要替換的XML element或XML attribute被當作常量,沒有包括在Scope樹中。

在Helper Class中分析Scope樹的方法:

1 var scope = XmlTemplateScope.Compile(xmlPath, entityType)

xmlPath是模板文件的路徑,entityType是Scope樹用於分析的對象類型

二、對給定的object,生成XML

這裡用了LINQ2XML,首先用XDocument.Load(xmlPath)的到XML模板文件的XDocument,然後根據Scope樹對XDocument上的節點進行屬性值替換、節點Value替換、增加節點(Repeat的節點)。幸運的是XDocument比 XmlDocument方便太多了,實現起來非常快。

在Helper Class中生成XML的方法:

1 var template = new XmlTemplate(xmlPath, scope);
2 Console.WriteLine(template.Translate(entityObj));

template是線程安全的,根據需要你可以Cache起來,不用每次都生成Scope樹,這樣或許會減少部分性能消耗(未測試)

完整的代碼如下(包含在Demo中):

01 var channel = new RssChannel();
02 channel.Title = "this is channel title";
03 channel.Link = "http://chwkai.cnblogs.com/";
04 channel.Description = "this is channel description";
05 channel.Category = new RssCategory();
06 channel.Category.Domain = "http://chwkai.cnblogs.com/";
07 channel.Category.Name = "this is channel category";
08 channel.SkipHours.Add(1);
09 channel.SkipHours.Add(2);
10 channel.SkipHours.Add(3);
11 channel.Items.Add(new RssItem { Title="Item1", Link="Link1", Description="Des1" });
12 channel.Items.Add(new RssItem { Title = "Item2", Link = "Link2", Description = "Des2" });
13 channel.Items.Add(new RssItem { Title = "Item3", Link = "Link3", Description = "Des3" });
14
15 var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rss.xml");
16 var template = new XmlTemplate(
17     path, XmlTemplateScope.Compile(path, typeof(RssChannel)));
18 template.Translate(channel).Dump();

生成XML如下:

01   <channel>
02     <title>this is channel title</title>
03     <link>http://chwkai.cnblogs.com/</link>
04     <category domain="http://chwkai.cnblogs.com/">this is channel category</cate
05 gory>
06     <noRelacement>不需要替換</noRelacement>
07     <skipHours>
08       <hour>1</hour>
09       <hour>2</hour>
10       <hour>3</hour>
11     </skipHours>
12     <item>
13       <title>Item1</title>
14       <link>Link1</link>
15       <description>Des1</description>
16       <channelCategory>this is channel category</channelCategory>
17     </item>
18     <item>
19       <title>Item2</title>
20       <link>Link2</link>
21       <description>Des2</description>
22       <channelCategory>this is channel category</channelCategory>
23     </item>
24     <item>
25       <title>Item3</title>
26       <link>Link3</link>
27       <description>Des3</description>
28       <channelCategory>this is channel category</channelCategory>
29     </item>
30   </channel>

總結:本文給出了另外一種XML序列化的方法,通過XmlTemplate,你可以更簡單更靈活的生成XML文檔,這在生成RSS, Atom等文檔,以及開發WebAPI的client的時候都是非常方便的。另外,本文給出的方法同樣適用於基於XML模板生成Html文件。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved