// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using Tools.DotNETCommon; namespace UnrealBuildTool { /// /// Attribute to annotate fields in type that can be set using XML configuration system. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class XmlConfigAttribute : Attribute { } /// /// Loads data from disk XML files and stores it in memory. /// public static class XmlConfigLoader { /// /// Resets given config class. /// /// The class to reset. public static void Reset() { Load(typeof(ConfigClass)); } /// /// Creates XML element for given config class. /// /// A type of the config class. /// XML element representing config class. private static XElement CreateConfigTypeXMLElement(Type ConfigType) { var NS = XNamespace.Get("https://www.unrealengine.com/BuildConfiguration"); return new XElement(NS + ConfigType.Name, from Field in GetConfigurableFields(ConfigType) where !(Field.FieldType == typeof(string) && Field.GetValue(null) == null) select CreateFieldXMLElement(Field)); } /// /// Gets XML representing current config. /// /// XML representing current config. private static XDocument GetConfigXML() { var NS = XNamespace.Get("https://www.unrealengine.com/BuildConfiguration"); return new XDocument( new XElement(NS + "Configuration", from ConfigType in GetAllConfigurationTypes() select CreateConfigTypeXMLElement(ConfigType) ) ); } /// /// Gets default config XML. /// /// Default config XML. public static string GetDefaultXML() { var Comment = @" ######################################################################### # # # This is an XML with default UBT configuration. If you want to # # override it, create the same file in the locations c. or d. # # (see below). DONT'T CHANGE CONTENTS OF THIS FILE! # # # ######################################################################### The syntax of this file is: Value First item Second item ... Last item ... ... ... ... There are four possible location for this file: a. UE4/Engine/Programs/UnrealBuildTool b. UE4/Engine/Programs/NotForLicensees/UnrealBuildTool c. UE4/Engine/Saved/UnrealBuildTool d. My Documents/Unreal Engine/UnrealBuildTool The UBT is looking for it in all four places in the given order and overrides already read data with the loaded ones, hence d. has the priority. Not defined classes and fields are left alone. "; var DefaultXml = GetConfigXML(); DefaultXml.AddFirst(new XComment(Comment)); return WriteToBuffer(DefaultXml); } /// /// Loads data for a given config class. /// /// A type of a config class. public static void Load(Type Class) { InvokeIfExists(Class, "LoadDefaults"); InvokeIfExists(Class, "Reset"); XmlConfigLoaderClassData ClassData; if (Data.TryGetValue(Class, out ClassData)) { ClassData.LoadXmlData(); } InvokeIfExists(Class, "PostReset"); } /// /// Builds xsd for BuildConfiguration.xml files. /// /// Content of the xsd file. public static string BuildXSD() { // all elements use this namespace var NS = XNamespace.Get("http://www.w3.org/2001/XMLSchema"); return WriteToBuffer(new XDocument( // define the root element that declares the schema and target namespace it is validating. new XElement(NS + "schema", new XAttribute("targetNamespace", "https://www.unrealengine.com/BuildConfiguration"), new XAttribute("elementFormDefault", "qualified"), new XElement(NS + "element", new XAttribute("name", "Configuration"), new XElement(NS + "complexType", new XElement(NS + "all", // loop over all public types in UBT assembly from ConfigType in GetAllConfigurationTypes() // find all public static fields of intrinsic types (and a few extra) let PublicStaticFields = GetConfigurableFields(ConfigType).ToList() where PublicStaticFields.Count > 0 select new XElement(NS + "element", new XAttribute("name", ConfigType.Name), new XAttribute("minOccurs", "0"), new XAttribute("maxOccurs", "1"), new XElement(NS + "complexType", new XElement(NS + "all", from Field in PublicStaticFields select CreateXSDElementForField(Field) ) ) ) ) ) ) ) )); } /// /// Initializing data and resets all found classes. /// public static void Init() { // No one should try to refereence configuration values until the config files are loaded. // The XmlConfig system itself will SET config values below, then anyone can read them. // But our static constructor checks can't differentiate this, so we just allow reads starting now, // right before the XML files are loaded. UnrealBuildTool.bIsSafeToReferenceConfigurationValues = true; OverwriteIfDifferent(GetXSDPath(), BuildXSD()); LoadDefaults(); CreateUserXmlConfigTemplate(); LoadData(); foreach (var ConfClass in Data.Keys) { Load(ConfClass); } } /// /// Overwrites file at FilePath with the Content if the content was /// different. If the file doesn't exist it creates file with the /// Content. /// /// File to check, overwrite or create. /// Content to fill the file with. /// If file can and should be read-only? private static void OverwriteIfDifferent(string FilePath, string Content, bool bReadOnlyFile = false) { if (FileDifferentThan(FilePath, Content)) { if (bReadOnlyFile && File.Exists(FilePath)) { var Attributes = File.GetAttributes(FilePath); if (Attributes.HasFlag(FileAttributes.ReadOnly)) { File.SetAttributes(FilePath, Attributes & ~FileAttributes.ReadOnly); } } Directory.CreateDirectory(Path.GetDirectoryName(FilePath)); File.WriteAllText(FilePath, Content, Encoding.UTF8); if (bReadOnlyFile) { File.SetAttributes(FilePath, File.GetAttributes(FilePath) | FileAttributes.ReadOnly); } } } /// /// Tells if file at given path has different content than given. /// /// Path of the file to check. /// Content to check. /// True if file at FilePath has different content than Content. False otherwise. private static bool FileDifferentThan(string FilePath, string Content) { if (!File.Exists(FilePath)) { return true; } return !File.ReadAllText(FilePath, Encoding.UTF8).Equals(Content, StringComparison.InvariantCulture); } /// /// Loads default values for all configuration classes in assembly. /// private static void LoadDefaults() { foreach (var ConfigType in GetAllConfigurationTypes()) { InvokeIfExists(ConfigType, "LoadDefaults"); } } /// /// Cache entry class to store loaded info for given class. /// class XmlConfigLoaderClassData { /// /// Loads previously stored data into class. /// public void LoadXmlData() { foreach (var DataPair in DataMap) { DataPair.Key.SetValue(null, DataPair.Value); } foreach (var PropertyPair in PropertyMap) { PropertyPair.Key.SetValue(null, PropertyPair.Value, null); } } /// /// Adds or overrides value in the cache. /// /// The field info of the class. /// The value to store. public void SetValue(FieldInfo Field, object Value) { if (DataMap.ContainsKey(Field)) { DataMap[Field] = Value; } else { DataMap.Add(Field, Value); } } /// /// Adds or overrides value in the cache. /// /// The property info of the class. /// The value to store. public void SetValue(PropertyInfo Property, object Value) { if (PropertyMap.ContainsKey(Property)) { PropertyMap[Property] = Value; } else { PropertyMap.Add(Property, Value); } } // Loaded data map. Dictionary DataMap = new Dictionary(); // Loaded data map. Dictionary PropertyMap = new Dictionary(); } /// /// Class that stores information about possible BuildConfiguration.xml /// location and its name that will be displayed in IDE. /// public class XmlConfigLocation { /// /// Returns location of the BuildConfiguration.xml. /// /// Location of the BuildConfiguration.xml. private static string GetConfigLocation(IEnumerable PossibleLocations, out bool bExists) { if (PossibleLocations.Count() == 0) { throw new ArgumentException("Empty possible locations", "PossibleLocations"); } const string ConfigXmlFileName = "BuildConfiguration.xml"; // Filter out non-existing var ExistingLocations = new List(); foreach (var PossibleLocation in PossibleLocations) { var FilePath = Path.Combine(PossibleLocation, ConfigXmlFileName); if (File.Exists(FilePath)) { ExistingLocations.Add(FilePath); } } if (ExistingLocations.Count == 0) { bExists = false; return Path.Combine(PossibleLocations.First(), ConfigXmlFileName); } bExists = true; if (ExistingLocations.Count == 1) { return ExistingLocations.First(); } // Choose most recently used from existing. return ExistingLocations.OrderBy(Location => File.GetLastWriteTime(Location)).Last(); } // Possible location of the config file in the file system. public string FSLocation { get; private set; } // IDE folder name that will contain this location if file will be found. public string IDEFolderName { get; private set; } // Tells if UBT has to create a template config file if it does not exist in the location. public bool bCreateIfDoesNotExist { get; private set; } // Tells if config file exists in this location. public bool bExists { get; protected set; } public XmlConfigLocation(string[] FSLocations, string IDEFolderName, bool bCreateIfDoesNotExist = false) { bool bExists; this.FSLocation = GetConfigLocation(FSLocations, out bExists); this.IDEFolderName = IDEFolderName; this.bCreateIfDoesNotExist = bCreateIfDoesNotExist; this.bExists = bExists; } public XmlConfigLocation(string FSLocation, string IDEFolderName, bool bCreateIfDoesNotExist = false) : this(new string[] { FSLocation }, IDEFolderName, bCreateIfDoesNotExist) { } /// /// Creates template file in the FS location. /// public virtual void CreateUserXmlConfigTemplate() { try { Directory.CreateDirectory(Path.GetDirectoryName(FSLocation)); var FilePath = Path.Combine(FSLocation); const string TemplateContent = "" + "\n" + " \n" + " \n" + "\n"; File.WriteAllText(FilePath, TemplateContent); bExists = true; } catch (Exception) { // Ignore quietly. } } /// /// Tells if procedure should try to create file for this location. /// /// True if procedure should try to create file for this location. False otherwise. public virtual bool IfShouldCreateFile() { return bCreateIfDoesNotExist && !bExists; } } /// /// Class that stores information about possible default BuildConfiguration.xml. /// public class XmlDefaultConfigLocation : XmlConfigLocation { public XmlDefaultConfigLocation(string FSLocation) : base(FSLocation, "Default", true) { } /// /// Creates template file in the FS location. /// public override void CreateUserXmlConfigTemplate() { try { Directory.CreateDirectory(Path.GetDirectoryName(FSLocation)); var FilePath = Path.Combine(FSLocation); OverwriteIfDifferent(FilePath, GetDefaultXML(), true); bExists = true; } catch (Exception) { // Ignore quietly. } } /// /// Tells if procedure should try to create file for this location. /// /// True if procedure should try to create file for this location. False otherwise. public override bool IfShouldCreateFile() { return true; } } public static readonly XmlConfigLocation[] ConfigLocationHierarchy; static XmlConfigLoader() { /* * There are four possible location for this file: * a. UE4/Engine/Programs/UnrealBuildTool * b. UE4/Engine/Programs/NotForLicensees/UnrealBuildTool * c. UE4/Engine/Saved/UnrealBuildTool * d. /Unreal Engine/UnrealBuildTool -- the location is * chosen by existence and if both exist most recently used. * * The UBT is looking for it in all four places in the given order and * overrides already read data with the loaded ones, hence d. has the * priority. Not defined classes and fields are left alone. */ var UE4EnginePath = new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().GetOriginalLocation()), "..", "..")).FullName; ConfigLocationHierarchy = new XmlConfigLocation[] { new XmlDefaultConfigLocation(Path.Combine(UE4EnginePath, "Programs", "UnrealBuildTool")), new XmlConfigLocation(Path.Combine(UE4EnginePath, "Programs", "NotForLicensees", "UnrealBuildTool"), "NotForLicensees"), new XmlConfigLocation(Path.Combine(UE4EnginePath, "Saved", "UnrealBuildTool"), "User", true), new XmlConfigLocation(new string[] { Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Unreal Engine", "UnrealBuildTool"), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Unreal Engine", "UnrealBuildTool") }, "Global", true) }; } /// /// Loads BuildConfiguration from XML into memory. /// private static void LoadData() { foreach (var PossibleConfigLocation in ConfigLocationHierarchy) { if (!PossibleConfigLocation.bExists) { continue; } try { LoadDataFromFile(PossibleConfigLocation.FSLocation); } catch (Exception Ex) { Console.WriteLine("Problem parsing {0}:\n {1}", PossibleConfigLocation.FSLocation, Ex.ToString()); } } } /// /// Creates XML files for all known XmlConfigLocations which return IfShouldCreateFile()==true. Files /// will be filled with default content chosen by the XmlConfigLocation implementation. /// private static void CreateUserXmlConfigTemplate() { foreach (var PossibleConfigLocation in ConfigLocationHierarchy) { if (!PossibleConfigLocation.IfShouldCreateFile()) { continue; } PossibleConfigLocation.CreateUserXmlConfigTemplate(); } } /// /// Reads config schema from XSD file. /// private static XmlSchema ReadConfigSchema() { var Settings = new XmlReaderSettings(); Settings.ValidationType = ValidationType.DTD; using (var SR = new StringReader(File.ReadAllText(GetXSDPath(), Encoding.UTF8))) { using (var XR = XmlReader.Create(SR, Settings)) { return XmlSchema.Read(XR, (object Sender, System.Xml.Schema.ValidationEventArgs EventArgs) => { throw new BuildException("XmlConfigLoader: Reading config XSD failed:\n{0}({1}): {2}", new Uri(EventArgs.Exception.SourceUri).LocalPath, EventArgs.Exception.LineNumber, EventArgs.Message); }); } } } // Stores read XSD schema for config files. private static XmlSchema ConfigSchemaCache = null; /// /// Gets config XSD schema. /// private static XmlSchema GetConfigSchema() { if (ConfigSchemaCache == null) { ConfigSchemaCache = ReadConfigSchema(); } return ConfigSchemaCache; } /// /// Sets values of this class with values from given XML file. /// /// The path to the file with values. private static void LoadDataFromFile(string ConfigurationXmlPath) { var ConfigDocument = new XmlDocument(); var NS = new XmlNamespaceManager(ConfigDocument.NameTable); NS.AddNamespace("ns", "https://www.unrealengine.com/BuildConfiguration"); var ReaderSettings = new XmlReaderSettings(); ReaderSettings.ValidationEventHandler += (object Sender, System.Xml.Schema.ValidationEventArgs EventArgs) => { throw new BuildException("XmlConfigLoader: Reading config XML failed:\n{0}({1}): {2}", ConfigurationXmlPath, EventArgs.Exception.LineNumber, EventArgs.Message); }; ReaderSettings.ValidationType = ValidationType.Schema; ReaderSettings.Schemas.Add(GetConfigSchema()); using (var SR = new StringReader(File.ReadAllText(ConfigurationXmlPath, Encoding.UTF8))) { var Reader = XmlReader.Create(SR, ReaderSettings); ConfigDocument.Load(Reader); } var XmlClasses = ConfigDocument.DocumentElement.SelectNodes("/ns:Configuration/*", NS); if (XmlClasses.Count == 0) { if (ConfigDocument.DocumentElement.Name == "Configuration") { ConfigDocument.DocumentElement.SetAttribute("xmlns", "https://www.unrealengine.com/BuildConfiguration"); var NSDoc = new XmlDocument(); NSDoc.LoadXml(ConfigDocument.OuterXml); try { File.WriteAllText(ConfigurationXmlPath, WriteToBuffer(NSDoc)); } catch (Exception) { // Ignore gently. } XmlClasses = NSDoc.DocumentElement.SelectNodes("/ns:Configuration/*", NS); } } foreach (XmlNode XmlClass in XmlClasses) { var ClassType = Type.GetType("UnrealBuildTool." + XmlClass.Name); if (ClassType == null) { Log.TraceVerbose("XmlConfig Loading: class '{0}' doesn't exist.", XmlClass.Name); continue; } if (!IsConfigurableClass(ClassType)) { Log.TraceVerbose("XmlConfig Loading: class '{0}' is not allowed to be configured using XML system.", XmlClass.Name); continue; } XmlConfigLoaderClassData ClassData; if (!Data.TryGetValue(ClassType, out ClassData)) { ClassData = new XmlConfigLoaderClassData(); Data.Add(ClassType, ClassData); } var XmlFields = XmlClass.SelectNodes("*"); foreach (XmlNode XmlField in XmlFields) { FieldInfo Field = ClassType.GetField(XmlField.Name); // allow settings in the .xml that don't exist, as another branch may have it, and can share this file from Documents if (Field == null) { PropertyInfo Property = ClassType.GetProperty(XmlField.Name); if (Property != null) { if (!IsConfigurable(Property)) { throw new BuildException("BuildConfiguration Loading: property '{0}' is either non-public, non-static or not-xml-configurable.", XmlField.Name); } ClassData.SetValue(Property, ParseFieldData(Property.PropertyType, XmlField.InnerText)); } continue; } if (!IsConfigurableField(Field)) { throw new BuildException("BuildConfiguration Loading: field '{0}' is either non-public, non-static or not-xml-configurable.", XmlField.Name); } if (Field.FieldType.IsArray) { // If the type is an array type get items for it. var XmlItems = XmlField.SelectNodes("ns:Item", NS); // Get the C# type of the array. var ItemType = Field.FieldType.GetElementType(); // Create the array according to the ItemType. var OutputArray = Array.CreateInstance(ItemType, XmlItems.Count); int Id = 0; foreach (XmlNode XmlItem in XmlItems) { // Append values to the OutputArray. OutputArray.SetValue(ParseFieldData(ItemType, XmlItem.InnerText), Id++); } ClassData.SetValue(Field, OutputArray); } else { ClassData.SetValue(Field, ParseFieldData(Field.FieldType, XmlField.InnerText)); } } } } private static object ParseFieldData(Type FieldType, string Text) { if (FieldType.Equals(typeof(System.String))) { return Text; } else { // Declaring parameters array used by TryParse method. // Second parameter is "out", so you have to just // assign placeholder null to it. object ParsedValue; if (!TryParse(FieldType, Text, out ParsedValue)) { throw new BuildException("BuildConfiguration Loading: Parsing {0} value from \"{1}\" failed.", FieldType.Name, Text); } // If Invoke returned true, the second object of the // parameters array is set to the parsed value. return ParsedValue; } } /// /// Emulates TryParse behavior on custom type. If the type implements /// Parse(string, IFormatProvider) or Parse(string) static method uses /// one of them to parse with preference of the one with format /// provider (but passes invariant culture). /// /// Type to parse. /// String representation of the value. /// Output parsed value. /// True if parsing succeeded. False otherwise. private static bool TryParse(Type ParsingType, string UnparsedValue, out object ParsedValue) { // Getting Parse method for FieldType which is required, // if it doesn't exists for complex type, author should add // one. The signature should be one of: // static T Parse(string Input, IFormatProvider Provider) or // static T Parse(string Input) // where T is containing type. // The one with format provider is preferred and invoked with // InvariantCulture. bool bWithCulture = true; var ParseMethod = ParsingType.GetMethod("Parse", new Type[] { typeof(System.String), typeof(IFormatProvider) }); if (ParseMethod == null) { ParseMethod = ParsingType.GetMethod("Parse", new Type[] { typeof(System.String) }); bWithCulture = false; } if (ParseMethod == null) { throw new BuildException("BuildConfiguration Loading: Parsing of the type {0} is not supported.", ParsingType.Name); } var ParametersList = new List { UnparsedValue }; if (bWithCulture) { ParametersList.Add(CultureInfo.InvariantCulture); } try { ParsedValue = ParseMethod.Invoke(null, ParametersList.ToArray()); } catch (Exception e) { if (e is TargetInvocationException && ( e.InnerException is ArgumentNullException || e.InnerException is FormatException || e.InnerException is OverflowException ) ) { ParsedValue = null; return false; } throw; } return true; } /// /// Tells if given class is XML configurable. /// /// Class to check. /// True if the class is configurable using XML system. Otherwise false. private static bool IsConfigurableClass(Type Class) { return Class.GetFields().Any((Field) => IsConfigurableField(Field)) || Class.GetProperties().Any(prop => IsConfigurable(prop)); } /// /// Tells if given property is XML configurable. /// /// Property to check. /// True if the property is configurable using XML system. Otherwise false. private static bool IsConfigurable(PropertyInfo Property) { return Property.GetCustomAttributes(typeof(XmlConfigAttribute), false).Length > 0 && Property.CanWrite && Property.GetSetMethod().IsStatic && Property.GetSetMethod().IsPublic; } /// /// Tells if given field is XML configurable. /// /// field to check. /// True if the field is configurable using XML system. Otherwise false. private static bool IsConfigurableField(FieldInfo Field) { return Field.IsStatic && Field.IsPublic && Field.GetCustomAttributes(typeof(XmlConfigAttribute), false).Length > 0; } /// /// Gets all types that can be configured using this XML system. /// /// Enumerable of types that can be configured using this xml system. private static IEnumerable GetAllConfigurationTypes() { return Assembly.GetExecutingAssembly().GetTypes().Where((Class) => IsConfigurableClass(Class)); } /// /// Invokes a public static parameterless method from type if it exists. /// /// Class type to look the method in. /// Name of the method. private static void InvokeIfExists(Type ClassType, string MethodName) { var Method = ClassType.GetMethod(MethodName, new Type[] { }, new ParameterModifier[] { }); if (Method != null && Method.IsPublic && Method.IsStatic) { Method.Invoke(null, new object[] { }); } } /// /// Enumerates all fields that are configurable using this system. /// /// Type of the class to enumrate field for. /// Enumerable fields that are configurable using this system. private static IEnumerable GetConfigurableFields(Type ClassType) { return ClassType.GetFields().Where((Field) => IsConfigurableField(Field)); } /// /// Gets xsd type string for the field type. /// /// Field type to get xsd string for. /// String representation of xsd type corresponding given field type. private static string GetFieldXSDType(Type FieldType) { var TypeMap = new Dictionary { { typeof(string), "string" }, { typeof(bool), "boolean" }, { typeof(int), "int" }, { typeof(long), "long" }, { typeof(DateTime), "dateTime" }, { typeof(float), "float" }, { typeof(double), "double" } }; return TypeMap.ContainsKey(FieldType) ? TypeMap[FieldType] : FieldType.Name.ToLowerInvariant(); } /// /// Gets xml representation of an array field value. /// /// Array field to represent. /// Array field value as XML elements. private static XElement[] GetArrayFieldXMLValue(FieldInfo Field) { var NS = XNamespace.Get("http://www.w3.org/2001/XMLSchema"); var Array = Field.GetValue(null) as IEnumerable; var ElementList = new List(); foreach (var Element in Array) { ElementList.Add(new XElement(NS + "Item", GetObjectXMLValue(Field.FieldType.GetElementType(), Element))); } return ElementList.ToArray(); } /// /// XML representation of a regular field value. /// /// Field to represent value. /// Field value as XML. private static object GetFieldXMLValue(FieldInfo Field) { return GetObjectXMLValue(Field.FieldType, Field.GetValue(null)); } /// /// Gets custom C# object XML string representation. /// /// The type of the field. /// The value. /// String representation. private static object GetObjectXMLValue(Type FieldType, object Obj) { if (Obj == null && FieldType == typeof(string)) { return ""; } if (FieldType == typeof(bool)) { return (bool)Obj ? "true" : "false"; } return Obj; } /// /// Gets standard BuildConfiguration.xml schema path. /// /// Standard BuildConfiguration.xml schema path. public static string GetXSDPath() { return new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().GetOriginalLocation()), "..", "..", "Saved", "UnrealBuildTool", "BuildConfiguration.Schema.xsd")).FullName; } private static XElement CreateXSDElementForField(FieldInfo Field) { var NS = XNamespace.Get("http://www.w3.org/2001/XMLSchema"); if (Field.FieldType.IsArray) { return new XElement(NS + "element", new XAttribute("name", Field.Name), new XAttribute("minOccurs", "0"), new XAttribute("maxOccurs", "1"), new XElement(NS + "complexType", new XElement(NS + "sequence", new XElement(NS + "element", new XAttribute("name", "Item"), new XAttribute("type", GetFieldXSDType(Field.FieldType.GetElementType())), new XAttribute("minOccurs", "0"), new XAttribute("maxOccurs", "unbounded") ) ) ) ); } return new XElement(NS + "element", new XAttribute("name", Field.Name), new XAttribute("type", GetFieldXSDType(Field.FieldType)), new XAttribute("minOccurs", "0"), new XAttribute("maxOccurs", "1")); } private static XElement CreateFieldXMLElement(FieldInfo Field) { var NS = XNamespace.Get("https://www.unrealengine.com/BuildConfiguration"); if (Field.FieldType.IsArray) { return new XElement(NS + Field.Name, GetArrayFieldXMLValue(Field)); } else { return new XElement(NS + Field.Name, GetFieldXMLValue(Field)); } } private static string WriteToBuffer(XmlDocument Document) { using (var Reader = new XmlNodeReader(Document)) { Reader.MoveToContent(); return WriteToBuffer(XDocument.Load(Reader)); } } private static string WriteToBuffer(XDocument Document) { var Settings = new XmlWriterSettings(); Settings.Indent = true; Settings.IndentChars = "\t"; Settings.CloseOutput = true; using (var MS = new MemoryStream()) { using (var SW = new StreamWriter(MS, Encoding.UTF8)) { using (var Writer = XmlWriter.Create(SW, Settings)) { Document.WriteTo(Writer); } } // Skipping first 3 bytes, that informs about encoding. return Encoding.UTF8.GetString(MS.ToArray().Skip(3).ToArray()); } } // Map to store class data in. private static readonly Dictionary Data = new Dictionary(); } }