因为我们的项目是跨平台的,最初做的是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
本文版权归作者所有,欢迎转载,演绎或用于商业目的,但是必须说明本文出处(包含链接)。