374 lines
12 KiB
C#
374 lines
12 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.IO;
|
|||
|
using System.Linq;
|
|||
|
using System.Xml;
|
|||
|
using System.Xml.Linq;
|
|||
|
using System.Xml.XPath;
|
|||
|
|
|||
|
namespace ImaginationOverflow.UniversalDeepLinking.Editor.Xcode
|
|||
|
{
|
|||
|
|
|||
|
public class PlistElement
|
|||
|
{
|
|||
|
protected PlistElement() { }
|
|||
|
|
|||
|
// convenience methods
|
|||
|
public string AsString() { return ((PlistElementString)this).value; }
|
|||
|
public int AsInteger() { return ((PlistElementInteger)this).value; }
|
|||
|
public bool AsBoolean() { return ((PlistElementBoolean)this).value; }
|
|||
|
public PlistElementArray AsArray() { return (PlistElementArray)this; }
|
|||
|
public PlistElementDict AsDict() { return (PlistElementDict)this; }
|
|||
|
|
|||
|
public PlistElement this[string key]
|
|||
|
{
|
|||
|
get { return AsDict()[key]; }
|
|||
|
set { AsDict()[key] = value; }
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementString : PlistElement
|
|||
|
{
|
|||
|
public PlistElementString(string v) { value = v; }
|
|||
|
|
|||
|
public string value;
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementInteger : PlistElement
|
|||
|
{
|
|||
|
public PlistElementInteger(int v) { value = v; }
|
|||
|
|
|||
|
public int value;
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementFloat : PlistElement
|
|||
|
{
|
|||
|
public PlistElementFloat(float v) { value = v; }
|
|||
|
|
|||
|
public float value;
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementBoolean : PlistElement
|
|||
|
{
|
|||
|
public PlistElementBoolean(bool v) { value = v; }
|
|||
|
|
|||
|
public bool value;
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementDict : PlistElement
|
|||
|
{
|
|||
|
public PlistElementDict() : base() { }
|
|||
|
|
|||
|
private SortedDictionary<string, PlistElement> m_PrivateValue = new SortedDictionary<string, PlistElement>();
|
|||
|
public IDictionary<string, PlistElement> values { get { return m_PrivateValue; } }
|
|||
|
|
|||
|
new public PlistElement this[string key]
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (values.ContainsKey(key))
|
|||
|
return values[key];
|
|||
|
return null;
|
|||
|
}
|
|||
|
set { this.values[key] = value; }
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// convenience methods
|
|||
|
public void SetInteger(string key, int val)
|
|||
|
{
|
|||
|
values[key] = new PlistElementInteger(val);
|
|||
|
}
|
|||
|
|
|||
|
public void SetString(string key, string val)
|
|||
|
{
|
|||
|
values[key] = new PlistElementString(val);
|
|||
|
}
|
|||
|
|
|||
|
public void SetBoolean(string key, bool val)
|
|||
|
{
|
|||
|
values[key] = new PlistElementBoolean(val);
|
|||
|
}
|
|||
|
|
|||
|
public PlistElementArray CreateArray(string key)
|
|||
|
{
|
|||
|
var v = new PlistElementArray();
|
|||
|
values[key] = v;
|
|||
|
return v;
|
|||
|
}
|
|||
|
|
|||
|
public PlistElementDict CreateDict(string key)
|
|||
|
{
|
|||
|
var v = new PlistElementDict();
|
|||
|
values[key] = v;
|
|||
|
return v;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public class PlistElementArray : PlistElement
|
|||
|
{
|
|||
|
public PlistElementArray() : base() { }
|
|||
|
public List<PlistElement> values = new List<PlistElement>();
|
|||
|
|
|||
|
// convenience methods
|
|||
|
public void AddString(string val)
|
|||
|
{
|
|||
|
values.Add(new PlistElementString(val));
|
|||
|
}
|
|||
|
|
|||
|
public void AddInteger(int val)
|
|||
|
{
|
|||
|
values.Add(new PlistElementInteger(val));
|
|||
|
}
|
|||
|
|
|||
|
public void AddBoolean(bool val)
|
|||
|
{
|
|||
|
values.Add(new PlistElementBoolean(val));
|
|||
|
}
|
|||
|
|
|||
|
public PlistElementArray AddArray()
|
|||
|
{
|
|||
|
var v = new PlistElementArray();
|
|||
|
values.Add(v);
|
|||
|
return v;
|
|||
|
}
|
|||
|
|
|||
|
public PlistElementDict AddDict()
|
|||
|
{
|
|||
|
var v = new PlistElementDict();
|
|||
|
values.Add(v);
|
|||
|
return v;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public class PlistDocument
|
|||
|
{
|
|||
|
public PlistElementDict root;
|
|||
|
public string version;
|
|||
|
|
|||
|
public PlistDocument()
|
|||
|
{
|
|||
|
root = new PlistElementDict();
|
|||
|
version = "1.0";
|
|||
|
}
|
|||
|
|
|||
|
// Parses a string that contains a XML file. No validation is done.
|
|||
|
internal static XDocument ParseXmlNoDtd(string text)
|
|||
|
{
|
|||
|
XmlReaderSettings settings = new XmlReaderSettings();
|
|||
|
//settings.DtdProcessing = DtdProcessing.Ignore;
|
|||
|
settings.ProhibitDtd = false;
|
|||
|
|
|||
|
settings.XmlResolver = null; // prevent DTD download
|
|||
|
|
|||
|
XmlReader xmlReader = XmlReader.Create(new StringReader(text), settings);
|
|||
|
return XDocument.Load(xmlReader);
|
|||
|
}
|
|||
|
|
|||
|
// LINQ serializes XML DTD declaration with an explicit empty 'internal subset'
|
|||
|
// (a pair of square brackets at the end of Doctype declaration).
|
|||
|
// Even though this is valid XML, XCode does not like it, hence this workaround.
|
|||
|
internal static string CleanDtdToString(XDocument doc)
|
|||
|
{
|
|||
|
// LINQ does not support changing the DTD of existing XDocument instances,
|
|||
|
// so we create a dummy document for printing of the Doctype declaration.
|
|||
|
// A single dummy element is added to force LINQ not to omit the declaration.
|
|||
|
// Also, utf-8 encoding is forced since this is the encoding we use when writing to file in UpdateInfoPlist.
|
|||
|
if (doc.DocumentType != null)
|
|||
|
{
|
|||
|
XDocument tmpDoc =
|
|||
|
new XDocument(new XDeclaration("1.0", "utf-8", null),
|
|||
|
new XDocumentType(doc.DocumentType.Name, doc.DocumentType.PublicId, doc.DocumentType.SystemId, null),
|
|||
|
new XElement(doc.Root.Name));
|
|||
|
return "" + tmpDoc.Declaration + "\n" + tmpDoc.DocumentType + "\n" + doc.Root;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
XDocument tmpDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement(doc.Root.Name));
|
|||
|
return "" + tmpDoc.Declaration + Environment.NewLine + doc.Root;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static string GetText(XElement xml)
|
|||
|
{
|
|||
|
return String.Join("", xml.Nodes().OfType<XText>().Select(x => x.Value).ToArray());
|
|||
|
}
|
|||
|
|
|||
|
private static PlistElement ReadElement(XElement xml)
|
|||
|
{
|
|||
|
switch (xml.Name.LocalName)
|
|||
|
{
|
|||
|
case "dict":
|
|||
|
{
|
|||
|
List<XElement> children = xml.Elements().ToList();
|
|||
|
var el = new PlistElementDict();
|
|||
|
|
|||
|
if (children.Count % 2 == 1)
|
|||
|
throw new Exception("Malformed plist file");
|
|||
|
|
|||
|
for (int i = 0; i < children.Count - 1; i++)
|
|||
|
{
|
|||
|
if (children[i].Name != "key")
|
|||
|
throw new Exception("Malformed plist file");
|
|||
|
string key = GetText(children[i]).Trim();
|
|||
|
var newChild = ReadElement(children[i + 1]);
|
|||
|
if (newChild != null)
|
|||
|
{
|
|||
|
i++;
|
|||
|
el[key] = newChild;
|
|||
|
}
|
|||
|
}
|
|||
|
return el;
|
|||
|
}
|
|||
|
case "array":
|
|||
|
{
|
|||
|
List<XElement> children = xml.Elements().ToList();
|
|||
|
var el = new PlistElementArray();
|
|||
|
|
|||
|
foreach (var childXml in children)
|
|||
|
{
|
|||
|
var newChild = ReadElement(childXml);
|
|||
|
if (newChild != null)
|
|||
|
el.values.Add(newChild);
|
|||
|
}
|
|||
|
return el;
|
|||
|
}
|
|||
|
case "real":
|
|||
|
{
|
|||
|
float r;
|
|||
|
if (float.TryParse(GetText(xml), out r))
|
|||
|
return new PlistElementFloat(r);
|
|||
|
return null;
|
|||
|
}
|
|||
|
case "string":
|
|||
|
return new PlistElementString(GetText(xml));
|
|||
|
case "integer":
|
|||
|
{
|
|||
|
int r;
|
|||
|
if (int.TryParse(GetText(xml), out r))
|
|||
|
return new PlistElementInteger(r);
|
|||
|
return null;
|
|||
|
}
|
|||
|
case "true":
|
|||
|
return new PlistElementBoolean(true);
|
|||
|
case "false":
|
|||
|
return new PlistElementBoolean(false);
|
|||
|
default:
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void Create()
|
|||
|
{
|
|||
|
const string doc = "<?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>" +
|
|||
|
"</dict>" +
|
|||
|
"</plist>";
|
|||
|
ReadFromString(doc);
|
|||
|
}
|
|||
|
|
|||
|
public void ReadFromFile(string path)
|
|||
|
{
|
|||
|
ReadFromString(File.ReadAllText(path));
|
|||
|
}
|
|||
|
|
|||
|
public void ReadFromStream(TextReader tr)
|
|||
|
{
|
|||
|
ReadFromString(tr.ReadToEnd());
|
|||
|
}
|
|||
|
|
|||
|
public void ReadFromString(string text)
|
|||
|
{
|
|||
|
XDocument doc = ParseXmlNoDtd(text);
|
|||
|
version = (string)doc.Root.Attribute("version");
|
|||
|
XElement xml = doc.XPathSelectElement("plist/dict");
|
|||
|
|
|||
|
var dict = ReadElement(xml);
|
|||
|
if (dict == null)
|
|||
|
throw new Exception("Error parsing plist file");
|
|||
|
root = dict as PlistElementDict;
|
|||
|
if (root == null)
|
|||
|
throw new Exception("Malformed plist file");
|
|||
|
}
|
|||
|
|
|||
|
private static XElement WriteElement(PlistElement el)
|
|||
|
{
|
|||
|
if (el is PlistElementBoolean)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementBoolean;
|
|||
|
return new XElement(realEl.value ? "true" : "false");
|
|||
|
}
|
|||
|
if (el is PlistElementInteger)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementInteger;
|
|||
|
return new XElement("integer", realEl.value.ToString());
|
|||
|
}
|
|||
|
if (el is PlistElementFloat)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementFloat;
|
|||
|
return new XElement("real", realEl.value.ToString());
|
|||
|
}
|
|||
|
if (el is PlistElementString)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementString;
|
|||
|
return new XElement("string", realEl.value);
|
|||
|
}
|
|||
|
if (el is PlistElementDict)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementDict;
|
|||
|
var dictXml = new XElement("dict");
|
|||
|
foreach (var kv in realEl.values)
|
|||
|
{
|
|||
|
var keyXml = new XElement("key", kv.Key);
|
|||
|
var valueXml = WriteElement(kv.Value);
|
|||
|
if (valueXml != null)
|
|||
|
{
|
|||
|
dictXml.Add(keyXml);
|
|||
|
dictXml.Add(valueXml);
|
|||
|
}
|
|||
|
}
|
|||
|
return dictXml;
|
|||
|
}
|
|||
|
if (el is PlistElementArray)
|
|||
|
{
|
|||
|
var realEl = el as PlistElementArray;
|
|||
|
var arrayXml = new XElement("array");
|
|||
|
foreach (var v in realEl.values)
|
|||
|
{
|
|||
|
var elXml = WriteElement(v);
|
|||
|
if (elXml != null)
|
|||
|
arrayXml.Add(elXml);
|
|||
|
}
|
|||
|
return arrayXml;
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
public void WriteToFile(string path)
|
|||
|
{
|
|||
|
System.Text.Encoding utf8WithoutBom = new System.Text.UTF8Encoding(false);
|
|||
|
File.WriteAllText(path, WriteToString(), utf8WithoutBom);
|
|||
|
}
|
|||
|
|
|||
|
public void WriteToStream(TextWriter tw)
|
|||
|
{
|
|||
|
tw.Write(WriteToString());
|
|||
|
}
|
|||
|
|
|||
|
public string WriteToString()
|
|||
|
{
|
|||
|
var el = WriteElement(root);
|
|||
|
var rootEl = new XElement("plist");
|
|||
|
rootEl.Add(new XAttribute("version", version));
|
|||
|
rootEl.Add(el);
|
|||
|
|
|||
|
var doc = new XDocument();
|
|||
|
doc.Add(rootEl);
|
|||
|
return CleanDtdToString(doc).Replace("\r\n", "\n");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} // namespace UnityModule.iOS.XCode
|