使用C#反序列化plist文件

因为我们的项目是跨平台的,最初做的是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

本文版权归作者所有,欢迎转载,演绎或用于商业目的,但是必须说明本文出处(包含链接)。

posted @ 2013-02-07 20:37  倾剑飞血  阅读(3594)  评论(4编辑  收藏  举报