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