1
0
mirror of https://github.com/SineVector241/VoiceCraft-MCBE_Proximity_Chat.git synced 2024-11-20 10:27:45 +00:00
VoiceCraft-MCBE_Proximity_Chat/ATL/AudioData/Utils/XmlArray.cs
2024-07-13 11:16:08 +10:00

306 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using ATL.AudioData.IO;
using ATL.Logging;
using Commons;
namespace ATL.AudioData
{
internal class XmlArray
{
private readonly string prefix;
private readonly string displayPrefix;
private readonly Func<string, bool> isCollection;
private readonly Func<string, bool> isIndex;
private readonly ISet<string> structuralAttributes = new HashSet<string>();
private readonly IDictionary<string, string> defaultNamespaces = new Dictionary<string, string>();
private sealed class Node
{
public string Prefix { get; }
public string Name { get; }
public Node(string prefix, string name)
{
Prefix = prefix;
Name = name;
}
}
public XmlArray(
string prefix,
string displayPrefix,
Func<string, bool> isCollection,
Func<string, bool> isIndex
)
{
this.prefix = prefix;
this.displayPrefix = displayPrefix;
this.isCollection = isCollection;
this.isIndex = isIndex;
}
public void setStructuralAttributes(ISet<string> attrs)
{
foreach (var attr in attrs) structuralAttributes.Add(attr.ToLower());
}
public void setDefaultNamespaces(IDictionary<string, string> defaultNs)
{
foreach (var ns in defaultNs) defaultNamespaces.Add(ns.Key, ns.Value);
}
public void FromStream(Stream source, MetaDataIO meta, MetaDataIO.ReadTagParams readTagParams, long chunkSize)
{
Stack<string> position = new Stack<string>();
position.Push(displayPrefix);
long initialOffset = source.Position;
int nbSkipBegin = StreamUtils.SkipValues(source, new[] { 10, 13, 32, 0 }); // Ignore leading CR, LF, whitespace, null
source.Seek(initialOffset + chunkSize, SeekOrigin.Begin);
int nbSkipEnd = StreamUtils.SkipValuesEnd(source, new[] { 10, 13, 32, 0, 0xFF }); // Ignore ending CR, LF, whitespace, null, 0xFF
source.Seek(initialOffset + nbSkipBegin, SeekOrigin.Begin);
using MemoryStream mem = new MemoryStream((int)chunkSize - nbSkipBegin - nbSkipEnd);
StreamUtils.CopyStream(source, mem, chunkSize - nbSkipBegin - nbSkipEnd); // Isolate XML structure in a clean memory chunk
mem.Seek(0, SeekOrigin.Begin);
try
{
// Try using the declared encoding
readXml(mem, null, position, meta, readTagParams);
}
catch (Exception e) // Fallback to forcing UTF-8 when the declared encoding is invalid (e.g. "UTF - 8")
{
Utils.TraceException(e, Log.LV_DEBUG);
mem.Seek(0, SeekOrigin.Begin);
position = new Stack<string>();
position.Push(displayPrefix);
readXml(mem, Encoding.UTF8, position, meta, readTagParams);
}
}
private void readXml(
Stream mem,
Encoding encoding,
Stack<string> position,
MetaDataIO meta,
MetaDataIO.ReadTagParams readTagParams)
{
Stack<int> listDepth = new Stack<int>();
IDictionary<int, int> listCounter = new Dictionary<int, int>();
using XmlReader reader = null == encoding ? XmlReader.Create(mem) : XmlReader.Create(new StreamReader(mem, encoding));
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element: // Element start
// Core element
string key = reader.Name;
if (listDepth.Count > 0 && reader.Depth == listDepth.Peek() + 1 && !isIndex.Invoke(key))
{
var counter = listCounter[listDepth.Peek()];
key = key + "[" + counter + "]";
listCounter[listDepth.Peek()] = counter + 1;
}
if (!key.Equals(prefix, StringComparison.OrdinalIgnoreCase) && !reader.IsEmptyElement) position.Push(key);
if (isCollection.Invoke(reader.Name))
{
listDepth.Push(reader.Depth);
listCounter[reader.Depth] = 1;
}
// Attributes
if (reader.HasAttributes)
{
var here = reader.IsEmptyElement ? "." + key : "";
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
// Don't show namespaces as metadata for simplicity's sake
if (!string.IsNullOrEmpty(reader.Value) && !reader.Name.StartsWith("xmlns:"))
{
meta.SetMetaField(string.Join(".", position.ToArray().Reverse()) + here + "." + reader.Name, reader.Value, readTagParams.ReadAllMetaFrames);
}
}
}
break;
case XmlNodeType.Text:
if (!string.IsNullOrEmpty(reader.Value))
{
meta.SetMetaField(string.Join(".", position.ToArray().Reverse()), reader.Value, readTagParams.ReadAllMetaFrames);
}
break;
case XmlNodeType.EndElement: // Element end
position.Pop();
if (listDepth.Count > 0 && isCollection.Invoke(reader.Name))
{
listDepth.Pop();
}
break;
}
}
}
public int ToStream(BinaryWriter w, MetaDataIO meta)
{
// Filter eligible additionalData
IDictionary<string, string> additionalFields = meta.AdditionalFields
.Where(f => f.Key.StartsWith(displayPrefix + ".", StringComparison.OrdinalIgnoreCase))
.ToDictionary(x => x.Key, x => x.Value);
XmlWriterSettings settings = new XmlWriterSettings
{
CloseOutput = false,
Encoding = Encoding.UTF8
};
// Isolate all namespaces provided as input
var nsKeys = meta.AdditionalFields.Keys.Where(k => k.Contains("xmlns:")).ToHashSet();
var namespaces = new Dictionary<string, string>();
foreach (var nsKey in nsKeys)
{
namespaces.Add(nsKey.Split(':')[^1], additionalFields[nsKey]);
}
// Complete with default namespaces if there's any missing
foreach (var defaultNs in defaultNamespaces)
{
if (!namespaces.ContainsKey(defaultNs.Key)) namespaces.Add(defaultNs.Key, defaultNs.Value);
}
using XmlWriter writer = XmlWriter.Create(w.BaseStream, settings);
writer.WriteStartDocument();
var node = parseNode(prefix);
if (null == node.Prefix) writer.WriteStartElement(node.Name);
else writer.WriteStartElement(node.Prefix, node.Name, namespaces[node.Prefix]);
// == Register all useful namespaces on the top level element
// Detect all used namespaces
ISet<string> usedNamespaces = new HashSet<string>();
var nonNsKeys = meta.AdditionalFields.Keys.Where(k => !k.Contains("xmlns:")).ToHashSet();
foreach (var nsKey in nonNsKeys)
{
var parts = nsKey.Split('.').Where(s => s.Contains(':'));
foreach (var part in parts) usedNamespaces.Add(part.Split(':')[0]);
}
// Register them on the top level element
foreach (var ns in usedNamespaces)
{
if (!namespaces.ContainsKey(ns))
{
LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Namespace not found : " + ns);
continue;
}
writer.WriteAttributeString(ns, "http://www.w3.org/2000/xmlns/", namespaces[ns]);
}
// Path notes : key = node path; value = node name
Dictionary<string, string> pathNodes = new Dictionary<string, string>();
List<string> previousPathNodes = new List<string>();
Stack<string> openedNodes = new Stack<string>();
var valuesWritten = 0;
foreach (var key in additionalFields.Keys
.Where(key => key.StartsWith(displayPrefix + "."))
.Where(key => !nsKeys.Contains(key))
)
{
// Create the list of path nodes
List<string> singleNodes = new List<string>(key.Split('.'));
singleNodes.RemoveAt(0);// Remove the root node
StringBuilder nodePrefix = new StringBuilder();
pathNodes.Clear();
foreach (string nodeName in singleNodes)
{
nodePrefix.Append('.').Append(nodeName);
pathNodes.Add(nodePrefix.ToString(), nodeName);
}
// Close all terminated (i.e. previously opened and not present in current path) nodes in reverse order
if (openedNodes.Count > 0)
{
var openedNode = openedNodes.Peek();
while (!pathNodes.ContainsKey(openedNode))
{
writer.WriteEndElement();
openedNodes.Pop();
if (0 == openedNodes.Count) break;
openedNode = openedNodes.Peek();
}
}
// Open all new (i.e. non present in previous path) nodes
foreach (string nodePath in pathNodes.Keys)
{
if (previousPathNodes.Contains(nodePath)) continue;
var subkey = pathNodes[nodePath];
if (subkey.Equals(singleNodes[^1])) continue; // Last node is a leaf, not a node
node = parseNode(subkey);
openedNodes.Push(nodePath);
if (null == node.Prefix) writer.WriteStartElement(node.Name);
else writer.WriteStartElement(node.Prefix, node.Name, namespaces[node.Prefix]);
}
// Write the last node (=leaf) as a proper value if it does not belong to structural attributes
node = parseNode(singleNodes[^1]);
if (structuralAttributes.Contains(singleNodes[^1].ToLower()))
{
if (null == node.Prefix) writer.WriteAttributeString(node.Name, additionalFields[key]);
else writer.WriteAttributeString(node.Prefix, node.Name, namespaces[node.Prefix], additionalFields[key]);
}
else if (previousPathNodes.Contains(key[displayPrefix.Length..]))
{
writer.WriteString(additionalFields[key]);
}
else
{
// ElementString is just a Helper for StartElement + String + EndElement
if (null == node.Prefix) writer.WriteElementString(node.Name, additionalFields[key]);
else writer.WriteElementString(node.Prefix, node.Name, namespaces[node.Prefix], additionalFields[key]);
}
valuesWritten++;
previousPathNodes = pathNodes.Keys.ToList();
}
// Close all terminated paths
while (openedNodes.Count > 0)
{
writer.WriteEndElement();
openedNodes.Pop();
}
return valuesWritten;
}
/**
* Returns prefix and name; prefix might be null
*/
private static Node parseNode(string key)
{
string pfx = null;
string name = key;
if (name.Contains('[')) name = name[..name.IndexOf('[')]; // Remove [x]'s
int pfxIdfx = name.IndexOf(':');
if (pfxIdfx > -1)
{
pfx = name[..pfxIdfx];
name = name[(pfxIdfx + 1)..];
}
return new Node(pfx, name);
}
}
}