因為我們的項目是跨平台的,最初做的是ios版,所以打包的xml都是plist格式的。plist在xcode里反序列化比較容易,而PC版里最初是依照不同文件的結構,做得不同解析,代碼復用性比較低。
我接手這個項目之后,老大讓我再加一個文件的解析,看了一下之前的那個代碼感覺比較亂,干脆就自己直接寫了一個plist文件的反序列化算法,在這里和大家分享一下。
我的思路是這樣的,先將plist文件中的元素用樹的形式儲存起來,再將樹組織成一個標准的XML文件,利用C#中的XmlSerializer將其反序列化。
若要解析plist文件,我們首先要了解其文件的格式,下面是一個plist文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>zh_CN</string> <key>CFBundleDisplayName</key> <string>${PRODUCT_NAME}</string> <key>CFBundleExecutable</key> <string>${EXECUTABLE_NAME}</string> <key>CFBundleIconFiles</key> <array> <string>Icon.png</string> <string>Icon@2x.png</string> </array> <key>CFBundleIcons</key> <dict> <key>CFBundlePrimaryIcon</key> <dict> <key>CFBundleIconFiles</key> <array> <string>Icon.png</string> <string>Icon@2x.png</string> </array> <key>UIPrerenderedIcon</key> <false/> </dict> </dict> <key>CFBundleIdentifier</key> <string>com.qixiangWeather.${PRODUCT_NAME:rfc1034identifier}</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>${PRODUCT_NAME}</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1.0</string> <key>LSRequiresIPhoneOS</key> <true/> <key>UIRequiredDeviceCapabilities</key> <array> <string>armv7</string> </array> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeLeft</string> </array> </dict> </plist>
我們可以看出它和標准的xml文件有很多不同,它具有以下幾種標簽格式來代表不同類型的數據:
Core Fundation XML
CFString <string>
CFNumber <real> 或 <integer>
CFDate <date>
CFBoolean <true/> 或 <false/>
CFData <data>
CFArray <array>
CFDictionary <dict>
在CFDictionary中數據主要由鍵值對組成。因此在XML中,CFDictioary成員的鍵對應為<key>,之后便是它相應的值。
根據這一特點我們可以枚舉出鍵值類型
enum EnumValueType { DICT, ARRAY, NUMBER, STRING, DTAE, BOOLEAN, DATA, }
現在我們要把plist格式轉化成樹,定義一下數據結構
class DataType { public int ID { get; set; }//元素ID public string DataName { get; set; }//元素數據名稱 public EnumValueType ValueType { get; set; }//元素值類型 public string Value { get; set; }//元素值 public int parentID { get; set; }//元素父節點ID public List<int> childrenID { get; set; }//元素子節點 }
解析的算法是一個遞歸的過程,這里我們用一個list將樹存起來,如果你疑惑為什么list里的ID不是連續的,我可以提醒你一下遍歷的過程是一個前序遍歷
class ReadPlist { public List<DataType> DateList { get; set; } int saveid = 1; DataType RootNode; int count; int itemCount; public ReadPlist() { count = 1; itemCount = 0; RootNode = new DataType(); RootNode.ID = count; RootNode.DataName = null; RootNode.parentID = 0; RootNode.ValueType = EnumValueType.DICT; RootNode.childrenID = new List<int>(); this.DateList = new List<DataType>(); DateList.Add(RootNode); } public XDocument LoadFromFile(string path) { return XDocument.Load(path); } public void XMLparser(string path) { XDocument doc = LoadFromFile(path); XElement FirstElement = doc.Root.Element("dict"); DateList[0].childrenID = XMLOnce(FirstElement, 1); foreach (var item in DateList) { if (item.Value == "FALSE"||item.Value == "TRUE") { item.Value = item.Value.ToLower(); } try { if (Char.IsNumber(item.DataName[0])) { item.DataName.Insert(0, "_"); } } catch (System.Exception ex) { } } print(); } public List<int> XMLOnce(XElement nowElement,int parentid) { List<DataType> DataTemp = new List<DataType>(); List<int> IDList = new List<int>(); List<int> childrenIDList = new List<int>(); var keys = from k in nowElement.Elements("key") select k; var values = from v in nowElement.Elements() where v.Name != "key" select v ; for (int i = 0; i < values.ToList().Count; i++) { int id = ++count; EnumValueType valuetype = (EnumValueType)Enum.Parse(typeof(EnumValueType), values.ToList()[i].Name.LocalName.ToString().ToUpper(), true); string value = null; if (valuetype == EnumValueType.ARRAY) { XElement newElement = nowElement.Elements().Except(nowElement.Elements("key")).ElementAt(i); int num = newElement.Elements().Count(); for (int j = 0; j < num;j++ ) { newElement.AddFirst(new XElement("key", "item")); } childrenIDList = XMLOnce(newElement,id); } else if (valuetype == EnumValueType.DICT) { XElement newElement = nowElement.Elements().Except(nowElement.Elements("key")).ElementAt(i); childrenIDList = XMLOnce(newElement, id); } else { value = values.ToList()[i].Value.ToString(); } try { DataTemp.Add(new DataType() { DataName = keys.ToList()[i].Value.ToString(), ValueType = valuetype, ID = id, Value = value, parentID = parentid, childrenID = childrenIDList }); } catch (System.Exception ex) { DataTemp.Add(new DataType() { DataName = "itemContent", ValueType = valuetype, ID = id, Value = value, parentID = parentid, childrenID = childrenIDList }); } } foreach (var item in DataTemp) { IDList.Add(item.ID); } DateList.AddRange(DataTemp); return IDList; }
}
好的,我們下一步是把這個樹寫成一個標准的xml格式,我這里對xml的解析用的是linq to xml,如果有不熟悉linq的朋友,可以翻閱園子里的其他文章學習一下
class ConvertXML { List<DataType> DataList{get;set;} public ConvertXML (List<DataType> datalist) { DataList = datalist; } public XDocument xdoc = new XDocument(new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("Model"))); public XDocument creatXML() { xdoc.Element("Model").SetAttributeValue("id","1"); foreach (var item in DataList) { if (DataList[0].childrenID.Contains(item.ID)) { //xdoc.Element("Model").Add(new XElement(item.DataName, item.Value)); XElement newElement = xdoc.Descendants().Where(e => e.Attribute("id").Value == "1").First(); newElement.Add(new XElement(item.DataName, item.Value, new XAttribute("id", item.ID))); if (item.ValueType == EnumValueType.ARRAY||item.ValueType == EnumValueType.DICT) { //XElement newElement = xdoc.Element("Model").Element(item.DataName); creatOnce(newElement, item); } } } return xdoc; } public void creatOnce(XElement doc,DataType parent) { foreach (var item in DataList) { if (parent.childrenID.Contains(item.ID)) { string parentID = parent.ID.ToString(); XElement newElement = doc.Descendants().Where(e => e.Attribute("id").Value.Equals(parentID)).First(); newElement.Add(new XElement(item.DataName, item.Value, new XAttribute("id", item.ID))); if (item.ValueType == EnumValueType.ARRAY||item.ValueType == EnumValueType.DICT) { //nowElement = nowElement.Element(item.DataName); creatOnce(newElement,item); } } } } }
剩下最后一步,就是對現在標准的xml進行反序列化了,我們對外只留文件路徑和類的程序集名稱
class PlistSerializer : XmlSerializer { public object Deserialize(string path,string assemblyName) { MemoryStream stream = new MemoryStream(); ReadPlist rp = new ReadPlist(); rp.XMLparser(path); ConvertXML cx = new ConvertXML(rp.DateList); //Console.WriteLine(cx.creatXML()); XDocument doc = cx.creatXML(); doc.Save(stream); stream.Seek(0, SeekOrigin.Begin); Type modelType = Type.GetType(assemblyName); XmlSerializer serializer = new XmlSerializer(modelType); try { object sender = serializer.Deserialize(stream); stream.Close(); return sender; } catch (Exception e) { Console.WriteLine(e.Message); stream.Close(); return null; } } }
這樣用C#對plist文件的反序列化就大功告成了,代碼比較簡單,大家還可以根據需要,加上序列化的功能。
本代碼codeplex:https://plistparser.codeplex.com/SourceControl/changeset/view/901458f7e974
如果大家有對plist序列化和反序列化的需求,感覺我的代碼不夠優化或者功能不夠強大,也可以利用網上的第三方庫來實現功能,比如iphone-plist-net
感謝閱讀傾劍飛血的博客
本文地址:http://www.cnblogs.com/jacklandrin/archive/2013/02/07/2908968.html
本文版權歸作者所有,歡迎轉載,演繹或用於商業目的,但是必須說明本文出處(包含鏈接)。