chovy-sign/DiscUtils/Plist.cs

208 lines
6.8 KiB
C#

//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
namespace DiscUtils
{
internal static class Plist
{
internal static Dictionary<string, object> Parse(Stream stream)
{
XmlDocument xmlDoc = new XmlDocument();
#if !NETSTANDARD1_5
xmlDoc.XmlResolver = null;
#endif
XmlReaderSettings settings = new XmlReaderSettings();
#if !NET20
// DTD processing is disabled on anything but .NET 2.0, so this must be set to
// Ignore.
// See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx for additional information.
settings.DtdProcessing = DtdProcessing.Ignore;
#endif
using (XmlReader reader = XmlReader.Create(stream, settings))
{
xmlDoc.Load(reader);
}
XmlElement root = xmlDoc.DocumentElement;
if (root.Name != "plist")
{
throw new InvalidDataException("XML document is not a plist");
}
return ParseDictionary(root.FirstChild);
}
internal static void Write(Stream stream, Dictionary<string, object> plist)
{
XmlDocument xmlDoc = new XmlDocument();
#if !NETSTANDARD1_5
xmlDoc.XmlResolver = null;
#endif
XmlDeclaration xmlDecl = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
xmlDoc.AppendChild(xmlDecl);
#if !NETSTANDARD1_5
XmlDocumentType xmlDocType = xmlDoc.CreateDocumentType("plist", "-//Apple//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
xmlDoc.AppendChild(xmlDocType);
#endif
XmlElement rootElement = xmlDoc.CreateElement("plist");
rootElement.SetAttribute("Version", "1.0");
xmlDoc.AppendChild(rootElement);
xmlDoc.DocumentElement.SetAttribute("Version", "1.0");
rootElement.AppendChild(CreateNode(xmlDoc, plist));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.Encoding = Encoding.UTF8;
using (XmlWriter xw = XmlWriter.Create(stream, settings))
{
xmlDoc.Save(xw);
}
}
private static object ParseNode(XmlNode xmlNode)
{
switch (xmlNode.Name)
{
case "dict":
return ParseDictionary(xmlNode);
case "array":
return ParseArray(xmlNode);
case "string":
return ParseString(xmlNode);
case "data":
return ParseData(xmlNode);
case "integer":
return ParseInteger(xmlNode);
case "true":
return true;
case "false":
return false;
default:
throw new NotImplementedException();
}
}
private static XmlNode CreateNode(XmlDocument xmlDoc, object obj)
{
if (obj is Dictionary<string, object>)
{
return CreateDictionary(xmlDoc, (Dictionary<string, object>)obj);
}
if (obj is string)
{
XmlText text = xmlDoc.CreateTextNode((string)obj);
XmlElement node = xmlDoc.CreateElement("string");
node.AppendChild(text);
return node;
}
throw new NotImplementedException();
}
private static XmlNode CreateDictionary(XmlDocument xmlDoc, Dictionary<string, object> dict)
{
XmlElement dictNode = xmlDoc.CreateElement("dict");
foreach (KeyValuePair<string, object> entry in dict)
{
XmlText text = xmlDoc.CreateTextNode(entry.Key);
XmlElement keyNode = xmlDoc.CreateElement("key");
keyNode.AppendChild(text);
dictNode.AppendChild(keyNode);
XmlNode valueNode = CreateNode(xmlDoc, entry.Value);
dictNode.AppendChild(valueNode);
}
return dictNode;
}
private static Dictionary<string, object> ParseDictionary(XmlNode xmlNode)
{
Dictionary<string, object> result = new Dictionary<string, object>();
XmlNode focusNode = xmlNode.FirstChild;
while (focusNode != null)
{
if (focusNode.Name != "key")
{
throw new InvalidDataException("Invalid plist, expected dictionary key");
}
string key = focusNode.InnerText;
focusNode = focusNode.NextSibling;
result.Add(key, ParseNode(focusNode));
focusNode = focusNode.NextSibling;
}
return result;
}
private static object ParseArray(XmlNode xmlNode)
{
List<object> result = new List<object>();
XmlNode focusNode = xmlNode.FirstChild;
while (focusNode != null)
{
result.Add(ParseNode(focusNode));
focusNode = focusNode.NextSibling;
}
return result;
}
private static object ParseString(XmlNode xmlNode)
{
return xmlNode.InnerText;
}
private static object ParseData(XmlNode xmlNode)
{
string base64 = xmlNode.InnerText;
return Convert.FromBase64String(base64);
}
private static object ParseInteger(XmlNode xmlNode)
{
return int.Parse(xmlNode.InnerText, CultureInfo.InvariantCulture);
}
}
}