using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using UnityEngine;
namespace Framework.Input {
/// Saves input mappings into streams and restores them again
public class XmlSaver {
/// Name of the root element in the saved input mappings
private const string RootElementName = "InputMappings";
/// Loads input mappings from a stream
/// Input mapper the mappings will be restored to
/// Stream the mappings will be loaded from
public static void Load(InputMapper inputMapper, Stream stream) {
XmlReader reader = XmlReader.Create(stream, ReaderSettings);
XmlNodeType nodeType = reader.MoveToContent();
if(nodeType != XmlNodeType.Element) {
throw new XmlException("XML document does not start with an element");
}
ReadRootInstance(inputMapper, reader);
}
/// Loads input mappings from a stream
/// Input mapper the mappings will be restored to
/// Path of a file from which the input mappings will be loaded
public static void Load(InputMapper inputMapper, string path) {
using(XmlReader reader = XmlReader.Create(path, ReaderSettings)) {
XmlNodeType nodeType = reader.MoveToContent();
if(nodeType != XmlNodeType.Element) {
throw new XmlException("XML document does not start with an element");
}
ReadRootInstance(inputMapper, reader);
}
}
/// Saves the input mappings into a stream
/// Input mapper whose mappings will be saved
/// Stream into which the input mappings that will be saved
public static void Save(InputMapper inputMapper, Stream stream) {
XmlWriter writer = XmlWriter.Create(stream, WriterSettings);
WriteRootInstance(inputMapper, writer);
writer.Flush();
}
/// Saves the input mappings into a stream
/// Input mapper whose mappings will be saved
/// Path of a file the input mappings will be saved to
public static void Save(InputMapper inputMapper, string path) {
using(XmlWriter writer = XmlWriter.Create(path, WriterSettings)) {
WriteRootInstance(inputMapper, writer);
writer.Flush();
}
}
/// Loads the input mapping from an XML document
/// Input mapper whose bindings will be loaded
/// Reader from which the bindings will be loaded
public static void ReadRootInstance(InputMapper inputMapper, XmlReader reader) {
// Whoever designed the XmlReader should a) look up 'consistency' on Wikipedia
// and b) cut down on psychedelic drugs or whatever else guides his thoughts.
if(reader.Name != RootElementName) {
throw new XmlException("XML document contains the wrong type of object");
}
readActions(inputMapper, reader);
}
/// Writes the input mapping into an XML document
/// Input mapper whose bindings will be saved
/// Writer into which the bindings will be saved
public static void WriteRootInstance(InputMapper inputMapper, XmlWriter writer) {
writer.WriteStartElement(RootElementName);
writeActions(inputMapper, writer);
writer.WriteEndElement();
}
/// Reads the actions layers of an input mapper from an XML reader
/// Input mapper whose actions will be read
/// XML reader the actions will be read from
private static void readActions(InputMapper inputMapper, XmlReader reader) {
IList loadedActions = new List();
while(reader.Read()) {
if(reader.IsStartElement()) {
if(reader.Name == "Action") {
string nameString = reader.GetAttribute("Name");
string descriptionString = reader.GetAttribute("Description");
if(string.IsNullOrEmpty(nameString)) {
throw new XmlException("Actions needs to provide a Name attribute");
}
IAction action = inputMapper.CreateAction(nameString, descriptionString);
action.RemoveAllBindings(); // In case the action already existed
readBindings(action, reader);
loadedActions.Add(action);
} else {
throw new XmlException("Only Actions may appear in the input mappings");
}
} else {
break;
}
}
// Remove all actions that weren't loaded
for(int index = 0; index < inputMapper.Actions.Count;) {
if(loadedActions.Contains(inputMapper.Actions[index])) {
++index;
} else {
inputMapper.Actions.Remove(inputMapper.Actions[index]);
}
}
}
/// Writes the actions of an input mapper into an XML writer
/// Input mapper whose actions will be written
/// XML writer the actions will be written into
private static void writeActions(InputMapper inputMapper, XmlWriter writer) {
for(int actionIndex = 0; actionIndex < inputMapper.Actions.Count; ++actionIndex) {
IAction action = inputMapper.Actions[actionIndex];
writer.WriteStartElement("Action");
writer.WriteAttributeString("Name", action.Name);
if(!string.IsNullOrEmpty(action.Description)) {
writer.WriteAttributeString("Description", action.Description);
}
writeBindings(action, writer);
writer.WriteEndElement();
}
}
/// Reads an action's bindings from an XML reader
/// Actions whose bindings will be read
/// XML reader the bindings will be read from
private static void readBindings(IAction action, XmlReader reader) {
while(reader.Read()) {
if(reader.IsStartElement()) {
if(reader.Name == "KeyBinding") {
string keyCodeString = reader.GetAttribute("KeyCode");
if(string.IsNullOrEmpty(keyCodeString)) {
throw new XmlException("KeyBinding needs to provide the KeyCode attribute");
}
var keyCode = (KeyCode)Enum.Parse(typeof(KeyCode), keyCodeString);
action.BoundKeys.Add(keyCode);
} else if(reader.Name == "AxisBinding") {
string axisNameString = reader.GetAttribute("AxisName");
string isNegativeString = reader.GetAttribute("IsNegative");
bool attributesMissing =
string.IsNullOrEmpty(axisNameString) ||
string.IsNullOrEmpty(isNegativeString);
if(attributesMissing) {
throw new XmlException(
"AxisBinding needs to provide AxisName and IsNegative attributes"
);
}
bool isNegative = bool.Parse(isNegativeString);
action.BoundJoystickAxes.Add(new JoystickAxis(axisNameString, isNegative));
} else {
throw new XmlException(
"Only KeyBindings or AxisBindings may appear under an Action"
);
}
} else {
break;
}
}
}
/// Writes an action's bindings into an XML writer
/// Action whose bindings will be written
/// XML writer the bindings will be written into
private static void writeBindings(IAction action, XmlWriter writer) {
foreach(KeyCode boundKey in action.BoundKeys) {
writer.WriteStartElement("KeyBinding");
writer.WriteAttributeString("KeyCode", boundKey.ToString());
writer.WriteEndElement();
}
foreach(JoystickAxis boundAxis in action.BoundJoystickAxes) {
writer.WriteStartElement("AxisBinding");
writer.WriteAttributeString("AxisName", boundAxis.AxisName);
writer.WriteAttributeString("IsNegative", boundAxis.IsNegative.ToString());
writer.WriteEndElement();
}
}
/// Default settings for the XML reader
private static XmlReaderSettings ReaderSettings {
get {
return new XmlReaderSettings() {
IgnoreComments = true,
CloseInput = true
};
}
}
/// Default settings for the XML writer
private static XmlWriterSettings WriterSettings {
get {
return new XmlWriterSettings() {
Encoding = Encoding.UTF8,
Indent = true,
//NewLineOnAttributes = true,
NewLineHandling = NewLineHandling.Replace,
CloseOutput = true
};
}
}
}
} // namespace Framework.Input