using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace Unity.XR.Management.AndroidManifest.Editor
{
///
/// This class holds information that should be displayed in an Editor tooltip for a given package.
///
internal class AndroidManifestDocument : XmlDocument
{
internal static readonly string k_androidXmlNamespace = "http://schemas.android.com/apk/res/android";
private readonly string m_Path;
private readonly XmlNamespaceManager m_nsMgr;
internal AndroidManifestDocument()
{
m_nsMgr = new XmlNamespaceManager(NameTable);
m_nsMgr.AddNamespace("android", k_androidXmlNamespace);
}
internal AndroidManifestDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
m_nsMgr = new XmlNamespaceManager(NameTable);
m_nsMgr.AddNamespace("android", k_androidXmlNamespace);
}
internal string Save()
{
return SaveAs(m_Path);
}
internal string SaveAs(string path)
{
// ensure the folder exists so that the XmlTextWriter doesn't fail
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
internal void CreateNewElement(List path, Dictionary attributes)
{
// Look up for closest parent node to new leaf node
XmlElement parentNode, node = null;
int nextNodeIndex = -1;
do
{
nextNodeIndex++;
parentNode = node;
node = (XmlElement)(parentNode == null ?
SelectSingleNode(path[nextNodeIndex]) :
parentNode.SelectSingleNode(path[nextNodeIndex]));
} while (node != null && nextNodeIndex < path.Count - 1);
// If nodes are missing between root and leaf, fill out hierarchy including leaf node
for (int i = nextNodeIndex; i < path.Count; i++)
{
node = CreateElement(path[i]);
parentNode.AppendChild(node);
parentNode = node;
}
// Apply attributes to leaf node
foreach (var attributePair in attributes)
{
node.SetAttribute(attributePair.Key, k_androidXmlNamespace, attributePair.Value);
}
}
internal void CreateNewElementIfDoesntExist(List path, Dictionary attributes)
{
var existingNodeElements = SelectNodes(string.Join("/", path));
foreach(XmlElement element in existingNodeElements)
{
if (CheckNodeAttributesMatch(element, attributes))
{
return;
}
}
CreateNewElement(path, attributes);
}
internal void CreateOrOverrideElement(List path, Dictionary attributes)
{
// Look up for leaf node or closest
XmlElement parentNode, node = null;
int nextNodeIndex = -1;
do
{
nextNodeIndex++;
parentNode = node;
node = (XmlElement)(parentNode == null ?
SelectSingleNode(path[nextNodeIndex]) :
parentNode.SelectSingleNode(path[nextNodeIndex]));
} while (node != null && nextNodeIndex < path.Count - 1);
// If nodes are missing between root and leaf, fill out hierarchy including leaf node
if (node == null)
{
for (int i = nextNodeIndex; i < path.Count; i++)
{
node = CreateElement(path[i]);
parentNode.AppendChild(node);
parentNode = node;
}
}
// Apply attributes to leaf node
foreach (var attributePair in attributes)
{
node.SetAttribute(attributePair.Key, k_androidXmlNamespace, attributePair.Value);
}
}
internal void RemoveMatchingElement(List elementPath, Dictionary attributes)
{
var xmlNodeList = SelectNodes(string.Join("/", elementPath));
foreach (XmlElement node in xmlNodeList)
{
if (CheckNodeAttributesMatch(node, attributes))
{
node.ParentNode?.RemoveChild(node);
}
}
}
private bool CheckNodeAttributesMatch(XmlNode node, Dictionary attributes)
{
var nodeAttributes = node.Attributes;
foreach (XmlAttribute attribute in nodeAttributes)
{
var rawAttributeName = attribute.Name.Split(':').Last();
if (!attributes.Contains(new KeyValuePair(rawAttributeName, attribute.Value)))
{
return false;
}
}
return true;
}
internal void CreateElements(IEnumerable newElements, bool allowDuplicates = true)
{
if(allowDuplicates)
{
foreach (var requirement in newElements)
{
this
.CreateNewElement(
requirement.ElementPath, requirement.Attributes);
}
}
else
{
foreach (var requirement in newElements)
{
this
.CreateNewElementIfDoesntExist(
requirement.ElementPath, requirement.Attributes);
}
}
}
internal void OverrideElements(IEnumerable overrideElements)
{
foreach (var requirement in overrideElements)
{
this
.CreateOrOverrideElement(
requirement.ElementPath, requirement.Attributes);
}
}
internal void RemoveElements(IEnumerable removableElements)
{
foreach (var requirement in removableElements)
{
this
.RemoveMatchingElement(
requirement.ElementPath, requirement.Attributes);
}
}
}
}