Reputation: 1015
I am converting an iOS app to WP7. What I would like to do is use the plist file I created for the iOS app in my WP7 app without making any changes to it. I tried using the libraries from here http://codetitans.codeplex.com/ but I was only able to parse down one level, my plist has multiple levels. Here is example of the plist file I'm trying to parse:
<?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>section0</key>
<dict>
<key>key0</key>
<dict>
<key>name</key>
<string>Title</string>
<key>type</key>
<string>text</string>
<key>filter</key>
<false/>
</dict>
<key>key1</key>
<dict>
<key>name</key>
<string>Season</string>
<key>type</key>
<string>text</string>
<key>filter</key>
<false/>
</dict>
</dict>
</dict>
Upvotes: 2
Views: 929
Reputation: 1356
Actually CodeTitans Libraries parses all the levels. Here is the unit-test I added today with your data, that shows, how you actually could access nested items:
[TestMethod]
public void LoadMultilevelItems()
{
var input = @"<?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>section0</key>
<dict>
<key>key0</key>
<dict>
<key>name</key>
<string>Title</string>
<key>type</key>
<string>text</string>
<key>filter</key>
<false/>
</dict>
<key>key1</key>
<dict>
<key>name</key>
<string>Season</string>
<key>type</key>
<string>text</string>
<key>filter</key>
<false/>
</dict>
</dict>
</dict>
</plist>";
var data = PropertyList.Read(input);
Assert.IsNotNull(data);
Assert.IsTrue(data.Contains("section0"));
var section0 = data["section0"];
Assert.IsNotNull(section0);
Assert.IsTrue(section0.Contains("key0"));
var key0 = section0["key0"];
Assert.IsNotNull(key0);
Assert.AreEqual("Title", key0["name"].StringValue);
Assert.AreEqual("text", key0["type"].StringValue);
Assert.IsFalse(key0["filter"].BooleanValue);
key0.Add("filter", true);
}
All the items are by default "dictionaries" or "arrays" that you can access using brackets. When you went down to the value, just use a dedicated property 'StringValue' or 'BooleanValue', to avoid type casting in your own code.
There are even more supportive properties like: Type (allowing you to check, the native plist type of an item) or ArrayItems and DictionaryItems (allowing you to enumerate content if you plist structure format is dynamic).
Upvotes: 1
Reputation: 84784
Part 1: What you asked for
WP doesn't currently have very good support for dynamic types so while parsing it won't be difficult, consuming it will be ugly.
This class will parse the PList using LINQ to XML:
public class PropertyListParser
{
public IDictionary<String, Object> Parse(string plistXml)
{
return Parse(XDocument.Parse(plistXml));
}
public IDictionary<String, Object> Parse(XDocument document)
{
return ParseDictionary(document.Root.Elements("plist")
.Elements("dict")
.First());
}
private IDictionary<String, Object> ParseDictionary(XElement dictElement)
{
return dictElement
.Elements("key")
.ToDictionary(
el => el.Value,
el => ParseValue(el.ElementsAfterSelf("*").FirstOrDefault())
);
}
private object ParseValue(XElement element)
{
if (element == null)
{
return null;
}
string valueType = element.Name.LocalName;
switch (valueType)
{
case "string":
return element.Value;
case "dict":
return ParseDictionary(element);
case "true":
return true;
case "false":
return false;
default:
throw new NotSupportedException("Plist element not supported: " + valueType);
}
}
}
Here's an example of how to use it (based on your example):
var parsedPlist = new PlistParser().Parse(Plist);
var section0 = (IDictionary<string, object>)parsedPlist["section0"];
var key0 = (IDictionary<string, object>)parsedPlist["key0"];
string type = (string)key0["type"];
bool filter = (bool)key0["filter"];
Part 2: What you probably need
Having said that, actually writing code that consumes it in this way would be pretty ugly. Based on your schema, I'd say that the following is actually what your application needs.
// I'm not sure what your domain object is, so please rename this
public class ConfigEntry
{
public string Name { get; set; }
public string Type { get; set; }
public bool Filter { get; set; }
}
public class ConfigEntryLoader
{
private PropertyListParser plistParser;
public ConfigEntryLoader()
{
plistParser = new PropertyListParser();
}
public ICollection<ConfigEntry> LoadEntriesFromPlist(string plistXml)
{
var parsedPlist = plistParser.Parse(plistXml);
var section0 = (IDictionary<string, object>)parsedPlist["section0"];
return section0.Values
.Cast<IDictionary<string,object>>()
.Select(CreateEntry)
.ToList();
}
private ConfigEntry CreateEntry(IDictionary<string, object> entryDict)
{
// Accessing missing keys in a dictionary throws an exception,
// so if they are optional you should check if they exist using ContainsKey
return new ConfigEntry
{
Name = (string)entryDict["name"],
Type = (string)entryDict["type"],
Filter = (bool)entryDict["filter"]
};
}
}
Now when you use ConfigEntryLoader
, you get a list of ConfigEntry objects which will make your code much easier to maintain that passing around dictionaries.
ICollection<ConfigEntry> configEntries = new ConfigEntryLoader()
.LoadEntriesFromPlist(plistXml);
Upvotes: 4