Introduction
Recently I've been put in charge of making a texture packaging tool at work. We are using pure C# (4.0) for our tools. I need to read and parse plist files in my code.
I searched the Internet, but unfortunately it seems it's lacking a plist parser in C#. I understood plist grammar very quick even by reading a few sample files,
since it would be easy to analyze for a human as well easy to recognize for a parser. I decided to reinvent this wheel.
Show me the code
The implementation is quite simple, I did it in about an hour. Let's take a quick look at the whole code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Data
{
public class PList : Dictionary<string, dynamic>
{
public PList()
{
}
public PList(string file)
{
Load(file);
}
public void Load(string file)
{
Clear();
XDocument doc = XDocument.Load(file);
XElement plist = doc.Element("plist");
XElement dict = plist.Element("dict");
var dictElements = dict.Elements();
Parse(this, dictElements);
}
private void Parse(PList dict, IEnumerable<XElement> elements)
{
for (int i = 0; i < elements.Count(); i += 2)
{
XElement key = elements.ElementAt(i);
XElement val = elements.ElementAt(i + 1);
dict[key.Value] = ParseValue(val);
}
}
private List<dynamic> ParseArray(IEnumerable<XElement> elements)
{
List<dynamic> list = new List<dynamic>();
foreach (XElement e in elements)
{
dynamic one = ParseValue(e);
list.Add(one);
}
return list;
}
private dynamic ParseValue(XElement val)
{
switch (val.Name.ToString())
{
case "string":
return val.Value;
case "integer":
return int.Parse(val.Value);
case "real":
return float.Parse(val.Value);
case "true":
return true;
case "false":
return false;
case "dict":
PList plist = new PList();
Parse(plist, val.Elements());
return plist;
case "array":
List<dynamic> list = ParseArray(val.Elements());
return list;
default:
throw new ArgumentException("Unsupported");
}
}
}
}
It's never more clear, isn't it?
The LINQ and dynamic
type benefit this lightweight class. It would cost more lines without them. This class is a recursive data structure,
although we can't see PList
property defined in it; the base class is Dictionary<string, dynamic>
, and values in it could be a value
of string/int/float/bool/PList/List<dynamic>
. The parsing methods are recursive, since the recursive structure. The Parse
method enumerates
all key-value pairs in a piece of XML data and fill the parsed string-dynamic pairs into the PList
structure; each step it will
call the ParseValue
method to parse a value; the ParseValue
method calls data
conversion methods or ParseArray
or Parse
recursively according to the type indicated in the name of a XML node; the ParseArray
method enumerates all sub XML
nodes and parse them to dynamic type by calling ParseValue
recursively. Of course we could use object
instead of dynamic
out there, but we had to add more type discrimination when we were retrieving a value from a PList
, that is something I'd like to avoid.
Using the code
To parse a plist file, just code it as:
PList plist = new PList();
plist.Load("file_path.plist");
or:
PList plist = new PList("file_path.plist");
To retrieve the parsed data it's all the same as using a generic Dictionary
, considering the PList
is derived from Dictionary<string, dynamic>
.
The code might looks crude, because I'd like to only show the main idea here without disturbing noise. I think you could and should polish it before using it, for example adding some
error/exception handling, or adding a serialization method, etc. Those could be very simple tasks.
Have fun
Making a plist parser in C# is easier than I expected, and it works fine for me. I hope it would help you too.