一、前言
可擴展標記語言 (XML) 是具有結構性的標記語言,可以用來標記數據、定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言。XML是用來存儲數據的,重在數據本身。本文中的代碼是幾個月前整理的,最近幾個月的時間很少寫隨筆,除了工作以外,主要還是忙於整理自己的框架。這篇隨筆主要是針對於XML的特性Attribute與實體之間的匹配與轉換,其中的內容包括反射、XML以及LinqToXml,代碼的內容也是想到什么就寫什么,純屬練習下手感,僅供參考。下一篇隨筆將以另外的形式來轉換Xml為對象實體,當然,也是以反射為主,和本隨筆中的思路差不多,主要是XML的格式和解決方案不同而已。對於如何將對象轉換成Xml的話,主要還是看情況,僅僅轉換簡單的對象的話,直接反射就可以生成。對於復雜的對象的話,可以采用dynamic,樹結構和遞歸算法的方案來定制XML。
二、類圖設計
該處的主要思路為:通過反射將與類名(類特性名稱或者類名)的節點匹配,然后匹配屬性(屬性特性名稱或者屬性名稱)值。反之,則遍歷實體對象的屬性,設置相應的XML。本來還想細化一下匹配與轉換操作的,但是該類的EA文件不知道放在哪里了,只有設計的截圖還在,XO。相關類圖設計如下:
PropertyAttribute主要設置屬性的特性名稱,用於轉換過程設置屬性的別名,同時匹配過程中匹配XML的特性與屬性的名稱。
ClassAttribute主要設置類的特性名稱,用於轉換過程設置類的別名,同時匹配過程中匹配XML的節點與類的名稱。
StringExtension主要是字符串的擴展方法。
FuncDictionary主要轉換字符串為特定類型的值。
三、具體實現
先看下FuncDictionary,該類主要通過異步委托將字符串轉換成相應的簡單類型,所有實現圍繞該類進行相關操作。FuncDictionary基本涵蓋了C#中的所有簡單類型,可以將字符串轉換成這些簡單類型。
2 {
3 public static IDictionary<Type, Delegate> Dictionary
4 {
5 get;
6 private set;
7 }
8
9 static FuncDictionary()
10 {
11 if (FuncDictionary.Dictionary == null)
12 {
13 FuncDictionary.Dictionary = CreateDictionary();
14 }
15 }
16
17 public object DynamicInvoke(Type type, string arg)
18 {
19 if (type == null)
20 {
21 return null;
22 }
23
24 if (FuncDictionary.Dictionary == null)
25 {
26 FuncDictionary.Dictionary = CreateDictionary();
27 }
28
29 if (!FuncDictionary.Dictionary.ContainsKey(type))
30 {
31 return null;
32 }
33
34 Delegate action = FuncDictionary.Dictionary[type];
35
36 return action.DynamicInvoke( new object[] { arg });
37 }
38
39 public static IDictionary<Type, Delegate> CreateDictionary()
40 {
41 var dictionary = new Dictionary<Type, Delegate>();
42
43 dictionary.Add( typeof( string), new Func< string, string>(p => p));
44 dictionary.Add( typeof( decimal), new Func< string, decimal>(p => p.AsDecimal()));
45 dictionary.Add( typeof(DateTime), new Func< string, DateTime>(p => p.AsDateTime()));
46 dictionary.Add( typeof( float), new Func< string, float>(p => p.AsFloat()));
47 dictionary.Add( typeof( double), new Func< string, double>(p => p.AsDouble()));
48 dictionary.Add( typeof( int), new Func< string, int>(p => p.AsInt()));
49 dictionary.Add( typeof( byte), new Func< string, byte>(p => p.AsByte()));
50 dictionary.Add( typeof( sbyte), new Func< string, sbyte>(p => p.AsSbyte()));
51 dictionary.Add( typeof( short), new Func< string, short>(p => p.AsShort()));
52 dictionary.Add( typeof( ushort), new Func< string, ushort>(p => p.AsUshort()));
53 dictionary.Add( typeof( uint), new Func< string, uint>(p => p.AsUint()));
54 dictionary.Add( typeof( long), new Func< string, long>(p => p.AsLong()));
55 dictionary.Add( typeof( ulong), new Func< string, ulong>(p => p.AsUlong()));
56 dictionary.Add( typeof( char), new Func< string, char>(p => p.AsChar()));
57 dictionary.Add( typeof( bool), new Func< string, bool>(p => p.AsBool()));
58 dictionary.Add( typeof(Color), new Func< string, Color>(p => p.AsColor()));
59
60 return dictionary;
61 }
62 }
再看下XmlAttributeUtility類,該類主要包括轉換和匹配操作。匹配主要為兩種方案(主要邏輯為以下代碼的72-183行):
1、通過XmlReader順序讀取來設置實體的值,主要方法為public static IList<T> Parse<T>(XmlReader reader) where T : new():
2、通過遍歷XmlNodeList中的節點,依次遍歷節點中的XmlAttribute設置實體的屬性的值,主要方法為:public static IList<T> Parse<T>(XmlNodeList nodeList) where T : new()
2 {
3 if (!File.Exists(inputUri) || string.IsNullOrWhiteSpace(parentXPath))
4 {
5 return new List<T>();
6 }
7
8 XmlDocument document = new XmlDocument();
9 document.Load(inputUri);
10
11 return Parse<T>(document, parentXPath);
12 }
13
14 public static IList<T> Parse<T>(XmlDocument document, string parentXPath) where T : new()
15 {
16 if (document == null || string.IsNullOrWhiteSpace(parentXPath))
17 {
18 return new List<T>();
19 }
20
21 XmlNode parentNode = document.DocumentElement.SelectSingleNode(parentXPath);
22
23 if (parentNode == null)
24 {
25 return new List<T>();
26 }
27
28 return Parse<T>(parentNode);
29 }
30
31 public static IList<T> Parse<T>(XmlNode parentNode) where T : new()
32 {
33 if (parentNode == null || !parentNode.HasChildNodes)
34 {
35 return new List<T>();
36 }
37
38 XmlNodeList nodeList = parentNode.ChildNodes;
39
40 return Parse<T>(nodeList);
41 }
42
43 public static IList<T> Parse<T>(XmlNodeList nodeList) where T : new()
44 {
45 if (nodeList == null || nodeList.Count == 0)
46 {
47 return new List<T>();
48 }
49
50 List<T> entities = new List<T>();
51 AddEntities<T>(nodeList, entities);
52
53 return entities;
54 }
55
56 public static IList<T> Parse<T>( string inputUri) where T : new()
57 {
58 if (!File.Exists(inputUri))
59 {
60 return new List<T>();
61 }
62
63 XmlReaderSettings settings = new XmlReaderSettings();
64 settings.IgnoreComments = true;
65 settings.IgnoreWhitespace = true;
66
67 XmlReader reader = XmlReader.Create(inputUri, settings);
68
69 return Parse<T>(reader);
70 }
71
72 public static IList<T> Parse<T>(XmlReader reader) where T : new()
73 {
74 if (reader == null)
75 {
76 return new List<T>();
77 }
78
79 reader.ReadStartElement();
80 string className = GetClassName<T>();
81 List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
82 List<T> entities = new List<T>();
83 T entity = new T();
84
85 while (!reader.EOF)
86 {
87 if (! string.Equals(reader.Name, className) || !reader.IsStartElement())
88 {
89 reader.Read();
90 continue;
91 }
92
93 entity = new T();
94
95 if (!reader.HasAttributes)
96 {
97 entities.Add(entity);
98 reader.Read();
99 continue;
100 }
101
102 SetPropertyValue<T>(reader, properties, entity);
103 entities.Add(entity);
104 reader.Read();
105 }
106
107 reader.Close();
108
109 return entities;
110 }
152
153 private static void AddEntities<T>(XmlNodeList nodeList,
154 List<T> entities) where T : new()
155 {
156 string className = GetClassName<T>();
157 List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
158 T entity = new T();
159
160 foreach (XmlNode xmlNode in nodeList)
161 {
162 XmlElement element = xmlNode as XmlElement;
163 if (element == null || ! string.Equals(className, element.Name))
164 {
165 continue;
166 }
167
168 entity = new T();
169 if (!element.HasAttributes)
170 {
171 entities.Add(entity);
172 continue;
173 }
174
175 XmlAttributeCollection attributes = element.Attributes;
176 foreach (XmlAttribute attribute in attributes)
177 {
178 SetPropertyValue<T>(properties, entity, attribute.Name, attribute.Value);
179 }
180
181 entities.Add(entity);
182 }
183 }
184
185 private static void SetPropertyValue<T>(XmlReader reader,
186 List<PropertyInfo> properties, T entity) where T : new()
187 {
188 while (reader.MoveToNextAttribute())
189 {
190 SetPropertyValue<T>(properties, entity, reader.Name, reader.Value);
191 }
192 }
193
194 private static void SetPropertyValue<T>(List<PropertyInfo> properties,
195 T entity, string name, string value) where T : new()
196 {
197 foreach ( var property in properties)
198 {
199 if (!property.CanWrite)
200 {
201 continue;
202 }
203
204 string propertyName = GetPropertyName(property);
205
206 if ( string.Equals(name, propertyName))
207 {
208 FuncDictionary action = new FuncDictionary();
209 object invokeResult = action.DynamicInvoke(property.PropertyType, value);
210
211 property.SetValue(entity, invokeResult, null);
212 }
213 }
214 }
2 {
3 List<XElement> elements = Convert<T>(entities);
4 if (elements == null || elements.Count == 0)
5 {
6 return string.Empty;
7 }
8
9 StringBuilder builder = new StringBuilder();
10 elements.ForEach(p => builder.AppendLine(p.ToString()));
11
12 return builder.ToString();
13 }
14
15 public static List<XElement> Convert<T>(List<T> entities) where T : new()
16 {
17 if (entities == null || entities.Count == 0)
18 {
19 return new List<XElement>();
20 }
21
22 List<XElement> elements = new List<XElement>();
23 XElement element;
24
25 foreach ( var entity in entities)
26 {
27 element = Convert<T>(entity);
28 if (element == null)
29 {
30 continue;
31 }
32
33 elements.Add(element);
34 }
35
36 return elements;
37 }
38
39 public static string ConvertToString<T>(T entity) where T : new()
40 {
41 XElement element = Convert<T>(entity);
42 return element == null ? string.Empty : element.ToString();
43 }
44
45 public static XElement Convert<T>(T entity) where T : new()
46 {
47 if (entity == null)
48 {
49 return null;
50 }
51
52 string className = GetClassName<T>();
53 XElement element = new XElement(className);
54
55 List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
56 string propertyName = string.Empty;
57 object propertyValue = null;
58
59 foreach (PropertyInfo property in properties)
60 {
61 if (property.CanRead)
62 {
63 propertyName = GetPropertyName(property);
64 propertyValue = property.GetValue(entity, null);
65 if (property.PropertyType.Name == " Color ")
66 {
67 propertyValue = ColorTranslator.ToHtml((Color)propertyValue);
68 }
69 element.SetAttributeValue(propertyName, propertyValue);
70 }
71 }
72
73 return element;
74 }
該類中還包括其他的一些操作,此處不再概述,詳細參見源碼。
四、單元測試
FuncDictionary的單元測試必須涵蓋所有類型的測試才能將代碼覆蓋率達到100%,此處只針對DateTime做正常測試、異常測試和空值測試(當然,對於其他類型的方法,可能還需要做腳本測試,SQL注入測試等,這三種類型的測試是最基本的測試),主要測試代碼如下:
2 public void DynamicInvokeTest()
3 {
4 FuncDictionary target = new FuncDictionary();
5 Type type = typeof(DateTime);
6 string arg = new DateTime( 2011, 9, 5, 18, 18, 18).ToString();
7
8 DateTime result = (DateTime)target.DynamicInvoke(type, arg);
9 Assert.AreEqual(result.Year, 2011);
10 Assert.AreEqual(result.Month, 9);
11 Assert.AreEqual(result.Day, 5);
12 Assert.AreEqual(result.Hour, 18);
13 Assert.AreEqual(result.Minute, 18);
14 Assert.AreEqual(result.Second, 18);
15 }
16
17 [TestMethod()]
18 public void DynamicInvokeWithNullOrEmptyArgsTest()
19 {
20 FuncDictionary target = new FuncDictionary();
21
22 object result = target.DynamicInvoke( typeof(DateTime), null);
23 Assert.AreEqual((DateTime)result,DateTime.MinValue);
24
25 result = target.DynamicInvoke( typeof(DateTime), string.Empty);
26 Assert.AreEqual((DateTime)result, DateTime.MinValue);
27
28 result = target.DynamicInvoke( null, string.Empty);
29 Assert.AreEqual(result, null);
30 }
31
32 [TestMethod()]
33 public void DynamicInvokeWithInvalidArgsTest()
34 {
35 FuncDictionary target = new FuncDictionary();
36
37 object result = target.DynamicInvoke( typeof(DateTime), " w007 ");
38 Assert.AreEqual((DateTime)result, DateTime.MinValue);
39 }
其他代碼的單元測試詳細見源代碼,也僅僅只做了些基本的測試,寫測試比寫代碼費哥的時間,。
五、總結
以上的代碼僅僅是當時想着怎么實現就怎么寫的,完全是隨意而寫。僅供參考,實戰沒有多大意義,純粹練習下靈感和手感,增強對技術的敏感性而已,純屬娛樂。對於
源碼下載:XmlAttribute轉換和匹配源代碼