Cleaning up some things.
This commit is contained in:
126
XmlFormat/IO/XmlFileReader.cs
Normal file
126
XmlFormat/IO/XmlFileReader.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlRobloxFileReader
|
||||
{
|
||||
private static Func<string, Exception> createErrorHandler(string label)
|
||||
{
|
||||
var errorHandler = new Func<string, Exception>((message) =>
|
||||
{
|
||||
string contents = $"XmlDataReader.{label}: {message}";
|
||||
return new Exception(contents);
|
||||
});
|
||||
|
||||
return errorHandler;
|
||||
}
|
||||
|
||||
public static void ReadSharedStrings(XmlNode sharedStrings, XmlRobloxFile file)
|
||||
{
|
||||
var error = createErrorHandler("ReadSharedStrings");
|
||||
|
||||
if (sharedStrings.Name != "SharedStrings")
|
||||
throw error("Provided XmlNode's class should be 'SharedStrings'!");
|
||||
|
||||
foreach (XmlNode sharedString in sharedStrings)
|
||||
{
|
||||
if (sharedString.Name == "SharedString")
|
||||
{
|
||||
XmlNode md5Node = sharedString.Attributes.GetNamedItem("md5");
|
||||
|
||||
if (md5Node == null)
|
||||
throw error("Got a SharedString without an 'md5' attribute!");
|
||||
|
||||
string key = md5Node.InnerText;
|
||||
string value = sharedString.InnerText.Replace("\n", "");
|
||||
|
||||
file.SharedStrings.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
||||
{
|
||||
var error = createErrorHandler("ReadProperties");
|
||||
|
||||
if (propsNode.Name != "Properties")
|
||||
throw error("Provided XmlNode's class should be 'Properties'!");
|
||||
|
||||
foreach (XmlNode propNode in propsNode.ChildNodes)
|
||||
{
|
||||
string propType = propNode.Name;
|
||||
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
||||
|
||||
if (propName == null)
|
||||
throw error("Got a property node without a 'name' attribute!");
|
||||
|
||||
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
||||
|
||||
if (tokenHandler != null)
|
||||
{
|
||||
Property prop = new Property()
|
||||
{
|
||||
Name = propName.InnerText,
|
||||
Instance = instance,
|
||||
XmlToken = propType
|
||||
};
|
||||
|
||||
if (!tokenHandler.ReadProperty(prop, propNode))
|
||||
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
|
||||
|
||||
instance.AddProperty(ref prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No IXmlPropertyToken found for property type: " + propType + '!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file)
|
||||
{
|
||||
var error = createErrorHandler("ReadInstance");
|
||||
|
||||
// Process the instance itself
|
||||
if (instNode.Name != "Item")
|
||||
throw error("Provided XmlNode's name should be 'Item'!");
|
||||
|
||||
XmlNode classToken = instNode.Attributes.GetNamedItem("class");
|
||||
if (classToken == null)
|
||||
throw error("Got an Item without a defined 'class' attribute!");
|
||||
|
||||
Instance inst = new Instance() { ClassName = classToken.InnerText };
|
||||
|
||||
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
|
||||
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
||||
|
||||
if (refToken != null && file != null)
|
||||
{
|
||||
string referent = refToken.InnerText;
|
||||
inst.XmlReferent = referent;
|
||||
|
||||
if (file.Instances.ContainsKey(referent))
|
||||
throw error("Got an Item with a duplicate 'referent' attribute!");
|
||||
|
||||
file.Instances.Add(referent, inst);
|
||||
}
|
||||
|
||||
// Process the child nodes of this instance.
|
||||
foreach (XmlNode childNode in instNode.ChildNodes)
|
||||
{
|
||||
if (childNode.Name == "Properties")
|
||||
{
|
||||
ReadProperties(inst, childNode);
|
||||
}
|
||||
else if (childNode.Name == "Item")
|
||||
{
|
||||
Instance child = ReadInstance(childNode, file);
|
||||
child.Parent = inst;
|
||||
}
|
||||
}
|
||||
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
}
|
191
XmlFormat/IO/XmlFileWriter.cs
Normal file
191
XmlFormat/IO/XmlFileWriter.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
using RobloxFiles.XmlFormat.PropertyTokens;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlRobloxFileWriter
|
||||
{
|
||||
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
|
||||
{
|
||||
Indent = true,
|
||||
IndentChars = "\t",
|
||||
NewLineChars = "\r\n",
|
||||
Encoding = Encoding.UTF8,
|
||||
OmitXmlDeclaration = true
|
||||
};
|
||||
|
||||
public static string CreateReferent()
|
||||
{
|
||||
Guid referentGuid = Guid.NewGuid();
|
||||
|
||||
string referent = "RBX" + referentGuid
|
||||
.ToString()
|
||||
.ToUpper();
|
||||
|
||||
return referent.Replace("-", "");
|
||||
}
|
||||
|
||||
private static string GetEnumName<T>(T item) where T : struct
|
||||
{
|
||||
return Enum.GetName(typeof(T), item);
|
||||
}
|
||||
|
||||
internal static void RecordInstances(XmlRobloxFile file, Instance inst)
|
||||
{
|
||||
foreach (Instance child in inst.GetChildren())
|
||||
RecordInstances(file, child);
|
||||
|
||||
if (inst.XmlReferent == "")
|
||||
inst.XmlReferent = CreateReferent();
|
||||
|
||||
file.Instances.Add(inst.XmlReferent, inst);
|
||||
}
|
||||
|
||||
public static XmlElement CreateRobloxElement(XmlDocument doc)
|
||||
{
|
||||
XmlElement roblox = doc.CreateElement("roblox");
|
||||
roblox.SetAttribute("version", "4");
|
||||
doc.AppendChild(roblox);
|
||||
|
||||
XmlElement externalNull = doc.CreateElement("External");
|
||||
roblox.AppendChild(externalNull);
|
||||
externalNull.InnerText = "null";
|
||||
|
||||
XmlElement externalNil = doc.CreateElement("External");
|
||||
roblox.AppendChild(externalNil);
|
||||
externalNil.InnerText = "nil";
|
||||
|
||||
return roblox;
|
||||
}
|
||||
|
||||
public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file)
|
||||
{
|
||||
string propType = prop.XmlToken;
|
||||
|
||||
if (prop.XmlToken.Length == 0)
|
||||
{
|
||||
propType = GetEnumName(prop.Type);
|
||||
|
||||
switch (prop.Type)
|
||||
{
|
||||
case PropertyType.CFrame:
|
||||
case PropertyType.Quaternion:
|
||||
propType = "CoordinateFrame";
|
||||
break;
|
||||
case PropertyType.Enum:
|
||||
propType = "token";
|
||||
break;
|
||||
case PropertyType.Rect:
|
||||
propType = "Rect2D";
|
||||
break;
|
||||
case PropertyType.Int:
|
||||
case PropertyType.Bool:
|
||||
case PropertyType.Float:
|
||||
case PropertyType.Int64:
|
||||
case PropertyType.Double:
|
||||
propType = propType.ToLower();
|
||||
break;
|
||||
case PropertyType.String:
|
||||
propType = (prop.HasRawBuffer ? "BinaryString" : "string");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType);
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
Console.WriteLine("XmlDataWriter.WriteProperty: No token handler found for property type: {0}", propType);
|
||||
return null;
|
||||
}
|
||||
|
||||
XmlElement propElement = doc.CreateElement(propType);
|
||||
propElement.SetAttribute("name", prop.Name);
|
||||
|
||||
XmlNode propNode = propElement;
|
||||
handler.WriteProperty(prop, doc, propNode);
|
||||
|
||||
if (propNode.ParentNode != null)
|
||||
{
|
||||
XmlNode newNode = propNode.ParentNode;
|
||||
newNode.RemoveChild(propNode);
|
||||
propNode = newNode;
|
||||
}
|
||||
|
||||
if (prop.Type == PropertyType.SharedString)
|
||||
{
|
||||
string data = prop.Value.ToString();
|
||||
byte[] buffer = Convert.FromBase64String(data);
|
||||
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] hash = md5.ComputeHash(buffer);
|
||||
string key = Convert.ToBase64String(hash);
|
||||
|
||||
if (!file.SharedStrings.ContainsKey(key))
|
||||
file.SharedStrings.Add(key, data);
|
||||
|
||||
propNode.InnerText = key;
|
||||
}
|
||||
}
|
||||
|
||||
return propNode;
|
||||
}
|
||||
|
||||
public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file)
|
||||
{
|
||||
XmlElement instNode = doc.CreateElement("Item");
|
||||
instNode.SetAttribute("class", instance.ClassName);
|
||||
instNode.SetAttribute("referent", instance.XmlReferent);
|
||||
|
||||
XmlElement propsNode = doc.CreateElement("Properties");
|
||||
instNode.AppendChild(propsNode);
|
||||
|
||||
var props = instance.Properties;
|
||||
|
||||
foreach (string propName in props.Keys)
|
||||
{
|
||||
Property prop = props[propName];
|
||||
XmlNode propNode = WriteProperty(prop, doc, file);
|
||||
propsNode.AppendChild(propNode);
|
||||
}
|
||||
|
||||
foreach (Instance child in instance.GetChildren())
|
||||
{
|
||||
XmlNode childNode = WriteInstance(child, doc, file);
|
||||
instNode.AppendChild(childNode);
|
||||
}
|
||||
|
||||
return instNode;
|
||||
}
|
||||
|
||||
public static XmlNode WriteSharedStrings(XmlDocument doc, XmlRobloxFile file)
|
||||
{
|
||||
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
|
||||
|
||||
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
||||
var bufferProp = new Property("SharedString", PropertyType.String);
|
||||
|
||||
foreach (string md5 in file.SharedStrings.Keys)
|
||||
{
|
||||
XmlElement sharedString = doc.CreateElement("SharedString");
|
||||
sharedString.SetAttribute("md5", md5);
|
||||
|
||||
string data = file.SharedStrings[md5];
|
||||
byte[] buffer = Convert.FromBase64String(data);
|
||||
|
||||
bufferProp.RawBuffer = buffer;
|
||||
binaryWriter.WriteProperty(bufferProp, doc, sharedString);
|
||||
|
||||
sharedStrings.AppendChild(sharedString);
|
||||
}
|
||||
|
||||
return sharedStrings;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user